添加选项卡时,TabControl的SelectedItem会被NewItemPlaceholder覆盖

时间:2012-01-20 16:06:38

标签: wpf mvvm tabcontrol

我正在使用WPF TabControl,其最后一项始终是添加新标签的按钮,类似于Firefox: screenshot 1

TabControl的ItemSource绑定到ObservableCollection,并通过此“+”按钮向集合添加项目非常有效。我遇到的唯一问题是,在单击“+”选项卡后,我无法为我的生活设置新创建的(或任何其他现有选项卡)进行聚焦,因此当添加选项卡时,UI看起来像这样:

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添加一个项目,该项目有效更新TabControl的ItemSource并添加一个新选项卡:

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属性不起作用,即使TabControl的SelectedItem绑定到它。它必须与在WPF中更新内容的顺序有关,因为如果我在SelectedListener的set中设置了断点,我可以看到以下情况:

  1. this.Listeners.Add(newListener);
  2. this.SelectedListener = newListener;
  3. 使用正确的侦听器对象
  4. 调用SelectedListener set
  5. 使用NewItemPlaceholder对象调用SelectedListener set(根据调试器的类型为MS.Internal.NamedObject)
  6. 有没有办法可以解决这个问题?我有错误的方法吗?

2 个答案:

答案 0 :(得分:3)

我认为您在点击新标签时会触发两个事件:MouseLeftButtonDownTabControl.SelectionChanged

我认为他们都排队等候,然后一次处理一个。

因此,您的项目将被添加,设置为已选中,然后在重新绘制之前发生SelectionChanged事件,以将选择更改为[+]选项卡。

也许尝试使用Dispatcher来设置SelectedItem,以便在TabControl更改选择后发生SelectionChanged。或者,如果用户尝试切换到NewT​​ab,它会取消SelectedTab事件,因此选定的选项卡实际上不会更改(当然,{Mouse}因为事件,Button将是您的NewItem将会发生)

当我过去做过类似的事情时,我实际上覆盖了TabControl Template,将AddTab按钮创建为TabItem,而不是NewItemPlaceholder。我想建议这样做,而不是首先使用NewItemPlaceholder,但我从未尝试使用Template,所以不要真的知道它是否比覆盖{{更好或更差1}}。

答案 1 :(得分:1)

看一下有关哨兵对象的帖子:WPF Sentinel objects and how to check for an internal type 有几种方法可以解决它们的问题,该帖子提供了其中一个。