I've (very) recently started studying about Prism (for a WPF app) and I've been working on a small demo app for my team. My question is a rather general one but I can't find a simple example to direct me:
Assuming I have the Shell (in the main module), and the Shell has a region which should be filled by a content from a different module (BTW, is this a good idea?).
How does this happen exactly? Who's responsible for adding the view from the other module into the Shell's designated region? When is the other module's relevant view-model being initialized? Code samples / links to them would be appreciated.
Thanks!
Edit: Question split, please see the new question.
In your bootstrapper you would define a module catalog (overriding CreateModuleCatalog) which lists the modules that will be used within your application. Each module has a class implementing IModule which contains an Initialize method that gets called when the module is being created. Within this you would define what is to be added to the region on the shell:
public void Initialize()
{
RegisterViewsAndServices(); //Method to register dependencies
IMyViewModel model = _container.Resolve<IMyViewModel>();
_regionManager.Regions[RegionNames.ShellHeaderRegion].Add(model);
}
Here I have added a viewmodel to the shell and I have a resource dictionary defined outside that determines what view should be applied to it via a DataTemplate.
The Prism documentation (particularly chapter 2) has a lot of useful information around this. http://msdn.microsoft.com/en-us/library/gg406140.aspx
Yes, separating shell and views is a good idea.
The RegionManager is responsible for adding the view into the regions. Therefore you register the region to the RegionManager as well as you register the view to the RegionManager.
Inside the shell you will end up with something like:
<Window xmlns:Regions="clr-namespace:Microsoft.Practices.Prism.Regions;assembly=Microsoft.Practices.Prism"
xmlns:Inf="clr-namespace:YourNamespace.Infrastructure;assembly=YourNamespaceInfrastructure">
<Grid>
<ContentControl
Regions:RegionManager.RegionName="{x:Static Inf:RegionNames.MainRegion}"/>
</Grid>
</Window>
Now you have to register the view to the region it will reside in:
public class FirstModule : IModule
{
public void Initialize( )
{
RegionManager.RegisterViewWithRegion( RegionNames.MainRegion,
typeof( MainView ) );
RegionManager.RegisterViewWithRegion( RegionNames.SecondRegion,
( ) => Container.Resolve<ISomethingViewModel>( ).View );
}
}
The example code shows both types of registrations. The first one is for view first approaches, the latter on for view model first approaches.
[EDIT]
The region names are defined in a static class in the infrastructure module:
namespace YourNamespace.Infrastructure
{
public static class RegionNames
{
public const string MainRegion = "MainRegion";
//...
}
}
[/EDIT]
Related
I am a beginner in MEF. According to my requirement I have to show the multiple plugin UI according to the selection of plugin. For that I have to pass plugin related data to the external plugin UserControl.
[InheritedExport(typeof(IConnect))]
public interface IConnect{}
Below code is using to initializing the external UI from the main application,
[ImportMany(typeof(IConnect))]
public IEnumerable<Lazy<IConnect>> Plugins;
....
var catalog = new DirectoryCatalog(#"C:\TestDll\");
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
var childControl = SelectedPlugin as UserControl;
//Here I have to pass the required data to the usercontrol. How to do this?
Dockpanel.Children.Add(childControl );
Could you please suggest a way to pass data to the external user control
Our entire team here at work has been using MEF for years now, and I'm reasonably familiar with how it works.
To get everything bootstrapped off the ground, the first thing to do is ensure that a common MEF container is shared between your main application and the user control in your external assembly.
If you don't have a common MEF container, then your MEF imported classes will never be able to communicate with the host application, as they will be living in separate MEF universes.
Once you have a common container, anywhere you want to bootstrap a class out of the container, you can use something like this:
var test = MefContainer.GetExportedValue<ITest>();
test.Run();
Q. How do I add MEF to my project?
A. Using NuGet, search for "microsoft composition", and add MEF 2.
Q. So how do we create this shared MEF container?
A. As follows:
Follow through a tutorial such as From Zero to Proficient with MEF.
Create a class library that is shared between your imported control, and the host application.
In this class library, create a singleton class called MefContainer. This means that anybody that wants to grab something out of the MEF container can reference this singleton to obtain the MEF container. The MEF container contains classes from all of the auto-discovered assemblies in the directories that we want to scan.
Q. How do I communicate with the user control?
A. Add this class to your shared class library:
public interface ITest
{
string SharedValue { get; set; }
}
[Export(typeof(ITest))]
public class Test : ITest
{
[ImportingConstructor]
public Test()
{
}
public string SharedValue { get; set; }
}
Now, both the host app and the user control can grab an instance of the same class out of the MEF container:
var test = MefContainer.GetExportedValue<ITest>();
var result = test.SharedValue;
By default, classes that are obtained from the MEF container are singleton's, unless we explicitly say that we want non-shared classes.
Q. How about adding Reactive Extensions (RX)?
It's ok to have a class with shared values. But it's nicer if one component can fire events off, and any other component can listen to this event.
To do this, add RX to your project using NuGet (search for "reactive extensions", add "Reactive Extensions - Main Library").
Add a Subject to your shared class:
private Subject<string> MyEvent { get; set; }
Now, if you have a MEF reference to this shared class, you can send an event out:
var t = MefContainer.GetExportedValue<ITest>();
t.MyEvent.OnNext("hello");
And, any other class can subscribe to these events:
var t = MefContainer.GetExportedValue<ITest>();
t.MyEvent.Subscribe(
o =>
{
Console.Write(o);
});
If code is already running in the MEF composition container, then there is no need to use MefContainer to get at the container. You can simply MEF import the shared communication class using the constructor:
public interface IClassRunningInContainer
{
}
[Export(typeof(IClassRunningInContainer))]
public class ClassRunningInContainer : IClassRunningInContainer
{
[ImportingConstructor]
public ClassRunningInContainer(ITest t)
{
t.OnNext("Hello, world!");
}
}
Final Notes
Don't make the mistake of using MEF as a general dependency injection (DI) solution. You will run into roadblocks further down the road, as MEF is does not have the level of built in diagnostics to find out if things have gone wrong, compared to a dedicated dependency injection solution, such as Unity.
The ideal pattern is to use MEF for importing whole assemblies, then use Unity as your day to day DI container.
In Prism's IModule Initialize() method, the RegisterViewWithRegion() method is called to map views and regions.
What is the difference between those two lines of code? Why use a ServiceLocator?
_regionManager.RegisterViewWithRegion("Region1", () => _serviceLocator.GetInstance<View1>());
_regionManager.RegisterViewWithRegion("Region1", typeof(View1));
Here is the whole ModuleInit.cs class for context:
namespace MyModule
{
[ModuleExport("MyModule.ModuleInit", typeof(MyModule.ModuleInit))]
public class ModuleInit : IModule
{
private readonly IRegionManager _regionManager;
public IServiceLocator _serviceLocator;
[ImportingConstructor]
public ModuleInit(IRegionManager regionManager, IServiceLocator serviceLocator)
{
_regionManager = regionManager;
_serviceLocator = serviceLocator;
}
#region IModule Members
public void Initialize()
{
_regionManager.RegisterViewWithRegion("Region1", () => _serviceLocator.GetInstance<View1>());
}
#endregion
}
}
[Edit]
The RegisterViewWithRegion Method MSDN site describes what the two different versions do:
RegisterViewWithRegion(IRegionManager, String, Func<Object>)
Associate a view with a region, using a delegate to resolve a
concreate instance of the view. When the region get's displayed, this
delelgate will be called and the result will be added to the views
collection of the region.
RegisterViewWithRegion(IRegionManager, String, Type)
Associate a view with a region, by registering a type. When the region
get's displayed this type will be resolved using the ServiceLocator
into a concrete instance. The instance will be added to the Views
collection of the region
So it seems to me that the only difference would be to use a ServiceLocator to resolve the type into an instance either immediately, or later when the region gets displayed?
[Edit2]
Found the answer elsewhere on Stackoverflow
That's because the main App is not supposed to know about modules.
When a module is loaded, it registers with the ServiceLocator and it has access to the RegionManager.
It can then, without the main app knowing anything about the newly loaded module, inject a view from the module into the main app (a new tab for example).
The ServiceLocator will ask MEF for dependencies, so you can always call _serviceLocator.GetInstance<View1>() parameterless, and MEF will go resolve whatever needs to be imported, no matter what you change in View1's constructor signature.
Every application contains some settings that are configurable. These settings can more or less put into two categories:
Appearance of application: example can be window location, window size, default options on views etc.
Business rules: these settings will be used by business logic.
In architecture that I implemented, View has its own project (WPF) and ViewModel has its own project (class library). From the lofical standpoint, View should be responsible of loading / saving view related settings, and ViewModel should be responsible for loading / saving business settings.
View settings are easy to handle. Create needed properties in Settings (app.config), and its easy to you can easily load save them.
However, ViewModel cannot access app.config settings through the built-in mechanisms that are Available in View project.
First idea I had was to make some helper methods that will allow me to read / write settings in app.config from ViewModel. What is your opinion? Am I complicating stuff here, or this is acceptable way of handling applic\tion settings?
There are three ways you could go here.
Add a reference to System.Configuration.dll and have your ViewModel project use the ConfigurationManager normally.
Have the ViewModel project ask for the configuration information it needs via constructors or other methods of Dependency Inversion, and have the View project pass it in.
Put the ViewModels and Views in the main application project.
Personally, I would go for option 3 unless there is some reason they need to be in separate assemblies. If they need to be separate, then I would favor option 1 because it's simpler.
Here's a cleaner option, if you would like to keep your assemblies separate and keep your ViewModels testable:
In your ViewModel project, add an interface which provides methods or properties for retrieving and saving business settings. Have your ViewModels accept this interface as a dependency in their constructors.
In your View project, add a class which implements this interface and talks with Settings
eg,
namespace ViewModel
{
public interface IBusinessSettingsStore
{
public string SomeSetting { get; set; }
public int AnotherSetting { get; set; }
}
public class SomeViewModel
{
private IBusinessSettingsStore _businessSettings;
public SomeViewModel(IBusinessSettingsStore businessSettings)
{
_businessSettings = businessSettings;
}
private void DoSomething()
{
Console.WriteLine(_businessSettings.SomeSetting);
_businessSettings.AnotherSetting = 10;
}
}
}
namespace View
{
public class BusinessSettingsStore : IBusinessSettingsStore
{
public string SomeSetting
{
get => Settings.Default.SomeSetting;
set => Settings.Default.SomeSetting = value;
}
public int AnotherSetting
{
get => Settings.Default.AnotherSetting;
set => Settings.Default.AnotherSetting = value;
}
}
}
I've been very impressed with this library: https://www.nuget.org/packages/UserSettingsApplied/. It basically allows you to serialize whatever you want to the user's roaming app config without any effort. It seems well thought out and well tested. This allows the view model to easily persist settings in the app.config.
FYI it is totally OK for the View project to reference View Model. More than that it is pretty much mandatory, so your view can do all of its persistence through the view model layer.
I have few public properties in App.xaml.cs which is in project A and I want to refer them in my project B. However my project A has a reference to project B, so I cannot add again the reference of project A in project B otherwise it will result in cyclic error. So how can I refer those properties in my class library? I don't want to use reflection :).
As a workaround I have stored those properties in one class in project B (so it can be referred in project A as well as project B) and made those properties to be static and all works fine. However I am still curious to know what if I had stored them in App.xaml.cs? Any options available?
Thanks in advance :)
The App class should expose things that are only relevant to the application project. As soon as you realised that you wanted these things accessable in B.dll they became relevant to more than just the application project and therefore no longer belong in the application project.
Adding a class to B.dll that carries these things as static properties could be a reasonable approach. Another common pattern is to have a single Current static property.
public MyClass
{
private static MyClass _current = new MyClass();
public static MyClass Current { get { return _current; } }
public string SomeInstanceValue { get; set; }
}
Both A and B would access things using the pattern var x = MyClass.Current.SomeInstanceValue. The advantage of this approach is that it allows the Current property getter to determine if a "current" instance is available or not.
You might also want to review the documentation on ApplicationLifeTimeObjects.
When A and B both need something, maybe you should put them in a C project (C as in Common) and then refer to C from both A and B.
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.
1st question: How would RegionManager come into play for the module-specific classes (presenters) that must interact with the main map control? Usually in a RegionManager you register a specific view which is linked to a ViewModel, but in my case there is one single view (the map view) with multiple presenters acting on it.
2nd question: I need to be able to open several windows (shells) -- a bit like an MS Word document -- that should all be extended by the plugin modules. In a single-shell environment, when the module specific classes were instantiated, they could use the Dependency Injection Container to get a reference to the RegionManager or the Shell itself in order to get access to the map control. However with multiple shells, I don't see how to get access to the map control of the right shell. The dependency container has references to object global to the application, not specific for the shell I'm currently working in. Same is true for the EventAggregator.
Any input would be very welcome,
Ed
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
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.