views:

698

answers:

3

I am building a LOB application that has a main section and a TabControl with various TabItems in it. On hitting save the idea is that any fields in error are highlighted and the first field in error gets the focus.

If the first, and only, field in error is on an Unselected tab the tab should then become selected and the field in error should become highlighted and have focus. But I can not get this to work.

What appears to be happening is that the Unselected tab is not in the visual tree so you can't navigate back to the owning TabItem and make it the currently selected TabItem in the TabControl.

Has anyone got an idea on how this can be done\achieved?

A: 

I know a way, but it is ugly. It involves using a DispatcherTimer with an interval of a few milliseconds. In Page_Loaded you would start the timer. Then on each tick it sets IsSelected = true for one of the tab items. On the next tick it selects the next tab item etc. until all the tabs have been selected. Then you would have to select the first item again and kill the timer. This will force the visuals in the tab items to load.

You would also have to cover the TabControl with a border or something during this operation. Otherwise the user will see all the tab items quickly flicking past.

Henrik Söderlund
Yeah - not a pretty solution.
David Gray Wright
True. I use this solution myself, and it makes me cringe every time I look at the code. :) If I were to do it again I would encapsulate it in a behavior to get it out of sight. Unfortunately I cannot think of another way to make the visuals render.
Henrik Söderlund
A: 

How I solved it (by asking the Lead Architect)...

Create an Interface ITabActivator with one method Activate.

Create a class derived from Grid and ITabActivator called TabPageActivator. The constructor of which takes the TabITem and the TabControl.

Instead of adding a simple Grid to the TabItem.Contents add a TabPageActivator.

Change the Parent detection to use...

DependencyObject parent = _Control.Parent;

...instead of using the VisualTreeHelper.

So when you navigate the Hierarchy test for...

if ( parent is TabActivator ) (parent as ITabActivator).Activate( )

... so when Activate is called

m_TabControl.SelectedItem = m_TabItem; // From Constructor Parameters.

...and don't forget you may have nested tabs so you need to keep going up the Hierarchy.

David Gray Wright
Surely you see every tab being selected first, before your final validation tab displays?
Chris S
No it doesn't, it works fine. I was maybe a little too brief in my explanation of the solution. I can explain in more detail if you'd like?
David Gray Wright
A: 

I use TabControls for navigation on one of my sites (YinYangMoney) and built a few extension methods that help me to select tabs using Tag names. Here are snippets that should work for you.

The Extensions class:

using System;
using System.Linq;
using System.Windows.Controls;

namespace MyApplication
{
    internal static class Extensions
    {
        // Extension for TabControl
        internal static void SelectTab(this TabControl tabControl, this TabItem tabItem)
        {
            if (tabControl == null || tabItem == null)
                return null;

            SelectTab(tabControl, tabItem.Tag);
        }

        // Extension for TabControl
        internal static void SelectTab(this TabControl tabControl, string tabTagName)
        {
            if (tabControl == null)
                return null;

            // Find the TabItem by its Tag name
            TabItem mainTabItem = tabControl.FindByTag(tabTagName);
            if (mainTabItem == null)
                return;

            // Check to see if the tab needs to be selected
            if (tabControl.SelectedItem != mainTabItem)
                tabControl.SelectedItem = mainTabItem;
        }

        // Extension for TabControl
        internal static TabItem FindByTag(this TabControl tabControl, string tagFragment)
        {
            if (tabControl == null || tagFragment == null)
                return null;

            return tabControl.Items
                    .OfType<TabItem>()
                    .Where(item => item.Tag != null && item.Tag.ToString().StartsWithIgnoreCase(tagFragment))
                    .FirstOrDefault();
        }

        // Extension for string
        internal static bool StartsWithIgnoreCase(this string source, string target)
        {
            return source.StartsWith(target, StringComparison.CurrentCultureIgnoreCase);
        }
    }
}

The XAML for your TabControl and TabItems would look something like this:

<Controls:TabControl x:Name="x_TabControl">
    <Controls:TabItem Header="Welcome" Tag="/Home/Welcome" x:Name="x_WelcomeTab" />
    <Controls:TabItem Header="FAQ" Tag="/Home/FAQ" />
    <Controls:TabItem Header="Contact Us" Tag="/Home/Contact_Us" />
    <Controls:TabItem Header="Privacy Policy" Tag="/Home/Privacy_Policy" />
    <Controls:TabItem Header="My Account" Tag="/Home/My_Account" />
</Controls:TabControl>

And you can select the Welcome TabItem like so:

x_TabControl.SelectTab("/Home/Welcome");  

or

x_TabControl.SelectTab(x_WelcomeTab);
Jim McCurdy
The problem is finding the TabItem that is to be selected based on a control that will be shown within it.
David Gray Wright