自从我上次写到关于标记扩展的时候已经有一段时间了...... Visual Studio 11 Developer Preview的发布给WPF带来了一些新功能,让我有理由再次使用它们。我要在这里讨论的功能可能不是最令人印象深刻的,但它填补了以前版本的空白:支持事件标记扩展。

到目前为止,可以在XAML中使用标记扩展来为属性赋值,但我们无法做到这一点来订阅事件。在WPF 4.5中,现在有可能。所以这里是我们可以用它做的一个小例子...

当使用MVVM模式时,我们通常通过绑定机制将ViewModel的命令与视图的控件关联起来。这种方法通常运行良好,但它有一些缺点:

  • 它在ViewModel中引入了很多样板代码
  • 并不是所有的控件都有一个Command属性(实际上大多数不是),当这个属性存在时,它只对应于控件的一个事件(例如点击一个按钮)。没有真正简单的方法将其他事件“绑定”到ViewModel的命令

能够直接将事件绑定到ViewModel方法会很好,如下所示:

1
2
<Button Content="Click me"
        Click="{my:EventBinding OnClick}" />

使用OnClickViewModel中定义的方法:

1
2
3
4
public void OnClick(object sender, EventArgs e)
{
    MessageBox.Show("Hello world!");
}

那么,现在可以!这是一个概念证明...下面EventBindingExtension显示的类首先获取DataContext控件,然后在该对象上查找指定的方法DataContext,并最终返回此方法的委托:

1
2
3
4
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Markup;
 
 
    public class EventBindingExtension : MarkupExtension
    {
        public EventBindingExtension() { }
 
        public EventBindingExtension(string eventHandlerName)
        {
            this.EventHandlerName = eventHandlerName;
        }
 
        [ConstructorArgument("eventHandlerName")]
        public string EventHandlerName { get; set; }
 
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (string.IsNullOrEmpty(EventHandlerName))
                throw new ArgumentException("The EventHandlerName property is not set", "EventHandlerName");
 
            var target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
 
            EventInfo eventInfo = target.TargetProperty as EventInfo;
            if (eventInfo == null)
                throw new InvalidOperationException("The target property must be an event");
             
            object dataContext = GetDataContext(target.TargetObject);
            if (dataContext == null)
                throw new InvalidOperationException("No DataContext found");
 
            var handler = GetHandler(dataContext, eventInfo, EventHandlerName);
            if (handler == null)
                throw new ArgumentException("No valid event handler was found", "EventHandlerName");
 
            return handler;
        }
 
        #region Helper methods
 
        static object GetHandler(object dataContext, EventInfo eventInfo, string eventHandlerName)
        {
            Type dcType = dataContext.GetType();
 
            var method = dcType.GetMethod(
                eventHandlerName,
                GetParameterTypes(eventInfo));
            if (method != null)
            {
                if (method.IsStatic)
                    return Delegate.CreateDelegate(eventInfo.EventHandlerType, method);
                else
                    return Delegate.CreateDelegate(eventInfo.EventHandlerType, dataContext, method);
            }
 
            return null;
        }
 
        static Type[] GetParameterTypes(EventInfo eventInfo)
        {
            var invokeMethod = eventInfo.EventHandlerType.GetMethod("Invoke");
            return invokeMethod.GetParameters().Select(p => p.ParameterType).ToArray();
        }
 
        static object GetDataContext(object target)
        {
            var depObj = target as DependencyObject;
            if (depObj == null)
                return null;
 
            return depObj.GetValue(FrameworkElement.DataContextProperty)
                ?? depObj.GetValue(FrameworkContentElement.DataContextProperty);
        }
 
        #endregion
    }

这个类可以像上面例子中所示的那样使用。

现在,这个标记扩展有一个令人讨厌的限制:DataContext必须在调用之前设置ProvideValue,否则将无法找到事件处理程序方法。一种解决方案可能是订阅DataContextChanged事件以在DataContext设置后查找方法,但同时我们仍然需要返回一些内容......并且我们不能返回null,因为它会导致异常(因为您无法订阅具有空处理程序的事件)。所以我们需要返回一个从事件签名中动态生成的假处理程序。它让事情变得更加困难......但它仍然是可行的。

这是实现这种改进的第二个版本:

1
2
3
4
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Windows;
using System.Windows.Markup;
 
    public class EventBindingExtension : MarkupExtension
    {
        private EventInfo _eventInfo;
 
        public EventBindingExtension() { }
 
        public EventBindingExtension(string eventHandlerName)
        {
            this.EventHandlerName = eventHandlerName;
        }
 
        [ConstructorArgument("eventHandlerName")]
        public string EventHandlerName { get; set; }
 
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (string.IsNullOrEmpty(EventHandlerName))
                throw new ArgumentException("The EventHandlerName property is not set", "EventHandlerName");
 
            var target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
 
            var targetObj = target.TargetObject as DependencyObject;
            if (targetObj == null)
                throw new InvalidOperationException("The target object must be a DependencyObject");
 
            _eventInfo = target.TargetProperty as EventInfo;
            if (_eventInfo == null)
                throw new InvalidOperationException("The target property must be an event");
 
            object dataContext = GetDataContext(targetObj);
            if (dataContext == null)
            {
                SubscribeToDataContextChanged(targetObj);
                return GetDummyHandler(_eventInfo.EventHandlerType);
            }
 
            var handler = GetHandler(dataContext, _eventInfo, EventHandlerName);
            if (handler == null)
            {
                Trace.TraceError(
                    "EventBinding: no suitable method named '{0}' found in type '{1}' to handle event '{2'}",
                    EventHandlerName,
                    dataContext.GetType(),
                    _eventInfo);
                return GetDummyHandler(_eventInfo.EventHandlerType);
            }
 
            return handler;
             
        }
 
        #region Helper methods
 
        static Delegate GetHandler(object dataContext, EventInfo eventInfo, string eventHandlerName)
        {
            Type dcType = dataContext.GetType();
 
            var method = dcType.GetMethod(
                eventHandlerName,
                GetParameterTypes(eventInfo.EventHandlerType));
            if (method != null)
            {
                if (method.IsStatic)
                    return Delegate.CreateDelegate(eventInfo.EventHandlerType, method);
                else
                    return Delegate.CreateDelegate(eventInfo.EventHandlerType, dataContext, method);
            }
 
            return null;
        }
 
        static Type[] GetParameterTypes(Type delegateType)
        {
            var invokeMethod = delegateType.GetMethod("Invoke");
            return invokeMethod.GetParameters().Select(p => p.ParameterType).ToArray();
        }
 
        static object GetDataContext(DependencyObject target)
        {
            return target.GetValue(FrameworkElement.DataContextProperty)
                ?? target.GetValue(FrameworkContentElement.DataContextProperty);
        }
 
        static readonly Dictionary<Type, Delegate> _dummyHandlers = new Dictionary<Type, Delegate>();
 
        static Delegate GetDummyHandler(Type eventHandlerType)
        {
            Delegate handler;
            if (!_dummyHandlers.TryGetValue(eventHandlerType, out handler))
            {
                handler = CreateDummyHandler(eventHandlerType);
                _dummyHandlers[eventHandlerType] = handler;
            }
            return handler;
        }
 
        static Delegate CreateDummyHandler(Type eventHandlerType)
        {
            var parameterTypes = GetParameterTypes(eventHandlerType);
            var returnType = eventHandlerType.GetMethod("Invoke").ReturnType;
            var dm = new DynamicMethod("DummyHandler", returnType, parameterTypes);
            var il = dm.GetILGenerator();
            if (returnType != typeof(void))
            {
                if (returnType.IsValueType)
                {
                    var local = il.DeclareLocal(returnType);
                    il.Emit(OpCodes.Ldloca_S, local);
                    il.Emit(OpCodes.Initobj, returnType);
                    il.Emit(OpCodes.Ldloc_0);
                }
                else
                {
                    il.Emit(OpCodes.Ldnull);
                }
            }
            il.Emit(OpCodes.Ret);
            return dm.CreateDelegate(eventHandlerType);
        }
 
        private void SubscribeToDataContextChanged(DependencyObject targetObj)
        {
            DependencyPropertyDescriptor
                .FromProperty(FrameworkElement.DataContextProperty, targetObj.GetType())
                .AddValueChanged(targetObj, TargetObject_DataContextChanged);
        }
 
        private void UnsubscribeFromDataContextChanged(DependencyObject targetObj)
        {
            DependencyPropertyDescriptor
                .FromProperty(FrameworkElement.DataContextProperty, targetObj.GetType())
                .RemoveValueChanged(targetObj, TargetObject_DataContextChanged);
        }
 
        private void TargetObject_DataContextChanged(object sender, EventArgs e)
        {
            DependencyObject targetObj = sender as DependencyObject;
            if (targetObj == null)
                return;
 
            object dataContext = GetDataContext(targetObj);
            if (dataContext == null)
                return;
 
            var handler = GetHandler(dataContext, _eventInfo, EventHandlerName);
            if (handler != null)
            {
                _eventInfo.AddEventHandler(targetObj, handler);
            }
            UnsubscribeFromDataContextChanged(targetObj);
        }
 
        #endregion
    }

所以这是我们可以做的事情,这要感谢这个新的WPF功能。我们也可以设想一个行为系统,类似于我们可以对附加属性进行的操作,例如在事件发生时执行标准操作。有很多可能的应用程序,我把它留给你找到它们.

from:https://www.thomaslevesque.com/2011/09/23/wpf-4-5-subscribing-to-an-event-using-a-markup-extension/

[No0000130]WPF 4.5使用标记扩展订阅事件的更多相关文章

  1. WPF:实现自定义标记扩展

    标记扩展使用{标记扩展类 参数}语法,如: <TextBlock Text={x:Null}/> 为什么x:Null就可以返回一个null值呢? 其实在System.Windows.Mar ...

  2. WPF,Silverlight与XAML读书笔记(3) - 标记扩展

    hystar的.Net世界 博客园 首页 新闻 新随笔 联系 管理 订阅 随笔- 103  文章- 0  评论- 107  WPF,Silverlight与XAML读书笔记(3) - 标记扩展   说 ...

  3. [No000012C]WPF(4/7)类型转换器和标记扩展[译]

    介绍 之前讨论了WPF的基础架构,然后逐步开始学习布局面板,转换,介绍了不同的控件,容器,UI转换等.在这篇文章中,我将讨论每个创建XAML应用前的开发人员应该了解的关于XAML最重要的东西. 标记扩 ...

  4. 2.6 wpf标记扩展

    1.什么是标记扩展?为什么要有标记扩展? 标记扩展是扩展xmal的表达能力 为了克服现存的类型转换机制存在的 常用的标记扩展有如下: x:Array 代表一个.net数组,它的子元素都是数组元素.它必 ...

  5. wpf中xaml的类型转换器与标记扩展

    原文:wpf中xaml的类型转换器与标记扩展 这篇来讲wpf控件属性的类型转换器 类型转换器 类型转换器在asp.net控件中已经有使用过了,由于wpf的界面是可以由xaml组成的,所以标签的便利也需 ...

  6. 标记扩展和 WPF XAML

      本主题介绍 XAML 的标记扩展概念,包括其语法规则.用途以及底层的类对象模型. 标记扩展是 XAML 语言以及 XAML 服务的 .NET 实现的常规功能. 本主题专门详细论述了用于 WPF X ...

  7. XAML语法及标记扩展、附加属性、特定的字符和空白

    1.对象元素语法 使用开闭标签将对象定义一个XML元素,这种语法与其他标记语言如HTML的元素语法非常相似,在以左右尖括号保卫要设置的类或结构的类型名称.对象元素可以声明0个或多个属性,以一个或多个空 ...

  8. XAML 属性元素,标记扩展和注释

    这节来讲一下XAML中的属性元素,标记扩展,和注释. 属性元素 一般的,我们想要对一个标签的属性赋值,可以直接在标签内部键入属性名给其赋值,如我们给button的Content属性赋值: <Bu ...

  9. XAML(4) - 标记扩展

    在为元素设置值时, 可以直接设置值, 但有时标记扩展非常有帮助.标记扩展包含花括号,其后是定义了标记扩展类型的字符串标志. 下面是一个Static Resource标记扩展: <Button N ...

随机推荐

  1. windows下JDK环境配置

    原文地址:http://blog.sina.com.cn/s/blog_618592ea0100oeif.html 一.JDK1.6下载 目前JDK最新版本是JDK1.6,到http://java.s ...

  2. IIS 之 应用程序池

    IIS(Internet Information Services),由于我使用的是Windows10系统,所以本文以其内置 10.0.14393.0 版本说明. 应用程序池 → 右键(待设置应用程序 ...

  3. 12C -- 配置Application Continuity

     

  4. fft ocean注解

    针对这两篇教程: http://www.keithlantz.net/2011/10/ocean-simulation-part-one-using-the-discrete-fourier-tran ...

  5. Atitit xml框架类库选型 attilax总结

    Atitit xml框架类库选型 attilax总结 1. 1. XML类库可以分成2大类.标准的.这些类库通常接口和实现都是分开的1 2. Jdom 和dom4j1 2.1. 5.1. jdom1 ...

  6. [APM] 2个实例+5个维度解读APM技术

    为了加深EGO会员之间的相互了解,同时也为大家提供更多线上相互学习交流的机会,EGO正式启动会员群线上分享活动.本文是根据陈靖华分享主题“APM的价值”的内容整理而成. 第二期分享嘉宾:陈靖华,EGO ...

  7. golang:slice切片

    一直对slice切片这个概念理解的不是太透彻,之前学习python的就没搞清楚,不过平时就用python写个工具啥的,也没把这个当回事去花时间解决. 最近使用go开发又遇到这个问题,于是打算彻底把这个 ...

  8. pom.xml文件详解

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...

  9. 【iCore1S 双核心板_FPGA】例程十三:FSMC总线通信实验——复用地址模式

    实验原理: STM32F103上自带FMC控制器,本实验将通过FMC总线的地址复用模式实现STM32与FPGA 之间通信,FPGA内部建立RAM块,FPGA桥接STM32和RAM块,本实验通过FSMC ...

  10. Java多线程系列——过期的suspend()挂起、resume()继续执行线程

    简述 这两个操作就好比播放器的暂停和恢复. 但这两个 API 是过期的,也就是不建议使用的. 不推荐使用 suspend() 去挂起线程的原因,是因为 suspend() 在导致线程暂停的同时,并不会 ...