Reputation: 1699
I have a TabControl
on a UserControl
backed by a ViewModel, and the Visibility
of one of the tab items is bound to a property on the ViewModel.
<TabControl x:Name="myTabControl">
<TabItem Header="Tab 1" />
<TabItem Header="Tab 2" Visibility="{Binding HasData, Converter={StaticResource boolToVisibilityConverter}}"/>
</TabControl>
When the Visibility
of the TabItem
changes, it collapses (hides) the TabItem
header, but it continues displaying its content.
I want the TabControl
to switch to the visible tab when the other tab is hidden, and was a little surprised to find out it doesn't happen automatically.
Attaching an event handler to the SelectionChanged
event of the TabControl
shows that TabItem.IsSelected
(and TabControl.SelectedItem
) is not even affected when the TabItem.Visibility
changes (is this a bug?!).
I've tried both a property trigger:
<!-- This doesn't compile because of TargetName on the Setter, think you can only use it in Control Templates.
I don't know how to refer to the parent TabControl from within the TabItem style. -->
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}" BasedOn="{StaticResource {x:Type TabItem}}">
<Style.Triggers>
<Trigger Property="Visibility" Value="Collapsed">
<Setter TargetName="myTabControl" Property="SelectedIndex" Value="0" />
</Trigger>
</Style.Triggers>
</Style>
</TabControl.ItemContainerStyle>
and a data trigger:
<!-- This doesn't quite work, it affects the Visibility of the TabItem's content too -->
<TabControl.Style>
<Style TargetType="{x:Type TabControl}" BasedOn="{StaticResource {x:Type TabControl}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItem.Visibility, ElementName=tabControl}"
Value="Collapsed">
<Setter Property="SelectedIndex" Value="0" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
I can't get the triggers to work, and there's no VisibilityChanged
event I can handle, so I'm kind of stuck and would appreciate some help.
Upvotes: 11
Views: 8894
Reputation: 91
You could add this event handler to the code behind. It will test your control in the first place and on changes to tab visibility due to bindings.
Instead of doing this OnLoaded of course it makes total sense to put this into an attached Property. (AutoSelect?) . The code is the same. You get called in the first place and attach events to IsVisibleChanged. Then the only trick is to use a lambda (Parameter binding) to get the TabControl instance into the event callback. I am posting this solution, because it is shorter.
private void FrameworkElement_OnLoaded(object sender, RoutedEventArgs e)
{
var tabControl = (TabControl) sender;
// register visibility changed to react on changes
foreach (TabItem item in tabControl.Items)
{
item.IsVisibleChanged += (mSender, ev) => item_IsVisibleChanged(mSender, ev, tabControl);
}
// if current selected tab is invisible, find and select first visible one.
if (!((TabItem) tabControl.SelectedItem).IsVisible)
{
foreach (TabItem item in tabControl.Items)
{
if (item.IsVisible)
{
tabControl.SelectedItem = item;
return;
}
}
}
}
private static void item_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e, TabControl tabControl)
{
// just became IsVisible = false
if ((bool)e.NewValue == false)
{
if (tabControl == null) return;
ItemCollection items = tabControl.Items;
foreach (UIElement item in items)
{
if (item.IsVisible)
{
tabControl.SelectedItem = item;
return;
}
}
}
}
Upvotes: 1
Reputation: 41
Bind SelectedIndex of TabControl to a property. And change the value of this property to the index of tab which you want to display whenever you change the visibility to collapse of tab item.
Upvotes: 3