TheJediCowboy
TheJediCowboy

Reputation: 9232

Should Model-View-Presenter Pattern Allow for Nested Presenters in WPF/Winforms

It was recently suggested to me on SO and by others that I could utilize the Model-View-Presenter pattern to refactor a diagram/flowchart designer I am building in WPF and Winforms (mostly WPF).The mockup is shown below.

enter image description here

What I do not understand is how this pattern would work with controls being added to the designer surface at runtime. The questions this raises for me is the following:

I have been playing around with some 'dummy' presenter code for this and I have the following:

public interface IDesignerView : IView
{
    Guid Id { get; set; }

    Canvas Canvas { get; set; }

    event EventHandler<MouseEventArgs> MouseDown;
    event EventHandler<MouseEventArgs> ControlDropped;
}

public interface IControlView : IView
{
    Guid Id { get; set; }

    event EventHandler<MouseEventArgs> MouseDown;
}

public class DesignerView : IDesignerView
{
    public Guid Id { get; set; }
    public Canvas Canvas { get; set; }

    public event EventHandler<MouseEventArgs> MouseDown;
    public event EventHandler<MouseEventArgs> ControlDropped;
}   

public class DesignerPresenter :Presenter<IDesignerView>
{
    public DesignerPresenter(IDesignerView view) : base(view)
    {

    }

    public override void Initialize()
    {
        View.ControlDropped += View_ControlDropped;
        View.MouseDown += View_MouseDown;

    }

    private void View_MouseDown(object sender, MouseEventArgs e)
    {
        //Might need to unselect selected controls
    }

    private void View_ControlDropped(object sender, MouseEventArgs e)
    {
        IControlView view = ControlBuilder.Build(...)
        View.Canvas.Children.Add(view)
    }
}

Upvotes: 0

Views: 978

Answers (2)

Rachel
Rachel

Reputation: 132648

I've done something similar using MVVM and don't see a problem with you using it here. I don't know enough about MVP to speak about it though.

(Also, I looked at your other question, and don't really see why you'd want to use MVP over MVVM for something like this since you're working with WPF)

Ideally each item on your canvas (Components, Overlays, and Connectors) would be represented by a data model that includes attributes containing the size and position of the boejct

public interface IDesignerComponent
{
    int X { get; set; }
    int Y { get; set; }
    int Height { get; set; }
    int Width { get; set; }
}

public class ComponentModel : IDesignerComponent { ... }
public class ConnectorModel: IDesignerComponent { ... }
public class OverlayModel: IDesignerComponent { ... }

And you'd have a collection of these objects for the UI to bind to in your designer view model

public class DesignerViewModel
{
    public ObservableCollection<IDesignerComponent> Components { get; set; }
    ...
}

I would then draw this collection using an ItemsControl that has a Canvas for an ItemsPanelTemplate, and that uses implicit DataTemplates to define how each item gets drawn.

<ItemsControl ItemsSource="{Binding Components}">

    <!-- // DataTemplates for all 3 types of objects -->
    <ItemsControl.Resources>
        <DataTemplate DataType="{x:Type local:ComponentModel}">
            <local:MyComponentControl 
                Height="{Binding Height}" 
                Width="{Binding Width}" />
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:ConnectorModel}">
            <local:MyConnectorControl 
                Height="{Binding Height}" 
                Width="{Binding Width}" />
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:OverlayModel}">
            <local:MyOverlayControl 
                Height="{Binding Height}" 
                Width="{Binding Width}" />
        </DataTemplate>
    </ItemsControl.Resources>

    <!-- // ItemsPanelTemplate - May need to set or bind Canvas Height/Width too-->
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <!-- // ItemContainerStyle - Sets x,y position of items -->
    <ItemsControl.ItemContainerStyle>
        <Style>
            <Setter Property="Canvas.Left" Value="{Binding X}" />
            <Setter Property="Canvas.Top" Value="{Binding Y}" />
        </Style>
    </ItemsControl.ItemContainerStyle>
</ItemsControl>

Components can be Added or Dragged around on the Canvas, changing their X,Y values (and possibly Height/Width if you allow it), and in the OnPropertyChange for these properties you can find any associated components and update their position and/or size as well.

Your Models/ViewModels don't need to care about the actual UI components at all, or how they get drawn by the UI. All they care about is their X,Y relation to each other.

Upvotes: 1

Kelly
Kelly

Reputation: 7193

I just wrote something similar to this a few months ago. It was system with Plugins and a user could select plugins and add them to a Canvas, then resize, move them etc... I didn't have connections between them like yours has, so that part I cannot speak to.

On the question of using a shared presenter or one each: it really depends on your scenario. You need to assess how "heavy" it is. ie: memory footprint, cpu resources etc. Also you need to take into consideration threading. If multiple objects need to update at the same time. Determine how many the max number of objects you plan to support is, and what it would do to single vs multiple presenters. (I went with multiple presenters as each one was a custom plugin and the author of the plugin wrote the view and presenter for it, so I didn't have much choice in this regard.)

On the question of letting the ViewModel have access to the Canvas: I hate to say it but I did let my VM have access to the Canvas. I tried for a bit, but just couldn't find a nice clean way to avoid it unless I wrote my own Canvas that accepted an ItemsSource of controls that would work with an ObservableCollection. In the end the path of least resistance was letting the ViewModel have access to the Canvas. If your a MVVM purest I'm sure that sounds awful, but the alternative was too time consuming.

Upvotes: 0

Related Questions