SelectedItem объекта TabControl перезаписывается держателем NewItemPlaceholder при добавлении вкладки - PullRequest
4 голосов
/ 20 января 2012

Я работаю над WPF TabControl, последним элементом которого всегда является кнопка для добавления новой вкладки, похожая на Firefox: screenshot 1

ItemSource TabControl привязан к ObservableCollection, и добавление элемента в коллекцию с помощью этой кнопки «+» работает очень хорошо. Единственная проблема, с которой я сталкиваюсь, заключается в том, что после нажатия на вкладку «+» я не могу на всю жизнь установить вновь созданную (или любую другую существующую вкладку) фокусировку, и поэтому при добавлении вкладки пользовательский интерфейс выглядит так:

screenshot 2

Чтобы объяснить немного, как я добиваюсь этого "особого" поведения вкладок, TabControl имеет шаблон, и его NewButtonHeaderTemplate имеет элемент управления (в моем случае Image), который вызывает команду AddListener в модели представления (только соответствующий код показано ниже):

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

Команда AddListener просто добавляет элемент в ObservableCollection, который имеет эффект для обновления ItemSource TabControl и добавления новой вкладки:

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];
}

Но установка свойства SelectedListener не работает, даже если к нему привязан SelectedItem объекта TabControl. Это должно быть как-то связано с порядком, в котором все обновляется в WPF, потому что, если я установлю точку останова в SelectedListener set, я смогу увидеть следующее:

  1. this.Listeners.Add(newListener);
  2. this.SelectedListener = newListener;
  3. SelectedListener set вызывается с правильным объектом Listener
  4. SelectedListener set вызывается с объектом NewItemPlaceholder (типа MS.Internal.NamedObject согласно отладчику)

Есть ли способ, которым я могу обойти эту проблему? У меня неправильный подход?

Ответы [ 2 ]

3 голосов
/ 20 января 2012

Я думаю, что вы запускаете два события при нажатии на новую вкладку: MouseLeftButtonDown и TabControl.SelectionChanged

Я думаю, что они оба ставятся в очередь, а затем обрабатывают по одному.

Таким образом, ваш элемент добавляется, устанавливается как выбранный, а затем перед повторным рисованием происходит событие SelectionChanged, чтобы изменить выбор на вкладку [+].

Возможно, попробуйте использовать Dispatcher, чтобы установить SelectedItem так, чтобы это произошло после того, как TabControl изменит свой выбор. Или сделайте так, если пользователь пытается переключиться на NewTab, он отменяет событие SelectionChanged, чтобы выбранная вкладка фактически не изменилась (конечно, SelectedTab будет вашим NewItem, поскольку произошло событие MouseDown)

Когда я делал что-то подобное в прошлом, я на самом деле переписал шаблон TabControl , чтобы создать кнопку AddTab как Button, а не как TabItem. Я хочу предложить сделать это вместо использования NewItemPlaceholder, но я никогда не пробовал работать с NewItemPlaceholder, поэтому не знаю, лучше это или хуже, чем перезаписывать Template.

1 голос
/ 20 января 2012

Посмотрите на этот пост, касающийся сторожевых объектов: WPF Sentinel объекты и как проверить внутренний тип Есть несколько способов обойти проблемы с ними, этот пост предлагает один из них.

...