Links
Description
Fully open source version of Prism including
MVVM
Commanding: DelegateCommand
Messaging: EventAggregator
Navigation
Dialog services
Modularity
Dependency injection
Prism 8 supports WPF, Xamarin Forms and UNO, but not Silverlight, Windows 8/8.1/WP8.1 or UWP.
Getting Started
Prism.Core
Prism.Wpf
Prism.DryIoc Prism.Unity dependency injection containers
Adding Prism.DryIoc will add Prism.Wpf and Prism.Core
Templates
Prism Blank App (.NET Core) - target .NET Core 3.1
Prism Blank App (WPF) - target .NET Framework 4.7.2
Prism Blank App (Uno plateform)
Snippets
propp property which notifies its changes
cmd cmdfull delegate command
cmdg cmgfull delegate command with parameter
App.xaml
Shell: the main / root window
App.xaml
<prism:PrismApplication x:Class ="PrismFull.App"
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism ="http://prismlibrary.com/" >
</prism:PrismApplication >
App.xaml.cs
public partial class App
{
protected override Window CreateShell ()
{
return Container.Resolve <MainWindow >();
}
protected override void RegisterTypes (IContainerRegistry containerRegistry )
{ }
}
ViewModels
ViewModels/MyViewModel.cs
public class MyViewModel : BindableBase
{
private string myProperty ;
public string MyProperty
{
get => myProperty;
set => SetProperty (ref myProperty , value );
}
}
Used to automatically wire the DataContext of a view to an instance of a ViewModel using a standard naming convention.
MyView.xaml
<UserControl x:Class ="MyApp.Views.MyView"
xmlns:mc ="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d ="http://schemas.microsoft.com/expression/blend/2008"
xmlns:viewModels ="clr-namespace:MyApp.ViewModels"
xmlns:prism ="http://prismlibrary.com/"
mc:Ignorable ="d"
d:DataContext ="{d:DesignInstance viewModels:MyViewModel}"
prism:ViewModelLocator.AutoWireViewModel ="True" >
This convention assumes:
that ViewModels are in the same assembly as the view types
that ViewModels are in a .ViewModels child namespace
that Views are in a .Views child namespace
that ViewModel names correspond with View names and end with ViewModel
Change the naming convention
afficher App.xaml.cs
protected override void ConfigureViewModelLocator ()
{
base .ConfigureViewModelLocator ();
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver ((viewType ) =>
{
var viewName = viewType .FullName ;
var viewAssemblyName = viewType.Assembly.FullName;
var suffix = viewName.EndsWith ("View" ) ? "Model " : "ViewModel ";
var viewModelName = $"{viewName .Replace (".Views." , ".ViewModels." )} {suffix } , {viewAssemblyName } " ;
var assembly = viewType.Assembly;
return Type.GetType (viewModelName , true );
});
}
Custom ViewModel registration
Faster because it doesn't use reflexion.
afficher ModuleAModule.cs
public void RegisterTypes (IContainerRegistry containerRegistry )
{
ViewModelLocationProvider.Register <ViewA , ViewAViewModel >();
ViewModelLocationProvider.Register <ViewA >(() => new ViewAViewModel ());
}
MyViewModel.cs
public DelegateCommand DoCommand { get ; }
public DelegateCommand DoWithParamCommand<string > { get ; }
public MyViewModel ()
{
DoCommand = new DelegateCommand (Do , CanDo );
DoWithParamCommand = new DelegateCommand <string >(DoWithParam , CanDoWithParam );
}
private void Do () { }
private bool CanDo () => true ;
private void DoWithParam (string parameter ) { }
private bool CanDoWithParam (string parameter ) => true ;
MyView.xaml
<Button Command ="{Binding DoCommand}"
Content ="Do" />
<Button Command ="{Binding DoWithParamCommand}"
CommandParameter ="{Binding Content, RelativeSource={RelativeSource Self}}}"
Content ="Do" />
Parameter can't be of value type (int, double, bool, etc). Use instead the equivalent nullable type.
RaiseCanExecuteChanged
private bool isEnabled ;
public bool IsEnabled
{
get => isEnabled;
set
{
SetProperty (ref isEnabled , value );
this .DoCommand.RaiseCanExecuteChanged ();
}
}
ObservesProperty
Whenever the value of the supplied property changes, the DelegateCommand will automatically call RaiseCanExecuteChanged to notify the UI of state changes.
DoCommand = new DelegateCommand (Do , CanDo ).ObservesProperty (() => IsEnabled );
ObservesCanExecute
If your CanExecute is the result of a simple Boolean property, you can eliminate the need to declare a CanExecute delegate, and use the ObservesCanExecute method instead. ObservesCanExecute will not only send notifications to the UI when the registered property value changes but it will also use that same property as the actual CanExecute delegate.
DoCommand = new DelegateCommand (Do ).ObservesCanExecute (() => IsEnabled );
Task-Based DelegateCommand
DoCommand = new DelegateCommand (DoAsync );
async void DoAsync ()
{
await SomeAsyncMethod ();
}
Composite command
Parent command to multiple child commands.
afficher Core/Commands/IApplicationCommands.cs
public interface IApplicationCommands
{
CompositeCommand SaveAllCommand { get ; }
}
afficher Core/Commands/ApplicationCommands.cs
public class ApplicationCommands : IApplicationCommands
{
public CompositeCommand SaveAllCommand { get ; } = new CompositeCommand ();
}
afficher App.xaml.cs
protected override void RegisterTypes (IContainerRegistry containerRegistry )
{
containerRegistry.RegisterSingleton <IApplicationCommands , ApplicationCommands >();
}
afficher ViewModels/MainWindowViewModel.cs
private IApplicationCommands applicationCommands ;
public IApplicationCommands ApplicationCommands
{
get { return applicationCommands; }
set { SetProperty (ref applicationCommands , value ); }
}
public MainWindowViewModel (IApplicationCommands applicationCommands )
{
ApplicationCommands = applicationCommands;
}
afficher ModuleA/ViewModels/ViewAViewModel.cs
public DelegateCommand SaveCommand { get ; private set ; }
public TabViewModel (IApplicationCommands applicationCommands )
{
SaveCommand = new DelegateCommand (Save ).ObservesCanExecute (() => CanSave );
applicationCommands.SaveAllCommand.RegisterCommand (SaveCommand );
}
App.xaml.cs
public partial class App
{
protected override void RegisterTypes (IContainerRegistry containerRegistry )
{
containerRegistry.RegisterSingleton <IMyService , MyService >();
containerRegistry.Register <IMyService , MyService >();
}
singleton service
for a service which is used throughout the application and that retains its state.
transient service
create a new instance each time.
scoped service
no implementation because unlike a web application, desktop applications are dealing with a single user and not scoped user requests.
Region
Region: placeholder for dynamic content where views will be injected.
Region manager: maintains a collection of all the regions of the application. Used for:
create and define regions
access to regions
region navigation
views composition
<ContentControl prism:RegionManager.RegionName ="MyRegion" />
Region adapter
Adapts a view to a region.
Prism provides 3 region adapters:
ContentControlRegionAdapter allow to inject a view in a ContentControl
ItemsControlRegionAdapter allow to inject a view in an ItemsControl
SelectorRegionAdapter allow to inject a view in a ComboBox ListBox Ribbon TabControl
Other controls require a custom region adapter.
afficher Core/Regions/StackPanelRegionAdapter.cs
internal sealed class StackPanelRegionAdapter : RegionAdapterBase <StackPanel >
{
public StackPanelRegionAdapter (RegionBehaviorFactory behaviorFactory )
: base (behaviorFactory )
{ }
protected override void Adapt (IRegion region , StackPanel regionTarget )
{
region.Views.CollectionChanged += (s, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (FrameworkElement item in e.NewItems)
{
regionTarget.Children.Add (item );
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (FrameworkElement item in e.OldItems)
{
regionTarget.Children.Remove (item );
}
}
};
}
protected override IRegion CreateRegion () => new Region ();
}
App.xaml.cs
protected override void ConfigureRegionAdapterMappings (RegionAdapterMappings regionAdapterMappings )
{
base .ConfigureRegionAdapterMappings (regionAdapterMappings );
regionAdapterMappings.RegisterMapping <StackPanel >(Container .Resolve <StackPanelRegionAdapter >());
}
View discovery
Region looks for view type.
ModuleAModule.cs
public void OnInitialized (IContainerProvider containerProvider )
{
this .regionManager.RegisterViewWithRegion ("MyRegion" , typeof (ViewA ));
}
View injection
ModuleAModule.cs
public void OnInitialized (IContainerProvider containerProvider )
{
var viewA = containerProvider.Resolve <ViewA >();
var contentRegion = this .regionManager.Regions["ContentRegion" ];
contentRegion.Add (viewA );
contentRegion.Add (anotherView );
contentRegion.Activate (anotherView );
contentRegion.Deactivate (anotherView );
}
Navigation
Move between views in a region based on url.
By default create a new instance a the view before to navigate to it or reuse the existing one (discriminate by view type) if it has already been created.
ModuleAModule.cs
public void RegisterTypes (IContainerRegistry containerRegistry )
{
containerRegistry.RegisterForNavigation <ViewA >();
containerRegistry.RegisterForNavigation <ViewB >();
containerRegistry.RegisterForNavigation <ViewA , ViewAViewModel >();
}
ViewAViewModel.cs
internal sealed class ViewAViewModel : BindableBase , INavigationAware
{
public void OnNavigatedTo (NavigationContext navigationContext )
{
var id = navigationContext.Parameters.GetValue <int >("id" );
var key = navigationContext.Parameters.GetValue <string >("key" );
}
public void OnNavigatedFrom (NavigationContext navigationContext )
{ }
public bool IsNavigationTarget (NavigationContext navigationContext ) => true ;
}
Views/MainWindow.xaml
<Button Command ="{Binding NavigateCommand}"
CommandParameter ="ViewA"
Content ="Navigate to A" />
<Button Command ="{Binding NavigateCommand}"
CommandParameter ="ViewB "
Content ="Navigate to B" />
ViewModels/MainWindowViewModel.cs
private readonly IRegionManager regionManager ;
public DelegateCommand <string > NavigateCommand { get ; private set ; }
public MainWindowViewModel (IRegionManager regionManager )
{
this .regionManager = regionManager;
this .NavigateCommand = new DelegateCommand <string >(Navigate );
}
private void Navigate (string uri )
{
var navigationParameters = new NavigationParameters ();
navigationParameters.Add ("key" , "value" );
this .regionManager.RequestNavigate ("ContentRegion" , $"{uri } ?id=1" , navigationParameters );
}
Confirm navigation request
ViewAViewModel.cs
internal sealed class ViewAViewModel : BindableBase , IConfirmNavigationRequest
{
public void ConfirmNavigationRequest (NavigationContext navigationContext , Action <bool > continuationCallBack )
{
var continueNavigation = true ;
continuationCallBack (continueNavigation );
}
}
Navigation journal
ViewAViewModel.cs
internal sealed class ViewAViewModel : BindableBase , INavigationAware
{
private IRegionNavigationJournal journal ;
public DelegateCommand GoBackCommand { get ; set ; }
public ViewAViewModel ()
{
GoBackCommand = new DelegateCommand (GoBack );
}
public void OnNavigatedTo (NavigationContext navigationContext )
{
journal = navigationContext.NavigationService.Journal;
}
private void GoBack () => journal.GoBack ();
}
Navigation with TabControl
afficher MainWindow.xaml
<Window x:Class ="NavigationWPF.Views.MainWindow"
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism ="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel ="True"
Title ="{Binding Title}" Height ="350" Width ="525" >
<Window.Resources >
<Style TargetType ="TabItem" >
<Setter Property ="Header"
Value ="{Binding DataContext.Title}" />
</Style >
</Window.Resources >
<Grid >
<Grid.RowDefinitions >
<RowDefinition Height ="Auto" />
<RowDefinition Height ="*" />
</Grid.RowDefinitions >
<StackPanel Orientation ="Horizontal" >
<Button Command ="{Binding NavigateCommand}"
CommandParameter ="ViewA" >
Navigate A
</Button >
<Button Command ="{Binding NavigateCommand}"
CommandParameter ="ViewB" >
Navigate B
</Button >
</StackPanel >
<TabControl Grid.Row ="1"
prism:RegionManager.RegionName ="TabRegion" />
</Grid >
</Window >
afficher MainWindowViewModel.cs
public class MainWindowViewModel : ViewModelBase
{
private readonly IRegionManager _regionManager ;
public DelegateCommand <string > NavigateCommand { get ; set ; }
public MainWindowViewModel (IRegionManager regionManager )
{
_ regionManager = regionManager;
NavigateCommand = new DelegateCommand <string >(Navigate );
}
void Navigate (string navigationPath )
{
_ regionManager.RequestNavigate ("TabRegion" , navigationPath );
}
}
A module represents a functional responsability of the application (independent feature).
Add → New Project → WPF UserControl Library
Remove AssemblyInfo.cs and UserControl1.xaml
Add Prism.Wpf Nuget package
ModuleAModule.cs
public sealed class ModuleAModule : IModule
{
public void OnInitialized (IContainerProvider containerProvider )
{ }
public void RegisterTypes (IContainerRegistry containerRegistry )
{ }
}
Module catalog
Collection of modules that the application is going to load.
Code base
Hard reference to the modules.
afficher App.xaml.cs
protected override void ConfigureModuleCatalog (IModuleCatalog moduleCatalog )
{
moduleCatalog.AddModule <ModuleAModule >();
}
App.config
More loosely coupled approach.
afficher App.xaml.cs
protected override IModuleCatalog CreateModuleCatalog ()
=> new ConfigurationModuleCatalog ();
afficher App.config
<configuration >
<configSections >
<section name ="modules"
type ="Prism.Modularity.ModulesConfigurationSection, Prism.Wpf" />
</configSections >
<modules >
<module assemblyFile ="ModuleA.dll"
moduleType ="ModuleA.ModileAModule, ModuleA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
moduleName ="ModuleAModule"
startupLoaded ="True" />
</modules >
</configuration >
xcopy /y "$(TargetDir)*.*" "$(SolutionDir)$(SolutionName)\$(OutDir)"
Directory
Define a location on disk where to find the modules.
afficher App.xaml.cs
protected override IModuleCatalog CreateModuleCatalog ()
=> new DirectoryModuleCatalog () { ModulePath = @".\Modules" };
xcopy /y "$(TargetDir)$(TargetName)$(TargetExt)" "$(SolutionDir)$(SolutionName)\$(OutDir)Modules\"
XAML resource dictionary
Loosely coupled event-based communication.
Event mechanism that enables communications between loosely coupled components in the application.
MyApp.Core/MyPayload.cs
public sealed class MyPayload
{
public string Prop1 { get ; set ; }
public string Prop2 { get ; set ; }
}
MyApp.Core/MyEvent.cs
public sealed class MyEvent : PubSubEvent <MyPayload >
{ }
MyApp/MyViewModel.cs
internal sealed class MyViewModel
{
IEventAggregator eventAggregator ;
public MyViewModel (IEventAggregator eventAggregator )
{
this .eventAggregator = eventAggregator;
}
var payload = new MyPayload
{
Prop1 = "value1" ,
Prop2 = "value2" ,
};
eventAggregator.GetEvent <MyEvent >().Publish (payload );
eventAggregator.GetEvent <MyEvent >().Subscribe (MyAction );
void MyAction (MyPayload payload )
{ }
}
Subscribing on the UI Thread
If the subscriber needs to update UI elements in response to events, subscribe on the UI thread. In WPF, only a UI thread can update UI elements.
eventAggregator.GetEvent <MyEvent >().Subscribe (DisplayMessage , ThreadOption .UIThread );
PublisherThread
use this setting to receive the event on the publishers' thread. Default value.
UIThread
use this setting to receive the event on the UI thread.
BackgroundThread
use this setting to asynchronously receive the event on a .NET Framework thread-pool thread.
In order for PubSubEvent to publish to subscribers on the UI thread, the EventAggregator must initially be constructed on the UI thread.
Subscription Filtering
eventAggregator.GetEvent <MyEvent >().Subscribe (MyAction , ThreadOption .PublisherThread , false , x => x .Prop1 == "KeyMessage" );
Performance concern
var keepSubscriberReferenceAlive = true ;
var myEvent = eventAggregator.GetEvent <MyEvent >();
myEvent.Subscribe (MyAction , keepSubscriberReferenceAlive );
myEvent.Unsubscribe (MyAction );
SubscriptionToken token = myEvent.Subscribe (MyAction , keepSubscriberReferenceAlive );
myEvent.Unsubscribe (token );
keepSubscriberReferenceAlive
true
the event instance keeps a strong reference to the subscriber instance, thereby not allowing it to get garbage collected.
false
default value. The event maintains a weak reference to the subscriber instance, thereby allowing the garbage collector to dispose the subscriber instance when there are no other references to it. When the subscriber instance gets collected, the event is automatically unsubscribed.
Dialog
Popup window displayed over the current application.
afficher ViewModels/ViewAViewModel.cs
private readonly IDialogService dialogService ;
public DelegateCommand OpenDialogCommand { get ; private set ; }
public ViewAViewModel (IDialogService dialogService )
{
this .dialogService = dialogService;
this .OpenDialogCommand = new DelegateCommand (OpenDialog );
}
private void OpenDialog ()
{
var dialogParameters = new DialogParameters ();
dialogParameters.Add ("message" , "Dialog message !" );
this .dialogService.ShowDialog ("MessageDialog" , dialogParameters , (dialogResult ) =>
{
var buttonResult = dialogResult .Result ;
var value = dialogResult.Parameters.GetValue <string >("key" );
});
}
afficher Dialogs/MessageDialog.xaml
<UserControl x:Class ="ModuleA.Dialogs.MessageDialog"
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d ="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dialogs ="clr-namespace:ModuleA.Dialogs"
xmlns:mc ="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism ="http://prismlibrary.com/"
d:DataContext ="{d:DesignInstance dialogs:MessageDialogViewModel}"
mc:Ignorable ="d" >
<prism:Dialog.WindowStyle >
<Style TargetType ="Window" >
<Setter Property ="Height" Value ="200" />
<Setter Property ="Width" Value ="400" />
<Setter Property ="ResizeMode" Value ="NoResize" />
<Setter Property ="prism:Dialog.WindowStartupLocation" Value ="CenterOwner" />
</Style >
</prism:Dialog.WindowStyle >
<DockPanel >
<Button Command ="{Binding CloseDialogCommand}"
Content ="Ok"
DockPanel.Dock ="Bottom" />
<TextBlock Text ="{Binding Message}" />
</DockPanel >
</UserControl >
afficher Dialogs/MessageDialogViewModel
internal sealed class MessageDialogViewModel : BindableBase , IDialogAware
{
private string message ;
public string Message
{
get { return message; }
set { SetProperty (ref message , value ); }
}
public string Title => "Dialog message" ;
public event Action <IDialogResult > RequestClose ;
public DelegateCommand CloseDialogCommand { get ; private set ; }
public MessageDialogViewModel ()
{
CloseDialogCommand = new DelegateCommand (CloseDialog , CanCloseDialog );
}
public bool CanCloseDialog () => true ;
public void OnDialogClosed ()
{ }
public void OnDialogOpened (IDialogParameters parameters )
{
Message = parameters.GetValue <string >("message" );
}
private void CloseDialog ()
{
var dialogParameters = new DialogParameters ();
dialogParameters.Add ("key" , "value" );
this .RequestClose?.Invoke (new DialogResult (ButtonResult .OK , dialogParameters ));
}
}
afficher ModuleAModule.cs
public void RegisterTypes (IContainerRegistry containerRegistry )
{
containerRegistry.RegisterDialog <MessageDialog , MessageDialogViewModel >();
}