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 a 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 the 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 following article.
http://www.c-sharpcorner.com/UploadFile/1a81c5/a-simple-wpf-application-implementing-mvvm/