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:
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;
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.
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
.
- Create an Interface for your view. This time, inherit from
IView
, rather than the generic version IView<tmodel></tmodel>
. - Right-click the Views folder and select Add > Windows Form from the context menu (and name it and hit enter)
- 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. - Create the presenter in the normal way.
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,>
public partial class AddProductView : MvpForm, IAddProductView
{
...
}
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:
- display a list of
SoftwareTypes
(for the user to choose from); - 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:
- 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. - 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.
- 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.
public partial class AddPersonViewSlice : MvpForm<addpersonmodel>
{
public AddPersonViewSlice()
{
}
}
</addpersonmodel>
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:
- Unity (which can be downloaded using Nuget);
- 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:
_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):
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. |