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

WinForms MVP - An MVP Framework for WinForms

Rate me:
Please Sign up or sign in to vote.
4.86/5 (40 votes)
20 Jan 2014CPOL12 min read 186.8K   7.9K   170   49
A basic introduction to the WinForms MVP framework and how to use it.

Introduction 

This article is not about the Model View Presenter (MVP) pattern itself. This article describes how to use a specific implementation of the MVP pattern, i.e. WinformsMVP (samples folder included).

WinForms MVP is an implementation of the MVP pattern for the WinForms platform. I wrote this framework because I felt that the MVP pattern was an excellent choice for WinForms, and when I was looking around for one to use for WinForms, I couldn’t find one which I was happy with. I already knew that WebFormsMVP existed, so I set out on an Endeavour to port that to WinForms.

There is an example project which accompanies this article in the download code. The code which I will describe in this article can be found in that example project.

UPDATE: The biggest problem with this framework to date has been the fact that there is no Visual Studio designer support for the generic forms. That is neither a bug in the framework nor Visual Studio. Visual Studio simply doesn’t support it. I have now addressed that directly by adding a non-generic MvpForm. In addition to that, a developer from the community has also come up with a workaround which enables designer support (see N Meakins comment in the thread entitled Vs Designer Can not suport Generic Form below).

UPDATE 2: There is one other feature which I have now added to the framework that was long overdue - support for proper dependency injection. WinformsMVP supports two dependency injection libraries, Unity and StructureMap. There are now two new projects in the source code, one for each of those dependency injection libraries.

I have created another very small sample application called LicenceTracker which demonstrates the usage of the new forms and of the workaround suggested by N Meakins, as well as how to inject services into presenters using the new dependency injection features for Unity. I’ve now set out the different options you have in creating forms with Visual Studio designer support at then end of this article. Following that, I've included an explanation of how use the new dependency injection features.

You can download that new sample application code here - Licence Tracker Application.

Getting Started

We’ll jump right in and get started by creating a class for our model:

C#
public class MainViewModel
{
    public IList<KeyValuePair<Type, String>> MenuItems { get; set; }
}

As you can see, this model contains a single member which will be used to populate a ListBox on the main form which we will create. The next thing we need to create is an interface which will represent our View. This interface will implement the strongly-typed interface WinFormsMvp.IView, which will be strongly-typed to our model MainViewModel:

C#
public interface IMainView : IView<MainViewModel>
{
    event EventHandler CloseFormClicked;
 
    void Exit();
}

There’s a few more members which we get from the framework. The interface WinFormsMvp.IView<TModel> gives us the member:

C#
TModel Model { get; set; }

(Note, you can view the contents of these interfaces by downloading the source of the framework at http://winformsmvp.codeplex.com/, or by de-compiling the DLL with something like JustDecompile or dotPeek).

And that interface implements the WinFormsMvp.IView interface, which gives us the following two members:

C#
bool ThrowExceptionIfNoPresenterBound { get; }
 
event EventHandler Load;

Don’t worry too much about the ThrowExceptionIfNoPresenterBound property for the purposes of this article. However, the Load event will be used in all of the forms and controls.

A View

The next thing which we need to create is the view. In this case, we will create a class that inherits from WinFormsMvp.Forms.MvpForm<TModel>. That class inherits from the standard System.Windows.Forms.Form class, i.e., your standard, vanilla WinForm. Our new form will also implement the interface which we created above, IMainView. Once we implement that interface, our form will look something like this:

C#
public class MainView : MvpForm<MainViewModel>, IMainView
{
    #region Private Variables
    private Button exitButton;
    private ListBox menuListBox;
    private Label menuTitleLabel;
    private MvpUserControl<InfoControlModel> panel;
    private bool firstLoad = true; 
    #endregion
 
    #region IMainView members
 
    public event EventHandler CloseFormClicked;
 
    public void Exit()
    {
        Close();
    } 
        #endregion

I will go into the rest of that class further on in the article. But for now, just take note of the way it inherits the strongly-type MvpForm class and implements the IMainView interface.

A Presenter 

We now need a presenter for our view. I’ll just say a quick word about plumbing. For this Presenter, we are going to perform the binding by convention. You will see in the example project that the MainView form is created in a folder called Views. We will place the Presenter (which we are about to create) in a folder called Presenters. The way the convention-based binding works is to look in the following places for a Presenter which has the same prefix as the View:

"{namespace}.Logic.Presenters.{presenter}",
"{namespace}.Presenters.{presenter}",
"{namespace}.Logic.{presenter}",
"{namespace}.{presenter}"

That is, if the View is called MainView, it will look for a Presenter called MainPresenter, the common prefix being "Main". In the example project, the Presenter called MainPresenter was created in the Presenters directory, which is one of the places that the convention-binder will look to find it. The Presenter looks like this:

C#
public class MainPresenter : Presenter<IMainView>
{
    public MainPresenter(IMainView view) : base(view)
    {
        View.CloseFormClicked += View_CloseFormClicked;
        View.Load += View_Load;
    }
 
    void View_CloseFormClicked(object sender, EventArgs e)
    {
        View.Exit();
    }
 
    private void View_Load(object sender, EventArgs e)
    {
       View.Model = new MainViewModel
       {
         MenuItems = new List<KeyValuePair<Type, string>>
        {
            new KeyValuePair<Type, string>(typeof (FirstInfoControl),
                                                    "FirstInfoContol"),
            new KeyValuePair<Type, string>(typeof (SecondInfoUserControl),
                                                    "SecondInfoUserControl")
        }
       };
    }
}

The framework injects the View into the constructor which passes it to the base constructor, where the framework assigns it to the View property of the Presenter<TView> class. But you do not need to worry about that. So long as you conform to the conventions (i.e., use a common prefix and create your files in the correct directories), the presenter-binder will be able to bind the Presenter to the View by convention.

We can then hook up the two events which are members of our View by virtue of the IMainView interface (and the IView interface, as discussed above). In the View_Load handler, we assign a model to the View’s Model property. We then call the Exit method when the CloseFormClicked handler is invoked.

Back to the View 

The event handler for the Click event of the exit button shows the textbook usage of the MVP pattern, whereby a handler on the View raises an event (CloseFormClicked) to the Presenter which subscribes to it (see the above paragraph on the Presenter where that subscription is shown). The Presenter can then do whatever it needs to do to organise state, and then calls a method on the View (Exit) to effect the closing of the form. You will see this pattern of raising events to the Presenter with the MvpUserControls as well (see below).

The layout of the View will be very simple. It will merely comprise one ListBox and a UserControl. The type of the UserControl will be determined by which item in the ListBox is selected i.e. when the user selects an item in the ListBox on the left, the corresponding UserControl will be displayed on the right.

The contents of the ListBox will be populated during the OnLoad handler of the form. You will recall that we assigned an object to the Model property of the View in the Presenter. Now, we can assign the MenuItem's property of the Model to the DataSource of the ListBox.

When the user clicks an item in the ListBox, the SelectedIndexChanged handler instantiates an object of the type selected in the ListBox:

C#
Type typeOfControlToLoad = ((KeyValuePair<Type, string>)menuListBox.SelectedItem).Key;
 
//  The next call creates the usercontrol. The presenter binding for the UserControl occurs now as it is instantiated.
//  Place a break point in the constructor of the relevant presenter to observe it's instantiation.
panel = (Activator.CreateInstance(typeOfControlToLoad) as MvpUserControl<InfoControlModel>);

This presents me with the opportunity to demonstrate how to work with MvpUserControls; a class in the framework which inherits from UserControl and is designed to be used in conjunction with the MVP pattern.

Working with MvpUserControls

Working with the MvpUserControl class is us the same as with the MvpForm. First, we will need a Model and we will use a simple one which just displays a message:

C#
public class InfoControlModel
{
    public string Message { get; set; }
}

Second, we will create a contact for the View:

C#
public interface IFirstInfoView : IView<InfoControlModel>
{
    event EventHandler PanelClicked;
 
    void ClearPanel();
}

Next, we will implement the members of the contract in the View (note, for brevity I have not included all the code for the View here. You can see the rest of the code for the View in the download code):

C#
public class FirstInfoControl : MvpUserControl<InfoControlModel>, IFirstInfoView
{
    void InfoClick(object sender, EventArgs e)
    {
        PanelClicked(this, EventArgs.Empty);
    }
 
    public event EventHandler PanelClicked;
 
    public void ClearPanel()
    {
        infoLabel.Text = string.Empty;
    }
...
}

And finally, we will create a Presenter which hooks up the events of the View interface:

C#
public class FirstInfoPresenter : Presenter<IFirstInfoView>
{
    public FirstInfoPresenter(IFirstInfoView view) : base(view)
    {
        View.Load += View_Load;
        View.PanelClicked += View_PanelClicked;
    }
 
    void View_PanelClicked(object sender, System.EventArgs e)
    {
        View.ClearPanel();
    }
 
    void View_Load(object sender, System.EventArgs e)
    {
        View.Model = new InfoControlModel { Message = "Convention bound;This control's presenter was bound by convention. The View is called FirstInfoControl and lives in the Views directory. The Presenter is called FirstInfoPresenter and lives in the Presenters directory. Both classes have the prefix \"FirstInfo\". As the View's name ends in \"Control\" and the Presenter's name ends in \"Presenter, the binder has enough information to perform the binding without any specific/express binding (i.e. outside of the framework itself)." };
    }
}

As you can see, working with an MvpUserControl is virtually the same as working with the MvpForm in terms of coding to the MVP pattern, using this framework. But where are we left in circumstances where, for some reason, we are not able to bind the Presenter to the View by convention? The next section covers that scenario.

Binding With Attributes 

There is another way of binding using the framework in the event that following the convention described above is not possible/practical in your project. The framework supports the use of attributes to bind a View with a Presenter. For the purposes of demonstration, I have used a second UserControl which will perform the binding by way of attributes. The way to do this is simply to decorate the class name of the UserControl with an attribute as so:

C#
[PresenterBinding(typeof(PresenterOfSecondInfo))]
public class SecondInfoUserControl : MvpUserControl<InfoControlModel>, ISecondInfoView
{
…
}

This will result in the SecondInfoUserControl View being bound to the PresenterOfSecondInfo Presenter. As you can see, the name of the Presenter does not comply with the convention described above. As such, the only way to bind it is to use the PresenterBinding attribute. So when the user clicks on the ListBox for the second UserControl, a SecondInfoUserControl object will be created and the Presenter will then be bound. The full code for that UserControl and Presenter is included in the download code.

Conclusion

The WinForms platform is still alive and kicking in many production environments. And it is also still a viable platform for future development (although the lion’s share will be done in more recent platforms such as Windows 8 or WPF). The whole reason I wrote this framework is because I was tasked with supporting a system which included a raft of small WinForms applications.

WinForms MVP is ideal for smaller WinForms applications and will also be useful as a stepping stone for developers who want to learn how to program against the MVP pattern. This article has set out the basics of how to use the WinForms MVP framework in the WinForms programming environment. You can see more usage examples in the example project that accompanies the source code of the framework, which can be downloaded at WinForms MVP.

Update - Visual Studio Designer Support

Using the New Non-Generic Forms

The following steps set out how to go about creating a form using the new non-generic MvpForm.

  1. Create an Interface for your view. This time, inherit from IView, rather than the generic version IView<tmodel></tmodel>.
  2. Right-click the Views folder and select Add > Windows Form from the context menu (and name it and hit enter)
  3. Press F7 to get to the code-behind and change the parent class from Form to MvpForm (the non-generic version). Ensure that it implements the Interface from Step 1.
  4. Create the presenter in the normal way.
C#
    public interface IAddProductView : IView
    {
        event EventHandler CloseFormClicked;
        event EventHandler AddProductClicked;

        int Id { get; set; }
        string Description { get; set; }
        string Name { get; set; }
        int TypeId { get; set; }
        Dictionary<int,> SoftwareTypes { get; set; }

        void Exit();
    }
</int,>
C#
public partial class AddProductView : MvpForm, IAddProductView
{
    ...
}
C#
    public class AddProductPresenter : Presenter<iaddproductview>
    {
        private readonly ISoftwareService softwareService;
        private AddProductModel model;

        public AddProductPresenter(IAddProductView view)
            : base(view)
        {
            View.CloseFormClicked += View_CloseFormClicked;
            View.Load += View_Load;
            View.AddProductClicked += View_AddProductClicked;
            softwareService = new SoftwareService();
            model = new AddProductModel { AllSoftwareTypes = softwareService.GetSoftwareTypes().ToList() };
        }

        void View_AddProductClicked(object sender, EventArgs e)
        {
            model.NewSoftwareProduct = new Software
            {
                Description = View.Description,
                Name = View.Name,
                TypeId = View.TypeId,
            };
            softwareService.AddNewProduct(model.NewSoftwareProduct);
            View.Id = model.NewSoftwareProduct.Id;
        }

        void View_Load(object sender, EventArgs e)
        {
            Dictionary<int,> softwareTypes = new Dictionary<int,>(model.AllSoftwareTypes.Count);

            foreach (var softwareType in model.AllSoftwareTypes.Select(x => new KeyValuePair<int,>(x.Id, x.Name)))
            {
                softwareTypes.Add(softwareType.Key, softwareType.Value);
            }

            View.SoftwareTypes = softwareTypes;
        }

        void View_CloseFormClicked(object sender, EventArgs e)
        {
            View.Exit();
        }
    }
</int,></int,></int,></iaddproductview>

In this example, you can see that the interface IAddProductView does not contain any of our domain entity types. The properties which it contains are made up of the atomic parts of the various entities required to create a new Software product. They are View-centric in nature.

Looking at the presenter AddProductPresenter, it knows about the Model (it has an AddProductModel model variable as a private member). However, the AddProductView view itself does not know about the Model. It exposes a set of properties via the IAddProductView interface, which enables the presenter to both:

  1. display a list of SoftwareTypes (for the user to choose from);
  2. access a set of values which are set by the user which can be used to create the new software product.

This is a very pure example of the MVP pattern whereby the Presenter knows about both the Model and the View, but neither the View nor the Model know about one another. In this style of MVP, the Model is less of a ViewModel and more of a domain Model in the true sense.

Using the New Non-Generic Forms and Manually Adding a Model

Another approach that you can take using the new non-generic forms is by implementing an Interface which inherits from the IView<TModel> interface. This will result in the view having a Model property. But, you need to add the Model property yourself. Refer to the AddSoftwareType form in the LicenceTracker sample app to see this approach (see the update paragraph near the top of the article for the download link).

Using the Workaround Which Enables Designer Support for the Generic Forms

In the Add Person functionality of the new LicenceTracker example app, the workaround which enables you to use the generic MvpForm<TModel> can be seen. The steps to implement this are as follows:

  1. create a new form in your project and make it inherit MvpForm<TModel> (in the LicenceTracker sample app, it inherits MvpForm<AddPersonModel>). See the figures below. This form will be an intermediate form which sits between the generic MvpForm and the form which you actually intend to manipulate in the designer.
  2. create another new form and make this form inherit from the form which you created an step 1 above. This form will be the one which you intend to manipulate in Visual Studio’s designer, enabling you to drag on controls with immediate visual feedback.
  3. makes the form which you created in step 2 implement the interface which you create for the View.

And now you will have the designer support with the goodness of a strongly typed form. My thanks to N Meakins for his contribution of this workaround.

C#
    public partial class AddPersonViewSlice : MvpForm<addpersonmodel>
    {
        public AddPersonViewSlice()
        {
            
        }
    }
</addpersonmodel>
C#
public partial class AddPersonView : AddPersonViewSlice, IAddPersonView
{
    public AddPersonView()
    {
        InitializeComponent();
    }

    private void CloseFormButton_Click(object sender, System.EventArgs e)
    {
        CloseFormClicked(this, EventArgs.Empty);
    }


    public event System.EventHandler CloseFormClicked;

    public event System.EventHandler AddPersonClicked;

    public void Exit()
    {
        Close();
    }

    private void AddPersonButton_Click(object sender, EventArgs e)
    {
        Model.NewPerson.FirstName = FirstNameTextBox.Text.Trim();
        Model.NewPerson.LastName = LastNameTextBox.Text.Trim();

        AddPersonClicked(this, EventArgs.Empty);
    }
}

How to Inject a Service into a Presenter

In this example, I am going to use the Unity dependency injection container to inject a service into a presenter. Before writing any code, you need to include the following libraries in your solution:

  1. Unity (which can be downloaded using Nuget);
  2. WinFormsMvp.Unity.dll

With those libraries available to us, we need to create a UnityContainer object which we will use to register the types which we want to instantiate from the Interface which they will be implementing. The use of ContainerControlledLifetimeManager will create that service as a singleton. If you do not want it to be a singleton, use TransientLifetimeManager. Now that we have registered all of the types which our container will produce, we need to set the static Factory property of the PresenterBinder class. That is easily done by passing the container to the constructor of the UnityPresenterFactory object which we assign to that static Factory property. Place the following code in the Main method of the Program class:

C#
	_unityContainer = new UnityContainer();

	_unityContainer.RegisterType<isoftwareservice,>(new ContainerControlledLifetimeManager());
	PresenterBinder.Factory = new UnityPresenterFactory(_unityContainer);
</isoftwareservice,>

Our presenter's constructor will now look like (the private field softwareService is also shown):

C#
private readonly ISoftwareService softwareService;

public AddPersonPresenter(IAddPersonView view, ISoftwareService softwareService)
    :base(view)
{
    this.softwareService = softwareService;
    View.CloseFormClicked += View_CloseFormClicked;
    View.AddPersonClicked += View_AddPersonClicked;
    View.Load += View_Load;
}

The software service is injected by the PresentBinder’s factory, which as taken on the responsibility of instantiating the service. You can see this in action in the example project.

History

Article

Version Date Summary
1.0 06 Feb. 2013 Original published article.
1.1 29 Nov. 2013 Added parts about Visual Studio Designer support.
1.2 20 Jan. 2014 Added parts about Dependency Injection.

License

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


Written By
Software Developer
Australia Australia
A mid-level developer writing .NET code to provide business solutions to a wide range of customer requirements.

Comments and Discussions

 
QuestionThis is a bit dated but I felt I had to add my opinion Pin
Member 1356598922-Oct-18 7:57
Member 1356598922-Oct-18 7:57 
AnswerRe: This is a bit dated but I felt I had to add my opinion Pin
abyjet9-Jun-19 4:45
abyjet9-Jun-19 4:45 
QuestionIt seems very powerful but... Pin
marcelo.nicolet22-Jun-17 12:30
marcelo.nicolet22-Jun-17 12:30 
QuestionDependency injection customization Pin
DevForRent6-Jan-17 18:39
DevForRent6-Jan-17 18:39 
QuestionMVP navigation with WinForms MDI Pin
Hemant Sathe19-Nov-16 9:00
Hemant Sathe19-Nov-16 9:00 
QuestionAsk for help Pin
Member 103795028-Oct-16 8:41
Member 103795028-Oct-16 8:41 
QuestionFive stars from me but why? Pin
netizenk10-Dec-15 7:53
professionalnetizenk10-Dec-15 7:53 
AnswerRe: Five stars from me but why? Pin
Chris Copeland15-Dec-15 2:07
mveChris Copeland15-Dec-15 2:07 
AnswerRe: Five stars from me but why? Pin
marcelo.nicolet22-Jun-17 11:09
marcelo.nicolet22-Jun-17 11:09 
QuestionPresenter Namespace Pin
Ruedi471128-Jun-15 0:05
Ruedi471128-Jun-15 0:05 
QuestionWinform MVP Framework - use SCSF Pin
Member 115857186-Apr-15 17:29
Member 115857186-Apr-15 17:29 
GeneralThis is good way. Forms are very fast with latest patterns and solutions. Pin
Krzych772-Oct-14 21:47
Krzych772-Oct-14 21:47 
GeneralRe: This is good way. Forms are very fast with latest patterns and solutions. Pin
David Rogers Dev2-Oct-14 22:42
David Rogers Dev2-Oct-14 22:42 
QuestionCompact Framework Support Pin
Member 1087882011-Jun-14 9:35
Member 1087882011-Jun-14 9:35 
AnswerRe: Compact Framework Support Pin
David Rogers Dev19-Jun-14 15:12
David Rogers Dev19-Jun-14 15:12 
QuestionI prefer a humble dialog approach Pin
Jason Storey3-Dec-13 5:32
Jason Storey3-Dec-13 5:32 
Questionlicense tracking application link does not work Pin
Southmountain29-Nov-13 5:45
Southmountain29-Nov-13 5:45 
AnswerRe: license tracking application link does not work Pin
David Rogers Dev29-Nov-13 12:33
David Rogers Dev29-Nov-13 12:33 
GeneralRe: license tracking application link does not work Pin
Chris87520-Aug-14 4:26
professionalChris87520-Aug-14 4:26 
GeneralRe: license tracking application link does not work Pin
David Rogers Dev20-Aug-14 4:36
David Rogers Dev20-Aug-14 4:36 
GeneralRe: license tracking application link does not work Pin
Chris87520-Aug-14 4:58
professionalChris87520-Aug-14 4:58 
GeneralRe: license tracking application link does not work Pin
David Rogers Dev20-Aug-14 5:10
David Rogers Dev20-Aug-14 5:10 
Yes. No need.

In any case, if you go to the Codeplex download tab for the project and download the source (WinFormsMvp v1.0.1008.2014 Source), that has the application in there.

The sln file WinFormsMvpLicenceTrackerSample.sln is in the root directory and Nuget restore is configured to restore any required Nuget packages (just build and it automatically brings down the Nuget stuff).

It was done using Visual Studio 2013. Should work in 2012 as well.

Best of luck. I have to hit the sack here now.
Suggestionmore simple example? Pin
Chris87521-Aug-14 2:53
professionalChris87521-Aug-14 2:53 
GeneralRe: more simple example? Pin
David Rogers Dev21-Aug-14 2:59
David Rogers Dev21-Aug-14 2:59 
GeneralRe: more simple example? Pin
David Rogers Dev22-Aug-14 1:42
David Rogers Dev22-Aug-14 1:42 

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.