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:
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!
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>
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>
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?"