效果图
控件功能:有关闭、新增选项卡按钮,功能栏溢出支持左右滑动。该控件主要应用绑定数据,批量设置某一功能选项。
封装过程使用NuGet:
<packages>
<package id="DevExpressMvvm" version="22.1.3" targetFramework="net472" />
<package id="Microsoft.Xaml.Behaviors.Wpf" version="1.1.31" targetFramework="net472" />
<package id="Prism.Core" version="8.1.97" targetFramework="net472" />
<package id="Prism.Wpf" version="8.1.97" targetFramework="net472" />
</packages>
代码部分:
1.CloseButtonExtend.cs
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace HT.ControlWPF
{
public class CloseButtonExtend : Button
{
protected override void OnClick()
{
base.OnClick();
if (!string.IsNullOrEmpty(Name) && Name == "PART_Close_TabItem")
{
TabItem itemclose = FindVisualParent<TabItem>(this);
if (itemclose == null)
{
return;
}
CloseTabControl closeTabControl = FindVisualParent<CloseTabControl>(itemclose);
if (closeTabControl == null)
{
return;
}
if (closeTabControl.ItemsSource == null)
{
closeTabControl.Items.Remove(itemclose);
}
else
{
if (closeTabControl.DataContext == null || itemclose.DataContext == null)
{
return;
}
(closeTabControl.ItemsSource as IList)?.Remove(itemclose.DataContext);
}
}
}
private T FindVisualParent<T>(DependencyObject obj) where T : class
{
while (obj != null)
{
if (obj is T)
return obj as T;
obj = VisualTreeHelper.GetParent(obj);
}
return null;
}
}
}
2.CloseTabControl.cs
using Prism.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace HT.ControlWPF
{
public class CloseTabControl: TabControl
{
/// <summary>
/// 单点移动间距
/// </summary>
public ushort HorizontalOffsetSpan { get; set; } = 50;
public bool HasAddSouceButton
{
get { return (bool)GetValue(HasAddSouceButtonProperty); }
set { SetValue(HasAddSouceButtonProperty, value); }
}
public static readonly DependencyProperty HasAddSouceButtonProperty =
DependencyProperty.Register(nameof(HasAddSouceButton), typeof(bool), typeof(CloseTabControl), new PropertyMetadata(false));
/// <summary>
/// 新增路由
/// </summary>
public static readonly RoutedEvent AddSouceOnClickEvent = EventManager.RegisterRoutedEvent(nameof(AddSouceOnClick), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(CloseTabControl));
public event RoutedEventHandler AddSouceOnClick
{
add
{
AddHandler(AddSouceOnClickEvent, value);
}
remove
{
RemoveHandler(AddSouceOnClickEvent, value);
}
}
/// <summary>
/// 左移动
/// </summary>
public ICommand LeftMoveCommand
{
get
{
return new DelegateCommand<ScrollViewer>((scrollViewer) =>
{
scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset - HorizontalOffsetSpan);
});
}
}
/// <summary>
/// 右移动
/// </summary>
public ICommand RightMoveCommand
{
get
{
return new DelegateCommand<ScrollViewer>((scrollViewer) =>
{
scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset+ HorizontalOffsetSpan);
});
}
}
public ICommand AddSouceCommand
{
get
{
return new DelegateCommand(() =>
{
RoutedEventArgs arg = new RoutedEventArgs();
arg.RoutedEvent = AddSouceOnClickEvent;
RaiseEvent(arg);
});
}
}
}
}
3.CloseTabControlHelper.cs
using System.Windows;
namespace HT.ControlWPF
{
public static class CloseTabControlHelper
{
public static bool GetHasCloseButton(DependencyObject obj)
{
return (bool)obj.GetValue(HasCloseButtonProperty);
}
public static void SetHasCloseButton(DependencyObject obj, bool value)
{
obj.SetValue(HasCloseButtonProperty, value);
}
public static readonly DependencyProperty HasCloseButtonProperty =
DependencyProperty.RegisterAttached("HasCloseButton", typeof(bool), typeof(CloseTabControlHelper), new PropertyMetadata(true));
}
}
4.App.xaml
<Application
x:Class="HT.ControlWPF.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:HT.ControlWPF"
StartupUri="MainWindow.xaml">
<Application.Resources>
<SolidColorBrush x:Key="TabItem.Selected.Background" Color="Transparent" />
<SolidColorBrush x:Key="TabItem.Static.Background" Color="#99CCFF" />
<SolidColorBrush x:Key="TabItem.MouseOver.Background" Color="#bee6fd" />
<SolidColorBrush x:Key="Static.Foreground" Color="#FFFFFF" />
<!-- 关闭按钮样式 -->
<Style TargetType="{x:Type local:CloseButtonExtend}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" SnapsToDevicePixels="true">
<ContentPresenter
x:Name="contentPresenter"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Focusable="False"
RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="border" Property="Background" Value="#FFBEE6FD" />
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="border" Property="Background" Value="#FFC4E5F6" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:CloseTabControl}">
<Setter Property="Padding" Value="2" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Background" Value="{StaticResource TabItem.Selected.Background}" />
<Setter Property="BorderBrush" Value="{StaticResource TabItem.Selected.Background}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CloseTabControl}">
<Grid
x:Name="templateRoot"
ClipToBounds="true"
KeyboardNavigation.TabNavigation="Local"
SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="ColumnDefinition0" />
<ColumnDefinition x:Name="ColumnDefinition1" Width="0" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition x:Name="RowDefinition0" Height="Auto" />
<RowDefinition x:Name="RowDefinition1" Height="*" />
</Grid.RowDefinitions>
<!-- 头部 -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- 左滑动按钮 -->
<Button
Grid.Column="0"
Padding="5,0"
Background="{StaticResource TabItem.Static.Background}"
BorderThickness="0"
Command="{Binding LeftMoveCommand, RelativeSource={RelativeSource TemplatedParent}}"
CommandParameter="{Binding ElementName=_scrollViewer}"
Content="<"
Foreground="{StaticResource Static.Foreground}" />
<!-- 标题 -->
<ScrollViewer
x:Name="_scrollViewer"
Grid.Column="1"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden">
<TabPanel
x:Name="headerPanel"
Panel.ZIndex="1"
Background="{StaticResource TabItem.Static.Background}"
IsItemsHost="true"
KeyboardNavigation.TabIndex="1" />
</ScrollViewer>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<!-- 右滑动按钮 -->
<Button
Padding="5,0"
Background="{StaticResource TabItem.Static.Background}"
BorderThickness="0"
Command="{Binding RightMoveCommand, RelativeSource={RelativeSource TemplatedParent}}"
CommandParameter="{Binding ElementName=_scrollViewer}"
Content=">"
Foreground="{StaticResource Static.Foreground}" />
<Button
x:Name="PART_AddButton"
Padding="5,0"
Background="{StaticResource TabItem.Static.Background}"
BorderThickness="0"
Command="{Binding AddSouceCommand, RelativeSource={RelativeSource TemplatedParent}}"
CommandParameter="{Binding ElementName=_scrollViewer}"
Content="新增"
Foreground="{StaticResource Static.Foreground}" />
</StackPanel>
</Grid>
<!-- 内容呈显者 -->
<Border
x:Name="contentPanel"
Grid.Row="1"
Grid.Column="0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
KeyboardNavigation.DirectionalNavigation="Contained"
KeyboardNavigation.TabIndex="2"
KeyboardNavigation.TabNavigation="Local">
<ContentPresenter
x:Name="PART_SelectedContentHost"
Margin="{TemplateBinding Padding}"
ContentSource="SelectedContent"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding HasAddSouceButton, RelativeSource={RelativeSource Mode=Self}}" Value="True">
<Setter TargetName="PART_AddButton" Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding HasAddSouceButton, RelativeSource={RelativeSource Mode=Self}}" Value="False">
<Setter TargetName="PART_AddButton" Property="Visibility" Value="Collapsed" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TabItemBaseStyle" TargetType="{x:Type TabItem}">
<Setter Property="local:CloseTabControlHelper.HasCloseButton" Value="True" />
<Setter Property="Foreground" Value="{StaticResource Static.Foreground}" />
<Setter Property="Background" Value="{StaticResource TabItem.Static.Background}" />
<Setter Property="BorderBrush" Value="{StaticResource TabItem.MouseOver.Background}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="FontSize" Value="14" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="Width" Value="Auto" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid
x:Name="templateRoot"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="true">
<StackPanel Orientation="Horizontal">
<ContentPresenter
x:Name="contentPresenter"
Margin="5,8"
HorizontalAlignment="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
VerticalAlignment="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
ContentSource="Header"
Focusable="False"
RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<!-- 关闭按钮 -->
<local:CloseButtonExtend
x:Name="PART_Close_TabItem"
Margin="2,0,2,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="×"
Foreground="{TemplateBinding Foreground}" />
</StackPanel>
</Grid>
<ControlTemplate.Triggers>
<!-- 是否隐藏关闭按钮 -->
<Trigger Property="local:CloseTabControlHelper.HasCloseButton" Value="True">
<Setter TargetName="PART_Close_TabItem" Property="Visibility" Value="Visible" />
</Trigger>
<Trigger Property="local:CloseTabControlHelper.HasCloseButton" Value="False">
<Setter TargetName="PART_Close_TabItem" Property="Visibility" Value="Collapsed" />
</Trigger>
<!-- 鼠标悬停 -->
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Mode=Self}}" Value="true">
<Setter TargetName="templateRoot" Property="Background" Value="{StaticResource TabItem.MouseOver.Background}" />
</DataTrigger>
<!-- 选中选项卡 -->
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Mode=Self}}" Value="true">
<Setter Property="Panel.ZIndex" Value="1" />
<Setter Property="Margin" Value="-2,-2,-2,0" />
<Setter TargetName="templateRoot" Property="Background" Value="{StaticResource TabItem.MouseOver.Background}" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style BasedOn="{StaticResource TabItemBaseStyle}" TargetType="{x:Type TabItem}" />
</Application.Resources>
</Application>
5.使用
MainWindow.xaml
<Window
x:Class="HT.ControlWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:HT.ControlWPF"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
FontSize="14"
mc:Ignorable="d">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<StackPanel>
<local:CloseTabControl DisplayMemberPath="Header">
<TabItem Header="标题1">
<StackPanel Height="100">
<TextBlock Foreground="Red" Text="标题1内容" />
</StackPanel>
</TabItem>
<TabItem Header="标题2">
<StackPanel Height="100">
<TextBlock Text="标题2内容" />
</StackPanel>
</TabItem>
</local:CloseTabControl>
<local:CloseTabControl
AddSouceOnClick="{DXEvent 'AddSouceOnClick()'}"
DisplayMemberPath="Header"
HasAddSouceButton="True"
HorizontalOffsetSpan="20"
ItemsSource="{Binding ClaseTabItemInfos, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TabControl.Resources>
<Style BasedOn="{StaticResource TabItemBaseStyle}" TargetType="{x:Type TabItem}">
<!-- 设置是否有关闭按钮 -->
<Setter Property="local:CloseTabControlHelper.HasCloseButton" Value="True" />
</Style>
</TabControl.Resources>
<local:CloseTabControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</local:CloseTabControl.ContentTemplate>
</local:CloseTabControl>
</StackPanel>
</Window>
MainWindowViewModel.cs
using Prism.Commands;
using Prism.Mvvm;
using System.Collections.ObjectModel;
using System.Windows.Input;
namespace HT.ControlWPF
{
public class MainWindowViewModel : BindableBase
{
private ObservableCollection<ClaseTabItemInfo> claseTabItemInfos = new ObservableCollection<ClaseTabItemInfo>()
{
new ClaseTabItemInfo ()
{
Header="标题1",
Name="标题1内容"
},
new ClaseTabItemInfo ()
{
Header="标题2",
Name="标题2内容"
},
new ClaseTabItemInfo ()
{
Header="标题3",
Name="标题3内容"
},
new ClaseTabItemInfo ()
{
Header="标题4",
Name="标题4内容"
}
};
public ObservableCollection<ClaseTabItemInfo> ClaseTabItemInfos
{
get
{
return claseTabItemInfos;
}
set
{
claseTabItemInfos = value;
RaisePropertyChanged(nameof(ClaseTabItemInfos));
}
}
public void AddSouceOnClick()
{
ClaseTabItemInfos.Add(new ClaseTabItemInfo()
{
Header = "新的Header",
Name = "Hello"
});
}
}
public class ClaseTabItemInfo : BindableBase
{
private string header;
public string Header
{
get
{
return header;
}
set
{
header = value;
RaisePropertyChanged(nameof(Header));
}
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
RaisePropertyChanged(nameof(Name));
}
}
}
}