带有添加新标签按钮的TabControl(+)

时间:2010-08-12 14:39:58

标签: wpf button tabs tabcontrol

在WPF中选项卡控件的选项卡条中的所有选项卡项末尾添加“+”按钮选项卡的正确方法是什么?

  1. 它应该可以正常使用多个标签页行。
  2. 它应该位于所有标签项的末尾
  3. 制表符循环应该正常工作( Alt + 制表符),也就是说,应跳过+标签。
  4. 我不应该修改我绑定的源集合。也就是说,控件应该是可重用的。
  5. 解决方案应与MVVM
  6. 一起使用

    Enter image description here

    enter image description here

    更准确地说,按钮应该完全显示为附加的最后一个选项卡,而不是作为所有标签条行右侧某处的单独按钮。

    我只是在寻找这样做的一般方法。

    谷歌抛出了很多例子,但是如果你深入挖掘它们,它们都不能满足上述五点。

8 个答案:

答案 0 :(得分:33)

使用IEditableCollectionView的几乎完整的解决方案:

ObservableCollection<ItemVM> _items;
public ObservableCollection<ItemVM> Items
{
    get
    {
        if (_items == null)
        {
            _items = new ObservableCollection<ItemVM>();
            var itemsView = (IEditableCollectionView)CollectionViewSource.GetDefaultView(_items);
            itemsView.NewItemPlaceholderPosition = NewItemPlaceholderPosition.AtEnd;
        }

        return _items;
    }
}

private DelegateCommand<object> _newCommand;
public DelegateCommand<object> NewCommand
{
    get
    {
        if (_newCommand == null)
        {
            _newCommand = new DelegateCommand<object>(New_Execute);
        }

        return _newCommand;
    }
}

private void New_Execute(object parameter)
{
    Items.Add(new ItemVM());
}
<DataTemplate x:Key="newTabButtonContentTemplate">
    <Grid/>
</DataTemplate>

<DataTemplate x:Key="newTabButtonHeaderTemplate">
    <Button Content="+"
        Command="{Binding ElementName=parentUserControl, Path=DataContext.NewCommand}"/>
</DataTemplate>

<DataTemplate x:Key="itemContentTemplate">
    <Grid/>
</DataTemplate>

<DataTemplate x:Key="itemHeaderTemplate">
    <TextBlock Text="TabItem_test"/>
</DataTemplate>

<vw:TemplateSelector x:Key="headerTemplateSelector"
                           NewButtonTemplate="{StaticResource newTabButtonHeaderTemplate}"
                           ItemTemplate="{StaticResource itemHeaderTemplate}"/>

<vw:TemplateSelector x:Key="contentTemplateSelector"
                            NewButtonTemplate="{StaticResource newTabButtonContentTemplate}"
                            ItemTemplate="{StaticResource itemContentTemplate}"/>

<TabControl ItemsSource="{Binding Items}"
        ItemTemplateSelector="{StaticResource headerTemplateSelector}"
        ContentTemplateSelector="{StaticResource contentTemplateSelector}"/>
public class TemplateSelector : DataTemplateSelector
{
    public DataTemplate ItemTemplate { get; set; }
    public DataTemplate NewButtonTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item == CollectionView.NewItemPlaceholder)
        {
            return NewButtonTemplate;
        }
        else
        {
            return ItemTemplate;
        }
    }
}

Enter code here

它几乎完成了,因为标签循环没有跳过'+'标签,并且会显示空内容(这不是很好,但我可以忍受它,直到更好的解决方案出现......)。

答案 1 :(得分:4)

我在视图模型中使用了选项卡控件模板的修改并绑定到 AddNewItemCommand 命令。 XAML

<TabControl x:Class="MyNamespace.MyTabView"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            ItemsSource="{Binding MyItemSource}"
            SelectedIndex="{Binding LastSelectedIndex}"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Control.Template>
        <ControlTemplate TargetType="{x:Type TabControl}">
            <Grid ClipToBounds="true"
                  SnapsToDevicePixels="true"
                  KeyboardNavigation.TabNavigation="Local">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition x:Name="ColumnDefinition0" />
                    <ColumnDefinition x:Name="ColumnDefinition1"
                                      Width="0" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition x:Name="RowDefinition0"
                                   Height="Auto" />
                    <RowDefinition x:Name="RowDefinition1"
                                   Height="*" />
                </Grid.RowDefinitions>
                <StackPanel Grid.Column="0"
                            Grid.Row="0"
                            Orientation="Horizontal"
                            x:Name="HeaderPanel">
                    <TabPanel x:Name="_HeaderPanel"
                              IsItemsHost="true"
                              Margin="2,2,2,0"
                              KeyboardNavigation.TabIndex="1"
                              Panel.ZIndex="1" />
                    <Button Content="+"
                            Command="{Binding AddNewItemCommand}" />
                </StackPanel>

                <Border x:Name="ContentPanel"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}"
                        Background="{TemplateBinding Background}"
                        Grid.Column="0"
                        KeyboardNavigation.DirectionalNavigation="Contained"
                        Grid.Row="1"
                        KeyboardNavigation.TabIndex="2"
                        KeyboardNavigation.TabNavigation="Local">
                    <ContentPresenter x:Name="PART_SelectedContentHost"
                                      ContentSource="SelectedContent"
                                      Margin="{TemplateBinding Padding}"
                                      SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                </Border>
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger Property="TabStripPlacement"
                         Value="Bottom">
                    <Setter Property="Grid.Row"
                            TargetName="HeaderPanel"
                            Value="1" />
                    <Setter Property="Grid.Row"
                            TargetName="ContentPanel"
                            Value="0" />
                    <Setter Property="Height"
                            TargetName="RowDefinition0"
                            Value="*" />
                    <Setter Property="Height"
                            TargetName="RowDefinition1"
                            Value="Auto" />
                    <Setter Property="Margin"
                            TargetName="HeaderPanel"
                            Value="2,0,2,2" />
                </Trigger>
                <Trigger Property="TabStripPlacement"
                         Value="Left">
                    <Setter Property="Orientation"
                            TargetName="HeaderPanel"
                            Value="Vertical" />
                    <Setter Property="Grid.Row"
                            TargetName="HeaderPanel"
                            Value="0" />
                    <Setter Property="Grid.Row"
                            TargetName="ContentPanel"
                            Value="0" />
                    <Setter Property="Grid.Column"
                            TargetName="HeaderPanel"
                            Value="0" />
                    <Setter Property="Grid.Column"
                            TargetName="ContentPanel"
                            Value="1" />
                    <Setter Property="Width"
                            TargetName="ColumnDefinition0"
                            Value="Auto" />
                    <Setter Property="Width"
                            TargetName="ColumnDefinition1"
                            Value="*" />
                    <Setter Property="Height"
                            TargetName="RowDefinition0"
                            Value="*" />
                    <Setter Property="Height"
                            TargetName="RowDefinition1"
                            Value="0" />
                    <Setter Property="Margin"
                            TargetName="HeaderPanel"
                            Value="2,2,0,2" />
                </Trigger>
                <Trigger Property="TabStripPlacement"
                         Value="Right">
                    <Setter Property="Orientation"
                            TargetName="HeaderPanel"
                            Value="Vertical" />
                    <Setter Property="Grid.Row"
                            TargetName="HeaderPanel"
                            Value="0" />
                    <Setter Property="Grid.Row"
                            TargetName="ContentPanel"
                            Value="0" />
                    <Setter Property="Grid.Column"
                            TargetName="HeaderPanel"
                            Value="1" />
                    <Setter Property="Grid.Column"
                            TargetName="ContentPanel"
                            Value="0" />
                    <Setter Property="Width"
                            TargetName="ColumnDefinition0"
                            Value="*" />
                    <Setter Property="Width"
                            TargetName="ColumnDefinition1"
                            Value="Auto" />
                    <Setter Property="Height"
                            TargetName="RowDefinition0"
                            Value="*" />
                    <Setter Property="Height"
                            TargetName="RowDefinition1"
                            Value="0" />
                    <Setter Property="Margin"
                            TargetName="HeaderPanel"
                            Value="0,2,2,2" />
                </Trigger>
                <Trigger Property="IsEnabled"
                         Value="false">
                    <Setter Property="Foreground"
                            Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Control.Template>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="5" />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
                <TextBlock Text="{Binding Caption}" />
                <Button Content="x"
                        Grid.Column="2"
                        VerticalAlignment="Top"/>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</TabControl>

相关视图模型中的代码如下所示:

public ICommand AddNewItemCommand
{
    get
    {
        return new DelegateCommand((param) =>
        {
            MyItemSource.Add(CreateMyValueViewModel());
        },
        (param) => MyItemSource != null);
    }
}

注意:我用StackPanel包裹TabPanel翻转&#34; +&#34;按钮与 TabPanel 一起关于属性值&#34; TabStripPlacement &#34;。没有继承而您的视图中没有code-behind

答案 2 :(得分:2)

我相信我已经提出了一个完整的解决方案,我开始使用NVM的解决方案来创建我的模板。然后引用DataGrid源代码来提供一个扩展的TabControl,它能够添加和删除项目。

<强> ExtendedTabControl.cs

public class ExtendedTabControl : TabControl
{
    public static readonly DependencyProperty CanUserAddTabsProperty = DependencyProperty.Register("CanUserAddTabs", typeof(bool), typeof(ExtendedTabControl), new PropertyMetadata(false, OnCanUserAddTabsChanged, OnCoerceCanUserAddTabs));

    public bool CanUserAddTabs
    {
        get { return (bool)GetValue(CanUserAddTabsProperty); }
        set { SetValue(CanUserAddTabsProperty, value); }
    }

    public static readonly DependencyProperty CanUserDeleteTabsProperty = DependencyProperty.Register("CanUserDeleteTabs", typeof(bool), typeof(ExtendedTabControl), new PropertyMetadata(true, OnCanUserDeleteTabsChanged, OnCoerceCanUserDeleteTabs));

    public bool CanUserDeleteTabs
    {
        get { return (bool)GetValue(CanUserDeleteTabsProperty); }
        set { SetValue(CanUserDeleteTabsProperty, value); }
    }

    public static RoutedUICommand DeleteCommand
    {
        get { return ApplicationCommands.Delete; }
    }

    public static readonly DependencyProperty NewTabCommandProperty = DependencyProperty.Register("NewTabCommand", typeof(ICommand), typeof(ExtendedTabControl));

    public ICommand NewTabCommand
    {
        get { return (ICommand)GetValue(NewTabCommandProperty); }
        set { SetValue(NewTabCommandProperty, value); }
    }

    private IEditableCollectionView EditableItems
    {
        get { return (IEditableCollectionView)Items; }
    }

    private bool ItemIsSelected
    {
        get
        {
            if (this.SelectedItem != CollectionView.NewItemPlaceholder)
                return true;

            return false;
        }
    }

    private static void OnCanExecuteDelete(object sender, CanExecuteRoutedEventArgs e)
    {
        ((ExtendedTabControl)sender).OnCanExecuteDelete(e);
    }

    private static void OnCanUserAddTabsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((ExtendedTabControl)d).UpdateNewItemPlaceholder();
    }

    private static void OnCanUserDeleteTabsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // The Delete command needs to have CanExecute run.
        CommandManager.InvalidateRequerySuggested();
    }

    private static object OnCoerceCanUserAddTabs(DependencyObject d, object baseValue)
    {
        return ((ExtendedTabControl)d).OnCoerceCanUserAddOrDeleteTabs((bool)baseValue, true);
    }

    private static object OnCoerceCanUserDeleteTabs(DependencyObject d, object baseValue)
    {
        return ((ExtendedTabControl)d).OnCoerceCanUserAddOrDeleteTabs((bool)baseValue, false);
    }

    private static void OnExecutedDelete(object sender, ExecutedRoutedEventArgs e)
    {
        ((ExtendedTabControl)sender).OnExecutedDelete(e);
    }

    private static void OnSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue == CollectionView.NewItemPlaceholder)
        {
            var tc = (ExtendedTabControl)d;

            tc.Items.MoveCurrentTo(e.OldValue);
            tc.Items.Refresh();
        }
    }

    static ExtendedTabControl()
    {
        Type ownerType = typeof(ExtendedTabControl);

        DefaultStyleKeyProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(typeof(ExtendedTabControl)));
        SelectedItemProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(OnSelectionChanged));

        CommandManager.RegisterClassCommandBinding(ownerType, new CommandBinding(DeleteCommand, new ExecutedRoutedEventHandler(OnExecutedDelete), new CanExecuteRoutedEventHandler(OnCanExecuteDelete)));
    }

    protected virtual void OnCanExecuteDelete(CanExecuteRoutedEventArgs e)
    {
        // User is allowed to delete and there is a selection.
        e.CanExecute = CanUserDeleteTabs && ItemIsSelected; 
        e.Handled = true;
    }

    protected virtual void OnExecutedDelete(ExecutedRoutedEventArgs e)
    {
        if (ItemIsSelected)
        {
            int indexToSelect = -1;

            object currentItem = e.Parameter ?? this.SelectedItem;
            if (currentItem == this.SelectedItem)
                indexToSelect = Math.Max(this.Items.IndexOf(currentItem) - 1, 0);

            if (currentItem != CollectionView.NewItemPlaceholder)
                EditableItems.Remove(currentItem);

            if (indexToSelect != -1)
            {
                // This should focus the row and bring it into view. 
                SetCurrentValue(SelectedItemProperty, this.Items[indexToSelect]);
            }
        }

        e.Handled = true;
    }

    protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
    {
        base.OnItemsSourceChanged(oldValue, newValue);

        CoerceValue(CanUserAddTabsProperty);
        CoerceValue(CanUserDeleteTabsProperty);

        UpdateNewItemPlaceholder();
    }

    protected override void OnSelectionChanged(SelectionChangedEventArgs e)
    {
        if (Keyboard.FocusedElement is TextBox)
            Keyboard.FocusedElement.RaiseEvent(new RoutedEventArgs(LostFocusEvent));

        base.OnSelectionChanged(e);
    }

    private bool OnCoerceCanUserAddOrDeleteTabs(bool baseValue, bool canUserAddTabsProperty)
    {
        // Only when the base value is true do we need to validate
        // that the user can actually add or delete rows. 
        if (baseValue)
        {
            if (!this.IsEnabled)
            {
                // Disabled TabControls cannot be modified. 
                return false;
            }
            else
            {
                if ((canUserAddTabsProperty && !this.EditableItems.CanAddNew) || (!canUserAddTabsProperty && !this.EditableItems.CanRemove))
                {
                    // The collection view does not allow the add or delete action.
                    return false;
                }
            }
        }

        return baseValue;
    }

    private void UpdateNewItemPlaceholder()
    {
        var editableItems = EditableItems;

        if (CanUserAddTabs)
        {
            // NewItemPlaceholderPosition isn't a DP but we want to default to AtEnd instead of None
            // (can only be done when canUserAddRows becomes true).  This may override the users intent
            // to make it None, however they can work around this by resetting it to None after making
            // a change which results in canUserAddRows becoming true.
            if (editableItems.NewItemPlaceholderPosition == NewItemPlaceholderPosition.None)
                editableItems.NewItemPlaceholderPosition = NewItemPlaceholderPosition.AtEnd;
        }
        else
        {
            if (editableItems.NewItemPlaceholderPosition != NewItemPlaceholderPosition.None)
                editableItems.NewItemPlaceholderPosition = NewItemPlaceholderPosition.None;
        }

        // Make sure the newItemPlaceholderRow reflects the correct visiblity 
        TabItem newItemPlaceholderTab = (TabItem)ItemContainerGenerator.ContainerFromItem(CollectionView.NewItemPlaceholder);
        if (newItemPlaceholderTab != null)
            newItemPlaceholderTab.CoerceValue(VisibilityProperty);
    }
}

<强> CustomStyleSelector.cs

internal class CustomStyleSelector : StyleSelector
{
    public Style NewItemStyle { get; set; }

    public override Style SelectStyle(object item, DependencyObject container)
    {
        if (item == CollectionView.NewItemPlaceholder)
            return NewItemStyle;
        else
            return Application.Current.FindResource(typeof(TabItem)) as Style;
    }
}

<强> TemplateSelector.cs

internal class TemplateSelector : DataTemplateSelector
{
    public DataTemplate ItemTemplate { get; set; }
    public DataTemplate NewItemTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item == CollectionView.NewItemPlaceholder)
            return NewItemTemplate;
        else
            return ItemTemplate;
    }
}

<强> Generic.xaml

<!-- This style explains how to style a NewItemPlaceholder. -->
<Style x:Key="NewTabItemStyle" TargetType="{x:Type TabItem}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TabItem}">
                <ContentPresenter ContentSource="Header" HorizontalAlignment="Left" />
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<!-- This template explains how to render a tab item with a close button. -->
<DataTemplate x:Key="ClosableTabItemHeader">
    <DockPanel MinWidth="120">
        <Button DockPanel.Dock="Right" Command="ApplicationCommands.Delete" CommandParameter="{Binding}" Content="X" Cursor="Hand" Focusable="False" FontSize="10" FontWeight="Bold" Height="16" Width="16" />
        <TextBlock Padding="0,0,10,0" Text="{Binding DisplayName}" VerticalAlignment="Center" />
    </DockPanel>
</DataTemplate>

<!-- This template explains how to render a tab item with a new button. -->
<DataTemplate x:Key="NewTabItemHeader">
    <Button Command="{Binding NewTabCommand, RelativeSource={RelativeSource AncestorType={x:Type local:ExtendedTabControl}}}" Content="+" Cursor="Hand" Focusable="False" FontWeight="Bold"
            Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"/>
</DataTemplate>

<local:CustomStyleSelector x:Key="StyleSelector" NewItemStyle="{StaticResource NewTabItemStyle}" />
<local:TemplateSelector x:Key="HeaderTemplateSelector" ItemTemplate="{StaticResource ClosableTabItemHeader}" NewItemTemplate="{StaticResource NewTabItemHeader}" />
<Style x:Key="{x:Type local:ExtendedTabControl}" BasedOn="{StaticResource {x:Type TabControl}}" TargetType="{x:Type local:ExtendedTabControl}">
    <Setter Property="ItemContainerStyleSelector" Value="{StaticResource StyleSelector}" />
    <Setter Property="ItemTemplateSelector" Value="{StaticResource HeaderTemplateSelector}" />
</Style>

答案 3 :(得分:2)

现有答案对我来说太复杂了,我很懒。因此,我尝试实现一个非常简单的想法。

  1. 总是将[+]标签添加到最后一个。
  2. 选择最后一个标签后,使其成为新标签,并添加另一个最后一个标签。

这个想法很简单,但是该死的WPF很冗长,因此代码变得有点长。但这可能很容易理解...因为甚至我也知道。

enter image description here

后面有代码。

public partial class MainWindow : Window
{
    int TabIndex = 1;
    ObservableCollection<TabVM> Tabs = new ObservableCollection<TabVM>();
    public MainWindow()
    {
        InitializeComponent();
        var tab1 = new TabVM()
        {
            Header = $"Tab {TabIndex}",
            Content = new ContentVM("First tab", 1)
        };
        Tabs.Add(tab1);
        AddNewPlusButton();

        MyTabControl.ItemsSource = Tabs;
        MyTabControl.SelectionChanged += MyTabControl_SelectionChanged;

    }

    private void MyTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if(e.Source is TabControl)
        {
            var pos = MyTabControl.SelectedIndex;
            if (pos!=0 && pos == Tabs.Count-1) //last tab
            {
                var tab = Tabs.Last();
                ConvertPlusToNewTab(tab);
                AddNewPlusButton();
            }
        }
    }

    void ConvertPlusToNewTab(TabVM tab)
    {
        //Do things to make it a new tab.
        TabIndex++;
        tab.Header = $"Tab {TabIndex}";
        tab.IsPlaceholder = false;
        tab.Content = new ContentVM("Tab content", TabIndex);
    }

    void AddNewPlusButton()
    {
        var plusTab = new TabVM()
        {
            Header = "+",
            IsPlaceholder = true
        };
        Tabs.Add(plusTab);
    }

    class TabVM:INotifyPropertyChanged
    {
        string _Header;
        public string Header
        {
            get => _Header;
            set
            {
                _Header = value;
                OnPropertyChanged();
            }
        }

        bool _IsPlaceholder = false;
        public bool IsPlaceholder
        {
            get => _IsPlaceholder;
            set
            {
                _IsPlaceholder = value;
                OnPropertyChanged();
            }
        }

        ContentVM _Content = null;
        public ContentVM Content
        {
            get => _Content;
            set
            {
                _Content = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        void OnPropertyChanged([CallerMemberName] string property = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
        }
    }

    class ContentVM
    {
        public ContentVM(string name, int index)
        {
            Name = name;
            Index = index;
        }
        public string Name { get; set; }
        public int Index { get; set; }
    }

    private void OnTabCloseClick(object sender, RoutedEventArgs e)
    {
        var tab = (sender as Button).DataContext as TabVM;
        if (Tabs.Count>2)
        { 
            var index = Tabs.IndexOf(tab);
            if(index==Tabs.Count-2)//last tab before [+]
            {
                MyTabControl.SelectedIndex--;
            }
            Tabs.RemoveAt(index);
        }
    }
}

XAML

<TabControl Name="MyTabControl">
    <TabControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Header, Mode=OneWay}" />
                <Button Click="OnTabCloseClick" Width="20" Padding="0" Margin="8 0 0 0" Content="X">
                    <Button.Style>
                        <Style TargetType="Button" x:Name="CloseButtonStyle">
                            <Setter Property="Visibility" Value="Visible"/>
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding IsPlaceholder}" Value="True">
                                    <Setter Property="Visibility" Value="Collapsed"/>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </Button.Style>
                </Button>
            </StackPanel>
        </DataTemplate>
    </TabControl.ItemTemplate>

    <TabControl.ContentTemplate>
        <DataTemplate>
            <ContentControl>
                <ContentControl.Resources>
                    <ContentControl x:Key="TabContentTemplate">
                        <StackPanel DataContext="{Binding Content}" Orientation="Vertical">
                            <TextBlock Text="{Binding Path=Name}"/>
                            <TextBlock Text="{Binding Path=Index}"/>
                        </StackPanel>
                    </ContentControl>
                </ContentControl.Resources>
                <ContentControl.Style>
                    <Style TargetType="ContentControl">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding IsPlaceholder}" Value="True">
                                <Setter Property="Content"
                                        Value="{x:Null}"/>                                    
                            </DataTrigger>
                            <DataTrigger Binding="{Binding IsPlaceholder}" Value="False">
                                <Setter Property="Content"
                                        Value="{StaticResource TabContentTemplate}"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </ContentControl.Style>
            </ContentControl>
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>

答案 4 :(得分:1)

定义TabControl的ControlTemplate,如下所示:

 <!-- Sets the look of the Tabcontrol. -->
<Style x:Key="TabControlStyle" TargetType="{x:Type TabControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TabControl}">
                <Grid>
                    <!-- Upperrow holds the tabs themselves and lower the content of the tab -->
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>

网格中的上一行是TabPanel,但你可以将它放在一个StackPanel中,并在TabPanel后面有一个按钮,并将按钮设置为看起来像一个标签。

现在按钮会创建一个新的TabItem(可能是您自定义创建的一个),并将其添加到您拥有的Tabs的ObservableCollection中,作为TabControl的Itemssource。

2&amp; 3)它应该总是出现在最后,它不是一个标签,所以希望不是标签循环的一部分

4)嗯,你的TabControl应该使用TabItems的ObservableCollection作为Itemssource,以便在添加/删除新的时通知

一些代码:

NewTabButton usercontrol .cs文件

public partial class NewTabButton : TabItem
{
    public NewTabButton()
    {
        InitializeComponent();

        Header = "+";
    }
}

主窗口:

public partial class Window1 : Window
{
    public ObservableCollection<TabItem> Tabs { get; set; }

    public Window1()
    {
        InitializeComponent();

        Tabs = new ObservableCollection<TabItem>();

        for (int i = 0; i < 20; i++)
        {
            TabItem tab = new TabItem();
            tab.Header = "TabNumber" + i.ToString();
            Tabs.Add(tab);
        }

        Tabs.Add(new NewTabButton());

        theTabs.ItemsSource = Tabs;
    }
}

现在我们需要找到一种方法让它始终显示在右下角,并为其添加事件和样式(加号作为占位符)。

答案 5 :(得分:1)

这可能会更好地评论@ NVM自己的解决方案;但我还没有让评论发表评论呢......

如果您尝试使用已接受的解决方案而未触发add命令,那么您可能没有名为&#34; parentUserControl&#34;的用户控件。

您可以按照以下方式更改@ NVM的TabControl声明,以使其正常工作:

<TabControl x:Name="parentUserControl"
            ItemsSource="{Binding Items}"
            ItemTemplateSelector="{StaticResource headerTemplateSelector}"
            ContentTemplateSelector="{StaticResource contentTemplateSelector}"/>

显然不是一个好的名称来给出一个标签控件:);但是我想@NVM的数据上下文将他的可视树连接到一个元素以匹配名称。

请注意,我个人更喜欢通过更改以下内容来使用相对绑定:

<Button Content="+" 
        Command="{Binding ElementName=parentUserControl, 
                          Path=DataContext.NewCommand}"/>

对此:

<Button Content="+" 
        Command="{Binding DataContext.NewCommand, 
                          RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}"/>

答案 6 :(得分:0)

除了NVM的回答。 我没有为NewItemPlaceholder使用这么多模板和选择器。更简单的解决方案,没有空白内容:

    <TabControl.ItemContainerStyle>
        <Style TargetType="TabItem">
           <Style.Triggers>
              <DataTrigger Binding="{Binding}" Value="{x:Static CollectionView.NewItemPlaceholder}">
                 <Setter Property="Template">
                    <Setter.Value>
                       <ControlTemplate>
                          <Button Command="{Binding DataContext.AddPageCommand, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}"
                                  HorizontalContentAlignment="Center" VerticalContentAlignment="Center" ToolTip="Add page" >
                             +
                          </Button>
                       </ControlTemplate>
                    </Setter.Value>
                 </Setter>
              </DataTrigger>
           </Style.Triggers>
        </Style>
     </TabControl.ItemContainerStyle>

Ctrl + Tab我决定禁用。这不容易,您应该在父元素上订阅KeyDown,即Window(Ctrl + Shift + Tab也正确处理):

  public View()
  {
     InitializeComponent();
     AddHandler(Keyboard.PreviewKeyDownEvent, (KeyEventHandler)controlKeyDownEvent);
  }

  private void controlKeyDownEvent(object sender, KeyEventArgs e)
  {
     e.Handled = e.Key == Key.Tab && Keyboard.Modifiers.HasFlag(ModifierKeys.Control);
  }

答案 7 :(得分:0)

要完成@NVM给出的答案,您需要添加的是PreviewMouseDown事件:

<TabControl PreviewMouseDown="ActionTabs_PreviewMouseDown"
</TabControl>

然后:

private void ActionTabs_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
  ouseButtonEventArgs args = e as MouseButtonEventArgs;

  FrameworkElement source = (FrameworkElement)args.OriginalSource;

  if (source.DataContext.ToString() == "{NewItemPlaceholder}")
  {
      e.Handled = true;
  }
}