在 TabControl 中获取可见的 TabItems

Get visible TabItems in TabControl

我有一个可滚动的 TabControl TabItems/Header。我的 TabControl ItemsSources 绑定到 ObservableCollection。有什么方法可以在 TabControl 中获取可见的 TabItems。

假设我有 20 个 TabItem,只有 7 个可见,或者 10 个或更多,具体取决于用户执行的操作类型(例如减少 window)。如何以编程方式检索可见的 TabItems?

这是 XAML 代码:

<TabControl x:Name="tabControl"
                  ItemsSource="{Binding Data}" 
                  ScrollViewer.CanContentScroll="True"
                  Width="440" 
                  Height="350"
                  TabStripPlacement="Top" 
                  Background="LightGray" 
                  BorderBrush="Blue">
        <TabControl.Template>
          <ControlTemplate TargetType="TabControl">
            <Grid>
              <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition />
              </Grid.RowDefinitions>
              <ScrollViewer HorizontalScrollBarVisibility="Auto"  VerticalScrollBarVisibility="Hidden" >
                <TabPanel x:Name="HeaderPanel"
                          Panel.ZIndex ="1" 
                          KeyboardNavigation.TabIndex="1"
                          Grid.Column="0"
                          Grid.Row="0"
                          Margin="2,2,2,0"
                          IsItemsHost="true" />
              </ScrollViewer>
              <ContentPresenter x:Name="PART_SelectedContentHost"
                                SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                Margin="{TemplateBinding Padding}"
                                ContentSource="SelectedContent" Grid.Row="1" />
            </Grid>
          </ControlTemplate>
        </TabControl.Template>
      </TabControl>

      <Button x:Name="button" 
              Content="Add Items" 
              Margin="5" 
              HorizontalAlignment="Center" 
              VerticalAlignment="Center"
              Width="120" 
              Click="Button_Click" />

      <Button x:Name="button2" 
              Content="TabItems in View" 
              Margin="5" 
              HorizontalAlignment="Center" 
              VerticalAlignment="Center"
              Width="120" 
              Click="Button_Click2" />

下面是我的代码:

public partial class MainWindow : Window
  {
    private static int counter = 0;
    public List<TabItem> visibleItems = new List<TabItem>();
    public ObservableCollection<string> Data {  get;  set; }
    public MainWindow()
    {
      InitializeComponent();
      this.DataContext = this;

      this.Data = new ObservableCollection<string>()
      {
        "item" + ++counter,
        "item" + ++counter,
        "item" + ++counter,
        "item" + ++counter,
        "item" + ++counter,
        "item" + ++counter,
        "item" + ++counter,
        "item" + ++counter,
        "item" + ++counter,
        "item" + ++counter,
        "item" + ++counter,
        "item" + ++counter,
        "item" + ++counter,
        "item" + ++counter,
        "item" + ++counter,
        "item" + ++counter,
        "item" + ++counter,
        "item" + ++counter,
        "item" + ++counter
      };
    }

    
    private void Button_Click( object sender, RoutedEventArgs e )
    {
      // add tab item and reduce the TabControl window:
      this.Data.Add( "newItem" + ++counter );
      
      this.tabControl.Width = 330d;
    }

    private void Button_Click2( object sender, RoutedEventArgs e )
    {
      visibleItems = new List<TabItem>();
      foreach( var item in tabControl.Items )
      {
        TabItem tabItem = this.tabControl.ItemContainerGenerator.ContainerFromItem( item ) as TabItem;
        if( tabItem != null && tabItem.Visibility == Visibility.Visible )
        {
          visibleItems.Add( tabItem );
        }
      }

      Debug.WriteLine( $"{visibleItems.Count}" ); // always return 20 ...
    }
  }

Button_Click2 事件处理程序是我实现逻辑以检索可见 TabItems 的地方。但它不起作用。它总是 return 我的项目来源总数。

您将不得不迭代项目容器并累积项目宽度。然后获取TabPanel的ScrollViewer,根据ScrollViewer.HorizontalOffset(起始位置)和ScrollViewer.ViewportWidth(确定实际可见项)收集可见项。
关键是滚动查看器的宽度是以 DIP 而非项目来衡量的。当您可以确保所有项目容器具有相同的宽度时,该算法会更精确。

您可以在 How to: Find DataTemplate-Generated Elements 找到 FindVisualChild 的实现。

private void Button_Click2(object sender, RoutedEventArgs e)
{
  var scrollViewer = FindVisualChild<ScrollViewer>(this.tabControl);
  if (scrollViewer != null)
  {
    int startIndex = GetFirstVisibleItemIndex(scrollViewer.HorizontalOffset);
    double totalItemContainerWidth = 0;
    List<(object Item, TabItem ItemContainer)> visibleItems = GetVisibleItems(startIndex, scrollViewer.ViewportWidth);
  }
}

private int GetFirstVisibleItemIndex(double horizontalScrollViewerOffset)
{
  double totalItemContainerWidth = 0;
  int itemIndex = 0;
  while (totalItemContainerWidth < horizontalScrollViewerOffset)
  {
    var hiddenItemContainer = this.tabControl.ItemContainerGenerator.ContainerFromIndex(++itemIndex) as FrameworkElement;
    totalItemContainerWidth += hiddenItemContainer.ActualWidth;
  }

  return itemIndex;
}

// Returns a collection of tuples (item and item container tuples)
private List<(object Item, TabItem ItemContainer)> GetVisibleItems(int startIndex, double viewportWidth)
{
  var visibleItems = new List<(object Item, TabItem ItemContainer)>();
  double totalItemContainerWidth = 0;
  for (int currentVisibleItemIndex = startIndex; currentVisibleItemIndex < this.tabControl.Items.Count; currentVisibleItemIndex++)
  {
    var visibleItemContainer = this.tabControl.ItemContainerGenerator.ContainerFromIndex(currentVisibleItemIndex) as TabItem;
    totalItemContainerWidth += visibleItemContainer.ActualWidth;
    if (totalItemContainerWidth > viewportWidth + visibleItemContainer.ActualWidth)
    {
      break;
    }

    object visibleItem = this.tabControl.Items[currentVisibleItemIndex];
    visibleItems.Add((visibleItem, visibleItemContainer));
  }

  return visibleItems;
}