TabControl SelectedItem is overwritten by NewItemPlaceholder when adding a tab

I am working on a WPC TabControl, whose last element is always a button for adding a new tab similar to Firefox: screenshot 1

The ItemSource TabControl is bound to an ObservableCollection, and adding the item to the collection using this โ€œ+โ€ button works very well. The only problem that I encountered is that after clicking on the โ€œ+โ€ tab I canโ€™t set my newly created (or any other existing tab) focus for life, and therefore, when the tab is added, the user interface looks like this way:

screenshot 2

To explain a little how I achieve this โ€œspecialโ€ behavior of tabs, TemControl templates and its NewButtonHeaderTemplate have a control (image in my case) that calls the AddListener command in the view model (only the corresponding 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 ObservableCollection element, which has the effect of updating the ItemControl TabControl element and adding 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, although the TabControl SelectedItem is bound to it. It should have something to do with the order in which things are updated in WPF, because if I set a breakpoint in the SelectedListener set , I see the following:

  • this.Listeners.Add(newListener);
  • this.SelectedListener = newListener;
  • SelectedListener set receives a call with the correct Listener object
  • The SelectedListener set is called with the NewItemPlaceholder object (of type MS.Internal.NamedObject according to the debugger)

Is there a way I can get around this problem? Am I getting the wrong approach?

+4
source share
2 answers

I think you are triggering two events when you click on a new tab: MouseLeftButtonDown and TabControl.SelectionChanged

I think they both end up in a queue and then process one at a time.

So, your element is added, set as the selected one, and then, before the second draw, the SelectionChanged event occurs to change the selection to the [+] tab.

Perhaps try using Dispatcher to set the SelectedItem so that it appears after TabControl changed its selection. Or do it if the user tries to switch to NewTab, he cancels the SelectionChanged event so that the selected tab does not actually change (of course, SelectedTab will be your NewItem, since the MouseDown event will occur)

When I did something similar in the past, I actually rewritten the TabControl template to create an AddTab button as a Button , and not as a TabItem . I want to propose doing this instead of using NewItemPlaceholder in the first place, but I have never tried working with NewItemPlaceholder , so I donโ€™t know if this is better or worse than rewriting Template .

+3
source

Take a look at this post regarding sentinel objects : WPF Sentinel objects and how to check the internal type There are several ways to get around problems with them, this post offers one of them.

+1
source

Source: https://habr.com/ru/post/1392073/


All Articles