In this basic introduction to MVVM, I will taking you through setting up an MVVM application and some simple binding to text and commands. This is intended for developers who know C# and Visual Studio, but an unfamiliar with MVVM.
Let’s jump straight in:
Create a new solution, selecting WPF application. The first thing to do to help enforce MVVM is create separate assemblies for Views and ViewModels. Also, a helpful assembly to add is one for Services. In this context, a service is usually a piece of business logic the ViewModels will use in order to perform their task. So, the first assembly to add to the solution will be Views, which should be added as a New Project with a type of WPF User Control Library. This will give us a reference to all the UI libraries we will need.
The next assembly to create will be the ViewModels. Just create this as a class library, as it shouldn’t contain any UI specific knowledge.
And finally, create a Services assembly, again, just a plain old class library assembly.
Let us now construct some sort of View for us to enter some information with a button that will add that information into a list to be displayed on the bottom in a grid. The Views assembly will have
UserControl1.xaml, let’s remove this and add in something more sensible. Let’s call it
InputForm
, for lack of inspiration. So, remove the UserControl1.xaml via the solution and then add new item and select User Control (WPF). Now rename that to
InputForm.xaml and select Add.
Now, inside the tags, add:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
This will just setup the grid to have three columns, all sizing to their content.
After this, enter the following:
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
This will tell the grid to have its first row to size to its contents and the rest of the grid should use the rest of the control’s space.
Add the following code:
<TextBlock Text="Name"/>
<TextBox Grid.Column="1″/>
<Button Grid.Column="2″>Add</Button>
This creates a basic view that has a label, followed by a text box and finally a button that will allow us to add the name.
Now, let us add a control to the second row of our grid. This will display a list of the added names. For this, we may as well just use a listbox.
<ListBox Grid.Row="1″ Grid.ColumnSpan="3″>
</ListBox>
This will add the ListBox
to the second row of the grid and let it span across the
three columns that we specified.
Ok, so we need to add this view to our MainWindow
in order for it to display. Add a reference to the Views assembly in the main application.
Now open the MainWindow.xaml and add in a namespace to reference our view's assembly.
xmlns:views="clr-namespace:Views;assembly=Views"
will do the trick. This says we are going to create a views namespace and that it comes from the namespace Views and assembly Views.
Now, in our XAML, we can reference our InputForm
inside the
Grid
tags like so:
<Grid>
<views:InputForm/>
</Grid>
In the designer, you should now see the InputForm
control show up. Ok, run it up and you should be able to type some text into the box next to name and press the button. Not very elegant, I know, but we are just demonstrating the MVVM part of this.
Now, we want to be able to capture that input and display it in the ListBox
below.
For this, we now need to move to the ViewModel's assembly and rename our Class1.cs to
CustomerViewModel.cs.
And give it a property ‘Name
’ which is of type string.
For now, just give make this an Auto
property with a get
and
set
:
public string Name
{
get;
set;
}
Back in our main application, we need to add a reference to our ViewModel's assembly.
Now, back in our MainWindow.xaml.cs we can set the DataContext
of the window to be a new one of these.
public MainWindow()
{
InitializeComponent();
this.DataContext = new CustomerViewModel();
}
This will set our MainWindow
’s DataContext
to an instance of
CustomerViewModel
. Because the DataContext
cascades down to its children, our
InputForm
will also have its DataContext
set to the instance of
CustomerViewModel
.
Back in our InputForm.xaml we can now bind the TextBox to our Property ‘Name’ in the CustomerViewModel:
<TextBox Grid.Column="1″ Text="{Binding Name}"/>
This means that the Name will be taken from what’s in the current DataContext
(which in this case is our
CustomerViewModel
instance).
Now, anything typed into our TextBox
will automatically be placed into our object’s Name property by the magic of binding.
Of course, you can’t see what’s in the ViewModel at this point. Ok, a little test to make sure it’s all working, lets place a
TextBlock
to the right of the Add button to display what’s currently in the ViewModel.
Our grid now looks like:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="Name"/>
<TextBox Grid.Column="1″ Text="{Binding Name}"/>
<Button Grid.Column="2″>Add</Button>
<TextBlock Grid.Column="3″ Text="{Binding Name}"/>
<ListBox Grid.Row="1″ Grid.ColumnSpan="4″>
</ListBox>
</Grid>
We’ve added in a 4th column to show the typed in text and added a new TextBlock
that will display in that column. This time the
TextBlock
’s Text
is bound to our Name
property.
Now when we run the application and type information into our name and we tab out of the box, the name is displayed to the right of the Add button, exactly as you typed it in.
Now we know our binding is working, we can get on with displaying our list of customers. At the moment, we have a ViewModel representing 1 customer. This won’t be any good. Let’s create a new ViewModel and call it
MainViewModel
.
This will have a view model for the new customer as well as a list of existing customers.
public class MainViewModel
{
public MainViewModel()
{
NewCustomer = new CustomerViewModel();
}
public CustomerViewModel NewCustomer
{
get;
private set;
}
}
This will expose our NewCustomer
as a read-only variable, as we don’t want any other developer’s overwriting it.
Our InputForm.xaml will then change its bindings to:
<TextBox Grid.Column="1″ Text="{Binding NewCustomer.Name}"/>
<TextBlock Grid.Column="3″ Text="{Binding NewCustomer.Name}"/>
Which says bind to our DataContext
’s NewCustomer
property and then its property Name.
In order for that to work, we must now change what the MainWindow
’s
DataContext
is set to:
this.DataContext = new MainViewModel();
Running that should react exactly as before, but we’ve now pushed the CustomerViewModel
down a layer.
We now need to modify the MainViewModel
to have a list of customers that have been added:
public class MainViewModel
{
private ObservableCollection customers = new ObservableCollection();
public MainViewModel()
{
NewCustomer = new CustomerViewModel();
}
public CustomerViewModel NewCustomer
{
get;
private set;
}
public ObservableCollectionCustomers
{
get { return this.customers; }
}
}
Here we have introduced a list of customers in an ObservableCollection
. This is a special type of collection that can be observed by the binding from
XAML.
When items are added, removed etc, notifications are sent out which WPF binding knows about and can keep things up to date on screen.
Now we need to be able to add customers to this list. This needs to be done when the user presses the Add button.
The nice thing about WPF buttons is they know about commands, more specifically commands that derive from
ICommand
. So on the MainVieWModel
, we need to add a command for adding customers.
Unfortunately, ICommand
resides in some WPF specific libraries and seeing we created this as a normal class library, their references aren’t there. We can easily remedy this and add the references to:
PresentationCore
PresentationFramework
Then add a new property to MainViewModel
for the add command:
public class MainViewModel
{
private ObservableCollection customers = new ObservableCollection();
public MainViewModel()
{
NewCustomer = new CustomerViewModel();
AddCustomerCommand = new DelegateCommand
public CustomerViewModel NewCustomer
{
get;
private set;
}
public ObservableCollectionCustomers
{
get { return this.customers; }
}
public ICommand AddCustomerCommand
{
get;
private set;
}
}
Here AddCustomerCommand
has been constructed as a new DelegateCommand
. This is a specialised command taken from Microsoft’s Prism Library.
The x=> represents what will happen when the command’s Execute
method is called. Again, the button will know about
ICommand
and its Execute
methods.
We now need to bind to the command from the InputForm.xaml:
<Button Grid.Column="2″ Command="{Binding AddCustomerCommand}">Add</Button>
This will execute our command, which will then add the customer to our Customers list. You can check this with a breakpoint inside the command function. We won’t see anything in the list yet, because we haven’t wired up the
XAML to bind to the Customers. Let’s do that now:
<ListBox Grid.Row="1″ Grid.ColumnSpan="4″ ItemsSource="{Binding Customers}">
</ListBox>
Run the application again and when you press add, it adds the customer to the list. But, what’s this? It’s showing
ViewModels.CustomerViewModel
which is the class name. This is because the
ListBox
’s default item template is being used and that doesn’t know what we want to display from this class.
Let’s rectify that:
<ListBox Grid.Row="1″ Grid.ColumnSpan="4″ ItemsSource="{Binding Customers}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
That’s better. We have now specified a DataTemplate
with which to display each item. Inside that, we have a
TextBlock
which is bound to the Name property.
This now correctly displays our name in the list. Pressing add now displays the correct information.
This has been a very basic introduction to MVVM using some basic ViewModels and Views. I’ll be expanding on this application with a series of articles getting
into MVVM and more complex binding, IValueConverter
s and Behaviors.
I hope you enjoyed this and please leave comments on anything not understood or things you would like expanded upon.