WPF EventHandler fired on the wrong Element

一个人想着一个人 提交于 2020-01-11 11:22:52

问题


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


回答1:


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>



回答2:


You're missing bubbling events here. http://msdn.microsoft.com/en-us/library/ms742806.aspx#routing_strategies

You can check RoutedEventArgs.OriginalSource to determine which border was clicked.

UPDATE: Ok, it looks like I missed, but not completely. I've played for some time with your sample, it looks like RichTextBox (or probably TextBoxBase) does something with PART_ContentHost which forces tunnelling events to go towards this part. Your sample behaves well out of RichTextBox so I assume it does something with own template. Though I do not how to investigate it further without going into .Net sources.



来源:https://stackoverflow.com/questions/5029820/wpf-eventhandler-fired-on-the-wrong-element

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!