Question

J'ai un éditeur tiers qui comprend essentiellement une zone de texte et un bouton (le contrôle DevExpress ButtonEdit). Je souhaite créer une frappe particulière ( Alt + Bas ) à émuler en cliquant sur le bouton. Afin d'éviter d'écrire ceci encore et encore, je veux créer un gestionnaire d'événements KeyUp générique qui déclenchera l'événement ButtonClick. Malheureusement, il ne semble pas y avoir de méthode dans le contrôle qui déclenche l'événement ButtonClick, donc ...

Comment déclencher l'événement à partir d'une fonction externe via une réflexion?

Était-ce utile?

La solution

Voici une démonstration utilisant des génériques (vérification des erreurs omise):

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

Autres conseils

En général, vous ne pouvez pas. Pensez aux événements comme à des paires de méthodes AddHandler / RemoveHandler (car c’est fondamentalement ce qu’elles sont). La façon dont ils sont mis en œuvre est à la hauteur de la classe. La plupart des contrôles WinForms utilisent EventHandlerList pour leur implémentation , mais votre code sera très fragile s'il commence à aller chercher les champs et les clés privés.

Le contrôle ButtonEdit expose-t-il une OnClick méthode que vous pouvez appeler?

Note de bas de page: En fait, les événements peuvent avoir & "déclencher &"; membres, d’où EventInfo.GetRaiseMethod. Cependant, cela n’est jamais peuplé par C # et je ne crois pas que ce soit dans le cadre général non plus.

Normalement, vous ne pouvez pas créer d’événements autres classes. Les événements sont réellement stockés dans un champ de délégué privé, plus deux accesseurs (add_event et remove_event).

Pour le faire par réflexion, il vous suffit de rechercher le champ de délégué privé, de l'obtenir, puis de l'invoquer.

J'ai écrit une extension aux classes, qui implémente INotifyPropertyChanged pour injecter RaisePropertyChange < T > méthode, donc je peux l'utiliser comme ça:

this.RaisePropertyChanged(() => MyProperty);

sans implémenter la méthode dans aucune classe de base. Pour mon usage, c'était trop lent, mais peut-être que le code source peut aider quelqu'un.

Alors la voici:

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

J'ai supprimé certaines parties du code d'origine. L'extension doit donc fonctionner telle quelle, sans référence à d'autres parties de ma bibliothèque. Mais ce n’est pas vraiment testé.

P.S. Certaines parties du code ont été empruntées à quelqu'un d'autre. Honte à moi, que j'ai oublié d'où je l'ai eu. : (

En fin de compte, je pouvais le faire et je ne m'en suis pas rendu compte:

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

Mais si je ne pouvais pas, j'aurais dû explorer le code source et trouver la méthode qui déclenche l'événement.

Merci pour l'aide, tout le monde.

De Élever un événement par réflexion , bien que je pense que la réponse soit VB.NET , c’est-à-dire que deux publications avant celle-ci vous fourniront l’approche générique (par exemple, je me tournerais vers celle de VB.NET pour trouver des idées pour référencer un type qui n’appartient pas à la même 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");

Si vous savez que le contrôle est un bouton, vous pouvez appeler sa méthode PerformClick(). J'ai un problème similaire pour d'autres événements tels que OnEnter, OnExit. Je ne peux pas élever ces événements si je ne veux pas dériver un nouveau type pour chaque type de contrôle.

Il semble que le code de la réponse acceptée par Wiebe Cnossen pourrait être simplifié comme suit:

((Delegate)source.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(source))
    .DynamicInvoke(source, eventArgs);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top