为什么附加到隧道事件的此类处理程序不在实例处理程序之前触发?
根据这篇 MSDN 文章(以及其他文章),
类处理程序在任何实例侦听器处理程序之前调用 每当路由事件发生时,都会附加到该类的实例 到达其路由中的元素实例。
我对 RoutedEvent 还很陌生,因此我的代码中可能有错误,但似乎附加到 RoutedEvent 的类处理程序是声明为 RoutingStrategy.Tunnel
并不总是在附加到同一事件的实例处理程序之前触发。
在下面的示例中,我创建了一个 TouchButton
控件类,其中包含隧道 RoatedEvent
和冒泡 RoatedEvent
。我已经为每个注册了类处理程序。然后,我在窗口中创建了该类的实例,并在后面的代码中处理每个事件。我在类元素和包含它的 Grid 上附加了相同的隧道事件处理程序。所有四个处理程序都在 MessageBox
中显示它们的名称,以便您可以清楚地看到执行顺序。
- Grid Instance PreviewTouch
- Class TouchButton_PreviewTouch
- TouchButton Instance PreviewTouch
- Class TouchButton_Touch
- TouchButton Instance Touch
这意味着如果我在类 PreviewTouch
事件处理程序中调用 e.Handled = true;
,我可以停止执行无法到达除附加到 Grid
元素的事件处理程序之外的所有其他事件处理程序。事情应该是这样的,还是我在某个地方犯了错误?否则,如何停止执行到达每个实例事件处理程序?
这是类:
public class TouchButton : Button
{
static TouchButton()
{
EventManager.RegisterClassHandler(typeof(TouchButton), PreviewTouchEvent,
new RoutedEventHandler(TouchButton_PreviewTouch), true);
EventManager.RegisterClassHandler(typeof(TouchButton), TouchEvent,
new RoutedEventHandler(TouchButton_Touch), true);
}
private static void TouchButton_PreviewTouch(object sender, RoutedEventArgs e)
{
MessageBox.Show("Class TouchButton_PreviewTouch");
}
private static void TouchButton_Touch(object sender, RoutedEventArgs e)
{
MessageBox.Show("Class TouchButton_Touch");
}
public static RoutedEvent TouchEvent = EventManager.RegisterRoutedEvent("Touch",
RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TouchButton));
public event RoutedEventHandler Touch
{
add { AddHandler(TouchEvent, value); }
remove { RemoveHandler(TouchEvent, value); }
}
public static RoutedEvent PreviewTouchEvent = EventManager.RegisterRoutedEvent(
"PreviewTouch", RoutingStrategy.Tunnel, typeof(RoutedEventHandler),
typeof(TouchButton));
public event RoutedEventHandler PreviewTouch
{
add { AddHandler(PreviewTouchEvent, value); }
remove { RemoveHandler(PreviewTouchEvent, value); }
}
protected override void OnClick()
{
RaiseTouchEvent();
}
private void RaiseTouchEvent()
{
RoutedEventArgs touchEventArgs = new RoutedEventArgs(PreviewTouchEvent);
RaiseEvent(touchEventArgs);
if (!touchEventArgs.Handled) RaiseEvent(new RoutedEventArgs(TouchEvent));
}
}
这是窗口代码中的实例处理程序:
private void TouchButton_PreviewTouch(object sender, RoutedEventArgs e)
{
MessageBox.Show(string.Format("{0} Instance PreviewTouch",
((FrameworkElement)sender).Name));
}
private void TouchButton_Touch(object sender, RoutedEventArgs e)
{
MessageBox.Show(string.Format("{0} Instance Touch",
((FrameworkElement)sender).Name));
}
这是控件 XAML:
<Grid Name="Grid" Controls:TouchButton.PreviewTouch="TouchButton_PreviewTouch">
<Controls:TouchButton x:Name="TouchButton" Width="200" Height="45" FontSize="24"
Content="Touch me" Touch="TouchButton_Touch" PreviewTouch="TouchButton_PreviewTouch" />
</Grid>
我确实明白,隧道事件是由 Grid 元素在“隧道”向下处理之前处理的 < code>TouchButton 元素,但我认为类处理程序总是应该在实例处理程序之前触发。如果没有,我怎样才能实现这一目标?
更新>>
感谢@sanguine的回答,我设法找到一种方法来阻止所有实例处理程序处理该事件。如果我没有按照乐观的建议将 TouchButton
声明的类处理类型替换为 Grid
,而是将其替换为 FrameworkElement
,那么它将捕获所有 < code>FrameworkElement 派生的控件。
EventManager.RegisterClassHandler(typeof(FrameworkElement), PreviewTouchEvent,
new RoutedEventHandler(TouchButton_PreviewTouch), true);
According to this MSDN article (among others),
Class handlers are invoked before any instance listener handlers that
are attached to an instance of that class, whenever a routed event
reaches an element instance in its route.
I'm quite new to RoutedEvent
s so there is a chance that I have a mistake in my code, but it seems as though a class handler attached to a RoutedEvent
that is declared as RoutingStrategy.Tunnel
does not always fire before the instance handlers attached to the same event.
In my example below, I have created a TouchButton
control class with a tunneling RoutedEvent
and a bubbling RoutedEvent
. I have registered class handlers for each. I then created an instance of the class in a window and handle each event in the code behind. I attached the same handler for the tunneling event on both the class element and the Grid
that contains it. All four handlers display their name in a MessageBox
so you can clearly see the order of execution.
- Grid Instance PreviewTouch
- Class TouchButton_PreviewTouch
- TouchButton Instance PreviewTouch
- Class TouchButton_Touch
- TouchButton Instance Touch
This means that if I call e.Handled = true;
in the class PreviewTouch
event handler, I can stop execution from reaching all of the other event handlers except for the one attached to the Grid
element. Is this supposed to be like this, or have I made a mistake somewhere? Otherwise, how can I stop execution from reaching every instance event handler?
Here is the class:
public class TouchButton : Button
{
static TouchButton()
{
EventManager.RegisterClassHandler(typeof(TouchButton), PreviewTouchEvent,
new RoutedEventHandler(TouchButton_PreviewTouch), true);
EventManager.RegisterClassHandler(typeof(TouchButton), TouchEvent,
new RoutedEventHandler(TouchButton_Touch), true);
}
private static void TouchButton_PreviewTouch(object sender, RoutedEventArgs e)
{
MessageBox.Show("Class TouchButton_PreviewTouch");
}
private static void TouchButton_Touch(object sender, RoutedEventArgs e)
{
MessageBox.Show("Class TouchButton_Touch");
}
public static RoutedEvent TouchEvent = EventManager.RegisterRoutedEvent("Touch",
RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TouchButton));
public event RoutedEventHandler Touch
{
add { AddHandler(TouchEvent, value); }
remove { RemoveHandler(TouchEvent, value); }
}
public static RoutedEvent PreviewTouchEvent = EventManager.RegisterRoutedEvent(
"PreviewTouch", RoutingStrategy.Tunnel, typeof(RoutedEventHandler),
typeof(TouchButton));
public event RoutedEventHandler PreviewTouch
{
add { AddHandler(PreviewTouchEvent, value); }
remove { RemoveHandler(PreviewTouchEvent, value); }
}
protected override void OnClick()
{
RaiseTouchEvent();
}
private void RaiseTouchEvent()
{
RoutedEventArgs touchEventArgs = new RoutedEventArgs(PreviewTouchEvent);
RaiseEvent(touchEventArgs);
if (!touchEventArgs.Handled) RaiseEvent(new RoutedEventArgs(TouchEvent));
}
}
Here are the instance handlers in the window code behind:
private void TouchButton_PreviewTouch(object sender, RoutedEventArgs e)
{
MessageBox.Show(string.Format("{0} Instance PreviewTouch",
((FrameworkElement)sender).Name));
}
private void TouchButton_Touch(object sender, RoutedEventArgs e)
{
MessageBox.Show(string.Format("{0} Instance Touch",
((FrameworkElement)sender).Name));
}
Here is the control XAML:
<Grid Name="Grid" Controls:TouchButton.PreviewTouch="TouchButton_PreviewTouch">
<Controls:TouchButton x:Name="TouchButton" Width="200" Height="45" FontSize="24"
Content="Touch me" Touch="TouchButton_Touch" PreviewTouch="TouchButton_PreviewTouch" />
</Grid>
I do understand that the tunneling event is handled by the Grid
element before 'tunneling' down to the TouchButton
element, but I thought that the class handlers were always supposed to fire before the instance handlers. If not, how can I achieve this?
UPDATE >>>
Thanks to @sanguine's answer, I managed to find a way to stop all instance handlers from handling the event. If instead of replacing the declared class handling type of TouchButton
with Grid
as sanguine suggested, I replace it with FrameworkElement
, then it will catch all FrameworkElement
-derived controls.
EventManager.RegisterClassHandler(typeof(FrameworkElement), PreviewTouchEvent,
new RoutedEventHandler(TouchButton_PreviewTouch), true);
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
MSDN 文章的意思是 - 当遍历事件找到一个同时提供类和实例处理程序的元素(在树中)时,它会在实例处理程序之前调用类处理程序。因此,在这种情况下,当事件被触发并从外到内传输时,它会遇到网格,但 Grid 类没有任何类处理程序,因此它仅调用“Grid”实例使用的实例处理程序。如果在切换按钮中添加此行 -
EventManager.RegisterClassHandler(typeof(Grid), PreviewTouchEvent,
新的 RoutedEventHandler(TouchButton_PreviewTouch), true);
然后在 Grid 的实例处理程序之前,将调用相应的类处理程序。
MSDN article means - When a traversing event finds an element(in tree) which has provision of both Class and instance handler then it invokes class handler before the instance handler. Therefore in this case when event is fired and tunneled from out to in, it encounters grid but the Grid class does not have any Class handler so it merely calls the instance handler used by the "Grid" instance. If this line is added in toggle button-
EventManager.RegisterClassHandler(typeof(Grid), PreviewTouchEvent,
new RoutedEventHandler(TouchButton_PreviewTouch), true);
then before Grid's instance handler, the corresponding Class handler will be called.