Click here to Skip to main content
13,832,097 members
Click here to Skip to main content
Add your own
alternative version

Stats

86.9K views
2.8K downloads
100 bookmarked
Posted 18 Jul 2016
Licenced CPOL

MVVM for Beginners

, 20 Jul 2016
Rate this:
Please Sign up or sign in to vote.
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:

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

License

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

Share

About the Author

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

You may also be interested in...

Comments and Discussions

 
QuestionModel or ViewModel? Pin
sbarnes17-Oct-18 6:17
membersbarnes17-Oct-18 6:17 
AnswerRe: Model or ViewModel? Pin
sbarnes26-Oct-18 7:32
membersbarnes26-Oct-18 7:32 
GeneralMy vote of 5 Pin
sbarnes17-Oct-18 6:05
membersbarnes17-Oct-18 6:05 
PraiseThank you Pin
SteveHolle12-Oct-18 14:00
memberSteveHolle12-Oct-18 14:00 
QuestionMessage Removed Pin
5-Sep-17 19:37
memberyashwant mahajan5-Sep-17 19:37 
QuestionWhat did you mean with this? Pin
Ibn Sani Al-Wadiyyah14-May-17 21:43
memberIbn Sani Al-Wadiyyah14-May-17 21:43 
AnswerRe: What did you mean with this? Pin
Super Lloyd15-May-17 3:25
memberSuper Lloyd15-May-17 3:25 
GeneralRe: What did you mean with this? Pin
Ibn Sani Al-Wadiyyah15-May-17 6:28
memberIbn Sani Al-Wadiyyah15-May-17 6:28 
GeneralRe: What did you mean with this? Pin
Super Lloyd15-May-17 6:39
memberSuper Lloyd15-May-17 6:39 
PraiseRe: What did you mean with this? Pin
Ibn Sani Al-Wadiyyah15-May-17 8:35
memberIbn Sani Al-Wadiyyah15-May-17 8:35 
QuestionHELP URGENTLY WITH THE VARIABLE ADDEDNAMES Pin
Member 1305743514-Mar-17 1:55
memberMember 1305743514-Mar-17 1:55 
AnswerRe: HELP URGENTLY WITH THE VARIABLE ADDEDNAMES Pin
Super Lloyd14-Mar-17 5:33
memberSuper Lloyd14-Mar-17 5:33 
QuestionHELP URGENTLY WITH THE Pin
Member 1305743514-Mar-17 1:03
memberMember 1305743514-Mar-17 1:03 
PraiseFive Stars! Pin
Togr82b4gotn23-Aug-16 7:30
memberTogr82b4gotn23-Aug-16 7:30 
GeneralRe: Five Stars! Pin
Super Lloyd23-Aug-16 16:17
memberSuper Lloyd23-Aug-16 16:17 
GeneralComment Pin
kalsa22-Aug-16 20:35
memberkalsa22-Aug-16 20:35 
GeneralRe: Comment Pin
Super Lloyd22-Aug-16 20:41
memberSuper Lloyd22-Aug-16 20:41 
QuestionHow did this get best article Pin
Clifford Nelson22-Aug-16 12:50
mvaClifford Nelson22-Aug-16 12:50 
AnswerRe: How did this get best article Pin
Super Lloyd22-Aug-16 18:34
memberSuper Lloyd22-Aug-16 18:34 
AnswerRe: How did this get best article Pin
Clifford Nelson27-Aug-16 8:53
mvaClifford Nelson27-Aug-16 8:53 
GeneralRe: How did this get best article Pin
Super Lloyd28-Aug-16 18:17
memberSuper Lloyd28-Aug-16 18:17 
QuestionVote of 5 Pin
Kenneth Haugland30-Jul-16 18:53
professionalKenneth Haugland30-Jul-16 18:53 
AnswerRe: Vote of 5 Pin
Super Lloyd30-Jul-16 19:07
memberSuper Lloyd30-Jul-16 19:07 
GeneralRe: Vote of 5 Pin
Kenneth Haugland30-Jul-16 19:19
professionalKenneth Haugland30-Jul-16 19:19 
GeneralRe: Vote of 5 Pin
Super Lloyd30-Jul-16 19:34
memberSuper Lloyd30-Jul-16 19:34 

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.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01 | 2.8.190114.1 | Last Updated 20 Jul 2016
Article Copyright 2016 by Super Lloyd
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid