MVVM (Model-View-ViewModel) Pattern For Windows Form Applications, using C#






4.84/5 (61 votes)
It's a pattern like MVVM but for WinForms.
Introduction
The MVVM pattern is in favour of WPF/Silverlight developers because of its advantages. Microsoft says that MVVM:
- Separates the business and presentation logic of the application from its UI, and makes it much easier to test, maintain, and evolve.
- Allows developers and UI designers to more easily collaborate when developing their respective parts of the application.(Look at the link)
MVVM is for WPF, but is there any way to have the advantages for WinForms too? The answer is yes (good news or maybe not so new). Windows Form Applications can also have the advantages, and of course it doesn't mean the exact real Model-View-ViewModel (MVVM) pattern. I searched and found many approaches (Google Search) some of them were great and some not(!) But I've tried to present a simple useful solution. I hope you'll be interested.
Background
As you know, Program Logic and Content are separated In ASP.Net. And also in WPF, Program Logic is separated from UI. In Windows Forms, I preferred to use partial classes to separate Program Logic from UI until MVVM pattern has been introduced to me. I interested in using the pattern for WPF, so I decided to find a same way for WinForms too, but I was always too busy to work on it. One day, I tried to impose MVVM on WinForms, and here is the result of 3 spare hours working on the subject.
A very fast and short MVVM review
MVVM means Model-View-ViewModel:
1. View: It refers to the elements that displayed by the UI. As some people say, View is UI and some others say that View is UI "but" Logic (I like the second one). And also, it passes commands to ViewModel.
2. ViewModel: It's an abstraction of the View, or mediation between View and Model. It passes Data between View and Model.
3. Model: It represents data from DAL (Data Access Layer) or a Domain Model. So, Model is not DAL but refers to it.
Using the code
View: I need two forms (MainForm
and ViewForm
). MainForm
contains a TextBox (NameForAdding
) and a Button (Add
). ListForm
contains only one DataGridView.
OK, there is not any code behind of MainForm
:
public partial class MainForm : Form
{
}
As you know, I used a partial class in a separated assembly (MainForm.CodeBehind.cs
) for it:
public partial class MainForm
{
//fields
private ViewModel viewModel;
//Constructor
public MainForm()
{
InitializeComponent();
this.InitialControlHandlers();
}
//Defining all Control handler methods
private void InitialControlHandlers()
{
this.viewModel = new ViewModel();
ListForm listForm = new ListForm()
{
ViewModel = this.viewModel
};
//Defines the listForm as an owned form
this.AddOwnedForm(listForm);
//Defines: if ListForm closes, the MainForm will close too
listForm.FormClosing+=new FormClosingEventHandler(
(object sender, FormClosingEventArgs e) =>
{
listForm.Dispose();
this.Close();
});
//If the end user press Enter key, it will be like (s)he clicked on Add button.
this.NameForAdding.KeyPress += new KeyPressEventHandler(
(object sender, KeyPressEventArgs e) =>
{
if (e.KeyChar == (char)13)
this.AddToList();
});
this.Add.Click += new EventHandler(
(object sender, EventArgs e) =>
{
AddToList();
}
);
//Defining a command for this button. It will be un-boxed to execute.
this.Add.Tag = viewModel.AddToListCommand;
//Defining a command for TextChanged. It will be un-boxed to execute.
this.NameForAdding.Tag = viewModel.TextChangedCommand;
//Binding the Text property of ViewModel to TextBox
BindingSource binding = new BindingSource();
binding.DataSource = viewModel.Text;
this.NameForAdding.DataBindings.Add(new Binding("Text", viewModel, "Text"));
//Showing ListForm at finishing the form load.
listForm.Show();
}
private void AddToList()
{
viewModel.Execute(this.Add.Tag, null);
//After passing the value of the TextBox, it will be empty.
this.NameForAdding.Text = string.Empty;
this.NameForAdding.Focus();
}
}
All events, methods and etc of the Form, are defined in the partial class.
ViewModel:
public class ViewModel : INotifyPropertyChanged
{
//Properties
public string Text { get; set; }
private ICommand _addToListCommand;
public ICommand AddToListCommand
{
get
{
if (_addToListCommand == null)
_addToListCommand = new AddToList(this);
return _addToListCommand;
}
}
private ICommand _textChangedCommand;
public ICommand TextChangedCommand
{
get
{
if (_textChangedCommand == null)
_textChangedCommand = new TextChanged(this);
return _textChangedCommand;
}
}
private ObservableCollection<StringValue> _nameList;
public ObservableCollection<StringValue> NameList
{
get
{
return _nameList;
}
set
{
_nameList = value;
_nameList.CollectionChanged +=
new System.Collections.Specialized.NotifyCollectionChangedEventHandler
(MyProperty_CollectionChanged);
}
}
//PropeetyChanged Handlers
void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.NameList =
(ObservableCollection<StringValue>)sender;
//For Geting any new Changes in Entity
}
void MyProperty_CollectionChanged
(object sender,
System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
{
NotifyPropertyChanged("NameList");
}
}
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
//INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
//Methods
public void Execute(object sender, object parameter)
{
((ICommand)sender).Execute(parameter);
}
//...
//For more information about the complete code, download the Source Code
And its constructor:
public ViewModel()
{
Model model = new Model();
model.PropertyChanged += new PropertyChangedEventHandler(model_PropertyChanged);
//...
}
And 2 command classes that are used in UI and ViewModel:
public class AddToList : ICommand
{
//Constructor
public AddToList(ViewModel viewModel)
{
this.ViewModel = viewModel;
}
//Properties
public ViewModel ViewModel { get; set; }
//ICommand Members
public void Execute(object sender)
{
if (string.IsNullOrEmpty(this.ViewModel.Text))
return;
if (string.IsNullOrEmpty(this.ViewModel.Text.Trim()))
return;
this.ViewModel.NameList.Add(new StringValue(this.ViewModel.Text));
}
}
public class TextChanged : ICommand
{
//Constructor
public TextChanged(ViewModel viewModel)
{
this.DataAccess = viewModel;
}
//Properties
public ViewModel DataAccess { get; set; }
//ICommand Members
public void Execute(object sender)
{
this.DataAccess.Text = sender.ToString();
}
}
Model:
public class Model:INotifyPropertyChanged
{
public ObservableCollection<StringValue> List = new ObservableCollection<StringValue>();
private DataAccess dal = new DataAccess();
public Model()
{
this.List = dal.Select();
dal.DatabaseUpdated += new DataAccess.UpdateHandler(UpdataFromDal);
this.List.CollectionChanged +=
new System.Collections.Specialized.NotifyCollectionChangedEventHandler
(List_CollectionChanged);
}
void List_CollectionChanged(object sender,
System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
dal.Update(this.List);
}
private void UpdataFromDal(ObservableCollection<StringValue> list)
{
this.List = list;
List.CollectionChanged +=
new System.Collections.Specialized.NotifyCollectionChangedEventHandler
(List_CollectionChanged);
if (PropertyChanged != null)
PropertyChanged(List, null);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
OK, and what about ListForm
? ListForm
has a DataGridView
and a Property as ViewModel
I bound DataGridView
to ViewModel.NameList
:
private void BindDataGridView()
{
BindingSource binding = new BindingSource();
binding.DataSource = ViewModel.NameList;
this.DataGridView.DataSource = binding;
}
void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
BindDataGridView();
}
And in form Load event:
ViewModel.PropertyChanged += new PropertyChangedEventHandler(ViewModel_PropertyChanged);
Illustrating generalities
If I want to use the "Inverted Pyramid" illlustrating:
- In the example, The end user is able to enter a name in the main form (
MainForm
) for many times, and seeing the names that are adding to the GirdView of the other form (ListForm
) - The end user is also confronting with some new names in
ListForm
that (s)he has never added them, like when another users are updating the list (just a simulating) - Adding proccess in the "end user"'s observation is 1.Filling the TextBox. 2. Click on Add (Button) 3. Watching the result in
ListForm
. - In behind, When the TextBox (
NameForAdding
) changes,Text
property ofViewModel
will change (because of binding) and when the Button (Add
) is clicked, a command will fire to addText
toList
ofModel
. After it,Model
will synchronize it'sList
with database (usingDataAccess
). If it will be any changes inList
, thenNameList
ofViewModel
will update and any updates inNameList
, re-bind the GridView inListForm
. - 2-Ways Binding is not available in WinForm, so I've implemented
INotifyPropertyChanged
and usedNotifyCollectionChangedEventHandler
.
Answering Obscurities
What is StringValue? A string object has only one public Property (Length) and if you bind a string collection to the DataGridView, it will only show the lengths. StringValue
is a defined class and I created a Collection of StringValue
to be shown in the DataGirdView
.
public class StringValue
{
public string Value { get; set; }
public StringValue(string value)
{
this.Value = value;
}
}
What is ICommand? It's an interface that I created, imitating WPF build-in ICommand Interface:
public interface ICommand
{
void Execute(object sender);
}
I set the Tag
of the controls to commands (In partial class)
this.Add.Tag = viewModel.AddToListCommand;
this.NameForAdding.Tag = viewModel.TextChangedCommand;
The tags will be un-boxed when it's going to execute its command:
public void Execute(object sender, object parameter)
{
((ICommand)sender).Execute(parameter);
}
Why didn't I expound enough about ViewModel class? Because, it's almost like View-Model in WPF and there are enough sites, books, articles and etc to refer. (A too long article because of untimelies).
Why does Model seem a little strange? Model
uses DataAcess
class simulating it's working with a database (but it isn't). All about Model
is, it's a gate between Presentation and DAL, in the meanwhile, it has implemented INotifyPropertyChanged
to notify ViewModel
about entity updates.
What does DataAccess do? DataAccess
class is not a part of the pattern, I created it to fake an accessing to database. It's also generating some random names to simulate concurrency updates. (Please download demo and source code.)
Anything else?
Please help! If you face any ambiguity in this writing, please notify me, and don't hesitate to ask question. It helps me to improve the article.
Thank you.