实现Avalonia平台下低配版的Dock控件:实现TabControl的可关闭
在弄一个项目,在WPF下用Dock控件,在Avalonia平台下实现也有一个Dock控件,但用起来有点复杂。
Install-Package Dock.Avalonia
Install-Package Dock.Model.Mvvm
感兴趣的可以访问网站了解:https://github.com/wieslawsoltes/Dock
其实本身用的比较简单,所以就想着,用TabControl来改一下。实现最基本的功能。
需要对TabControl和TabItem进行改造
TabControlEx
using Avalonia;
using Avalonia.Controls;
namespace CommandTerminal.Controls;
public class TabControlEx : TabControl
{
private ICommand _closeItemCommand;
public ICommand CloseItemCommand
{
get => _closeItemCommand;
private set => SetAndRaise(CloseItemCommandProperty, ref _closeItemCommand, value);
}
public static readonly DirectProperty<TabControlEx, ICommand> CloseItemCommandProperty =
AvaloniaProperty.RegisterDirect<TabControlEx, ICommand>(
nameof(CloseItemCommand),
o => o.CloseItemCommand,
(o, v) => o.CloseItemCommand = v);
public TabControlEx()
{
_closeItemCommand = new SimpleParamActionCommand(CloseItem);
}
private void CloseItem(object? tabItemSource)
{
ArgumentNullException.ThrowIfNull(tabItemSource);
if (tabItemSource is not TabItemEx tabItem)
return;
RemoveItem(tabItem);
}
private void RemoveItem(TabItemEx container)
{
if (container == null || Items == null)
{
Debug.Assert(false);
return;
}
Items.Remove(container);
}
private void SetSelectedNewTab(IList items, int removedItemIndex) =>
SelectedItem = removedItemIndex == items.Count ? items[^1] : items[removedItemIndex];
}
public class SimpleParamActionCommand(Action<object?> action) : ICommand
{
public bool CanExecute(object? parameter) => true;
public void Execute(object? parameter) => action.Invoke(parameter);
public event EventHandler? CanExecuteChanged;
}
TabItemEx
using Avalonia;
using Avalonia.Controls;
namespace CommandTerminal.Controls;
public class TabItemEx : TabItem
{
public string ContentId { get; set; }
public bool CanClose
{
get => GetValue(CanCloseProperty);
set => SetValue(CanCloseProperty, value);
}
public static readonly StyledProperty<bool> CanCloseProperty =
AvaloniaProperty.Register<TabItemEx, bool>(nameof(CanClose), defaultValue: true);
}
加载控件样式
在App.axaml文件里添加:
<Application.Resources>
<Thickness x:Key="TabControlTopPlacementItemMargin">0 0 0 2</Thickness>
<x:Double x:Key="TabItemMinHeight">48</x:Double>
<x:Double x:Key="TabItemVerticalPipeHeight">24</x:Double>
<x:Double x:Key="TabItemPipeThickness">2</x:Double>
<ControlTheme x:Key="CloseItemCommandButton" TargetType="Button">
<Setter Property="Content">
<Template>
<Viewbox
Width="16"
Height="16"
Stretch="Uniform">
<Canvas Width="24" Height="24">
<Path Data="M13.46,12L19,17.54V19H17.54L12,13.46L6.46,19H5V17.54L10.54,12L5,6.46V5H6.46L12,10.54L17.54,5H19V6.46L13.46,12Z" Fill="{Binding $parent[Button].Foreground}" />
</Canvas>
</Viewbox>
</Template>
</Setter>
<!--<Setter Property="Background" Value="{DynamicResource ButtonBackground}" />-->
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ButtonBorderThemeThickness}" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Padding" Value="2" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="RenderTransform" Value="none" />
<Setter Property="Transitions">
<Transitions>
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:.075" />
</Transitions>
</Setter>
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter
x:Name="PART_ContentPresenter"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
CornerRadius="{TemplateBinding CornerRadius}"
Foreground="{TemplateBinding Foreground}"
RecognizesAccessKey="True" />
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
<Style Selector="^:pressed">
<Setter Property="RenderTransform" Value="scale(0.98)" />
</Style>
<Style Selector="^:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
<Style Selector="^:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style>
<Style Selector="^.accent">
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrush}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForeground}" />
</Style>
<Style Selector="^:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundPointerOver}" />
</Style>
<Style Selector="^:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPressed}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundPressed}" />
</Style>
<Style Selector="^:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundDisabled}" />
</Style>
</Style>
</ControlTheme>
<ControlTheme x:Key="{x:Type ctl:TabControlEx}" TargetType="ctl:TabControlEx">
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="{DynamicResource TabItemMargin}" />
<Setter Property="Background" Value="{DynamicResource TabControlBackground}" />
<Setter Property="Template">
<ControlTemplate>
<Border
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<DockPanel>
<ItemsPresenter
Name="PART_ItemsPresenter"
DockPanel.Dock="{TemplateBinding TabStripPlacement}"
ItemsPanel="{TemplateBinding ItemsPanel}" />
<ContentPresenter
Name="PART_SelectedContentHost"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding SelectedContent}"
ContentTemplate="{TemplateBinding SelectedContentTemplate}" />
</DockPanel>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^[TabStripPlacement=Left] /template/ ItemsPresenter#PART_ItemsPresenter > WrapPanel">
<Setter Property="Orientation" Value="Vertical" />
</Style>
<Style Selector="^[TabStripPlacement=Right] /template/ ItemsPresenter#PART_ItemsPresenter > WrapPanel">
<Setter Property="Orientation" Value="Vertical" />
</Style>
<Style Selector="^[TabStripPlacement=Top] /template/ ItemsPresenter#PART_ItemsPresenter">
<Setter Property="Margin" Value="{DynamicResource TabControlTopPlacementItemMargin}" />
</Style>
</ControlTheme>
<ControlTheme x:Key="{x:Type ctl:TabItemEx}" TargetType="ctl:TabItemEx">
<Setter Property="FontSize" Value="{DynamicResource TabItemHeaderFontSize}" />
<Setter Property="FontWeight" Value="{DynamicResource TabItemHeaderThemeFontWeight}" />
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundUnselected}" />
<Setter Property="Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselected}" />
<Setter Property="Padding" Value="{DynamicResource TabItemHeaderMargin}" />
<Setter Property="Margin" Value="0" />
<Setter Property="MinHeight" Value="{DynamicResource TabItemMinHeight}" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<ControlTemplate>
<Border
Name="PART_LayoutRoot"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Panel>
<Grid ColumnDefinitions="* Auto">
<ContentPresenter
Name="PART_ContentPresenter"
Grid.Column="0"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
RecognizesAccessKey="True" />
<Button
Grid.Column="1"
Margin="2,0,0,0"
Command="{Binding $parent[ctl:TabControlEx].CloseItemCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent}}"
IsVisible="{TemplateBinding CanClose}"
Theme="{StaticResource CloseItemCommandButton}" />
</Grid>
<Border
Name="PART_SelectedPipe"
Background="{DynamicResource TabItemHeaderSelectedPipeFill}"
CornerRadius="{DynamicResource ControlCornerRadius}"
IsVisible="False" />
</Panel>
</Border>
</ControlTemplate>
</Setter>
<!-- Selected state -->
<!-- We don't use selector to PART_LayoutRoot, so developer can override selected item background with TabStripItem.Background -->
<Style Selector="^:selected">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundSelected}" />
<Setter Property="Foreground" Value="{DynamicResource TabItemHeaderForegroundSelected}" />
</Style>
<Style Selector="^:selected /template/ Border#PART_SelectedPipe">
<Setter Property="IsVisible" Value="True" />
</Style>
<!-- PointerOver state -->
<Style Selector="^:pointerover /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundUnselectedPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" />
</Style>
<!-- Selected PointerOver state -->
<Style Selector="^:selected:pointerover /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundSelectedPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundSelectedPointerOver}" />
</Style>
<!-- Pressed state -->
<Style Selector="^:pressed /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundUnselectedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPressed}" />
</Style>
<!-- Selected Pressed state -->
<Style Selector="^:selected:pressed /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundSelectedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundSelectedPressed}" />
</Style>
<!-- Disabled state -->
<Style Selector="^:disabled /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundDisabled}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundDisabled}" />
</Style>
<!-- TabStripPlacement States Group -->
<Style Selector="^[TabStripPlacement=Left] /template/ Border#PART_SelectedPipe">
<Setter Property="Width" Value="{DynamicResource TabItemPipeThickness}" />
<Setter Property="Height" Value="{DynamicResource TabItemVerticalPipeHeight}" />
<Setter Property="Margin" Value="0,0,2,0" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style Selector="^[TabStripPlacement=Left] /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Margin" Value="8,0,0,0" />
</Style>
<Style Selector="^[TabStripPlacement=Top] /template/ Border#PART_SelectedPipe, ^[TabStripPlacement=Bottom] /template/ Border#PART_SelectedPipe">
<Setter Property="Height" Value="{DynamicResource TabItemPipeThickness}" />
<Setter Property="Margin" Value="0,0,0,2" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Bottom" />
</Style>
<Style Selector="^[TabStripPlacement=Right] /template/ Border#PART_SelectedPipe">
<Setter Property="Width" Value="{DynamicResource TabItemPipeThickness}" />
<Setter Property="Height" Value="{DynamicResource TabItemVerticalPipeHeight}" />
<Setter Property="Margin" Value="2,0,0,0" />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style Selector="^[TabStripPlacement=Right] /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Margin" Value="0,0,8,0" />
</Style>
<Style Selector="^[TabStripPlacement=Right]">
<Setter Property="HorizontalContentAlignment" Value="Right" />
</Style>
</ControlTheme>
</Application.Resources>
前台使用
添加引用:
xmlns:ctl="clr-namespace:CommandTerminal.Controls"
代码:
<ctl:TabControlEx
x:Name="TabList"
Grid.Row="1"
Grid.Column="1">
<ctl:TabItemEx CanClose="False" Header="遥测监视">
<v:UcTelemetryMonitoring />
</ctl:TabItemEx>
<ctl:TabItemEx Header="发令记录">
<v:UcCommandSendList />
</ctl:TabItemEx>
<ctl:TabItemEx Header="发令查询">
<v:UcCommandHistory />
</ctl:TabItemEx>
</ctl:TabControlEx>
后台使用:
public void ShowLayoutDocument(string contendId, string title, Func<UserControl> func, bool canClose = true)
{
var items = _tabList.Items.Cast<TabItemEx>().ToArray();
var info = items.FirstOrDefault(s => s.ContentId == contendId);
if (info != null)
{
_tabList.SelectedItem = info;
return;
}
var item = new TabItemEx();
item.ContentId = contendId;
item.Header = title;
item.CanClose = canClose;
item.Content = func.Invoke();
_tabList.Items.Add(item);
_tabList.SelectedItem = item;
}