Page 1 of 1

Introducing MVP (Model-View-Presenter) Pattern (WinForms)

#1 andrewsw  Icon User is online

  • Fire giant boob nipple gun!
  • member icon

Reputation: 3459
  • View blog
  • Posts: 11,721
  • Joined: 12-December 12

Posted 21 March 2014 - 05:42 PM

*
POPULAR

This provides a basic introduction to the MVP pattern. I am not (yet) an expert on this subject but I struggled to find a straight-forward, introductory, example that I liked, so decided to build it myself. It should start you on the road to explore this, and other patterns, in more detail.

The examples I found were either too detailed-too quickly, or too simple to make much progress with. I like my example because it can easily be added to, and can likely be adapted to demonstrate other patterns as well.

Pre-requisites: You should be comfortable with WinForms, events, methods, properties, classes and interfaces. You might be familiar with events of WinForms, but perhaps less so with implementing interface events. I think you could still follow the tutorial but you might need to pause to read-up on this topic when you encounter it.

Note: Please also read any comments that might follow this tutorial. Other, more experienced, members may offer some useful insights or criticisms.

Initially you might just scan through the following reference material, revisiting it after the tutorial. The tutorial itself begins in the section Walking Through the Code.




Model-view-presenter :wikipedia

Attached Image

wikipedia said:

Model–view–presenter (MVP) is a derivative of the model–view–controller (MVC) software pattern, also used mostly for building user interfaces.
...

MVP is a user interface architectural pattern engineered to facilitate automated unit testing and improve the separation of concerns in presentation logic:

  • The model is an interface defining the data to be displayed or otherwise acted upon in the user interface.
  • The view is a passive interface that displays data (the model) and routes user commands (events) to the presenter to act upon that data.
  • The presenter acts upon the model and the view. It retrieves data from repositories (the model), and formats it for display in the view.

The term interface is being used here in a generic, or dictionary, sense: a point where two systems, or subjects, meet and interact.

There is a very detailed reference here:

Layered Application Guidelines :MSDN

My example follows the MVP, Passive View pattern, discussed here:

Comparison of Architecture presentation patterns :codeproject

codeproject said:

Passive view (PV)
Fundamentals about PV :-

  • State is stored in the view.
  • All logic of UI is stored in presenter.
  • View is completely isolated from the model. It [the presenter] also takes the extra task of synchronizing data between model and view.
  • Presenter is aware of the view.
  • View is not aware of the presenter. **see note

** This isn't entirely, or always, the case. Often the view can call a method of the presenter. This is discussed at a few points during the tutorial. In particular, the view is passive in the sense that it has no knowledge of the model.

The above is a very useful article, and has some nice diagrams. Reading the comments though, there is occasionally debate about the distinctions made between the different patterns. This is not unusual. Patterns are extremely useful but they are not a precise science and, in the real world, they do tend to overlap (and cause debate among developers).

What are MVP and MVC and what is the difference? :SO topic




Walking Through the Code

The section below (Outlining the Pattern) talks through the requirements of the MVP pattern, using Passive View, in detail. However, it will make more sense after I guide you through the code. A brief outline of what is going on is:

The view, meaning our form, does very little. It has properties that reflect the data that will be stored in the data-layer, and fires some specific events.

The model is usually very simple and in our case will be a single class that has properties that we want to store. We will store our data in a (non-persistent) List<> of this type. In a fuller example there will be a database (or other persistent storage) and the model would reflect those tables and fields that the application can work with.

The presenter does all the work: listening for events fired by the view and synchronizing the data displayed in the view (via its properties) with the data stored in the data-layer (via the model).

A note on our List<>:

Spoiler

Attached Image

The application is a simple task list. We can add new tasks, go back and forwards through them, and make changes to them. If we edit a task, then attempt to move to the next or previous task (or to start a new one) we will be required to either save these changes or discard them.

There is very little in the way of error checking, and no exception handling. I don't want these concerns to distract us from understanding the pattern itself. (The only validation is the requirement that the task must have a name, its description.) These are certainly things that I encourage you to explore and perhaps add to the application.

If you wish to develop the application further then you should consider persistent storage of the tasks, perhaps in a database. You could also consider requiring that the StartDate of a Task be on or before the DueDate, and that checking Completed requires a CompletionDate to be entered.

You might build the form-UI first, named frmTasks. I'm using the namespace SeparationMVP. These are the names of the controls:

Spoiler

The ComboBox cboPriority has values of Low, Medium, High.

Here is the very simple model, Task.cs:
namespace SeparationMVP {
    class Task {
        public string Name { get; set; }
        public string Priority { get; set; }
        public DateTime? StartDate { get; set; }
        public DateTime? DueDate { get; set; }
        public bool Completed { get; set; }
        public DateTime? CompletionDate { get; set; }
    }
}


The DateTime values are nullable?; that is, can store null. I started with DateTimePickers rather than TextBoxes for the date-values. DTPs don't allow a null value so I took a primitive approach using a default date of '1999-01-01'. This was messy and, although there are approaches to working with DTPs and nulls (such as placing a TextBox above the DTP), I didn't want these complications to obscure the details of the pattern.

Note that the model could include methods that the presenter can call. For example, a method to return a Task instance, or perhaps one to validate data before it can be saved as a Task.

Here is the interface that integrates the view with the model (ITaskLayer.cs):
namespace SeparationMVP {
    interface ITaskLayer {
        string TaskName { get; set; }
        string Priority { get; set;  }
        DateTime? StartDate { get; set; }
        DateTime? DueDate { get; set; }
        bool Completed { get; set; }
        DateTime? CompletionDate { get; set; }

        // communication/ messaging
        string StatusChange { set; }
        bool isDirty { get; set; }

        event EventHandler<EventArgs> SaveTask;
        event EventHandler<EventArgs> NewTask;
        event EventHandler<EventArgs> PrevTask;
        event EventHandler<EventArgs> NextTask;
    }
}


StatusChange is a simple message that the view can choose to ignore. I simply print the message to a label on the form (which could also help with debugging).

isDirty can be changed by either the presenter or view. The presenter changes it to true when a task has just been saved, or a different one loaded. The view changes it to false as soon as one of the control's-values has been changed.

Here is the presenter (TaskPresenter.cs):

Spoiler

From this presenter code..
    class TaskPresenter {
        private readonly ITaskLayer view;
        private List<Task> tasks;

        // (primitive) maintenance of state:
        private int currentIndex = 0;
        private bool isNew = true;

        public TaskPresenter(ITaskLayer view) {
            this.view = view;


The view is passed, and stored, in the presenter's constructor. Notice, importantly, that it is not the view, or form, itself that is important: it is an object that implements the interface. This indicates how the pattern can facilitate unit testing.

wikipedia said:

In computer programming, unit testing is a method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures are tested to determine if they are fit for use. Intuitively, one can view a unit as the smallest testable part of an application.

In a fuller example the model would represent a database or other persistent storage. I am just using a simple List<Task>. A List<T> is a very flexible object but it doesn't have a concept of currency. That is, it doesn't maintain, or understand, the concept of a current Task. (With formal data-binding there is a CurrencyManager Class.)

I am using two simple values of currentIndex and isNew to maintain the currency-state. That is, to know which Task we are currently on, and whether there is a next or previous task. The view's property of isDirty is also part of this process of maintaining currency-state.




This actually gives you a little insight into how some database operations work. You can obtain a cursor from a database, which is (simplistically) a set of records. You can move forwards, and possibly backwards, through the recordset (the set of records in the cursor), and the CP (cursor-position or current-pointer, but generally just referred to as the current record position) effectively tells you whether you have passed the end or beginning of the records. I will speculate that the cursor also maintains, internally, properties similar to our isDirty and isNew.

wikipedia said:

In computer science, a database cursor is a control structure that enables traversal over the records in a database.

Cursor (databases) :wiki




(further code from the presenter above)
        public TaskPresenter(ITaskLayer view) {
            this.view = view;
            Initialize();
        }
        private void Initialize() {
            tasks = new List<Task>();
            view.SaveTask += Save;
            view.NewTask += New;
            view.PrevTask += Previous;
            view.NextTask += Next;
            BlankTask();
            view.StatusChange = "Ready";
        }


Initialize() instantiates List<Task> (our data-store) and registers event-listeners with the events exposed by the interface.
        private void BlankTask() {
            view.TaskName = String.Empty;
            view.Priority = "Low";
            view.StartDate = null;
            view.DueDate = null;
            view.Completed = false;
            view.CompletionDate = null;
        }


This sets the initial state of the view by changing the exposed properties. There is no corresponding method for the model; instead, this is achieved just by setting isNew to true. There is a similar method named loadTask() (at the bottom).

Please read through the rest of the presenter's code, it should be fairly easy to follow.

Here is the code for the form (the view) itself (frmTasks.cs):

Spoiler

    public partial class frmTasks : Form, ITaskLayer {
        private TaskPresenter presenter;
// ...
        private void frmTasks_Load(object sender, EventArgs e) {
            presenter = new TaskPresenter(this);
            this.isDirty = false;
        }


The form implements the interface and stores a reference to a new presenter instance, passing itself in its constructor. (You could consider this as a signature of the MVP pattern, although there are slight variations on it.)
        public string Priority {
            get { return cboPriority.Text; }
            set { cboPriority.Text = value; }
        }
        public DateTime? StartDate {
            get {
                if (string.IsNullOrWhiteSpace(txtStartDate.Text))
                    return null;
                else
                    return DateTime.Parse(txtStartDate.Text);
            }
            set {
                if (value == null)
                    txtStartDate.Text = String.Empty;
                else
                    txtStartDate.Text = value.Value.ToShortDateString();
            }
        }


These correspond the values displayed in the form with the data required by the model, through the form's exposed properties. They also correspond empty-text in the textboxes with null required by the DateTime fields.

For this simple example you MUST key a valid date in the date-textboxes, otherwise an exception occurs. This raises the spectre of validation and where this should occur. This is discussed further in the section below, Thoughts on Validation.
        public string StatusChange {
            set { lblStatus.Text = value; }
        }

        public bool isDirty { get; set; }

        public event EventHandler<EventArgs> SaveTask;
        public event EventHandler<EventArgs> NewTask;
        public event EventHandler<EventArgs> PrevTask;
        public event EventHandler<EventArgs> NextTask;


When the status is changed by the presenter we just display the message in a label.

isDirty could, I suppose, be called a free variable. It is appropriate, in my opinion, for this to be changed by either the presenter or the view. You might notice, though, that the presenter only ever sets this to false - when a task is successfully saved, or a new, previous or next task is shown. The form sets it to true as soon as any of the currently displayed values are changed.

The (public) events are declared but they are not defined (assigned to) in the view. It is the presenter that listens for these events. While it is possible for the view to also assign to these events this, firstly, adds a level of complexity, but also breaks the pattern that we set out to follow.
        private void btnSave_Click(object sender, EventArgs e) {
            // some basic validation
            if (string.IsNullOrWhiteSpace(txtTask.Text)) {
                MessageBox.Show("Enter the task name/description.", "Task Detail",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                txtTask.Focus();
                return;
            }
            if (SaveTask != null) {
                SaveTask(this, EventArgs.Empty);
            }
        }


This demonstrates the kind of simple validation that belongs in the view. The role of this textbox on the form is that it shouldn't be empty, regardless of any further requirements of the model.
        private void txtTask_TextChanged(object sender, EventArgs e) {
            this.isDirty = true;
        }
        private void cboPriority_SelectedIndexChanged(object sender, EventArgs e) {
            this.isDirty = true;
        }


Any changes to the current task's details will set isDirty to true. When any attempt is made to move away from the current task then the user will be asked to confirm discarding of these changes, otherwise they will have to save the task.

Give it a go!

You can now build and run the application.

  • Type the details for a new task and press Save.
  • Once you save a task you can press New.
  • You can only go to a next or previous task if one exists.
  • If you change some detail of a task then you will either have to save it or discard the change(s) in response to the MessageBox that appears.




Outlining the Pattern

The view means our form. With our passive view approach the view/form should be, as you would guess, passive: an empty shell. It stores state by having properties that mirror those required by the data-model. It doesn't, itself, modify these properties directly - that is the job of the presenter.

An alternative to the passive view is the supervising controller pattern.

Spoiler

The view communicates (passively) with the presenter using the event model. That is, the view fires a specified set of events that the presenter listens for. The list of these events are those which make sense to the model; that is, that require some interaction with the model (via the presenter). For example, NewRecord, EditRecord, SaveRecord, etc..

To ensure that the view will provide the appropriate properties and events - that is, those that enable the view to work with the model - it implements an interface. This interface effectively provides a common-language (or a communication or messaging layer) between the model and view.

The form's constructor creates and stores an instance of the presenter, passing itself as a reference in the presenter's constructor. The purpose of this is simply to provide the presenter with an object of the correct interface. Notice in the form's code that private TaskPresenter presenter; is never used or referenced anywhere else, other than in the constructor. This is a (tacit) requirement, or assumption, of the Passive View pattern, that the view has no knowledge of the presenter. But..

This is where it becomes important to realise that these patterns are a guide. They are not 'written in stone' and if you decide to follow a particular pattern it does not mean that you have to follow it in every detail, and at the expense of simpler code. It is also common for patterns to be mix-n-matched and to overlap.

In many MVP examples the view will call methods of the presenter, rather than rigidly sticking to the event-driven model. Doesn't this break the pattern? It means that the view now knows about the presenter; they are no longer de-coupled. Passive View means that the view is unaware of the model so, in this sense, calling methods of the presenter doesn't break the pattern, although some would dispute this. This is discussed further here:

View to Presenter Communication :codebetter.com

In brief I will say that calling a method of the presenter is often much simpler than sticking rigidly to the event-driven approach. For example, an event of the view could cause execution of some code in the presenter, which then needs to change a property of the view and, possibly, to fire an event of the presenter that the view listens for (another coupling).

Anyway, to return to our example, all the presenter does (or is required to do) is to store a reference to the view (or, more specifically, the interface) and attach event-listeners to those events exposed by the view's interface. In these listeners the presenter has access to both the model (in our case, by means of private List<Task> tasks;) and properties of the form. This is were the synchronization occurs, between the data displayed in the view and changes (to the data-layer) via the model.

Note that in other MVP examples you will find that an instance of the model is passed to the presenter-constructor, along with an instance of the view. This isn't necessary for our example as our (simple) data-layer is contained in List<Task>, which is maintained internally by the presenter.

Comparing MVC (model view controller) and MVP

MVP is considered a variant of the MVC pattern. I won't pursue this any further here, other than with a broad statement:

In MVC the controller sits above the model and view, controlling or marshalling activity. Controlling is the wrong word, mediating is better. You should investigate this further.
In MVP the presenter sits between the model and view, acting as a bridge, or conduit, between them.

Everything You Wanted To Know About MVC and MVP But Were Afraid To Ask
Twisting the Triad – MVC
Twisting the MVC Triad

Thoughts on Validation

This tutorial is already complete and I recommend that you pursue these patterns (MVP, MVC, etc.) further, starting with some of the links that I have provided. The following is just me thinking out loud about validation. You might find it interesting or, more likely, confusing. I am not sufficiently experienced to tutor you on this subject so, if it interests, or concerns, you, please investigate it further using the links provided, or other resources.

Sensible validation:

  • The task name/description shouldn't be empty (we have this code already)
  • The dates should be dates (I'll concentrate on this requirement)
  • The DueDate should be on or after the StartDate
  • There should, or shouldn't be, a CompletionDate depending on whether Completed is ticked

Where should this validation occur? In the view, the presenter, the model, or some combination of these? The answer isn't obvious (at least to me) and there seems to occur a lot of debate about this. This is, again, where we need to bear in mind that patterns are a guide and we are the ultimate arbiters of where, and how, validation occurs.

I have a suspicion that this validating-issue may be more pronounced with the MVP pattern, perhaps with other patterns the decision is more clear-cut.

An esteemed colleague:

Spoiler

Most often there is a database involved. The top-level of validation should occur with the database design, using:

  • Specific/correct data-types
  • NOT NULL
  • Primary and foreign keys
  • Unique indexes
  • Constraints

This is crucial. It won't matter what application is built on-top of the database, the database simply won't allow invalid data beyond its walls.

The textboxes are named txtStartDate, txtDueDate etc. They are specifically intended to accept only valid date-values. In my opinion this simple level of validation belongs in the view.

These could be DateTimePickers although, as mentioned earlier, we have to deal with null values (which DTPs don't accept) in some way. Or perhaps MaskedTextBoxes. WinForms already have a validation feature (see CausesValidation, Validating, etc.) or we could choose to implement this ourselves using TextChanged and other events. (Currently we only use the TextChanged events to set isDirty to true.)

I favour a single isValid() routine in the view. This will return true or false. As our example currently stands, though, this method cannot (and shouldn't) use the form's properties, as these are already defined as DateTime values. (If the controls didn't contain valid dates then attempting to read these values via the properties would already create errors, defeating our attempt at validation.)

isValid() can refer directly to the form-controls because they are comfortably within the form's domain. The method can also use MessageBoxes and cause the focus to change (before returning true or false) because, again, this is all within the form's comfort zone.

Spoiler

Something like this:

Spoiler

This method will be called before firing some of the events of the interface. In fact, for our example, it only needs to be called before SaveTask.

You could, if you prefer, use TextChanged, etc., to validate each control individually, rather than waiting to run a single isValid() method. This is perfectly fine but you would still need to account for combinations of controls (e.g. txtStartDate and txtDueDate) being correct collectively.

An alternative approach, which is more in-line with the pattern, is to include this method in the interface and for the presenter to call this method (view.isValid()) before attempting to make changes to the model. I am happy with this approach as well: the view/form can piddle-around with MessageBoxes, focus, etc., with the only requirement being that it eventually return either true or false. What we want to avoid is duplicating all this effort from both the view and the presenter (and possibly from the model as well).

As Skydiver mentions, the supposed correct way to deal with this in MVP is to throw the data at the model (via the presenter) and let the model throw exceptions that we can handle. In which case, we might not even assign data-types to the view's properties, they could largely be strings. The presenter would handle the exceptions and (somehow) convert them into meaningful information for the view. This is obviously possible but seems a lot of work.

Rather than dealing with exceptions the model could contain the method isValid(). The view's properties could be mainly strings, and these values would be passed to the isValid() method by the presenter. It might be messy to pass this clump of data but this approach could prove easier to implement than the exception handling. For example, we could create our own errors class that we can use to get back some useful details, that the presenter can make good use of to update the view.




I will stress again that these are just my current thoughts on validation, and I apologise if I have confused! Concentrate on the pattern demonstrated in the main tutorial, which I hope provides a good start to discover MVP and other patterns. Andy.

Added: There is another useful article on MVP here from Informatech:

UI Design Using Model-View-Presenter

This post has been edited by andrewsw: 25 March 2014 - 08:11 AM


Is This A Good Question/Topic? 6
  • +

Replies To: Introducing MVP (Model-View-Presenter) Pattern (WinForms)

#2 andrewsw  Icon User is online

  • Fire giant boob nipple gun!
  • member icon

Reputation: 3459
  • View blog
  • Posts: 11,721
  • Joined: 12-December 12

Posted 22 March 2014 - 08:21 AM

If you want to continue the application, and perhaps make it something you might use ;), you can investigate serializing the tasks. Serialize List :dotnetperls. Continuing with the same pattern it isn't too tricky to incorporate. Here is some sample code:

Tasks.cs
namespace SeparationMVP {
    [Serializable()]            // add this line/attribute
    class Task {
        public string Name { get; set; }
        public string Priority { get; set; }
        public DateTime? StartDate { get; set; }
        public DateTime? DueDate { get; set; }
        public bool Completed { get; set; }
        public DateTime? CompletionDate { get; set; }
    }
}

TaskPresenter.cs
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

// etc.
        private void Serial(object sender, EventArgs e) {
            try {
                using (Stream stream = File.Open("tasks.bin", FileMode.Create)) {
                    BinaryFormatter bin = new BinaryFormatter();
                    bin.Serialize(stream, tasks);
                }
            } catch (IOException) {
                throw new NotImplementedException();
            }
        }
        private void DeSerial(object sender, EventArgs e) {
            try {
                using (Stream stream = File.Open("tasks.bin", FileMode.Open)) {
                    BinaryFormatter bin = new BinaryFormatter();
                    tasks.Clear();
                    tasks = (List<Task>)bin.Deserialize(stream);
                }
            } catch (IOException) {
                throw new NotImplementedException();
            }
        }


This post has been edited by andrewsw: 22 March 2014 - 08:24 AM

Was This Post Helpful? 0
  • +
  • -

Page 1 of 1