6
votes

Ok, guys, for instance I have this form I told you before Only one DockContent in DockPanel

One edit I made since that times is words in each project appering when user clicks on some project in left pane. I easily created one triad for left pane.

It has projects presenter, projects view, projects model. Here is interfaces for each stuff:

interface IProjectsModel
{
    void AttachPresenter(IProjectsModelObserver observer);
    System.Collections.Generic.List<Project> projects { get; }
    Project selectedProject { get; set; }
}

public interface IProjectsViewObserver
{
    void UserChangedSelectedProject(Project project);
}
public interface IProjectsModelObserver
{
    void SelectedProjectChanged(Project project);
}

public interface IProjectsView : IView
{
    List<Project> projects { set; }
    Project project { set; }

    void AttachPresenter(IProjectsViewObserver presenter);
}

So at the moment I'm thinking about making a brand new another MVP triad for right pane. But this is not the main issue. The main issue I ran is how can I make communication process between MVP triads I told you above?

I have been read some article on the web telling that in this situation it's necessary to introduce some Model Coordinator to the project?

So guys my questions is:

  1. Am I right doing two triads intead of just one?
  2. How can I coordinate two triads between each other?
  3. Any proposes/suggestions/offers/advices/tips any many many many other stuff you think as useful for me will be very appreciated!

Big thanks in advance, guys! Thanks for your attention and time!

4

4 Answers

2
votes

To coordinate between presenters you can have your MainForm class implement IProjectsViewObserver and set the text on the right dock pane when the user selects a different project. For instance:

class MainForm : IProjectsViewObserver
{
    void UserChangedSelectedProject(Project project)
    {
          rightDockPane.setText(project.getText());
    }
}

If you want to test this behavior, you can create a separate class:

class DockPaneProjectUpdater : IProjectsViewObserver

although this is unnecessary in this case because the code is so simple.

You can investigate simpler ways to do this using anonymous methods, but I do not know enough C# to help you there.

Am I right doing two triads instead of just one?

Yes, because the dock and pane are separate components they will likely need separate MVP triads. You can decide on the granularity (smallness) the MVP triads by trying to make code reusable and testable.

How can I coordinate two triads between each other?

Create a window-level presenter that wires the sub-presenters and can coordinate behavior between triads. You can have the window-presenter be a IProjectsViewObserver and act upon the DockContent. Or if you really want to modularize/unit test the behavior, you can make a separate presenter class (often an anonymous class) for inter-triad communication.

Any proposes/suggestions/offers/advices/tips any many many many other stuff you think as useful for me will be very appreciated!

Read Wikipedia and online articles about MVP and presenter-first. The biggest benefits of MVP are testability and behavior (presenter) modularity so make sure you are taking advantage of them by unit testing (often with a mock/DI framework) and refactoring out behavior into MVP triads when you can reuse code.

It will take some time to figure out how to apply the MVP pattern effectively, so be patient!

1
votes

Follow Garrett's advice. Create subpresenters by a masterpresenter that are aware of each other and can act accordingly. The easiest way is to create a property in both panes' subpresenters.

Are you coming from the Java world? :) C# has its own implementation of observer pattern: events. Please have a look, so you don't need extra java-like interfaces anymore.

UPDATE: As I started writing I changed my mind. In my opinion, the best way to communicate between presenters is to use the shared model.

class SelectedProjectChangedEventArgs : EventArgs
{
    public Project SelectedProject {get;set;}
}

class Project
{

}

interface IReadOnlyModel
{
    Project SelectedProject {get;} 
    event EventHandler<SelectedProjectChangedEventArgs> SelectedProjectChanged;
}

interface IWritableModel
{
    Project SelectedProject {get;set;} 
    IList<Project> Projects {get;}
}

class Model : IReadOnlyModel, IWritableModel
{
    public Project SelectedProject {get;set;} 
    public event EventHandler<SelectedProjectChangedEventArgs> SelectedProjectChanged;
    public IList<Project> Projects {get;set;}
}

class ProjectsListPresenter
{
    readonly IWritableModel _model;

    public ProjectsListPresenter(IWritableModel model)
    {
        _model = model;
    }

    public void ChangeSelectedProject(Project project)
    {
        _model.SelectedProject = project;
    }
}

class ProjectDetailsPresenter
{
    readonly IReadOnlyModel _model;

    public ProjectDetailsPresenter(IReadOnlyModel model)
    {
        _model = model;
        _model.SelectedProjectChanged += ModelSelectedProjectChanged;
    }

    void ModelSelectedProjectChanged(object sender, SelectedProjectChangedEventArgs e)
    {
        //update right pane
    }
}


class WholeFormPresenter
{
    public ProjectDetailsPresenter DetailsPresenter {get;private set;}
    public ProjectsListPresenter ListPresenter {get;private set;}

    public WholeFormPresenter(Model model)
    {
        DetailsPresenter = new ProjectDetailsPresenter(model);
        ListPresenter = new ProjectsListPresenter(model);
    }
}

class WholeForm
{
    ListBox _projectsListControl;
    Panel _detailsPanel;
    public WholeForm()
    {
        var presenter = new WholeFormPresenter(new Model());
        _projectsListControl.Presenter = presenter.ListPresenter;
        _detailsPanel.Presenter = presenter.DetailsPresenter;
    }
}

I'm aware that the class/interface names are not perfect, but I hope the idea is clear.

1
votes

If you want to achieve totally thin and abstract way of interaction between your triads, you can use Mediator pattern. Create "message" class/structure and one presenter will subscribe to it and another will send it.

This approach is very similar to subscription to events but gives more abstraction, since presenters on both sides could not even know whether recipient(s) of their message exists or not. Since you are working in context of one process you can pass anything you want in your messages even callback delegates. A good example of mediator pattern implementation could be found in MVVM Light framework. There it is called Messenger. Take a look at on of tests.

Personally, I used mediator when needed to send information between dialog windows that had no common parent or creator. In that scenario it helped to simplify things.

0
votes

I've created a static class called EventHub, which can register "listeners" and publish new events. I'm a bit of a newbie myself with C#, so I'm sure there are ways to make this class better:

public enum EventType
{
    // Your event types
}

public static class EventHub
{
    static Dictionary<EventType, List<Delegate>> _eventHandlers;

    // Use this to fire events with input
    public static void Publish( EventType eventType, object data )
    {
        // Fire event handler if found
        if( _eventHandlers.ContainsKey( eventType ) )
        {
            _invokeHandlers( _eventHandlers[ eventType ], data );
        }
    }

    static void _invokeHandlers(List<Delegate> list, object data)
    {
        if( !(list != null && list.Count > 0) )
            return;

        foreach( var handler in list )
            handler.DynamicInvoke( data );
    }

    // Use this to register new "listeners"
    public static void Register( EventType eventType, Delegate handler )
    {
        // Init dictionary
        if( _eventHandlers == null )
            _eventHandlers = new Dictionary<EventType, List<Delegate>>();

        // Add handler
        if( _eventHandlers.ContainsKey( eventType ) )
            _eventHandlers[ eventType ].Add( handler );
        else
            _eventHandlers.Add( eventType, new List<Delegate> { handler });
    }

    // Use this to remove "listeners"
    public static void Dismiss( EventType eventType, Delegate handler )
    {
        // Nothing to remove
        if( _eventHandlers == null || _eventHandlers.Count == 0 )
            return;

        // Remove handler
        if( _eventHandlers.ContainsKey( eventType ) && _eventHandlers[ eventType ].Count > 0 )
            _eventHandlers[ eventType ].Remove( handler );

        // Remove Key if no handlers left
        if( _eventHandlers[ eventType ].Count == 0 )
            _eventHandlers.Remove( eventType );
    }
}

Usage:

void MyHandler(object data) { /*...*/ }
EventHub.Register( EventType.MyType, MyHandler );
//...
EventHub.Publish( EventType.MyType, "I am data, I can be anything" );