WPF - XAML TabControl SelectionChanged Threading Errors

The following code sample offers a simplistic approach to dealing with threading issues while trying to launch ModalDialogs in the SelectionChanged event of a TabControl, ListBox, or ComboBox.

Most of us come from the .NET Windows Forms world where you can easily incorporate validation in various events of user interface controls such as the TabControl, ListBox, ComboBox, etc...  If you are reading this article, then you've no doubt discovered that WPF controls do not like having their events interupted with other user interface dialogs such as MessageBox confirmations or other dialogs.  You don't have an ability to cancel the event either.

In Microsoft speak, we call taking steps backward with commonplace capabilities when incorporating new technologies "Microsoft Innovation".

There are options that include launching a delegate via a Dispatcher but that isn't always a solution that solves our problem.  This little code sample provides are more simplistic way of dealing with the problem by avoiding the SelectionChanged event altogether.

All I've done was wire up the PreviewMouseDown event to trigger to find the tab the user is clicking and calling a my own method to alter the selectedIndex rather than rely on the SelectionChanged event.  This simple technique then permits me to incorporate back/next buttons or even remote selection of tabs from other dialogs will still processing the same validation.  You can also now determine not only the clicked on tab but the previous tab as well.  In many cases, you need to validate if you can leave tab A and validate whether you are allowed to see tab B.

As you can see below, no rocket science here:

  Download Source Code

<Window x:Class="WPFTutorial.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="518" Width="658" WindowStartupLocation="CenterScreen">
    
    
     <TabControl IsSynchronizedWithCurrentItem="True" x:Name="MainTabControl" Margin="0,5,0,0" >
            <TabItem Header="Tab1">
               <StackPanel VerticalAlignment="Top">  
                <StackPanel Orientation="Horizontal" VerticalAlignment="Top">  
                  <Label Name="Tab1Label1" Margin="3,3,3,3" Height="25" 
                            VerticalAlignment="Top" >Password:</Label> 
                  <TextBox Name="Password" Height="30" Width="200" 
                            VerticalAlignment="Center" VerticalContentAlignment="Center"  />
                </StackPanel>
                 <StackPanel VerticalAlignment="Top">  
                  <Label Name="Tab1Label2" Margin="3,10,3,3" Height="Auto" 
                         VerticalAlignment="Top" >Type in a password other than "eggheadcafe".  Then, try to skip to tab 2.</Label> 
                </StackPanel>
                </StackPanel>
            </TabItem>
            <TabItem Header="Tab2">
               <Label Name="Tab2Label1">This is tab 2</Label> 
            </TabItem>
   </TabControl>
   
</Window>

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Windows.Threading;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Diagnostics; 

namespace WPFTutorial
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
 
        private int _lastTabIndex = 0;

        public Window1()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(Window1_Loaded);
            this.MainTabControl.PreviewMouseDown += 
                     new MouseButtonEventHandler(MainTabControl_PreviewMouseDown);
        }
 
        private void Window1_Loaded(object sender, RoutedEventArgs e)
        {
            this.Password.Focus(); 
        }
 
        private void MainTabControl_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {

            TabItem tabItem = e.Source as TabItem;

            if (tabItem == null) { return; }

            TabControl tabControl = sender as TabControl;

            if (tabControl.SelectedItem == e.Source) { return; }

            for (int i = 0; i < MainTabControl.Items.Count; i++)
            {
                TabItem item = (TabItem)MainTabControl.Items[i];

                if (item.Header.ToString() != tabItem.Header.ToString()) { continue; }
            
                e.Handled = true;
                SelectMainTabControlIndex(i);
                return;
                
            }

        }
 
        private bool SelectMainTabControlIndex(int selectedIndex)
        {
            
            TabItem tab = null;
            string password = string.Empty; 

            try
            {

                 // Get the tab we are on
                 tab = this.MainTabControl.Items[_lastTabIndex] as TabItem;

                 // Perform some validation before leaving

                 #region Validate Tab 1
                 if (_lastTabIndex == 0)
                 {
                     password = this.Password.Text.Trim();

                     if (password.Length < 1)
                     {
                         MessageBox.Show("Password is required prior to navigating to tab 2.");
                         return false;
                     }
                     if (password != "eggheadcafe")
                     {
                         MessageBox.Show("The correct password is required prior to navigating to tab 2.");
                         return false;
                     }
                 }
                 #endregion
 
                 // Get the tab we are going to
                 tab = this.MainTabControl.Items[selectedIndex] as TabItem;

                 // Perform some validation before allowing tab to be selected.
 
                this.MainTabControl.SelectedIndex = selectedIndex;

                _lastTabIndex = this.MainTabControl.SelectedIndex;

                // Launch some method for the next tab if needed
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            return true;
        }
      
    }
}
By Robbe Morris   Popularity  (4273 Views)
Picture
Biography - Robbe Morris
Robbe has been a Microsoft MVP in C# since 2004. He is also the co-founder of NullSkull.com which provides .NET articles, book reviews, software reviews, and software download and purchase advice.  Robbe also loves to scuba dive and go deep sea fishing in the Florida Keys or off the coast of Daytona Beach. Microsoft MVP