3
votes

I've been looking in the MVP pattern for a while, and managed to create some simple MVP-compliant applications.

I am now trying to apply the pattern to a more complex application, and I have some doubts on the best way of doing that.

My application has a single WinForm, with two buttons for loading two different kinds of data. My view interface looks like the following:

interface IView_MainForm
{
    //  Load input
    //
    event EventHandler<InputLoadEventArgs> LoadInput_01;
    event EventHandler<InputLoadEventArgs> LoadInput_02;

    bool Input01_Loaded { get; set; }
    bool Input02_Loaded { get; set; }
}

The IView is referenced in my presenter via constructor injection:

public Presenter_MainForm(IView_MainForm view)
{
    this.View = view;
    this.View.LoadInput_01 += new EventHandler<InputLoadEventArgs>(OnLoadInput_01);
    this.View.LoadInput_02 += new EventHandler<InputLoadEventArgs>(OnLoadInput_02);
}

So far, so good. When the user clicks any of the two buttons for loading data, a LoadInput_## event is raised, the Presenter is handling it, checks the input for errors and structures it according to my data model.

My next step would be displaying the processed data back in the View.

I'm striving to keep my View as passive and "dumb" as possible, assuming it knows nothing of the Presenter (it doesn't subscribe to its events, the Presenter sends data to the View by calling IView methods instead), let alone of the Model.

How am I supposed to populate a control like a TreeView, if the View has no idea of what the data model looks like?

Also, am I getting the whole MVP thing right, or is there anything I have missed?

1
I have edited your title. Please see, "Should questions include “tags” in their titles?", where the consensus is "no, they should not".John Saunders
Does your view (Form) implements the IView_MainForm ?huMpty duMpty
If your view won't know what's the data model (I'm assuming you mean Model), how it is supposed to bind itself to data?Michael
@JohnSaunders, got it, thanks. @huMptyduMpty, yes, my view (MainForm) implements IView_MainForm, this is how I've been able to implement the MVP pattern at a basic level for now: Presenter takes IView_MainForm as parameter in its constructor, subscribes to its events and displays results by calling methods specified in the interface (and implemented by MainForm).vi3x
@michaelmoore that's exactly the question. As far as I understand, in MVP(PV) View and Model know nothing about each other, since it's the Presenter which handles user events and instructs the View on how to display data. If my View already knows what my data looks like, what's the point of MVP in the first place? I could then simply populate my controls by directly referencing my Model... Am I missing something here?vi3x

1 Answers

1
votes

There is nothing wrong with having complex type properties in your View. Let's say you have some ComplexType.

class ComplexType
{
   public string ParentNode {get;set;}
   public List<string> ChildNodes {get;set;}
   // some other properties
}

Let's also assume ComplexType is data model for your TreeView. It is perfectly fine with MVP pattern to have properties on your View that will have ComplexType. So having something like this is perfectly fine

interface IView_MainForm
{
    //  Load input
    //
    event EventHandler<InputLoadEventArgs> LoadInput_01;
    event EventHandler<InputLoadEventArgs> LoadInput_02;

    bool Input01_Loaded { get; set; }
    bool Input02_Loaded { get; set; }

    ComplexType Input01Data {get;set;} // you might actually have some code in get/set setters
    ComplexType Input02Data {get;set;} // you might actually have some code in get/set setters

    public void SetInput01Data(ComplexType input01Data)
    {
       Input01Data = input01Data;
       // some other stuff
    }
}

And since your Model is for View that has 2 inputs, your Model could look something like this

public interface IModel
{
   public ComplexType Input01Data {get;set;}
   public ComplexType Input02Data {get;set;}
}

Now in your Presenter you would just handle event fired from View, populate Model and set properties on View

class Presenter
{
    private IModel _myModel...
    private IRepository _repository;
    public Presenter(IView_MainForm view, IRepository repository)
    {
        _repository = repository;
        this.View = view;
        this.View.LoadInput_01 += new EventHandler<InputLoadEventArgs>(OnLoadInput_01);
        this.View.LoadInput_02 += new EventHandler<InputLoadEventArgs>(OnLoadInput_02);
    }

    public void OnLoadInput_01(object sender, InputLoadEventArgs e)
    {
         // get data based on passed arguments (e.SomeProperty)
         // construct IModel
         myModel = _repository.GetData(e.SomeProperty);
         // pass data to IView_MainForm
         View.SetInput01Data(myModel.Input01Data);
     }
}

And regarding your concern

I'm striving to keep my View as passive and "dumb" as possible, assuming it knows nothing of the Presenter (it doesn't subscribe to its events, the Presenter sends data to the View by calling IView methods instead), let alone of the Model.

Your View still doesn't know anything about Presenter nor Model. It just fires events, get data from Presenter and binds its controls. And you have testability in place (please note this Unit Test is pseudo code, since I don't know how you retrieve data, what input you required in button click event etc...) .

[Test]
public void ShouldLoadInput01DataOnButtonClick()
{
   // Arrange
   IModel data = // create dummy data

   Mock<IView_MainForm> clientsViewMock = new  Mock<IView_MainForm>();
   Mock<IRepository> clientsRepositoryMock = new Mock<IRepository>();

   clientsRepositoryMock.Setup(repository => repository.GetData(something)).Returns(data.Input01Data);     
   var presenter = new Presenter(clientsViewMock.Object, clientsRepositoryMock .Object);

   // Act
   clientsViewMock.Raise(view => view.LoadInput01 += null, new InputLoadEventArgs());

   // Assert
   clientsViewMock.Verify(view => view.SetInput01Data(data.Input01Data), "Input01 data expected be set on button click.");
}