Johannes Schacht
Johannes Schacht

Reputation: 1334

Event handler on parent catches event from child

Probably I have not really understood the event system in WPF.

I have a TabItem that has a header consisting of a TextBox and a Button. The TextBox is readonly. (In the real application it allows editing on double clicking, but that's irrelevant here.)

It was difficult to select a tab because the TextBox grabs the MouseLeftButtonDown event. I therefore added an event handler to the TabItem that brings it into foreground. However with that event handler the button no longer receives the event. Why does the button not get the event before the TabItem gets the event? I thought it bubbles from the leaves to the root of the logical tree.

Here's the XAML:

<Window x:Class="tt_WPF.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:tt_WPF"
    Title="MainWindow" SizeToContent="WidthAndHeight">
<TabControl x:Name="TC"></TabControl> 
</Window>  

Here's the code behind:

   public class myItem : TabItem
{
    public myItem(string name)
    {
        // Create horizontal StackPanel
        StackPanel sp = new StackPanel();
        sp.Orientation = Orientation.Horizontal;
        // Create a readonly TextBox
        TextBox tb = new TextBox(); 
        tb.Text = name;
        tb.IsReadOnly = true;
        // Create a Button with a simple command
        Button b = new Button();
        b.Content = "X";
        b.Click += Button_Click;
        // Add Button and TextBlock to StackPanel and StackPanel to this TabIten
        sp.Children.Add(tb);
        sp.Children.Add(b);
        this.Header = sp;
        this.Content = "This is " + name;
        // --> Here's the trouble: Install an event handler that brings the TabItem into foreground when clicked
        this.AddHandler(MouseLeftButtonDownEvent, new RoutedEventHandler(TabItem_MouseLeftButtonDownEvent), true);

    }
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        MessageBox.Show("Button X");
    }
    private void TabItem_MouseLeftButtonDownEvent(object sender, RoutedEventArgs e)
    {
        this.IsSelected = true;
    }
}

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        TC.Items.Add(new myItem("Tab 1"));
        TC.Items.Add(new myItem("Tab 2"));
    }
}

Upvotes: 1

Views: 1911

Answers (1)

Anatolii Gabuza
Anatolii Gabuza

Reputation: 6260

The thing you have faced is called event routing in WPF. To prevent event from being routed to the next level you have to set Handled = true. Also you can check for sender type which raised this event to filter out unnecessary calls.

private void TabItem_MouseLeftButtonDownEvent(object sender, RoutedEventArgs e)
{
    this.IsSelected = true;
    e.Handled = true;
}

There is a very nice diagram displaying events routing when it comes to user input (from MSDN):

Events routing

So to get desired bottom->up routing path you need to change behavior to Bubble instead of Tunnel by using PreviewLeftMouseButtonDown.

Upvotes: 3

Related Questions