这个TabControl/TabItem的bug有没有解决方法?

3

除非我错了(但我希望我是错的),否则与TabControl有关的TabItems的可见性存在一个错误。 以下是可以重现此错误的XAML代码:

<UserControl x:Class="TabControl_bug.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    <Grid x:Name="LayoutRoot" Background="White">
        <sdk:TabControl>
            <sdk:TabItem Header="tabItem1"  Visibility="Collapsed">
                <TextBlock Text="TabItem1 which should not be visible" VerticalAlignment="Center" HorizontalAlignment="Center"/>
            </sdk:TabItem>
            <sdk:TabItem Header="tabItem2">
                <TextBlock Text="TabItem2 which should be visible" VerticalAlignment="Center" HorizontalAlignment="Center"/>
            </sdk:TabItem>
        </sdk:TabControl>
    </Grid>
</UserControl>

当您运行此代码时,您会发现TabItem2未被“选中”,因此显示的内容是

TabItem 1,它不应该可见

一旦您选择了选项卡,当然TabItem2的内容就会显示出来,没有办法返回tabItem1,但问题在于初始显示。
如果我将SelectedIndex属性设置为1,则会显示正确的内容。但是我不知道在XAML中哪个选项卡应该首先被选中。
针对这个问题有哪些解决方法?理想情况下,选项卡控件应预先选择其第一个可见的选项卡。

我们能澄清一些事情吗?我们知道默认选择的项目是第一个项目(索引0)。假设此时没有任何选项卡被折叠,我们是否仍然不确定“应该首先选择哪个选项卡”?还是默认值是正确的?如果默认值是正确的,那么如果默认值实际上被折叠了,那么下一个可用的选项卡应该被选择。由于您可以在Xaml中看到哪些被折叠,因此我们可以看到所选索引应该是什么。 - AnthonyWJones
如果我们不知道将要折叠的内容是什么,那一定是代码的结果,难道这段代码不应该设置SelectedIndex吗? - AnthonyWJones
1
解决方法:订阅TabControl的Loaded事件,查找第一个可见的TabItem并将其标记为选定状态。 - NestorArturo
1
@Nestor:听起来像是一个答案,为什么你把它发表为评论呢? - AnthonyWJones
NestorArturo,把那个作为答案,我会接受它,因为这就是我最终做的。 - Ralph Shillington
显示剩余3条评论
2个回答

1
我已经找到了下一个解决方案。对于你在MainPage构造函数的样例:
tabControl.SetValue(TabControl.SelectedContentProperty, null);

你也可以在Loaded事件中这样做。

不幸的是,TabControl.SelectedContent属性没有公共的setter,所以你不能直接设置SelectedContentProperty。

编辑:

此功能的行为:

public class UnselectContentBehavior : Behavior<TabControl>
{
        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.Loaded += OnLoaded;
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            AssociatedObject.SetValue(TabControl.SelectedContentProperty, null);
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.Loaded -= OnLoaded;
        }
 }

使用示例:

       <sdk:TabControl x:Name="tabControl">
            <sdk:TabItem Header="tabItem1"  Visibility="Collapsed">
                <TextBlock Text="TabItem1 which should not be visible" VerticalAlignment="Center" HorizontalAlignment="Center"/>
            </sdk:TabItem>
            <sdk:TabItem Header="tabItem2">
                <TextBlock Text="TabItem2 which should be visible" VerticalAlignment="Center" HorizontalAlignment="Center"/>
            </sdk:TabItem>

            <i:Interaction.Behaviors>
                <b:UnselectContentBehavior/>
            </i:Interaction.Behaviors>
        </sdk:TabControl> 

0

在尝试解决描述在这里的问题时,我遇到了这个问题。我找到了一个合理的解决方案,涵盖了两种情况。在TabControl中设置一个附加属性"SelectOnlyVisibleTabs"为true可以纠正其行为。为了在运行时更改TabItems的可见性,还需要使用附加的"Visibility"属性来防止TabControl的不良行为。完整的解决方案如下:

public static class TabControlExtensions
{
    /// <summary>
    /// Use this property on a TabControl to correct the behavior
    /// of selecting Collapsed TabItems.
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public static bool GetSelectOnlyVisibleTabs(DependencyObject obj)
    {
        return (bool)obj.GetValue(SelectOnlyVisibleTabsProperty);
    }
    public static void SetSelectOnlyVisibleTabs(DependencyObject obj, bool value)
    {
        obj.SetValue(SelectOnlyVisibleTabsProperty, value);
    }
    public static readonly DependencyProperty SelectOnlyVisibleTabsProperty =
        DependencyProperty.RegisterAttached("SelectOnlyVisibleTabs", typeof(bool), typeof(TabControlExtensions), new PropertyMetadata(false, SelectOnlyVisibleTabsChanged));
    public static void SelectOnlyVisibleTabsChanged(object sender, DependencyPropertyChangedEventArgs args)
    {
        var tabControl = sender as TabControl;
        if (tabControl == null) return;

        if ((bool)args.NewValue)
        {
            tabControl.SelectionChanged += TabControl_SelectionChanged;
            CorrectSelection(tabControl);
        }
        else
        {
            tabControl.SelectionChanged -= TabControl_SelectionChanged;
        }
    }

    private static void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs args)
    {
        var tabControl = sender as TabControl;
        if (tabControl == null) return;

        CorrectSelection(tabControl);
    }

    public static void CorrectSelection(TabControl tabControl)
    {
        var selected = tabControl.SelectedItem as UIElement;
        if (selected == null) return;

        // If the selected element is not suposed to be visible,
        // selects the next visible element
        if (selected.Visibility == System.Windows.Visibility.Collapsed)
            tabControl.SelectedItem = tabControl.Items.OfType<UIElement>()
                .Where(e => e.Visibility == System.Windows.Visibility.Visible)
                .FirstOrDefault();
    }
}

public static class TabItemExtensions
{
    /// <summary>
    /// Use this property in a TabItem instead of the original "Visibility" to 
    /// correct the behavior of a TabControl when a TabItem's Visibility changes.
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public static Visibility GetVisibility(DependencyObject obj)
    {
        return (Visibility)obj.GetValue(VisibilityProperty);
    }
    public static void SetVisibility(DependencyObject obj, Visibility value)
    {
        obj.SetValue(VisibilityProperty, value);
    }
    public static readonly DependencyProperty VisibilityProperty =
        DependencyProperty.RegisterAttached("Visibility", typeof(Visibility), typeof(TabItemExtensions), new PropertyMetadata(Visibility.Visible, VisibilityChanged));

    public static void VisibilityChanged(object sender, DependencyPropertyChangedEventArgs args)
    {
        var tabItem = sender as TabItem;
        if (tabItem == null) return;

        var visibility = (Visibility)args.NewValue;
        if (tabItem.Visibility == visibility) return;

        tabItem.Visibility = visibility;
        if (visibility == Visibility.Visible) return;

        // Finds the tab's parent tabcontrol and corrects the selected item, 
        // if necessary.
        var tabControl = tabItem.Ancestors().OfType<TabControl>().FirstOrDefault();
        if (tabControl == null) return;

        TabControlExtensions.CorrectSelection(tabControl);
    }
}

使用方法:

<sdk:TabControl local:TabControlExtensions.SelectOnlyVisibleTabs="True">
    <sdk:TabItem Header="tabItem1" Visibility="Collapsed">
        <TextBlock HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   Text="TabItem1 which should not be visible (1)" />
    </sdk:TabItem>
    <sdk:TabItem Header="tabItem2">
        <TextBlock HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   Text="TabItem2 which should be visible (2)" />
    </sdk:TabItem>
    <sdk:TabItem DataContext="{Binding ViewModel}"
                 Header="tabItem3"
                 local:TabItemExtensions.Visibility="{Binding MyProperty,
                                                             Converter={StaticResource BoolToVisibilityConverter}}">
        <TextBlock HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   Text="TabItem with binded Visibility (3)" />
    </sdk:TabItem>
</sdk:TabControl>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接