search
Japanese Chinese Nederlands Espanol Italiano Deutsch Francais Twitter Rss Feeds
MicrosoftArticlesForumsFAQs
C# .NET
VB.NET
Visual Studio .NET
ADO.NET
Xml / Xslt
VB 6.0
.NET CF
GDI+
LINQ
Deployment
Security
FoxPro
Silverlight / WPF
Entity Framework
RIA Services

Web ProgrammingArticlesForumsFAQs
JavaScript
ASP
ASP.NET
Web Services

Non-MicrosoftArticlesForumsFAQs
NHibernate
Perl
PHP
Ruby
Java
Linux / Unix
Apple
Open Source

DatabasesArticlesForumsFAQs
SQL Server
Access
Oracle
MySQL
Other Databases

OfficeArticlesForumsFAQs
Excel
Word
Powerpoint
Outlook
Publisher
Money

Operating SystemsArticlesForumsFAQs
Windows 7
Windows Server
Windows Vista
Windows XP
Windows Update
MAC
Linux / UNIX

Server PlatformsArticlesForumsFAQs
BizTalk
Site Server
Exhange Server
IIS
Transaction Server

Graphic DesignArticlesForumsFAQs
Macromedia Flash
Adobe PhotoShop
Expression Blend
Expression Design
Expression Web

OtherArticlesForumsFAQs
Subversion / CVS
Ask Dr. Dotnetsky
Active Directory
Networking
Uninstall Virus
Job Openings
Product Reviews
Search Engines
Resumes

 

WPF - XAML TabControl SelectionChanged Threading Errors


By Robbe Morris
Printer Friendly Version
View My Articles
13 Views
    

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 Morris
Robbe has been a Microsoft MVP in C# since 2004. He is also the co-founder of EggHeadCafe.com which provides .NET articles, book reviews, software reviews, and software download and purchase advice.

button
Article Discussion: WPF - XAML TabControl SelectionChanged Threading Errors
Robbe Morris posted at Friday, September 05, 2008 6:41 PM
Original Article