本文说明怎样把 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)

C# WPF – 利用“Attached Property” 把 RoutedEvent 接上 ICommand的更多相关文章

  1. WPF利用radiobutton制作菜单按钮

    原文:WPF利用radiobutton制作菜单按钮 版权声明:欢迎转载.转载请注明出处,谢谢 https://blog.csdn.net/wzcool273509239/article/details ...

  2. UWP开发入门(十一)——Attached Property的简单应用

    UWP中的Attached Property即附加属性,在实际开发中是很常见的,比如Grid.Row: <Grid Background="{ThemeResource Applica ...

  3. WPF利用动画实现圆形进度条

    原文:WPF利用动画实现圆形进度条 这是我的第一篇随笔,最近因为工作需要,开始学习WPF相关技术,自己想实现以下圆形进度条的效果,逛了园子发现基本都是很久以前的文章,实现方式一般都是GDI实现的,想到 ...

  4. WPF利用通过父控件属性来获得绑定数据源RelativeSource

    WPF利用通过父控件属性来获得绑定数据源RelativeSource   有时候我们不确定作为数据源的对象叫什么名字,但知道作为绑定源与UI布局有相对的关系,如下是一段XAML代码,说明多层布局控件中 ...

  5. WPF利用HelixToolKit后台导入3D模型

    原文:WPF利用HelixToolKit后台导入3D模型 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/m0_37591671/article/de ...

  6. c# wpf 利用截屏键实现截屏功能

    原文:c# wpf 利用截屏键实现截屏功能     最近做一个wpf程序需要截图功能,查找资料费了一些曲折,跟大家分享一下.     先是找到了这样一份代码:     static class Scr ...

  7. WPF利用VisualTreeHelper遍历寻找对象的子级对象或者父级对象

    原文:WPF利用VisualTreeHelper遍历寻找对象的子级对象或者父级对象 简介 本文将完整叙述我利用VisualTreeHelper实现题述功能的全部过程,想直接看函数实现的朋友可以跳到函数 ...

  8. 利用spring的MultipartFile实现文件上传【原】

    利用spring的MultipartFile实现文件上传 主要依赖jar包 spring-web-3.0.6.RELEASE.jar 用到 (org.springframework.web.multi ...

  9. win7下利用ftp实现华为路由器的上传和下载

    win7下利用ftp实现华为路由器的上传和下载 1.  Win7下ftp的安装和配置 (1)开始->控制面板->程序->程序和功能->打开或关闭Windows功能 (2)在Wi ...

随机推荐

  1. DataGridView 些许事件测试

    原始设计需求:当单元格内容是空白时,鼠标进入之后,显示一些数据 直观的第一感觉必然是用CellClick,细想,如果用户不用鼠标,直接按Tab键切换单元格呢?又或者,用户直接双击涅~ 主要测试的是:  ...

  2. 图像处理工具包ImagXpress教程:Accusoft不同组件间的图像数据传递

    图像处理工具包ImagXpress的开发厂商Accusoft Pegasus旗下有多种图像处理相关的控件,但是这些图像处理控件之间的如何加传递图像数据呢?在ImagXpress 11版本之前,是需要将 ...

  3. android 学习随笔二十二(小结)

    ADB进程 * adb指令 * adb install xxx.apk * adb uninstall 包名 * adb devices * adb start-server * adb kill-s ...

  4. linux,Mac下lu 一把

    习惯Terminal没有不知道ls命令的(等同于DOS的dir),经常只是需要查看目录的内容大小,但ls -h显示的只是目录的本身大小,而且很多项内容 ls 在这方面的两个诟病出现了: 小诟1. 显示 ...

  5. fprintf 读入%s,要注意

    eg  文件内容faninfd 14   "%s %d",会把整行内容放到%s 而%d是乱码 ps: 文件内容是 1,2,3    “%d,%d,%d” 文件内容是1:2:3   ...

  6. 【PHP设计模式 01_DuoTai.php】多态的说明

    <?php /** * [多态] * 定义一个抽象类:Tiger,有两个子类:XTiger 和 MTiger */ header("Content-type: text/html; c ...

  7. 如何为github上的项目添加gif效果图

    一.制作gif图片 如何制作可以参考: http://www.jianshu.com/p/27ec6375b8ab?utm_campaign=maleskine&utm_content=not ...

  8. 通达OA 免狗迁移到公网 的另类解决办法

    1,通达OA 发布到公网 ,要真正的 Anywhere2,正版通达OA,有加密狗在本地机器上 ,通达必须检测有狗才可以运行3,阿里云服务器  (你想往上插加密狗都没地方的说..汗)4,本地ISP 不提 ...

  9. UVa 1339,紫书P73,词频

    题目链接:https://uva.onlinejudge.org/external/13/1339.pdf 紫书P73 解题报告: #include <stdio.h> #include ...

  10. 本地计算机上的MSSQLSERVER服务启动后又停止了。一些服务自动停止,如果它们没有什么可做的

    本地计算机上的MSSQLSERVER服务启动后又停止了.一些服务自动停止,如果它们没有什么可做的 笔者ASP运行环境:操作系统为Windows XP SP2,IIS的版本为默认的5.1,数据库为SQL ...