views:

3515

answers:

8

I have a third-party editor that basically comprises a textbox and a button (the DevExpress ButtonEdit control). I want to make a particular keystroke (Alt-Down) emulate clicking the button. In order to avoid writing this over and over, I want to make a generic KeyUp event handler that will raise the ButtonClick event. Unfortunately, there doesn't seem to be a method in the control that raises the ButtonClick event, so...

How do I raise the event from an external function via reflection?

A: 

From:

http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/44b0d573-5c53-47b0-8e85-6056cbae95b0/, although I think the answer in VB.Net that is two posts ahead of this one will provide you with the generic approach (e.g. I'd look to the VB.Net one for inspiration on referencing a type not in the same class).

 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 (i.e. "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");
torial
+7  A: 

You can't normally raise another classes events. Events are really stored as a private delegate field, plus two accessors (add_event and remove_event).

To do it via reflection, you simply need to find the private delegate field, get it, then invoke it.

MichaelGG
+5  A: 

In general, you can't. Think of events as basically pairs of AddHandler/RemoveHandler methods (as that's basically what what they are). How they're implemented is up to the class. Most WinForms controls use EventHandlerList as their implementation, but your code will be very brittle if it starts fetching private fields and keys.

Does the ButtonEdit control expose an OnClick method which you could call?

Footnote: Actually, events can have "raise" members, hence EventInfo.GetRaiseMethod. However, this is never populated by C# and I don't believe it's in the framework in general, either.

Jon Skeet
Yet again Jon Skeet gives advice that solves a problem.
impulse3d
A: 

Often the OnEvent() function is declared as protected. Can you create a control that inherits from this 3rd party control so that you can then expose the functionality that you need?

Jon B
+2  A: 

As it turns out, I could do this and didn't realize it:

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

But if I couldn't I would've have to delve into the source code and find the method that raises the event.

Thanks for the help, all.

Josh Kodroff
+1  A: 

If you know that the control is a button you can call its PerformClick() method. I have similar problem for other events like OnEnter, OnExit. I can't raise those events if I don't want to derive a new type for each control type.

'Perform click'? You've gotta be kidding me, it's fantastic!
SDX2000
+2  A: 

Here's a demo using generics (error checks omitted):

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 eventInfo = source.GetType().GetEvent(eventName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
    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();
  }
}
brilsmurf
I noticed you made an eventInfo local variable, but you never do anything with it. Is it just me or could the first line be removed?
Nick
A: 

Hey Guys,

i wrote an Extension to classes, which implements INotifyPropertyChanged to inject the RaisePropertyChange method so i can use it like this:

this.RaisePropertyChanged(() => MyProperty);

without implementing the method in any base class. For my usage it was to slow, but maybe the Sourcecode can help someone.

So here it is:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using System.Globalization;

namespace Infrastructure
{
    /// 
    /// Adds a RaisePropertyChanged method to objects implementing INotifyPropertyChanged.
    /// 
    public static class NotifyPropertyChangeExtension
    {
        #region private fields

        private static readonly Dictionary eventArgCache = new Dictionary();
        private static readonly object syncLock = new object();

        #endregion

        #region the Extension's

        /// 
        /// Verifies the name of the property for the specified instance.
        /// 
        /// The bindable object.
        /// Name of the property.
        [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));
        }

        /// 
        /// Gets the property name from expression.
        /// 
        /// The notify object.
        /// The property expression.
        /// a string containing the name of the property.
        public static string GetPropertyNameFromExpression(this INotifyPropertyChanged notifyObject, Expression> propertyExpression)
        {
            return GetPropertyNameFromExpression(propertyExpression);
        }

        /// 
        /// Raises a property changed event.
        /// 
        /// 
        /// The bindable object.
        /// The property expression.
        public static void RaisePropertyChanged(this INotifyPropertyChanged bindableObject, Expression> propertyExpression)
        {
            RaisePropertyChanged(bindableObject, GetPropertyNameFromExpression(propertyExpression));
        }

        #endregion

        /// 
        /// Raises the property changed on the specified bindable Object.
        /// 
        /// The bindable object.
        /// Name of the property.
        private static void RaisePropertyChanged(INotifyPropertyChanged bindableObject, string propertyName)
        {
            bindableObject.VerifyPropertyName(propertyName);
            RaiseInternalPropertyChangedEvent(bindableObject, GetPropertyChangedEventArgs(propertyName));
        }

        /// 
        /// Raises the internal property changed event.
        /// 
        /// The bindable object.
        /// The  instance containing the event data.
        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 });
        }

        /// 
        /// Gets the property name from an expression.
        /// 
        /// The property expression.
        /// The property name as string.
        private static string GetPropertyNameFromExpression(Expression> 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;
        }

        /// 
        /// Returns an instance of PropertyChangedEventArgs for the specified property name.
        /// 
        /// 
        /// The name of the property to create event args for.
        ///         
        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;
        }
    }
}

I removed some parts of the original code, so the extension should work as is, without references to other parts of my library. But it's not really tested.

cheers, Chris

P.S. Some parts of the code was borrowed from someone else. Shame on me, that i forgot from where i got it. :(

ChrisTTian667