C# WPF – 利用“Attached Property” 把 RoutedEvent 接上 ICommand
本文说明怎样把 DoubleClick 连接至 ICommand。方法很多。推荐使用 Attach Property 方式,因为它能把任何 RoutedEvent 接上任何 ICommand。
之前写过一篇博文关于 MVVM 中双击事件触发 ICommand 的办法,我说要么你自己写 Attached Property,要么下载别人写好的,比如支持 Collections 的 CommandBehaviors。我认为这两个办法是比较好的。有网友说我没有解释清楚,因为我觉得 Attached Property 有点离题,跟 MVVM 关系不太大。反正有得用就行了。
下面以 ListView 为例。
1. InputBindings
先不说 Attached Property,看看有什么办法可以把双击绑定到 ICommand。最简单的办法是 InputBindings。
XAML:
<ListView.InputBindings><MouseBinding Gesture="LeftDoubleClick" Command=""/></ListView.InputBindings>
支持 KeyBinding (键盘),和 MouseBinding (鼠标)。能做到,如果只需要管键盘或鼠标,这是比较简单。
2. 隐形 Button (不建议)
我见过第二个办法,隐形 Button, (Visibility=”Collapsed”),ICommand 绑定进去,ListView MouseDoubleClick 在视图建立句柄,由它再触发 Button 的 Command.Execute(object)。
XAML:
<Button Name="button1" Visibility="Collapsed" Command=""/><ListView MouseDoubleClick="ListView_MouseDoubleClick"/>
Code:
privatevoid ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e) {button1.Command.Execute(null); }
这比较傻,不建议。
3. Attached Property
MSDN 有介绍怎样为控件添加新的属性,这里不详细说了。关键是静态方法 Set,和静态 DependencyProperty。(MSDN 说 GET SET 都要,但其实写 XAML 时只用到 SET,后续启动后,你需要拿回属性值才需要 GET)。
先看一下,Attached Property 是怎样写的,热热身:
CODE:
publicstaticclass MyProperty {publicstaticreadonly DependencyProperty ParameterProperty = DependencyProperty.RegisterAttached("Parameter",typeof(Object),typeof(MyProperty),new FrameworkPropertyMetadata(null));publicstatic Object GetParameter(UIElement obj) {return obj.GetValue(ParameterProperty);}publicstaticvoid SetParameter(UIElement obj, Object value) {obj.SetValue(ParameterProperty, value);} }
get、set 参数 UIElement 类型是为了确保所有控件能用它。这 Parameter 没有配置CallBack,这个MyProperty不对值变化做什么动作,也不设置默认值,所以 RegisterAttached 时候 FrameworkPropertyMetadata是 null。
命名规范必须跟从,MSDN 有说明。当你希望在 XAML 这属性叫做 Parameter 的时候(RegisterAttached 的第一个参数),它的get、set 方法必须命名为 GetParameter 和 SetParameter。编译后 XAML 可用。
XAML:
<Window x:Class="WpfApplication1.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:y="clr-namespace:WpfApplication1"Title="MainWindow" Height="350" Width="525"><Grid><ListView y:MyProperty.Parameter="ABC"/></Grid></Window>
新手记得加上正确的 XML namespace,xmlns:y="clr-namespace:WpfApplication1" 是因为我把MyProperty类放在这 WpfApplication1 项目的最外层。
知道了怎么写 Attached Property 之后,入正题,加入 ICommand。为灵活性,做法是让程序员配置要绑的 RoutedEvent ,和对应要触发的 ICommand 同时作为 DependencyProperty,让程序员自己配置哪个Event 接哪个 ICommand。(注:handler 那 Dictionary 的做法,和 Detach Attach 是参考某大神的)。为缩短代码,只写 ICommand 和 Event,没写 ICommand 的命令参数。
(以下代码网上其实很多,也有很多版本,大同小异)
CODE:
using System.Collections.Generic; using System.Windows; using System.Windows.Input;namespace WpfApplication1 {publicstaticclass CommandBehavior {// UI,Handler Listprivatestatic Dictionary<UIElement, RoutedEventHandler> handlers =new Dictionary<UIElement, RoutedEventHandler>();#region Command Propertypublicstaticreadonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command",typeof(ICommand),typeof(CommandBehavior),new FrameworkPropertyMetadata() {DefaultValue =null,PropertyChangedCallback =new PropertyChangedCallback(OnCommandPropertyChanged)});publicstatic ICommand GetCommand(UIElement obj) {return (ICommand)obj.GetValue(CommandProperty);}publicstaticvoid SetCommand(UIElement obj, ICommand value) {obj.SetValue(CommandProperty, value);}#endregion#region Event Propertypublicstaticreadonly DependencyProperty EventProperty = DependencyProperty.RegisterAttached("Event",typeof(RoutedEvent),typeof(CommandBehavior),new FrameworkPropertyMetadata() {DefaultValue =null,PropertyChangedCallback =new PropertyChangedCallback(OnEventPropertyChanged)});publicstatic RoutedEvent GetEvent(DependencyObject obj) {return (RoutedEvent)obj.GetValue(EventProperty);}publicstaticvoid SetEvent(DependencyObject obj, RoutedEvent value) {obj.SetValue(EventProperty, value);}#endregion#region CallBacksprivatestaticvoid OnCommandPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) {UIElement element = obj as UIElement;ICommand oldCommand = args.OldValue as ICommand;ICommand newCommand = args.NewValue as ICommand;RoutedEvent routedEvent = element.GetValue(EventProperty) as RoutedEvent;Detach(element, routedEvent, oldCommand);Attach(element, routedEvent, newCommand);}privatestaticvoid OnEventPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) {UIElement element = obj as UIElement;RoutedEvent oldEvent = args.OldValue as RoutedEvent;RoutedEvent newEvent = args.NewValue as RoutedEvent;ICommand command = element.GetValue(CommandProperty) as ICommand;Detach(element, oldEvent, command);Attach(element, newEvent, command);}#endregionprivatestaticvoid Attach(UIElement element, RoutedEvent Event, ICommand command) {if (Event !=null&& element !=null&& command !=null) {RoutedEventHandler InvokeCommandHandler =new RoutedEventHandler(delegate {command.Execute(null);});handlers.Add(element, InvokeCommandHandler);element.AddHandler(Event, InvokeCommandHandler);}}privatestaticvoid Detach(UIElement element, RoutedEvent Event, ICommand command) {if (Event !=null&& element !=null&& command !=null) {RoutedEventHandler handler = handlers[element];if (handler !=null) {element.RemoveHandler(Event, handler);handlers.Remove(element);}}}} }
跟之前那个 Parameter 例子很像,只是同一个静态类,做了两个属性,一个叫做 Event,一个叫做 Command。另外,多了一个 Dictionary,还有,这次 Event 和 Command 的变化,都注册了 PropertyChangedCallback 的句柄。最下面的 Attach Detach 的 private 帮助方法,只是重构时从PropertyChangedCallBack 的句柄抽出来而已。
控件、事件、命令,三者是一起的组合,某 UIElement 的某 RoutedEvent 触发到某 ICommand 的 Execute。但RoutedEvent 触发的是 RoutedEventHandler 句柄,不是 ICommand。所以这个静态类所做最重要的事,见 private static void Attach(),就是创建新的 RoutedEventHandler,让它执行委托运行 command 的 Execute,然后把准备好 RoutedEventHandler 之后粘上 UIElement,即 AddHandler(RoutedEvent,RoutedEventHandler)。把这搭配,UIElement 和已做好ICommand委托的 RoutedEventHandler,放在 Dictionary,是为了 Detach 时候找回。
要做 Detach 是因为,DependencyProperty 的值是能变化的(上例中是 Event和Command这两个,都能在运行时变),不一定是写死在 XAML,比如 {Binding Path=XXX} 这情况。万一 Command 变了,或者 RoutedEvent 变了,上述做好了的搭配就失效,是需要 RemoveHandler 然后重新组合。所以,PropertyChangedCallBack 所做的,都是先 Detach 旧值(args.OldValue),然后再 Attach 粘上新值(args.NewValue)。不管 Event 变还是 Command 变,都需要如此。
这静态类的解释到此为止。不复杂。用法如下:
XAML:
<Window x:Class="WpfApplication1.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:y="clr-namespace:WpfApplication1"Title="MainWindow" Height="350" Width="525"><Grid><ListView y:CommandBehavior.Command="{Binding Path=TestCommand}"y:CommandBehavior.Event="ListView.MouseDoubleClick"></ListView></Grid></Window>
因为一开始设置了Command 和 Event 的默认值为 null (RegisterAttached 时候的 FrameworkPropertyMetadata 内,DefaultValue),所以 XAML 运行写入值时,值变化触发 CallBack,完成了我们需要的连接。
最后,改一下 CommandBehavior,让它能接受参数,传过去 ICommand。因为 ICommand 的命令参数类型是 object,所以写的 CommandParameter 类型也是 object。
完整版本 CODE:
using System.Collections.Generic; using System.Windows; using System.Windows.Input;namespace WpfApplication1 {publicstaticclass CommandBehavior {// UI,Handler Listprivatestatic Dictionary<UIElement, RoutedEventHandler> handlers =new Dictionary<UIElement, RoutedEventHandler>();#region Command Propertypublicstaticreadonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command",typeof(ICommand),typeof(CommandBehavior),new FrameworkPropertyMetadata() {DefaultValue =null,PropertyChangedCallback =new PropertyChangedCallback(OnCommandPropertyChanged)});publicstatic ICommand GetCommand(UIElement obj) {return (ICommand)obj.GetValue(CommandProperty);}publicstaticvoid SetCommand(UIElement obj, ICommand value) {obj.SetValue(CommandProperty, value);}#endregion#region Event Propertypublicstaticreadonly DependencyProperty EventProperty = DependencyProperty.RegisterAttached("Event",typeof(RoutedEvent),typeof(CommandBehavior),new FrameworkPropertyMetadata() {DefaultValue =null,PropertyChangedCallback =new PropertyChangedCallback(OnEventPropertyChanged)});publicstatic RoutedEvent GetEvent(DependencyObject obj) {return (RoutedEvent)obj.GetValue(EventProperty);}publicstaticvoid SetEvent(DependencyObject obj, RoutedEvent value) {obj.SetValue(EventProperty, value);}#endregion#region CommandParameter Propertypublicstaticreadonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached("CommandParameter",typeof(object),typeof(CommandBehavior),new FrameworkPropertyMetadata(null));publicstaticobject GetCommandParameter(UIElement obj) {return obj.GetValue(CommandParameterProperty);}publicstaticvoid SetCommandParameter(UIElement obj, object value) {obj.SetValue(CommandParameterProperty, value);}#endregion#region CallBacksprivatestaticvoid OnCommandPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) {UIElement element = obj as UIElement;ICommand oldCommand = args.OldValue as ICommand;ICommand newCommand = args.NewValue as ICommand;RoutedEvent routedEvent = element.GetValue(EventProperty) as RoutedEvent;object commandParameter = element.GetValue(CommandParameterProperty);Detach(element, routedEvent, oldCommand);Attach(element, routedEvent, newCommand, commandParameter);}privatestaticvoid OnEventPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) {UIElement element = obj as UIElement;RoutedEvent oldEvent = args.OldValue as RoutedEvent;RoutedEvent newEvent = args.NewValue as RoutedEvent;ICommand command = element.GetValue(CommandProperty) as ICommand;object commandParameter = element.GetValue(CommandParameterProperty);Detach(element, oldEvent, command);Attach(element, newEvent, command, commandParameter);}#endregionprivatestaticvoid Attach(UIElement element, RoutedEvent Event, ICommand command, object commandParameter) {if (Event !=null&& element !=null&& command !=null) {RoutedEventHandler InvokeCommandHandler =new RoutedEventHandler(delegate {command.Execute(commandParameter);});handlers.Add(element, InvokeCommandHandler);element.AddHandler(Event, InvokeCommandHandler);}}privatestaticvoid Detach(UIElement element, RoutedEvent Event, ICommand command) {if (Event !=null&& element !=null&& command !=null) {RoutedEventHandler handler = handlers[element];if (handler !=null) {element.RemoveHandler(Event, handler);handlers.Remove(element);}}}} }
完整版本的 CommandBehavior 在 XAML 用法:
XAML:
<Window x:Class="WpfApplication1.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:y="clr-namespace:WpfApplication1"Title="MainWindow" Height="350" Width="525"><Grid><ListView y:CommandBehavior.Command="{Binding Path=TestCommand}"y:CommandBehavior.Event="ListView.MouseDoubleClick"y:CommandBehavior.CommandParameter="TestParameter"/></Grid></Window>
Attach Property 方法介绍到此为止。点击这里下载最终版本的代码。
这类简单,用来解释工作原理比较合适。但我之前博文没用这个类,因为以上代码,有一个明显缺陷。源于 Dictionary<UIElement, RoutedEventHandler> 这样的简单搭配,UIElement 作为 Key。而且 CommandBehavior 这静态类,没有集合暴露给 XAML。这意味着,一个控件,只能设置一次。比如,当一个控件你有两个 RoutedEvent 希望绑定到两个ICommand,这代码不支持。
为了解决这问题,网上已经有很多人写好了一个叫做 CommandBehaviorCollection 的类(懒到搜索都不想搜的,点击这里),很多不同的版本,功能其实都一样,让你在 XAML 内一个控件能同时配置多个 Event 和 Command 的组合。这个类就是我在之前博文上用到的那个。我不打算解释里面内容,其工作基本原理,与上述代码一摸一样,只是它暴露了集合让你在 XAML 内填多个组合。
我在这群里,欢迎加入交流:
开发板玩家群 578649319
硬件创客 (10105555)
转载于:https://www.cnblogs.com/leptonation/p/3279190.html
C# WPF – 利用“Attached Property” 把 RoutedEvent 接上 ICommand相关推荐
- WPF中的Attached Property
Attached Property是Dependency Property的一种特殊形式,它是用DependencyProperty.RegisterAttached方法注册的,可以被有效地添加到任何 ...
- 通过Attached Property给控件绑定Command(二)
上一篇我们提到希望建立一个通用的Command绑定,本篇就这个问题来和各位进行讨论.也希望各位能指出不足,提出改进的建议. 我希望最终实现的效果如下图所示,可以给任何一个Control绑定Comman ...
- 照书学WPF之 Dependency Property 1
简介 Dependency Property: 它们依赖一些其他的property和外在的影响,引起自身的变化. 如: WPF框架的编程经常和界面打交道,经常遇到的一个情况是某个属性的值的变化会影响到 ...
- WPF利用动画实现圆形进度条
WPF利用动画实现圆形进度条 原文:WPF利用动画实现圆形进度条 这是我的第一篇随笔,最近因为工作需要,开始学习WPF相关技术,自己想实现以下圆形进度条的效果,逛了园子发现基本都是很久以前的文章,实现 ...
- UWP开发入门(十一)——Attached Property的简单应用
UWP中的Attached Property即附加属性,在实际开发中是很常见的,比如Grid.Row: <Grid Background="{ThemeResource Applica ...
- WPF 利用键盘钩子来捕获键盘,做一些不为人知的事情...完整实例
原文:WPF 利用键盘钩子来捕获键盘,做一些不为人知的事情...完整实例 键盘钩子是一种可以监控键盘操作的指令. 看到这句话是不是觉得其实键盘钩子可以做很多事情. 场景 当你的程序需要一个全局的快捷键 ...
- php 输出图片给js,如何在php中利用croppic.js对图片进行剪切并上传
如何在php中利用croppic.js对图片进行剪切并上传 发布时间:2021-01-30 16:41:02 来源:亿速云 阅读:92 作者:Leah 这篇文章将为大家详细讲解有关如何在php中利用c ...
- DL之DCGNN:基于TF利用DCGAN实现在MNIST数据集上训练生成新样本
DL之DCGNN:基于TF利用DCGAN实现在MNIST数据集上训练生成新样本 目录 输出结果 设计思路 实现部分代码 说明:所有图片文件丢失 输出结果 更新-- 设计思路 更新-- 实现部分代码 更 ...
- c# 联合halcon 基于相关性 模板匹配_宣城seo公司_有效利用及时相关性解决关键词排名上不到首页的难题...
发布时间:2020-11-04 09:11:33 当你写一条热门新闻时,百度总是能很快地收录和显示你.这就是即时新闻的魅力所在,考虑到全站的权重不算太多. 它完全基于即时新闻和页面相关性,因此,如果你 ...
- 利用FormData对象实现AJAX文件上传功能及后端实现
包括HTML基础设置.CSS界面优化.JS利用FormData对象和AJAX进行上传.后端接收文件并存储到指定路径以及删除文件操作. FE HTML 基础的设置: <form enctype=& ...
最新文章
- undefined reference to symbol ‘_ZN2cv7imwriteERKNS_6StringERKNS_11_InputArrayERKSt6vectorIiSaIiEE‘
- 《Python编程快速上手》7.18 实践项目
- Hadoop学习之HDFS架构(一)
- 详析 Kubernetes 在边缘计算领域的发展
- 仲裁服务器装什么系统,Windows 2008故障转移集群之仲裁配置
- docker删除为none的镜像
- 【MS SQL】通过执行计划来分析SQL性能
- 学计算机程序ui设计,学习UI设计需要什么样的电脑配置
- 服务器开启虚拟化有什么好处
- 菜鸟谈VBA最最基础入门
- 【UIAutomator2】实现微信自动加好友功能
- Rockchip Linux PCIe 开发指南
- 配置oem 13c以监控管理数据库
- 基于JSP的“爱心宠物诊所”系统课程设计
- 汇文从marc_idx根据书名检索图书
- anaconda的令牌是啥_Anaconda是什么?香吗?
- Android Studio打包APK安装失败:应用是非正式版本,当前设备不支持安装
- 宇宙的电网模型之太阳实验起源谭
- Field xxxMapper in xxxServiceImpl required a bean of type XxxMapper解决方法
- saiku 3.7.4 构建过程