Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prism/MVVM (MEF/WPF): Exposing navigation [Menu's for example] from modules

I am starting my first foray into the world of Prism v4/MVVM with MEF & WPF. I have sucessfully built a shell and, using MEF, I am able to discover and initialise modules. I am however unsure as to the correct way to provide navigation to the views exposed by these modules.

For example, let's say that one of the modules exposes three views and I want to display navigation to these views on a menu control. So far, I have sucessfully exposed a view based upon a MenuItem and this MenuItem contains child MenuItem controls thus providing a command heirarchy that can be used. Great.

Thing is, this feels wrong. I am now stating within my module that navigation (and therefore the shell) MUST support the use of menu's. What if I wanted to change to using a ToolBar or even a Ribbon. I would then have to change all of my modules to expose the corresponding control types for the shell.

I have looked around and there is mention on some sites of using a "Service" to provide navigation, whereby during the initialisation of the module, navigation options are added to the service which in turn is used by the shell to display this navigation in whatever format it wants (ToolBar, TreeView, Ribbon, MenuItem etc.) - but I cannot find any examples of actually doing this.

To put all of this into perspective, I am eventually looking to be able to select views from a menu and/or other navigation control (probably a Ribbon) and then open those views on demand within a TabControl. I have already gotten as far as being able to create the views in the TabControl at module initialisation time, now I need the next step.

What I need to know is this: what would be the correct way to expose navigation options in such a way as not the insist on support of a specific control by the shell, and if a service is the way to go then how would one put this together within the Prism/MVVM patterns.

Thanks in advance for any insight you can offer.

like image 276
Martin Robins Avatar asked Nov 14 '10 17:11

Martin Robins


2 Answers

I suppose you have a main module containing common interfaces. You could create a simple interface like

public interface IMenuService {
    void AddItem(string name, Action action);
    IEnumerable<MenuItemViewModel> GetItems { get; }
}

Create 1 implementation and a single instance.

public class MenuService : IMenuService {

    private readonly IList<MenuItemViewModel> items = new List<MenuItemViewModel>();

    void AddItem(string name, Action action) {
        items.Add(new MenuItemViewModel {
            Name = name,
            Action = action
        });
    }

    IEnumerable<MenuItemViewModel> GetItems {
        get { return list.AsEnumerable(); }
    }
}

Within your modules, use MEF to resolve this instance and call AddItem() to register your views. The Action property is a simple delegate to activate a view or do anything else.

Then in your shell or any view, you just need to call the GetItems property to populate your menu.

like image 67
SandRock Avatar answered Oct 06 '22 03:10

SandRock


Having thought about this some more, I have come to the following conclusion that I feel affects the way that I need to deal with this...

The modules need to be partially aware of the shell layout anyway - that is, the shell exposes a number of regions and the modules need to be aware of those regions (by name as well as what is expected to be shown) in order to populate them correctly when functionality is requested (either by means of registering a view within a region or as the reaction to a user action).

Because of this, the modules need to be designed to interact with the shell to place content into the named regions and as such, I see no reason why the modules should not expose whatever type of navigation the shell supports.

Therefore, my modules (currently) expose a "RibbonView" (a RibbonTab) with the necessary icons, buttons and commands etc to expose the functionality of the module. Each "RibbonView" is registered with the "RibbonRegion" of the shell, along with hints for ordering, and this is then rendered within the shell.

If in the future I choose to update my shell to use the latest+greatest navigation control (whatever that may be in x years time) then I simply need to update each of the modules to expose the necessary items to integrate with that new navigation and, because I am loading into a new shell, I can then update my view registration accordingly.

I just hope that I am not breaking any of the principles of the composite application in doing this, but that said I have never yet found a pattern that can actually be implemented in a real scenario without some "interpretation".

I would be interested to hear if anybody has any opinions on this.

like image 22
Martin Robins Avatar answered Oct 06 '22 03:10

Martin Robins