C# WinForms Model-View-Presenter (Passive View)
Asked Answered
B

2

20

I'm developing a WinForms application in C#. I have limited experience in GUI programming, and I am having to learn a great deal on the fly. That being said, here's what I am building.

See the general GUI look at the following link:

GUI http://img227.imageshack.us/img227/1084/program0.jpg

Now, I have done a lot of the work already, but in the very bad Autonomous design pattern. I did not know the project would ever reach a certain size, and, as such, it is time to do some major refactoring.

I have been studying a great deal about GUI design patterns, and the pattern I am wishing to implement is the Passive View (see http://martinfowler.com/eaaDev/PassiveScreen.html). I am looking for some help on how to bring this all together.

Background:

1) Depending on what the user clicks in the "TreeView", the "List" in the bottom left-hand corner will display a list of objects that can populate the "Editor" area. These objects might be a TextBox or a DataGridView. The user toggles the List to choose what he/she wants to see in the "Editor"

2) The model is essentially a folder with data and configuration files. There is an external program that runs on a given directory, creates output files/folders, etc. This program I am developing is designed to effectively manage/configure these objects in a user-friendly way

3) The problem with the way I have been doing things is that it is next to impossible to test, and hence the move to the MVP-esque Passive View design pattern

I am trying to make it so that the program works independently of the View. I have not been able to find any examples where a more complex, interactive view is used with the Passive View pattern.

Questions:

1) Do I need to implement one large interface/view for the entire "look" of the program, then implement sub-interfaces/sub-views for each of the TreeView, Editor, Logger, etc.? Or is there a better "structure" to doing this?

2) When it comes to "handing off" events from the View to the Presenter/Controller (whatever terminology you wish to use W.R.T. the Passive View design pattern), what is the way I should be doing this? Sometimes I have simple properties that need to be updated, and sometimes I need a whole series of steps to unfold.

I would love suggestions and advice on this topic. I have scoured the Internet, and I haven't found adequate examples to help me continue with this project.

Thanks in advance!

Daniel

Bundesrat answered 30/11, 2010 at 19:12 Comment(0)
S
18

Here is a simple example that demonstrates the concept of passive views using the MVP design pattern. Because we are using passive views the view has no knowledge of the presenter. The presenter will simply subscribe to events published by the view and act accordingly.

To start out we need to define a contract for our view. This is typically achieved using an interface, essentially, we want to have a very loose coupling with our view. We want the ability to switch to different views or event create mock views for unit testing.

Here is a contract that describes a simple view that will be used to display customer information

public interface ICustomerManagementView
{
    void InitializeCustomers(ICustomer[] customers);
    void DisplayCustomer(ICustomer customer);
    event EventHandler<EventArgs<ICustomer>> SelectedCustomerChanged;
}

It exposes a single method InitializeCustomers that will be used to initialize our view with objects from our model.

We also have an event SelectedCustomerChanged that will be used by our presenter to receive notification that an action has occurred in the view.

Once we have our contract we can start to handle these interactions in our presenter.

public class CustomerManagementPresenter
{
    private ICustomer _selectedCustomer;
    private readonly ICustomerManagementView _managementView;
    private readonly ICustomerRepository _customerRepository;

    public CustomerManagementPresenter(ICustomerManagementView managementView, ICustomerRepository customerRepository)
    {
        _managementView = managementView;
        _managementView.SelectedCustomerChanged += this.SelectedCustomerChanged;

        _customerRepository = customerRepository;

        _managementView.InitializeCustomers(_customerRepository.FetchCustomers());
    }

    private void SelectedCustomerChanged(object sender, EventArgs<ICustomer> args)
    {
        // Perform some logic here to update the view
        if(_selectedCustomer != args.Value)
        {
            _selectedCustomer = args.Value;
            _managementView.DisplayCustomer(_selectedCustomer);
        }
    }
}

In the presenter we can use another design pattern called dependency injection to provide access to our view and any model classes that we may need. In this example I have a CustomerRepository that is responsible for fetching customer details.

In the constructor we have two important lines of code, firstly we have subscribed to the SelectedCustomerChanged event in our view, it is here that we can perform associated actions. Secondly we have called InitilaizeCustomers with data from the repository.

At this point we haven't actually defined a concrete implementation for our view, all we need to do is create an object that implements ICustomerManagementView. For example in a Windows Forms application we can do the following

public partial class CustomerManagementView : Form, ICustomerManagementView
{
    public CustomerManagementView()
    {
        this.InitializeComponents();
    }

    public void InitializeCustomers(ICustomer[] customers)
    {
        // Populate the tree view with customer details
    }

    public void DisplayCustomer(ICustomer customer)
    {
        // Display the customer...
    }

    // Event handler that responds to node selection
    private void CustomerTreeViewAfterSelect(object sender, TreeViewEventArgs e)
    {
        var customer = e.Node.Tag as ICustomer;
        if(customer != null)
        {
            this.OnSelectedCustomerChanged(new EventArgs<ICustomer>(customer));
        }
    }

    // Protected method so that we can raise our event
    protected virtual void OnSelectedCustomerChanged(EventArgs<ICustomer> args)
    {
        var eventHandler = this.SelectedCustomerChanged;
        if(eventHandler != null)
        {
            eventHandler.Invoke(this, args);
        }
    }

    // Our view will raise an event each time the selected customer changes
    public event EventHandler<EventArgs<ICustomer>> SelectedCustomerChanged;
}

If we wanted to test our presentation logic we could mock our view and perform some assertions.

EDIT : Included custom event args

public class EventArgs<T> : EventArgs
{
    private readonly T _value;

    public EventArgs(T value)
    {
        _value = value;
    }

    public T Value
    {
        get { return _value; }
    }
}
Sayyid answered 30/11, 2010 at 20:11 Comment(9)
I appreciate the informative reply, but it does particularly address my questions. Should I be using one View (with these 4-5 subcomponents the user can interact with), or should there be Views within Views (hence interfaces within interfaces)? Also, I am still unsure of the "handing off" of events from the View to the Presenter, and I still need clarification on how the View observes the Model. I'm concerned about the "structure" of the program, too.Bundesrat
I prefer to break it down into smaller components, especially if you want to encourage reuse. Create views that have a single purpose but can be used together to create a composite UISayyid
Also, if you look at the presenter constructor you can see how to handle events from the view.Sayyid
Yes, I see that now. I missed it the first time around. ThanksBundesrat
Question 1: EventHandler<EventArgs<ICustomer>> format doesn't work in VisualStudio 2008, unless there is something I am missing. Question 2: Now what do I do to make the Presenter observe the Model? Just create public events inside the model and have the Presenter subscribe to said events? So that when the model changes, the presenter is notified (so he can make changes to the view).Bundesrat
Q1 EventArgs<T> is a simple class that will allow you to pass objects between the different layers Q2 That sounds good.Sayyid
Thanks again for the help. If a property inside Customer gets changed, such as his/her address, then we make an event fire notifying the change. Then, the CustomerRepository should get hold of that event, and then it should notify the View that a change is needed. Is this correct? This means there is a 1-1-1 ratio for Customer event to CustomerRepository event to View receiving that event. So, for each property that gets updated, there are 3 events to get it from Customer to View. Is there a way to cut down on this number?Bundesrat
I have seen someone use this before, but I don't know if I like it? c-sharpcorner.com/UploadFile/rmcochran/…Bundesrat
One last question, while I am at it (thanks for putting up with all this!). For my program, which composes of 4 parts of the screen with which the user can interact with the program, I am considering how to break the work apart. Should there be one main Presenter object (split across several partial class files) to handle all the 4 pieces of functionality, or should I have a sub-Presenter for each of the 4 GUI pieces buried inside the main Presenter object? So, either I can give each piece its own responsibility, or I can treat it at one large View/Presenter combo. What do you think?Bundesrat
E
0

I would break them down into separate views with their own presents, and use a "controlling" presenter / view to manage message delegation between them all. Not only will this aid testability but it'll keep your controls fulfilling SRP, too.

So in your case you might have an IFormManager which your main window will implement, and then an IFileManager, ILoggerWindow etc. etc.

Although it might be a bit overkill to use, I would suggest that you have a look at Smart Client Software Factory (from the Microsoft Patterns and Practices team) - it's not being actively developed any more, but it has a good implementation of MVP and does this sort of view composition stuff quite well, so might give you some good ideas.

Expiry answered 2/12, 2010 at 2:3 Comment(3)
Thanks for the input. But where would I be putting the individual controls? Inside the main view? But I am trying to keep the main view lightweight in the first place.Bundesrat
I found a picture that might detail what I am asking. Do you think this graphic will suffice to solve my problem? diskordia.ch/blog/wp-content/uploads/2009/03/subview.pngBundesrat
There's nothing to stop you putting the child views inside the main view - your main view is still lightweight logically i.e. its sole responsibility is to maintain layout of them delegate messages across the views; it would not control anything about them.Expiry

© 2022 - 2024 — McMap. All rights reserved.