Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / Windows Forms
Article

A Practical Use of the MVC Pattern

Rate me:
Please Sign up or sign in to vote.
4.55/5 (41 votes)
8 Jan 2007CPOL5 min read 177.9K   2.7K   145   29
Another approach to the MVC pattern

Sample Image - MvcTest.gif

Table of Contents

  1. Introduction
  2. Background
  3. An Example
  4. Description of the MVC
  5. Benefits of the MVC
  6. Drawbacks
  7. How It Works / Dissecting the Sample Application
  8. Conclusions
  9. References
  10. History

1. Introduction

There are many articles here on The Code Project that detail the working of the MVC pattern. So you may ask why we need another one? In this article, I try to present a simple and clear use of the MVC pattern.

The demo project uses ZedGraph for generating the chart (http://www.codeproject.com/csharp/zedgraph.asp) and an
implementation of the BackgroundWorker found in .NET 2.0 ported for .NET 1.1 by Juval Lovy (http://www.idesign.net/).

2. Background

So What is the MVC Pattern?

As the GoF book states:

"MVC decouples views and models by establishing a subscribe/notify protocol between them. A view must ensure that its appearance reflects the state of the model. Whenever the model's data changes, the model notifies views that depend on it. In response, each view gets an opportunity to update itself. This approach lets you attach multiple views to a model to provide different presentations. You can also create new views for a model without rewriting it."

3. An Example

An example of the use of this pattern would be a reporting system where the same data needs to be presented to the user in different ways. Like an Excel sheet may be presented as columns and rows or by using a chart. Another need of this pattern as I encountered recently was the use of the same data in a desktop application and in a GUI developed for touch-screen devices.

The example in this article shows some test data in a listview and a chart representation using ZedGraph.
This design decouples the views from the model that generates the test data.

4. Description of the MVC

Most modern applications are separated into separate layers: presentation (GUI), business logic (how and why we do things), data layer (persistence).

The MVC pattern is separated similarly as a:

  • Model – manipulates the raw data that the application uses (calculations, enforcing business rules, querying the database, etc). The data provided by the model is independent of the visual representation – so it can be used by any number of views without code redundancy!
  • View – renders the model into a user interface (the visual representation of the data). The view is isolated from data operations.
  • Controller – processes and responds to events, defines the way the user interface (the view) and the model reacts to user input. The user triggers the events that change the model which in turn notifies the registered views to update/refresh their data.
    Mvc diagram

5. Benefits of the MVC

Because the model is decoupled from the view, it allows a great flexibility to implement the model using code reusability and modularity.

The inner working of the model can be changed any time without any effect on the view as long as the output remains the same.

Parallel development process for model and view!

6. Drawbacks

  • Adds more complexity to the development
  • Not suitable for small applications

7. How It Works /Dissecting the Sample Application

You could start by either developing the model or the view.
Let’s start by describing the model. I created an abstract base class for all models as follows:

C#
public abstract class BaseModel
{
    /// <SUMMARY>
    /// Event fired when the model is finished calculating/processing the data.
    /// </SUMMARY>
    public event EventHandler ModelChanged;
    /// <SUMMARY>
    /// Event fired when the model is calculating the data to notify the view
    /// about its progress.
    /// </SUMMARY>
    public event ProgressEventHandler ModelProgress;

    #region protected members
    protected BackgroundWorker backgroundWorker;
    protected DataSet ds;
    #endregion protected members

    #region constructor
    protected BaseModel()
    {
        ds = new DataSet();
        backgroundWorker = new BackgroundWorker();

        backgroundWorker.WorkerReportsProgress = false;
        backgroundWorker.WorkerSupportsCancellation = true;
    }
    #endregion constructor
    
    #region public abstract methods
    public abstract void GenerateReport();
    #endregion public abstract methods

    #region public methods
    /// <SUMMARY>
    /// Returns the report data.
    /// </SUMMARY>
    public DataSet QueryModel()
    {
        return ds;
    }

    public void CancelBackgroundWorker()
    {
        backgroundWorker.CancelAsync();
    }
    #endregion public methods

    #region event firing methods
    protected void Fire_ModelChanged(object sender, RunWorkerCompletedEventArgs ea)
    {
        BackgroundWorker bw = sender as BackgroundWorker;
        if (bw != null)
            bw.RunWorkerCompleted -= 
		new RunWorkerCompletedEventHandler(Fire_ModelChanged);

        if (ModelChanged != null)
            ModelChanged(this, EventArgs.Empty);
    }

    protected void Fire_ModelProgress(object sender, int percent)
    {
        if (ModelProgress != null)
            ModelProgress(this, percent);
    }
    #endregion event firing methods
}

The base class for models contains a BackgroundWorker and a dataset object. I use a modified BackgroundWorker implementation of Juval Lovy. This way we can do the calculations in a separate thread and have a responsive user interface. The dataset simply stores the test data that the view uses.

The Model_Income class derives from the BaseModel and adds a table with two columns to the dataset that is filled with test data in the overridden GenerateReport method.

There are two events that the BaseModel exposes:

  • ModelChanged – fired when the model has finished generating the data needed by the view.
  • ModelProgress – fired every time the model wants to notify the view of the progress made in calculating the data.
    The controller subscribes the views to these events exposed by the model.

Role of the Model: Generate the Data, Fire Events when Needed to Notify the View of its State.

The view is also derived from a base class BaseView that in turn exposes one or more events and public methods for calling from the controller.

C#
public class BaseView : Form
{
    public event EventHandler ViewClosed;

    #region Windows Form Designer generated code
    . . . 
    #endregion

    #region constructor
    public BaseView()
    {
        InitializeComponent();
        this.Closed += new EventHandler(OnViewClosed);
    }
    #endregion constructor

    #region form eventhandlers
    private void OnViewClosed(object sender, EventArgs ea)
    {
        if (ViewClosed != null)
        {
            ViewClosed(this, ea);
        }
    }

    private void butClose_Click(object sender, EventArgs e)
    {
        this.Close();
    }
    #endregion form eventhandlers

    #region public methods 
    /// <SUMMARY>
    /// Called from the controller to update the View when the Model 
    /// is finished loading the data.
    /// </SUMMARY>
    public void UpdateView(object sender, EventArgs ea)
    {
        BaseModel model = sender as BaseModel;
        if (model == null)
            return;

        DataSet ds = model.QueryModel();
        ContinueUpdateView(ref ds);

        progressBar.Visible = false;
    }

    /// <SUMMARY>
    /// Called from the controller to update the View's progressbar when
    /// the Model is reporting some progress.
    /// </SUMMARY>
    public void UpdateProgress(object sender, int percent)
    {
        progressBar.Visible = true;

        if (percent >= 0 && percent <= 100)
        {
            progressBar.Value = percent;
        }
        else
        {
            progressBar.Value = 0;
        }
    }
    #endregion public methods

    #region virtual methods
    /// <SUMMARY>
    /// This method could be declared as abstract (and then the whole 
    /// BaseView class as an abstract class) but then we lose the ability to edit the
    /// form in the form editor.
    /// </SUMMARY>
    /// <param name="ds"></param>
    protected virtual void ContinueUpdateView(ref DataSet ds)
    {
    }
    #endregion virtual methods
}

The actual implementation of the View overrides the ContinueUpdateView to visually show the data of the model.

Role of the View: Queries the Model for Data and Presents that Data to the User

What glues this whole thing together is the controller. The controller instantiates the model and the view and subscribes them for the events exposed by the other. There is also an abstract base class for different kinds of controllers:

C#
public abstract class BaseController
{
    protected abstract void SubscribeControllerToView();
    protected abstract void SubsctribeModelToView();
}

As the code shows, each controller has to make sure that the view’s events are catched and propagated by the controller to the view (SubscribeControllerToView) and vice versa the model events catched and propagated to the view (SubscribeModelToView).
For instantiating the views (the reports in this case), we just need to instantiate the proper controller like:

C#
RptController controller = new RptController(RptControllerType.List);

The controller knows by the parameter passed in to the constructor as to which view and model to use to create the report.

8. Conclusions

I think if the MVC pattern is used properly, it can speed up development time and also simplify the code complexity by separating the UI layer from the data layer.

Please keep in mind that this article is the first that I have ever written, so if some of the sentences seem to be long and unclear, it is my fault.;)

9. References

10. History

  • 8th January, 2007: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Romania Romania
I've did some programming for Macintosh a few years back and working with Microsoft technologies since then.

Comments and Discussions

 
QuestionWhat's in a name? Pin
B. Clay Shannon2-Jul-13 11:51
professionalB. Clay Shannon2-Jul-13 11:51 
Generalgood first article Pin
Donsw24-Jul-09 15:44
Donsw24-Jul-09 15:44 
GeneralVery Good~ Pin
kyokof30-Mar-09 3:55
kyokof30-Mar-09 3:55 
QuestionRefactoring Pin
paheli23-Dec-08 20:31
paheli23-Dec-08 20:31 
GeneralDiagram Pin
Muhammad Azam10-Nov-08 20:49
Muhammad Azam10-Nov-08 20:49 
GeneralGood article Pin
N a v a n e e t h17-Apr-08 2:28
N a v a n e e t h17-Apr-08 2:28 
GeneralRe: Good article Pin
Zoltan Balazs17-Apr-08 8:09
Zoltan Balazs17-Apr-08 8:09 
GeneralRe: Good article Pin
N a v a n e e t h17-Apr-08 18:16
N a v a n e e t h17-Apr-08 18:16 
GeneralNice article Pin
martin_hughes13-Apr-08 8:10
martin_hughes13-Apr-08 8:10 
GeneralRe: Nice article Pin
Zoltan Balazs13-Apr-08 21:31
Zoltan Balazs13-Apr-08 21:31 
GeneralIssue Closing the form during processing Pin
ZGers31-Oct-07 5:35
ZGers31-Oct-07 5:35 
GeneralRe: Issue Closing the form during processing Pin
Zoltan Balazs31-Oct-07 6:03
Zoltan Balazs31-Oct-07 6:03 
GeneralRe: Issue Closing the form during processing Pin
ZGers31-Oct-07 22:02
ZGers31-Oct-07 22:02 
GeneralRe: Issue Closing the form during processing Pin
Zoltan Balazs31-Oct-07 23:44
Zoltan Balazs31-Oct-07 23:44 
GeneralNice work!!! Pin
hav29a2-Oct-07 4:26
hav29a2-Oct-07 4:26 
GeneralRe: Nice work!!! Pin
Zoltan Balazs2-Oct-07 4:34
Zoltan Balazs2-Oct-07 4:34 
GeneralSuggestopns - Better implementation Pin
Tawani Anyangwe23-Apr-07 10:11
Tawani Anyangwe23-Apr-07 10:11 
GeneralA couple of minor suggestions for improvement regarding event handling Pin
Zoodor10-Feb-07 5:47
Zoodor10-Feb-07 5:47 
GeneralRe: A couple of minor suggestions for improvement regarding event handling Pin
Zoltan Balazs11-Feb-07 21:33
Zoltan Balazs11-Feb-07 21:33 
GeneralRe: A couple of minor suggestions for improvement regarding event handling Pin
Zoodor13-Feb-07 2:41
Zoodor13-Feb-07 2:41 
QuestionRe: A couple of minor suggestions for improvement regarding event handling Pin
Kamil Kwarciak24-Oct-07 6:46
Kamil Kwarciak24-Oct-07 6:46 
GeneralAnother suggestion.. Pin
alexdresko10-Jan-07 8:10
alexdresko10-Jan-07 8:10 
GeneralRe: Another suggestion.. Pin
Zoltan Balazs10-Jan-07 21:03
Zoltan Balazs10-Jan-07 21:03 
alexdresko wrote:
the view querying the model

This is done in the BaseView class:
public void UpdateView(object sender, EventArgs ea)
{
    BaseModel model = sender as BaseModel;
    if (model == null)
        return;

                // this is when we query the model (we ask for it's data)
    DataSet ds = model.QueryModel();
                // and as a response the view updates itself
    ContinueUpdateView(ref ds);

alexdresko wrote:
receiving notifications from the model

As in the RptController class:
protected override void SubsctribeModelToView()
{
    if (model == null)
        return;

    model.ModelChanged += new EventHandler(OnModel_ModelChanged);
    model.ModelProgress += new ProgressEventHandler(OnModel_ModelProgress);
}

        // the controller forwards the notification from the model
        // to the view (so the view receives notifications from the model!)
private void OnModel_ModelChanged(object sender, EventArgs ea)
{
    if (view == null)
        return;

    if (view.InvokeRequired)
    {
        view.Invoke(
            new GenericEventHandler(view.UpdateView),
            new object[] {sender, ea});
    }
    else
    {
        view.UpdateView(sender, ea);
    }
}

As I said earlier the controller stands in the center of this pattern and through it
happens everything.



company, work and everything else www.netis.ro

GeneralClarification.. Pin
alexdresko10-Jan-07 5:16
alexdresko10-Jan-07 5:16 
GeneralRe: Clarification.. Pin
Zoltan Balazs10-Jan-07 20:53
Zoltan Balazs10-Jan-07 20:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.