Friday, March 4, 2011

MVP Design Patterns with WinForms

In this long awaited entry dear reader who is likely only me,  I’ll aim to educate thee with the above noted two design patterns. I’ll highlight Model View Presenter and Model View Controller the latter most typically deployed as web applications and show a few examples of them in action within a windows forms project.

Our focus will be on separating behavioral and synchronization code away from the view into presentation class objects following the Single Responsibility Principal (SRP) coined by Robert C. Martin closely.

Applying these principles will result in the ability for us to test the design in smaller granular increments and also test behavior in isolation. I would recommend reading the views of Fowler on both of these patterns. When all code is self-contained behind the form developers will commonly duplicate common logic throughout the application. This is a definite anti-pattern and a maintenance nightmare.

Model View Presenter

According to the definition in Wikipedia the Model View Presenter is a UI design pattern engineered to facilitate automated unit testing and to improve the separation of concerns in the presentation logic. Furthering on from Wikipedia here are the definitions of each term in the pattern.

  • Model – interface that defines the data that will be displayed and acted upon in the UI
  • View – interface that displays the data and routes user commands (events) to the presenter for it to act on
  • Presenter – acts upon the model and the view retrieving data from repositories (model), persists it and format it for display in the view. Known as the middle-man assuming the same role as served by the Controller in the MVC pattern

 

Pattern Variations

There are two main variants to this design pattern namely Passive View and Supervising Controller the latter appearing to be used mostly within web applications.  With the Passive View interaction with the Model (data) is handled exclusively by the Presenter. The View is updated exclusively by the Presenter. With Supervising Controller the View interacts with the Model for simple data binding and the View is updated by the Presenter via data-binding.

Example – Supervising Controller

Here is a look at the project within Visual Studio. I have moved assemblies into separate projects.

BlogMVPVSSetup

The separation allows us to let the front-end be completely agnostic. We can easily re-use the assemblies easily within with a web project which is flexible and a good thing! This also allows us to freely test the Presentation layer and domain layer for meeting requirements. This example will focus solely on the Supervising Controller. 

Here is a screenshot of what we are aiming for. This is not an overly complex UI.

BlogMVP1

We will send messaging upon the successful completion of a task. When the state is such that success occurs the edit panel will become hidden and the message panel will become visible to the user. This is all handled via a Boolean property contained within the view’s interface.

BlogMVPSave

Here is a simple class called Task that will become our Model.

    public class Task
    {
        public string Name { get; set; }
        public bool Completed { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime CompletionDate { get; set; }
        public void Save() {}
    }


Here is the interface that is passed into the constructor of our Presentation layer.



public interface ITaskScreen
    {
        string TaskName { get; }
        bool Completed { get; }
        DateTime StartDate { get; }
        DateTime CompletionDate { get; }
        bool IsTaskSaved { set; }
        event EventHandler<EventArgs> Save;
    }


Within the constructor we also wire up the data binding for our domain objects values stored in the form. The ViewSave method will set each of the properties in the Model to the values set in the form. After the save finishes we wire up our message and set the visibility of the two panels within the form to their respective required states.



    public class TaskScreenPresenter
    {
        private readonly ITaskScreen _view;
        public TaskScreenPresenter (ITaskScreen view)
        {
            _view = view;
            Initialize();
        }
        private void Initialize()
        {
            _view.Save += ViewSave;
        }
        private void ViewSave(object sender, EventArgs e)
        {
            Task task;
            try
            {
                task = new Task
                           {
                               Name = _view.TaskName,
                               StartDate = _view.StartDate,
                               Completed = _view.Completed,
                               CompletionDate = _view.CompletionDate
                           };
                task.Save();
                _view.IsTaskSaved = true;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
                throw;
            }
        }
    }


The code in the view is very compact and easy to get your head around. Lets have a look at that. The view references only the Presentation layer. This provides us with clear separation of responsibility. The view implements the ITaskScreen interface through the Presentation layer. When the view is loaded we call into the constructor of the Presentation layer through a private instance. The IsTaskSaved property sends messaging back to the UI notifying a successful Save action result.



    public partial class TaskUpdate : Form, ITaskScreen
    {
        private TaskScreenPresenter presenter;
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            presenter = new TaskScreenPresenter(this);
        }
        public TaskUpdate()
        {
            InitializeComponent();
        }
        private void btnSaved_Click(object sender, EventArgs e)
        {
            if (Save != null)
            {
                Save(this, EventArgs.Empty);
            }
        }
        public string TaskName
        {
            get { return txtTaskName.Text; }
        }
        public bool Completed
        {
            get { return chkComplete.Checked; }
        }
        public DateTime StartDate
        {
            get { return Convert.ToDateTime(txtStartDate.Text); }
        }
        public DateTime CompletionDate
        {
            get { return Convert.ToDateTime(txtCompleteDate.Text); }
        }
        public bool IsTaskSaved
        {
            set
            {
                if (!value) return;
                pnlEdit.Visible = false;
                pnlMessage.Visible = true;
            }
        }
        public event EventHandler<EventArgs> Save;
    }

No comments: