I’m working on a WPF TabControl whose last item is always a button to add a new tab, similar to Firefox:

The TabControl’s ItemSource is bound to an ObservableCollection, and adding an item to the collection via this “+” button works very well. The only problem I’m having is that, after having clicked the “+” tab, I cannot for the life of me set the newly created (or any other existing tab) to focus, and so when a tab is added, the UI looks like this:

To explain a bit how I’m achieving this “special” tab behavior, the TabControl is templated and its NewButtonHeaderTemplate has a control (Image in my case) which calls the AddListener Command in the view-model (only relevant code is shown):
<Window x:Class="AIS2.PortListener.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ais="http://www.leica-geosystems.com/xaml"
xmlns:l="clr-namespace:AIS2.PortListener"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4"
DataContext="{Binding Source={StaticResource Locator}>
<Window.Resources>
<ResourceDictionary>
<DataTemplate x:Key="newTabButtonHeaderTemplate">
<Grid>
<Image Source="..\Images\add.png" Height="16" Width="16">
</Image>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<cmd:EventToCommand
Command="{Binding Source={StaticResource Locator},
Path=PortListenerVM.AddListenerCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
</DataTemplate>
<DataTemplate x:Key="newTabButtonContentTemplate"/>
<DataTemplate x:Key="itemHeaderTemplate">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<DataTemplate x:Key="itemContentTemplate">
<l:ListenerControl></l:ListenerControl>
</DataTemplate>
<l:ItemHeaderTemplateSelector x:Key="headerTemplateSelector"
NewButtonHeaderTemplate="{StaticResource newTabButtonHeaderTemplate}"
ItemHeaderTemplate="{StaticResource itemHeaderTemplate}"/>
<l:ItemContentTemplateSelector x:Key="contentTemplateSelector"
NewButtonContentTemplate="{StaticResource newTabButtonContentTemplate}"
ItemContentTemplate="{StaticResource itemContentTemplate}"/>
</ResourceDictionary>
</Window.Resources>
<TabControl Name="MainTab" Grid.Row="2" ItemsSource="{Binding Listeners}"
ItemTemplateSelector="{StaticResource headerTemplateSelector}"
ContentTemplateSelector="{StaticResource contentTemplateSelector}"
SelectedItem="{Binding SelectedListener}">
</TabControl>
The AddListener command simply adds an item to the ObservableCollection which has for effect to update the TabControl’s ItemSource and add a new tab:
private ObservableCollection<Listener> _Listeners;
public ObservableCollection<Listener> Listeners
{
get { return _Listeners; }
}
private object _SelectedListener;
public object SelectedListener
{
get { return _SelectedListener; }
set
{
_SelectedListener = value;
OnPropertyChanged("SelectedListener");
}
}
public PortListenerViewModel()
{
// Place the "+" tab at the end of the tab control
var itemsView = (IEditableCollectionView)CollectionViewSource.GetDefaultView(_Listeners);
itemsView.NewItemPlaceholderPosition = NewItemPlaceholderPosition.AtEnd;
}
private RelayCommand _AddListenerCommand;
public RelayCommand AddListenerCommand
{
get
{
if (_AddListenerCommand == null)
_AddListenerCommand = new RelayCommand(param => this.AddListener());
return _AddListenerCommand;
}
}
public void AddListener()
{
var newListener = new TCPListener(0, "New listener");
this.Listeners.Add(newListener);
// The following two lines update the property, but the focus does not change
//this.SelectedListener = newListener;
//this.SelectedListener = this.Listeners[0];
}
But setting the SelectedListener property does not work, even though the TabControl’s SelectedItem is bound to it. It must have something to do with the order in which things get updated in WPF, because if I set a breakpoint in the SelectedListener’s set I can see the following happening:
this.Listeners.Add(newListener);this.SelectedListener = newListener;- SelectedListener
setgets called with correct Listener object - SelectedListener
setgets called with NewItemPlaceholder object (of type MS.Internal.NamedObject according to the debugger)
Is there a way that I can work around this issue? Do I have the wrong approach?
I think you are triggering two events when you click the new tab:
MouseLeftButtonDownandTabControl.SelectionChangedI think they’re both getting queued, then processing one at a time.
So your item is getting added, set as selected, and then before the re-draw occurs the SelectionChanged event occurs to change the selection to the
[+]tab.Perhaps try using the
Dispatcherto set theSelectedItemso it occurs after theTabControlchanges it’s selection. Or make it so if the user tries to switch to the NewTab, it cancels theSelectionChangedevent so the selected tab doesn’t actually change (of course, theSelectedTabwill be your NewItem since the MouseDown event will have occurred)When I did something like this in the past, I actually overwrote the TabControl Template to create the AddTab button as a
Button, not as aTabItem. I want to suggest doing that instead of using theNewItemPlaceholderin the first place, but I’ve never tried working with theNewItemPlaceholderso don’t really know if it’s better or worse than overwriting theTemplate.