I've been trying to create a custom skin/template for a TabControl in WPF.
I want the tabs to be displayed in a ComboBox. When you select the item from the ComboBox, I want the content area of the tab control to display the TabItem contents.
Here's an image showing what I'm looking for:
I could do this using some sort of master-detail setup with data objects and templates, but the problem is I want to set up the controls using the TabControl XAML format, like this:
<TabControl Style="{DynamicResource ComboTabControlStyle}">
<TabItem Header="TabItem1">
<TextBlock Text="TabItem1 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
<TabItem Header="TabItem2">
<TextBlock Text="TabItem2 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
<TabItem Header="TabItem3">
<TextBlock Text="TabItem3 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
</TabControl>
Any thoughts or suggestions?
It is very easy to change the layout of the tab items using a different Panel, but a ComboBox is an ItemsControl, not a Panel.
I tried putting the ComboBox in the TabControl template and binding the ItemsSource of the ComboBox to the TabControl.Items property, but it didn't seem to work correctly.
I also tried creating a custom panel that only shows one "selected" item at a time and shows all of the items in a drop-down when you click on it (basically a "ComboBox" panel). I ran into trouble because visuals can only be in one place in the visual tree. So putting the children of the panel into a popup caused an exception to be thrown.
Anybody have any other ideas?
Thanks for the help!
It's surprisingly difficult to do what you're trying to do. This comes very close:
<DockPanel>
<ComboBox x:Name="ItemSelector" DockPanel.Dock="Top">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type TabItem}">
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<TabItem Header="TabItem1">
<TextBlock Text="TabItem1 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
<TabItem Header="TabItem2">
<TextBlock Text="TabItem2 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
<TabItem Header="TabItem3">
<TextBlock Text="TabItem3 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
</ComboBox>
<ContentPresenter Content="{Binding SelectedItem.Content, ElementName=ItemSelector}" DockPanel.Dock="Top"/>
<TextBlock/>
</DockPanel>
Where it breaks down, oddly, is displaying the selected item in the ComboBox: the ItemTemplate is ignored when rendering the item, so the selection box contains a TabItem. To fix this, you have to subclass ComboBox and implement a read/write SelectionBoxItemTemplate dependency property, because, for some reason that I'm sure is not as stupid as it seems to me at this moment, that property's read-only.
Create a new class, inherit from panel, put a combo box inside, do a lot of parent binding, and use it. it'll do the trick.
You must use custom class to write the tabs as they are regular tab items.
I found a solution.
I created a custom Control class (MasterDetailControl).
This class has two template parts:
[TemplatePart(Name = "PART_MasterSelector", Type = typeof(Selector))]
[TemplatePart(Name = "PART_DetailPresenter", Type = typeof(ContentPresenter))]
The control has an items dependency property:
public IList Items { ... }
I added a helper class:
[ContentProperty("Detail")]
public class MasterDetail
{
public object Master { get; set; }
public object Detail { get; set; }
}
Items that are placed in the Items DP are processed by the MasterDetailControl. If they are of type MasterDetail, the master is added to the selector items list. For other child item types, a new MasterDetail object is created with the object assigned to the master and detail fields. A separate list maintains all of the generated MasterDetail objects with indexes that correspond to those in the Selector control.
When the SelectionChanged event fires on the Selector object, I set the ContentPresenter's Content property to the Detail field of the item corresponding to the selected master object.
If anyone wants more details, feel free to comment.
In the end, I can now use this control with a simple control template specifying any selector object (ListBox, ComboBox, etc) and a ContentPresenter.
Related
I need some TabItems to have a customized Header.
For example, given the following (working) XAML:
<TabControl>
<TabItem>
<TabItem.Header>
<Button>Header 1</Button>
</TabItem.Header>
<Label>Content 1</Label>
</TabItem>
<TabItem>
<TabItem.Header>
<Label>Header 2</Label>
</TabItem.Header>
<Grid>
<TextBlock>Content 2</TextBlock>
</Grid>
</TabItem>
</TabControl>
I would like to extract the tab items into their own Views + ViewModels. The TabItem's View should still be a TabItem, so that I can configure the Header per tab item instead of setting TabControl.ItemTemplate and using a DataTemplateSelector to achieve different headers per tab item.
At the same time I'd need to be able to bind the selected tab item view model to a property ActiveItem. => The underlying view-model for the TabControl is a Conductor.Collection.OneActive<T> (only the selected tab should be activated).
If there's an alternative to using TabItem as view-type, but still achieving the Header and Content to be specified in the same view, it would be acceptable, too.
You should be able to achieve this by binding a TabControlViewModel to the TabControl, and that VM should have an ObservableCollection of TabViewModels (maybe a base class or interface). You would bind your collection of TabViewModels to the TabControl's ItemsSource. Here is my implementation, but using the Telerik TabControl (should be same for MS):
<telerik:RadTabControl x:Name="RadTabControl"
Grid.Row="0"
Align="Justify"
ContentTemplateSelector="{StaticResource LoggerDataTemplateSelector}"
IsContentPreserved="True"
IsDefaultItemSelected="True"
ItemsSource="{Binding LogHistory}"
SupressSelectedContentTemplateReapplying="False">
<telerik:RadTabControl.ItemContainerStyle>
<!-- Allow IsSelected to be bound to view models-->
<Style BasedOn="{StaticResource RadTabItemStyle}"
TargetType="{x:Type telerik:RadTabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</telerik:RadTabControl.ItemContainerStyle>
<telerik:RadTabControl.ItemTemplate>
<!-- Define what is shown in the header -->
<DataTemplate>
<Grid Height="30">
<TextBlock VerticalAlignment="Center"
Text="{Binding Title}" />
</Grid>
</DataTemplate>
</telerik:RadTabControl.ItemTemplate>
</telerik:RadTabControl>
Be aware, MS doesn't have something like IsContentPreserved, so switching tabs that have a lot of data to show will be rather timely. There are also a couple other properties not in MS TabControl, however the important properties should all be there. In this case, your TabViewModel should have a IsSelected property and Title property.
I have looked at most resources but i can find a good solution. I have a tab control HARD coded.
<TabControl TabStripPlacement="Left" Padding="0" Style="{DynamicResource SettingsTab}" ItemContainerStyle="{DynamicResource SettingsTabItemStyle}" Background="WhiteSmoke" >
<TabItem Header="ΓΕΝΙΚΑ" Margin="0" IsEnabled="False" > <Grid /></TabItem>
<TabItem Header="Προσωπικό" Margin="0" IsSelected="True">
<Grid MinHeight="400">
<ContentControl HorizontalAlignment="Stretch" Margin="50,67,50,0" Name="ActiveItem" />
</Grid>
</TabItem>
<TabItem Header="Τραπέζια" Margin="0">
<Grid />
</TabItem>
UPDATE - Restate problem
Here is my customized tab control. The gray text is a disabled tab item it acts like a group. Like General settings, System settings etc. So it has a role as a navigation menu. Curently i have a content control at each tabitem (not the disabled ones) and bind the view model i want.
But i cant use Conductor.Collection.OneActive with CM.
Why?
I have seen helloscreens example from CM sample and other samples but the problem here is that if i do this via binding then there is no way to display the disabled tabitems other than creating a dummy view model that serves no purpose.So how can i achieve this ?
Update: source here
I have a tab control HARD coded.
Is this a requirement?
But i cant use Conductor.Collection.OneActive with CM. Why?
I think the reason this won't work is to use Conductor.Collection.OneActive you need to bind the ItemsSource to the Items collection. If you're going to bind ItemsSource you can't also describe the tab items in XAML.
I created a solution that doesn't involve describing the tab items in xaml.
The key parts:
In ShellView the TabControl ItemContainerStyle is described to the tab items IsEnabled property can be bound to the view model.
<TabControl x:Name="Items"
Grid.Row="1"
TabStripPlacement="Left">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
ShellViewModel is similar but we're now activating the second view model because the first one is not enabled.
public class HeaderViewModel : BaseTabViewModel
{
public HeaderViewModel(string name)
{
DisplayName = name;
IsEnabled = false;
}
}
public ShellViewModel(Tab2ViewModel tab2ViewModel,
Tab3ViewModel tab3ViewModel
)
{
Items.Add (new HeaderViewModel ("ΓΕΝΙΚΑ"));
Items.Add(tab2ViewModel);
Items.Add(tab3ViewModel);
ActivateItem (tab2ViewModel);
}
Enable or disable the tab item in the constructor of the view model.
public Tab2ViewModel()
{
DisplayName = "Προσωπικό";
IsEnabled = true;
}
The un-styled result is that the first tab item is disabled and the next two are enabled.
I want to have complex combobox with checkboxes, text, and may be a thumbnail. I have already looked at the following links which helped me alot while building complex comboboxes.
http://blogs.microsoft.co.il/blogs/justguy/archive/2009/01/19/wpf-combobox-with-checkboxes-as-items-it-will-even-update-on-the-fly.aspx
Looking for a WPF ComboBox with checkboxes
But, I can not find a way to consume these complex usercontol in my application. I am new to WPF, so any kind of demonstration support would be highly appreciated.
Dean, I was looking a solution of how to bind in code behind file with following example mentioned in an SO post earlier.
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected}"
Width="20" />
<TextBlock Text="{Binding DayOfWeek}"
Width="100" />
</StackPanel>
</DataTemplate>
So the question is, Do I need DataTable or something else to bind my list of Checkboxes and Titles with this combobox template?
Thanks in Advance
A Combobox is an ItemsControl. All ItemsControls can be filled "hardcoded" with items or containers.
This adds a new entry in the combobox, and wrappes the string into an ItemsContainer, which is a ComboBoxItem.
<ComboBox>
<sys:string>Hello</string>
<ComboBox>
Here we create a combobox item directly, and add its content to a string with the value "Hello"
<ComboBox>
<ComboBoxItem Content="Hello"/>
<ComboBox>
Both look visually the same. Its important to understand that in the first case the ComboBox takes care of wrapping our, to the ComboBox unknown type string, into an ComboBoxItem, and uses a default DataTemplate to display it. The default DataTemplate will display a TextBlock and calls ToString() on the given data item.
Now to have dynamic data, we need a ObservableCollection with our data items.
class Employee
{
public BitmapSource Picture {get;set;}
public string Name{get;set}
}
ObservableCollection<Employee> employees;
myComboBox.ItemsSource = employees;
We have a DataClass called Employee, an observable Collection which holds many of our dataitem, and set this collection as the ItemsSource. From this point on, our Combobox listens to changes to this collection. Like adding and removing Employees and automatically takes care of wrapping the new Employee into a ComboBoxItem. Everything is done automatically. The only thing we need to do is to provide a proper DataTemplate. The combobox doesn't know how to "display" an employee and thats exactly what a DataTemplate is for.
<DataTemplate x:Key="employeeTemplate">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Picture}"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
We know an employee is wrapped in a ComboBoxItem, and the ComboBoxItem uses a provided Datatemplate to display its data, which means that inside the DataTemplate we can use Binding to access all properties on the data item.
Hope that helps you.
to just answer your question. all you need is a collection of an object with at least 2 public properties (IsSelected as bool and DayOfWeek as string) and just set these collection as the itemssource.
so all you need is a collection of such an object. just comment if you need an example.
ps: pls read through the www for wpf and binding to get the basics.
you could simple add the items directly
<ComboBox>
<ComboBox.Items>
<ComboBoxItem>
<TextBlock Text="test text" />
</ComboBoxItem>
<ComboBoxItem>
<CheckBox Content="test checkbox" />
</ComboBoxItem>
<ComboBoxItem>
<Button Content="test button" />
</ComboBoxItem>
</ComboBox.Items>
</ComboBox>
or if you want to use ItemsSource, the a DataTemplateSelector would be required
<ComboBox>
<ComboBox.ItemTemplateSelector>
<local:MyCustomTemplateSelector />
</ComboBox.ItemTemplateSelector>
</ComboBox>
here is a link that explains DataTemplateSelectors
http://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.itemtemplateselector.aspx
I have a User class and an Author class that extends User. I have an ObservableCollection<User> being displayed in a ListBox. For this, I have a DataTemplate to display each item and another to display each selected item. I also have a column of TextBoxes that are bound to the properties of the ListBox.SelectedItem property. So far, so good.
At the moment, I am displaying extra controls in the column and DataTemplates if the selected User is an Author and it all works fine, but I'm cheating. I have added an IsAuthor bool property into the User class so that I could bind to it and determine whether a User was an Author. I know it's wrong, but I couldn't work out any other way to do it, so my first question is how do you display extended classes differently from the base class? I tried a different DataTemplate for the type Author, but it never worked... maybe because the collection was of type User?
The second question is should I have all of the many TextBox controls in the column in a UserControl and change the Visibility of the Author related controls, or somehow put them in a DataTemplate and create one for each type? I am using the first method currently and the problem is that each control bound to an Author property is throwing errors (I can see them in the Output window in Visual Studio) when the currently selected item is not an Author.
I have a similar setup which uses data templates and it works just fine with inherited classes. This is how I did it.
<ListBox Name="UserList" ItemsSource="{Binding Path=Users}"
ItemTemplate="{StaticResource ShowUserName}"
SelectedItem="{Binding Path=SelectedUser, Mode=TwoWay}">
</ListBox>
<ContentControl Content="{Binding ElementName=UserList, Path=SelectedItem}"/>
In the Window.Resources section I have the following DataTemplates:
<DataTemplate x:Key="ShowTime" DataType="TestApp.User">
<TextBlock Text="{Binding Path=Name}" HorizontalAlignment="Center"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:User}">
<StackPanel Margin="10">
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text="{Binding Path=Age}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Author}">
<StackPanel Margin="10">
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text="{Binding Path=Age}"/>
<TextBlock Text="{Binding Path=FirstTitle}"/>
</StackPanel>
</DataTemplate>
The first template is what will be displayed in the list itself. We are referencing it by key in the ItemTemplate property of the listbox. The other two data templates are used by the content control when determining what to display for the selected item. When the selected item is just a User, the User DataTemplate will be displayed, if an author is selected, the author DataTemplate will be shown.
The x:Type local:Author is referring to the the class type. local should be declared in your namespace declarations.
xmlns:local="clr-namespace:TestApp"
Keep in mind that this is my namespace, you will have to specify the one you are using. And of course the data templates are just basic examples, presumably you will want to do something more tailored to your application.
However it might be irritating to have to define two separate Data templates that are almost exactly the same for your two classes. Although you certainly could. I do it in my own application (not in this example), because what I want to display for each type are vastly different.
So what might be useful is to create a common DataTemplate for all the User properties, and simply extend this DataTemplate for Authors. If you want to do that you could set up your templates this way:
<DataTemplate x:Key="UserTemplate">
<!-- show all the properties of the user class here -->
</DataTemplate>
<DataTemplate DataType="{x:Type local:User}">
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource UserTemplate}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Author}">
<StackPanel>
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource UserTemplate}"/>
<!-- show all the additional Author properties here -->
</StackPanel>
</DataTemplate>
So as you can see, both of the DataTemplates for User and for Author start out using the DataTemplate called "UserTemplate". But in the Author DataTemplate we will add Author specific properties.
I hope that helps.
What i am really trying to achieve is to full control the active TabItem by using the combobox as the navigation control.
Here is what ive got so far:
<TabControl Canvas.Left="26" Canvas.Top="27" Height="100" Name="TabControl1" Width="220">
<TabItem Header="TabItem1" x:Name="TabItem1">
<Grid />
</TabItem>
<TabItem Header="TabItem2" x:Name="TabItem2">
<Grid />
</TabItem>
</TabControl>
<ComboBox Canvas.Left="126" Canvas.Top="134" Height="23" Name="CmbTabs" Width="120"
ItemsSource="{Binding ElementName=TabControl1, Path=Items}"
SelectedValue="{Binding ElementName=TabControl1, Path=SelectedIndex}"
SelectedValuePath="TabIndex"
DisplayMemberPath="Header"/>
Still the only thing that actual works is the list that shows up when i press the togglebutton of the combobox.
Even selecting a tabitem name through the list does not do anything, it does not even update the selected value textbox of the combobox.
Any help ?
Edit:
Ok the answer of Steve Robbins worked fine for the "controling" issue.
What about the fact that selecting an item in the combobox drop down list does not update the value of the combobox? (the comboboxes textbox is still blank!!)
If you're trying to control the TabControl from the Combo then it looks a bit backwards to me.. if you change the SelectedIndex on the tab control to bind it to the combo it should work:
<TabControl Canvas.Left="26" Canvas.Top="27" Height="100" Name="TabControl1" Width="220" SelectedIndex="{Binding ElementName=CmbTabs, Path=SelectedIndex}">
<TabItem Header="TabItem1" x:Name="TabItem1">
<Grid />
</TabItem>
<TabItem Header="TabItem2" x:Name="TabItem2">
<Grid />
</TabItem>
</TabControl>
I started looking at this because of some problems I'm having with the combobox. Unfortunately, I haven't solved my problem but I can provide some additional insight and a workaround to this problem. First off, lets start with my changes to the original xaml.
<TabControl Height="100" Name="TabControl1" Width="220">
<TabItem Header="TabItem1" x:Name="TabItem1">
<TextBlock Text="TabItem1 Content" />
</TabItem>
<TabItem Header="TabItem2" x:Name="TabItem2">
<TextBlock Text="TabItem2 Content" />
</TabItem>
</TabControl>
<ComboBox Height="23" Name="CmbTabs" Width="120"
ItemsSource="{Binding ElementName=TabControl1, Path=Items}"
SelectedIndex="{Binding ElementName=TabControl1, Path=SelectedIndex}"
DisplayMemberPath="Name"
>
</ComboBox>
Notice that instead of creating a binding from the tab control to the ComboBox and vice versa we can create a TwoWay binding (the default in this case) between the SelectedIndex of the tab and combo controls. Next, let's add some content to the TabItems. At this point, similar to Steve's suggestion, we've fixed the "controling" problem. That is, changing the selected TabItem changes the selected ComboBox item (you'll have to trust me on this one or keep reading!) and changing the ComboBox changes the selected TabItem. Great!
The above xaml also changes DiplayMemberPath property to "Name". I think you will find that this eliminates hughdbrown's "weird result". Recall that the Header property (an object) is wrapped by a ContentPresenter. I believe that if no template is supplied the default behavior is to display the Header object as a string in a TextBlock. Thus, the "weird result" correctly reports that the TextBlock control does not contain a Header property.
Now let's make some changes to the previous ComboBox xaml.
<ComboBox Height="23" Name="CmbTabs" Width="120"
ItemsSource="{Binding ElementName=TabControl1, Path=Items}"
SelectedIndex="{Binding ElementName=TabControl1, Path=SelectedIndex}"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This actually produces an interesting result and makes use of the content we added to each TabItem. When this code runs, you'll notice that the ComboBox now displays the selected TabItem's content. Furthermore, the list now displays "System.Windows.Controls.TabItem..." for each TabItem. We can change the TextBlock binding to {Binding Header} and display the Header object but the ComboBox still displays the selected TabItem's content. As it is late on a Friday evening and there just is not enough beer in the world, I didn't look into possible reasons for this. However, I do have a workaround!
First, let's create a ValueConverter to convert the TabControl's Items collection to something we can use. Here's the code.
public class TabItemCollectionConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ItemCollection collection = value as ItemCollection;
IList<string> names = new List<string>();
foreach (TabItem ti in collection.SourceCollection)
{
names.Add(ti.Header.ToString());
}
return names;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
The converter simply creates a new collection from the TabControl's Items collection that contains the string-ized Header object from each TabItem. This works fine for simple Header objects but obviously has limitations. Now let's consider how we use this in the xaml.
<ComboBox Height="23" Name="CmbTabs" Width="120"
ItemsSource="{Binding ElementName=TabControl1, Path=Items, Converter={StaticResource ItemConverter}}"
SelectedIndex="{Binding ElementName=TabControl1, Path=SelectedIndex}"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Remember that a ValueConverter used in an ItemsSource binding returns a new collection. In this case we are converting the TabControl's Items collection to a string collection. Don't forget to create the converter StaticResource! It looks something like this.
<local:TabItemCollectionConverter x:Key="ItemConverter"/>
Now, using the converter, the whole ball of wax works as expected.
What still puzzles me is why the ComboBox displays the TabItem Headers in the list and the TabItem content as the selection. No doubt there is some obvious explanation but as I said, it's Friday...
Hope this helps!
I've been playing with this a lot. There is something about the source of the ComboBox's databinding changing after the selection is made. Or something.
I added the Diagnostics namespace:
xmlns:debug="clr-namespace:System.Diagnostics;assembly=WindowsBase"
And I changed your <Grid /> into TextBoxes with big honking numbers so that I could see that things really were changing:
<TabItem Header="TabItem1" x:Name="TabItem1">
<TextBlock Name="tb1" FontSize="24" Text="1" Width="100" Height="26" />
</TabItem>
And when I ran the app, I found that Diagnostics reports a weird result:
System.Windows.Data Error: 39 :
BindingExpression path error: 'Header'
property not found on 'object'
''TextBlock' (Name='tb1')'.
BindingExpression:Path=Header;
DataItem='TextBlock' (Name='tb2');
target element is 'TextBlock'
(Name=''); target property is 'Text'
(type 'String')
I tried to set the databinding once, in code, at startup:
public Window1()
{
InitializeComponent();
Binding positionBinding = new Binding("Items");
positionBinding.ElementName = "TabControl1";
positionBinding.Path = new PropertyPath("Items");
positionBinding.Mode = BindingMode.OneTime;
CmbTabs.SetBinding(ComboBox.ItemsSourceProperty, positionBinding);
CmbTabs.DisplayMemberPath = "Header";
}
And it still switches the CombobBox to show no selected item after the selection and change of TabItem is made. It's as if the DataContext is switched to the TabControl.TabItem.TextBlock after the TabControl changes selection.
So I don't exactly have an answer for you but I have some results for you to work on.
Bea Stollnitz has a good article on using this diagnostic technique. "How can I debug WPF bindings?"
Building off of hughdbrown's answer, I found this post on MSDN that describes your problem as a bug. You can reproduce it with this XAML (which has the opposite problem as your XAML):
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TabControl Name="TabControl1" Width="220" Height="100" Canvas.Left="26" Canvas.Top="27">
<TabItem x:Name="TabItem1" Header="TabItem1">
foobar
</TabItem>
<TabItem x:Name="TabItem2" Header="TabItem2">
fizzbuzz
</TabItem>
</TabControl>
<ComboBox Name="CmbTabs" Width="120" Height="23" Canvas.Left="126" Canvas.Top="134"
ItemsSource="{Binding ElementName=TabControl1, Path=Items}"
DisplayMemberPath="Length"
SelectedIndex="{Binding ElementName=TabControl1, Path=SelectedIndex}"/>
</Canvas>
As you can see the Length binding works fine except for in the dropdown, where it is going off the TabItem instead of the string inside.
I'm not sure it's ideal for your purposes, but you can get around it by being a little less elegant and reproducing your headers in ComboBoxItems:
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TabControl Name="TabControl1" Width="220" Height="100" Canvas.Left="26" Canvas.Top="27">
<TabItem x:Name="TabItem1" Header="TabItem1">
<Grid/>
</TabItem>
<TabItem x:Name="TabItem2" Header="TabItem2">
<Grid/>
</TabItem>
</TabControl>
<ComboBox Name="CmbTabs" Width="120" Height="23" Canvas.Left="126" Canvas.Top="134"
SelectedIndex="{Binding ElementName=TabControl1, Path=SelectedIndex}">
<ComboBoxItem>TabItem1</ComboBoxItem>
<ComboBoxItem>TabItem2</ComboBoxItem>
</ComboBox>
</Canvas>