Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια

Dot Net Rules

Yes, to dance beneath the diamond sky with one hand waving free

Ιστορικό Δημοσιεύσεων

An introduction to MVVM pattern using WPF

In this post I will show you how to build a simple WPF Application using the popular and well know MVVM (Model View View Model) pattern.

I assume that you know the basics of XAML,WPF. I am going to use a hands-one example using Visual Studio and 2010.

Before we go on and begin with our example I must say a few things about MVVM.Make a note that we can use this pattern when building Silverlight applications as well.

So what is MVVM? In a nutshell it is a UI Design Pattern. You do not have to explore too many things or spend too much time studying this pattern before start using it.

A pattern is a documented solution to a specific software problem. So MVVM being a pattern,  is a set of guidelines that makes it much easier to write WPF applications.

It is based on a very important software principle called seperation of concerns.By building WPF applications using MVMV we have solid maintainable code.

By breaking apart our application, enables us to do UI testing.

On top of that we have applications that ( that are built with MVVM ) are much more maintenable and extensible. We keep the designers and the developers happy!!!! Each of them can focus on their tasks and noone interferes with each other's work.

Let's have a closer look on the main components of MVVM pattern. Have a look at the picture below.It gives a high level overview of the pattern.

MVVM PatternMVVM Pattern

The Model has no reference to the View or the ViewModel . It does not or care about the existence of the View or the Model.

The Model is that data. It could be an xml file or a data access layer (e.g EF). It could be a custom object(s) representing a part of our domain.

The View does not have a reference to the Model. A View is defined in XAML and should not have any logic in the code-behind. It binds to the ViewModel by only using data binding.
The ViewModel can be thought of as an abstraction of the View. It provides enough information about the Model data points that the View can use for data-binding. It contains Commands the View can use to interact with the Model. The ViewModel has a reference to the Model.

Let's start with our hands on example.I will create a simple WPF application

1) Launch Visual Studio 2010 (any edition will do). Choose C# as the development language and WPF Application from the available templates.

2) Choose a suitable name for your application. I have named it "WpfMVVM"

3) Add 3 folders in your solution with the names View,Model,ViewModel.

4) I will use a custom object to store the data so I will add a new class to the Model folder. I will name it Footballer.cs ( yes, we will save and display footballer names ). I will add some properties on this class like  firstname,lastname,age,height,weight and another little property called SavedTime so I know when the data was last updated. So the first version of the Footballer class follows.

[sourcecode language="csharp"]
class Footballer
{

private string _firstname;

public string FirstName
{
get { return _firstname; }
set
{
_firstname = value;

}
}

private string _lastname;

public string LastName
{
get { return _lastname; }
set
{

_lastname = value;

}
}

private int _age;

public int Age
{
get { return _age; }
set
{
_age = value;

}
}

private double _height;

public double Height
{
get { return _height; }
set
{
_height = value;

}
}

private double _weight;

public double Weight
{
get { return _weight; }
set
{
_weight = value;

}
}

private DateTime _SavedTime;

public DateTime SavedTime
{
get { return _SavedTime; }
set
{
_SavedTime = value;

}
}

}
[/sourcecode]

Then I need to notify my UI (View) that some of these properties changed. I do that by implementing the INotifyPropertyChanged interface.

Now the View is notified when there changes in the property values,since when that happens notifications are fired. This allows the View to display any changes that have been made in the object's underlying properties. The second version of the Footballer class follows

[sourcecode language="csharp"]

class Footballer:INotifyPropertyChanged
{

private string _firstname;

public string FirstName
{
get { return _firstname; }
set
{
_firstname = value;
OnPropertyChanged("FirstName");

}
}

private string _lastname;

public string LastName
{
get { return _lastname; }
set
{

_lastname = value;
OnPropertyChanged("LastName");
}
}

private int _age;

public int Age
{
get { return _age; }
set
{
_age = value;
OnPropertyChanged("Age");
}
}

private double _height;

public double Height
{
get { return _height; }
set
{
_height = value;
OnPropertyChanged("Height");
}
}

private double _weight;

public double Weight
{
get { return _weight; }
set
{
_weight = value;
OnPropertyChanged("Weight");
}
}

private DateTime _SavedTime;

public DateTime SavedTime
{
get { return _SavedTime; }
set
{
_SavedTime = value;
OnPropertyChanged("SavedTime");
}
}

public event PropertyChangedEventHandler PropertyChanged;

public void OnPropertyChanged(string property)
{
if (PropertyChanged !=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}

}

}
[/sourcecode]

If I want to implement data validation logic, the Model class is a good place to do it.In this case I need to implement the IDataErrorInfo interface.In this example I will add some validation for the fields "FirstName", "Lastname". These fields cannot be empty.

The third version of the Footballer class follows

[sourcecode language="csharp"]

class Footballer:INotifyPropertyChanged,IDataErrorInfo
{

private string _firstname;

public string FirstName
{
get { return _firstname; }
set
{
_firstname = value;
OnPropertyChanged("FirstName");

}
}

private string _lastname;

public string LastName
{
get { return _lastname; }
set
{

_lastname = value;
OnPropertyChanged("LastName");
}
}

private int _age;

public int Age
{
get { return _age; }
set
{
_age = value;
OnPropertyChanged("Age");
}
}

private double _height;

public double Height
{
get { return _height; }
set
{
_height = value;
OnPropertyChanged("Height");
}
}

private double _weight;

public double Weight
{
get { return _weight; }
set
{
_weight = value;
OnPropertyChanged("Weight");
}
}

private DateTime _SavedTime;

public DateTime SavedTime
{
get { return _SavedTime; }
set
{
_SavedTime = value;
OnPropertyChanged("SavedTime");
}
}

public event PropertyChangedEventHandler PropertyChanged;

public void OnPropertyChanged(string property)
{
if (PropertyChanged !=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}

}

public string Error
{
get { return null; }
}

public string this[string columnName]
{
get

{
string theerror = string.Empty;

if ((string.IsNullOrEmpty (_firstname)))
{
theerror = "This field is required";
}

else if ((string.IsNullOrEmpty(_lastname)))
{
theerror = "This field is required";
}
return theerror;
}
}

}
[/sourcecode]

We will have to make some changes to the XAML (Set the ValidatesOnDataErrors=True for firstname and lastname elements) for this to work but I will show you later.

Νow let's move to the ViewModel implementation. This model must have properties that expose instances of the Model objects.

So in this case we must have a property in the ViewModel class that exposes the Footballer class that lives in the Model.

Add a new item to your project, a class file, name it FootballerViewModel.cs

The first thing I need to do in my new class is to implement the INotifyPropertyChanged interface.This is because the View is bound to the ViewModel. INotifyPropertyChanged interface is the way to push data to theView.

The first version of the ViewModel class follows

[sourcecode language="csharp"]
class FootballerViewModel:INotifyPropertyChanged
{

public event PropertyChangedEventHandler PropertyChanged;

public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

}
}
[/sourcecode]

Let's create a property that  is an instance of the Model object .The second version of the ViewModelclass follows

[sourcecode language="csharp"]
class FootballerViewModel:INotifyPropertyChanged
{

private Footballer _myfootballer;

public Footballer MyFootballer
{
get { return _myfootballer; }
set

{

_myfootballer = value;
OnPropertyChanged("MyFootballer");

}
}

public event PropertyChangedEventHandler PropertyChanged;

public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

}
}
[/sourcecode]

Now I will use the constructor for the ViewModel class and populate it with some random data.

The third version of the ViewModel follows

[sourcecode language="csharp"]

class FootballerViewModel:INotifyPropertyChanged
{

public FootballerViewModel()
{
LoadFootballers();
}

private void LoadFootballers()
{
MyFootballer  = new Footballer()
{
FirstName = "Steven",
LastName = "Gerrard",
Age = 31,
Weight = 88.6,
Height = 1.84

};
}

private Footballer _myfootballer;

public Footballer MyFootballer
{
get { return _myfootballer; }
set

{

_myfootballer = value;
OnPropertyChanged("MyFootballer");

}
}

public event PropertyChangedEventHandler PropertyChanged;

public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

}
}

[/sourcecode]

Now, let's create the View. The View will be purely XAML. It consists of the visual elements like buttons,textboxes. It is not responsible for retrieving data,implement business logic or validation logic.

First things first. Move the MainWindow.xaml file to the View folder.

Go to the App.xaml file and change the StartupUri attribute of the Application element to StartupUri="View/MainWindow.xaml"

[sourcecode language="xml"]

<Window x:Class="WpfMVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" >

<Grid>

<Grid.RowDefinitions>
<RowDefinition Height="46*" />
<RowDefinition Height="46*" />
<RowDefinition Height="46*" />
<RowDefinition Height="46*" />
<RowDefinition Height="46*" />
<RowDefinition Height="46*" />
<RowDefinition Height="46*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="121*" />
<ColumnDefinition Width="382*" />
</Grid.ColumnDefinitions>
<TextBlock Height="23" HorizontalAlignment="Left" Margin="10,12,0,0"
Name="textBlock1" Text="First Name:" VerticalAlignment="Top"
Width="82" FontSize="16" />
<TextBox Grid.Column="1" Height="23" HorizontalAlignment="Left"
Margin="45,12,0,0" Name="textBox1" VerticalAlignment="Top"
Width="266" />
<TextBlock Grid.Row="1" Height="23" HorizontalAlignment="Left"
Margin="12,8,0,0" Name="textBlock2" Text="Last Name:"
VerticalAlignment="Top" Width="82" FontSize="16" />
<TextBlock Grid.Row="2" Height="23" HorizontalAlignment="Left"
Margin="12,9,0,0" Name="textBlock3" Text="Age:"
VerticalAlignment="Top" FontSize="16" />
<TextBlock Grid.Row="3" Height="23" HorizontalAlignment="Left"
Margin="10,12,0,0" Name="textBlock4" Text="Height:"
VerticalAlignment="Top" FontSize="16" />
<TextBlock Grid.Row="4" Height="23" HorizontalAlignment="Left"
Margin="10,9,0,0" Name="textBlock5" Text="Weight:"
VerticalAlignment="Top" FontSize="16" />
<TextBox Grid.Column="1" Grid.Row="1" Height="23"
HorizontalAlignment="Left" Margin="45,9,0,0" Name="textBox2"
VerticalAlignment="Top" Width="266"  />
<TextBox Grid.Column="1" Grid.Row="2" Height="23"
HorizontalAlignment="Left" Margin="45,9,0,0" Name="textBox3"
VerticalAlignment="Top" Width="266"  />
<TextBox Grid.Column="1" Grid.Row="3" Height="23"
HorizontalAlignment="Left" Margin="45,12,0,0" Name="textBox4"
VerticalAlignment="Top" Width="266"  />
<TextBox Grid.Column="1" Grid.Row="4" Height="23"
HorizontalAlignment="Left" Margin="45,9,0,0" Name="textBox5"
VerticalAlignment="Top" Width="266"  />
<TextBlock Grid.Row="5" HorizontalAlignment="Left"
Margin="12,12,0,13" Name="textBlock6" Text="Time Saved:" FontSize="16" />
<TextBox Grid.Column="1" Grid.Row="5" Height="23"
HorizontalAlignment="Left" Margin="45,13,0,0" Name="textBox6"
VerticalAlignment="Top" Width="266" />
<Button Content="Update" Grid.Column="1" Grid.Row="6" Height="24"
HorizontalAlignment="Left"
Margin="92,15,0,0" Name="button1" VerticalAlignment="Top" Width="156"
FontSize="16" FontFamily="Garamond"
FontWeight="Bold" BorderBrush="#FF407F2E" Foreground="#FFE39223"
Background="#FF175D17" />
</Grid>
</Window>

[/sourcecode]

Now let's make the necessary changes on the View so it can bind to the Model through the ViewModel.

We have first to make a reference to theViewModel class/object so I include the following namespace.

xmlns:football="clr-namespace:WpfMVVM.ViewModel"

This is the first step to hook a View to the ViewModel. We tell WPF where our ViewModel lives.

Then I will create the ViewModel as a resource (static resource).

<Window.Resources>

        <football:FootballerViewModel x:Key="FootballerViewModel" />

</Window.Resources>

Then in the Grid element I make the following change

Grid DataContext="{StaticResource FootballerViewModel}" >

The DataContext property inherits its value to child elements. So you can set the DataContext on a layout container (Grid) and its values are inherited to all child elements. This is very useful in our case where we want to bind to the same data object.

Databinding is typically done in XAML by using the {Binding} markup extension. We have to bind the Text attribute of the TextBox element to the various properties as implemented in the Model through theViewModel.

[sourcecode language="xml"]
<TextBox Grid.Column="1" Height="23" HorizontalAlignment="Left"
 Margin="45,12,0,0" Name="textBox1"
VerticalAlignment="Top" Width="266"
 Text="{Binding MyFootballer.FirstName ValidatesOnDataErrors=True}" />

<TextBox Grid.Column="1" Grid.Row="1" Height="23"
 HorizontalAlignment="Left" Margin="45,9,0,0" Name="textBox2"
VerticalAlignment="Top" Width="266"
Text="{Binding MyFootballer.LastName, ValidatesOnDataErrors=True}" />

<TextBox Grid.Column="1" Grid.Row="2" Height="23"
 HorizontalAlignment="Left" Margin="45,9,0,0" Name="textBox3"
VerticalAlignment="Top" Width="266" Text="{Binding MyFootballer.Age}" />

<TextBox Grid.Column="1" Grid.Row="3" Height="23"
 HorizontalAlignment="Left" Margin="45,12,0,0" Name="textBox4"
VerticalAlignment="Top" Width="266"
Text="{<strong>Binding MyFootballer.Height</strong>}" />

<TextBox Grid.Column="1" Grid.Row="4"
 Height="23" HorizontalAlignment="Left" Margin="45,9,0,0" Name="textBox5"
VerticalAlignment="Top" Width="266" Text="{Binding MyFootballer.Weight}" />

<TextBox Grid.Column="1" Grid.Row="5" Height="23"
 HorizontalAlignment="Left" Margin="45,13,0,0" Name="textBox6"
VerticalAlignment="Top" Width="266" Text="{Binding MyFootballer.SavedTime}" />
[/sourcecode]

Run your application and see the values from the domain object appearing in the textboxes.

We do have a button in our UI. We need to update data from the UI and bind the new data to the textboxes.

We need to have a different communnication between the View and the ViewModel. In this case we will use Commands.

First we need to update the XAML.

[sourcecode language="xml"]
<Button Content="Update" Grid.Column="1" Grid.Row="6" Height="24"
 HorizontalAlignment="Left" Margin="92,15,0,0"
Name="button1" VerticalAlignment="Top" Width="156" FontSize="16"
 FontFamily="Garamond"
FontWeight="Bold" BorderBrush="#FF407F2E" Foreground="#FFE39223"
Background="#FF175D17"
Command="{Binding SaveFootballerCommand}" />
[/sourcecode]

So I updated the XAML for the button element by adding the Command="{Binding SaveFootballerCommand}"

Now we must add some more code in our ViewModel.

[sourcecode language="csharp"]
public class SaveFootballerCommand:ICommand
{

Action _saveMethod;

public bool CanExecute(object parameter)
{
return true;
}

public event EventHandler CanExecuteChanged;

public void Execute(object parameter)
{
_saveMethod.Invoke();
}
}
[/sourcecode]

I create a new class, SaveFootballerCommand, that has to implement the ICommand interface. Then I define an Action, _saveMethod, which is what is executed when the button is clicked.

The CanExecute tells me if I am allowed to execute the command.Then I create the Execute method and invoke it.

Now we need to create a property in my ViewModel that exposes that command.

[sourcecode language="csharp"]
private ICommand _SaveFootballerCommand;

public ICommand SaveFootballerCommand
{
get { return _SaveFootballerCommand; }
set
{
_SaveFootballerCommand = value;
OnPropertyChanged("SaveFootballerCommand");

}
}

[/sourcecode]

Now we need to create an instance of the SaveFootballerCommand.

[sourcecode language="csharp"]
private void FireCommand()
{
SaveFootballerCommand = new SaveFootballerCommand(updateFootballer) ;
}

[/sourcecode]

I place the inside my constructor the FireCommand() method.

[sourcecode language="csharp"]
public FootballerViewModel()
{
FireCommand();
LoadFootballers();
}

[/sourcecode]

The updateFootballer method is going to be invoked when the command is executed.So I must implement it.

[sourcecode language="csharp"]
private void updateFootballer()
{

MyFootballer.SavedTime = DateTime.Now;

}
[/sourcecode]

Now I need to create a constructor of the SavePersonCommand class that takes a parameter. The code follows

[sourcecode language="csharp"]
public SaveFootballerCommand(Action updateFootballer)
{
_saveMethod = updateFootballer;

}
[/sourcecode]

The whole code for the ViewModel class follows

[sourcecode language="csharp"]
public SaveFootballerCommand(Action updateFootballer)
{
class FootballerViewModel:INotifyPropertyChanged
{

public FootballerViewModel()
{
FireCommand();
LoadFootballers();
}

private void FireCommand()
{
SaveFootballerCommand = new SaveFootballerCommand(updateFootballer) ;
}

private void updateFootballer()
{

MyFootballer.SavedTime = DateTime.Now;

}

private void LoadFootballers()
{
MyFootballer  = new Footballer()
{
FirstName = "Steven",
LastName = "Gerrard",
Age = 31,
Weight = 88.6,
Height = 1.84

};
}

private Footballer _myfootballer;

public Footballer MyFootballer
{
get { return _myfootballer; }
set

{

_myfootballer = value;
OnPropertyChanged("MyFootballer");

}
}

private ICommand _SaveFootballerCommand;

public ICommand SaveFootballerCommand
{
get { return _SaveFootballerCommand; }
set
{
_SaveFootballerCommand = value;
OnPropertyChanged("SaveFootballerCommand");

}
}

public event PropertyChangedEventHandler PropertyChanged;

public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

}
}

}
[/sourcecode]

Run your application and click the button.Note how the date is updated.

Note that we do not have written a single line of code to the MainWindow.xaml.cs file.

Now that we have a clear understanding of what MVVM is and what the different components are, let's review again the main concepts.

The ViewModel is responsible for the state and behaviour of the View and it acts like the "middle man" between the View and the Model.

It the middle layer and its main job is to send View information to the Model and Model information to the View.

It has no dependency on the View so we can reuse the ViewModel on different Views or even other platforms.

The ViewModel exposes the Model as properties or commands.

In any case it must implement the INotifyPropertyChanged interface.

The View binds to properties in the ViewModel. It does that by setting the DataContext of a View to an instance of the ViewModel object.

The Model is tha data.Simple as that. The Model's job is to represent the data and has no knowledge of where or how the data will be presented.

By using this pattern we can have the designers in our company designing the Views with xaml using Visual Studio Designer or Blend. The developers in our company can work with the Model and the ViewModel writing the data access code and businnes logic . We reduce the development time.

This is great pattern because we can make our code more testable and maintainable.

If you want to have a look at the MVVM more closely have a look here.

It would be nice if you could spare some time and have a  look at these related patterns with MVVM pattern like MVP,MVC,PM.

Hope it helps!!! Email me if you need the source code.


Share
Posted: Δευτέρα, 18 Ιουλίου 2011 6:44 μμ από το μέλος nikolaosk

Σχόλια:

Χωρίς Σχόλια

Έχει απενεργοποιηθεί η προσθήκη σχολίων από ανώνυμα μέλη