WPF SelectionChanged events fire when visual children fire their SelectionChanged event

by Trent Guidry13. June 2009 09:53
For a WPF TabControl, the SelectionChanged event fires whenever any child that inherits from Selector, such as ListBox, ComboBox, TabControl, or ListView fires its SelectionChanged event.

To see this, consider the code below:

WPF

XAML

<Grid><TabControl Name="tbTopmost"><TabItem Header="Top Tab 1"><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="Auto"/><ColumnDefinition Width="*"/><ColumnDefinition Width="*"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions><TabControl Name="tbChild" Grid.Column="0"><TabItem Header="Child Tab 1"/><TabItem Header="Child Tab 2"/></TabControl><ListBox Grid.Column="1"><ListBoxItem>Item 1</ListBoxItem><ListBoxItem>Item 2</ListBoxItem><ListBoxItem>Item 3</ListBoxItem><ListBoxItem>Item 4</ListBoxItem><ListBoxItem>Item 5</ListBoxItem></ListBox><ComboBox Grid.Column="2" Height="26" VerticalAlignment="Top"><ComboBoxItem>Item1</ComboBoxItem><ComboBoxItem>Item2</ComboBoxItem><ComboBoxItem>Item3</ComboBoxItem><ComboBoxItem>Item4</ComboBoxItem><ComboBoxItem>Item5</ComboBoxItem></ComboBox><ListView Grid.Column="3"><ListView.View><GridView><GridViewColumn Header="Column"/></GridView></ListView.View><ListViewItem>Item1</ListViewItem><ListViewItem>Item2</ListViewItem><ListViewItem>Item3</ListViewItem><ListViewItem>Item4</ListViewItem><ListViewItem>Item5</ListViewItem></ListView></Grid></TabItem><TabItem Header="Top Tab 2"></TabItem></TabControl></Grid>
C#
using System.Windows;
using System.Windows.Controls;namespace WpfApplication3
{/// <summary>/// Interaction logic for Window1.xaml/// </summary>public partial class Window1 : Window{public Window1(){InitializeComponent();}private void Window_Loaded(object sender, RoutedEventArgs e){tbTopmost.SelectionChanged += new SelectionChangedEventHandler(tbTopmost_SelectionChanged);}void tbTopmost_SelectionChanged(object sender, SelectionChangedEventArgs e){MessageBox.Show("Top TabControl SelectionChanged fired.");}}
}

This basically consists of a TabControl with four child controls which are a TabControl, a ListBox, a ComboBox, and a ListView. The SelectionChanged event of the topmost TabControl is wired in the window load event to display a message box indicating that the event has been fired.

When this application is run, clicking tabs in the child TabControl, items in the ListBox, selecting an item in the ComboBox, or clicking an item in the ListView causes the message box in the top most TabControl SelectionChanged event handler to be displayed.

This is rather unintuitive since most would not expect the SelectionChanged event on the top most TabControl to be fired when the selection in the top most TabControl has not actually changed. A more colorful response to this issue can be found here.

Basically, this problem is occurring because the TabControl, ListBox, ComboBox, and ListView derive from Selector and the SelectionChanged event is inherited from Selector. So, the event being routed is actually Selector.SelectionChanged, not ComboBox.SelectionChanged, TabControl.SelectionChanged, etc.

When you wire up the SelectionChanged event on the ComboBox, TabControl, and other Selectors, they are wired up to listen for Selector.SelectionChanged events. When the child ComboBox or other Selector derived controls selection changes, the Selector.SelectionChanged event bubbles up the tree, which includes the parent TabControl.

Workarounds for this issue include either wiring up the child SelectionChanged event and adding e.Handled = true to stop the event from propagating up to the TabControl or checking the OriginalSource property of the event argument when the event is fired and ensuring that the parent TabControl was the source of the event and not a child that derives from Selector.

Even though I understand why this issue occurs, it is so very counter intuitive that I submitted it as a bug to Microsoft, unfortunately, they closed it saying that this behavior is by design.

Silverlight

XAML

<Grid x:Name="LayoutRoot"><sdk:TabControl Name="tbTopmost" SelectionChanged="tbTopmost_SelectionChanged"><sdk:TabItem Header="Top Tab 1"><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="Auto"/><ColumnDefinition Width="Auto"/><ColumnDefinition Width="Auto"/><ColumnDefinition Width="Auto"/><ColumnDefinition Width="Auto"/><ColumnDefinition Width="Auto"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions><sdk:TabControl Grid.Column="0"><sdk:TabItem Header="Child Tab 1"><Grid/></sdk:TabItem><sdk:TabItem Header="Child Tab 2"><Grid/></sdk:TabItem></sdk:TabControl><ListBox Grid.Column="1"><ListBoxItem>Item 1</ListBoxItem><ListBoxItem>Item 2</ListBoxItem><ListBoxItem>Item 3</ListBoxItem><ListBoxItem>Item 4</ListBoxItem><ListBoxItem>Item 5</ListBoxItem></ListBox><ComboBox Grid.Column="2" VerticalAlignment="Top" SelectedIndex="0"><ComboBoxItem>Item1</ComboBoxItem><ComboBoxItem>Item2</ComboBoxItem><ComboBoxItem>Item3</ComboBoxItem><ComboBoxItem>Item4</ComboBoxItem><ComboBoxItem>Item5</ComboBoxItem></ComboBox></Grid></sdk:TabItem><sdk:TabItem Header="Top Tab 2"><Grid/></sdk:TabItem></sdk:TabControl></Grid>
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;namespace BlogPostsSL
{public partial class MainPage : UserControl{public MainPage(){InitializeComponent();}void tbTopmost_SelectionChanged(object sender, SelectionChangedEventArgs e){MessageBox.Show("Top TabControl SelectionChanged fired.");}}
}

Interestingly, this problem does not occur in Silverlight. In Silverlight, the TabControls SelectionChanged event fires as expected. When either “Top Tab 1” or “Top Tab 2” is selected after the other has been selected, then the SelectionChanged event fires. This is the expected behavior. If items are changed in any of the child controls of the top level tab control, such as clicking on tabs in the left most tab control, or selecting items in the list box, or selecting an item in the combo box, then the top level SelectionChanged event does not fire. This is also the expected behavior since the top level tab control SelectionChanged event should only fire when the top level tab controls selection actually changes.

If I had to guess, Microsoft realized that the way they implemented the SelectionChanged event behavior in WPF was odd and strange after they released it to the public. After it was released to the public, they probably didn’t want to change that behavior because changing that behavior might break existing applications. With Silverlight, Microsoft of sorts had an opportunity to revise the way the SelectionChanged events behave and they took advantage of that opportunity to prevent the SelectionChanged event from firing when the selection of that control hasn’t actually changed.

Tags: , ,

Comments (2) -

BorodaAlex
BorodaAlexBelarus
10/25/2009 10:10:33 PM #

Thanks, this was useful.

Michael
MichaelUnited States
7/7/2010 12:13:28 PM #

Also ran into this very annoying problem.
Workaround as stated is to check OriginalSource in the event handlers. bleh.