Introduction
I looked out for basic application samples using Prism over the net, and got few examples. I therefore, thought to write one such application, for reference purpose by other new bees like me.
Background
Design patterns are used to provide a generic and reusable solution to frequently occurring similar types of problems. Among many of design patterns are Prism, MVVM.
Before getting on with the sample project, allow me to throw some light over the big words like Prism, MEF, and MVVM used above.
Prism: Prism majorly provides guidance for the programmers to develop an application more cohesive and loosely coupled. Architecture developed over prism concepts is more robust and maintainable with time.
For detailed information on prism concepts, go through the below link:
http://www.intertech.com/Blog/7-things-you-need-to-know-before-starting-a-prism-wpf-project/
MVVM: Model-View-ViewModel is the term for the acronym MVVM. This design pattern is naturally blended with WPF and thereof with Prism because of its strong support to data binding , commands and behaviours.
Visit the link below for details on MVVM:
https://msdn.microsoft.com/en-in/library/hh848246.aspx
MEF: Managed Extensibility framework is a step further from Microsoft to make lightweight, extensible applications, avoiding hard dependencies within the modules.
https://msdn.microsoft.com/en-us/library/dd460648(v=vs.110).aspx
Logical separation of the solution:
Before getting on with the coding part, I recommend to create a high level structure and seggregation of the involved items. Below is the solution I carried on with:
Above described:
1. Framework Extended is WPF library, which is kept for the purpose of extending any WPF control. or to keep framework related items at common location.
2. InterfacesAndBaseClasses is a class library, which keeps all the interfaces and base classes ( abstract class) that can be used for import purposes. Solutions - Prism_Sample and ViewsAndViewModels don't refer to each other, as previous one will access the later one through MEF, therefore the interfaces and classes that needs to be imported/ exported are kept at common place.
3. Prism_Sample is the executable project in the solution group, This solution is a WPF application, It is in this solution, I will implement Bootstrapper in the App.Xaml.
4. ViewsAndViewModels is a WPF library. Here, I have kept all the Views - xamls and their ViewModels for the binding. Also in this solution I have kept IModule implementation.
<span style="color: rgb(255, 153, 0); font-family: 'Segoe UI', Arial, sans-serif; font-size: 29px;">Using the code</span>
Now jumping to the coding part, the Bootstrapper class needs to be implemented, that will tie up the shell with the different modules and their views. In prism, Bootstrapper class can be derived from UnitBootstrapper or MefBootstrapper. Since, I chose to go the MEF way, I chose MefBootstrapper as the parent class.
Below is the implementation code.
Bootstrapper class:
class BootStrapperClass:MefBootstrapper
{
MEFImplementation _MefImplementation = new MEFImplementation();
protected override void ConfigureAggregateCatalog()
{
_MefImplementation.LoadAssembly();
this.AggregateCatalog = _MefImplementation.AggregateCatalog;
}
protected override System.Windows.DependencyObject CreateShell()
{
return (_MefImplementation.InstanceShell.CreateInstance(_MefImplementation.InstanceViewModel) as System.Windows.Window);
}
protected override void InitializeShell()
{
try
{
base.InitializeShell();
App.Current.MainWindow = this.Shell as System.Windows.Window;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
In the above excerpt, MEFImplementation is the class, I have declared MEFImplementation class myself for implementing MEF, please refer to the attached zipped solution for details. This Mef Implemenation class is used in the Bootstrapper class to attain the object of the shell.xaml and its view model dynamically at run time. Implementation of Bootstrapper is shown above.
MEFImplementation class:
1. Import Attribute: Use Import attribute in the solution where you want to use the exported types.
public class MEFImplementation
{
[Import(typeof(IShell))]
public IShell InstanceShell = null;
Import(typeof(BaseViewModelClass))]
public BaseViewModelClass InstanceViewModel = null;
public CompositionContainer Container { get; set; }
public AggregateCatalog AggregateCatalog { get; set; }
public AssemblyCatalog AssemblyCatalog { get; set; }
public MEFImplementation()
{ }
public void LoadAssembly( )
{
AssemblyCatalog = new AssemblyCatalog(ConfigurationManager.AppSettings["ViewsAndViewModels"]);
this.AggregateCatalog = new AggregateCatalog(AssemblyCatalog);
this.Container = new CompositionContainer(this.AggregateCatalog);
Container.ComposeParts(this);
}
public void GetInstanceOfShell()
{
InstanceShell = this.Container.GetExportedValue<IShell>();
}
public void GetInstanceOfViewModel()
{
InstanceViewModel = this.Container.GetExportedValue<BaseViewModelClass>();
}
}<span style="font-size: 9pt;"> </span>
Shell.Xaml
Your Shell.xaml is the container xaml, all other UI( Xamls) will be loaded over this shell, in their respective regions provided. Please refer to the Shell.xaml in the ViewsAndViewModels solution.
Key Points
1. Region Manager, can have multiple activate views in one region or it can have single activate view.The Multiple Activated Views in one region allow mutiple views to be displayed in one region at the same time. while in Single Activated Views, you have to first deactivate the already activated views and then activate the new one. A Region Manager is multiple views activate or single activated view depends upong the container control, you have chosen in Shell.xaml. For eg: Use ContentControl for single view active and use ItemControl for mutiple views active.
Hide Copy Code
<ContentControl region:RegionManager.RegionName="{x:Static reg:RegionConstants.ModuleRegion }" Margin="5" BorderThickness="1" BorderBrush="Black" Grid.Column="1" HorizontalAlignment="Left" Width="325" Height="450" />
I have used ContentControl in my xaml , keeping above mentioned point.
2. CommandBindings: Use Command bindings to interact with the view model.I have implemented RelayCommands derived from ICommand for binding of events. The code behind of the Xaml should not be used for writing any piece of code. Rather use it only for assigning datacontext with the view model.
<local:ZComboBox Margin="3" Style="{StaticResource RoundCornerStyle}" Width="150" x:Name="MyTabControl" HorizontalAlignment="Left" DisplayMemberPath="ModuleName"
SelectedValue="{Binding MenuItemSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectionChangeInvoke ="{Binding TabSelectionChange, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding TabControlCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="ModuleType">
</local:ZComboBox>
Also you need to implement in ICommand interface provided by Microsoft, to delegate the event handler for every commands bound to your properties in view models. For this purpose, I created another class RelayCommands:
public class RelayCommand:ICommand
{
public RelayCommand(Func<object,bool> validateExecution, Action<object,object> executeCommand)
{
ValidateExecution = validateExecution;
ExecuteCommand = executeCommand;
}
public RelayCommand( Action<object,object> executeCommand)
{
ExecuteCommand = executeCommand;
}
public Func<object, bool> ValidateExecution { get; set; }
public Action<object,object> ExecuteCommand { get; set; }
bool ICommand.CanExecute(object parameter)
{
if (this.ValidateExecution != null)
{
return this.ValidateExecution(parameter);
}
else return true;
}
event EventHandler ICommand.CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
void ICommand.Execute(object sender)
{
this.ExecuteCommand(sender, null);
}
}
I have inherited my view models from the base class known as BaseViewModelClass (self declared class), so as to make my run time initialization type conversion safe while importing.
And on my view models, I have mentioned Export attribute, for the purpose described below.
3. Export Attribute: Use Export attribute whenever you want to export the type outside the assembly. And also don't add the reference of the library exporting the type into the solution importing the type.
My view model for the Shell, looks likes this:
[Export( typeof(BaseViewModelClass))]
public class ShellLoaderViewModel:BaseViewModelClass
{
# region Propeties
public ICommand TabSelectionChange
{
get;
set;
}
public ICommand ViewActivated { get; set; }
private IRegionManager _RegionManager;
public IRegionManager RegionManager
{
get
{
return this._RegionManager;
}
set
{
this._RegionManager = value;
this.RaisePropertyChange();
}
}
private string menuItemSelected;
public string MenuItemSelected
{
get
{
return this.menuItemSelected;
}
set
{
this.menuItemSelected = value;
this.RaisePropertyChange();
}
}
private ObservableCollection<ViewsAndViewModels.ModulesType> tabControlCollection;
public ObservableCollection<ViewsAndViewModels.ModulesType> TabControlCollection
{
get
{
return this.tabControlCollection;
}
set
{
this.tabControlCollection = value;
this.RaisePropertyChange();
}
}
private IUnityContainer _Container;
#endregion
#region Ctor
public ShellLoaderViewModel( )
{
this.TabControlCollection = new ObservableCollection<ViewsAndViewModels.ModulesType>();
this.TabControlCollection.Add(new ModulesType() { ModuleName = "Home", ModuleType = "HomeModuleImplementation" });
this.TabControlCollection.Add(new ModulesType() { ModuleName = "About", ModuleType = "AboutModule" });
RegisterCommands();
}
#endregion
}
......
Later when my Shell is loaded and its view model is set using Bootstrapper class, in App.Xaml:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
try
{
base.OnStartup(e);
BootStrapperClass bootStrapper = new BootStrapperClass();
bootStrapper.Run(true);
App.Current.MainWindow.Show();
}
catch (Exception ex)
{
Console.Write(ex.Message);
}
}
}
4. Instance of RegionManager: The shell view model needs the current instance of the running region manager on the Shell.xaml.
this.RegionManager= ServiceLocator.Current.GetInstance<IRegionManager>();
5. Events: I have created an Events class myself, to publish and remove the modules from the region explicitly.
On event invoked from the shells, trigger the loading of your modules, I wrote below code in my view model for shell:
private void TabSelectionChangeEventHandler(object param, object args)
{
if (this.menuItemSelected == null)
{
return;
}
CommandUtility.CreateNewEventsInstance();
IModule myModule = FactoryClass.CreateInstance(this.MenuItemSelected, this.RegionManager);
if (CommandUtility.MyEvent != null)
{
CommandUtility.MyEvent.onPublish(this, myModule);
}
}
Here, in the above code, CommandUtility is the class I created for publishing new modules in the shell.
My CommandUtility class looks like:
public class CommandUtility
{
private ViewsAndViewModels.Events.Events _MyEvent;
public static ViewsAndViewModels.Events.Events MyEvent = null;
public static ViewsAndViewModels.Events.Events GetInstanceofEvents()
{
if (MyEvent == null)
{
MyEvent = new Events.Events();
}
return MyEvent;
}
public static void CreateNewEventsInstance()
{
MyEvent = null;
MyEvent = new Events.Events();
}
}
6. IModules: This interface is provided by Prism, all the modules will implement this interface. One such sample of module is shown below:
Hide Copy Code
[Module (ModuleName="HomeModule")]
public class HomeModuleImplementation:IModule
{
private CompositionContainer _Container = new CompositionContainer();
private IRegionManager _RegionManager;
public HomeModuleImplementation( IRegionManager regionManager)
{
this._RegionManager = regionManager;
Events.Events myEvent = CommandUtility.GetInstanceofEvents();
myEvent.Publish += PublishModule;
}
~HomeModuleImplementation()
{
Events.Events myEvent = CommandUtility.GetInstanceofEvents();
myEvent.Publish -= PublishModule;
}<span style="color: rgb(17, 17, 17); font-family: 'Segoe UI', Arial, sans-serif; font-size: 14px;">
</span>
Above in the IModule implementation in modules class, you need to activate or deactivate your views mannually from a region, since we opted for single active modules in a region, discussed above. Above hihglighted parts in the code first deactivates all the already active veiws from the region manager, corresponding to a module, and then maps a fresh view(UI) with the region and activates it.
The excerpts provided above may not be elaborate and well explained, it is request from the author to please go through the solution for comprehensive illustration of PRISM, MEF, and MVVM.
History
2/04/2016 --- Initial release.