リフレクションを通じてルーティングされたイベント ハンドラーを削除しますか?
-
13-09-2019 - |
質問
背景:私は WPF と C# (3.5) を使用しており、コンパイル済みアセンブリの一部であるフォーム/ウィンドウ/ユーザー コントロールをユーザーが表示できるようにするアプリに取り組んでいます。これを表示すると、任意のコントロール (ボタン、テキストボックス、さらにはラベル) をクリックできるようになり、コントロールのそばに小さなポップアップ エディターが表示され、そこでそのコントロールのツールチップやヘルプ ID などを入力できるようになります。
要点は次のとおりです。WPF の基本的なデザイン ビューを模倣する必要があります。つまり、少なくとも次のことを行う必要があります。
- 指定されたアセンブリからユーザーコントロール/ウィンドウをロードします (問題ない)
- ユーザーコントロール/ウィンドウをインスタンス化します。 (問題ない)
- すべてのコントロールのサブスクライブされた EventHandler をすべてクリアします
- 独自の「ShowEditorPopup」EventHandler を各コントロールに割り当てます。 (問題ないはずです)
まず、誰かがより簡単またはより良いルートについての提案を持っている場合は、私に知らせてください。(どうやら、WPF には (.NET 2 にあるような) DesignHost のようなコンポーネントはないので、それはアウトです。)
私は太字の項目で立ち往生しています - サブスクライブされた EventHandler をクリアします。いくつか調べて Reflector に取り掛かった後、このクールな危険なコードの塊を思いつきました (ここでは、XAML で定義されている someButton と呼ばれる 1 つのボタンのすべての EventHandler を取得しようとしているだけです)。
<Button Name="someButton" Click="someButton_Click"/>
コードは次のとおりです (必要に応じて someButton_Click イベントハンドラーから実行できます)。
public void SomeFunction()
{
// Get the control's Type
Type someButtonType = ((UIElement)someButton).GetType();
// Dig out the undocumented (yes, I know, it's risky) EventHandlerStore
// from the control's Type
PropertyInfo EventHandlersStoreType =
someButtonType.GetProperty("EventHandlersStore",
BindingFlags.Instance | BindingFlags.NonPublic);
// Get the actual "value" of the store, not just the reflected PropertyInfo
Object EventHandlersStore = EventHandlersStoreType.GetValue(someButton, null);
// Get the store's type ...
Type storeType = EventHandlersStore.GetType();
// ... so we can pull out the store's public method GetRoutedEventHandlers
MethodInfo GetEventHandlers =
storeType.GetMethod("GetRoutedEventHandlers",
BindingFlags.Instance | BindingFlags.Public);
// Reflector shows us that the method's sig is this:
// public RoutedEventHandlerInfo[] GetRoutedEventHandlers(RoutedEvent routedEvent);
// So set up the RoutedEvent param
object[] Params = new object[] { ButtonBase.ClickEvent as RoutedEvent };
// I've also seen this for the param, but doesn't seem to make a difference:
// object[] Params = new object[] { someButton.ClickEvent };
// And invoke it ... and watch it crash!
GetEventHandlers.Invoke(someButton, Params);
}
これは Invoke まで機能し、Invoke は以下を返します。オブジェクトがターゲット タイプと一致しません (つまり、パラメータまたはターゲット オブジェクトが壊れています)。次の方法でこれを解決できることがわかりました。
GetEventHandlers.Invoke(Activator.CreateInstance(someButton.GetType()), Params);
// Also doesn't work...
GetEventHandlers MethodInfo に監視を設定すると、見た目は素晴らしく見えますが、Invoke を呼び出すときに渡す内容が気に入らないだけです。
RoutedEvent ハンドラーのリストを取得する方法の最後のステップにいるような気がします (古き良き GetInvocationList() のようなものですが、どうやら WPF RoutedEvents では機能しません)。そこから、各コントロールからこれらのハンドラーを削除し、イベントのないフォームを作成するのは非常に簡単です。その後、独自のイベントを追加できます。
手がかりはありますか?繰り返しになりますが、タスク全体を実行するためのより良い/より簡単な方法がある場合は、お知らせください:)
解決
あなたは異なるアプローチを取る場合。その後、呼び出し EventManager.RegisterClassHandler()のすべてのイベントのために、そして可能性がありあなたのハンドラ内で(イベントと仮定すると、あなたのUIの一部デザインサーフェイス上のコントロールのためである、としない)扱いとしてイベントをマーク。これは、クラスハンドラは、標準のイベントハンドラの前に呼び出されているので、デザイン面でのコントロールに転送されることを防ぐ必要があります。
あなたはまだコントロールが提供するイベントのリストを取得するためにリフレクションを使用する必要があると思いますが、少なくともこの方法、あなたは、イベントを削除するためにリフレクションを使用していないでしょう。あなたも読み込むアセンブリは(あなたのコードが行う前に、可能性の高い)クラスハンドラを登録する場合にも、彼らが最初に呼び出されますが、私は、これはまれであろうと推測ます。
他のヒント
GetEventHandlers.Invoke(EventHandlersStore , Params)
を使用する場合は、うまく動作するようにし、クラッシュしないようです。
私はこれをした上から、あなたのコードを使用します:
// Get the control's Type
Type controlViewType = ((UIElement)control).GetType();
// Dig out the undocumented (yes, I know, it's risky) EventHandlerStore
// from the control's Type
PropertyInfo EventHandlersStoreType =
controlViewType.GetProperty("EventHandlersStore",
BindingFlags.Instance | BindingFlags.NonPublic);
// Get the actual "value" of the store, not just the reflected PropertyInfo
Object EventHandlersStore = EventHandlersStoreType.GetValue(tree, null);
var miGetRoutedEventHandlers
EventHandlersStore.GetType().GetMethod("GetRoutedEventHandlers",
BindingFlags.Public | BindingFlags.Instance);
RoutedEventHandlerInfo[] res =
(RoutedEventHandlerInfo[])miGetRoutedEventHandlers.Invoke(EventHandlersStore,
new object[] { CheckedTreeViewItem.CheckedEvent });
あなたは、唯一の問題は、あなたがそのメソッドを実装するオブジェクトにインスタンスを取得する必要があるので、あなたが今、メソッド情報を持っていることであるとしたら。通常、イベントハンドラは、WindowまたはPageオブジェクトに定義されています。だから、それを得るために: VaRの親= VisualTreeHelper.GetParent(コントロール); (!(コントロールはウィンドウです)&&!(コントロールがページである))ながら、 { 親= VisualTreeHelper.GetParent(親)。 }
そして、そのインスタンスを使用すると、その後、イベントwtihを呼び出すことができます:
res.[0].Handler.Method.Invoke(parent, new object[] { control, new RoutedEventArgs() }