The view-model is the "VM" in MVVM. This is a class that acts as a go-between, exposes the model(s) to the user interface (view), and handling requests from the view, such as commands raised by button clicks. Here is a basic view-model:
public class CustomerEditViewModel
{
/// <summary>
/// The customer to edit.
/// </summary>
public Customer CustomerToEdit { get; set; }
/// <summary>
/// The "apply changes" command
/// </summary>
public ICommand ApplyChangesCommand { get; private set; }
/// <summary>
/// Constructor
/// </summary>
public CustomerEditViewModel()
{
CustomerToEdit = new Customer
{
Forename = "John",
Surname = "Smith"
};
ApplyChangesCommand = new RelayCommand(
o => ExecuteApplyChangesCommand(),
o => CustomerToEdit.IsValid);
}
/// <summary>
/// Executes the "apply changes" command.
/// </summary>
private void ExecuteApplyChangesCommand()
{
// E.g. save your customer to database
}
}
The constructor creates a Customer
model object and assigns it to the CustomerToEdit
property, so that it's visible to the view.
The constructor also creates a RelayCommand
object and assigns it to the ApplyChangesCommand
property, again making it visible to the view. WPF commands are used to handle requests from the view, such as button or menu item clicks.
The RelayCommand
takes two parameters - the first is the delegate that gets called when the command is executed (e.g. in response to a button click). The second parameter is a delegate that returns a boolean value indicating whether the command can execute; in this example it's wired up to the customer object's IsValid
property. When this returns false, it disables the button or menu item that is bound to this command (other controls may behave differently). This is a simple but effective feature, avoiding the need to write code to enable or disable controls based on different conditions.
If you do get this example up and running, try emptying out one of the TextBox
es (to place the Customer
model into an invalid state). When you tab away from the TextBox
you should find that the "Apply" button becomes disabled.
Remark on Customer Creation
The view-model doesn't implement INotifyPropertyChanged
(INPC). This means that if a different Customer
object was to be assigned to the CustomerToEdit
property then the view's controls wouldn't change to reflect the new object - the TextBox
es would still contain the forename and surname of the previous customer.
The example code works because the Customer
is created in the view-model's constructor, before it gets assigned to the view's DataContext
(at which point the bindings are wired up). In a real-world application you might be retrieving customers from a database in methods other than the constructor. To support this, the VM should implement INPC, and the CustomerToEdit
property should be changed to use the "extended" getter and setter pattern that you see in the example Model code, raising the PropertyChanged
event in the setter.
The view-model's ApplyChangesCommand
doesn't need to implement INPC as the command is very unlikely to change. You would need to implement this pattern if you were creating the command somewhere other than the constructor, for example some kind of Initialize()
method.
The general rule is: implement INPC if the property is bound to any view controls and the property's value is able to change anywhere other than in the constructor. You don't need to implement INPC if the property value is only ever assigned in the constructor (and you'll save yourself some typing in the process).