Introduction
This article is here to help people go from absolute beginner to normal beginner with MVVM.
Remark: This article assumes minimal familiarity with XAML and a UI library using XAML such as WPF. Just that you can read it is good enough. Why? Because before XAML, MVVM was not possible (without lots of extra work) as summarily explained below.
Remark(2): The code sample is using some C# 6 functionality (null
condition a?.b
, auto property initializer) and some .NET 4.5 one (CallerMemberAttribute
) hence one will need at least VS2015 to compile it. But hey, it's free!
Background
Today, there are many articles on MVVM. Unfortunately, they are often long and complex. This article attempts to be the simplest article you can find on that topic, as well as shows why you would even like to try.
So what is MVVM? MVVM is a technique to write UI that could be described as follows:
Short Explanation 1
M is for Model, this is your data. That is what people were using before MVVM. V is for view, that's what we are going to make easier to write. VM is for ViewModel
, i.e., Model for the View, aka, a model that you write specifically to make MVVM work. Plus, it might contain view specific property, like "selected item".
Short Explanation 2
It's a code technique where one describes the UI like a user would, there is a button here and it does that, there is a text box there and it's the user name... And one can write the UI pretty much like that.. and it all magically works!
So why are many MVVM articles so long? Well, it's because MVVM DOES NOT just work. One needs to use it with a UI library that enables it. WPF+XAML was the first such framework. Even so, as people get familiar with MVVM, there were many things which still didn't quite work. Most of the articles about MVVM are either large business samples or about filling the missing pieces. This article will NOT do that. It will show a very basic business app with the out of the box tools. But this is the reason why MVVM started at WPF.
Lastly, MVVM applies to developing "Data Views", such as "UserView
" or "SchoolList
", but is not good for low level control such as DatePicker
, or TextBox
.
MVVM Etymology
After some debate here on the CP forums, I want to add this etymology section. While it will have absolutely no impact on your understanding and implementation of MVVM as a GUI coding technique, it might smooth over some discussion that you might have on the topic.
MVVM is only a GUI coding technique which helps make GUI coding simpler and more efficient. But it was born as an enterprise development pattern. In such environment, it is common place to have many layers. For example, data exchange layer, business layer, etc.
When MVVM was born, with its many UI interfaces (most notably, INotifyPropertyChanged
, INotifyColllectionChanged
, ICommand
), it was apparent that a new layer was needed. It is the ViewModel
layer, that implements those interfaces.
The M in MVVM is an umbrella term for all those enterprise layers that are totally irrelevant to MVVM as a GUI coding technique. It is only there to be opposed by the VM, ViewModel
, which is what matters for MVVM, and for this article.
Let's Get Started
Start Visual Studio (VS2015 is a free download) and the menu File > New > Project > Windows > WPF Application.
Open MainWidow.xaml and add replace the <Grid>
tag with:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<DockPanel>
<TextBlock Text="Added Names"
DockPanel.Dock="Top" Margin="5,3"/>
<ListBox></ListBox>
</DockPanel>
<GridSplitter Grid.Column="1"
VerticalAlignment="Stretch" Width="5"
Background="Gray" HorizontalAlignment="Left" />
<Grid Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Name" Margin="5,3"/>
<TextBox Grid.Row="0" Grid.Column="1" Margin="5,3"/>
<TextBlock Grid.Row="1" Text="Your name is:" Margin="5,3"/>
<TextBlock Grid.Row="1" Grid.Column="1" Margin="5,3"/>
<Button Grid.Row="2" Grid.ColumnSpan="2" HorizontalAlignment="Left"
Content="Add Me" Margin="5,3" MinWidth="75" />
</Grid>
</Grid>
If you run the app (F5), you should see the following:
When one clicks the button or edits the text box, nothing happens! Let's fix that.
First ViewModel
Let's write a model (all in one Model - View Model really, it's fine to do so) that nicely represents the intent of this form.
public class Model
{
public string CurrentName { get; set; }
public List<string> AddedNames { get; } = new List<string>()
}
And now, we have to associate this model with the view. This is done through the DataContext property.
DataContext
is a special property that will flow through all elements of the visual tree. Set it on the MainWindow
, it will be available to all controls. Let's set it in the window constructor as follows:
public MainWindow()
{
InitializeComponent();
DataContext = new Model();
}
And let's update the text box and label to reflect the CurrentName
model property and the list box to be the AddedNames
property. Below is the new XAML for the view, with change in bold.
<TextBlock Text="Added Names"
DockPanel.Dock="Top" Margin="5,3"/>
<ListBox ItemsSource="{Binding AddedNames}">
<TextBlock Grid.Row="0" Text="Name" Margin="5,3"/>
<TextBox Grid.Row="0" Grid.Column="1"
Text="{Binding CurrentName,
UpdateSourceTrigger=PropertyChanged}" Margin="5,3"/>
<TextBlock Grid.Row="1"
Text="Your name is:" Margin="5,3"/>
<TextBlock Grid.Row="1" Grid.Column="1"
Text="{Binding CurrentName}" Margin="5,3"/>
We made usage of this Binding XAML Markup extension. MarkupExtension is a special way to provide value programmatically and must be written between curly brace {}
, Binding is central to MVVM functionality and is the magic sauce that synchronizes your UI with your model.
Run the app and... Nothing has changed!
The problem here is while the CurrentName
property does change, the UI doesn't know it does. Hence, doesn't update.
Notify Change Interfaces
Enter INotifyPropertyChanged and INotifyCollectionChanged interfaces. ViewModel
s should implement these interfaces to notify the UI they have changed.
With that in mind, here is the new code for our model:
public class Model : INotifyPropertyChanged
{
#region CurrentName
public string CurrentName
{
get { return mCurrentName; }
set
{
if (value == mCurrentName)
return;
mCurrentName = value;
OnPropertyChanged();
}
}
string mCurrentName;
#endregion
public ObservableCollection<string>
AddedNames { get; } = new ObservableCollection<string>();
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
If the app is run now, the label will update live as we change the value in the TextBox
! Those two controls are now "magically" synchronized through Binding. The binding will propagate change to and from the TextBox
to the model's property. And from the model's property to the TextBlock
any time it changes for any reason.
Pro Tip: Writing a model property that fires a PropertyChangedEvent
, even if only 8 lines long, can become quite tedious. Reduce your error rate by using snippets!
AddedNames
is now an ObservableCollection, an out of the box implementation of IList
and INotifyCollectionChanged
.
ICommand
Button
and other actionable items (such as MenuItem
) work through an interface named ICommand. One glaring omission of WPF is that it doesn't provide an out of the box, simple, model friendly ICommand
implementation. One could refer here to the seminal RelayCommand by Josh Smith... but since it's a very simple interface and I don't want to use any third party, we are just going to implement it inline.... Add this to the Model
class:
public Model()
{
AddCommand = new AddNameCommand(this);
}
class AddNameCommand : ICommand
{
Model parent;
public AddNameCommand(Model parent)
{
this.parent = parent;
parent.PropertyChanged += delegate { CanExecuteChanged?.Invoke(this, EventArgs.Empty); };
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) { return !string.IsNullOrEmpty(parent.CurrentName); }
public void Execute(object parameter)
{
parent.AddedNames.Add(parent.CurrentName); ;
parent.CurrentName = null;
}
}
public ICommand AddCommand { get; private set; }
Needless to say, this ICommand
could be generalized... I left it as an exercise to the reader as they say!
And now, let the button know about the command with that simple XAML change (in bold).
<Button Grid.Row="2" Grid.ColumnSpan="2"
HorizontalAlignment="Left" Content="Add Me"
Command="{Binding AddCommand}"/>
Run the app, test....
Woot... The Add button now automatically enables / disables if the text box is empty, add the name to the list and reset the text box!
Congratulations, you have now written an MVVM app! :)
Most of the procedural code was in the model. The UI was just declarative XAML that synchronizes with the model through Bindings. That is the essence of MVVM.
Advanced Topic: DataTemplate
Below, in that particular section, I won't post full code, I suggest you download the article code and look at the code there.
DataTemplate are XAML fragments can be used by other control to create part of their UI on demand. There is a lot to say about data templates and templates in general. What I want to briefly cover here is how it relates to ItemsControl.
ListBox
is an ItemsControl
. There are many ItemsControl
(TreeView
, MenuItem
, ListBox
, etc.). What they all share is that they display list of items. ItemsControl
has an ItemsSource
property which must be set to a (possibly observable) list of items, as in (the previous sample):
<ListBox ItemsSource="{Binding AddedNames}">
What if the model in the list is a little bit more complicated as, say:
(getter / setter code omitted, but just the same as CurrentName
above).
public class Person : INotifyPropertyChanged
{
public string FirstName { get; set; }
public string LastName { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
How will the ItemsControl
(in this case ListBox
) know how to display the Persons
?
By giving it a DataTemplate
that will generate the view for each item! For each of these XAML fragments, the DataContext
will be the item itself. One can then directly bind to the Person
's property.
<DataTemplate x:Key="PersonTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding LastName}" FontWeight="Bold" Margin="0,0,5,0"/>
<TextBlock Text="{Binding FirstName}"/>
</StackPanel>
</DataTemplate>
<ListBox ItemsSource="{Binding AddedPersons}"
ItemTemplate="{StaticResource PersonTemplate}">
</ListBox>
Giving us:
Voila!
You know enough to start using MVVM now. And make your overall UI code both more simple and more dynamic. Happy coding! :)
History
This is the first version. I don't expect any more...