Creating view on receiving a message - c#

I have a MVVM view and viewmodel. In the constructor of my viewmodel I pass a list of IObservable messages and subscribe to them through a simple class sitting outide of my viewmodel and view
Outside class
{
viewModel =
new ViewModelClass(
responseHandler.AsObservable());
viewModel.PropertyChanged += ViewModel_PropertyChanged;
}
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ViewModelClass.MyProperty))
{
// Error here
view = new MyViewClass() { DataContext = viewModel };
}
}
In the view model constructor
subscription = receiveMessages.Subscribe(MessageReceived);
private void MessageReceived(GvsMessage message)
{
MyProperty = true;
}
On receiving a message I want to create my view not before that. Although the viewmodel is created before to handle property change etc
The problem is that I get "the calling thread must be sta because many ui components require this". Could someone please help

As we spoke in the comments you need to use a Dispatcher which can be acquired from different parts of the app.
To initialize the dispatcher you can use this snippet:
protected static Dispatcher _d;
if (Application.Current != null)
{
_d = Application.Current.Dispatcher;
}
else
{
_d = Dispatcher.CurrentDispatcher;
}
Explanation:
The first dispatcher is done when the application has UnitTests when you run the tests this dispatcher will not be null,
The second one is the current Dispatcher used by your application.
After you have this in your code when initializing your VM now you can send messages Actions, Events to the UI Thread.
I have a little method just for that:
public static void UIThread(Action action)
{
if (_d == null)
{
if (Application.Current != null)
{
_d = Application.Current.Dispatcher;
}
else
{
_d = Dispatcher.CurrentDispatcher;
}
}
_d.Invoke(action);
}
This function will accept lambda, like so:
UIThread(() =>
{
Processing = true;
Message = "Working ...";
//in your case you would raise the Loaded event here
});
This way you EventHandler in your view will have no problem showing that view.
If you need any more info let us know.
HTH

I resolved this by creating the view and the viewmodel in the constructor. In the propertychanged event I just set a property IsVisible to 'true' binds the window visibility
<Window.Visibility>
<Binding Path="IsVisible" Converter="{StaticResource BoolToVisibilityConverter}" Mode="TwoWay"/>
</Window.Visibility>

Related

How do I update ProgressBar from the Model in MVVM?

I have a ProgressBar in my MVVM View, which is bound to a View Model property. Updating the property in the VM all works correctly. However, I have some longer-running file/network operations which take place in another class (Model), and I would like to update the ProgressBar property in the middle of the Model operations. I can't pass the ProgressBar property via reference to the Model class. I definitely don't want to pass a handle to the VM to the Model. How do I update this VM property from the Model classes adData and fileOps?
Edit: Additional code to show where I need to update the ProgressBar property.
View
<ProgressBar Value="{Binding ProgressMeter}"/>
<TextBlock Text="{Binding CurrentStatusMsg}"/>
ViewModel
public class ViewModel : INotifyPropertyChanged
{
private readonly IADData adData;
private readonly IFileOps fileOps;
public ViewModel(IADData adData, IFileOps fileOps)
{
this.adData = adData;
this.fileOps = fileOps;
}
// INPC Implementation goes here
private int progressMeter;
public int ProgressMeter
{
get => progressMeter;
set
{
if (progressMeter != value)
{
progressMeter = value;
RaisePropertyChanged("ProgressMeter");
}
}
}
// Similar property for CurrentStatusMsg
public void DoIt()
{
BackgroundWorker bgWorker = new BackgroundWorker
{
WorkerReportsProgress = true
};
bgWorker.DoWork += CreatePhoneList;
bgWorker.RunWorkerCompleted += BgWorker_RunWorkerCompleted;
CurrentStatusMsg = "Creating Phone List...";
ProgressMeter = 5;
bgWorker.RunWorkerAsync();
}
private void CreatePhoneList(object sender, DoWorkEventArgs e)
{
// How do I update ProgressMeter in adData and fileOps classes?
DataTable t = adData.ReportLines();
fileOps.AddDeptRows(t);
e.Result = t;
}
private void BgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
ProgressMeter = 100;
CurrentStatusMsg = "Creating Phone List... Complete.";
reportCreator.ShowReport((DataTable)e.Result);
}
}
Thanks to #Adam Vincent, I ended up using the Messenger Pattern - specifically this implementation. I can now pass the ProgressBar value from the Model to the View, without breaking abstraction layers.

EventHandler is always null?

I want to create a event and subscribe is on another ViewModel. The event handler is always getting null on the first ViewModel.
In the first Viewmodel I declared Event and raised as follows
public event EventHandler EditSearchChanged;
and raised as
if (EditSearchChanged != null)
{
EditSearchChanged(this, null);
}
In the second Viewmodel,I have declared a property of first Viewmodel.
private EditTileViewModel editTileVM;
public EditTileViewModel EditTileVM
{
get
{
return editTileVM ?? (editTileVM = new EditTileViewModel());
}
set
{
editTileVM = value;
RaisePropertyChanged();
}
}
and subscribe the event as follows
EditTileVM.EditSearchChanged += EditTileVM_EditSearchChanged;
private void EditTileVM_EditSearchChanged(object sender, EventArgs e)
{
this.EditTileVM = (sender as EditTileViewModel);
}
Debugger Result
It happens as you create another new instance of ViewModel in the following property:
private EditTileViewModel editTileVM;
public EditTileViewModel EditTileVM
{
get
{
return editTileVM ?? (editTileVM = new EditTileViewModel());
}
set
{
editTileVM = value;
RaisePropertyChanged();
}
}
So there are two instances of EditViewModel.
I suggest you to use EventAggregator pattern between two viewModels from Prism framework:
// Subscribe
eventAggregator.GetEvent<CloseAppliactionMessage>().Subscribe(ExitMethod);
// Broadcast
eventAggregator.GetEvent<CloseAppliactionMessage>().Publish();
Please, see a very good tutorial of Rachel Lim about simplified Event Aggregator pattern.
Or use MVVM Light messenger:
//Subscribe
Messenger.Default.Register<CloseAppliactionMessage>(ExitMethod);
// Broadcast
Messenger.Default.Send<CloseAppliactionMessage

mvvm light -Sending Notification message with callback

I need the result of FolderBrowserDialog in my view-model,
CodeBehind.cs
private static void SelectFolderDialog()
{
using (System.Windows.Forms.FolderBrowserDialog folderdialg = new System.Windows.Forms.FolderBrowserDialog())
{
folderdialg.ShowNewFolderButton = false;
folderdialg.RootFolder = Environment.SpecialFolder.MyComputer;
folderdialg.Description = "Load Images for the Game";
folderdialg.ShowDialog();
if (folderdialg.SelectedPath != null)
{
var notifypath = new GenericMessage<string>(folderdialg.SelectedPath);
Messenger.Default.Send(notifypath);
}
}
What i'm planning is , From View-model send a notification with callback to view , executing the FolderBrowserDialog return the Selected path back to the view model.
How do i send notificationmessage with callback / NotificationWithAction using MVVM-Light . please help me with a sample as I'm new to Wpf and MVVM-Light.
Any Help is appreciated
I was looking for almost the exact same thing except for with a SaveFileDialog. Here is what I came up with:
Create a message class with an Action<string> property and a constructor with an Action<string> argument.
public class SelectFolderMessage
{
public Action<string> CallBack {get;set;}
public SelectFolderMessage(Action<string> callback)
{
CallBack = callback;
}
}
In your ViewModel class, pass in a method or lambda expression when you call Messenger.Default.Send. I set a property in my ViewModel class with the path returned by the view. I wrapped this inside the execute section of a RelayCommand. I bound the RelayCommand to a button in the view
...
new RelayCommand(() =>
{
Messenger.Default.Send(new SelectFolderMessage(
(pathfromview) => { viewmodelproperty = pathfromview;}));
})
In your view code behind, create a method to handle the message and register the handler with the messenger service. Don't forget to unregister if this is not your main window.
public MainWindow()
{
Messenger.Default.Register<SelectFolderMessage>(this, SelectFolderHandler);
}
private void SelectFolderHandler(SelectFolderMessage msg)
{
using (System.Windows.Forms.FolderBrowserDialog folderdialg = new System.Windows.Forms.FolderBrowserDialog())
{
folderdialg.ShowNewFolderButton = false;
folderdialg.RootFolder = Environment.SpecialFolder.MyComputer;
folderdialg.Description = "Load Images for the Game";
folderdialg.ShowDialog();
if (folderdialg.SelectedPath != null)
{
msg.CallBack(folderdialg.SelectedPath);
}
}
}
I came up with this idea reading Laurent Bugnion's Messenger article in MSDN Magazine: http://msdn.microsoft.com/en-us/magazine/jj694937.aspx

How to bring window to front with wpf and using mvvm

I have a window that essentially runs a timer. When the timer hits 0 I want to bring the window to the front so that it is visible and not hidden behind some other application.
From what I can gather I would simply call window.activate() to accomplish this but with mvvm my view model doesn't have a reference to window.
A "purist" MVVM solution is to use a behavior. Below is a behavior for a Window with an Activated property. Setting the property to true will activate the window (and restore it if it is minimized):
public class ActivateBehavior : Behavior<Window> {
Boolean isActivated;
public static readonly DependencyProperty ActivatedProperty =
DependencyProperty.Register(
"Activated",
typeof(Boolean),
typeof(ActivateBehavior),
new PropertyMetadata(OnActivatedChanged)
);
public Boolean Activated {
get { return (Boolean) GetValue(ActivatedProperty); }
set { SetValue(ActivatedProperty, value); }
}
static void OnActivatedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) {
var behavior = (ActivateBehavior) dependencyObject;
if (!behavior.Activated || behavior.isActivated)
return;
// The Activated property is set to true but the Activated event (tracked by the
// isActivated field) hasn't been fired. Go ahead and activate the window.
if (behavior.AssociatedObject.WindowState == WindowState.Minimized)
behavior.AssociatedObject.WindowState = WindowState.Normal;
behavior.AssociatedObject.Activate();
}
protected override void OnAttached() {
AssociatedObject.Activated += OnActivated;
AssociatedObject.Deactivated += OnDeactivated;
}
protected override void OnDetaching() {
AssociatedObject.Activated -= OnActivated;
AssociatedObject.Deactivated -= OnDeactivated;
}
void OnActivated(Object sender, EventArgs eventArgs) {
this.isActivated = true;
Activated = true;
}
void OnDeactivated(Object sender, EventArgs eventArgs) {
this.isActivated = false;
Activated = false;
}
}
The behavior requires a reference to System.Windows.Interactivity.dll. Fortunately, this is now available on NuGet in the Blend.Interactivity.Wpf package.
The behavior is attached to a Window in XAML like this:
<Window ...>
<i:Interaction.Behaviors>
<Behaviors:ActivateBehavior Activated="{Binding Activated, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
The view-model should expose a boolean Activated property. Setting this property to true will activate the window (unless it is already activated). As an added bonus it will also restore a minimized window.
You could go about it in a couple of ways - adding a reference to the window could work since the viewmodel is not coupled with the view but related to it, but I don't really like that approach since it pretty much does couple your view to your viewmodel - which is not really the point of MVVM
A better approach may be to have your viewmodel raise an event or a command which the view can handle. This way the view gets to decide what UI action is associated with the command/event
e.g. simply
class SomeView
{
void HandleSomeCommandOrEvent()
{
this.Activate();
}
}
Of course how you wire this up is up to you but I'd probably try and get routed commands happening
Edit: You can't really 'bind' a simple event, since it's invoked from the viewmodel.
A simple event based example is just to add the event to the viewmodel and handle it directly ... e.g. imagine the following MainWindow with a ViewModel property
public partial class MainWindow : Window
{
MainWindowViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new MainWindowViewModel();
ViewModel.ShowMessage += ViewModel_ShowMessage;
this.DataContext = ViewModel;
}
void ViewModel_ShowMessage(object sender, ShowMessageEventArgs e)
{
MessageBox.Show(e.Message, "Some caption", MessageBoxButton.OK);
}
}
Then the ViewModel can just fire the event:
// The view model
public class MainWindowViewModel
{
// The button click command
public RelayCommand ButtonClickCommand { get; set; }
// The event to fire
public event EventHandler<ShowMessageEventArgs> ShowMessage;
public MainWindowViewModel()
{
ButtonClickCommand = new RelayCommand(ButtonClicked);
}
void ButtonClicked(object param)
{
// This button is wired up in the view as normal and fires the event
OnShowMessage("You clicked the button");
}
// Fire the event - it's up to the view to decide how to implement this event and show a message
void OnShowMessage(string message)
{
if (ShowMessage != null) ShowMessage(this, new ShowMessageEventArgs(message));
}
}
public class ShowMessageEventArgs : EventArgs
{
public string Message { get; private set; }
public ShowMessageEventArgs(string message)
{
Message = message;
}
}
The XAML would be:
<Button Command="{Binding ButtonClickCommand}">Click me!</Button>
So the button invokes the command, which in turn fires the event which the view (MainWindow) handles and shows a messagebox. This way the view/UI decides on the course of action based on the type of event raised. Of course it could be your timer which fired the event
You can always go down the more involved route such as some of the answers on this question...
How should the ViewModel close the form?
but to be honest, it depends if you really need it - a simple event works well - some people overcomplicate things for the sake of elegance, but at the detriment of simplicity and productivity!
I would go this way:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
// View
public partial class TestActivateWindow : Window
{
public TestActivateWindow() {
InitializeComponent();
Messenger.Default.Register<ActivateWindowMsg>(this, (msg) => Activate());
}
}
// View Model
public class MainViewModel: ViewModelBase
{
ICommand _activateChildWindowCommand;
public ICommand ActivateChildWindowCommand {
get {
return _activateChildWindowCommand?? (_activateChildWindowCommand = new RelayCommand(() => {
Messenger.Default.Send(new ActivateWindowMsg());
}));
}
}
}
public class ActivateWindowMsg
{
}

Windows Phone data binding: cross-thread

we have a problem with data binding on windows phone (using xaml). i have created a simple example, which should allow to reproduce the problem.
Here is our model-class:
public class Data : INotifyPropertyChanged
{
private int value = 0;
public int Value
{
get
{
return value;
}
set
{
this.value = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public Data()
{
var t = new Thread(new ThreadStart(() =>
{
while (true)
{
Thread.Sleep(2000);
Value += 1;
}
}));
t.IsBackground = true;
t.Start();
}
}
which uses a thread to update the value-property and fire the PropertyChanged-event.
Now i want to bind this value-property to a gui control:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<TextBlock Text="{Binding Path=Value}" />
</Grid>
public MainPage()
{
InitializeComponent();
DataContext = new Data();
}
when the value first changes (and the PropertyChanged-event gets fired) the data binding system tries to copy the value of Data.Value to TextBlock.Text, which results in an invalid cross-thread exception, as this event is not fired on the ui thread.
my question: shouldn't the .NET databinding framework recognize that i'm binding to a ui control and perform the thread switching itself? i know that i can simply use a dispatcher to fire the PropertyChanged-event on the main thread, but i'd like to have my model-class more seperated from the gui component.
is there a better solution to this problem? i am unable to use the DependencyObject approach, because our core project (which contains the model class) should run on Windows Phone AND Android, and Android doesn't support the System.Windows-namespace.
One way to solve this would be to store a reference to the dispatcher on your view model and only use it to execute the property changed event if it is not null. Then you can set the dispatcher property in your VM's constructor.
I do like this:
public event PropertyChangedEventHandler PropertyChanged;
private void PropertyEventChanged(string propertyName)
{
if (PropertyChanged == null) return;
if (Application.OpenForms.Count > 0 && Application.OpenForms[0].InvokeRequired)
Application.OpenForms[0].Invoke(new MethodInvoker(() => PropertyChanged(this, new PropertyChangedEventArgs(propertyName))));
else
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

Categories

Resources