Show SelectedIndex in WPF Tabcontrol header template

半城伤御伤魂 提交于 2019-12-21 17:08:36

问题


I have 1...n tabcontrols in my application, with the following XAML setup:

<TabControl Name="ordersTabControl" ItemsSource="{Binding CoilItems}">
  <TabControl.ItemTemplate>
    <DataTemplate DataType="models:Coil">
      <StackPanel>
        <TextBlock Text="{Binding CoilCode, StringFormat='Coil: {0}'}" />
        <TextBlock Text="{Binding ArticleCode, StringFormat='Auftrag: {0}'}" />
        <TextBlock Text="{Binding RestWeight, StringFormat='Restgewicht: {0} kg'}" />
      </StackPanel>
    </DataTemplate>
  </TabControl.ItemTemplate>
  <TabControl.ContentTemplate>
  [...]
  </TabControl.ContentTemplate>
</TabControl>

The amount of open tabs changes at runtime. Now I'd like to show an index in each tab (i.e. the first tab shows "Order 1", the second "Order 2" and so on) in addition to the information already in each header.

AFAIK when using DataTemplate I can't access the tab-properties through the code-behind, so is there any way in XAML to bind a textblock inside a tabheader to show the Index of that specific tab in the tabcontrol?

I think it should be possible with RelativeSource and FindAncestors? Alas I couldn't really find any clear tutorial on those settings (and I only started using WPF 2 days ago).


回答1:


I'm going to give you a solution using attached properties. Check the code:

Attached Properties

public static class IndexAttachedProperty
{


    #region TabItemIndex

    public static int GetTabItemIndex(DependencyObject obj)
    {
        return (int) obj.GetValue(TabItemIndexProperty);
    }

    public static void SetTabItemIndex(DependencyObject obj, int value)
    {
        obj.SetValue(TabItemIndexProperty, value);
    }

    // Using a DependencyProperty as the backing store for TabItemIndex.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty TabItemIndexProperty =
        DependencyProperty.RegisterAttached("TabItemIndex", typeof (int), typeof (IndexAttachedProperty),
                                            new PropertyMetadata(-1));



    #endregion

    #region TrackTabItemIndex

    public static bool GetTrackTabItemIndex(DependencyObject obj)
    {
        return (bool) obj.GetValue(TrackTabItemIndexProperty);
    }

    public static void SetTrackTabItemIndex(DependencyObject obj, bool value)
    {
        obj.SetValue(TrackTabItemIndexProperty, value);
    }

    // Using a DependencyProperty as the backing store for TrackTabItemIndex.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty TrackTabItemIndexProperty =
        DependencyProperty.RegisterAttached("TrackTabItemIndex", typeof (bool), typeof (IndexAttachedProperty),
                                            new PropertyMetadata(false, TrackTabItemIndexOnPropertyChanged));

    private static void TrackTabItemIndexOnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var tabControl = GetParent(d, p => p is TabControl) as TabControl;
        var tabItem = GetParent(d, p => p is TabItem) as TabItem;
        if (tabControl == null || tabItem == null)
            return;
        if (!(bool)e.NewValue)
            return;
        int index = tabControl.Items.IndexOf(tabItem.DataContext == null ? tabItem : tabItem.DataContext);
        SetTabItemIndex(d, index);
    }
    #endregion





    public static DependencyObject GetParent(DependencyObject item, Func<DependencyObject, bool> condition)
    {
        if (item == null)
            return null;
        return condition(item) ? item : GetParent(VisualTreeHelper.GetParent(item), condition);
    }
}

This code define two attached properties, the first one is to set if an item tracks the the tab item index in wich it is contained. The second one is the index property.

XAML Sample code:

        <TabControl.ItemTemplate>
            <DataTemplate DataType="{x:Type WpfApplication3:A}">
                <StackPanel x:Name="tabItemRoot" WpfApplication3:IndexAttachedProperty.TrackTabItemIndex ="True">
                    <TextBlock Text="{Binding Text}"/>
                    <TextBlock Text="{Binding Path=(WpfApplication3:IndexAttachedProperty.TabItemIndex), ElementName=tabItemRoot}"/>

                </StackPanel>
            </DataTemplate>
        </TabControl.ItemTemplate>

The above code is an example of using the attached property. You may adapt to your code easy.

Result:

Hope this code works for you...




回答2:


If you are not using AlternationCount property for other purposes, you can hack it for an easier solution.

bind AlternationCount like this

<TabControl AlternationCount="{Binding Path=Items.Count, RelativeSource={RelativeSource Self}}">

And then in your ItemTemplate bind the TextBlock or other control you wish to the AlternationIndex like this,

<TextBlock Text="{Binding Path=(ItemsControl.AlternationIndex), RelativeSource={RelativeSource  FindAncestor, AncestorType=TabItem}}" />

With a custom converter plugged into the above binding, you can display anything you want.




回答3:


Even if you had access to the just the TabItem properties in the code-behind, it wouldn't help as the tab doesn't know it's own index in the TabControl collection. This is true of all ItemsControls and seems annoying, but it makes sense because when can any object ever tell you what its own position is within a collection?

It can be done with IndexOf, however, as long as you have access to both the control's ItemsCollection and the content of the tab. We can do this in a MultiValueConverter, so that it can be done within the DataTemplate.

Converter code:

   public class ItemsControlIndexConverter : IMultiValueConverter
   {
      public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
      {
         ItemCollection itemCollection = (ItemCollection)values[0];
         return (itemCollection.IndexOf(values[1]) + 1).ToString();
      }

      public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
      {
         throw new NotImplementedException();
      }
   }

TabControl XAML:

<TabControl ItemsSource="{Binding CoilItems}">
    <TabControl.Resources>
        <local:ItemsControlIndexConverter x:Key="IndexConverter"/>
    </TabControl.Resources>
    <TabControl.ItemTemplate>
         <DataTemplate>
             <StackPanel Orientation="Horizontal">
                 <TextBlock>
                     <TextBlock.Text>
                         <MultiBinding Converter="{StaticResource IndexConverter}" StringFormat="Order {0}" Mode="OneWay">
                             <Binding RelativeSource="{RelativeSource AncestorType=TabControl}" Path="Items"/> <!-- First converter index is the ItemsCollection -->
                             <Binding /> <!-- Second index is the content of this tab -->
                         </MultiBinding>
                     </TextBlock.Text>
                 </TextBlock>
                 <!-- Fill in the rest of the header template -->
             </StackPanel>
         </DataTemplate>
     </TabControl.ItemTemplate>
 </TabControl>


来源:https://stackoverflow.com/questions/15553238/show-selectedindex-in-wpf-tabcontrol-header-template

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!