Eduard
Eduard

Reputation: 71

WPF / Prism library and multiple shells

I'm pretty new with Prism and after playing a bit around, there a few questions that arise. I'm trying to create a modular application that basically contains a map control in a shell window. The plugin modules offer different tools for interacting with the map. Some of the modules are pretty independent and simply display pins on the map.

Any input would be very welcome,

Ed

Upvotes: 3

Views: 3394

Answers (2)

Eduard
Eduard

Reputation: 71

After hours of reading Prism-related articles and forums I've come across the article "How to build an outlook style application" on Erwin van der Valk's Blog - How to Build an Outlook Style Application.

In one part of the architecture, a Unity Child Container was used to resolve type instances. That's exactly what I needed for the answer to my 2nd question: I needed to have "scoped" (by window) dependency injection (ex: window scoped EventAggregator, Map control, etc.)

Here's how I create a new window:

private IShellWindow CreateNewShell(IRegionManager regionManager)
{
  IUnityContainer childContainer = this.Container.CreateChildContainer();

  ... register types in child container ...      

  var window = new ShellWindow();
  RegionManager.SetRegionManager(window, regionManager);
  window.Content = childContainer.Resolve<MapDocumentView>();
  return window;
}

So MapDocumentView and all its components will be injected (if needed) window-scoped instances.

Now that I can have scoped injected objects, I can get the window-scoped map in my module-based MapPresenter. To answer my 1st question, I defined an interface IHostApplication which is implemented by the Bootstrapper which has a MapPresenterRegistry property. This interface is added to the main container.
Upon initialization, the modules will register their presenters and upon the window creation, they will be instantiated.

So for the module initialization:

public void Initialize() 
{
  ...
  this.hostApplication.MapPresenterRegistry.Add(typeof(ModuleSpecificMapPresenter));
  ...
}

The code that initializes the map window:

private void View_Loaded(object sender, RoutedEventArgs e)
{
  // Register map in the == scoped container ==
  container.RegisterInstance<IMap>(this.View.Map);

  // Create map presenters
  var hostApplication = this.container.Resolve<IHostApplication>();
  foreach (var mapPresenterType in hostApplication.MapPresenterRegistry)
  {
    var mapPresenter = this.container.Resolve(mapPresenterType) as IMapPresenter;
    if (mapPresenter != null)
    {
      this.mapPresenters.Add(mapPresenter);
    }
  }
}

The module-specific MapPresenter:

public ModuleSpecificMapPresenter(IEventAggregator eventAggregator, IMap map)
{
  this.eventAggregator = eventAggregator;
  this.map = map;
  this.eventAggregator.GetEvent<AWindowSpecificEvent>().Subscribe(this.WindowSpecificEventFired);

  // Do stuff on with the map
}

So those are the big lines of my solution. What I don't really like is that I don't take advantage of region management this way. I pretty much have custom code to do the work.

If you have any further thoughts, I would be happy to hear them out. Eduard

Upvotes: 3

vortexwolf
vortexwolf

Reputation: 14037

You have one main view and many child views, and child views can be added by different modules.

I'm not sure that the RegionManager class can be applied in this situation, so I would create a separate global class IPinsCollectionState which must be registered as singleton in the bootstrapper.

public interface IPin
{
    Point Coordinates { get; }
    IPinView View { get; }
    //You can use a view model or a data template instead of the view interface, but this example is the simplest
}

public interface IPinsCollectionState
{
    ObservableCollection<IPin> Pins { get; }
}

Your main view model and different modules can receive this interface as a constructor parameter:

public class MapViewModel
{
    public MapViewModel(IPinsCollectionState collectionState)
    {
        foreach (var item in collectionState.Pins)
        { /* Do something */ };

        collectionState.Pins.CollectionChanged += (s, e) => {/* Handle added or removed items in the future */};
    }

    //...
}

Example of a module view model:

public class Module1ViewModel
{
    public Module1ViewModel(IPinsCollectionState collectionState)
    {
        //somewhere in the code
        collectionState.Pins.Add(new Module1Pin());
    }
}

The second question can be solved in many different ways:

  • Application.Current.Windows
  • A global MainViewModel which contains the list of ShellViewModels and if you add new view model it will be displayed in new window. The bootstrapper is single for all windows.
  • Some kind of shared state which is passed to the constructor of the bootstrapper.

I don't know how these windows are related between themselves, and I don't know which way is the best, maybe it is possible to write an application with separated windows.

Upvotes: 1

Related Questions