An almost complete solution using 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
It's almost complete, because the tab cycle doesn't skip the '+' tab, and will show empty content (which is not exactly great, but I can live with it until a better solution come around...).
Existing answers were too complex for me and I am lazy. So, I tried to implement a very simple idea.
The idea was simple, but the damn WPF is verbose, so the code became a little bit long. But it probably is very simple to understand... because even I did.
Code behind.
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>
I used a modification of the tab control template and binding to the AddNewItemCommand command in my view model. 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>
Code in the relevant view model looks like this:
public ICommand AddNewItemCommand
{
get
{
return new DelegateCommand((param) =>
{
MyItemSource.Add(CreateMyValueViewModel());
},
(param) => MyItemSource != null);
}
}
Pay attention: I wrapped TabPanel by StackPanel to flip the "+" button together with TabPanel regarding to value of property "TabStripPlacement". Without inheritance and without code-behind in your view.
I believe I have come up with a complete solution, I started with NVM's solution to create my template. And then referenced the DataGrid source code to come up with an extended TabControl capable of adding and removing items.
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>
Define the ControlTemplate of the TabControl like this:
<!-- 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>
The upper row in the grid would be the TabPanel, but you would put that into a StackPanel with a button following the TabPanel, and style the button to look like a tab.
Now the button would create a new TabItem (your custom-created one perhaps) and add it to the ObservableCollection of Tabs you have as the Itemssource for your TabControl.
2 & 3) It should always appear at the end, and it's not a tab so hopefully not part of tab cycling
4) Well, your TabControl should use a ObservableCollection of TabItems as Itemssource to be notified when a new one is added/removed
Some code:
The NewTabButton usercontrol .cs file
public partial class NewTabButton : TabItem
{
public NewTabButton()
{
InitializeComponent();
Header = "+";
}
}
And the main window:
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;
}
}
Now we would need to find a way to let it always appear bottom right and also add the event and style for it (the plus sign is there as a placeholder).
This would likely be better as a comment on @NVM's own solution; but I don't have the rep to comment yet so...
If you are trying to use the accepted solution and not getting the add command to trigger then you probably don't have a usercontrol named "parentUserControl".
You can alter @NVM's TabControl declaration as follows to make it work:
<TabControl x:Name="parentUserControl"
ItemsSource="{Binding Items}"
ItemTemplateSelector="{StaticResource headerTemplateSelector}"
ContentTemplateSelector="{StaticResource contentTemplateSelector}"/>
Obviously not a good name to give a tab control :); but I guess @NVM had the data context hooked further up his visual tree to an element to match the name.
Note that personally I preferred to use a relative binding by changing the following:
<Button Content="+"
Command="{Binding ElementName=parentUserControl,
Path=DataContext.NewCommand}"/>
To this:
<Button Content="+"
Command="{Binding DataContext.NewCommand,
RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}"/>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With