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?
To load the TabItem:
tabControl.SelectedItem = tabItemOfInterest;
tabControl.UpdateLayout();
This causes the tabItemOfInterest to load alongwith all the contained controls within the TabItem.
The below line alone does not loads the tabItemOfInterest:
tabControl.SelectedItem = tabItemOfInterest;
I would, however, be very interested in the approach David adopted to get to the erroneous control.
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.
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);
My solution using attached property TabItem. Create class TabItemExtender:
/// <summary>
/// TabItem Extender class with TabItem property
/// </summary>
public class TabItemExtender
{
#region property getters/setters
/// <summary>
/// TabItem attached dependency property
/// </summary>
public static readonly DependencyProperty TabItemProperty = DependencyProperty.RegisterAttached("TabItem", typeof(TabItem), typeof(TabItemExtender), null);
/// <summary>
/// TabItem Property getter
/// </summary>
public static TabItem GetNavigateUri(DependencyObject source)
{
return (TabItem)source.GetValue(TabItemExtender.TabItemProperty);
}
/// <summary>
/// TabItem Property setter
/// </summary>
public static void SetNavigateUri(DependencyObject target, TabItem value)
{
target.SetValue(TabItemExtender.TabItemProperty, value);
}
#endregion
}
Next do this on TabControl load event:
private void ExtendedTabControl_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
foreach (object item in this.Items)
{
var tabItem = item as TabItem;
if (tabItem != null && tabItem.Content != null)
{
var element = (FrameworkElement)tabItem.Content;
element.SetValue(TabItemExtender.TabItemProperty, tabItem);
}
}
}
and this before setting focus:
var element = (UIElement)control;
while (element != null)
{
//Get TabItem
var tabItem = (TabItem)element.GetValue(TabItemExtender.TabItemProperty);
if (tabItem != null)
{
if (!tabItem.IsSelected && tabItem.IsEnabled)
{
tabItem.IsSelected = true;
((TabControl)tabItem.Parent).UpdateLayout();
}
break;
}
element = (UIElement)VisualTreeHelper.GetParent(element);
}
control.Focus();
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.
精彩评论