WPF Prism入门笔记

使用技术:Prism、ReactiveProperty

Prism项目创建

Prism项目创建有多重方式,这里列两种:

  1. 使用Prism Template Pack插件创建(要求Visual Studio 2019以上,笔者在2017上没有找到这个插件
  2. 创建普通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抽象方法,使用空实现即可。
创建文件夹ViewsViewModels,并在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"/>

标签: C#, WPF

添加新评论