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
|
<!-- automatically wire the DataContext of MyView to an instance of MyApp.ViewModels.MyViewModel -->
<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:mvvm="http://prismlibrary.com/"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance viewModels:MyViewModel}"
mvvm:ViewModelLocator.AutoWireViewModel="True"> <!-- true by default -->
|
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
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);
SubmitCommand.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();
}
|
App.xaml.cs
|
public partial class App
{
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// register a singleton service
containerRegistry.RegisterSingleton<IMyService, MyService>();
// registera transient service
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.
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));
}
|
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.
App.xaml.cs
|
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<ModuleAModule>();
}
|
App.config
More loosely coupled approach.
App.xaml.cs
|
protected override IModuleCatalog CreateModuleCatalog()
=> new ConfigurationModuleCatalog();
|
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>
|
|
REM ModuleA post build event
xcopy /y "$(TargetDir)*.*" "$(SolutionDir)$(SolutionName)\$(OutDir)"
|
Directory
Define a location on disk where to find the modules.
App.xaml.cs
|
protected override IModuleCatalog CreateModuleCatalog()
=> new DirectoryModuleCatalog() { ModulePath = @".\Modules" };
|
|
REM ModuleA post build event
xcopy /y "$(TargetDir)$(TargetName)$(TargetExt)" "$(SolutionDir)$(SolutionName)\$(OutDir)Modules\"
REM xcopy /y C:\PrismNetCore\ModuleA\bin\Debug\net5.0-windows\ModuleA.dll C:\PrismNetCore\PrismNetCore\bin\Debug\net5.0-windows\Modules\
|
XAML resource dictionary
Event mechanism that enables communications between loosely coupled components in the application.
MyApp.Shared/MyPayload.cs
|
public sealed class MyPayload
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
|
MyApp/MyViewModel.cs
|
internal sealed class MyViewModel
{
IEventAggregator eventAggregator;
public MyViewModel(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
}
// publishing an event
var payload = new MyPayload
{
Prop1 = "value1",
Prop2 = "value2",
};
eventAggregator.GetEvent<PubSubEvent<MyPayload>>().Publish(payload);
// subscribing to events
eventAggregator.GetEvent<PubSubEvent<MyPayload>>().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(DisplayMessage, ThreadOption.UIThread, false, x => x == "KeyMessage");
|
Performance concern
|
var keepSubscriberReferenceAlive = true;
var myEvent = eventAggregator.GetEvent<MyEvent>();
myEvent.Subscribe(DisplayMessage, keepSubscriberReferenceAlive);
myEvent.Unsubscribe(DisplayMessage);
|
keepSubscriberReferenceAlive
true |
the event instance keeps a strong reference to the subscriber instance, thereby not allowing it to get garbage collected. For information about how to unsubscribe, see the section Unsubscribing from an Event later in this topic.
|
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.
|