WPF EventHandler发出错误的元素 [英] WPF EventHandler fired on the wrong Element

查看:110
本文介绍了WPF EventHandler发出错误的元素的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我很困惑:



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



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 =MainWindowHeight =350 Width =525>

< Window.Resources>
< Style TargetType =RichTextBox>
< Setter Property =Template>
< Setter.Value>
< ControlTemplate TargetType =RichTextBox>
< Grid Height =100Width =200>
< Grid.RowDefinitions>
< RowDefinition />
< RowDefinition />
< /Grid.RowDefinitions>
< Label Background =BlueGrid.Row =0> Label< / Label>
< Border PreviewMouseDown =Border_PreviewMouseDownBackground =RedGrid.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:

 使用System.Diagnostics; 
使用System.Windows;
使用System.Windows.Input;

命名空间测试
{
public partial class MainWindow:Window
{
public MainWindow()
{
InitializeComponent() ;
}

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

}
}

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



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



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



我检查了:如果我从边框的属性中删除 PreviewMouseDown =Border_PreviewMouseDown代码,则不会触发该事件



我在这里缺少什么?



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



提前感谢



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



Edit2 只是为了有趣,我创建了一个只有打印事件的发件人/来源/原始来源,并将其附加到模板中的网格,边框和滚动浏览器。



这是我当我得到的在垂直滚动条上单击ONCE(只有一次),例如:

  Clic  - 发件人:System.Windows.Controls.Grid -  OriginalSource:Microsoft.Windows.Themes.ScrollChrome  - 源:MyRichTextBox 
Clic - 发件人:System.Windows.Controls.Border - OriginalSource:Microsoft.Windows.Themes.ScrollChrome - 来源:MyRichTextBox
Clic - 发件人:System.Windows.Controls.ScrollViewer - OriginalSource:Microsoft.Windows.Themes.ScrollChrome - 来源:MyRichTextBox
Clic - 发件人:System.Windows.Controls.Grid - OriginalSource: Microsoft.Windows.Themes.ScrollChrome - 源:System.Windows.Controls.ScrollViewer
Cl ic - 发件人:System.Windows.Controls.Border - OriginalSource:Microsoft.Windows.Themes.ScrollChrome - 源:System.Windows.Controls.ScrollViewer
Clic - 发件人:System.Windows.Controls.ScrollViewer - OriginalSource:Microsoft.Windows.Themes.ScrollChrome - Source:System.Windows.Controls.ScrollViewer

这清楚地解决了事情:事件确实是隧道两次,由于某种原因,首先用TemplatedParent(即:RichtextBox)作为Source,然后使用contentPresenter(即:ScrollViewer)作为Source。



由Merlin最宽松的裤子,我真的很想知道通过编程这个...的MS开发人员的头脑。

解决方案

Gee,你发现了一个非常奇怪的行为。看来,作为 TextBoxBase 控件模板一部分的所有元素的鼠标处理可以被覆盖到文本内容元素,然后从那里传播/传播。因此,在富文本框控件模板中包含一个标签意味着它将参与富文本上的鼠标事件,就像它是富文本本身的一部分一样。



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

 < Grid> 
< StackPanel>
< TextBox Text =Some text>
< TextBox.Template>
< ControlTemplate TargetType =TextBox>
< StackPanel>
< Rectangle Fill =GreenWidth =200Height =50/>
< Border x:Name =RedBorderPreviewMouseDown =Border_PreviewMouseDownBackground =Red>
< AdornerDecorator x:Name =PART_ContentHost/>
< / Border>
< / StackPanel>
< / ControlTemplate>
< /TextBox.Template>
< / TextBox>
< ContentControl Content =某些文字>
< ContentControl.Template>
< ControlTemplate TargetType =ContentControl>
< StackPanel>
< Rectangle Fill =GreenWidth =200Height =50/>
< Border x:Name =RedBorderPreviewMouseDown =Border_PreviewMouseDownBackground =Red>
< TextBlock Text ={TemplateBinding Content}/>
< / Border>
< / StackPanel>
< / ControlTemplate>
< /ContentControl.Template>
< / ContentControl>
< / StackPanel>
< / Grid>


I am puzzled by this:

I have made a very simple example:

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 !");
      }

   }
}

now, as I explicitly put the PreviewMouseDown EventHandler on the Border and not on the label in my template, I expect that it will fire when I click on the (red) border of the control, but not when I click on the (blue) label.

However, the event is fired when I click on the (red) Border AND when I click on the (blue) label.

so why does the Label call an EventHandler that I explicitly attached to an other part of the controlTemplate (i.e.: the border)?

I've checked: If I remove the PreviewMouseDown="Border_PreviewMouseDown" code from the border's properties, the event is not fired on the label any more.

what am I missing here?

and what would be the right way to do? How can I design my controlTemplate so that the PreviewMouseDown Event is fired only by a sub-part of the templated control?

thanks in advance

Edit: following Snowbear's answer, I checked the originalSource of the event when I click on the Label. It is indeed the border. Why is this so? in what way is the border encapsulating the label in the template above? I specifically set them on different grid rows to avoid this, so how come?

Edit2 Just for the fun, I created a handler that only prints the sender/source/originalSource of the event, and I attached it in the template to The grid, the border and the scrollviewer.

Here Is what I get when I click ONCE (and only once) on the vertical scrollbar for instance:

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

this clearly settles the matter: The event is indeed tunnelled twice, for some reason, First with the TemplatedParent (i.e.: the RichtextBox) as Source, and Then with the contentPresenter (i.e.: the ScrollViewer) as Source.

By Merlin's most baggy pants, I really wonder what went through the head of the MS Dev that programmed this...

解决方案

Gee, you've uncovered a very strange behavior indeed. It appears that the mouse handling for all the elements that are part of a TextBoxBase control template can be revectored to the text content element and then tunnel/bubble from there. As a result, including a label in a rich text box control template means it will participate in mouse events on the rich text as though it were part of the rich text itself.

To work around this problem, you can use a ContentControl that includes the associated elements and then forwards its Content property to a "normal" TextBoxBase derived element. Here is a simplified XAML example. The first section reproduces the strange behavior in a simpler example and the second section shows how to use a ContentControl to work around the problem.

<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>

这篇关于WPF EventHandler发出错误的元素的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆