Previously in Code Project
AvalonDock
is a WPF controls library which can be used to create a docking layout system like that is present in Visual Studio.
It supports fly-out panes, floating windows, multiple docking manager in same window, styles and themes. Sofa embeds AvalonDock
in a WPF UserControl
and this allows integrating it in applications as easily as any other advanced UserControl
, a DataGrid
for instance.
A previous article in CodeProject introduced Sofa, AvalonDock and MEF.
Introduction
This article is about Prism integration. The example is a remake of the StockTraderRI
, the Prism Reference Implementation: We first used this application to demonstrate Sofa can be used in any framework and we added it to a Prism RegionManager
before moving some Prism
modules from the RegionManager
to Sofa
. This Sofa-Prism Reference Implementation places Sofa at the heart of the application and shows how to dynamically create content in 2 SofaContainers
. We kept most of the structure of the StockTraderRI
application as well as its Shell and one of its modules. So, however the ultimate goal of this text is to introduce Sofa and Prism, most of it is about Prism.
The Pitch is Very Simple
The user can select a ticker symbol and the target container that will show news about it.
Articles are shown in the selected SofaContainer
and all features of the Sofa
/AvalonDock
libraries can be used. The 2 SofaContainers
have their own behaviors: The left one creates only 1 instance of the News module and updates it as the right one creates a new instance of the News module for each call.
The cast features 5 actors, including 2 twins:
- The Shell and the bootstraper are the foundation of the application.
- The
Infrastructure
module mainly contains all commons elements. - The
CommandBoard
will show the combobox
es allowing selections and the button to open the news. - The 2
SofaContainers
will host the News module. - The News module is a copy of the one used in the
StockTraderRI
, the Prism reference implementation.
We reused it to demonstrate any piece of code can be used as a Sofa component (and also because we are a bit lazy…).
Here a picture of actors in Visual Studio:
There are also 2 supporting roles:
- Prism offers a set of facilities allowing not to worry about a lot of plumber concerns.
- MEF will be in charge of implementing the usual dependency injection and inversion of control patterns.
Relationships of actors:
- The bootstraper must have a reference on all other modules, mainly to initialize the MEF catalog.
- All modules needing a resource stored in the Infrastructure have a reference on it.
And that's it: Modules do not have references on each other; this is the base of the composite application architecture.
Dialogs Will Use the Usual MEF Words
A class [Export]
and other(s) [Import]
. There are many examples in the SofaPrismRI
example, this one shows the [Export]
of the View
of a Sofa
component and the [Import]
in the ViewModel
of a SofaContainer
:
Export of a Component
[^__strong__^Export("Sofa.Examples.SofaPrismRI.ViewContract", typeof(UserControl))]
[PartCreationPolicy(CreationPolicy.NonShared)]
[ExportMetadata("ProductName", "ArticleView")]
public partial class ArticleView : UserControl
{…
- and Import in a Container
[ImportMany("Sofa.Examples.SofaPrismRI.ViewContract")]
public Lazy<UserControl, ISofaComponent_Metadata>[] MefViews { get; set; }
{…
The plot is driven by Prism and Sofa communication:
When a user clicks on the "Load document" button:
=> Action 1 - Inside a module: Prism DelegateCommand
The action of the button is a Prism DelegateCommand
. The Prism ICommand
interface implementation will manage the communication inside the CommandBoard
module, from the View
to the ViewModel
.
Prism documentation: The DelegateCommand allows you to trigger a command at a point in the visual tree and handle it at a higher level… To create a delegate command, instantiate a DelegateCommand field in the constructor of your view model, and then expose it as an ICommand property.
The DelegateCommand
will send the information from the View to the ViewModel
.
=> Action 2: Between 2 modules: Sofa Prism EventAggregator
When the ViewModel
receives the command, it must send it outside of the CommandBoard
module without knowing who will catch it. The right tool for this is the EventAggregator
.
Prism Documentation: This mechanism, based on the event aggregator service, allows publishers and subscribers to communicate through events and still do not have a direct reference to each other … Publishers raise an event by retrieving the event from the EventAggregator and calling the Publish method. To access the EventAggregator, you can use dependency injection by adding a parameter of type IEventAggregator to the class constructor.
Then any modules, here the 2 SofaContainer
modules, can subscribe to the Prism EventAggregator
event.
SofaContainers
will have 3 tasks: First register the News as a Sofa
Component, then create an instance of it and at last inform the instance of the value of the ticker symbol to show.
The 1st task has been done when initializing the Sofacontainers
. Using MEF Sofacontainers ViewModels
have imported references to the News module seen here as a Sofa
component:
[ImportMany("Sofa.Examples.SofaPrismRI.ViewContract")]
public Lazy<UserControl, ISofaComponent_Metadata>[] MefViews { get; set; }
Then the Lazy MefViews
was used while setting the ViewModel
as DataContext
of the SofaContainer
View to register Component(s):
foreach (var view in ViewModel.MefViews)
{
sofaContainer.RegisterComponent(view);
}
The 2nd task of a SofaContainer
consists in instantiating the News module. This is done in the ViewModel
, using the very simple OpenComponent
method.
CmpLoaded cmpLoaded = SofaContainer.OpenComponent("ArticleView");
Then the selected ticker symbol must be sent to the newly created News instance:
=> Action 3: Between Sofa Components: Sofa SofaCommon event
The 3rd task will be achieved using the SofaCommon
event. It will send the ticker symbol value to the new instance and allows filtering on the GUID
of the News instance so that not all instances update their content with the latest selected ticker symbol.
SofaContainer.RaiseSofaCommonEvent
(cmpLoaded.GUID, "TickerSymbolSelectedEvent", callForOpenDocumentParameters);
Note: The Prism
event mechanism associated to Subscription Filtering can also be used in place of the SofaCommon
events.
At last, the
ViewModel
of the News module handles the
Sofa SofaCommon
event raised by the
SofaContainer
and calls the
loadTickerSymbol
method that triggers the display of the News.
A Few Things We Learnt
- Prism avoids the need to write complex syntax about
Dependency
properties or Routed command: Not all developers are WPF tightrope walkers (I don't think I am), however they are asked to produce robust code in a short time and this is maybe the main advantage of Prism. - The News module of the
StockTraderRI
application was reused in the SofaPrismRI
application with a very few modifications. Let's be sincere: Most of the developers spend most of their time recycling previous code. Prism may help them a lot! - We did not face complex debugging. This is related to Composite Application architecture and this way of developing becomes obvious when you have used it once: Code, don't debug.
History
- 27th May, 2011: Initial post
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.