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

问题描述 投票:-1回答:1

我的英语能力很差,因为我不会说英语。希望您能理解。

我想创建具有可关闭功能的选项卡控件。 (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>

如您所见,它不需要连接关闭命令即可删除外部项目中的文档。同样,它不需要重写ItemTemplate即可绑定。 (它将解决使用HeaderBinding功能)我认为上述自定义控件为外部项目带来了便利。

我试图像上面的控件一样创建,但是遇到了下面的问题。

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

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

我应如何解决上述问题?希望您的帮助。

感谢您阅读。

wpf binding tabcontrol
1个回答
0
投票

此快速简单的示例扩展了TabControl,并且还覆盖了Style的默认TabControl。新的Style必须放置在“ / Themes / Generic.xaml”文件中。 Style会覆盖默认的TabItem ControlTemplate,并向其添加一个关闭按钮。

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

如果通过TabItem(绑定)创建ItemsSource,则将执行ICommand属性ClosableTabControl.RemoveItemCommand,以使视图模型从集合中删除该项目。这是为了避免直接来自ItemsSource的项目,这会破坏此属性上的绑定。传递给命令委托的参数是需要删除的选项卡项目的数据模型(在您的情况下为DocumentViewModel类型)。如果TabItem是通过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));
    ...
}

ClosableTabControl.cs

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>

备注

而不是从ItemsSource中删除项目(这实际上是您的要求),我建议切换TabItem.Visibility并将其设置为Visibility.Collapsed以从视图中删除TabItem。这是一种更直观,更灵活的行为(例如,重新打开最近关闭的行为)。因为当用户将其从视图中删除时,这并不意味着也必须从视图模型中将其删除。如果视图模型决定really删除数据模型,则只需将其从绑定源集合中删除。这也将消除对ClosableTabControl.RemoveItemCommand属性的需要,因为Visibility可以/必须在ClosableTabControl内部处理。

所以ClosableTabControl.ExecuteRemoveTab方法将变成:

private void ExecuteRemoveTab(object sender, ExecutedRoutedEventArgs e)
{
  object tabItemToRemove = this.ItemsSource == null
    ? e.Source
    : (e.OriginalSource as FrameworkElement).DataContext;

  // Select the next tab after the removed tab
  int lastItemIndex = this.Items.Count - 1;
  int nextItemIndex = this.Items.IndexOf(tabItemToRemove) + 1;
  this.SelectedIndex = Math.Min(lastItemIndex, nextItemIndex);

  (this.ItemContainerGenerator.ContainerFromItem(tabItemToRemove) as UIElement).Visibility = Visibility.Collapsed;
}

用法示例

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

  <ClosableTabControl ItemsSource="{Binding Documents}" />
</Window>
© www.soinside.com 2019 - 2024. All rights reserved.