Удаление маршрутизированных обработчиков событий через отражение? - PullRequest
3 голосов
/ 11 июня 2009

Справочная информация: я использую WPF и C # (3.5) и работаю над приложением, которое позволяет пользователю просматривать форму / window / usercontrol, которая уже является частью скомпилированной сборки. Когда они просматривают его, они должны иметь возможность щелкать по любому элементу управления (кнопкам, текстовым полям, даже надписям), рядом с элементом управления должен появиться небольшой всплывающий редактор, где они могут затем ввести подсказку, helpID и т. Д. Для этого элемента управления.

Короче говоря: мне нужно имитировать базовый дизайн в WPF. Что означает, что мне нужно сделать по крайней мере следующее:

  • Загрузка пользовательского элемента управления / окна из данной сборки (без проблем)
  • Создайте его в пользовательском контроле / окне (без проблем)
  • Очистить все подписанные EventHandlers для всех его элементов управления
  • Назначьте свой собственный EventHandler «ShowEditorPopup» для каждого элемента управления (не должно быть проблемой)

Прежде всего, если у кого-то есть предложения по более легкому или лучшему маршруту, пожалуйста, дайте мне знать. (Очевидно, что нет никакого компонента DesignHost для WPF (как я читал .NET 2), так что это не так.)

Я застрял на выделенном элементе - очищаю все подписанные EventHandlers. После того, как я покопался в некоторых и попал в Reflector, я нашел этот классный кусок опасного кода (здесь я просто пытаюсь получить все EventHandlers для одной кнопки с именем someButton, определенной в XAML):

<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, который возвращает: Объект не соответствует типу цели (т. Е. Мои параметры или целевой объект перепутаны). Я обнаружил, что вы можете решить эту проблему с помощью:

GetEventHandlers.Invoke(Activator.CreateInstance(someButton.GetType()), Params);
// Also doesn't work...

Когда я устанавливаю часы в методе GetEventHandlers, он выглядит великолепно, ему просто не нравится то, что я передаю, когда я вызываю Invoke.

Мне кажется, что я на самом последнем этапе получения списка обработчиков RoutedEvent (например, старого доброго GetInvocationList (), который, очевидно, не работает для WPF RoutedEvents). Оттуда будет достаточно просто удалить эти обработчики из каждого элемента управления и получить форму без событий, к которой я затем смогу добавить свои собственные события.

Есть какие-нибудь подсказки? Опять же, если есть лучший / более простой способ выполнить задачу в целом, дайте мне знать:)

Ответы [ 3 ]

3 голосов
/ 01 августа 2009

Если вы используете GetEventHandlers.Invoke(EventHandlersStore , Params), это, кажется, работает хорошо и не падает.

3 голосов
/ 12 июня 2009

Что если вы выберете другой подход? Вы можете вызвать EventManager.RegisterClassHandler () для всех событий, а затем в своем обработчике (при условии, что это событие для элемента управления на поверхности разработки, а не для вашего пользовательского интерфейса) пометить событие как обработанное. Это должно предотвратить передачу его на элементы управления в вашей области разработки, поскольку обработчики классов вызываются раньше стандартных обработчиков событий.

Вам все еще нужно было бы использовать отражение, чтобы получить список событий, предоставленных элементом управления, но, по крайней мере, таким образом, вы бы не использовали отражение для удаления событий. Кроме того, если загружаемая вами сборка также регистрирует обработчик класса (вероятно, до того, как это сделает ваш код), сначала будет вызвана их сборка, но я предполагаю, что это будет редким случаем.

0 голосов
/ 06 апреля 2013

Используя ваш код сверху, я сделал это:

// 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 parent = VisualTreeHelper.GetParent (control); while (! (элемент управления Window) &&! (элемент управления Page)) { parent = VisualTreeHelper.GetParent (parent); }

И с этим экземпляром вы можете затем вызвать событие с:

res.[0].Handler.Method.Invoke(parent, new object[] { control, new RoutedEventArgs() }
...