I'm using MVVM to bind a ComboBox to a ViewModel, and I have few question about heavy actions and selection change.
I want to trigger some actions when the selected item is changed, my initial approach was to put the logic in the setter of the field to which the selected item is binded.
So my first question is, is this good practice or there is a better approach?
Those actions may be very expensive in time and resources (need to retrieve data through a web service) and I don't want the UI to freeze, so lately I've started to send a message from the set which is received in the view's code-behind and that call a ViewModel command asynchronously.
Am I just wasting time or does this make any sense?
The problem is that when I'm debugging the UI sometimes freeze anyway (it doesn't happened on release). Reading here and there I've come to know that it may be debugger related, can anyone confirm this behavior on VS 2015?
Additional information
As requested I provide some examples. This is my first approach:
(XAML)
<ComboBox SelectedItem="{Binding SelectedField}"/>
(ViewModel)
public class ViewModel
{
private MyObject _selectedField = null;
public MyObject SelectedField
{
get
{
return _selectedField;
}
set
{
if(_selectedField != value)
{
// Expensive action
_selectedField = value;
RaisePropertyChanged(() => SelectedField);
}
}
}
}
The expensive action make some web service calls and may take long, is this design good or is there a better way to achieve this?
My second approach is through messages, as shown in this example:
(ViewModel)
public class ViewModel
{
private MyObject _selectedField = null;
public MyObject SelectedField
{
get
{
return _selectedField;
}
set
{
if(_selectedField != value)
{
Messenger.Default.Send(new DoStuffMessage());
_selectedField = value;
RaisePropertyChanged(() => SelectedField);
}
}
}
private RelayCommand _doStuffCommand = null;
public ICommand DoStuffCommand
{
get
{
if (_doStuffCommand == null)
_doStuffCommand = new RelayCommand(async () => await DoStuff());
return _doStuffCommand;
}
}
private async Task DoStuff()
{
// Expensive action
}
}
(Code-behind)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Messenger.Default.Register<DoStuffMessage>(this, DoStuffMessage_Handler);
}
private void DoStuffMessage_Handler(DoStuffMessage msg)
{
(DataContext as ViewModel).DoStuffCommand.Execute(null);
}
}
Is this approach better or is just bad and useless?
For MVVM, I prefer to use RelayCommands to bind an EventTrigger in XAML to an ICommand in the viewmodel. I feel this creates the best separation of code and is clearer than adding a lot of logic to my setters, where it might be overlooked during troubleshooting. Here is an overview of the process: https://msdn.microsoft.com/en-us/magazine/dn237302.aspx
This is to wire up a button and pass in a parameter, so obviously you would need to modify it for your use case, but it will show the basic technique. In XAML:
<Button Content="Click Me">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<Custom:EventToCommand Command="{Binding MyCommand}" CommandParameter="foo"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
In your VM:
public static ICommand MyCommand { get; set; } // declare an ICommand - bind to this!
public MainViewModel(IDataService dataService)
{
// associate your ICommand with a method. If you don't use a parameter, you don't need the lambda expression here.
MyCommand = new RelayCommand<string>((paramater) => MyCommandMethod(parameter));
}
public void MyCommandMethod(string parameter)
{
Debug.WriteLine("This is the code I want to run in my VM. The parameter is " + parameter);
}
I use the [free] MVVMLight toolkit for my applications, which was written by the guy who wrote the article that I linked to, but a lot of this is baked into .Net also. Using Expression Blend can make it easier to wire this stuff up when you are designing.
You can do whatever you like in setter as long as it is async.
private string _test;
public string Test
{
get { return _test; }
set
{
Task.Run(() =>
{
//do stuff
});
_test = value;
}
}
If you don't want to place logic in setter, because for example the Single Responsibility principle is violated, you should use interactions to catch the SelectionChange event and call a command in VM which should call an async method.
Here you have a sample that uses interactions : cute link
That's it!
Related
I am implementing a WPF application and I am switching view models on button click. I had to implement an navigation store by youtube tutorial. When I click a button, navigateCommand will execute, creating a new viewModel and notifying view to change. However I dont understand what is method OnCurrentViewModelChanged() doing and why is it needed, action CurrentViewModelChanged is returning void, and is empty? Or am I missing something? What is CurrentViewModelChanged doing? Can someone please explain?
public class NavigationStore
{
public event Action CurrentViewModelChanged;
private NotifyPropertyChanged currentViewModel;
public NotifyPropertyChanged CurrentViewModel
{
get => currentViewModel;
set
{
currentViewModel = value;
OnCurrentViewModelChanged();
}
}
private void OnCurrentViewModelChanged()
{
CurrentViewModelChanged?.Invoke();
}
}
public class NavigateCommand<TViewModel> : CommandBase where TViewModel : NotifyPropertyChanged
{
private readonly NavigationStore _navigationStore;
private readonly Func<TViewModel> _createViewModel;
public NavigateCommand(NavigationStore navigationStore, Func<TViewModel> createViewModel)
{
_navigationStore = navigationStore;
_createViewModel = createViewModel;
}
public override void Execute()
{
_navigationStore.CurrentViewModel = _createViewModel();
}
}
public class MainViewModel : NotifyPropertyChanged
{
private readonly NavigationStore _navigationStore;
public NotifyPropertyChanged CurrentViewModel => _navigationStore.CurrentViewModel;
public MainViewModel(NavigationStore navigationStore)
{
_navigationStore = navigationStore;
_navigationStore.CurrentViewModelChanged += OnCurrentViewModelChanged;
}
private void OnCurrentViewModelChanged()
{
OnPropertyChanged(nameof(CurrentViewModel));
}
}
So first of all, I also followed his tutorials (it's most likely SingletonSean's) and I don't share #BenicCode's opinion on that (tho I'm not a professional at WPF like he may be), I really like his explanations and solutions to problems. Besides, he keeps changing the project throughout the guide, implementing better solutions and explaining why it's better to use this than that.
The OnCurrentViewModelChanged() method raises an event so that any method that is subscribed to it will be invoked. However, you actually don't need it, you can implement NavigationStore like this:
NavigationStore.cs
public class NavigationStore : INavigationStore
{
private ViewModelBase? _currentViewModel;
public ViewModelBase? CurrentViewModel
{
get => _currentViewModel;
set
{
_currentViewModel?.Dispose();
_currentViewModel = value;
NavigationStateChanged?.Invoke();
}
}
public event Action? NavigationStateChanged;
}
And now, in your MainViewModel, you can simply subscribe the NavigationStateChanged action to OnCurrentViewModelChanged() instead of having one more method in your navigation store.
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
private readonly INavigationStore _navigationStore;
public ViewModelBase? CurrentViewModel => _navigationStore.CurrentViewModel;
public MainViewModel(INavigationStore navigationStore)
{
_navigationStore = navigationStore;
_navigationStore.NavigationStateChanged += OnNavigator_NavigationStateChanged;
}
private void OnNavigator_NavigationStateChanged()
{
OnPropertyChanged(nameof(CurrentViewModel));
}
}
It's basically the same, but a bit simpler (correct me if I'm wrong). By subscribing NavigationStateChanged to OnNavigator_NavigationStateChanged, whenever NavigationStateChanged is raised, OnNavigator_NavigationStateChanged will fire too, which will notify your UI to change (since you bind the ContentControl's Content property to the CurrentViewModel property).
MainWindow.xaml
<Grid>
<ContentControl Content="{Binding CurrentViewModel}" />
</Grid>
At this point of the tutorial he just wanted to demonstrate really basic navigation. As you progress further, things get cleaner and more complicated. I really suggest finishing his tutorials, there might be better guides, but as a starting point, I couldn't find any better channel.
This is probably the easiest thing in the world to do but I'm struggling a bit.
I have this in XAML:
<Button Name="browseButton" Content="Browse" Grid.Column="1" />
I've got everything binding correctly from the view to the viewmodel, like radio buttons and input text boxes etc... but what I want is to bind this button to a function, such that when the user clicks it some operation occurs.
But I'm really having a hard time figuring out how to bind clicking this button to a function in the viewmodel. I've played a bit with ICommand and didn't get very far, and I don't want to do the hack-ish thing of just sticking it in the code behind.
I'm using MVVMLight (Galasoft) if that helps.
Any guidance appreciated.
Edit
Following the example from https://msdn.microsoft.com/en-us/magazine/dn237302.aspx I have, but where does canExecuteMyCommand come into it? And how do I bind it in the XAML?
public RelayCommand BrowseCommand
{
get;
private set;
}
public LoadFilesViewModel()
{
BrowseCommand = new RelayCommand(executeBrowse, () => _canExecuteMyCommand);
}
private void executeBrowse()
{
// Do something
}
Solution
<Button Name="browseButton" Content="Browse" Grid.Column="1" Command="{Binding BrowseCommand}" />
And
public RelayCommand BrowseCommand
{
get;
private set;
}
public LoadFilesViewModel()
{
BrowseCommand = new RelayCommand(executeBrowse, () => true);
}
private void executeBrowse()
{
// Do something
}
If you look at the code you provided, the RelayCommand constructor comes with 2 parameters.
public RelayCommand BrowseCommand
{
get;
private set;
}
public LoadFilesViewModel()
{
BrowseCommand = new RelayCommand(executeBrowse, () => _canExecuteMyCommand);
}
private void executeBrowse()
{
// Do something
}
Checking the source code (that's the learning a code base and open-source makes this possible) or Visual Studio IntelliSense, you'll see this signature:
public RelayCommand(Action execute, Func<bool> canExecute)
So the first parameter is an action to be executed, and the second parameter is a check if it can execute. You've correctly identified the executeBrowse as a method to "do something". The _canExecuteMyCommand parameter is a class variable of the type bool that can be either true or false (set somewhere else).
In your own solution (posted in question), you replaced this by true (hardcoded). Note that you can also drop the second parameter in this case:
public LoadFilesViewModel()
{
BrowseCommand = new RelayCommand(executeBrowse); // will always execute
}
Bonus
Note that instead of using a local variable, you can also use a method to defined whether the method can execute (or write the check logic inline with the delegate syntax).
public LoadFilesViewModel()
{
BrowseCommand = new RelayCommand(ExecuteBrowse, CanExecuteBrowse);
}
private void ExecuteBrowse()
{
// Do something
}
private bool CanExecuteBrowse()
{
// Check if we are allowed to browser
return true; // or false :)
}
I have a DropDownView which contains a DropDownList as follows
<ComboBox Grid.Column="1" ItemsSource="{Binding Path=MyList}"
SelectedItem="{Binding Path=Item}" Height="30"/>
The DropDownViewModel has 2 properties as follows
private ObservableCollection<string> _myList;
public ObservableCollection<string> MyList {
get { return _myList; }
set {
if (_myList == value)
return;
_myList = value;
RaisePropertyChanged("MyList");
}
}
private string _item;
public string Item {
get { return _item; }
set {
if (_item == value)
return;
_item = value;
Messenger.Default.Send(_item); //line1
RaisePropertyChanged("Item");
}
}
Now, I have multiple ViewModels which creates multiple instances of this DropdownViewModel as follows. Each ViewModel belong to a separate View and are in no way interconnected. (For simplicity consider only 2 view models created.)
ViewModel1
public class ViewModel1
{
private readonly DropDownViewModel _ddVM1;
public ViewModel1(){
_ddVM1 = new DropDownViewModel();
Messenger.Default.Register<string>(this, this.GetItem1);
}
private void string GetItem1(string obj){
//perform some function
}
}
ViewModel2
public class ViewModel2
{
private readonly DropDownViewModel _ddVM2;
public ViewModel2(){
_ddVM2 = new DropDownViewModel();
Messenger.Default.Register<string>(this, this.GetItem2);
}
void string GetItem2(string obj){
//perform some function
}
}
Now when I run the application and select a value from DropDownList of any one View, always the first registered function is called(in this case GetItem1). I have tested the code with only one Messenger.Default.Register in only one ViewModel and the app runs fine. I have also tested the code whether multiple instances of DropDownView and DropDownViewModel are being created or not. There seems to be no issues in that area too.
I do not understand why such a scenario is occuring in case of more than one ViewModel as every ViewModel has its own DropDownView and DropDownViewModel instance. So what is exactly happening internally? Why is the app behaving in a weird manner on line1 when more than one instance of DropDownViewModel is created? How to solve this issue?
It looks to me that the issue is not WPF of MVVM related but is related with this code:
Messenger.Default.Send(_item); //line1
The issue is that Messenger.Default looks to me like a static class and this static default messenger does not change. Is it correct? I cannot debug on your machine, but this looks to me as a code smell.
As a side note, you should be able switch and change the view models with no issue. To solve your design issue is to add your messenger instance inside the view model so you don't have a shared global state.
"Why is the app freezing on line1 when more than one instance of DropDownViewModel is created?"
Very likely that is not related with WPF code but again with the Messenger.Default.Send method code.
For your binging code, you can write without: "Path" just:
<ComboBox Grid.Column="1" ItemsSource="{Binding MyList}"
SelectedItem="{Binding Item}" Height="30"/>
and it should work in all cases I am aware of.
I suppose this is MVVMLight you are using. The defining factor for the Messenger to know which recipients to send the message to, is TMessage, the type of the message:
public virtual void Register<TMessage>(object recipient, Action<TMessage> action)
So with your line
Messenger.Default.Register<string>(this, this.GetItem1);
the ViewModel will now receive any message of type string. Both methods (GetItem1 and GetItem2) are executed when you send a string type message.
The normal case is that you have a complex type for each Message, so you could use something like this, with DropDownId mirroring a unique identifier you attach to each DropDownViewModel.
public class DropDownSelectedItemMessage
{
public string DropDownId { get; set; }
public string SelectedItem { get; set; }
}
Then you'd register like this
Messenger.Default.Register<DropDownSelectedItemMessage>(this, this.GetItem1);
sending the message
Messenger.Default.Send(new DropDownSelectedItemMessage() { DropDownId = _id, SelectedItem = _item });
and in the message handler, you compare Ids:
private void string GetItem1(DropDownSelectedItemMessage message)
{
if (message.DropDownId == _ddVM1.Id)
{
//perform some function
}
}
However, my two cents: Forget about DropDownViewModel (keep both SelectedItem and ItemsSource properties on your actual ViewModel), completely refrain from using the Mediator pattern. Introduce a base class that implements and handles INotifyPropertyChanged so you can write properties on one line again. Here's what I use.
Lets say I have a MainWindow and a MainViewModel, I'm not using MVVM Light or Prism in this example.
In this MainWindow I want to click a MenuItem or Button to open a NewWindow.xaml not a UserControl.
I know how to use this with UserControl to open a new UserControl in my existing Window in a ContrntControl or a Frame.
<ContentControl Content="{Binding Path=DisplayUserControl,UpdateSourceTrigger=PropertyChanged}" />
Code
public ViewModelBase DisplayUserControl
{
get
{
if (displayUserControl == null)
{
displayUserControl = new ViewModels.UC1iewModel();
}
return displayUserControl;
}
set
{
if (displayUserControl == value)
{
return;
}
else
{
displayUserControl = value;
OnPropertyChanged("DisplayUserControl");
}
}
}
In the ResourceDitionary for MainWindow I have :
<DataTemplate DataType="{x:Type localViewModels:UC1ViewModel}">
<localViews:UC1 />
</DataTemplate>
<DataTemplate DataType="{x:Type localViewModels:UC2ViewModel}">
<localViews:UC2 />
</DataTemplate>
The thing is that I want to open a new Window, not a UserControl. So I use some code like this :
private ICommand openNewWindow;
public ICommand OpenNewWindow
{
get { return openNewWindow; }
}
public void DoOpenNewWindow()
{
View.NewWindowWindow validationWindow = new View.NewWindow();
NewWindowViewModel newWindowViewModel = new NewWindowViewModel();
newWindow.DataContext = ewWindowViewModel;
newWindow.Show();
}
and then a bind OpenNewWindow to a MenuItem or Button.
I know this is not the right way, but what is the right way to do this ?
Thanks!
There are two problems you need to solve with this type of application.
Firstly, you do not want to have the View-Model creating and displaying UI components directly. One of the motivations for using MVVM is to introduce test-ability in to your View-Model, and having this class pop up new windows makes this class harder to test.
The second problem you need to solve is how to resolve the dependencies in your application, or in this instance – how to you “hook up” the View-Model to the corresponding View? A maintainable solution to this latter problem is given by the use of a DI container. A very good reference to this subject is given by Mark Seemann’s Dependency Injection in .NET. He actually also discusses how to solve the first problem too!
To solve the former problem, you need to introduce a layer of indirection to your code, to make the View-Model not dependent on a concrete implementation of creating a new window. A very simple example is given in the code below:
public class ViewModel
{
private readonly IWindowFactory m_windowFactory;
private ICommand m_openNewWindow;
public ViewModel(IWindowFactory windowFactory)
{
m_windowFactory = windowFactory;
/**
* Would need to assign value to m_openNewWindow here, and associate the DoOpenWindow method
* to the execution of the command.
* */
m_openNewWindow = null;
}
public void DoOpenNewWindow()
{
m_windowFactory.CreateNewWindow();
}
public ICommand OpenNewWindow { get { return m_openNewWindow; } }
}
public interface IWindowFactory
{
void CreateNewWindow();
}
public class ProductionWindowFactory: IWindowFactory
{
#region Implementation of INewWindowFactory
public void CreateNewWindow()
{
NewWindow window = new NewWindow
{
DataContext = new NewWindowViewModel()
};
window.Show();
}
#endregion
}
Note that you take an implementation of IWindowFactory in the constructor of your View-Model, and it is to this object that the creation of the new window is delegated to. This allows you to substitute the production implementation for a different one during testing.
I have been trying to create a fairly simple application in WPF following the MVVM development pattern but I have been going crazy over how difficult it seems to be to do simple things. I have already created this app in Forms and had it successfully running, but my boss requested I rewrite the interface in WPF as a demo of the technology. I decided to try to follow as many best practices as I can in order to make the app and code as educational as possible. My current dilemma is using a listbox to run some code every time the selection changes. I'm ready to just use the code-behind with an event to call the method on the view-model. To me this seems to still be essentially MVVM since no logic is executing. Thanks for any help/insight.
You can do that simply binding selecteditem property of listbox... on selection change a setter in the view model will be called and you can do what ever you want...
Here is a sample which will help you
XAML
<Grid Canvas.Left="0" Canvas.Bottom="0" Height="300" Width="300" Background="Bisque">
<ListBox ItemsSource="{Binding Employes}" SelectedItem="{Binding SelectedEmploye}"/>
</Grid>
View Model
public class ViewModel : ViewModelBase
{
private List<Employee> _employes;
public List<Employee> Employes
{
get { return _employes; }
set { _employees = value; OnPropertyChanged("Employes"); }
}
private Employee _selectedEmploye;
public Employee SelectedEmploye
{
get { return _selectedEmploye; }
set
{
_selectedEmployee = value;
OnPropertyChanged("SelectedEmploye");
}
}
}
View model base
public class ViewModelBase : INotifyPropertyChanged
{
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Employee Class
public class Employee : ViewModelBase
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
In your ViewModel you can create a Property "SelectedItem". Bind then the SelectedItem-property if your ListBox to your property.
If it's a POCO clr-property (INotifyPropertyChanged), then you can trigger your code from the properties setter.
IF it's a DependencyProperty, you have to add a PropertyChangedCallback and trigger your code from there.
Don't be afraid to use code-behind. No code-behind is a guideline to avoid too much logic being placed in the view, not a hard and fast rule. In this case (as others have suggested) you can bind the SelectedItem property to some property on your viewmodel. With non-data-related events, my recommendation would be to handle the event as normal and delegate execution logic to the viewmodel.
In my opinion, design patterns should always be taken as rule of thumb and used with some judgement as it's quite easy to apply them too strictly in areas where they don't belong, which usually makes things worse.
Checkout the EventToCommand behavior in Galasoft MVVM Light
Here's the SO post
you can bind to ListBox.SelectedItem to get the selected item in your vm.