In the previous posts, which you can find here and here, we created a first, simple MVVM app. We already set up some basic databinding between our view, MainView, and viewmodel, MainViewModel, to show something in the ContentPresenter of the view. What we still need to do, however, is react to the button clicks of the buttons present in the MainView. In a normal, non-MVVM application, you would use eventhandlers for the button clicks. This is, however, something we will not do in an MVVM application, mostly because we want our viewmodels to be testable. Instead off an eventhandler with a sender object and eventargs, in MVVM, we create properties of the ICommand interface.
ICommand properties are properties which return a type that implements the ICommand interface. They are bindable to commands in a view, like the Command property of a Button. And second, they consist of an Execute and CanExecute method. The combination of these two gives a nice way of indicating which code needs to be executed and whether or not it can be executed. On the downside of ICommand properties is the fact that they can only be bound to commands in a view and not to events. This is why a lot of MVVM frameworks add an EventToCommand kind of type which makes it possible to bind ICommand properties to events. More on that in a later post.
For now, let's add a first ICommand property to the MainViewModel and bind it to the Command property of a button. Let's start with the ICommand for a new customer, the NewCustomerCommand.
public class MainViewModel { private ICommand _newCustomerCommand; public ICommand NewCustomerCommand { get { if (_newCustomerCommand == null) _newCustomerCommand = new NewCustomerCommand(); return _newCustomerCommand; } } }
The NewCustomerCommand is a class we still need to write, so lets add it to our solution. I prefer adding an extra Commands folder for these kinds of classes (not that we will be creating much of them, you will soon see why). Since the NewCustomerCommand needs to implement the ICommand interface it will initially look like this.
public class NewCustomerCommand : ICommand
{
public bool CanExecute(object parameter)
{
throw new NotImplementedException();
}
public void Execute(object parameter)
{
throw new NotImplementedException();
}
public event EventHandler CanExecuteChanged;
}
Since there are no restrictions on executing this command, we will have the CanExecute method always return true. The Execute method needs to open the NewCustomerView within the ContentPresenter of the MainView. We will again use the same technique of initializing a view and viewmodel and then binding the two together using the DataContext property of the view (didn't we do this three times allready? As any good (lazy) programmer, might we not think about making this something more generic??? Yes, we will, but not yet).
public class NewCustomerCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
var view = new NewCustomerView();
var viewmodel = new NewCustomerViewModel();
view.DataContext = viewmodel;
}
public event EventHandler CanExecuteChanged;
}
Now that we have this view, we need to assign it to the ViewToShow property of our MainViewModel. This is kind of hard, since the NewCustomerCommand and the MainViewModel don't know each other. We need to add a constructor to the NewCustomerCommand that takes the MainViewModel as a parameter and then uses it as a private datamember. Now we can assign the ViewToShow property.
public class NewCustomerCommand : ICommand
{
private MainViewModel _mainViewModel;
public NewCustomerCommand(MainViewModel mainViewModel)
{
_mainViewModel = mainViewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
var view = new NewCustomerView();
var viewmodel = new NewCustomerViewModel();
view.DataContext = viewmodel;
_mainViewModel.ViewToShow = view;
}
public event EventHandler CanExecuteChanged;
}
The call to this constructor in the MainViewModel needs to pass itself (this) now to the NewCustomerCommand.
public ICommand NewCustomerCommand
{
get
{
if (_newCustomerCommand == null)
_newCustomerCommand = new NewCustomerCommand(this);
return _newCustomerCommand;
}
}
The only thing left now, is add a binding to the MainView so the Command property of the NewCustomer button is bound to the NewCustomerCommand property.
<Button Content="New Customer" Margin="10" Command="{Binding NewCustomerCommand}" />
Again, add a TextBlock with some dummy text to your NewCustomerView to test this out. If you run the application you will find, however, the NewCustomer button does not seem to work. Clicking it, will not result in the other view being shown. If you place breakpoints in your code however, you will see that the Execute method of your NewCustomerCommand does get called.
What is actually wrong is the fact that the ViewToShow property gets a new value, but it does not announce this fact. For this you need the INotifyPropertyChanged interface. Implement it in your MainViewModel and change the ViewToShow property from an autoproperty to a property with a backing field and call the PropertyChanged event.
public class MainViewModel : INotifyPropertyChanged
{
private ICommand _newCustomerCommand;
private FrameworkElement _viewToShow;
public MainViewModel()
{
var view = new AllCustomersView();
var viewmodel = new AllCustomersViewModel();
view.DataContext = viewmodel;
ViewToShow = view;
}
public FrameworkElement ViewToShow
{
get { return _viewToShow; }
set
{
_viewToShow = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("ViewToShow"));
}
}
public ICommand NewCustomerCommand
{
get
{
if (_newCustomerCommand == null)
_newCustomerCommand = new NewCustomerCommand(this);
return _newCustomerCommand;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
If you now run the application, you can use the NewCustomer button to show the other view.
In a next post I will show you how to add some more framework-like capabilities. As in, how can you provide certain parts, so you don't need to rewrite masses of code every time you need to add something new (like a new command).
No comments:
Post a Comment