Pergunta

Eu tenho um editor de terceiros que basicamente compreende uma caixa de texto e um botão (o controle DevExpress ButtonEdit). Eu quero fazer um determinado keystroke ( Alt + para baixo ) emular clicando no botão. A fim de evitar escrever isso mais e mais, eu quero fazer um manipulador de eventos KeyUp genérico que irá aumentar o evento ButtonClick. Infelizmente, não parece ser um método no controle que gera o evento ButtonClick, então ...

Como faço para aumentar o evento a partir de uma função externa através de reflexão?

Foi útil?

Solução

Aqui está uma demonstração usando os genéricos (verificações de erro omitido):

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

Outras dicas

Em geral, você não pode. Pense em eventos como basicamente pares de métodos AddHandler / RemoveHandler (como isso é basicamente o que eles são). Como eles são implementados é até a classe. A maioria dos controles WinForms usar EventHandlerList como a sua implementação, mas seu código será muito frágil se ele começa buscar campos privados e chaves.

O controle ButtonEdit expor um método OnClick que você poderia chamar?

Nota de rodapé: Na verdade, eventos pode tem "raise" membros, daí EventInfo.GetRaiseMethod. No entanto, isso nunca é preenchida por C # e eu não acredito que é no âmbito de um modo geral, qualquer um.

Você não pode normalmente levantar uma outra classes de eventos. Os eventos são realmente armazenado como um campo delegado privada, além de dois acessores (add_Event e remove_Event).

Para fazê-lo através de reflexão, você simplesmente precisa encontrar o campo delegado privado, obtê-lo, em seguida, invocá-lo.

Eu escrevi uma extensão para as classes, que implementa INotifyPropertyChanged para injetar o RaisePropertyChange método, para que eu possa usá-lo como este:

this.RaisePropertyChanged(() => MyProperty);

sem implementar o método em qualquer classe base. Para meu uso era a abrandar, mas talvez o código-fonte pode ajudar alguém.

Então aqui 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;
        }
    }
}

Tirei algumas partes do código original, de modo que a extensão deve funcionar como está, sem referências a outras partes da minha biblioteca. Mas não é realmente testado.

P.S. Algumas partes do código foi emprestado de outra pessoa. Shame on me, que eu esqueci de onde eu consegui. : (

Como se vê, eu poderia fazer isso e não sabia que isso:

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

Mas se eu pudesse não eu teria que mergulhar no código-fonte e encontrar o método que gera o evento.

Obrigado pela ajuda, tudo.

De Disparar um evento através da reflexão , embora eu acho que a resposta em VB.NET , isto é, dois postos à frente de um presente irá fornecer-lhe com a abordagem genérica (por exemplo, eu olhar para o VB.NET para a inspiração em referência a um tipo não na mesma classe) :

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

Se você sabe que o controle é um botão, você pode chamar seu método PerformClick(). Eu tenho problema semelhante para outros eventos como OnEnter, OnExit. Eu não posso levantar esses eventos se eu não quiser derivar um novo tipo para cada tipo de controle.

Parece que o código da resposta aceito por Wiebe Cnossen poderia ser simplificado para este forro um:

((Delegate)source.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(source))
    .DynamicInvoke(source, eventArgs);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top