【问题标题】:wpf custom TabControlwpf 自定义 TabControl
【发布时间】:2011-02-10 18:34:08
【问题描述】:

我真的只想要一个 TabControl,它具有可关闭的自定义 TabItems,基于来自 here 的代码。

我以为this question和我的一样,但是下面的代码和xaml的组合留下了空白标签。

public class ClosableTabControl : TabControl
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        return new ClosableTabItem();
    }
}

        <uc:ClosableTabControl x:Name="Items" Grid.Column="1">
            <uc:ClosableTabControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding DisplayName}" />
                </DataTemplate>
            </uc:ClosableTabControl.ItemTemplate>
            <uc:ClosableTabControl.ContentTemplate>
                <DataTemplate>
                    <ContentControl>
                        <local:EmpView DataContext="{Binding ., Mode=TwoWay}"/>
                    </ContentControl>
                </DataTemplate>
            </uc:ClosableTabControl.ContentTemplate>
        </uc:ClosableTabControl>

xaml 适用于 TabControl(而不是 uc:ClosableTabControl)。

让 TabControl 具有针对儿童的 ClosableTabItems 的最佳方式是什么?

干杯,
浆果

附言我会发布 ClosableTabItem 的代码,但我想在第一篇文章中降低噪音水平。如果有帮助,请让我发布。

【问题讨论】:

  • 您可以记住我的 TabControl 示例,我使用了 TabControl.ItemTemplate 和一个绑定到 ViewModel 中的命令的关闭按钮。我的解决方案的优点是可以使用保存或取消等内部按钮关闭选项卡项。
  • 这是一个 silverlight 示例,但无论如何,这里是链接:stackoverflow.com/questions/4828661/… 如果您准备好使用如此大量的 ViewModel,我可以将此示例重写为 WPF。
  • 嘿漩涡。我现在没有时间再看你的例子,但我记得你有一个关闭按钮。我在问题中引用的 ClosableTab 代码具有在选择或不选择时隐藏/显示按钮的优点。我想你的可能是声明式的,是吗?请稍后再回来查看 - 留下一些我也可以标记为答案的内容!
  • 我有一个 TabControl,它有一个关闭按钮,其行为与 Visual Studio 2010 选项卡相同:鼠标悬停和选中时可见,默认情况下不可见。当我找到它时,我会发布它作为答案。
  • 虽然我的代码示例看起来过度设计,但这些样式确实用于 Visual Studio 的 UI(我使用反射器获得它们)。因此,在某些时候,这是专业人士的选择。还有一点不是关于这个关于 SO 上 cmets 的问题的评论:它只通知是否使用带有名称的 @ 符号。这很奇怪,我需要很多时间来意识到这个错误。反正已经过去了。

标签: wpf silverlight tabcontrol


【解决方案1】:

这是我的 TabControl 的截图:

首先,关闭按钮是一个自定义控件,允许为悬停和按下状态使用不同的颜色。

Add -> New Item -> Custom Control -> GlyphButton

GlyphButton.cs

public class GlyphButton : Button
{
    public static readonly DependencyProperty GlyphForegroundProperty = DependencyProperty.Register("GlyphForeground", typeof(Brush), typeof(GlyphButton));
    public static readonly DependencyProperty HoverBackgroundProperty = DependencyProperty.Register("HoverBackground", typeof(Brush), typeof(GlyphButton));
    public static readonly DependencyProperty HoverBorderBrushProperty = DependencyProperty.Register("HoverBorderBrush", typeof(Brush), typeof(GlyphButton));
    public static readonly DependencyProperty HoverBorderThicknessProperty = DependencyProperty.Register("HoverBorderThickness", typeof(Thickness), typeof(GlyphButton));
    public static readonly DependencyProperty HoverForegroundProperty = DependencyProperty.Register("HoverForeground", typeof(Brush), typeof(GlyphButton));
    public static readonly DependencyProperty PressedBackgroundProperty = DependencyProperty.Register("PressedBackground", typeof(Brush), typeof(GlyphButton));
    public static readonly DependencyProperty PressedBorderBrushProperty = DependencyProperty.Register("PressedBorderBrush", typeof(Brush), typeof(GlyphButton));
    public static readonly DependencyProperty PressedBorderThicknessProperty = DependencyProperty.Register("PressedBorderThickness", typeof(Thickness), typeof(GlyphButton));
    public static readonly DependencyProperty PressedForegroundProperty = DependencyProperty.Register("PressedForeground", typeof(Brush), typeof(GlyphButton));

    static GlyphButton()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(GlyphButton), new FrameworkPropertyMetadata(typeof(GlyphButton)));
    }

    public Brush GlyphForeground
    {
        get
        {
            return (Brush)base.GetValue(GlyphForegroundProperty);
        }
        set
        {
            base.SetValue(GlyphForegroundProperty, value);
        }
    }

    public Brush HoverBackground
    {
        get
        {
            return (Brush)base.GetValue(HoverBackgroundProperty);
        }
        set
        {
            base.SetValue(HoverBackgroundProperty, value);
        }
    }

    public Brush HoverBorderBrush
    {
        get
        {
            return (Brush)base.GetValue(HoverBorderBrushProperty);
        }
        set
        {
            base.SetValue(HoverBorderBrushProperty, value);
        }
    }

    public Thickness HoverBorderThickness
    {
        get
        {
            return (Thickness)base.GetValue(HoverBorderThicknessProperty);
        }
        set
        {
            base.SetValue(HoverBorderThicknessProperty, value);
        }
    }

    public Brush HoverForeground
    {
        get
        {
            return (Brush)base.GetValue(HoverForegroundProperty);
        }
        set
        {
            base.SetValue(HoverForegroundProperty, value);
        }
    }

    public Brush PressedBackground
    {
        get
        {
            return (Brush)base.GetValue(PressedBackgroundProperty);
        }
        set
        {
            base.SetValue(PressedBackgroundProperty, value);
        }
    }

    public Brush PressedBorderBrush
    {
        get
        {
            return (Brush)base.GetValue(PressedBorderBrushProperty);
        }
        set
        {
            base.SetValue(PressedBorderBrushProperty, value);
        }
    }

    public Thickness PressedBorderThickness
    {
        get
        {
            return (Thickness)base.GetValue(PressedBorderThicknessProperty);
        }
        set
        {
            base.SetValue(PressedBorderThicknessProperty, value);
        }
    }

    public Brush PressedForeground
    {
        get
        {
            return (Brush)base.GetValue(PressedForegroundProperty);
        }
        set
        {
            base.SetValue(PressedForegroundProperty, value);
        }
    }
}

主题/Generic.xaml

<Style TargetType="{x:Type local:GlyphButton}">
    <Setter Property="Width" Value="{Binding Path=ActualHeight, RelativeSource={RelativeSource Self}}" />
    <Setter Property="Foreground" Value="{Binding Path=GlyphForeground, RelativeSource={RelativeSource Self}}" />
    <Setter Property="Background" Value="Transparent" />
    <Setter Property="BorderBrush" Value="Transparent" />
    <Setter Property="BorderThickness" Value="1" />
    <Setter Property="Focusable" Value="false" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:GlyphButton}">
                <Border Name="Border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                    <ContentPresenter />
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="true">
                        <Setter TargetName="Border" Value="{Binding HoverBackground , RelativeSource={RelativeSource TemplatedParent}}" Property="Background" />
                        <Setter TargetName="Border" Value="{Binding HoverBorderBrush , RelativeSource={RelativeSource TemplatedParent}}" Property="BorderBrush" />
                        <Setter TargetName="Border" Value="{Binding HoverBorderThickness , RelativeSource={RelativeSource TemplatedParent}}" Property="BorderThickness" />
                        <Setter Value="{Binding HoverForeground , RelativeSource={RelativeSource TemplatedParent}}" Property="Foreground" />
                    </Trigger>
                    <Trigger Property="IsPressed" Value="True">
                        <Setter TargetName="Border" Value="{Binding PressedBackground , RelativeSource={RelativeSource TemplatedParent}}" Property="Background" />
                        <Setter TargetName="Border" Value="{Binding PressedBorderBrush , RelativeSource={RelativeSource TemplatedParent}}" Property="BorderBrush" />
                        <Setter TargetName="Border" Value="{Binding PressedBorderThickness , RelativeSource={RelativeSource TemplatedParent}}" Property="BorderThickness" />
                        <Setter Value="{Binding PressedForeground , RelativeSource={RelativeSource TemplatedParent}}" Property="Foreground" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

TabItem 的样式。您可以将其放在任何位置,App.xaml 或资源字典中。

TabItem 样式

<SolidColorBrush x:Key="FileTabTextKey" Color="#ffffff"/>

<SolidColorBrush x:Key="ToolWindowButtonHoverActiveKey" Color="#fffcf4"/>
<SolidColorBrush x:Key="ToolWindowButtonHoverActiveBorderKey" Color="#e5c365"/>
<SolidColorBrush x:Key="ToolWindowButtonHoverActiveGlyphKey" Color="#000000"/>
<SolidColorBrush x:Key="ToolWindowButtonDownKey" Color="#ffe8a6"/>
<SolidColorBrush x:Key="ToolWindowButtonDownBorderKey" Color="#e5c365"/>
<SolidColorBrush x:Key="ToolWindowButtonDownActiveGlyphKey" Color="#000000"/>

<LinearGradientBrush x:Key="FileTabHotGradientKey">
    <GradientStop Color="#707776"/>
    <GradientStop Color="#4b5c74"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="FileTabHotBorderKey" Color="#9ba7b7"/>
<SolidColorBrush x:Key="FileTabHotTextKey" Color="#ffffff"/>
<SolidColorBrush x:Key="FileTabHotGlyphKey" Color="#ced4dd"/>

<LinearGradientBrush x:Key="FileTabSelectedGradientKey" StartPoint="0.5,0" EndPoint="0.5,1">
    <GradientStop Color="#fffcf4"/>
    <GradientStop Color="#fff3cd" Offset="0.5"/>
    <GradientStop Color="#ffe8a6" Offset="0.5"/>
    <GradientStop Color="#ffe8a6" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="FileTabSelectedTextKey" Color="#000000"/>
<SolidColorBrush x:Key="FileTabSelectedGlyphKey" Color="#75633d"/>

<Style x:Key="OrangeTabItem" TargetType="{x:Type TabItem}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TabItem}">
                <Border AllowDrop="true" ToolTip="{Binding Title}">
                    <Border Name="Border" Background="Transparent" BorderBrush="Transparent" BorderThickness="1,1,1,0" CornerRadius="2,2,0,0">
                        <DockPanel x:Name="TitlePanel" TextElement.Foreground="{StaticResource FileTabTextKey}">
                            <controls:GlyphButton x:Name="HideButton" 
                            DockPanel.Dock="Right" 
                            GlyphForeground="Transparent" 
                            HoverBackground="{StaticResource ToolWindowButtonHoverActiveKey}" 
                            HoverBorderBrush="{StaticResource ToolWindowButtonHoverActiveBorderKey}" 
                            HoverForeground="{StaticResource ToolWindowButtonHoverActiveGlyphKey}" 
                            PressedBackground="{StaticResource ToolWindowButtonDownKey}" 
                            PressedBorderBrush="{StaticResource ToolWindowButtonDownBorderKey}" 
                            PressedForeground="{StaticResource ToolWindowButtonDownActiveGlyphKey}"
                            HoverBorderThickness="1" PressedBorderThickness="1" Margin="3,2,3,4" 
                            Command="{Binding RequestCloseCommand}"
                            CommandParameter="{Binding}"
                            ToolTip="Close">
                                <Path x:Name="CloseButtonStroke" Width="10" Height="8" Stretch="Uniform" Data="F1 M 0,0 L 2,0 5,3 8,0 10,0 6,4 10,8 8,8 5,5 2,8 0,8 4,4 0,0 Z" 
                                Fill="{Binding Path=(TextElement.Foreground), RelativeSource={RelativeSource Self}}" />
                            </controls:GlyphButton>

                            <ContentPresenter x:Name="Content" HorizontalAlignment="Stretch" Margin="4,2,4,4" VerticalAlignment="Stretch" RecognizesAccessKey="true" ContentSource="Header" />
                        </DockPanel>
                    </Border>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="true">
                        <Setter TargetName="Border" Value="{StaticResource FileTabHotGradientKey}" Property="Background" />
                        <Setter TargetName="Border" Value="{StaticResource FileTabHotBorderKey}" Property="BorderBrush" />
                        <Setter TargetName="TitlePanel" Value="{StaticResource FileTabHotTextKey}" Property="TextElement.Foreground" />
                        <Setter TargetName="HideButton" Value="{StaticResource FileTabHotGlyphKey}" Property="GlyphForeground" />
                    </Trigger>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter Property="Panel.ZIndex" Value="1" />
                        <Setter TargetName="Border" Value="{StaticResource FileTabSelectedGradientKey}" Property="Background" />
                        <Setter TargetName="Border" Value="{StaticResource FileTabSelectedGradientKey}" Property="BorderBrush" />
                        <Setter TargetName="Border" Property="BorderThickness" Value="0" />
                        <Setter TargetName="Border" Property="Padding" Value="0,1,0,0" />
                        <Setter TargetName="HideButton" Property="Margin" Value="3" />
                        <Setter TargetName="TitlePanel" Value="{StaticResource FileTabSelectedTextKey}" Property="TextElement.Foreground" />
                        <Setter TargetName="HideButton" Value="{StaticResource FileTabSelectedGlyphKey}" Property="GlyphForeground" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

现在是Main Window。我的TabControl 需要蓝色背景,但您可以根据需要更改颜色。

    <Grid Background="#FF293955">
    <TabControl ItemsSource="{Binding Items}" ItemContainerStyle="{StaticResource OrangeTabItem}">
        <TabControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Title}"/>
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.ContentTemplate>
            <DataTemplate>
                <ContentControl Content="{Binding Content}"/>
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>
</Grid>

最后还有一个重要的说明:代表TabItem 的模型必须包含命令RequestCloseCommand

两个ViewModels的示例

public class MainViewModel
{
    public MainViewModel()
    {
        this.Items = new ObservableCollection<TabItemViewModel>
                     {
                         new TabItemViewModel("Tab 1", OnItemRequestClose),
                         new TabItemViewModel("Tab item 2", OnItemRequestClose)
                     };
    }

    public ObservableCollection<TabItemViewModel> Items { get; set; }

    public void OnItemRequestClose(TabItemViewModel item)
    {
        this.Items.Remove(item);
    }
}

public class TabItemViewModel
{
    public TabItemViewModel(string title, Action<TabItemViewModel> onClose)
    {
        this.Title = title;
        this.RequestCloseCommand = new SimpleCommand(obj => onClose(this));
        this.Content = "Test content " + title;
    }

    public string Title { get; set; }

    public ICommand RequestCloseCommand { get; set; }

    public object Content { get; set; }
}

我考虑过使用RoutedUICommand,但是修改这种类型的命令需要很多时间。所以这个解决方案是最适合我现在的。

【讨论】:

  • @vortex。天哪,这个比我给你的点+答案更有价值。很棒的作品!
  • @vortex。当我有机会时,我会提出另一个你可以回答的问题;还没有玩太多内容,但它看起来您的解决方案需要在代码中实例化用户控件。这是真的吗?
  • @Berryl 是的,这是真的,因为我在不同的选项卡中有不同的视图。例如,第一个选项卡有一个 DataGrid;第二个选项卡 - 文本框、组合框和保存按钮;第三个选项卡 - 树视图。但我知道您在选项卡中拥有相同的视图,因此在这种情况下最好不要在代码中使用 UserControl。
  • @Berryl 我从未使用过框架,因为它们有很多我几乎不使用的功能。依赖注入、引导程序等。我按文件夹分隔模块,我认为这与按程序集分隔没有区别。
  • @Berryl:如果您希望进一步奖励他的回答,您可以随时打开赏金并立即将其奖励给 vorrtex。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-03
  • 2016-01-01
  • 2011-02-01
  • 2018-04-21
相关资源
最近更新 更多