Wpf 如何创建TabControl(自定义控件)的可关闭TabItem?

Wpf 如何创建TabControl(自定义控件)的可关闭TabItem?,wpf,binding,tabcontrol,Wpf,Binding,Tabcontrol,我的英语水平很差,因为我不是以英语为母语的人。 我希望你能理解 我想创建具有可关闭功能的选项卡控件。(ClosableTabControl) ClosableTabControl必须具有在单击关闭按钮时关闭选项卡项的功能。 另外,我想自动删除与关闭的选项卡项相关的ItemsSource 因此,我希望在外部项目中使用ClosableTabControl,如下所示 class MainViewModel { public ObservableCollection<DocumentVie

我的英语水平很差,因为我不是以英语为母语的人。 我希望你能理解

我想创建具有可关闭功能的选项卡控件。(ClosableTabControl)

ClosableTabControl必须具有在单击关闭按钮时关闭选项卡项的功能。 另外,我想自动删除与关闭的选项卡项相关的ItemsSource

因此,我希望在外部项目中使用ClosableTabControl,如下所示

class MainViewModel
{
    public ObservableCollection<DocumentViewModel> Documents {get;}
    ...
}

class DocumentViewModel
{
    public string Title {get;}
    public object Content {get;}
}

<Window DataContext="MainViewModel">
    <ClosableTabControl ItemsSource="Documents"
                        HeaderBinding="{Binding Title}"/>
</Window>
class主视图模型
{
公共可观察收集文档{get;}
...
}
类DocumentViewModel
{
公共字符串标题{get;}
公共对象内容{get;}
}
如您所见,无需连接close命令即可删除外部项目中的文档。 此外,它不需要将ItemTemplate重写为绑定。(使用HeaderBinding功能可以解决此问题) 我认为上面的自定义控件为外部项目提供了方便

我试图创建上面的控件,但我遇到了下面的问题

1。它无法删除ClosableTabControl的ItemsSource元素。(关闭选项卡项时需要此选项)

2。我不知道如何实现HeaderBinding功能。

我应该做什么来解决上述问题? 我希望你能帮助我


感谢阅读。

这个简单快速的示例扩展了
TabControl
,并覆盖了
TabControl
的默认
样式。新的
样式
必须放在“/Themes/Generic.xaml”文件中。
样式
覆盖默认的
选项卡项
控制模板
,并向其添加关闭按钮

按钮.命令
绑定到
ClosableTabControl
的路由命令
CloseTabRoutedCommand
调用后,
ClosableTabControl
检查
集合是否通过数据绑定或例如XAML填充

如果通过
ItemsSource
(绑定)创建了
TabItem
,则将执行
ICommand
属性
ClosableTabControl.removietem命令
,以便视图模型从集合中删除该项。这是为了避免直接从
ItemsSource
中删除项,这将破坏此属性上的绑定。传递给命令委托的参数是选项卡项的数据模型(在您的类型为
DocumentViewModel
)被请求删除。
如果
选项卡项
是通过XAML创建的,则该项将直接删除,而不需要视图模型中的命令

MainViewModel.cs

class MainViewModel
{
  public ObservableCollection<DocumentViewModel> Documents {get;}

  // The remove command which is bound to the ClosableTabControl RemoveItemCommand property. 
  // In case the TabControl.ItemsSource is data bound,
  // this command will be invoked to remove the tab item
  public ICommand RemoveTabItemCommand => new AsyncRelayCommand<DocumentViewModel>(item => this.Documents.Remove(item));
    ...
}
public class ClosableTabControl : TabControl
{
  public static readonly RoutedUICommand CloseTabRoutedCommand = new RoutedUICommand(
    "Close TabItem and remove item from ItemsSource", 
    nameof(ClosableTabControl.CloseTabRoutedCommand), 
    typeof(ClosableTabControl));

  // Bind this property to a ICommand implementation of the view model
  public static readonly DependencyProperty RemoveItemCommandProperty = DependencyProperty.Register(
    "RemoveItemCommand",
    typeof(ICommand),
    typeof(ClosableTabControl),
    new PropertyMetadata(default(ICommand)));

  public ICommand RemoveItemCommand 
  { 
    get => (ICommand) GetValue(ClosableTabControl.RemoveItemCommandProperty); 
    set => SetValue(ClosableTabControl.RemoveItemCommandProperty, value); 
  }

  static ClosableTabControl()
  {
    // Override the default style. 
    // The new Style must be located in the "/Themes/Generic.xaml" ResourceDictionary
    DefaultStyleKeyProperty.OverrideMetadata(typeof(ClosableTabControl), new FrameworkPropertyMetadata(typeof(ClosableTabControl)));
  }

  public ClosableTabControl()
  {
    this.CommandBindings.Add(
      new CommandBinding(ClosableTabControl.CloseTabRoutedCommand, ExecuteRemoveTab, CanExecuteRemoveTab));
  }

  private void CanExecuteRemoveTab(object sender, CanExecuteRoutedEventArgs e)
  {
    e.CanExecute = e.OriginalSource is FrameworkElement frameworkElement 
                   && this.Items.Contains(frameworkElement.DataContext)
                   || this.Items.Contains(e.Source);
  }

  private void ExecuteRemoveTab(object sender, ExecutedRoutedEventArgs e)
  {
    if (this.ItemsSource == null)
    {
      object tabItemToRemove = e.Source;
      this.Items.Remove(tabItemToRemove);
    }
    else
    {
      object tabItemToRemove = (e.OriginalSource as FrameworkElement).DataContext;
      if (this.RemoveItemCommand?.CanExecute(tabItemToRemove) ?? false)
      {
        this.RemoveItemCommand.Execute(tabItemToRemove);
      }
    }
  }
}
Generic.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">


  <Style TargetType="{x:Type ClosableTabControl}"
         BasedOn="{StaticResource {x:Type TabControl}}">
    <Setter Property="Background"
            Value="{x:Static SystemColors.ControlBrush}" />

    <!-- Add a close button to the tab header -->
    <Setter Property="ItemContainerStyle">
      <Setter.Value>
        <Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
          <Setter Property="BorderThickness"
                  Value="1,1,1,0" />
          <Setter Property="Margin"
                  Value="0,2,0,0" />
          <Setter Property="BorderBrush" Value="DimGray" />
          <Setter Property="Background" Value="LightGray" />
          <Setter Property="Template">
            <Setter.Value>
              <ControlTemplate TargetType="TabItem">
                <Border BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}"
                        Background="{TemplateBinding Background}">
                  <StackPanel Orientation="Horizontal">
                    <ContentPresenter x:Name="ContentSite"
                                      VerticalAlignment="Center"
                                      HorizontalAlignment="Center"
                                      ContentSource="Header"
                                      Margin="12,2,12,2"
                                      RecognizesAccessKey="True" />
                    <Button Content="X"
                            Command="{x:Static local:ClosableTabControl.CloseTabRoutedCommand}"
                            Height="16"
                            Width="16"
                            VerticalAlignment="Center"
                            VerticalContentAlignment="Center"
                            Margin="4" />
                  </StackPanel>
                </Border>
                <ControlTemplate.Triggers>
                  <Trigger Property="IsSelected" Value="True">
                    <Setter Property="Background"
                            Value="{x:Static SystemColors.ControlBrush}" />
                    <Setter Property="Panel.ZIndex"
                            Value="100" />
                    <Setter Property="Margin"
                            Value="0,0,0,-1" />
                  </Trigger>
                </ControlTemplate.Triggers>
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Style>
      </Setter.Value>
    </Setter>

    <!-- Provide a default DataTemplate for the tab header
         This will only work if the data item has a property Title -->
    <Setter Property="ItemTemplate">
        <DataTemplate>
          <TextBlock Text="{Binding Title}"/>
        </DataTemplate>
      </Setter.Value>
    </Setter>

    <!-- Provide a default DataTemplate for the tab content
         This will only work if the data item has a property Content -->
    <Setter Property="ContentTemplate">
      <Setter.Value>
        <DataTemplate>
          <ContentPresenter Content="{Binding Content}" />
        </DataTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>
用法示例

<Window> 
  <Window.DataContext>
    <MainViewModel" x:Name="MainViewModel" />
  </Window.DataContext>

  <ClosableTabControl ItemsSource="{Binding Documents}"
                      RemoveItemCommand="{Binding RemoveTabItemCommand}" />
</Window>
<Window> 
  <Window.DataContext>
    <MainViewModel" x:Name="MainViewModel" />
  </Window.DataContext>

  <ClosableTabControl ItemsSource="{Binding Documents}" />
</Window>


这回答了你的问题吗?您需要显示实现,否则没有人知道为什么删除项不起作用。另外,不要从
ItemsSource
中删除项目,而是将
选项卡item.Visibility
设置为
Visibility.Collapsed
。控件不应该弄乱数据。它也更容易实现“重新打开最近关闭的”行为。哦,这是个好主意。我会考虑的。
<Window> 
  <Window.DataContext>
    <MainViewModel" x:Name="MainViewModel" />
  </Window.DataContext>

  <ClosableTabControl ItemsSource="{Binding Documents}" />
</Window>