Domanda

Ho un editor di terze parti che comprende fondamentalmente una casella di testo e un pulsante (il controllo DevExpress ButtonEdit). Voglio fare una particolare sequenza di tasti ( Alt + Giù ) emulare facendo clic sul pulsante. Per evitare di scrivere più e più volte, voglio creare un gestore di eventi KeyUp generico che genererà l'evento ButtonClick. Sfortunatamente, non sembra esserci un metodo nel controllo che genera l'evento ButtonClick, quindi ...

Come posso sollevare l'evento da una funzione esterna tramite la riflessione?

È stato utile?

Soluzione

Ecco una demo che usa generics (controlli degli errori omessi):

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

Altri suggerimenti

In generale, non puoi. Pensa agli eventi come sostanzialmente coppie di metodi AddHandler / RemoveHandler (dato che è sostanzialmente quello che sono). Il modo in cui sono implementati dipende dalla classe. La maggior parte dei controlli di WinForms utilizza EventHandlerList come loro implementazione , ma il tuo codice sarà molto fragile se inizia a recuperare chiavi e campi privati.

Il controllo ButtonEdit espone un metodo OnClick che potresti chiamare?

Nota a piè di pagina: in realtà, gli eventi possono avere " aumentare " membri, quindi EventInfo.GetRaiseMethod. Tuttavia, questo non è mai popolato da C # e non credo che sia nemmeno nel framework in generale.

Normalmente non è possibile generare eventi di un'altra classe. Gli eventi sono realmente archiviati come un campo delegato privato, più due accessor (add_event e remove_event).

Per farlo tramite la riflessione, devi semplicemente trovare il campo del delegato privato, ottenerlo, quindi invocarlo.

Ho scritto un'estensione per le classi, che implementa INotifyPropertyChanged per iniettare RaisePropertyChange < T > metodo, quindi posso usarlo in questo modo:

this.RaisePropertyChanged(() => MyProperty);

senza implementare il metodo in nessuna classe base. Per il mio uso è stato rallentare, ma forse il codice sorgente può aiutare qualcuno.

Quindi eccolo qui:

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

Ho rimosso alcune parti del codice originale, quindi l'estensione dovrebbe funzionare così com'è, senza riferimenti ad altre parti della mia libreria. Ma non è davvero testato.

P.S. Alcune parti del codice sono state prese in prestito da qualcun altro. Peccato per me, che ho dimenticato da dove l'ho preso. : (

A quanto pare, ho potuto farlo e non me ne sono reso conto:

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

Ma se non potessi avrei dovuto approfondire il codice sorgente e trovare il metodo che genera l'evento.

Grazie per l'aiuto, tutti.

Da Generazione di un evento tramite la riflessione , anche se penso che la risposta sia VB.NET , ovvero due post prima di questo ti forniranno l'approccio generico (ad esempio, guarderei a VB.NET per trovare ispirazione su come fare riferimento a un tipo non appartenente alla stessa 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 sai che il controllo è un pulsante puoi chiamarne il metodo PerformClick(). Ho un problema simile per altri eventi come OnEnter, OnExit. Non posso generare tali eventi se non desidero derivare un nuovo tipo per ciascun tipo di controllo.

Sembra che il codice della risposta accettata di Wiebe Cnossen potrebbe essere semplificato in questa riga:

((Delegate)source.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(source))
    .DynamicInvoke(source, eventArgs);
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top