Prism-WPF使用
- 前言
- [Prism ------ DelegateCommand](#Prism —— DelegateCommand)
- [Prism ------ PubSubEvent](#Prism —— PubSubEvent)
- [Prism ------ Navigate](#Prism —— Navigate)
- 总结
前言
这是第二篇关于Prism-WPF的介绍,第一篇中我们简单介绍了Prism,然后讲述了如何搭建一个MVVM的简单页面程序。其实我写的文章就是把github上面的官方例子摘出来自己跑了一遍,然后加上了一些自己的理解,简单给大家分享一下。
Prism ------ DelegateCommand
我们完成了View和ViewModel的关联之后,就是为了使用Binding功能,那么我们想要触发UI事件,并执行我们自己的方法,很多情况下都会需要用到Command。
比如对于一个按钮,<Button Content="你好" Command="{Binding SayHelloCommand}"/>
我们为其添加了Command的绑定,当我们触发按钮的点击事件的时候,Command的Action就会被执行,而Command则是一个需要继承接口ICommand
的一个实例。
我们需要去定义我们自己的Command才能够去使用,当然也有各种各样的库,都提供了各种Command(我个人暂时没有了解过这些Command具体有什么区别)
这是Prism提供的Command类型------DelegateCommand
csharp
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
SetProperty(ref _isEnabled, value);
ExecuteDelegateCommand.RaiseCanExecuteChanged();
}
}
private string _updateText;
public string UpdateText
{
get { return _updateText; }
set { SetProperty(ref _updateText, value); }
}
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);
DelegateCommandObservesCanExecute = new DelegateCommand(Execute).ObservesCanExecute(() => 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;
}
DelegateCommand就是标准的ICommand,需要有一个Execute()
的Action(Action是一个不需要返回值的泛型委托类型)和一个可选的CanExecute()
的Func(Func是一个必须要有返回值的泛型委托类型)
因此,我们定义一个Command只需要如下代码
csharp
public DelegateCommand ExecuteDelegateCommand { get; private set; }
public MainWindowViewModel()
{
ExecuteDelegateCommand = new DelegateCommand(Execute, CanExecute);
}
那么诸如
ExecuteDelegateCommand.RaiseCanExecuteChanged();
.ObservesProperty(() => IsEnabled);
.ObservesCanExecute(() => IsEnabled);
又是做什么用的呢?
我们知道即使我们使用了Binding之后,也不是说视图和视图模型就会完全同步了,如果是视图改变了某些东西,视图模型会直接发生变化,但是如果是视图模型因为某些逻辑,改变了某个值,这时候,我们需要通过事件来主动通知视图需要做出某些改变,我们的ViewModel通常需要继承接口INotifyPropertyChanged
,这个接口提供了通知View元素需要改变的事件。
那么同理,CanExecute会影响按钮是否被Enable的状态,这个状态会决定按钮能否被点击,同样是UI控件的变化,需要主动通知,而我们刚才写的那三种写法,就是通过不同的写法来通知UI的按钮需要调整Enable状态。
Prism ------ PubSubEvent
我们能够让UI通过委托执行后台逻辑的代码之后呢,我们就要满足第二种需求,就是几个不同的页面直接的通信。想要实现页面之间的通信,我们有几种方法
- 让多个页面共享视图模型,不过这个方法比较丑陋,导致代码耦合度急剧上升,并且会导致代码混乱,优势就是没有什么额外的资源开销,属于效率最高的模式之一了
- 通过创建视图的时候传递参数,进行一次性的通讯,只需要传递一次的消息或者资源,我个人认为只需要传递参数就行
- 通过事件的发布和订阅来实现消息的传递,一般情况下,我们都会使用这种方式,而成熟的框架也会为我们实现好这一服务,而不需要我们自己手动写一套事件管理器,但是使用事件的时候需要额外注意,如果有需要,一定要及时注销的于事件的订阅,不然可能会导致内存泄露
PubSubEvent就是Prism提供的事件管理器,为我们提供了一个使用起来比较简单的视图通讯机制。
-
首先我们需要先定义自己的事件
csharppublic class MessageSentEvent : PubSubEvent<string> { }
-
然后在需要触发消息的位置写如下代码
csharppublic 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); } }
可以看到ViewModel的构造函数写了一个参数
IEventAggregator ea
这边构造函数是Prism框架自动实例化的,因此参数也是从IoC容器中自动找到后自动传递的。通过这个事件管理器来获取事件并调用Publish发送,我们就完成了消息的发布动作
_ea.GetEvent<MessageSentEvent>().Publish(Message);
-
然后我们需要接收消息
csharppublic 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>(); _ea.GetEvent<MessageSentEvent>().Subscribe(MessageReceived); } private void MessageReceived(string message) { Messages.Add(message); } }
同样的,
_ea.GetEvent<MessageSentEvent>().Subscribe(MessageReceived);
通过调用Subscribe(),我们就完成了事件的监听,当事件触发时就会调用传入Subscribe的回调函数。
上面的例子大致是用于实现把一个消息框中的消息,存入另外一个页面里面的消息列表中去。
Prism ------ Navigate
顾名思义,这就是页面导航功能,我不知道其他框架有没有提供,但是有了这个功能之后做页面导航或者页面切换十分方便。
我记得以前我做的页面切换显示就是自己通过修改Visibility或者修改ContentControl的内容来实现页面切换,现在我们将操作托管给Prism框架,我们只是把资源给框架,然后通知框架选择什么资源来显示。
Prism的Navigation通过如下两个步骤实现:
-
首先,我们需要创建一个Module
在Module中注册我们的页面信息
csharppublic class ModuleAModule : IModule { public void OnInitialized(IContainerProvider containerProvider) { } public void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterForNavigation<ViewA>(); containerRegistry.RegisterForNavigation<ViewB>(); } }
-
将Module信息注册到App.xaml.cs中去
csharp/// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : PrismApplication { protected override Window CreateShell() { return Container.Resolve<MainWindow>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { } protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { moduleCatalog.AddModule<ModuleA.ModuleAModule>(); } }
-
在主页面创建Region,用于显示子页面
xml
<Window x:Class="BasicRegionNavigation.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">
<DockPanel LastChildFill="True">
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="5" >
<Button Command="{Binding NavigateCommand}" CommandParameter="ViewA" Margin="5">Navigate to View A</Button>
<Button Command="{Binding NavigateCommand}" CommandParameter="ViewB" Margin="5">Navigate to View B</Button>
</StackPanel>
<ContentControl prism:RegionManager.RegionName="ContentRegion" Margin="5" />
</DockPanel>
</Window>
- 在主页面视图模型中完成导航操作的实现
通过代码中的_regionManager.RequestNavigate
实现了通知region当前应该显示哪个页面。
csharp
public class MainWindowViewModel : BindableBase
{
private readonly IRegionManager _regionManager;
private string _title = "Prism Unity Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
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, NavigationComplete);
}
private void NavigationComplete(NavigationResult result)
{
System.Windows.MessageBox.Show(String.Format("Navigation to {0} complete. ", result.Context.Uri));
}
}
总结
现在我们已经学会绑定、命令、导航、消息传递等功能,已经能够实现一个比较复杂的应用了,这个Prism框架我个人觉得如果有一定基础,上手其实是很快的。
最后我写了一个样例工程,包含了上述功能的应用
Prism-Wpf-Demo