问题
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