WPF - XAML TabControl SelectionChanged Threading Errors


By Robbe Morris
Printer Friendly Version
View My Articles

  

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;
        }
      
    }
}


Biography
Robbe has been a Microsoft MVP in C# since 2004.  He is also the co-founder of EggHeadCafe. Robbe enjoys scuba diving with the folks at wet-n-fla.

button
 
Article Discussion: WPF - XAML TabControl SelectionChanged Threading Errors
Robbe Morris posted at 05-Sep-08 06:41
Original Article

promotion
Silverlight    WPF    WCF    WWF    LINQ   
JavaScript    AJAX    ASP.NET    XAML   
C#    VB.NET    VB 6.0    GDI+    IIS    XML   
.NET Generics    Anonymous Methods    Delegate   
Visual Studio .NET    Expression Blend    Virus   
Windows Vista    Windows XP    Windows Update   
Windows 2003 Server    Windows 2008 Server   
SQL Server    Microsoft Excel    Microsoft Word   
SharePoint    BizTalk    Virtual Earth   
.NET Compact Framework    Web Service   

"Everything" RSS / ATOM Feed Parser
How to send and receive messages through message queuing in .Net
How to Read text file as database
SQL Server 2005 Paging Performance Tip
Display code of web page.
Fully Scalable Excel File Importer class for .net using Microsoft Jet driver
Generic Chart Color Manager class that can be used for any charts
Helper class to style the infragistics wingrid
Using Reflection to detemine as Assembly Info in and out.
Helper class to play with Window (Owners and position)
Resolving displayname from the culture using the XmlLanguage and LanguageSpecificStringDictionary class
Manipulate file attributes in VB.NET
Forms Based Authentication Filtered Content Editor for SharePoint
How to create a Tree View of the Windows Folder and extract all the file-folder info.
How to use AssemblyInfo.cs file in win forms to provide much needed information on Assemblies
Sorting In Datagrid
Helper class to work with NativeMethods in the native api's
Silverlight Line Of Business Applications With Offline WPF Versions
C# : Database monitoring system using XML file
C# : Adding ComboBox to ListView SubItem
Sum of Numbers Captcha: Keeping it Simple
C# Create a Piechart for the specified Hard Disk Drive Utilization
Extension Methods for DataSet and DataTable that makes tasks easier
Accessing IIS Hosted WCF Services from PHP
Helper class that provides most commonly used Extension Methods for DateTime object
Helper class to work with a Status Bar in WPF.
Finding Unmatched Records in Dataset Tables Using Linq
Silverlight Toolkit: Autocomplete TextBox Stock Symbols and Chart
COOL Auto Complete textbox using javascript
Creating a Serializable Log Entry for Microsoft Enterprise Library to log to a Database
ASP.NET Searching Values in Datagrid