Click here to Skip to main content
Click here to Skip to main content

WinForms MVP - An MVP Framework for WinForms

By , 5 Feb 2013
 

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., WinForms MVP.

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.

Getting Started

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

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:

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:

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:

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:

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:

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:

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:

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

Second, we will create a contact for the View:

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):

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:

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:

[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.

License

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

About the Author

David Rogers Dev
Software Developer
Australia Australia
Member
A junior/mid-level developer writing .NET code to provide business solutions to a wide range of customer requirements.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionGeneric WinformsMVPmemberaberowland14 Apr '13 - 14:09 
AnswerRe: Generic WinformsMVPmemberDavid Rogers Dev30 Apr '13 - 3:14 
QuestionNice'ish but a few questions....MVPUserControl is not designer friendlymvpSacha Barber5 Feb '13 - 22:41 
AnswerRe: Nice'ish but a few questions....MVPUserControl is not designer friendlymemberDavid Rogers Dev2 Mar '13 - 12:50 
GeneralMy vote of 5memberFred Flams22 Jan '13 - 3:00 
Great article.
 
Reading this, I can't stop thinking MS missed something by focusing it's mind on the web ecosystem and left WinForm to dwindle....
But that gives the opportunity for good developers to show their talent
GeneralRe: My vote of 5memberDavid Rogers Dev25 Jan '13 - 13:00 
GeneralMy vote of 4membernando.potter22 Jan '13 - 0:13 
GeneralRe: My vote of 4memberDavid Rogers Dev25 Jan '13 - 13:01 

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 5 Feb 2013
Article Copyright 2013 by David Rogers Dev
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid