[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/
轉載于:https://www.cnblogs.com/Chary/p/No0000130.html
總結
以上是生活随笔為你收集整理的[No0000130]WPF 4.5使用标记扩展订阅事件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 随州回中国人民解放军海军上海博物馆怎么方
- 下一篇: ResNet笔记