111

In WPF, is there an event that can be used to determine when a TabControl's selected tab changes?

I have tried using TabControl.SelectionChanged but it is getting fired many times when a child's selection within a tab is changed.

skeletank
  • 2,880
  • 5
  • 43
  • 75
Jon Kragh
  • 4,529
  • 5
  • 26
  • 26

10 Answers10

143

You need to check the event's source to isolate the outer-most TabControl you are looking for.

I tied this in the handler to make it work:

void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (e.Source is TabControl)
    {
        // do work when tab is changed
    }
}
Aurumaker72
  • 33
  • 1
  • 7
Jon Kragh
  • 4,529
  • 5
  • 26
  • 26
93

If you set the x:Name property to each TabItem as:

<TabControl x:Name="MyTab" SelectionChanged="TabControl_SelectionChanged">
    <TabItem x:Name="MyTabItem1" Header="One"/>
    <TabItem x:Name="MyTabItem2" Header="2"/>
    <TabItem x:Name="MyTabItem3" Header="Three"/>
</TabControl>

Then you can access to each TabItem at the event:

private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (MyTabItem1.IsSelected)
    // do your stuff
    if (MyTabItem2.IsSelected)
    // do your stuff
    if (MyTabItem3.IsSelected)
    // do your stuff
}
Elmo
  • 6,409
  • 16
  • 72
  • 140
unexpectedkas
  • 931
  • 6
  • 2
61

If you just want to have an event when a tab is selected, this is the correct way:

<TabControl>
    <TabItem Selector.Selected="OnTabSelected" />
    <TabItem Selector.Selected="OnTabSelected" />
    <TabItem Selector.Selected="OnTabSelected" />
    <!-- You can also catch the unselected event -->
    <TabItem Selector.Unselected="OnTabUnSelected" />
</TabControl>

And in your code

    private void OnTabSelected(object sender, RoutedEventArgs e)
    {
        var tab = sender as TabItem;
        if (tab != null)
        {
            // this tab is selected!
        }
    }
MicBig
  • 721
  • 5
  • 2
  • Unfortunately as nice as this one looks, I don't get the Selected property available to me in xaml, just the IsSelected. Sorry. – PHenry Mar 28 '14 at 21:05
  • 2
    I stand corrected....kind of. DOH! When I try to type about the above in VS, it gives me the red squigglies, hence I thought it was wrong. BUT when I cut'n'pasted it in and just blindly F5'd it, to my astonishment, IT WORKED. HUH?! Why did it work THAT way? – PHenry Mar 28 '14 at 21:13
  • 2
    How can I access "Selector.Selected" event in code instead of xaml – Ahmed_Faraz Feb 14 '20 at 07:08
  • 2
    @Ahmed_Faraz: `someTabItem.AddHandler(Selector.SelectedEvent, ....` – Ben Voigt Jul 28 '21 at 17:03
15

You could still use that event. Just check that the sender argument is the control you actually care about and if so, run the event code.

Nidonocu
  • 12,476
  • 7
  • 42
  • 43
8

If you're using the MVVM pattern then it is inconvenient (and breaks the pattern) to use the event handler. Instead, you can bind each individual TabItem's Selector.IsSelected property to a dependency property in your viewmodel and then handle the PropertyChanged event handler. That way you know exactly which tab was selected/deselected based on the PropertyName and you have a special handler for each tab.

Example: MainView.xaml

<TabControl>
 <TabItem Header="My tab 1" Selector.IsSelected="{Binding IsMyTab1Selected}"> ... </TabItem>
 <TabItem Header="My tab 2" Selector.IsSelected="{Binding IsMyTab2Selected}"> ... </TabItem>
</TabControl>

Example: MainViewModel.cs

public bool IsMyTab1Selected {
 get { return (bool)GetValue(IsMyTab1SelectedProperty); }
 set { SetValue(IsMyTab1SelectedProperty, value); }
}
public static readonly DependencyProperty IsMyTab1SelectedProperty =
DependencyProperty.Register("IsMyTab1Selected", typeof(bool), typeof(MainViewModel), new PropertyMetadata(true, new PropertyChangedCallback(MyPropertyChanged)));

public bool IsMyTab2Selected {
 get { return (bool)GetValue(IsMyTab2SelectedProperty); }
 set { SetValue(IsMyTab2SelectedProperty, value); }
}
public static readonly DependencyProperty IsMyTab2SelectedProperty =
DependencyProperty.Register("IsMyTab2Selected", typeof(bool), typeof(MainViewModel), new PropertyMetadata(false, new PropertyChangedCallback(MyPropertyChanged)));

private void MyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
 if (e.Property.Name == "IsMyTab1Selected") {
  // stuff to do
 } else if (e.Property.Name == "IsMyTab2Selected") {
  // stuff to do
 }
}

If your MainViewModel is INotifyPropertyChanged rather than DependencyObject, then use this instead:

Example: MainViewModel.cs

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

public MainViewModel() {
 PropertyChanged += handlePropertyChanged;
}

public bool IsMyTab1Selected {
 get { return _IsMyTab1Selected ; }
 set {
  if (value != _IsMyTab1Selected ) {
   _IsMyTab1Selected = value;
   OnPropertyChanged("IsMyTab1Selected ");
  }
 }
}
private bool _IsMyTab1Selected = false;

public bool IsMyTab2Selected {
 get { return _IsMyTab2Selected ; }
 set {
  if (value != _IsMyTab2Selected ) {
   _IsMyTab2Selected = value;
   OnPropertyChanged("IsMyTab2Selected ");
  }
 }
}
private bool _IsMyTab2Selected = false;

private void handlePropertyChanged(object sender, PropertyChangedEventArgs e) {
 if (e.PropertyName == "IsMyTab1Selected") {
  // stuff to do
 } else if (e.PropertyName == "IsMyTab2Selected") {
  // stuff to do
 }
}
Nikola Novak
  • 4,091
  • 5
  • 24
  • 33
4

The event generated is bubbling up until it is handled.

This xaml portion below triggers ui_Tab_Changed after ui_A_Changed when the item selected in the ListView changes, regardless of TabItem change in the TabControl.

<TabControl SelectionChanged="ui_Tab_Changed">
  <TabItem>
    <ListView SelectionChanged="ui_A_Changed" />
  </TabItem>
  <TabItem>
    <ListView SelectionChanged="ui_B_Changed" />
  </TabItem>
</TabControl>

We need to consume the event in ui_A_Changed (and ui_B_Changed, and so on):

private void ui_A_Changed(object sender, SelectionChangedEventArgs e) {
  // do what you need to do
  ...
  // then consume the event
  e.Handled = true;
}
rolling
  • 41
  • 1
4

That is the correct event. Maybe it's not wired up correctly?

<TabControl SelectionChanged="TabControl_SelectionChanged">
    <TabItem Header="One"/>
    <TabItem Header="2"/>
    <TabItem Header="Three"/>
</TabControl>

in the codebehind....

private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    int i = 34;
}

if I set a breakpoint on the i = 34 line, it ONLY breaks when i change tabs, even when the tabs have child elements and one of them is selected.

Muad'Dib
  • 28,542
  • 5
  • 55
  • 68
  • put a grid in the tab, selecting a grid row will bubble up to the tab selected event if not handled before it gets there. – Paul Swetz Mar 23 '18 at 14:10
2

This code seems to work:

    private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        TabItem selectedTab = e.AddedItems[0] as TabItem;  // Gets selected tab

        if (selectedTab.Name == "Tab1")
        {
            // Do work Tab1
        }
        else if (selectedTab.Name == "Tab2")
        {
            // Do work Tab2
        }
    }
Mc_Topaz
  • 31
  • 1
0

If you're doing mvvm, you can also do this by binding to the SelectedIndex Property of TabControl:

<TabControl SelectedIndex="{Binding SelectedTabIndex}"

Then you get notified of the change when the Property updates, and you immediately get the index of the selected TabItem.

Craig Colomb
  • 3
  • 1
  • 3
-2

If anyone use WPF Modern UI,they cant use OnTabSelected event.but they can use SelectedSourceChanged event.

like this

<mui:ModernTab Layout="Tab" SelectedSourceChanged="ModernTab_SelectedSourceChanged" Background="Blue" AllowDrop="True" Name="tabcontroller" >

C# code is

private void ModernTab_SelectedSourceChanged(object sender, SourceEventArgs e)
    {
          var links = ((ModernTab)sender).Links;

          var link = this.tabcontroller.Links.FirstOrDefault(l => l.Source == e.Source);

          if (link != null) {
              var index = this.tabcontroller.Links.IndexOf(link);
              MessageBox.Show(index.ToString());
          }            
    }
Sandun Harshana
  • 721
  • 1
  • 13
  • 28