In this article you will see how to implement MVVM in a
Silverlight application.
MVVM
In simple words,View knows ViewModel, ViewModel knows Model, But not vice versa.
Why MVVM
- Easy to
unit test.
- No need
to change model to support changes in View.
- Very
minor changes required in ViewModel to support changes in View.
-
Separates UX designer and developer i.e the development team can focus on
creating ViewModel classes, and the design team can focus on making
user-friendly Views.
Simple Silverlight application using MVVM
Here I am just going to get a student name and age from the user and display the
details in a GridView as in the figure.
The important areas covered are MVVM, using Relay Command and IValueConverter.
Creating a Student class
I created student class with the following properties, Name, Age, JoiningDate.
using
System;
using
System.Net;
using
System.Windows;
using
System.Windows.Controls;
using
System.Windows.Documents;
using
System.Windows.Ink;
using
System.Windows.Input;
using
System.Windows.Media;
using
System.Windows.Media.Animation;
using
System.Windows.Shapes;
namespace
MVVMDemoinSL
{
public class
Student
{
public string
Name { get; set;
}
public int Age {
get; set; }
public DateTime
JoiningDate { get; set;
}
}
}
Creating ViewModelBase and ViewModel class
It's always a good idea to have a ViewModelBase class and inherit all the
ViewModels from that. Hence we can reuse the code for implementing
INotifyPropertyChanged.
The main purpose of using INotifyPropertyChanged is to get notification whenever
the property value is changed.
I created a ViewModelBase as below.
using
System;
using
System.Net;
using
System.Windows;
using
System.Windows.Controls;
using
System.Windows.Documents;
using
System.Windows.Ink;
using
System.Windows.Input;
using
System.Windows.Media;
using
System.Windows.Media.Animation;
using
System.Windows.Shapes;
using
System.ComponentModel;
namespace
MVVMDemoinSL
{
public class
ViewModelBase :
INotifyPropertyChanged
{
public event
PropertyChangedEventHandler PropertyChanged;
protected void
NotifyPropertyChanged(string propertyName)
{
if
(PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
}
I also created a ViewModel class which inherits from ViewModelBase.
using
System;
using
System.Net;
using
System.Windows;
using
System.Windows.Controls;
using
System.Windows.Documents;
using
System.Windows.Ink;
using
System.Windows.Input;
using
System.Windows.Media;
using
System.Windows.Media.Animation;
using
System.Windows.Shapes;
using
System.Collections.ObjectModel;
namespace
MVVMDemoinSL
{
public class
ViewModel :
ViewModelBase
{
private Student
_student;
private
ObservableCollection<Student>
_students;
private ICommand
_SubmitCommand;
public Student
Student
{
get
{
return _student;
}
set
{
_student = value;
NotifyPropertyChanged("Student");
}
}
public
ObservableCollection<Student> Students
{
get
{
return _students;
}
set
{
_students = value;
NotifyPropertyChanged("Students");
}
}
public ICommand
SubmitCommand
{
get
{
if (_SubmitCommand ==
null)
{
_SubmitCommand = new
RelayCommand(Submit);
}
return _SubmitCommand;
}
}
public ViewModel()
{
Student
= new Student();
Students = new
ObservableCollection<Student>();
Students.CollectionChanged += new
System.Collections.Specialized.NotifyCollectionChangedEventHandler(Students_CollectionChanged);
}
//Whenever new item is added to the
collection, am explicitly calling notify property changed
void Students_CollectionChanged(object
sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs
e)
{
NotifyPropertyChanged("Students");
}
private void
Submit()
{
Student.JoiningDate = DateTime.Today.Date;
Students.Add(Student);
Student
= new Student();
}
}
}
RelayCommand
Since we are following the MVVM pattern, instead of having a Button click event
in code behind, we are going to use the Command Property of the button. The
RelayCommand allows you to inject the command's logic via delegates ed into
its constructor. This approach allows for terse, concise command implementation
in ViewModel classes. RelayCommand is a simplified variation of the
DelegateCommand. RelayCommand implementation using CommandManager is not
possible using Silverlight.
using
System;
using
System.Net;
using
System.Windows;
using
System.Windows.Controls;
using
System.Windows.Documents;
using
System.Windows.Ink;
using
System.Windows.Input;
using
System.Windows.Media;
using
System.Windows.Media.Animation;
using
System.Windows.Shapes;
namespace
MVVMDemoinSL
{
public class
RelayCommand :
ICommand
{
private Func<bool>
canExecute;
private Action
executeAction;
public event
EventHandler CanExecuteChanged;
public RelayCommand(Action
executeAction,
Func<bool>
canExecute)
{
this.executeAction = executeAction;
this.canExecute = canExecute;
}
public RelayCommand(Action
executeAction)
{
this.executeAction = executeAction;
this.canExecute = () =>
true;
}
public void
RaiseCanExecuteChanged()
{
if (CanExecuteChanged !=
null)
{
CanExecuteChanged(this,
EventArgs.Empty);
}
}
public bool
CanExecute(object parameter)
{
return canExecute ==
null ? true : canExecute();
}
public void
Execute(object parameter)
{
executeAction();
}
}
}
View
Now we have to create a view and bind it with ViewModel properties. The code for
the view can be found below. I have used a window, Grid, Textbox, TextBlock,
Button and GridView.
<UserControl
x:Class="MVVMDemoinSL.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:viewmodel="clr-namespace:MVVMDemoinSL"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<viewmodel:ViewModel
x:Key="ViewModel"/>
<viewmodel:DatetimeToDateConverter
x:Key="MyConverter"/>
</UserControl.Resources>
<Grid
DataContext="{Binding
Source={StaticResource
ViewModel}}">
<Grid.RowDefinitions>
<RowDefinition
Height="Auto"/>
<RowDefinition
Height="Auto"/>
<RowDefinition
Height="Auto"/>
<RowDefinition
Height="Auto"/>
<RowDefinition
Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="Auto"/>
<ColumnDefinition
Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Text="Name"
HorizontalAlignment="Center"/>
<TextBox
Grid.Row="0"
Grid.Column="1"
Width="100"
HorizontalAlignment="Center"
Text="{Binding
Student.Name,
Mode=TwoWay}"/>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Text="Age"
HorizontalAlignment="Center"/>
<TextBox
Grid.Row="1"
Grid.Column="1"
Width="100"
HorizontalAlignment="Center"
Text="{Binding
Student.Age,
Mode=TwoWay}"/>
<Button
Content="Submit"
Command="{Binding
SubmitCommand}"
HorizontalAlignment="Right"
Grid.Row="2"
Grid.Column="0"/>
<sdk:DataGrid
ItemsSource="{Binding
Students}"
Grid.Row="3"
Grid.Column="0"
Width="200"
AutoGenerateColumns="False">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn
Header="Name"
Binding="{Binding
Name}" Width="60"/>
<sdk:DataGridTextColumn
Header="Age"
Binding="{Binding
Age}" Width="60"/>
<sdk:DataGridTextColumn
Header="Joining
Date"
Binding="{Binding
JoiningDate,
Converter={StaticResource
MyConverter}}"
Width="80"
/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</Grid>
</UserControl>
If you see the above XAML code, you can see I am using Windows resources in my
view. In resources, I am adding a reference to my ViewModel and
DateTimeToDateConverter.
ValueConverter
We might often get some values from the property but we have to display some
other value in the View. Say for example in my project, the Joining date is date
time value but I need to display only date. I can do this either by String
format in binding or else ValueConverter. I just want to go with latter one,
since you can understand ValueConverter also.
using
System;
using
System.Net;
using
System.Windows;
using
System.Windows.Controls;
using
System.Windows.Documents;
using
System.Windows.Ink;
using
System.Windows.Input;
using
System.Windows.Media;
using
System.Windows.Media.Animation;
using
System.Windows.Shapes;
using
System.Windows.Data;
using
System.Globalization;
namespace
MVVMDemoinSL
{
public class
DatetimeToDateConverter :
IValueConverter
{
public object
Convert(object value,
Type targetType,
object parameter, CultureInfo culture)
{
DateTime date = (DateTime)value;
return date.ToString("MM/d/yyyy");
}
public object
ConvertBack(object value,
Type targetType,
object parameter, CultureInfo culture)
{
return value;
}
}
}
For implementing MVVM in WPF, please go through the below article.
http://www.c-sharpcorner.com/UploadFile/1a81c5/a-simple-wpf-application-implementing-mvvm/