Vous êtes sur la page 1sur 19

The Model-View-Controller(MVC) Pattern

with C#/WinForms
Introduction
This article is used to demonstrate the MVC Pattern in .NET using C#/WinForm.
Here a simple "User Information Manager" application which is organized according the the
Model-View-Controller(MVC) pattern.
The application displays a list of contacts and allows you to add, modify, and delete existing
contacts. The idea is to separate the user interface into View (creates the display, calling the
Model as necessary to get information) and Controller (responds to user requests, interacting
with both the View and Controller as necessary). The main advantage of MVC pattern is Loose
Coupling. All the layers are separated with their own functionality. It is easy to replace a layer
with some other type of layer. In other words, MVC pattern is to break up UI behavior into
separate pieces in order to increase reuse possibilities and testability. I am using Visual Studio
2010 Ultimate with .NET 4.0 for creating this Application.
Background
Model-View-Controller as the name applies considers three pieces:
Model: it should be responsible for the data of the application domain
View: it presents the display of the model in the user interface
Controller: it is really the heart of the MVC, the intermediary that ties the Model and the View
together, i.e. it takes user input, manipulates the model & causes the view to update

For more information about MVC, please see the following article from Wikipedia
The Solution
The User Information Manager is an application where you can store your customers' contact
information. The application displays a list of contacts and allows you to add, modify, and delete
existing contacts. All customers have an ID, first name, last name and sex. The screen that
operator of this app uses to maintain his list of customers could look something like this:


List of customers can be viewed, added, removed and updated (currently is contains only V.I.P
Members, but if you want to become a member of the club, just ask "
src="http://www.codeproject.com/script/Forums/Images/smiley_smile.gif" /> .... and no
problems, it's free). After a new user is added, his ID cannot change anymore.
The Class Diagram
In the design of a system, a number of classes are identified and grouped together in a class
diagram which helps to determine the relations between objects.

The Description of Components
Part of Controller
In order to detach logic from the View, we have to make a View feel as helpless as possible, so
we'd prefer to make the Controller do all the hard work and just hand the View some simple
commands that do not require any further processing. According to our design, we do this by
defining an interface, IUsersView, which the View must implement. This interface contain only
the signatures of properties/methods we need to use.
Collapse | Copy Code
using System;
using WinFormMVC.Model;

namespace WinFormMVC.Controller
{
public interface IUsersView
{
void SetController(UsersController controller);
void ClearGrid();
void AddUserToGrid(User user);
void UpdateGridWithChangedUser(User user);
void RemoveUserFromGrid(User user);
string GetIdOfSelectedUserInGrid();
void SetSelectedUserInGrid(User user);

string FirstName { get; set; }
string LastName { get; set; }
string ID { get; set; }
string Department { get; set; }
User.SexOfPerson Sex { get; set; }
bool CanModifyID { set; }
}
}
Now we have a fairly good interface with number of methods. Even if the MVC pattern formally
declares that the Controller should receive the events and act upon the View, is often more
practical and easier to have the View subscribe to the events and then delegate the handling to
the Controller.
Finally I show the actual realization of the Controller (see the UsersController class). He
hooks up the Model (User class) with View (UserView class).
Collapse | Copy Code
public class UsersController
{
//Notice we only use the interfaces. This makes the test more
//robust to changes in the system.
IUsersView _view;
IList _users;
User _selectedUser;

//The UsersController depends on abstractions(interfaces).
//It's easier than ever to change the behavior of a concrete class.
//Instead of creating concrete objects in UsersController class,
//we pass the objects to the constructor of UsersController
public UsersController(IUsersView view, IList users)
{
_view = view;
_users = users;
view.SetController(this);
}

public IList Users
{
get { return ArrayList.ReadOnly(_users); }
}

private void updateViewDetailValues(User usr)
{
_view.FirstName = usr.FirstName;
_view.LastName = usr.LastName;
_view.ID = usr.ID;
_view.Department = usr.Department;
_view.Sex = usr.Sex;
}

private void updateUserWithViewValues(User usr)
{
usr.FirstName = _view.FirstName;
usr.LastName = _view.LastName;
usr.ID = _view.ID;
usr.Department = _view.Department;
usr.Sex = _view.Sex;
}

public void LoadView()
{
_view.ClearGrid();
foreach (User usr in _users)
_view.AddUserToGrid(usr);

_view.SetSelectedUserInGrid((User)_users[0]);
}

public void SelectedUserChanged(string selectedUserId)
{
foreach (User usr in this._users)
{
if (usr.ID == selectedUserId)
{
_selectedUser = usr;
updateViewDetailValues(usr);
_view.SetSelectedUserInGrid(usr);
this._view.CanModifyID = false;
break;
}
}
}

public void AddNewUser()
{
_selectedUser = new User("" /*firstname*/,
"" /*lastname*/,
"" /*id*/,
""/*department*/,
User.SexOfPerson.Male/*sex*/);

this.updateViewDetailValues(_selectedUser);
this._view.CanModifyID = true;
}

public void RemoveUser()
{
string id = this._view.GetIdOfSelectedUserInGrid();
User userToRemove = null;

if (id != "")
{
foreach (User usr in this._users)
{
if (usr.ID == id)
{
userToRemove = usr;
break;
}
}

if (userToRemove != null)
{
int newSelectedIndex = this._users.IndexOf(userToRemove);
this._users.Remove(userToRemove);
this._view.RemoveUserFromGrid(userToRemove);

if (newSelectedIndex > -1 && newSelectedIndex < _users.Count)
{

this._view.SetSelectedUserInGrid((User)_users[newSelectedIndex]);
}
}
}
}

public void Save()
{
updateUserWithViewValues(_selectedUser);
if (!this._users.Contains(_selectedUser))
{
//Add new user
this._users.Add(_selectedUser);
this._view.AddUserToGrid(_selectedUser);
}
else
{
//Update existing user
this._view.UpdateGridWithChangedUser(_selectedUser);
}
_view.SetSelectedUserInGrid(_selectedUser);
this._view.CanModifyID = false;

}
}
The controller class is very important and central to the application. It's really important to keep
it light, agile and loosely coupled to other components of the program.
Part of View
This section will focus on the scenario of loading the View with the list of users.
As said before our View must implement the IUsersView interface. A subset of the
implementation is shown in the following code :
Collapse | Copy Code
namespace WinFormMVC.View
{
public partial class UsersView : Form, IUsersView
{
The SetController() member function of UsersView allows us to tell the View to which
Controller instance it must forward the events and all event handlers simply call the
corresponding "event" method on the Controller. As you can see here, UsersView also depends
on abstractions...
Collapse | Copy Code
public void SetController(UsersController controller)
{
_controller = controller;
}
We also use realisation of several methods from the IUsersView interface which use the User
object:
Collapse | Copy Code
public void AddUserToGrid(User usr)
{
ListViewItem parent;
parent = this.grdUsers.Items.Add(usr.ID);
parent.SubItems.Add(usr.FirstName);
parent.SubItems.Add(usr.LastName);
parent.SubItems.Add(usr.Department);
parent.SubItems.Add(Enum.GetName(typeof(User.SexOfPerson), usr.Sex));
}

public void UpdateGridWithChangedUser(User usr)
{
ListViewItem rowToUpdate = null;

foreach (ListViewItem row in this.grdUsers.Items)
{
if (row.Text == usr.ID)
{
rowToUpdate = row;
}
}

if (rowToUpdate != null)
{
rowToUpdate.Text = usr.ID;
rowToUpdate.SubItems[1].Text = usr.FirstName;
rowToUpdate.SubItems[2].Text = usr.LastName;
rowToUpdate.SubItems[3].Text = usr.Department;
rowToUpdate.SubItems[4].Text = Enum.GetName(typeof(User.SexOfPerson),
usr.Sex);
}
}

public void RemoveUserFromGrid(User usr)
{

ListViewItem rowToRemove = null;

foreach (ListViewItem row in this.grdUsers.Items)
{
if (row.Text == usr.ID)
{
rowToRemove = row;
}
}

if (rowToRemove != null)
{
this.grdUsers.Items.Remove(rowToRemove);
this.grdUsers.Focus();
}
}

public string GetIdOfSelectedUserInGrid()
{
if (this.grdUsers.SelectedItems.Count > 0)
return this.grdUsers.SelectedItems[0].Text;
else
return "";
}

public void SetSelectedUserInGrid(User usr)
{
foreach (ListViewItem row in this.grdUsers.Items)
{
if (row.Text == usr.ID)
{
row.Selected = true;
}
}
}

public string FirstName
{
get { return this.txtFirstName.Text; }
set { this.txtFirstName.Text = value; }
}

public string LastName
{
get { return this.txtLastName.Text; }
set { this.txtLastName.Text = value; }
}

public string ID
{
get { return this.txtID.Text; }
set { this.txtID.Text = value; }
}


public string Department
{
get { return this.txtDepartment.Text; }
set { this.txtDepartment.Text = value; }
}

public User.SexOfPerson Sex
{
get
{
if (this.rdMale.Checked)
return User.SexOfPerson.Male;
else
return User.SexOfPerson.Female;
}
set
{
if (value == User.SexOfPerson.Male)
this.rdMale.Checked = true;
else
this.rdFamele.Checked = true;
}
}

public bool CanModifyID
{
set { this.txtID.Enabled = value; }
}

...
}
Part of Model
This User class is a Model class. In this example, the User is an extremely simple domain class
with no behavior, whereas in a realworld Domain Model you would probably have much more
functionality in the domain classes. The model is independent of the user interface. It doesn't
know if it's being used from a text-based, graphical or web interface. The Model only holds the
in-memory state in a structured format. As you can see, the class contains only private data
members and the public interfaces (properties) available to the client code
Collapse | Copy Code
using System;

namespace WinFormMVC.Model
{
public class User
{
public enum SexOfPerson
{
Male = 1,
Female = 2
}

private string _FirstName;
public string FirstName
{
get { return _FirstName; }
set
{
if (value.Length > 50)
Console.WriteLine("Error! FirstName must be less than 51 characters!");
else
_FirstName = value;
}
}

private string _LastName;
public string LastName
{
get { return _LastName; }
set
{
if (value.Length > 50)
Console.WriteLine("Error! LastName must be less than 51 characters!");
else
_LastName = value;
}
}

private string _ID;
public string ID
{
get { return _ID; }
set
{
if (value.Length > 9)
Console.WriteLine("Error! ID must be less than 10 characters!");
else
_ID = value;
}
}

private string _Department;
public string Department
{
get { return _Department; }
set { _Department = value; }
}

private SexOfPerson _Sex;
public SexOfPerson Sex
{
get { return _Sex; }
set { _Sex = value; }
}


public User(string firstname, string lastname, string id, string
department, SexOfPerson sex)
{
FirstName = firstname;
LastName = lastname;
ID = id;
Department = department;
Sex = sex;
}
}

}
Part of Client
And now it's good time to show how we use the MVC paradigm effectively, i.e. our code's
components of MVC architecture (please see UseMVCApplication.csproj)
Collapse | Copy Code
using System.Collections;
using WinFormMVC.Model;
using WinFormMVC.View;
using WinFormMVC.Controller;

namespace UseMVCApplication
{
static class Program
{
/// The main entry point for the application.
[STAThread]
static void Main()
{
//Here we are creating a View
UsersView view = new UsersView();
view.Visible = false;

//Here we are creating a list of users
IList users = new ArrayList();

//Here we are add our "commoners" in the list of users
users.Add(new User("Vladimir", "Putin",
"122", "Government of Russia",
User.SexOfPerson.Male));
users.Add(new User("Barack", "Obama",
"123", "Government of USA",
User.SexOfPerson.Male));
users.Add(new User("Stephen", "Harper",
"124", "Government of Canada",
User.SexOfPerson.Male));
users.Add(new User("Jean", "Charest",
"125", "Government of Quebec",
User.SexOfPerson.Male));
users.Add(new User("David", "Cameron",
"126", "Government of United Kingdom",
User.SexOfPerson.Male));
users.Add(new User("Angela", "Merkel",
"127", "Government of Germany",
User.SexOfPerson.Female));
users.Add(new User("Nikolas", "Sarkozy",\
"128", "Government of France",
User.SexOfPerson.Male));
users.Add(new User("Silvio", "Berlusconi",
"129", "Government of Italy",
User.SexOfPerson.Male));
users.Add(new User("Yoshihiko", "Noda",
"130", "Government of Japan",
User.SexOfPerson.Male));

//Here we are creating a Controller and passing two
//parameters: View and list of users (models)
UsersController controller = new UsersController(view, users);
controller.LoadView();
view.ShowDialog();
}
}
}
Why is it good?
1. The main advantage of using the MVC pattern is that it makes the code of the user interface
more testable
2. It makes a very structured approach onto the process of designing the user interface, which in
itself contributes to writing clean, testable code, that will be easy to maintain and extend
The Model-View-Controller is a well-proven design pattern to solve the problem of separating
data (model) and user interface (view) concerns, so that changes to the user interface do not
affect the data handling, and that the data can be changed without impacting/changing the UI.
The MVC solves this problem by decoupling data access and business logic layer from UI and
user interaction, by introducing an intermediate component: the controller. This MVC
architecture enables the creation of reusable components within a flexible program design
(components can be easily modified)
Your first program using MVC pattern with
C#/WinForms
Introduction
Even though I have experience writing code using the MVC pattern in other languages such as
PHP, Java, etc., I came upon a requirement to use MVC for C#/WinForms. When I tried
searching for articles and examples of MVC pattern for C#, I could find quite a number of
articles but the simplicity was missing and most of the articles were written for ASP.NET where
MVC is most used. In other words, as part of explaining the MVC pattern, the article/tutorial
tries do much more than is required to explain the pattern itself.
Therefore I decided to write this article to explain the implementation of the MVC pattern with
C#/WinForms and keep it as simple as possible.
Background
The Model-View-Controller as it implies contains three components when this model is
implemented:
The Model - This should take care of all the operations which deal with the data required (in
other words the business logic) for the application, which is implementing the MVC model from
now onwards will be referred to as by the MVC application. The operations can mean reading,
writing data into the database, getting information from remote machines via network, time
consuming operations etc. The model should also inform the view about any changes to the
data happening in the background.
The View - This component takes care of presenting the data to the user. With respect to the
context of this article, i.e., WinForms, the view class will be tied around with the Form which will
be shown to the user.
The Controller - This is the center and important component of the MVC pattern as it ties the
Model and View together. The Model which manipulates the data and the View which presents
the data to the user does not know the existence of each other or they interact directly with
each other. It is the controller which acts as an intermediary and ties them together. For
example, the controller takes the input from the user such as a button click and informs the
model to take appropriate action, if there should be an action that needs to be initiated to
manipulate the project data.

Using the code
Let us take a simple use case of an application which increments the number so as not to divert
the reader to get carried away from concentrating on the MVC pattern into the details of other
aspects of the project. Exactly, the reason for which I wrote this article. For the sake of
simplicity, I did not change the name of the Form or button and left it as is such as button1 and
textbox1.
The description of the components
The controller
The controller is the component which does all the work of tying the view and model together.
The view does not know about the business logic of how the data is retrieved or handled.
Whenever there is an action performed on the view by the user such as a clicking a button or
changing a text on the text box it will inform the controller about it. The controller will decide
what action needs to be taken based on user input. It might request the model to do some
processing of the data after evaluation of the validity of the user input.
Collapse | Copy Code
namespace MVCTest
{
// The Icontroller supports only one functionality that is to increment
the value
public interface IController
{
void incvalue();
}
public class IncController : IController
{
IView view;
IModel model;
// The controller which implements the IController interface ties the
view and model
// together and attaches the view via the IModelInterface and addes
the event
// handler to the view_changed function. The view ties the controller
to itself.
public IncController(IView v, IModel m)
{
view = v;
model = m;
view.setController(this);
model.attach((IModelObserver)view);
view.changed += new ViewHandler<IView>(this.view_changed);
}
// event which gets fired from the view when the users changes the
value
public void view_changed(IView v, ViewEventArgs e)
{
model.setvalue(e.value);
}
// This does the actual work of getting the model do the work
public void incvalue()
{
model.increment();
}
}
}
The View
The view class is the component which will really be the Form itself which will implement the
View interface. In the below component it can be seen that the view interface just contains the
event handler when the view is changed and to set the controller, which controls this view.
Collapse | Copy Code
namespace MVCTest
{
public delegate void ViewHandler<IView>(IView sender, ViewEventArgs e);
// The event arguments class that will be used while firing the events
// for this program, we have only one value which the user changed.
public class ViewEventArgs: EventArgs
{
public int value;
public ViewEventArgs(int v) { value = v; }
}
// Currently, the interface only contains the method to set the
controller to which
// it is tied. The rest of the view related code is implemented in the
form.
public interface IView
{
event ViewHandler<IView> changed;
void setController(IController cont);
}
}
The Model
The model handles the business logic of incrementing the number passed to it by the controller.
It also informs the change in the data value (incremented) to the View. (On the contrary to the
statement that, Model does not directly talk to the view, the controller is the one who ties the
model to its view.) The view, i.e., the form, implements the IModelObserver which gets
invoked (if required) by the model.
Collapse | Copy Code
namespace MVCTest
{
public delegate void ModelHandler<IModel>(IModel sender, ModelEventArgs
e);
// The ModelEventArgs class which is derived from th EventArgs class to
// be passed on to the controller when the value is changed
public class ModelEventArgs : EventArgs
{
public int newval;
public ModelEventArgs(int v)
{
newval = v;
}
}
// The interface which the form/view must implement so that, the event
will be
// fired when a value is changed in the model.
public interface IModelObserver
{
void valueIncremented(IModel model, ModelEventArgs e);
}
// The Model interface where we can attach the function to be notified
when value
// is changed. An actual data manipulation function increment which
increments the value
// A setvalue function which sets the value when users changes the
textbox
public interface IModel
{
void attach(IModelObserver imo);
void increment();
void setvalue(int v);
}
public class IncModel : IModel
{
public event ModelHandler<IncModel> changed;
int value;
// implementation of IModel interface set the initial value to 0
public IncModel()
{
value = 0;
}
// Set value function to set the value in case if the user directly
changes the value
// in the textbox and the view change event fires in the controller
public void setvalue(int v)
{
value = v;
}
// Change the value and fire the event with the new value inside
ModelEventArgs
// This will invoke the function valueIncremented in the model and
will be displayed
// in the textbox subsequently
public void increment()
{
value++;
changed.Invoke(this, new ModelEventArgs(value));
}
// Attach the function which is implementing the IModelObserver so
that it can be
// notified when a value is changed
public void attach(IModelObserver imo)
{
changed += new ModelHandler<IncModel>(imo.valueIncremented);
}
}
}
Tying the model-view-controller together
The view class is the component which will really be the Form itself which will implement the
View interface.
In the below component it can be seen that the view interface just contains the event handler
when the view is changed and sets the controller which controls this view.
Collapse | Copy Code
namespace MVCTest
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//Application.Run(new Form1());
// Create the View, Model and Controller object yourself.
// Create a controller object with the concreate implementation
IncController
// and pass the view and model.
// Controller will store its associated model and view inside the
constructor
// View which is inside the form will tie the controller
automatically
// Run the view object created by you which is infact the form
Form1 view = new Form1();
IModel mdl = new IncModel();
IController cnt = new IncController(view,mdl);
Application.Run(view);
}
}
}
The Form
Finally, the form code which is the real view which implements the setController function for
the IView interface and keeps the reference to the controller. For any operation which it needs to
perform on the model, it will use the controller to signal it. We can see from the following code
that when the button is clicked, it calls the controller to increment the value. When the user types
in the textbox, it raises a view changed event which sets the new value into the model via the
controller as the controller has attached to this event from the view. The function implemented,
valueIncremented, is the implementation of the interface IModelObserver which will be
invoked by the model whenever a value changes in the model.
Collapse | Copy Code
namespace MVCTest
{
// Form is reallly the view component which will implement the
IModelObserver interface
// so that, it can invoke the valueIncremented function which is the
implementation
// Form also implements the IView interface to send the view changed
event and to
// set the controller associated with the view
public partial class Form1 : Form, IView, IModelObserver
{
IController controller;
public event ViewHandler<IView> changed;
// View will set the associated controller, this is how view is
linked to the controller.
public void setController(IController cont)
{
controller = cont;
}

public Form1()
{
InitializeComponent();
}
// when the user clicks the button just ask the controller to
increment the value
// do not worry about how and where it is done
private void button1_Click(object sender, EventArgs e)
{
controller.incvalue();
}
// This event is implementation from IModelObserver which will be
invoked by the
// Model when there is a change in the value with ModelEventArgs
which is derived
// from the EventArgs. The IModel object is the one from which
invoked this.
public void valueIncremented(IModel m, ModelEventArgs e)
{
textBox1.Text = "" + e.newval;
}
// When this event is raised can mean the user must have changed the
value.
// Invoke the view changed event in the controller which will call
the method
// in IModel to set the new value, the user can enter a new value and
the
// incrementing starts from that value onwards
private void textBox1_Leave(object sender, EventArgs e)
{
try
{
changed.Invoke(this, new
ViewEventArgs(int.Parse(textBox1.Text)));
}
catch (Exception)
{
MessageBox.Show("Please enter a valid number");
}
}
}
}
Points of Interest
This is my first submission to the CodeProject community. It is interesting to contribute to
CodeProject and I am looking forward to start contributing more and more samples in the area
where I have gained experience.

Vous aimerez peut-être aussi