Пользовательский TabControl для CloseableTabs

Здесь есть несколько решений для закрываемых вкладок, но все они зависят от владельца коллекции, обеспечивающего функцию закрытия.

Я подумал, что возможно, создав пользовательские TabControl и TabItem, это можно будет сделать в CodeBehind.

Идея состоит в том, чтобы каким-то образом предупредить TabControl из TabItem о том, что была нажата кнопка закрытия (возможно, с всплывающим событием или чем-то еще, пожалуйста, предложите), а затем удалить эти конкретные элементы, завернутые в этот TabItem, из ItemsSource.

Проблема в том, что я не уверен, как и даже если это возможно. Можете ли вы как-то удалить элементы из набора коллекций как ItemsSource в TabControl? Можем ли мы, возможно, установить новый DP, чтобы у нас был полный контроль над коллекцией, и просто установить его как ItemsSource в программном коде.

Кто-нибудь делал это или имеет какие-либо идеи, как это сделать. Это вообще возможно?


person Ingó Vals    schedule 27.05.2011    source источник


Ответы (2)


Даже если это технически возможно, я бы посоветовал придерживаться M-V-VM. Позвольте владельцу коллекции, то есть ViewModel, управлять добавлением/удалением элементов из коллекции. Пусть представление будет тонким и синхронизируется с коллекцией виртуальной машины через привязку данных. Это предотвращает усложнение представлений и устраняет графический интерфейс, который мешает тестированию.

Вы должны найти это как путь наименьшего сопротивления WPF.

person Gishu    schedule 27.05.2011
comment
См. статью Джоша Смита MVVM MSDN для иллюстрации того, как закрываемые вкладки могут быть реализованы с помощью MVVM. - person Gishu; 27.05.2011
comment
Ну, некоторые вы могли бы увидеть это как проблему для графического интерфейса, а не ViewModel. Речь идет о представлении данных, поэтому я не вижу, чтобы это обязательно ломало MVVM, например, как у вас может быть расширитель и тому подобное. Но, конечно, это не влияет на закулисные коллекции. - person Ingó Vals; 27.05.2011

Это появилось в нашем приложении, поэтому я решил опубликовать свое решение. ClosableTabControl закрывает любую вкладку, удаляя элемент из привязанного ItemsSource. Это частично взято из других решений (Szymon Kobalczyk et al. .).

Пример стиля для ClosableTabControl

ClosableTabControl.cs:

[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(ClosableTabItem))]
public class ClosableTabControl : TabControl
{
    static ClosableTabControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(ClosableTabControl),
            new FrameworkPropertyMetadata(typeof(ClosableTabControl)));

        CloseTabCommand = new RoutedCommand();
    }

    public ClosableTabControl()
        : base()
    {
        CommandBindings.Add(new CommandBinding(CloseTabCommand, CloseTabCommand_Execute));
    }

    public static ICommand CloseTabCommand { get; private set; }

    private void CloseTabCommand_Execute(object sender, ExecutedRoutedEventArgs args)
    {
        if (args.Parameter == null || !(args.Parameter is ClosableTabItem))
            throw new ArgumentNullException("parameter must be of type ClosableTabItem");

        var item = this.ItemContainerGenerator.ItemFromContainer((ClosableTabItem)args.Parameter);
        if (item == null)
            throw new InvalidOperationException("Item not in collection");

        IEditableCollectionView view = this.Items;
        if (!view.CanRemove)
            throw new InvalidOperationException("Read-only collection");

        view.Remove(item);
    }

    protected override DependencyObject GetContainerForItemOverride()
    {
        return new ClosableTabItem();
    }
}

ClosableTabItem.cs:

public class ClosableTabItem : TabItem
{
    static ClosableTabItem()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(ClosableTabItem),
            new FrameworkPropertyMetadata(typeof(ClosableTabItem)));
    }
}

Темы/Generic.xaml:

<Style TargetType="{x:Type local:ClosableTabControl}" BasedOn="{StaticResource {x:Type TabControl}}" />

<Style x:Key="TabItemFocusVisual">
    <Setter Property="Control.Template">
        <Setter.Value>
            <ControlTemplate>
                <Rectangle SnapsToDevicePixels="true" Stroke="Black" StrokeDashArray="1 2" StrokeThickness="1" Margin="3,3,3,1"/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<SolidColorBrush x:Key="TabControlNormalBorderBrush" Color="#8C8E94"/>
<LinearGradientBrush x:Key="TabItemHotBackground" EndPoint="0,1" StartPoint="0,0">
    <GradientStop Color="#EAF6FD" Offset="0.15"/>
    <GradientStop Color="#D9F0FC" Offset=".5"/>
    <GradientStop Color="#BEE6FD" Offset=".5"/>
    <GradientStop Color="#A7D9F5" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="TabItemSelectedBackground" Color="#F9F9F9"/>
<SolidColorBrush x:Key="TabItemHotBorderBrush" Color="#3C7FB1"/>
<SolidColorBrush x:Key="TabItemDisabledBackground" Color="#F4F4F4"/>
<SolidColorBrush x:Key="TabItemDisabledBorderBrush" Color="#FFC9C7BA"/>

<Style TargetType="{x:Type local:ClosableTabItem}">
    <Style.Resources>
        <LinearGradientBrush x:Key="ButtonNormalBackground" EndPoint="0,1" StartPoint="0,0">
            <GradientStop Color="#F3F3F3" Offset="0"/>
            <GradientStop Color="#EBEBEB" Offset="0.5"/>
            <GradientStop Color="#DDDDDD" Offset="0.5"/>
            <GradientStop Color="#CDCDCD" Offset="1"/>
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="ButtonOverBackground" EndPoint="0,1" StartPoint="0,0">
            <GradientStop Color="#FFFAFAFA" Offset="0"/>
            <GradientStop Color="#FFE0E0E3" Offset="1"/>
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="ButtonPressedBackground" EndPoint="0,1" StartPoint="0,0">
            <GradientStop Color="#FFE0E0E2" Offset="0"/>
            <GradientStop Color="#FFF8F8F8" Offset="1"/>
        </LinearGradientBrush>
        <SolidColorBrush x:Key="ButtonNormalBorder" Color="#FF969696"/>
        <Style x:Key="CloseableTabItemButtonStyle" TargetType="{x:Type Button}">
            <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
            <Setter Property="Background" Value="{StaticResource ButtonNormalBackground}"/>
            <Setter Property="BorderBrush" Value="{StaticResource ButtonNormalBorder}"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
            <Setter Property="HorizontalContentAlignment" Value="Center"/>
            <Setter Property="VerticalContentAlignment" Value="Center"/>
            <Setter Property="Padding" Value="4"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Button}">
                        <Grid>
                            <Border SnapsToDevicePixels="true" x:Name="Chrome" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2" Opacity="0" />
                            <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True"/>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Opacity" TargetName="Chrome" Value="1"/>
                                <Setter Property="Background" TargetName="Chrome" Value="{DynamicResource ButtonOverBackground}" />
                            </Trigger>
                            <Trigger Property="IsPressed" Value="True">
                                <Setter Property="Opacity" TargetName="Chrome" Value="1"/>
                                <Setter Property="Background" TargetName="Chrome" Value="{DynamicResource ButtonPressedBackground}" />
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="false">
                                <Setter Property="Foreground" Value="#ADADAD"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Style.Resources>
    <Setter Property="FocusVisualStyle" Value="{StaticResource TabItemFocusVisual}"/>
    <Setter Property="Foreground" Value="Black"/>
    <Setter Property="Padding" Value="6,1,6,1"/>
    <Setter Property="BorderBrush" Value="{StaticResource TabControlNormalBorderBrush}"/>
    <Setter Property="Background" Value="{StaticResource ButtonNormalBackground}"/>
    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
    <Setter Property="VerticalContentAlignment" Value="Stretch"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:ClosableTabItem}">
                <Grid SnapsToDevicePixels="true">
                    <Border x:Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1,1,1,0" >
                        <DockPanel x:Name="ContentPanel">
                            <Button x:Name="PART_Close" HorizontalAlignment="Center" Margin="3,0,3,0" VerticalAlignment="Center" Width="16" Height="16" DockPanel.Dock="Right" Style="{DynamicResource CloseableTabItemButtonStyle}" ToolTip="Close" Command="{x:Static local:ClosableTabControl.CloseTabCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:ClosableTabItem}}}">
                                <Path x:Name="Path" Stretch="Fill" StrokeThickness="0.5" Stroke="#FF333333" Fill="#FF969696" Data="F1 M 2.28484e-007,1.33331L 1.33333,0L 4.00001,2.66669L 6.66667,6.10352e-005L 8,1.33331L 5.33334,4L 8,6.66669L 6.66667,8L 4,5.33331L 1.33333,8L 1.086e-007,6.66669L 2.66667,4L 2.28484e-007,1.33331 Z " HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
                            </Button>
                            <ContentPresenter x:Name="Content" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" ContentSource="Header" RecognizesAccessKey="True" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="{TemplateBinding Padding}"/>
                        </DockPanel>
                    </Border>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" SourceName="PART_Close" Value="True">
                        <Setter Property="Fill" TargetName="Path" Value="#FFB83C3D"/>
                    </Trigger>
                    <Trigger Property="IsPressed" SourceName="PART_Close" Value="True">
                        <Setter Property="Fill" TargetName="Path" Value="#FF9D3838"/>
                    </Trigger>
                    <Trigger Property="IsMouseOver" Value="true">
                        <Setter Property="Background" TargetName="Bd" Value="{StaticResource TabItemHotBackground}"/>
                    </Trigger>
                    <Trigger Property="IsSelected" Value="true">
                        <Setter Property="Panel.ZIndex" Value="1"/>
                        <Setter Property="Background" TargetName="Bd" Value="{StaticResource TabItemSelectedBackground}"/>
                    </Trigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="false"/>
                            <Condition Property="IsMouseOver" Value="true"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource TabItemHotBorderBrush}"/>
                    </MultiTrigger>
                    <Trigger Property="TabStripPlacement" Value="Bottom">
                        <Setter Property="BorderThickness" TargetName="Bd" Value="1,0,1,1"/>
                    </Trigger>
                    <Trigger Property="TabStripPlacement" Value="Left">
                        <Setter Property="BorderThickness" TargetName="Bd" Value="1,1,0,1"/>
                    </Trigger>
                    <Trigger Property="TabStripPlacement" Value="Right">
                        <Setter Property="BorderThickness" TargetName="Bd" Value="0,1,1,1"/>
                    </Trigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="true"/>
                            <Condition Property="TabStripPlacement" Value="Top"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Margin" Value="-2,-2,-2,-1"/>
                        <Setter Property="Margin" TargetName="ContentPanel" Value="0,0,0,1"/>
                    </MultiTrigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="true"/>
                            <Condition Property="TabStripPlacement" Value="Bottom"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Margin" Value="-2,-1,-2,-2"/>
                        <Setter Property="Margin" TargetName="ContentPanel" Value="0,1,0,0"/>
                    </MultiTrigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="true"/>
                            <Condition Property="TabStripPlacement" Value="Left"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Margin" Value="-2,-2,-1,-2"/>
                        <Setter Property="Margin" TargetName="ContentPanel" Value="0,0,1,0"/>
                    </MultiTrigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="true"/>
                            <Condition Property="TabStripPlacement" Value="Right"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Margin" Value="-1,-2,-2,-2"/>
                        <Setter Property="Margin" TargetName="ContentPanel" Value="1,0,0,0"/>
                    </MultiTrigger>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Background" TargetName="Bd" Value="{StaticResource TabItemDisabledBackground}"/>
                        <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource TabItemDisabledBorderBrush}"/>
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
person Mitch    schedule 29.10.2013