WPF 中隧道事件和冒泡事件在什么情况下有用?

14

我了解冒泡和隧道的工作原理。然而,我对如何使用它们感到困惑。

这是为什么:

我想处理鼠标单击事件。要冒泡它,有MouseDown,要隧道它,有PreviewMouseDown。然而,MouseDown并不一定意味着用户单击了控件。也许用户按下按钮并移开以取消单击。如果按钮没有被点击,我就不想改变任何东西。

因此,我的问题是,冒泡和隧道策略有什么用处?

3个回答

9

如果事件列在RoutedEventArgs中,则为路由事件。路由事件支持冒泡、隧道或直接的RoutingStrategy。让我们来看一下Button.Click的事件处理程序:

private void Grid_Click(object sender, RoutedEventArgs e)
{
    MessageBox.Show("Button Test clicked!");
}

这里指定了RoutedEventArgs,所以它是一个路由事件。因为名称中没有指定preview,所以这是一个冒泡事件。这可以通过以下方式进行演示:

<Grid ButtonBase.Click="Grid_Click">
    <Button Name="TestButton" Width="100" Height="30" Content="Test" />
</Grid>

当你点击 TestButton,事件会升级到 Grid 以上,并显示一条消息:

Button Test clicked!

Bubbling/Tunneling 策略的使用价值 Tunneling 许多标准控件都会监听事件,例如 KeyDown, MouseDown等。例如-DataGrid 控件。我希望按下回车键时调用添加记录功能。但是,DataGrid 已经有了 KeyDown 事件,因此该事件不会被触发。因此,您必须在 Tunnel 事件 - PreviewKeyDown 中执行逻辑,它将在 KeyDown 事件之前起作用。对于 RichTextBoxControl 同样适用。 Bubbling 有时,您需要针对特定事件的全局处理程序,以使其适用于 VisualTree 中的所有控件。自然地,您不能直接执行事件。因此,Bubbling 事件登场了。
另一个原因是 WPF 的思想。此 Button 可包含任何东西: Image、其他 Button 等:

enter image description here

用户可以点击 Button 中的 TextBlock/Image。我们如何知道单击是在 Button 中发生的?没错,使用 Bubbling 事件来实现。
欲了解更多信息,请参见: Understanding Routed Events and Commands In WPF Edit 我稍微改变了 Click 处理程序:
private void Grid_Click(object sender, RoutedEventArgs e)
{
    String message = "#" + eventCounter.ToString() + ":\r\n" +
            " Sender: " + sender.ToString() + ":\r\n" +
            " Source: " + e.Source + ":\r\n" +
            " Original Source: " + e.OriginalSource;

    lstEvents.Items.Add(message);
}

点击 按钮 后的结果:

图片描述


但据我所知,Click是直接事件而不是冒泡事件。 - SanSolo
1
@SanSolo:如果是直接的话,那么消息“Button Test clicked!”就不会出现。我们为Grid设置了一个处理程序,而不是为Button设置,当点击Button时,事件对Grid起作用。事件在Button上向上移动,并来到Grid。据我所知,直接单击事件已经存在于*WinForms*中。 - Anatoliy Nikolaev
1
是的,你说得对。我运行了你提供的示例来检查Sender、Source和Original Source。它们的值分别为Grid、Button和Button:Test。所以默认情况下,所有WPF事件都是冒泡事件?这很令人困惑...隧道事件有预览关键字,但不知道哪些是冒泡事件。 - SanSolo
1
@SanSolo:默认情况下,所有事件都是“路由事件”。如果名称中包含*preview*,则为隧道事件。您可以查看我在答案中提供给您的文章。 - Anatoliy Nikolaev
1
谢谢。我在另一篇 MSDN 文章上找到了这个信息:
事件路径根据事件定义可以向两个方向之一传播,但通常路线从源元素开始,然后通过元素树“冒泡”向上,直到到达元素树的根(通常是页面或窗口)。 因此,默认策略是冒泡。
- SanSolo

4

你好,虽然你可以在网上找到一些关于这方面的好文章,但我还是会尝试回答这个问题。

假设您给一个按钮一个非常简单的外观,由一个矩形组成,并提供一个简单的文本作为内容。即使具有基本可视化元素,仍然存在两个元素:文本和矩形。无论鼠标是否位于文本或矩形上,按钮都应该响应鼠标点击。在标准的.NET事件处理模型中,这意味着为这两个元素都注册一个MouseLeftButtonUp事件处理程序。

利用WPF的内容模型时,这个问题会变得更加严重。一个按钮不限于只有纯文本作为标题 - 它可以包含任何对象作为内容。下面的xaml并不特别雄心勃勃,但是即使如此,它也有六个可见元素:黄色轮廓圆形,两个眼睛点,曲线嘴巴,文本和按钮背景本身。 为每个元素附加事件处理程序将是冗长和低效的。 如果想要工作MouseDown,我们需要向此代码添加8个MouseDownEvents。

<Button PreviewMouseDown="PreviewMouseDownButton" MouseDown="MouseDownButton">
        <Grid PreviewMouseDown="PreviewMouseDownGrid" MouseDown="MouseDownGrid">
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Canvas PreviewMouseDown="PreviewMouseDownCanvas" MouseDown="MouseDownCanvas" Width="20" Height="18" VerticalAlignment="Center">
                <Ellipse PreviewMouseDown="PreviewMouseDownEllipse" MouseDown="MouseDownEllipse" x:Name="myEllipse" Canvas.Left="1" Canvas.Top="1" Width="16" Height="16" Fill="Yellow" Stroke="Black" />
                <Ellipse Canvas.Left="4.5" Canvas.Top="5" Width="2.5" MouseDown="MouseDownEllipse" Height="3" Fill="Black" />
                <Ellipse Canvas.Left="11" Canvas.Top="5" Width="2.5" MouseDown="MouseDownEllipse" Height="3" Fill="Black" />
                <Path Data="M 5,10 A 3,3 0 0 0 13,10" Stroke="Black" MouseDown="Path_MouseDown_1"/>
            </Canvas>
            <TextBlock Grid.Column="1" MouseDown="TextBlock_MouseDown_1">Click!</TextBlock>
        </Grid>
    </Button>

WPF使用路由事件冒泡/隧道/普通,比普通事件更加彻底。WPF不仅调用附加到引发事件的元素的处理程序,而且还遍历用户界面元素树,调用附加到从源元素直到用户界面树根节点的任何节点的路由事件的所有处理程序。

那个回答阐明了 Bubbling/Tunneling 的需求,这也是我问题的标题。然而,我仍然困惑为什么我们会使用 MouseUp 或 MouseDown,因为它不像 Click 那样是一个确定性的动作。MousewheelUp/Down 我可以看到有用的场景。但在 MouseUp 的情况下,我们改变了某些东西,用户移开了按钮,没有点击...我们会在哪里使用它呢? - SanSolo

1

你说你知道冒泡/隧道的概念,所以我们不再深入讨论。但这就是这些事件的目的,即它们可以让你知道鼠标按钮在控件或其子控件上是按下还是释放。事件运行良好。对于你的情况,你应该使用按钮的Click事件,它会告诉你鼠标是在按钮本身上按下和释放的。

谢谢


点击事件将是一个直接事件。我的困惑在于冒泡/隧道事件的使用。 - SanSolo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接