【问题标题】:Weird event behavior in a Popup inside a DataGridColumnHeaderDataGridColumnHeader 内的弹出窗口中的奇怪事件行为
【发布时间】:2020-09-16 15:56:51
【问题描述】:

我在这里没有使用任何外部库,只是普通的 WPF。

我有一个带有自定义 DataGridColumnHeader 的 DataGrid。此列标题包含一个用于切换 Popup 的 ToggleButton。在弹出窗口中有一个文本框。我遇到的问题是在 TextBox 内双击会引发 DataGrid 上的 MouseDoubleClick 事件。这是一个包含编号 cmets 的简化版本,我稍后会参考

<Window x:Class="PopupsAreWeird.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:PopupsAreWeird" xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <Style TargetType="DataGridColumnHeader">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="DataGridColumnHeader">
                        <!-- 1) This eventhandler is never called -->
                        <Grid Control.MouseDoubleClick="Grid_MouseDoubleClick_1">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions>

                            <ContentPresenter Grid.Column="0" />
                            <ToggleButton x:Name="openToggle" Grid.Column="1" Content="Open" />

                            <Popup IsOpen="{Binding ElementName=openToggle, Path=IsChecked}" StaysOpen="True">
                                <!-- 2) This eventhandler is always called, and the problem I am having is there regardless of whether I set e.Handled = true in this handler or not -->
                                <TextBox Width="200" MouseDoubleClick="TextBox_MouseDoubleClick" />
                            </Popup>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <StackPanel Orientation="Vertical">
        <!-- 3) This eventhandler is always called, but never should be -->
        <DataGrid MouseDoubleClick="DataGrid_MouseDoubleClick">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Header 1" Width="200" />
            </DataGrid.Columns>
        </DataGrid>
    </StackPanel>
</Window>

1) 我不明白为什么这个处理程序永远不会被调用。这意味着将 e.Handled 设置为 true 以停止向上冒泡到 DataGrid。虽然 Grid 没有 MouseDoubleClick 的定义,但据我了解,我可以将任何事件的事件处理程序附加到任何元素(例如将 ButtonBase.Click 附加到 Panel 元素)。这不是真的,还是我遗漏了什么特殊情况?

3) 我想避免双击事件冒泡到这里,但是即使我在事件处理程序 2 中将 e.Handled 设置为 true,也会调用该事件处理程序,并且在该处理程序中,e.Handled 是错误的。我认为原因是 Popup 是在 DataGridColumnHeader 内部定义的,并且由于某种奇怪的原因引发了 2 个事件,一个用于弹出窗口的树,一个用于包含 Popup 元素的树,但这似乎有点荒谬.

我知道 Popup 在 WPF 中是一种奇怪的东西,但这似乎我错过了一些明显的东西。有什么方法可以实现我想要的,即没有事件(或至少 MouseDoubleClick 事件)冒泡到 DataGrid?

提前致谢, 大卫

【问题讨论】:

    标签: c# wpf datagrid wpf-controls


    【解决方案1】:

    1) 你是对的,你可以将路由事件的处理程序附加到任何UIElement。但确实Popup 是一个特例。 Popup 是一个特殊控件,其行为类似于 Window。它可以在屏幕上的任何地方弹出,始终呈现在最顶层,并且不一定绑定到应用程序本身。这就是为什么它的可视化树与应用程序的可视化树分离的原因。 Popup.Child 将是一个单独的隔离视觉树。 Microsoft Docs: Popup and the Visual Tree.
    由于路由事件遍历要由任何节点处理的可视树,因此Popup 内的冒泡/隧道路由事件将在此隔离树的根部停止/开始是有意义的。因此,源自Popup 的路由事件无法在Popup 之外处理。

    3) 短版:Control.MouseDoubleClick(和预览版)是一个表现不同的特殊事件。当事件遍历可视化树时,路由上的每个 UIElement 都会引发此事件。所以将Handled设置为true是没有效果的。
    要解决您的问题,您应该处理UIElement.PreviewMousLeftButtonDown 并检查MouseButtonEventArgs.ClickCount 是否等于2 以检测双击,然后设置Handled = true
    或者在处理之前检查senderRoutedEventArgs.Source的类型是否不是TextBox(显式事件过滤)。

    “虽然这个路由事件似乎遵循一个冒泡的路径 元素树,它实际上是引发的直接路由事件 每个UIElement 沿着元素树。如果您设置Handled MouseDoubleClick 事件处理程序中 true 的属性,后续 MouseDoubleClick 沿途发生的事件将在 Handled 设置为 false。对于想要控制的消费者来说,这是一个更高级别的事件 当用户双击控件并处理 应用程序中的事件。

    想要处理鼠标双击的控件作者应该使用 ClickCount 等于 2 时的 MouseLeftButtonDown 事件。这会 导致Handled 的状态在这种情况下适当传播 其中元素树中的另一个元素处理事件。

    Control 类定义了PreviewMouseDoubleClickMouseDoubleClick 事件,但没有对应的单击事件。到 查看用户是否点击过一次控件,处理MouseDown 事件(或其对应物之一)并检查ClickCount 属性值为1。"
    Microsoft Docs: Control.MouseDoubleClick

    【讨论】:

    • 您好,感谢您的回答!我不知何故错过了文档中的这一部分,感谢您指出这一点。我会在今天晚些时候回到它时尝试一下。不过,我确实有一个澄清问题。在您的回复中,第 1 点和第 3 点似乎相互排斥。如果 Popup Control 中的事件永远不会在 Popup 之外冒泡(这是可以理解的,并且是对 eventhandler 1 的解释),为什么要调用 eventhandler 3?这也在弹出窗口之外。两者也是 MouseDoubleClick 事件,所以如果这是特殊情况,两者不应该相同吗?
    • 我比预期更早地解决了它,它确实有效,所以我已经将它标记为答案,因为原始问题已得到回答。如果您知道后续问题的答案,但如果您也能提供答案,我们将不胜感激
    • 这是一个“人工”遍历可视化树的特殊事件。该事件在每个UIElement创建。没有像this.GetVisualParent().RaiseEvent(e) 这样的真正的可视化树遍历。 Popup.Child 树仍然连接到 Popup 作为逻辑树。所以引擎显然会遍历逻辑树并访问每个节点的内容或子树。由于Popup.Child 被标记为内容属性,引擎会将其识别为可能的子树。这个路由事件很特别。这是一个伪路由事件,包含对(Preview)MouseLeftButtonDownClickCount 检查。
    • Popup.Child 与应用程序的可视化树分离的证明:Microsoft Docs: Popup and the Visual Tree
    • MouseDoubleClick 遍历逻辑树而不是可视树(因此不会在Grid 上提出)并且Popup 作为包含元素的逻辑子元素很有意义,谢谢为了澄清。
    猜你喜欢
    • 1970-01-01
    • 2015-01-12
    • 2017-03-20
    • 1970-01-01
    • 1970-01-01
    • 2011-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多