WPF Prism入门笔记
WPF Prism入门笔记
使用技术:Prism、ReactiveProperty
Prism项目创建
Prism项目创建有多重方式,这里列两种:
- 使用Prism Template Pack插件创建(要求Visual Studio 2019以上,笔者在2017上没有找到这个插件)
- 创建普通WPF程序,然后手动修改配置
Prism Template Pack插件创建(推荐)
在Visual Studio扩展中安装Prism Template Pack然后重启,创建项目中就有Prism项目,可以创建Prism Full App,如果不熟悉的话推荐创建Prism Blank APP,框架使用.net Core
或者.net Framework
都可以。
创建好就可以看到最基础的项目结构。
手动创建
创建一个普通WPF程序,然后在Nugget中添加Prism.Unity、ReactiveProperty两个引用。
删除APP.xaml中的StartUrl属性,添加prism属性:xmlns:prism="http://prismlibrary.com/"
(注意这里的网址最后要带上/
),然后把跟标签Application
修改为prism:PrismApplication
App.xaml.cs不再继承Window类,并且提示需要实现RegisterTypes抽象方法,使用空实现即可。
创建文件夹Views
、ViewModels
,并在Views中创建一个测试用的Index窗口,Index.xaml中的跟标签需要添加xmlns:prism="http://prismlibrary.com/" prism:ViewModelLocator.AutoWireViewModel="True"
两个属性;在ViewModels文件夹创建IndexViewModel类(public),继承BindableBase
在App.xaml.cs中重写CreateShell方法来设置起始页,把Index添加到Container中:return Container.Resolve<Index>();
这样,prism就会自动把Views文件夹中的视图和ViewModels文件夹中的视图模型绑定起来。
同时还可以配置默认Views和ViewModels文件夹的名称、注册自定义ViewModel,详见.NET Core 3 WPF MVVM框架 Prism系列之数据绑定
文章中的Text属性可以使用ReactiveProperty进行简化,绑定写作{Binding Text.Value}
ReactiveProperty、ReactiveCommand的简单使用
ReactiveProperty可以看做一个响应式的绑定容器,不再需要手动去实现WPF中的INotifyPropertyChanged接口,同时还添加了一些Linq方法。
首先在xaml中添加一个文本框、一个按钮、一个单选框:
<StackPanel>
<!-- 绑定ReactiveProperty类型的属性时需要在后面加.Value -->
<TextBox Name="TextBox1" Height="23"
Text="{Binding Text1.Value}"/>
<!-- 如果不需要带参数,就不需要CommandParameter属性 -->
<Button Name="Button1"
Content="Button1"
Command="{Binding Button1Command}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}}"/>
<CheckBox Content="CheckBox"
IsChecked="{Binding IsChecked1.Value}"/>
</StackPanel>
然后在viewModel中加入如下属性:
// ReactiveProperty的泛型是存储值的类型
public ReactiveProperty<string> Text1 { get; } = new ReactiveProperty<string>();
// ReactiveCommand的泛型是参数的类型,比如上面Button1的CommandParameter将按钮自身作为参数传进来,如果不需要传参,可以不加泛型。
public ReactiveCommand<Button> Button1Command { get; } = new ReactiveCommand<Button>();
public ReactiveProperty<bool> IsChecked1 { get; } = new ReactiveProperty<bool>();
然后在viewModel的构造方法中添加command的响应处理:
Button1Command.Subscribe(btn => {
this.Text1.Value = DateTime.Now.ToString();
});
这样点击按钮就可以把当前时间显示在编辑框内
如果想要单选框选中的时候才能点击按钮,可以根据IsChecked1生成ReactiveCommand
Button1Command = IsChecked1.ToReactiveCommand<Button>();
Button1Command.Subscribe(btn => {
this.Text1.Value = DateTime.Now.ToString();
});
这样单选框未选中的状态,按钮将不能点击。
如果有两个单选框,需要根据这两个单选框的状态来判断按钮是否可点击,只需要这么写:
// 这个数据的类型是IObservable<bool>[]
Button1Command = new[] {
IsChecked1.Select(s => s),
IsChecked2.Select(s => s),
}.CombineLatest(s => s[0] && s[1]).ToReactiveCommand<Button>();
这样两个点选框都选中的时候按钮才能点击,并且这种是关系响应式的。
异步命令
ReactiveCommand的代码是同步执行的,如果执行时间过长,会阻塞UI线程,因此就可以使用异步command
例子:
AsyncCommand = new AsyncReactiveCommand();
AsyncCommand.Subscribe(async () => {
await Task.Run(() => {
Thread.Sleep(3000);
this.Text1.Value = "Async Test";
});
});
事件转命令
如果需要通过事件来执行命令,可以使用下面这种方式。
首先添加System.Windows.Interactivity的引用,并在xaml根标签中添加属性:xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
然后就可以添加第一个文本框:
<TextBox Name="TextBox2" Height="23"
Text="{Binding Text2.Value, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding Text2ChangeCommand}"
CommandParameter="{Binding ElementName=Button1}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
然后在viewModel中声明一个Text2ChangeCommand属性即可。
对弈TextChange事件,会有一个TextChangedEventArgs参数,如果想要把这个参数是传给command,上述方法是不行的,可以使用prism提供的InvokeCommandAction,也就是把上述xml中的InvokeCommandAction修改为:
<prism:InvokeCommandAction Command="{Binding Text2ChangeCommand}"/>
如果只需要串EventArgs中的特定属性,比如触发该事件的名字,也就是父类RoutedEventArgs的Source属性,只需在标签上加如下属性:
<prism:InvokeCommandAction Command="{Binding Text2ChangeCommand}" TriggerParameterPath="Source"/>