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
{
private ViewModel viewModel;
public MainForm()
{
InitializeComponent();
this.InitialControlHandlers();
}
private void InitialControlHandlers()
{
this.viewModel = new ViewModel();
ListForm listForm = new ListForm()
{
ViewModel = this.viewModel
};
this.AddOwnedForm(listForm);
listForm.FormClosing+=new FormClosingEventHandler(
(object sender, FormClosingEventArgs e) =>
{
listForm.Dispose();
this.Close();
});
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();
}
);
this.Add.Tag = viewModel.AddToListCommand;
this.NameForAdding.Tag = viewModel.TextChangedCommand;
BindingSource binding = new BindingSource();
binding.DataSource = viewModel.Text;
this.NameForAdding.DataBindings.Add(new Binding("Text", viewModel, "Text"));
listForm.Show();
}
private void AddToList()
{
viewModel.Execute(this.Add.Tag, null);
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
{
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);
}
}
void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.NameList =
(ObservableCollection<StringValue>)sender;
}
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));
}
public event PropertyChangedEventHandler PropertyChanged;
public void Execute(object sender, object parameter)
{
((ICommand)sender).Execute(parameter);
}
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
{
public AddToList(ViewModel viewModel)
{
this.ViewModel = viewModel;
}
public ViewModel ViewModel { get; set; }
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
{
public TextChanged(ViewModel viewModel)
{
this.DataAccess = viewModel;
}
public ViewModel DataAccess { get; set; }
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 of ViewModel
will change (because of binding) and when the Button (Add
) is clicked, a command will fire to add Text
to List
of Model
. After it, Model
will synchronize it's List
with database (using DataAccess
). If it will be any changes in List
, then NameList
of ViewModel
will update and any updates in NameList
, re-bind the GridView in ListForm
.
- 2-Ways Binding is not available in WinForm, so I've implemented
INotifyPropertyChanged
and used NotifyCollectionChangedEventHandler
.
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.