【问题标题】:WPF EventHandler fired on the wrong ElementWPF EventHandler 在错误的元素上触发
【发布时间】:2011-02-17 13:54:57
【问题描述】:

我对此感到困惑:

我做了一个很简单的例子:

MainWindow.xaml:

<Window x:Class="Test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <Style TargetType="RichTextBox">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="RichTextBox">
                        <Grid Height="100" Width="200">
                            <Grid.RowDefinitions>
                                <RowDefinition/>
                                <RowDefinition/>
                            </Grid.RowDefinitions>
                            <Label Background="Blue" Grid.Row="0">Label</Label>
                            <Border PreviewMouseDown="Border_PreviewMouseDown" Background="Red" Grid.Row="1">
                                <ScrollViewer x:Name="PART_ContentHost" />
                            </Border>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>

    <Grid>
        <RichTextBox>
            <FlowDocument>
                <FlowDocument.Blocks>
                    <Paragraph>
                        oaizeropiazuerpoaizeurpoaizeurpaozieurpaozieru
                    </Paragraph>
                </FlowDocument.Blocks>
            </FlowDocument>
        </RichTextBox>
    </Grid>
</Window>

MainWindow.xaml.cs:

using System.Diagnostics;
using System.Windows;
using System.Windows.Input;

namespace Test
{
   public partial class MainWindow : Window
   {
      public MainWindow()
      {
         InitializeComponent();
      }

      private void Border_PreviewMouseDown(object sender, MouseButtonEventArgs e)
      {
          Debug.WriteLine("Click !");
      }

   }
}

现在,当我明确地将 PreviewMouseDown EventHandler 放在边框上而不是在模板中的标签上时,我希望它会在我单击控件的(红色)边框时触发,但不会在我单击(蓝色)标签。

但是,当我点击(红色)边框AND时,当我点击(蓝色)标签时,会触发该事件。

那么为什么 Label 会调用我明确附加到 controlTemplate 的另一部分(即:边框)的 EventHandler?

我已检查:如果我从边框属性中删除 PreviewMouseDown="Border_PreviewMouseDown" 代码,则不会再在标签上触发事件。

我在这里错过了什么?

什么是正确的做法?如何设计我的 controlTemplate 以便仅由模板化控件的子部分触发 PreviewMouseDown 事件?

提前致谢

编辑:按照 Snowbear 的回答,我在单击标签时检查了事件的 originalSource。这确实是边界。为什么会这样?边框以什么方式封装了上面模板中的标签?为了避免这种情况,我专门将它们设置在不同的网格行上,那怎么会呢?

Edit2 只是为了好玩,我创建了一个只打印事件的发送者/来源/原始来源的处理程序,并将其附加到模板中的网格、边框和滚动查看器。

这是我在垂直滚动条上单击一次(且仅一次)时得到的结果:

Clic -- Sender: System.Windows.Controls.Grid -- OriginalSource: Microsoft.Windows.Themes.ScrollChrome -- Source: MyRichTextBox
Clic -- Sender: System.Windows.Controls.Border -- OriginalSource: Microsoft.Windows.Themes.ScrollChrome -- Source: MyRichTextBox
Clic -- Sender: System.Windows.Controls.ScrollViewer -- OriginalSource: Microsoft.Windows.Themes.ScrollChrome -- Source: MyRichTextBox
Clic -- Sender: System.Windows.Controls.Grid -- OriginalSource: Microsoft.Windows.Themes.ScrollChrome -- Source: System.Windows.Controls.ScrollViewer
Clic -- Sender: System.Windows.Controls.Border -- OriginalSource: Microsoft.Windows.Themes.ScrollChrome -- Source: System.Windows.Controls.ScrollViewer
Clic -- Sender: System.Windows.Controls.ScrollViewer -- OriginalSource: Microsoft.Windows.Themes.ScrollChrome -- Source: System.Windows.Controls.ScrollViewer

这清楚地解决了问题:由于某种原因,事件确实被隧道化了两次,首先以 TemplatedParent(即:RichtextBox)作为源,然后以 contentPresenter(即:ScrollViewer)作为源。

Merlin 最宽松的裤子,我真的很想知道编写这个程序的 MS Dev 的脑袋是怎么想的……

【问题讨论】:

    标签: wpf event-handling


    【解决方案1】:

    哎呀,你确实发现了一个非常奇怪的行为。似乎可以将作为TextBoxBase 控件模板一部分的所有元素的鼠标处理重新矢量化到文本内容元素,然后从那里进行隧道/气泡。因此,在富文本框控件模板中包含标签意味着它将参与富文本上的鼠标事件,就好像它是富文本本身的一部分一样。

    要解决此问题,您可以使用包含关联元素的ContentControl,然后将其Content 属性转发给“正常”的TextBoxBase 派生元素。这是一个简化的 XAML 示例。第一部分在一个更简单的例子中重现了奇怪的行为,第二部分展示了如何使用ContentControl 来解决这个问题。

    <Grid>
        <StackPanel>
            <TextBox Text="Some text">
                <TextBox.Template>
                    <ControlTemplate TargetType="TextBox">
                        <StackPanel>
                            <Rectangle Fill="Green" Width="200" Height="50"/>
                            <Border x:Name="RedBorder" PreviewMouseDown="Border_PreviewMouseDown" Background="Red">
                                <AdornerDecorator x:Name="PART_ContentHost" />
                            </Border>
                        </StackPanel>
                    </ControlTemplate>
                </TextBox.Template>
            </TextBox>
            <ContentControl Content="Some text">
                <ContentControl.Template>
                    <ControlTemplate TargetType="ContentControl">
                        <StackPanel>
                            <Rectangle Fill="Green" Width="200" Height="50"/>
                            <Border x:Name="RedBorder" PreviewMouseDown="Border_PreviewMouseDown" Background="Red">
                                <TextBlock Text="{TemplateBinding Content}"/>
                            </Border>
                        </StackPanel>
                    </ControlTemplate>
                </ContentControl.Template>
            </ContentControl>
        </StackPanel>
    </Grid>
    

    【讨论】:

    • 感谢使用 ControlTemplate 的建议。我将此标记为最佳答案。我想我会以这种方式使用 UserControl。我填写了一份关于 MS 连接的错误报告,因为我看不出这可能意味着这样的行为(谁知道呢,也许 MS 不会忽略这个......)
    【解决方案2】:

    您在这里缺少冒泡事件。 http://msdn.microsoft.com/en-us/library/ms742806.aspx#routing_strategies

    您可以检查RoutedEventArgs.OriginalSource 以确定单击了哪个边框。

    更新:好的,看起来我错过了,但不是完全错过。我已经用你的样本玩了一段时间,看起来RichTextBox(或者可能是TextBoxBase)对PART_ContentHost做了一些事情,这迫使tunnelling事件走向这部分。您的示例在 RichTextBox 之外表现良好,因此我假设它使用自己的模板执行某些操作。虽然我不知道如何在不进入 .Net 资源的情况下进一步调查它。

    【讨论】:

    • "冒泡:事件源上的事件处理程序被调用。路由事件然后路由到连续的父元素,直到到达元素树根"
    • 我根据您的回答编辑了我的问题。我认为你在正确的轨道上,但这仍然不能解释一切(见编辑)
    • 我同意您的编辑。我真的很想知道发生了什么以及如何才能使这个该死的模板按预期工作...:/
    猜你喜欢
    • 2019-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多