如何通过.NET / C#中的reflection来提高事件?
我有一个第三方编辑器,基本上包含一个文本框和一个button(DevExpress ButtonEdit控件)。 我想做一个特定的按键( Alt + Down )模拟点击button。 为了避免一遍又一遍地写这个,我想做一个通用的KeyUp事件处理程序,将引发ButtonClick事件。 不幸的是,控件中似乎没有引发ButtonClick事件的方法,所以…
如何通过reflection从外部函数中提取事件?
这是一个使用generics的示例(省略了错误检查):
using System; using System.Reflection; static class Program { private class Sub { public event EventHandler<EventArgs> SomethingHappening; } internal static void Raise<TEventArgs>(this object source, string eventName, TEventArgs eventArgs) where TEventArgs : EventArgs { var eventDelegate = (MulticastDelegate)source.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(source); if (eventDelegate != null) { foreach (var handler in eventDelegate.GetInvocationList()) { handler.Method.Invoke(handler.Target, new object[] { source, eventArgs }); } } } public static void Main() { var p = new Sub(); p.Raise("SomethingHappening", EventArgs.Empty); p.SomethingHappening += (o, e) => Console.WriteLine("Foo!"); p.Raise("SomethingHappening", EventArgs.Empty); p.SomethingHappening += (o, e) => Console.WriteLine("Bar!"); p.Raise("SomethingHappening", EventArgs.Empty); Console.ReadLine(); } }
一般来说,你不能。 把事件想象成基本上是AddHandler
/ RemoveHandler
方法的对(因为基本上它们是什么)。 他们如何实施是由class级决定的。 大多数WinForms控件都使用EventHandlerList
作为它们的实现,但是如果开始读取私有字段和密钥,代码会非常脆弱。
ButtonEdit
控件是否暴露了可以调用的OnClick
方法?
脚注:事实上,事件可以 “引发”成员,因此EventInfo.GetRaiseMethod
。 但是,这从来没有被C#填充,我也不相信它是在一般的框架。
你通常无法举办另一个class级活动。 事件实际上被存储为一个私人委托字段,另外还有两个访问器(add_event和remove_event)。
通过reflection来完成,你只需要find私人委托字段,获取它,然后调用它。
我写了一个扩展类,实现INotifyPropertyChanged注入RaisePropertyChange <T>方法,所以我可以这样使用它:
this.RaisePropertyChanged(() => MyProperty);
没有在任何基类中实现该方法。 对于我的使用来说,这是缓慢的,但也许源代码可以帮助别人。
所以这里是:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using System.Globalization; namespace Infrastructure { /// <summary> /// Adds a RaisePropertyChanged method to objects implementing INotifyPropertyChanged. /// </summary> public static class NotifyPropertyChangeExtension { #region private fields private static readonly Dictionary<string, PropertyChangedEventArgs> eventArgCache = new Dictionary<string, PropertyChangedEventArgs>(); private static readonly object syncLock = new object(); #endregion #region the Extension's /// <summary> /// Verifies the name of the property for the specified instance. /// </summary> /// <param name="bindableObject">The bindable object.</param> /// <param name="propertyName">Name of the property.</param> [Conditional("DEBUG")] public static void VerifyPropertyName(this INotifyPropertyChanged bindableObject, string propertyName) { bool propertyExists = TypeDescriptor.GetProperties(bindableObject).Find(propertyName, false) != null; if (!propertyExists) throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "{0} is not a public property of {1}", propertyName, bindableObject.GetType().FullName)); } /// <summary> /// Gets the property name from expression. /// </summary> /// <param name="notifyObject">The notify object.</param> /// <param name="propertyExpression">The property expression.</param> /// <returns>a string containing the name of the property.</returns> public static string GetPropertyNameFromExpression<T>(this INotifyPropertyChanged notifyObject, Expression<Func<T>> propertyExpression) { return GetPropertyNameFromExpression(propertyExpression); } /// <summary> /// Raises a property changed event. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="bindableObject">The bindable object.</param> /// <param name="propertyExpression">The property expression.</param> public static void RaisePropertyChanged<T>(this INotifyPropertyChanged bindableObject, Expression<Func<T>> propertyExpression) { RaisePropertyChanged(bindableObject, GetPropertyNameFromExpression(propertyExpression)); } #endregion /// <summary> /// Raises the property changed on the specified bindable Object. /// </summary> /// <param name="bindableObject">The bindable object.</param> /// <param name="propertyName">Name of the property.</param> private static void RaisePropertyChanged(INotifyPropertyChanged bindableObject, string propertyName) { bindableObject.VerifyPropertyName(propertyName); RaiseInternalPropertyChangedEvent(bindableObject, GetPropertyChangedEventArgs(propertyName)); } /// <summary> /// Raises the internal property changed event. /// </summary> /// <param name="bindableObject">The bindable object.</param> /// <param name="eventArgs">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param> private static void RaiseInternalPropertyChangedEvent(INotifyPropertyChanged bindableObject, PropertyChangedEventArgs eventArgs) { // get the internal eventDelegate var bindableObjectType = bindableObject.GetType(); // search the base type, which contains the PropertyChanged event field. FieldInfo propChangedFieldInfo = null; while (bindableObjectType != null) { propChangedFieldInfo = bindableObjectType.GetField("PropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic); if (propChangedFieldInfo != null) break; bindableObjectType = bindableObjectType.BaseType; } if (propChangedFieldInfo == null) return; // get prop changed event field value var fieldValue = propChangedFieldInfo.GetValue(bindableObject); if (fieldValue == null) return; MulticastDelegate eventDelegate = fieldValue as MulticastDelegate; if (eventDelegate == null) return; // get invocation list Delegate[] delegates = eventDelegate.GetInvocationList(); // invoke each delegate foreach (Delegate propertyChangedDelegate in delegates) propertyChangedDelegate.Method.Invoke(propertyChangedDelegate.Target, new object[] { bindableObject, eventArgs }); } /// <summary> /// Gets the property name from an expression. /// </summary> /// <param name="propertyExpression">The property expression.</param> /// <returns>The property name as string.</returns> private static string GetPropertyNameFromExpression<T>(Expression<Func<T>> propertyExpression) { var lambda = (LambdaExpression)propertyExpression; MemberExpression memberExpression; if (lambda.Body is UnaryExpression) { var unaryExpression = (UnaryExpression)lambda.Body; memberExpression = (MemberExpression)unaryExpression.Operand; } else memberExpression = (MemberExpression)lambda.Body; return memberExpression.Member.Name; } /// <summary> /// Returns an instance of PropertyChangedEventArgs for the specified property name. /// </summary> /// <param name="propertyName"> /// The name of the property to create event args for. /// </param> private static PropertyChangedEventArgs GetPropertyChangedEventArgs(string propertyName) { PropertyChangedEventArgs args; lock (NotifyPropertyChangeExtension.syncLock) { if (!eventArgCache.TryGetValue(propertyName, out args)) eventArgCache.Add(propertyName, args = new PropertyChangedEventArgs(propertyName)); } return args; } } }
我删除了原始代码的一些部分,所以扩展应该按原样运行,而不需要引用我的库的其他部分。 但它并没有真正的testing。
PS代码的某些部分是从其他人借来的。 对我感到羞耻,忘了我从哪里得到它。 🙁
事实certificate,我可以做到这一点,没有意识到:
buttonEdit1.Properties.Buttons[0].Shortcut = new DevExpress.Utils.KeyShortcut(Keys.Alt | Keys.Down);
但是,如果我不能,我将不得不钻研源代码,find引发事件的方法。
感谢您的帮助,一切。
从通过reflection来提高事件 ,虽然我认为VB.NET中的答案,也就是在这个之前的两个post将为您提供通用的方法(例如,我想看看VB.NET的灵感引用不在同一类中的types):
public event EventHandler<EventArgs> MyEventToBeFired; public void FireEvent(Guid instanceId, string handler) { // Note: this is being fired from a method with in the same // class that defined the event (that is, "this"). EventArgs e = new EventArgs(instanceId); MulticastDelegate eventDelagate = (MulticastDelegate)this.GetType().GetField(handler, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(this); Delegate[] delegates = eventDelagate.GetInvocationList(); foreach (Delegate dlg in delegates) { dlg.Method.Invoke(dlg.Target, new object[] { this, e }); } } FireEvent(new Guid(), "MyEventToBeFired");
如果你知道控件是一个button,你可以调用它的PerformClick()
方法。 我有类似的问题像OnEnter
, OnExit
等其他事件。 如果我不想为每种控件types派生新的types,我不能提出这些事件。
看起来Wiebe Cnossen 接受的答案中的代码可以简化为这一行:
((Delegate)source.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(source)) .DynamicInvoke(source, eventArgs);