¿Cómo planteo un evento a través de la reflexión en.NET/C#?
event-handling devexpress (8)
Aquí hay una demostración usando genéricos (se omiten las verificaciones de error):
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();
}
}
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 elevo el evento desde una función externa a través de la reflexión?
Como resultado, pude hacer esto y no me di cuenta:
buttonEdit1.Properties.Buttons[0].Shortcut = new DevExpress.Utils.KeyShortcut(Keys.Alt | Keys.Down);
Pero si no pudiera, tendría que profundizar en el código fuente y encontrar el método que genera el evento.
Gracias por toda la ayuda.
Desde plantear un evento a través de la reflexión , aunque creo que la respuesta en VB.NET , es decir, dos entradas antes de esta le proporcionará el enfoque genérico (por ejemplo, buscaría en el de VB.NET una inspiración para 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");
En general, no puedes. 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.
¿ ButtonEdit
control ButtonEdit
expone un método OnClick
que puedes llamar?
Nota al pie: en realidad, los eventos pueden tener miembros "raise", de ahí EventInfo.GetRaiseMethod
. Sin embargo, esto nunca está poblado por C # y tampoco creo que esté en el marco general.
Escribí una extensión para las clases, que implementa INotifyPropertyChanged para inyectar el método RaisePropertyChange <T>, así que puedo usarlo así:
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.
Asi que aqui esta:
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;
}
}
}
Eliminé algunas partes del código original, por lo que la extensión debería funcionar como está, sin referencias a otras partes de mi biblioteca. Pero no está realmente probado.
PD Algunas partes del código fueron tomadas prestadas de otra persona. Lástima de mí, que me olvidé de donde lo obtuve. :(
Normalmente no puedes plantear 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 a través de la reflexión, simplemente necesita encontrar el campo de delegado privado, obtenerlo y luego invocarlo.
Parece que el código de la respuesta aceptada por Wiebe Cnossen podría simplificarse a este delineador:
((Delegate)source.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(source))
.DynamicInvoke(source, eventArgs);
Si sabe que el control es un botón, puede llamar a su 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.