Prism 8在WPF项目中的使用
Prism可用来开发一个松耦合,可维护,可测试的WPF或者Xamain Forms,主要功能包括Delegate Commands,Composite Commands,Event Aggregation,Modules, Regions, Dependency Injection, Navigation, ViewModelLocator, Dialog Services。目前支持WPF,Xamain Forms,UNO。
接下来会介绍每个功能如何使用。
一、创建一个简单的Prism Demo
- 使用VS2017或以上创建一个WPF程序,添加Prism.Unity或者Prism.DryIoc,推荐使用前者。
- 修改App.xaml:
<prism:PrismApplication x:Class="Centurion.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
xmlns:local="clr-namespace:Centurion">
<!--StartupUri="Views/Shell.xaml"-->
<Application.Resources>
</Application.Resources>
</prism:PrismApplication>
public partial class App : PrismApplication
{
}
- RegisterTypes:注册依赖。
例如,现在有一个写log的接口,需要定义它的实现:
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// Dependency Injection
containerRegistry.RegisterSingleton<ILogger, Log>();
}
- CreateShell:将创建应用程序的主窗口。
protected override Window CreateShell()
{
return Container.Resolve<Shell>();
}
- View Models:Prism有一个处理INotifyPropertyChanged基础设施的基类,该基础设施将从视图模型发布到视图的更改。还有一些其他的类可以使从视图模型中处理按钮变得简单,而不是在后台代码中编写事件处理程序。例如,Shell前台有一个"LogOn"按钮:
<Grid HorizontalAlignment="Left">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid x:Name="MenuGrid" Grid.Row="0">
<Button Content="LogOn" Width="100" Command="{Binding LogOnCommand}"/>
</Grid>
</Grid>
然后ShellViewModel定义LogOnCommand:
public class ShellViewModel: BindableBase
{
public string _Title = "Prism Demo";
public string Title
{
get { return _Title; }
set { SetProperty(ref _Title, value); }
}
public DelegateCommand LogOnCommand { get; private set; }
private IDialogService DialogService;
public ShellViewModel(IDialogService _dialogService)
{
DialogService = _dialogService;
LogOnCommand = new DelegateCommand(ShowLogOnDialog);
}
private void ShowLogOnDialog()
{
DialogService.ShowDialog("LogOnDialog", new DialogParameters($"message=123"), r =>
{
});
}
}
- 然后我们需要将上面的Shell和ShellViewModel绑定起来,就要用到ViewModelLocator:
<Window ...
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
...
>
二、Prism功能介绍及代码示例
- Delegate Commands:这个功能简单理解就是在ViewModel中执行Command,像上面提到的,Shell有一个按钮LogOn需要绑定Command来实现LogOn功能,在ShellViewModel中定义LogOnCommand来实现。
<Window x:Class="Demo.Views.Shell"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:local="clr-namespace:Centurion.Views"
mc:Ignorable="d"
Title="{Binding Title}" Height="450" Width="800">
<Grid HorizontalAlignment="Left">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid x:Name="MenuGrid" Grid.Row="0">
<Button Content="LogOn" Width="100" Command="{Binding LogOnCommand}"/>
</Grid>
</Grid>
</Window>
public class ShellViewModel: BindableBase
{
public string _Title = "Prism Demo";
public string Title
{
get { return _Title; }
set { SetProperty(ref _Title, value); }
}
public DelegateCommand LogOnCommand { get; private set; }
private IDialogService DialogService;
public ShellViewModel(IDialogService _dialogService)
{
DialogService = _dialogService;
LogOnCommand = new DelegateCommand(ShowLogOnDialog);
}
private void ShowLogOnDialog()
{
DialogService.ShowDialog("LogOnDialog", new DialogParameters($"message=123"), r =>
{
});
}
}
- Composite Commands:组合的Command,比如现在有按钮“Show All”, “Show Grid1”,“Show Grid2”,点击后,点击“Show All”按钮时,“Show Grid1”和“Show Grid2”的点击事件也要执行,那么就可以给“Show All”定义一个CompositeCommand ShowAllDelegateCommand,然后“Show Grid1”和“Show Grid2”的Command也要绑定到ShowAllDelegateCommand上。
public DelegateCommand ShowGrid1Command { get; private set; }
public DelegateCommand ShowGrid2Command { get; private set; }
public CompositeCommand ShowAllDelegateCommand { get; private set; }
public ViewAViewModel()
{
ShowAllDelegateCommand = new CompositeCommand();
ShowAllDelegateCommand.RegisterCommand(ShowGrid1Command);
ShowAllDelegateCommand.RegisterCommand(ShowGrid2Command);
}
- Event Aggregation,事件聚合器,比如说我们需要收集整个程序所有模块的按钮动作,先定义一个MessageSentEvent:
public class MessageSentEvent : PubSubEvent<string>
{
}
在需要使用的ViewModel中定义EventAggregator,使用事件发布:
IEventAggregator EventAggregator;
public ViewAViewModel(IEventAggregator ea)
{
EventAggregator = ea;
}
private void Execute(string str)
{
EventAggregator.GetEvent<MessageSentEvent>().Publish("Publish ViewA Events.");
}
IEventAggregator EventAggregator;
public ViewBViewModel(IEventAggregator ea)
{
EventAggregator = ea;
}
private void Execute(string str)
{
EventAggregator.GetEvent<MessageSentEvent>().Publish("Publish ViewB Events.");
}
在需要订阅的ViewModel中使用,这样每当其他ViewModel有事件发生,这里订阅的也会发生。
private IEventAggregator EventAggregator;
public ShellViewModel(IEventAggregator ea)
{
EventAggregator = ea;
EventAggregator.GetEvent<MessageSentEvent>().Subscribe(MessageCollectAction, ThreadOption.UIThread);
}
private void MessageCollectAction(string str)
{
Logger.Log(str);
}
- Modules,一个解决方案有多个模块,注册后才能使用这些模块的功能。
定义模块LogModule:
public class LogModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("LogContentRegion", typeof(LogView));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
在App.xaml.cs注册模块:
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<LogModule.LogModule>();
}
- Regions,区域管理,比如现在界面有一块区域Region,点Button A时显示A,点Button B时显示B,
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<Button
Margin="5"
Command="{Binding OpenCommand}"
CommandParameter="ViewA"
Content="模块A" />
<Button
Margin="5"
Command="{Binding OpenCommand}"
CommandParameter="ViewB"
Content="模块B" />
</StackPanel>
<ContentControl Grid.Column="1" prism:RegionManager.RegionName="ModuleContent" />
</Grid>
<UserControl
x:Class="BlankCoreApp1.Views.ViewA"
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:local="clr-namespace:BlankCoreApp1.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid Background="Red">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="60"
Foreground="White"
Text="我是模块A" />
</Grid>
</UserControl>
public ShellViewModel(IRegionManager regionManager)
{
_regionManage = regionManager;
OpenCommand = new DelegateCommand<string>(OpenMethod);
}
private void OpenMethod(string obj)
{
_regionManage.Regions["ModuleContent"].RequestNavigate(obj);
}
还需要在App.xaml.cs中注册这些区域视图:
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<ViewA>();
containerRegistry.RegisterForNavigation<ViewB>();
}
- Dependency Injection:将接口与继承相对应,譬如上面提到的写log的ILog接口,有两种实现,log到Txt文档和Log到一个客户的Webapi服务器。
private int Type { get; set; }
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// Dependency Injection
if (Type == 0)
containerRegistry.RegisterSingleton<ILogger, LogToTxt>();
else if (Type == 1)
containerRegistry.RegisterSingleton<ILogger, LogToWebAPI>();
}
- Navigation导航:
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<ViewB,ViewBViewModel>();
}
实现导航页面的功能:
public class ViewBViewModel : INavigationAware
{
public bool IsNavigationTarget(NavigationContext navigationContext)
{
throw new NotImplementedException();
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
throw new NotImplementedException();
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
throw new NotImplementedException();
}
}
- ViewModelLocator:将View与ViewModel绑定起来。
- Dialog Services:将页面注册成对话框类型。
<UserControl x:Class="AccessControl.Views.NotificationDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:AccessControl.Views"
mc:Ignorable="d"
Width="300" Height="150">
<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>
public class NotificationDialogViewModel : BindableBase, IDialogAware
{
private string _message;
public string Message
{
get { return _message; }
set { SetProperty(ref _message, value); }
}
private string _title = "Notification";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private DelegateCommand<string> _closeDialogCommand;
public DelegateCommand<string> CloseDialogCommand =>
_closeDialogCommand ?? (_closeDialogCommand = new DelegateCommand<string>(CloseDialog));
public event Action<IDialogResult> RequestClose;
protected virtual void CloseDialog(string parameter)
{
ButtonResult result = ButtonResult.None;
if (parameter?.ToLower() == "true")
result = ButtonResult.OK;
else if (parameter?.ToLower() == "false")
result = ButtonResult.Cancel;
RaiseRequestClose(new DialogResult(result));
}
public virtual void RaiseRequestClose(IDialogResult dialogResult)
{
RequestClose?.Invoke(dialogResult);
}
public bool CanCloseDialog()
{
return true;
}
public void OnDialogClosed()
{
}
public void OnDialogOpened(IDialogParameters parameters)
{
Message = parameters.GetValue<string>("message");
}
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterDialog<NotificationDialog, NotificationDialogViewModel>();// Dialog Service
}