Prism介绍和使用
使用Prism框架,可以为我们带来,IOC,Region,Navigation,Dialog,Binding,MvvM等特征
Application:
Add package:
<PackageReference Include=”Prism.Unity” />
//App.cs
public partial class App : PrismApplication
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);//调用prism的Startup
}
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
程序启动的生命周期:
App.xaml.cs中
App构造函数 -> OnStartup -> RegisterTypes -> ConfigureModuleCatalog -> CreateShell -> 初始化每个子模块-> OnInitialized->ShowWindow
IOC
//老版本使用ServiceLocator
//新版本使用这个API
//需要添加依赖:
// <PackageReference Include="Prism.Container.Extensions" Version="8.0.62" />
ContainerLocator.Container.Resolve<IEventAggregator>();
Region
Region定义
<Window x:Class="Regions.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/"
Title="Shell" Height="350" Width="525">
<Grid>
//如果每次导航到此Region,则会复用或者替换原来的View
<ContentControl prism:RegionManager.RegionName="ContentRegion" />
//如果导航到此Region,则新增或者复用View(因为该Region支持Items)
<TabControl prism:RegionManager.RegionName="ContentRegion" Margin="5" />
</Grid>
</Window>
public MainWindow(IRegionManager regionManager)
{
//view discovery
//程序执行到此句时,会先初始化ViewA和ViewA的ViewModel实例
regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA));
}
ViewRegister
我将View分为两种,一种是用于导航到Region区域的View,另外一种是一个Component,Component可以用于组成第一种的View,将一个大的View分解成很多Component,有助于减少View对应的Viewmodel的逻辑。
第一种的View需要显示的注册到Region或Navigation,并管理生命周期
第二种Component无需注册,直接在View中使用,初始化的时候Component不会被IOC管理,但是使用自动绑定时,对应的Component的ViewModel会在Prism的IOC中进行管理。
public partial class MainWindow : Window
{
IContainerExtension _container;
IRegionManager _regionManager;
public MainWindow(IContainerExtension container, IRegionManager regionManager)
{
InitializeComponent();
_container = container;
_regionManager = regionManager;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// View injection
// 采用这种方式AddView后,该Region会默认显示第一个添加的View
_viewA = _container.Resolve<ViewA>();
_viewB = _container.Resolve<ViewB>();
_region = _regionManager.Regions["ContentRegion"];
_region.Add(_viewA);
_region.Add(_viewB);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//activate view a
_region.Activate(_viewA);
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
//deactivate view a
_region.Deactivate(_viewA);
}
}
Navigation
public class ModuleAModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
//采用此方式注册View,此时不会初始化ViewA及其ViewModel
containerRegistry.RegisterForNavigation<ViewA>();
containerRegistry.RegisterForNavigation<ViewB>();
}
}
public class MainWindowViewModel : BindableBase
{
private readonly IRegionManager _regionManager;
public DelegateCommand<string> NavigateCommand { get; private set; }
public MainWindowViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
NavigateCommand = new DelegateCommand<string>(Navigate);
}
private void Navigate(string navigatePath)
{
if (navigatePath != null)
_regionManager.RequestNavigate("ContentRegion", navigatePath);
}
}
Navigation时,如果目标的ViewModel没有实现INavigationAware接口,则每次Navigation到相同的View,会复用之前的View实例,不会再重新创建新的View和ViewModel。
Navigation with parameters:
var parameters = new NavigationParameters();
parameters.Add("person", personif (person != null)
_regionManager.RequestNavigate("PersonDetailsRegion", "PersonDetail", parameters);
public void OnNavigatedTo(NavigationContext navigationContext)
{
var person = navigationContext.Parameters["person"] as Person;
if (person != null)
SelectedPerson = person;
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
var person = navigationContext.Parameters["person"] as Person;
if (person != null)
return SelectedPerson != null && SelectedPerson.LastName == person.LastName;
else
return true;
}
Navigation Aware
public class ViewAViewModel : BindableBase, INavigationAware
{
public void OnNavigatedTo(NavigationContext navigationContext)
{
}
//Navigation执行逻辑:
如果是第一次Navigation到此View,会执行构造函数和INavigationAware的逻辑
如果不是第一次Navigation到此View,则会先判断当前存在的Navigation的目标是不是目标View
如果是,则复用此View,不会执行构造函数,
如果不是,则重新创建一个View,会执行构造函数
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
}
Navigation callback:
从ViewA导航到ViewB成功后,ViewA的ViewModel中会收到Callback。
public class AViewModel:BindableBase{
private void Navigate(string navigatePath)
{
if (navigatePath != null)
_regionManager.RequestNavigate("ContentRegion", navigatePath, NavigationComplete);
}
private void NavigationComplete(NavigationResult result)
{
MessageBox.Show(String.Format("Navigation to {0} complete. ", result.Context.Uri));
}
}
IConfirmNavigationRequest:INavigationAware
如果想在导航到View时,给用户确认,则ViewModel可以实现此接口
public class ViewAViewModel : BindableBase, IConfirmNavigationRequest
{
public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
{
bool result = true;
if (MessageBox.Show("Do you to navigate?", "Navigate?", MessageBoxButton.YesNo) == MessageBoxResult.No)
result = false;
continuationCallback(result);
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
}
}
IRegionMemberLifetime
ViewModel中实现此接口,意味着Region中的View被其他View给替换时,是否还保留此View的实例。
Module
public class ModuleAModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<ModuleA.ModuleAModule>();
//Add Module with more custom
var moduleAType = typeof(ModuleAModule);
moduleCatalog.AddModule(new ModuleInfo()
{
ModuleName = moduleAType.Name,
ModuleType = moduleAType.AssemblyQualifiedName,
InitializationMode = InitializationMode.OnDemand
});
}
ViewModel
ViewModelLocator:
根据默认的View和ViewModel的命名格式来自动设定DataContext
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Binding & Command
在Prism中,通知Command是否可执行的方式:
注意:关联的属性需要是可通知属性。
方式1:
初始化Command时指定CanExecute函数,当相关的可通知属性发生变化时,调用DelegateCommand.RaiseCanExecuteChanged(), 则会通知Command检查CanExecute是否能用。
public class MainWindowViewModel : BindableBase
{
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
SetProperty(ref _isEnabled, value);
ExecuteDelegateCommand.RaiseCanExecuteChanged();
}
}
public DelegateCommand ExecuteDelegateCommand { get; private set; }
public MainWindowViewModel()
{
ExecuteDelegateCommand = new DelegateCommand(Execute, CanExecute);
}
private void Execute()
{
UpdateText = $"Updated: {DateTime.Now}";
}
private bool CanExecute()
{
return IsEnabled;
}
}
方式2:
初始化Command的时候,使用ObservesProperty来监听属性变化,自动实现,属性变化->CanExecute的流程。
public bool IsEnabled
{
get { return _isEnabled; }
set
{
SetProperty(ref _isEnabled, value);
}
}
DelegateCommandObservesProperty =
new DelegateCommand(Execute, CanExecute).ObservesProperty(() => IsEnabled);
public class MainWindowViewModel : BindableBase
{
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
SetProperty(ref _isEnabled, value);
ExecuteDelegateCommand.RaiseCanExecuteChanged();
}
}
public DelegateCommand ExecuteDelegateCommand { get; private set; }
public DelegateCommand<string> ExecuteGenericDelegateCommand { get; private set; }
public DelegateCommand DelegateCommandObservesProperty { get; private set; }
public DelegateCommand DelegateCommandObservesCanExecute { get; private set; }
public MainWindowViewModel()
{
ExecuteDelegateCommand = new DelegateCommand(Execute, CanExecute);
DelegateCommandObservesProperty = new DelegateCommand(Execute, CanExecute).ObservesProperty(() => IsEnabled);
ExecuteGenericDelegateCommand = new DelegateCommand<string>(ExecuteGeneric).ObservesCanExecute(() => IsEnabled);
}
private void Execute()
{
UpdateText = $"Updated: {DateTime.Now}";
}
private void ExecuteGeneric(string parameter)
{
//UpdateText = parameter;
}
private bool CanExecute()
{
return IsEnabled;
}
}
Event To Command
<Window x:Class="UsingInvokeCommandAction.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="{Binding Title}" Height="350" Width="525">
<Grid>
<ListBox ItemsSource="{Binding Items}" SelectionMode="Single">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<prism:InvokeCommandAction Command="{Binding SelectedCommand}"
CommandParameter="{Binding MyParameter}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</Grid>
</Window>
给ListView的单元格增加双击事件
<ListView Grid.Row="1"
ItemsSource="{Binding FacFolderItems}"
Background="Transparent"
HorizontalAlignment="Left">
<ListView.View>
<GridView>
<GridViewColumn >
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick"
Command="{
Binding DataContext.OpenFavDir
,RelativeSource=
{RelativeSource FindAncestor,
AncestorType=UserControl}}"
CommandParameter="{Binding}"/>
</StackPanel.InputBindings>
<Image
Source="/Resources/folder.png"
Width="15" Height="15"
VerticalAlignment="Center" Margin="5,0" />
<TextBlock Text="{Binding DirectoryPath}"
VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
EventAggregator
使用观察者模式,管理事件的发布和订阅,用于不同模块之间的消息通信,减少耦合和依赖。
<PackageReference Include=”Prism.Core” />
public class MessageSentEvent : PubSubEvent<string>
{
}
public class MessageViewModel : BindableBase
{
IEventAggregator _ea;
private string _message = "Message to Send";
public string Message
{
get { return _message; }
set { SetProperty(ref _message, value); }
public DelegateCommand SendMessageCommand { get; private set; }
public MessageViewModel(IEventAggregator ea)
{
_ea = ea;
SendMessageCommand = new DelegateCommand(SendMessage);
private void SendMessage()
{
_ea.GetEvent<MessageSentEvent>().Publish(Message);
}
}
public class MessageListViewModel : BindableBase
{
IEventAggregator _ea;
private ObservableCollection<string> _messages;
public ObservableCollection<string> Messages
{
get { return _messages; }
set { SetProperty(ref _messages, value); }
}
public MessageListViewModel(IEventAggregator ea)
{
_ea = ea;
Messages = new ObservableCollection<string>();
//Simle subscribe event
_ea.GetEvent<MessageSentEvent>().Subscribe(MessageReceived);
//Subscribe with filter:
_ea.GetEvent<MessageSentEvent>()
.Subscribe(MessageReceived,
ThreadOption.PublisherThread,
false,
(filter) => filter.Contains("Brian"));
}
private void MessageReceived(string message)
{
Messages.Add(message);
}
}
Dialog
定义Dialog View
<UserControl x:Class="StylingDialog.Views.NotificationDialog"
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"
Width="300" Height="150">
<prism:Dialog.WindowStyle>
<Style TargetType="Window">
<Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterScreen" />
<Setter Property="ResizeMode" Value="NoResize"/>
<Setter Property="ShowInTaskbar" Value="False"/>
<Setter Property="SizeToContent" Value="WidthAndHeight"/>
</Style>
</prism:Dialog.WindowStyle>
<Grid x:Name="LayoutRoot" Margin="5">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Message}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="0" TextWrapping="Wrap" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0" Grid.Row="1" >
<Button Command="{Binding CloseDialogCommand}" CommandParameter="true" Content="OK" Width="75" Height="25" IsDefault="True" />
<Button Command="{Binding CloseDialogCommand}" CommandParameter="false" Content="Cancel" Width="75" Height="25" Margin="10,0,0,0" IsCancel="True" />
</StackPanel>
</Grid>
</UserControl>
DialogViewModel需要实现IDialogAware接口
这个接口提供了:
设置窗体的Title的属性
提供了一个DialogCloseListener RequestClose,
可以通过调用此来通知prism关闭此弹窗,
关闭弹窗时需要指定DialogResult,new DialogResult(ButtonResult.Cancel),用来表示,弹窗关闭后,是否需要继续处理或者Cancel。
CanCloseDialog(): 实现此方法,指示是否可以关闭此弹窗
OnDialogClosed(): 实现此方法,可以在关闭弹窗的时候,添加一些参数
OnDialogOpened():实现此方法,可以在弹窗打开的时候,获取传入的一些参数。
生命周期为:
ShowDialog -> OnDialogOpened ->RequestClose ->CanCloseDialog -> OnDialogClosed
定义Dialog ViewModel
public partial class NotificationDialogViewModel : ObservableObject, IDialogAware
{
[ObservableProperty]
private string _Title;
[ObservableProperty]
private string _Message;
public DialogCloseListener RequestClose { get; }
public bool CanCloseDialog()
{
return true;
}
public void OnDialogClosed()
{
}
public void OnDialogOpened(IDialogParameters parameters)
{
var title = parameters.GetValue<string>("Title");
Title = title;
var message = parameters.GetValue<string>("Message");
Message = message;
}
[RelayCommand]
private void CloseDialog(string value)
{
if (value.Equals("true"))
{
RequestClose.Invoke(new DialogResult(ButtonResult.OK));
//带参数
RequestClose.Invoke(new DialogResult(ButtonResult.OK) { Parameters = parameters });
}
}
}
设置Dialog的window样式:
在定义Dialog的地方添加下方的Style
<prism:Dialog.WindowStyle>
<Style TargetType="Window">
<Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterScreen" />
<Setter Property="ResizeMode" Value="NoResize"/>
<Setter Property="ShowInTaskbar" Value="False"/>
<Setter Property="SizeToContent" Value="WidthAndHeight"/>
</Style>
</prism:Dialog.WindowStyle>
Prism中IDialogWindow只能设置一种,所以使用框架里的ShowDialog方式,所有的Window都是同一个,如果想在Window中设置某些Window事件,则会影响所有的窗口,可以考虑自定义实现一个新的DialogWindow并注册到IOC中。
public interface IPopupDialogWindow : IDialogWindow
{
}
新建一个WPF window,并实现接口IPopupDialogWindow
新增一个DialogService,并继承自Prism中的DialogService
public class MyDialogService:DialogService{
protected virtual TDialogWindow CreateDialogWindow<TDialogWindow>() where TDialogWindow : IDialogWindow
{
return containerExtension.Resolve<TDialogWindow>();
}
protected virtual void ConfigureDialogWindowEventsWrapper(IDialogWindow dialogWindow, Action<IDialogResult> callback)
{
BindingFlags eFlags = BindingFlags.Instance | BindingFlags.Public |
BindingFlags.NonPublic;
var ConfigureDialogWindowEventsMethod = this.GetType().BaseType.GetMethod("ConfigureDialogWindowEvents", eFlags);
ConfigureDialogWindowEventsMethod.Invoke(this, new object[] { dialogWindow, callback });
}
protected virtual void ConfigureDialogWindowContentWrapper(string viewName, IDialogWindow window, IDialogParameters parameters)
{
BindingFlags eFlags = BindingFlags.Instance | BindingFlags.Public |
BindingFlags.NonPublic;
var ConfigureDialogWindowContentMethod = this.GetType().BaseType.GetMethod("ConfigureDialogWindowContent", eFlags);
ConfigureDialogWindowContentMethod.Invoke(this, new object[] { viewName, window, parameters });
}
public void ShowDialog<TDialogWindow>(string viewName, IDialogParameters parameters, Action<IDialogResult> callback) where TDialogWindow : IDialogWindow
{
IDialogWindow dialogWindow = CreateDialogWindow<TDialogWindow>();
ConfigureDialogWindowEventsWrapper(dialogWindow, callback);
ConfigureDialogWindowContentWrapper(viewName, dialogWindow, parameters);
dialogWindow.ShowDialog();
}
}
在Prism的IOC中注册Dialog
public partial class App
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterDialog<NotificationDialog, NotificationDialogViewModel>();
}
}
使用Dialog
var parameters = new DialogParameters();
parameters.Add("Key","Value");
//方式1:
_dialogService.ShowDialog("NotificationDialog", parameters, r =>
{
if (r.Result == ButtonResult.None)
Title = "Result is None";
else if (r.Result == ButtonResult.OK)
Title = "Result is OK";
else if (r.Result == ButtonResult.Cancel)
Title = "Result is Cancel";
else
Title = "I Don't know what you did!?";
//获取弹窗返回时的参数:
r.Parameters.Get
});
// 方式2:
var dialogResult=_dialogService.ShowDialog("NotificationDialog", parameters, r => r);
if(dialogResult == ButtonResult.Ok){
...
}