ApplicationCommands.Delete 的 TabControl 实现

TabControl implementation of ApplicationCommands.Delete

我正在尝试扩展 TabControl 以便我可以添加和删除项目,我之前通过向我的视图模型添加一个关闭命令来完成此操作,该命令引发事件并且 parent 视图模型中的订阅将被删除collection.

中的项目

我想让这种方法更通用,并且正在尝试实施 ApplicationCommands.Delete 命令。

ExtendedTabControl.cs

public class ExtendedTabControl : TabControl
{
    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; }
    }

    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)
    {
        ((WorkspacesTabControl)sender).OnCanExecuteDelete(e);
    }

    private static void OnCanUserDeleteTabsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // The Delete command needs to have CanExecute run.
        CommandManager.InvalidateRequerySuggested();
    }

    private static object OnCoerceCanUserDeleteTabs(DependencyObject d, object baseValue)
    {
        return ((WorkspacesTabControl)d).OnCoerceCanUserAddOrDeleteTabs((bool)baseValue, false);
    }

    private static void OnExecutedDelete(object sender, ExecutedRoutedEventArgs e)
    {
        ((WorkspacesTabControl)sender).OnExecutedDelete(e);
    }

    static ExtendedTabControl()
    {
        Type ownerType = typeof(ExtendedTabControl);

        DefaultStyleKeyProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(typeof(ExtendedTabControl)));

        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;
    }
    #endregion

    protected virtual void OnExecutedDelete(ExecutedRoutedEventArgs e)
    {
        if (ItemIsSelected)
        {
            object currentItem = SelectedItem;
            int indexToSelect = Items.IndexOf(currentItem) - 1;

            if (currentItem != CollectionView.NewItemPlaceholder)
                EditableItems.Remove(currentItem);

            // This should focus the row and bring it into view. 
            SetCurrentValue(SelectedItemProperty, Items[indexToSelect]);
        }

        e.Handled = true;
    }

    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;
    }
}

Generic.xaml

<!-- This template explains how to render a tab item with a close button. -->
<DataTemplate x:Key="CloseableTabItemHeader">
    <DockPanel MinWidth="120">
        <Button DockPanel.Dock="Right" Command="ApplicationCommands.Delete" 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>

<Style x:Key="{x:Type local:ExtendedTabControl}" BasedOn="{StaticResource {x:Type TabControl}}" TargetType="{x:Type local:ExtendedTabControl}">
    <Setter Property="ItemTemplate" Value="{StaticResource CloseableTabItemHeader}" />
</Style>

这几乎有效,我可以选择一个项目并通过点击关闭按钮或使用删除键将其删除。但是,如果我点击未选中项目的关闭按钮,它仍会删除所选项目。这种行为的原因很明显,但我不确定如何访问正确的 object 以进行删除?我还需要以更好的方式分配在 OnExecutedDelete 中找到的 indexToSelect,尽管我很乐意为此找到解决方案。

ExecutedRoutedEventArgs 有 属性 Parameter。尝试将 TabItemDataContext 设置为 CommandParameter:

<DataTemplate x:Key="CloseableTabItemHeader">
<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>

然后您可以在OnExecutedDelete中访问DataContext:

protected virtual void OnExecutedDelete(ExecutedRoutedEventArgs e)
{
    if (ItemIsSelected)
    {
        object currentItem = e.Parameter ?? SelectedItem;
        int indexToSelect = Items.IndexOf(currentItem) - 1;

        ...
    }

    e.Handled = true;
}

我刚刚在 http://dragablz.net

中做了非常相似的事情

管理选项卡添加和删除的两个签到:

https://github.com/ButchersBoy/Dragablz/commit/47bbc302f5ffeaa8c234269ab4ff11bc80f7fa10 https://github.com/ButchersBoy/Dragablz/commit/c1dce0435683db83f163a77ccb9a19b3218b3ca7