2008-10-13 21 views
29

Tengo un editor de terceros que básicamente comprende un cuadro de texto y un botón (el control DevExpress ButtonEdit). Quiero hacer una pulsación de tecla particular (Alt + Abajo) emular haciendo clic en el botón. Para evitar escribir esto una y otra vez, quiero crear un controlador genérico de eventos KeyUp que genere el evento ButtonClick. Desafortunadamente, no parece haber un método en el control que genere el evento ButtonClick, así que ...¿Cómo puedo generar un evento mediante reflejo en .NET/C#?

¿Cómo puedo plantear el evento desde una función externa a través de la reflexión?

Respuesta

34

He aquí una demostración utilizando los genéricos (comprobaciones de errores omitidos):

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

me di cuenta! usted hizo una variable local eventInfo, pero nunca hace nada con ella. ¿Soy solo yo o podría eliminarse la primera línea? – Nick

+1

no es necesario obtener el eventInfo. Es inútil. de lo contrario, buena muestra! – henon

+3

I no creo que sea necesario llamar a GetInvocationList(). Simplemente invocar 'eventDelegate' es suficiente. – HappyNomad

5

De Raising an event via reflection, aunque creo que la respuesta en VB.NET, es decir, dos puestos por delante de éste le proporcionará el enfoque genérico (por ejemplo, me vería a la otra para VB.NET inspiración haciendo referencia a un tipo que no está en la misma clase):

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

Normalmente no se pueden generar otros eventos de clases. Los eventos se almacenan realmente como un campo delegado privado, más dos accesadores (add_event y remove_event).

Para hacerlo por reflexión, simplemente necesita encontrar el campo de delegado privado, obtenerlo, luego invocarlo.

13

En general, no se puede. Piense en los eventos como básicamente pares de métodos AddHandler/RemoveHandler (ya que eso es básicamente lo que son). Cómo se implementan depende de la clase. La mayoría de los controles de WinForms usan EventHandlerList como su implementación, pero su código será muy frágil si comienza a recuperar campos y claves privadas.

¿El control ButtonEdit expone un método OnClick que usted podría llamar?

Nota al pie: en realidad, los eventos pueden tienen miembros "raise", de ahí EventInfo.GetRaiseMethod. Sin embargo, esto nunca está poblado por C# y tampoco creo que esté en el marco general.

6

Como resultado, podría hacer esto y no nos dimos cuenta que:

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

Pero si pudiera no lo habría que profundizar en el código fuente y encontrar el método que plantea el evento.

Gracias por la ayuda, todo.

5

Si sabe que el control es un botón, puede llamar al método PerformClick(). Tengo un problema similar para otros eventos como OnEnter, OnExit. No puedo plantear esos eventos si no quiero derivar un nuevo tipo para cada tipo de control.

+0

'Realizar clic' Tienes que estar bromeando, es fantástico –

8

escribí una extensión de clases, que implementa INotifyPropertyChanged para inyectar el T> método RaisePropertyChange <, por lo que puede utilizar de esta manera:

this.RaisePropertyChanged(() => MyProperty); 

sin implementar el método en ninguna clase base. Para mi uso fue lento, pero tal vez el código fuente puede ayudar a alguien.

así que aquí está:

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; 
     } 
    } 
} 

me eliminado algunas partes del código original, por lo que la extensión debe trabajar como es, sin referencias a otras partes de mi biblioteca. Pero no está realmente probado.

P.S. Algunas partes del código fueron tomadas de otra persona. Lástima de mí, que me olvidé de donde lo obtuve. :(

+0

Muchas gracias. De hecho, tu respuesta es la única aquí que investiga el árbol de herencia y la comprobación nula correcta. – firegurafiku

+0

¿Por qué guarda en caché los argumentos del evento? El equipaje requerido, el Diccionario y el bloqueo, supera cualquier ganancia de rendimiento potencial, yo. ¿Me estoy perdiendo algo (estado compartido, etc.)? – skataben

3

Parece que el código de la accepted answer por Wiebe Cnossen podría simplificarse a este un trazador de líneas:?

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