button - 带有新选项卡按钮的wpf TabControl ( + )

  显示原文与译文双语对照的内容
146 4

在WPF选项卡控件的选项卡项的选项卡条的末尾添加'+'按钮选项卡的正确方法?

  • 它应该能在多个选项卡标题行正确工作。
  • 它应该在所有标签项的末尾
  • 标签循环应该正常工作( Alt + 标签),也就是说,+ 标签应该被跳过。
  • 不应修改要绑定到的源集合。也就是说,控件应该是可以重用的。
  • 解决方案应该与 MVVM插件一起工作。

Enter image description here

enter image description here

为更精确起见,按钮应该显示为附加的最后一个选项卡,而不是所有选项卡行右边的单独按钮。

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

谷歌推出了许多例子,但如果你挖掘了一点深度,那么它们就会满足上面的。

时间:原作者:0个回答

106 0

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

它几乎完全完成,因为选项卡周期不跳过'+'选项卡,并将显示空内容( 这不太好,但是我可以以直接生活直到一个更好的解决方案出现。"。) 。

原作者:
98 3

我在视图模型中使用了选项卡控件模板的修改,并绑定到 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按钮,并将"+"按钮与 TabPanel TabPanel有关属性的值。如果没有继承并且没有代码,你的视图中就会出现。

原作者:
81 0

我想我已经提出了一个完整的解决方案,我从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>
原作者:
...