2

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

David
  • 6,014
  • 4
  • 39
  • 55

2 Answers2

0

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.

Snowbear
  • 16,924
  • 3
  • 43
  • 67
  • "Bubbling: Event handlers on the event source are invoked. The routed event then routes to successive parent elements until reaching the element tree root" < The label is not parent of the Border by any means, so why would the event bubble up for the border to the label? I understand why it bubbles up to the RichTextBox, which is the templatedParent, but how is the Label a parent of the Border here? – David Feb 17 '11 at 14:04
  • I edited my question with regards to your answer. I think you're on the right track, but this still does not explain everything (see the edit) – David Feb 17 '11 at 14:09
  • I agree with your edit. I'd really like to know what is going on and how I can possibly make this damn template work as supposed though... :/ – David Feb 17 '11 at 15:01
0

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>
Rick Sladkey
  • 33,988
  • 6
  • 71
  • 95
  • thanks for the suggestion of using a ControlTemplate. I marked this as best answer. I think I'll go for a UserControl on this fashion. And I filled a bug report on MS connect since I can't see how this can possibly be meant to behave like this (who knows, maybe MS will not ignore this one...) – David Feb 18 '11 at 08:08