WPF EventHandler触发了错误的Element

时间:2011-02-17 13:54:57

标签: wpf event-handling

我很困惑:

我做了一个非常简单的例子:

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放在Border而不是我模板中的标签上,我希望当我点击控件的(红色)边框时它会触发,但是当我点击它时不会(蓝色)标签。

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

那么为什么Label会调用一个我明确附加到controlTemplate其他部分的EventHandler(即边界)?

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

我在这里错过了什么?

什么是正确的方法?如何设计我的controlTemplate,以便只能通过模板化控件的子部分触发PreviewMouseDown事件?

提前致谢

编辑:在Snowbear的回答之后,我点击了标签时检查了事件的原始来源。这确实是边界。为什么会这样?以什么方式将标签封装在上面的模板中?我特意将它们设置在不同的网格行上以避免这种情况,为什么会这样呢?

Edit2 为了好玩,我创建了一个仅打印事件的sender / source / originalSource的处理程序,并将其附加到模板中的网格,边框和scrollviewer。

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

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)作为Source,然后使用contentPresenter(即:ScrollViewer)作为Source。

由于Merlin最宽松的裤子,我真的很想知道MS编程的头部是什么......

2 个答案:

答案 0 :(得分:0)

你在这里错过了冒泡活动。 http://msdn.microsoft.com/en-us/library/ms742806.aspx#routing_strategies

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

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

答案 1 :(得分:0)

哎呀,你确实发现了一种非常奇怪的行为。似乎可以将对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>