带有添加新选项卡按钮 (+) 的 TabControl
Posted
技术标签:
【中文标题】带有添加新选项卡按钮 (+) 的 TabControl【英文标题】:TabControl with Add New Tab Button (+) 【发布时间】:2011-03-28 23:40:24 【问题描述】:在 WPF 中选项卡控件的选项卡条中的所有选项卡项的末尾添加“+”按钮选项卡的正确方法是什么?
-
它应该与多个标签标题行一起正常工作。
它应该在所有标签项的末尾
标签循环应该可以正常工作(Alt + Tab),也就是说,
+
标签应该被跳过。
我不应该修改我绑定到的源集合。也就是说,控件应该是可重复使用的。
该解决方案应该适用于MVVM
更准确地说,该按钮应完全显示为附加的最后一个选项卡,而不是所有选项卡条行右侧某处的单独按钮。
我只是在寻找执行此操作的一般方法。
谷歌举了很多例子,但如果你深入挖掘,没有一个能满足以上五点。
【问题讨论】:
标签不属于标题,停止重新添加。 [CTRL]+[TAB] 在应用程序中循环窗口(或选项卡)。不是 [ALT]+[TAB],它会循环应用程序本身 【参考方案1】:现有的答案对我来说太复杂了,而且我很懒。所以,我尝试实现一个非常简单的想法。
-
始终将 [+] 选项卡添加到最后。
选择最后一个选项卡后,将其设为新选项卡,然后添加另一个最后一个选项卡。
这个想法很简单,但是该死的 WPF 很冗长,所以代码变得有点长。但这可能很容易理解......因为即使我也这样做了。
代码隐藏。
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>
【讨论】:
在XAML
中有没有办法在TabContentTemplate
中设置网格?我这样做了,但是如果我更改其 Text 以反映所有选项卡的控件,则可以像 TextBox
那样对网格进行任何控制。如何在那里添加网格而不将其值反映到其他选项卡?【参考方案2】:
要完成@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;
【讨论】:
【参考方案3】:除了 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);
【讨论】:
【参考方案4】:作为对@NVM 自己的解决方案的评论,这可能会更好;但我还没有代表发表评论所以......
如果您尝试使用已接受的解决方案并且没有触发添加命令,那么您可能没有名为“parentUserControl”的用户控件。
您可以按如下方式更改@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"/>
【讨论】:
【参考方案5】:我使用了选项卡控件模板的修改并绑定到我的视图模型中的 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);
注意:关于属性“TabStripPlacement”的值,我用 StackPanel 包裹了 TabPanel 以将“+”按钮与 TabPanel 一起翻转。在您看来,没有继承,也没有 code-behind。
【讨论】:
【参考方案6】:使用 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
几乎完成了,因为标签循环不会跳过“+”标签,并且会显示空白内容(这不是很好,但我可以忍受它,直到出现更好的解决方案......)。
【讨论】:
你应该接受这个作为你的正确答案,这样人们在寻找相同问题的答案时会找到它。顺便说一句,干得好。 我希望有人能提出一个完整的解决方案。但我想我已经等得够久了...... 这是一个很棒的解决方案。非常适合我。 你有没有回来过这个?我很好奇你是否想出了一个完整的解决方案? 对不起,我在这个项目之后停止使用 WPF。那时确实想到了一些东西,但我不再记得了。我想如果你问一个新问题,有人可能会回答这个特定的问题。【参考方案7】:像这样定义 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(可能是您自定义创建的)并将其添加到您作为 TabControl 的 Itemssource 的 Tabs 的 ObservableCollection 中。
2 & 3) 它应该总是出现在最后,而且它不是一个标签,所以希望它不是标签循环的一部分
4) 好吧,您的 TabControl 应该使用 TabItems 的 ObservableCollection 作为 Itemssource,以便在添加/删除新项目时收到通知
一些代码:
NewTabButton 用户控件 .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;
现在我们需要找到一种方法让它始终显示在右下角,并为其添加事件和样式(加号作为占位符)。
【讨论】:
感谢 Ingo,我刚刚添加了一张图片以使问题更清楚。当我认为有多行标签项时,您的建议将不起作用。 您可以尝试创建一个继承 TabItem 的 UserControls 并在该事件上创建一个事件,当它被选中时,它会创建一个新的 TabItem 并将其放入自身的集合中。那应该可以完美地工作。如果我有时间,我可能会稍后添加代码。 如果您通过 ItemsSource 绑定,则无法在 XAML 中添加额外的 TabItem。如果您正在谈论使用 CompositeCollection,我认为它仅适用于静态 TabItems,并且您无法绑定项目 您可以添加到作为 itemssource 的 ObservableCollection我相信我已经想出了一个完整的解决方案,我从 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>
【讨论】:
哪里有这个项目的演示? 呃……怎么用?尤其是 Generic.XAML 部分。我将所有这些都放入以上是关于带有添加新选项卡按钮 (+) 的 TabControl的主要内容,如果未能解决你的问题,请参考以下文章