2008-10-13 26 views
29

我有一个第三方编辑器,基本上包含一个文本框和一个按钮(DevExpress ButtonEdit控件)。我想做一个特定的击键(Alt + 向下)模拟点击按钮。为了避免一遍又一遍地写这个,我想创建一个通用的KeyUp事件处理程序,它将引发ButtonClick事件。不幸的是,控件中似乎没有引发ButtonClick事件的方法,所以...如何通过.NET/C#中的反射来引发事件?

如何通过反射从外部函数中引发事件?

回答

34

下面是使用泛型(省略错误检查)演示:

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(); 
    } 
} 
+2

我注意到!你做了一个eventInfo局部变量,但是你从来没有做过任何事情,它只是我或者第一行可以被删除吗? – Nick 2009-10-11 20:31:09

+1

你不需要获取eventInfo,它是没用的 否则,很好的示例! – henon 2011-12-21 09:08:57

5

Raising an event via reflection,虽然我觉得在VB.NET的答案,那就是,提前这一个两个职位将为您提供通用的方法(例如,我想看看在VB.NET一个灵感参考不在同一类别中的类型):

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"); 
12

您通常无法引发其他类事件。事件实际上存储为私人委托字段,另外还有两个访问器(add_event和remove_event)。

要通过反射来做到这一点,你只需要找到私人委托字段,获取它,然后调用它。

13

一般来说,你不能。把事件想象成基本上是AddHandler/RemoveHandler方法的对(因为基本上它们是什么)。他们如何实施取决于班级。大多数WinForms控件使用EventHandlerList作为它们的实现,但是如果它开始提取私有字段和密钥,代码将非常脆弱。

ButtonEdit控件是否公开了您可以调用的方法OnClick

脚注:事实上,事件可以有“提高”成员,因此EventInfo.GetRaiseMethod。然而,这从来没有被C#填充,我也不相信它通常在框架中。

6

事实证明,我能做到这一点,并没有意识到这一点:

buttonEdit1.Properties.Buttons[0].Shortcut = new DevExpress.Utils.KeyShortcut(Keys.Alt | Keys.Down); 

但如果我不能我会一直有深入研究的源代码,并发现,提高了方法事件。

感谢您的帮助,所有。

5

如果您知道该控件是一个按钮,您可以调用其方法PerformClick()。对于其他事件,例如OnEnter,OnExit,我也有类似的问题。如果我不想为每种控件类型派生新的类型,我无法提出这些事件。

+0

“执行点击”,你不是开玩笑吧,这是梦幻般的 – 2010-06-01 05:24:35

8

我写的扩展类,其中实现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; 
     } 
    } 
} 

我去掉了原代码的某些部分,所以应延长工作原样,但没有我的媒体库的其他部分的引用。但它没有经过真正的测试。

P.S.代码的某些部分是从别人借来的。对我感到羞耻,我忘了自己在哪里。 :(

3

看来,由韦博Cnossen从accepted answer的代码可以简化为这一个班轮:

((Delegate)source.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(source)) 
    .DynamicInvoke(source, eventArgs);