• [No0000130]WPF 4.5使用标记扩展订阅事件


    自从我上次写到关于标记扩展的时候已经有一段时间了...... 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/

  • 相关阅读:
    说说css中pt、px、em、rem都扮演了什么角色
    前端设计师如何提高UI界面中的阅读性
    前端设计师如何写一个交互好转化率高的表单
    教程:安装禅道zentao项目管理软件github上的开发版
    开源软件如何赚钱?
    警示!一幅漫画揭示了项目研发过程中存在的问题
    前端设计师常用的一些基础工具素材合集
    前端设计师必收的5款免费响应式布局测试工具
    推荐的五款市面上常用的免费CMS建站系统
    为什么在有的服务器上禅道、蝉知安装会报错? 之理解MySQL的SQL_MODE
  • 原文地址:https://www.cnblogs.com/Chary/p/No0000130.html
Copyright © 2020-2023  润新知