WPF入门到跪下 第十一章 Prism(二)行为处理

2024-01-22 22:36

本文主要是介绍WPF入门到跪下 第十一章 Prism(二)行为处理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Prism框架提供了DelegateCommand类型,专门用于进行WPF中的行为处理。

基本使用

一、命令的使用

DelegateCommand(Action executeMethod)DelegateCommand的构造函数,创建DelegateCommand对象。

  • executeMethod:无参的命令执行函数。

定义命令

public class MainViewModel
{public ICommand BtnCommand => new DelegateCommand(Execute);private void Execute(){//做命令执行业务}
}

xaml中绑定命令

<Window ......><Window.DataContext><local:MainViewModel/></Window.DataContext><Grid><StackPanel><Button Content="BtnCommand" Command="{Binding BtnCommand}"/></StackPanel></Grid>
</Window>

以上做法就能实现最简单的命令了,缺点是无法进行命令状态检查。

带参数的命令

Prism框架中,命令的使用跟MVVMToolkit框架是类似的,命令的执行函数可以有参,也可以无参,具体用法如下:

public ICommand BtnCommand => new DelegateCommand<string>(Execute);private async void Execute(string str)
{await Task.Delay(2000);Value = 100;
}

二、命令的状态检查

Prism提供了三种命令状态检查方式

1、方式1:调用RaiseCanExecuteChanged方法

可以向DelegateCommand的构造函数中传入状态检查的委托函数。然后在合适的节点去调用DelegateCommand对象的RaiseCanExecuteChanged方法来执行状态检查函数。

DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)DelegateCommand的构造函数,创建DelegateCommand对象。

  • executeMethod:无参的命令执行函数。
  • canExecuteMethod:命令的状态检查函数。

RaiseCanExecuteChanged():调用DelegateCommand对象的命令状态检查函数(构造时传入的函数)。

定义命令

需要注意以下两点:

  • RaiseCanExecuteChanged():此方法是DelegateCommand对象的方法,ICommand接口中没有,为了后续方便,创建时直接使用DelegateCommand而不是ICommand类型。
  • DelegateCommand对象的初始化(new)必须放在所在类型的构造函数中进行,否则调用RaiseCanExecuteChanged函数不起作用。
public class MainViewModel
{private int _value = 10;public int Value{get { return _value; }set { _value = value;//调用命令状态检查函数BtnCheckCommand.RaiseCanExecuteChanged();}}//声明属性,但不做初始化,留在构造函数中做public DelegateCommand BtnCheckCommand { get; }private bool CheckCanExecute(){return Value == 100;}private void Execute(){//做命令执行业务}public MainViewModel(){//继续命令初始化,只能放在这里做,放在属性中进行初始化的话,调用状态检查函数无效果。BtnCheckCommand = new DelegateCommand(Execute, CheckCanExecute);}
}

2、方式2:使用ObservesProperty监听属性变化

DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)DelegateCommand的构造函数,创建DelegateCommand对象。

  • executeMethod:无参的命令执行函数。
  • canExecuteMethod:命令的状态检查函数。

DelegateCommand ObservesProperty<T>(Expression<Func<T>> propertyExpression)DelegateCommand对象的方法,用于监听指定属性的变化,一旦属性发生变化,就调用构造时传入的状态检查函数。

  • propertyExpression:一个返回指定监控属性的无参Lambda表达式。
  • 由于调用后就返回监听该属性的DelegateCommand对象,因此可以通过链式调用监听多个属性的变化。
public class MainWindowViewModel : BindableBase
{private int _value1;public int Value1{get { return _value1; }set { SetProperty(ref _value1, value); }}private int _value2;public int Value2{get { return _value2; }set { SetProperty(ref _value2, value); }}private void Execute(){//做命令执行业务}public DelegateCommand BtnCheckCommand { get; }private bool CheckCanExecute(){return Value1 + Value2 == 100;}public MainWindowViewModel(){BtnCheckCommand = new DelegateCommand(Execute, CheckCanExecute).ObservesProperty(() => Value1).ObservesProperty(() => Value2);//通过链式结构可以同时监听多个属性,进行组合判断}
}

这种方式比第1种方式要更加方便,不用考虑在哪个地方去主动调用命令状态检查函数。

3、方式3:使用ObservesProperty

DelegateCommand ObservesCanExecute(Expression<Func<bool>> canExecuteExpression)DelegateCommand的实例方法,根据传入的Lambda表达式,来检查命令的可执行状态。

  • canExecuteExpression:一个返回bool类型属性的表达式。

注意

传递给ObservesCanExecute的Lambda表达式中,需要返回bool类型的属性而不是直接的bool值,否则无效。

传递给ObservesCanExecute的Lambda表达式中返回的属性,有两点需要注意:

  • 必须做属性变化通知处理。
  • 在第二次属性变化通知起,才能正确完成命令可执行状态检查,也就是第一次变化通知不会正常完成命令状态检查,需要提前做一下第一次的属性变化。
  • 此属性发生变化时才会触发命令可执行状态检查,因此要在合适的时候去改变属性来触发检查。

命令定义

public class MainViewModel: BindableBase
{private int _value = 10;public int Value{get { return _value; }set{_value = value;//在合适的时机去改变Status属性,触发变化通知,从而调用状态检查Status = !Status;}}public bool status = false;public bool Status{get { return Value == 100; }set {//一定要做属性变化通知,以此来触发状态检查SetProperty(ref status, value); }}private void Execute(){//做命令执行业务}public DelegateCommand BtnCheckCommand { get; }public MainViewModel(){//与前两中方式相比,可以不需要传入命令状态检查函数BtnCheckCommand = new DelegateCommand(Execute).ObservesCanExecute(()=> Status);Status = !Status;//要先触发一次Status的属性变化通知,在第二次属性变化通知时才能顺利的检查命令的状态}
}

三、异步处理

Prism的异步处理没什么新鲜的,就是正常的使用异步函数,具体如下

public ICommand BtnCommand
{get => new DelegateCommand(Execute);
}private async void Execute()
{await Task.Delay(2000);Value = 100;
}

触发事件的选用

默认的命令触发事件是鼠标左键单击,如果希望通过其他事件来触发指定的命令,除了需要用到Prism的控件外,还需要借助Behaviors库的控件。

Nuget中安装Behaviors库

PS:Prism框架中自带了这个包,因此在Prism框架下可以直接使用。

在这里插入图片描述

一、事件触发命令

定义命令

public class MainViewModel: BindableBase
{private int value = 10;public int Value{get { return value; }set {SetProperty(ref this.value, value);    }}public ICommand BtnCommand { get => new DelegateCommand<object>(Execute); }private void Execute(object obj){Value = 100;}}

xaml中绑定命令

注意,这里因为使用了Prism框架的命令,因此在绑定命令时使用了prism:InvokeCommandAction。如果没有使用Prism框架,可以使用i:InvokeCommandAction,也就是Behaviors包自带的。

<Window ......xmlns:i="http://schemas.microsoft.com/xaml/behaviors"xmlns:prism="http://prismlibrary.com/"......><Window.DataContext><local:MainViewModel/></Window.DataContext><Grid><StackPanel><TextBlock Text="{Binding Value}"/><ComboBox SelectedIndex="0"><i:Interaction.Triggers><i:EventTrigger EventName="SelectionChanged"><prism:InvokeCommandAction Command="{Binding BtnCommand}"/></i:EventTrigger></i:Interaction.Triggers><ComboBoxItem Content="item1"/><ComboBoxItem Content="item2"/><ComboBoxItem Content="item3"/><ComboBoxItem Content="item4"/></ComboBox></StackPanel></Grid>
</Window>

注意

默认情况下,上述做法会直接将EventArgs对象作为参数传递给命令执行函数,如果需要进行参数的指定,可以通过prism:InvokeCommandAction元素的TriggerParameterPath属性来将EventArgs中的某个属性成员作为参数传入,或者使用CommandParameter属性,将其他对象作为参数传递给命令执行函数。如果同时使用TriggerParameterPathCommandParameter属性,则优先传递CommandParameter属性设置的参数。

例如:<prism:InvokeCommandAction Command="{Binding BtnCommand}" TriggerParameterPath="Handled"/>就是将EventArgs对象中的Handled属性作为参数传递给命令执行函数。

二、事件触发方法

在Behaviores包的帮助下,也可以直接让事件来触发指定的方法。

定义执行方法

public class MainWindowViewModel
{public void TextBox_KeyDown(object sender, KeyEventArgs e){if (e.Key == Key.Enter){//输入键}}
}

注意,事件处理方法会接收到几个默认的参数,如果不知道是什么参数对象可以通过在控件中自动生成事件方法来参考一下。在本例中可以通过<TextBox …… KeyDown="TextBox_KeyDown">,自动生成TextBox_KeyDown方法来查看接收什么参数。

XAML中使用

注意,这里使用的是b:CallMethodAction,是Behaviors包中提供的对象。

TargetObject:指定事件处理方法所在的对象。

MethodName:事件处理方法的名称。

<Window ......xmlns:b="http://schemas.microsoft.com/xaml/behaviors"><Grid><TextBox Height="30" Width="100"><b:Interaction.Triggers><b:EventTrigger EventName="KeyDown"><b:CallMethodAction TargetObject="{Binding}" MethodName="TextBox_KeyDown"/></b:EventTrigger></b:Interaction.Triggers></TextBox></Grid>
</Window>

复合命令

复合命令是指一个命令可以同时完成多个命令,Prism提供了CompositeCommand类型专门用于实现复合命令。

注册命令

RegisterCommand(ICommand command)CompositeCommand的实例方法,用于注册命令,也就是将指定命令添加到当前复合命令对象中。

UnregisterCommand(ICommand command)CompositeCommand的实例方法,用于取消注册指定命令,即从当前复合命令对象中移除指定命令。


同一个模块(例如要复合的命令全部都在MainWindowViewModel中)的复合命令是很容易实现的,即使不使用CompositeCommand也可以实现。然而在实际开发中,要用到复合命令的情景往往是需要将多个不同模块中的命令集中起来一次性全部调用。这个过程不仅需要使用到CompositeCommand类,还需要借助IOC容器的依赖注入来实现。

①、创建复合命令类型

创建复合命令接口与实现子类,内含CompositeCommand对象属性。(这里是在程序集中创建Base文件夹,在Base中创建对应的IUnionCommand和UnionCommands)

  • 接口

    public interface IUnionCommand
    {CompositeCommand Commands { get; }
    }
    
  • 实现子类

    需要注意,这里CompositeCommand的创建必须放在字段上进行,如果放在属性中,每次get再创建,有可能每次从IOC中获取的都是一个新的实例。

    public class UnionCommands : IUnionCommands
    {private CompositeCommand _doCommand = new CompositeCommand();public CompositeCommand DoCommands{get => _doCommand;}
    }
    

②、注册复合命令类型

在App后台代码的RegisterTypes函数中,注册类型,然后通过IOC依赖注入从而在整个项目中使用同一个CompositeCommand对象。

注意,必须注册为单例的。

public partial class App : PrismApplication
{protected override Window CreateShell(){return Container.Resolve<Views.MainWindow>();}protected override void RegisterTypes(IContainerRegistry containerRegistry){//必须设置为单例,保证全局中都是同一个对象containerRegistry.RegisterSingleton<Base.IUnionCommand, Base.UnionCommand>();}
}

③、ViewModel层

这里分别在ViewModels文件夹中创建了MainWindowViewModel、ViewAViewModel和ViewBViewModel

  • MainWindowViewModel

    public class MainWindowViewModel:BindableBase
    {[Dependency]public IRegionManager regionManager { get; set; }[Dependency]public IUnionCommand UnionCommand { get; set; }public ICommand ShowViewACommand { get => new DelegateCommand(() =>{regionManager.RequestNavigate("ViewA", "ViewA");});}public ICommand ShowViewBCommand {get => new DelegateCommand(() =>{regionManager.RequestNavigate("ViewB", "ViewB");});}
    }
    
  • ViewAViewModel

    public class ViewAViewModel:BindableBase
    {private int _valueA=0;public int ValueA{get { return _valueA; }set { SetProperty(ref _valueA, value); }}public ICommand ValueACommand { get; set; }public ViewAViewModel(IUnionCommand unionCommand){ValueACommand = new DelegateCommand(() =>{ValueA = 100;});//注册命令unionCommand.Commands.RegisterCommand(ValueACommand);}
    }
    
  • ViewBViewModel

    public class ViewBViewModel:BindableBase
    {private int _valueB = 0;public int ValueB{get { return _valueB; }set { SetProperty(ref _valueB, value); }}public ICommand ValueBCommand{ get; set; }public ViewBViewModel(IUnionCommand unionCommand){ValueBCommand = new DelegateCommand(() =>{ValueB = 200;});//注册命令unionCommand.Commands.RegisterCommand(ValueBCommand);}
    }
    

④、View层

这里在Views文件夹中放置了MainWindow,新建了ViewA和ViewB用户控件。

  • MainWindow

    <Window ......xmlns:prism="http://prismlibrary.com/"><Grid><Grid.RowDefinitions><RowDefinition Height="150"/><RowDefinition/></Grid.RowDefinitions><UniformGrid Rows="2"><Button Content="UnionCommand" Command="{Binding UnionCommand.Commands}"/><UniformGrid Columns="2"><Button Content="ViewA" Command="{Binding ShowViewACommand}"/><Button Content="ViewB" Command="{Binding ShowViewBCommand}"/></UniformGrid></UniformGrid><UniformGrid Grid.Row="1" Rows="1"><ContentControl prism:RegionManager.RegionName="ViewA"/><ContentControl prism:RegionManager.RegionName="ViewB"/></UniformGrid></Grid>
    </Window>
    
  • ViewA

    <UserControl ......><Grid><StackPanel VerticalAlignment="Center"><TextBox Text="{Binding ValueA}"/><Button Content="ShouViewA" Command="{Binding ValueACommand}"/></StackPanel></Grid>
    </UserControl>
    
  • ViewB

    <UserControl ......><Grid><StackPanel VerticalAlignment="Center"><TextBox Text="{Binding ValueB}"/><Button Content="ShouViewB" Command="{Binding ValueBCommand}"/></StackPanel></Grid>
    </UserControl>
    

⑤、注册Region视图对象

public partial class App : PrismApplication
{......protected override void RegisterTypes(IContainerRegistry containerRegistry){containerRegistry.RegisterForNavigation<Views.ViewA>();containerRegistry.RegisterForNavigation<Views.ViewB>();//必须设置为单例,保证全局中都是同一个对象containerRegistry.RegisterSingleton<Base.IUnionCommand, Base.UnionCommand>();}
}

这篇关于WPF入门到跪下 第十一章 Prism(二)行为处理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/634447

相关文章

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

数论入门整理(updating)

一、gcd lcm 基础中的基础,一般用来处理计算第一步什么的,分数化简之类。 LL gcd(LL a, LL b) { return b ? gcd(b, a % b) : a; } <pre name="code" class="cpp">LL lcm(LL a, LL b){LL c = gcd(a, b);return a / c * b;} 例题:

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

Java 创建图形用户界面(GUI)入门指南(Swing库 JFrame 类)概述

概述 基本概念 Java Swing 的架构 Java Swing 是一个为 Java 设计的 GUI 工具包,是 JAVA 基础类的一部分,基于 Java AWT 构建,提供了一系列轻量级、可定制的图形用户界面(GUI)组件。 与 AWT 相比,Swing 提供了许多比 AWT 更好的屏幕显示元素,更加灵活和可定制,具有更好的跨平台性能。 组件和容器 Java Swing 提供了许多

【IPV6从入门到起飞】5-1 IPV6+Home Assistant(搭建基本环境)

【IPV6从入门到起飞】5-1 IPV6+Home Assistant #搭建基本环境 1 背景2 docker下载 hass3 创建容器4 浏览器访问 hass5 手机APP远程访问hass6 更多玩法 1 背景 既然电脑可以IPV6入站,手机流量可以访问IPV6网络的服务,为什么不在电脑搭建Home Assistant(hass),来控制你的设备呢?@智能家居 @万物互联

poj 2104 and hdu 2665 划分树模板入门题

题意: 给一个数组n(1e5)个数,给一个范围(fr, to, k),求这个范围中第k大的数。 解析: 划分树入门。 bing神的模板。 坑爹的地方是把-l 看成了-1........ 一直re。 代码: poj 2104: #include <iostream>#include <cstdio>#include <cstdlib>#include <al

MySQL-CRUD入门1

文章目录 认识配置文件client节点mysql节点mysqld节点 数据的添加(Create)添加一行数据添加多行数据两种添加数据的效率对比 数据的查询(Retrieve)全列查询指定列查询查询中带有表达式关于字面量关于as重命名 临时表引入distinct去重order by 排序关于NULL 认识配置文件 在我们的MySQL服务安装好了之后, 会有一个配置文件, 也就

Thymeleaf:生成静态文件及异常处理java.lang.NoClassDefFoundError: ognl/PropertyAccessor

我们需要引入包: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>sp

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显