.NET / C#でリフレクションを介してイベントを発生させるにはどうすればよいですか?
-
10-07-2019 - |
質問
テキストボックスとボタン(DevExpress ButtonEditコントロール)で基本的に構成されるサードパーティエディターがあります。特定のキーストローク( Alt + Down )でボタンのクリックをエミュレートしたい。これを何度も書くことを避けるために、ButtonClickイベントを発生させる一般的なKeyUpイベントハンドラを作成します。残念ながら、ButtonClickイベントを発生させるコントロールにはメソッドがないようです...
リフレクションを介して外部関数からイベントを発生させるにはどうすればよいですか
解決
ジェネリックを使用したデモ(エラーチェックは省略):
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();
}
}
他のヒント
一般的にはできません。イベントは、基本的にはAddHandler
/ RemoveHandler
メソッドのペアと考えてください(基本的には何であるかです)。それらの実装方法はクラス次第です。ほとんどのWinFormsコントロールは、実装として EventHandlerList
を使用します、ただし、プライベートフィールドとキーの取得を開始すると、コードは非常に脆弱になります。
ButtonEdit
コントロールは、呼び出すことができるOnClick
メソッドを公開していますか?
脚注:実際には、イベントは<!> quot; raise <!> quot;を持つことができます 。メンバー、したがってEventInfo.GetRaiseMethod
。ただし、これはC#によって設定されることはなく、一般的なフレームワークにも含まれるとは思わない。
通常、別のクラスイベントを発生させることはできません。イベントは、実際にはプライベートデリゲートフィールドと2つのアクセサ(add_eventおよびremove_event)として保存されます。
リフレクションを介してこれを行うには、プライベートデリゲートフィールドを見つけて取得し、呼び出します。
RaisePropertyChange <!> lt; T <!> gt;を注入するためにINotifyPropertyChangedを実装するクラスの拡張機能を作成しました。メソッドなので、次のように使用できます:
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;
}
}
}
元のコードの一部を削除したため、拡張機能はライブラリの他の部分を参照せずにそのまま機能するはずです。しかし、実際にはテストされていません。
PSコードの一部は他の人から借用されました。恥ずかしくて、どこから来たのか忘れた。 :(
判明したように、私はこれを行うことができましたが、気づきませんでした:
buttonEdit1.Properties.Buttons[0].Shortcut = new DevExpress.Utils.KeyShortcut(Keys.Alt | Keys.Down);
しかし、できなかった場合は、ソースコードを掘り下げて、イベントを発生させるメソッドを見つける必要があります。
助けてくれてありがとう、すべて
リフレクションを介してイベントを発生させる 。ただし、 VB.NET 、つまり、この記事の前の2つの投稿は、一般的なアプローチを提供します(たとえば、同じクラスにない型を参照するためのインスピレーションを得るために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");
コントロールがボタンであることを知っている場合、そのPerformClick()
メソッドを呼び出すことができます。 OnEnter
、OnExit
などの他のイベントでも同様の問題があります。各コントロールタイプの新しいタイプを派生させたくない場合、これらのイベントを発生させることはできません。
Wiebe Cnossenによる受け入れられた回答のコードは、この1つのライナーに簡略化できるようです:
((Delegate)source.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(source))
.DynamicInvoke(source, eventArgs);