Click here to Skip to main content
15,867,330 members
Articles / Desktop Programming / XAML

MVVM for Beginners

Rate me:
Please Sign up or sign in to vote.
4.97/5 (71 votes)
19 Jul 2016CPOL7 min read 176.5K   4.6K   108   88
Introduction to MVVM for absolute beginners

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:

XML
<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:

Image 1

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.

C#
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:

C#
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.

XML
            <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. ViewModels should implement these interfaces to notify the UI they have changed.

With that in mind, here is the new code for our model:

C#
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:

C#
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).

XML
<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!

Image 2

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):

XML
<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).

C#
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.

XML
        <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:

Image 3

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...

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) http://www.ansibleww.com.au
Australia Australia
The Australia born French man who went back to Australia later in life...
Finally got over life long (and mostly hopeless usually, yay!) chronic sicknesses.
Worked in Sydney, Brisbane, Darwin, Billinudgel, Darwin and Melbourne.

Comments and Discussions

 
SuggestionCan you create an alternative, please? Pin
Mr.PoorEnglish19-Jul-16 21:46
Mr.PoorEnglish19-Jul-16 21:46 
AnswerRe: Can you create an alternative, please? Pin
Super Lloyd19-Jul-16 21:56
Super Lloyd19-Jul-16 21:56 
GeneralRe: Can you create an alternative, please? Pin
Mr.PoorEnglish20-Jul-16 2:44
Mr.PoorEnglish20-Jul-16 2:44 
GeneralRe: Can you create an alternative, please? Pin
Super Lloyd20-Jul-16 5:26
Super Lloyd20-Jul-16 5:26 
GeneralRe: Can you create an alternative, please? Pin
Pete O'Hanlon17-Aug-16 23:10
subeditorPete O'Hanlon17-Aug-16 23:10 
GeneralRe: Can you create an alternative, please? Pin
Super Lloyd18-Aug-16 4:53
Super Lloyd18-Aug-16 4:53 
GeneralRe: Can you create an alternative, please? Pin
Pete O'Hanlon18-Aug-16 5:10
subeditorPete O'Hanlon18-Aug-16 5:10 
GeneralRe: Can you create an alternative, please? Pin
Super Lloyd18-Aug-16 6:39
Super Lloyd18-Aug-16 6:39 
GeneralRe: My vote of 1 Pin
Super Lloyd19-Jul-16 22:45
Super Lloyd19-Jul-16 22:45 
QuestionFix margins Pin
Philippe Mori18-Jul-16 3:19
Philippe Mori18-Jul-16 3:19 
AnswerRe: Fix margins Pin
Super Lloyd18-Jul-16 5:41
Super Lloyd18-Jul-16 5:41 
QuestionUnfortunately ... Pin
Richard MacCutchan18-Jul-16 3:04
mveRichard MacCutchan18-Jul-16 3:04 
AnswerRe: Unfortunately ... Pin
Super Lloyd18-Jul-16 3:09
Super Lloyd18-Jul-16 3:09 
GeneralRe: Unfortunately ... Pin
Richard MacCutchan18-Jul-16 3:32
mveRichard MacCutchan18-Jul-16 3:32 
GeneralRe: Unfortunately ... Pin
Super Lloyd18-Jul-16 6:31
Super Lloyd18-Jul-16 6:31 
BugRe: Unfortunately ... Pin
Philippe Mori18-Jul-16 3:14
Philippe Mori18-Jul-16 3:14 
GeneralRe: Unfortunately ... Pin
Richard MacCutchan18-Jul-16 3:34
mveRichard MacCutchan18-Jul-16 3:34 
GeneralRe: Unfortunately ... Pin
Master.Man198018-Jul-16 3:50
Master.Man198018-Jul-16 3:50 
GeneralRe: Unfortunately ... Pin
Richard MacCutchan18-Jul-16 3:53
mveRichard MacCutchan18-Jul-16 3:53 
GeneralRe: Unfortunately ... Pin
Master.Man198018-Jul-16 4:03
Master.Man198018-Jul-16 4:03 
GeneralRe: Unfortunately ... Pin
Richard MacCutchan18-Jul-16 4:18
mveRichard MacCutchan18-Jul-16 4:18 
GeneralRe: Unfortunately ... Pin
Master.Man198018-Jul-16 4:40
Master.Man198018-Jul-16 4:40 
GeneralRe: Unfortunately ... Pin
Super Lloyd18-Jul-16 6:37
Super Lloyd18-Jul-16 6:37 
GeneralRe: Unfortunately ... Pin
Richard MacCutchan18-Jul-16 6:53
mveRichard MacCutchan18-Jul-16 6:53 
GeneralRe: Unfortunately ... Pin
Super Lloyd18-Jul-16 7:11
Super Lloyd18-Jul-16 7:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.