У меня есть сторонний редактор, который в основном состоит из текстового поля и кнопки (элемент управления DevExpress ButtonEdit). Я хочу, чтобы конкретное нажатие клавиши (Alt + Down) имитировало нажатие кнопки. Чтобы избежать повторения этого снова и снова, я хочу создать общий обработчик событий KeyUp, который будет вызывать событие ButtonClick. К сожалению, похоже, что в элементе управления нет метода, вызывающего событие ButtonClick, поэтому ...
Как вызвать событие из внешней функции через отражение?





Из Создание события через отражение, хотя я думаю, что ответ в VB.NET, то есть два сообщения впереди этого, предоставит вам общий подход (например, я бы посмотрел на VB.NET для вдохновения при ссылке на тип, не входящий в тот же класс):
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");
Обычно вы не можете вызывать события других классов. События действительно хранятся как частное поле делегата плюс два метода доступа (add_event и remove_event).
Чтобы сделать это через отражение, вам просто нужно найти поле частного делегата, получить его, а затем вызвать его.
В общем, нельзя. Думайте о событиях как о парах методов AddHandler / RemoveHandler (поскольку это в основном то, чем они являются). Как они будут реализованы, зависит от класса. Большинство элементов управления WinForms используют EventHandlerList в качестве своей реализации, но ваш код будет очень хрупким, если он начнет извлекать закрытые поля и ключи.
Предоставляет ли элемент управления ButtonEdit метод OnClick, который вы могли бы вызвать?
Сноска: На самом деле, события может имеют элементы "повышения", следовательно, EventInfo.GetRaiseMethod. Однако это никогда не заполняется C#, и я также не верю, что это входит в структуру в целом.
Оказывается, я мог это сделать и не осознавал этого:
buttonEdit1.Properties.Buttons[0].Shortcut = new DevExpress.Utils.KeyShortcut(Keys.Alt | Keys.Down);
Но если бы я не мог, мне пришлось бы углубиться в исходный код и найти метод, который вызывает событие.
Всем спасибо за помощь.
Если вы знаете, что это кнопка, вы можете вызвать ее метод PerformClick(). У меня аналогичная проблема для других событий, таких как OnEnter, OnExit. Я не могу инициировать эти события, если не хочу создавать новый тип для каждого типа элемента управления.
«Выполнить щелчок»? Ты, должно быть, шутишь, это фантастика!
Вот демонстрация использования дженериков (проверка ошибок опущена):
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();
}
}
Я заметил, что вы создали локальную переменную eventInfo, но ничего с ней не делаете. Это только у меня или можно первую строчку убрать?
получать eventInfo не нужно. это бесполезно. в остальном хороший образец!
Я не считаю необходимым вызывать GetInvocationList (). Достаточно просто вызвать eventDelegate.
eventDelegate.DynamicInvoke (args) отлично работает, как заявил @HappyNomad.
вы не используете eventInfo
Я написал расширение для классов, которое реализует INotifyPropertyChanged для внедрения метода RaisePropertyChange <T>, поэтому я могу использовать его следующим образом:
this.RaisePropertyChanged(() => MyProperty);
без реализации метода в каком-либо базовом классе. Для моего использования это было медленно, но, возможно, исходный код может кому-то помочь.
Итак, вот оно:
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;
}
}
}
Я удалил некоторые части исходного кода, поэтому расширение должно работать как есть, без ссылок на другие части моей библиотеки. Но на самом деле это не проверено.
P.S. Некоторые части кода были заимствованы у кого-то другого. Позор мне, что я забыл, откуда взял. :(
Большое спасибо. Фактически, ваш ответ здесь единственный, который выполняет исследование дерева наследования и правильную проверку нуля.
Почему вы кешируете аргументы событий? Требуемый багаж, словарь и замок, перевешивают любую потенциальную выгоду в производительности, imho. Я что-то упустил (общее состояние и т. д.)?
Вы должны проверить Решение 1 здесь: stackoverflow.com/a/54789191/7108481
Кажется, что код из принятый ответ от Wiebe Cnossen можно упростить до следующего:
private void RaiseEventViaReflection(object source, string eventName)
{
((Delegate)source
.GetType()
.GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(source))
.DynamicInvoke(source, EventArgs.Empty);
}
Этот пример мне помог. Мне нужно было вызвать событие CLR в объекте приложения Windows в моем приложении WPF, и мне нужно было ссылаться на него через отражение. Поэтому вместо этого в методе FireEvent я использовал Application.Current. Отличный совет, при необходимости измените флаги привязки метода FireEvent.