在 WPF 中使用 WindowChrome 自定义标题栏

本文将介绍在 WPF 中如何使用 WindowChrome 自定义标题栏。

一、使用 WindowChrome

<Window></Window> 节点里写如下代码便能够完成客户区(Client Area)到非客户区(Non-client Area)的覆盖:

1
2
3
<WindowChrome.WindowChrome>
    <WindowChrome />
</WindowChrome.WindowChrome>
样式已经被遮挡

样式已经被遮挡

现在,为了能够观察到 WindowChrome 各种属性设置的效果,我们为 Window 定义一个新的 Template,里面就是空的,这样就没有什么内容能够遮挡我们设置的样式了。

1
2
3
4
5
<Window.Template>
    <ControlTemplate TargetType="Window">
        <Border />
    </ControlTemplate>
</Window.Template>
没有遮挡的窗口

没有遮挡的窗口

然而即便如此,我们也只解决了系统主题色边框的问题,没有解决调整窗口的拖拽热区问题。而且边框还如此之丑。

1.1 GlassFrameThicknessNonClientFrameEdges

1
2
3
<WindowChrome.WindowChrome>
    <WindowChrome GlassFrameThickness="0 30 0 0" NonClientFrameEdges="Left,Bottom,Right"/>
</WindowChrome.WindowChrome>
设置 GlassFrameThickness 和 NonClientFrameEdges

设置 GlassFrameThickness 和 NonClientFrameEdges

到这里,我们算是模拟得比较像了。

二、定制 Window 的控件模板

WindowChrome 提供客户区内容覆盖到非客户区的能力,所以我们通过定制 WindowControlTemplate 能够在保证原生窗口体验的同时,尽可能定制我们的窗口样式。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<Window.Template>
    <ControlTemplate TargetType="Window">
        <Border Name="RootBorder">
            <Grid Background="Aqua">
                <AdornerDecorator>
                    <ContentPresenter/>
                </AdornerDecorator>
            </Grid>
        </Border>
    </ControlTemplate>
</Window.Template>

<WindowChrome.WindowChrome>
    <WindowChrome GlassFrameThickness="0 30 0 0" NonClientFrameEdges="Left,Bottom,Right"/>
</WindowChrome.WindowChrome>

<Grid>
    <TextBlock Text="12345"></TextBlock>
</Grid>
客户区内容覆盖到非客户区

客户区内容覆盖到非客户区

可以看到 <Grid>(客户区)的内容覆盖到了非客户区,并且默认的“最小化”、“最大化”、“关闭”功能还是在的,也是可以点击的,只是看不见了而已(被客户区覆盖)。

我们调整一下边距,让非客户区域露出来。

1
2
3
4
5
6
7
<Border Name="RootBorder"  Padding="0 30 0 0" BorderBrush="Transparent" BorderThickness="4 0 4 4">
    <Grid Background="Aqua">
        <AdornerDecorator>
            <ContentPresenter/>
        </AdornerDecorator>
    </Grid>
</Border>
调整客户区域的边距

调整客户区域的边距

2.1 定制布局

Grid 分为上下两行,上面一行作为标题栏,下面一行作为内容主体。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>
    <!--标题栏 开始-->
    <Border Grid.Row="0" Height="30" Margin="0 -29 0 0">
        <DockPanel>
            <StackPanel Orientation="Horizontal" DockPanel.Dock="Right">
                <Button Content="最小化"></Button>
                <Button Content="最大化"></Button>
                <Button Content="向下还原"></Button>
                <Button Content="关闭"></Button>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock
                    Text="自定义的标题栏"
                    VerticalAlignment="Center"></TextBlock>
            </StackPanel>
        </DockPanel>
    </Border>
    <!--标题栏 结束-->
    <Grid Grid.Row="1">
        <TextBlock Text="主体内容区域"></TextBlock>
    </Grid>
</Grid>
定制布局

定制布局

现在,点击右上角,还是会触发默认的“最小化”、“最大化”、“关闭”行为。所以,我们需要禁用它的默认行为。

1
2
3
<WindowChrome.WindowChrome>
    <WindowChrome GlassFrameThickness="0 30 0 0" NonClientFrameEdges="Left,Bottom,Right" UseAeroCaptionButtons="False"/>
</WindowChrome.WindowChrome>

2.2 自定义“最小化”、“最大化”、“关闭”事件

定义命令

1
2
3
4
5
6
<Window.CommandBindings>
    <CommandBinding Command="{x:Static SystemCommands.CloseWindowCommand}" CanExecute="CommandBinding_OnCanExecute" Executed="CommandBinding_OnExecuted_CloseWindow"/>
    <CommandBinding Command="{x:Static SystemCommands.MaximizeWindowCommand}" CanExecute="CommandBinding_OnCanExecute" Executed="CommandBinding_OnExecuted_MaximizeWindow"/>
    <CommandBinding Command="{x:Static SystemCommands.MinimizeWindowCommand}" CanExecute="CommandBinding_OnCanExecute" Executed="CommandBinding_OnExecuted_MinimizeWindow"/>
    <CommandBinding Command="{x:Static SystemCommands.RestoreWindowCommand}" CanExecute="CommandBinding_OnCanExecute" Executed="CommandBinding_OnExecuted_RestoreWindow"/>
</Window.CommandBindings>

按钮上绑定命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<Button Name="ButtonMinimizeWindow"  Content="最小化"
        WindowChrome.IsHitTestVisibleInChrome="True"
        Command="{x:Static SystemCommands.MinimizeWindowCommand}"></Button>
<Button Name="ButtonMaximizeWindow" Content="最大化"
        WindowChrome.IsHitTestVisibleInChrome="True"
        Command="{x:Static SystemCommands.MaximizeWindowCommand}"></Button>
<Button Name="ButtonRestoreWindow" Content="向下还原"
        WindowChrome.IsHitTestVisibleInChrome="True"
        Command="{x:Static SystemCommands.RestoreWindowCommand}"></Button>
<Button Name="ButtonCloseWindow" Content="关闭"
        WindowChrome.IsHitTestVisibleInChrome="True"
        Command="{x:Static SystemCommands.CloseWindowCommand}"></Button>

2.3 美化按钮

定义字体资源,参考 https://blog.csdn.net/mybelief321/article/details/102461597

1
2
3
4
5
<Window.Resources>
    <ResourceDictionary>
        <FontFamily x:Key="IconFont">pack://application:,,,/WindowChromeExample;component/#iconfont,</FontFamily>
    </ResourceDictionary>
</Window.Resources>

使用字体资源

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<Button Name="ButtonMinimizeWindow" FontFamily="{StaticResource IconFont}"  Content="&#xe629;"  Width="24" Height="24"
        WindowChrome.IsHitTestVisibleInChrome="True"
        Command="{x:Static SystemCommands.MinimizeWindowCommand}"></Button>
<Button Name="ButtonMaximizeWindow" FontFamily="{StaticResource IconFont}" Content="&#xe653;"  Width="24" Height="24"
        WindowChrome.IsHitTestVisibleInChrome="True"
        Command="{x:Static SystemCommands.MaximizeWindowCommand}"></Button>
<Button Name="ButtonRestoreWindow" FontFamily="{StaticResource IconFont}" Content="&#xe677;"  Width="24" Height="24"
        Visibility="Collapsed"
        WindowChrome.IsHitTestVisibleInChrome="True"
        Command="{x:Static SystemCommands.RestoreWindowCommand}"></Button>
<Button Name="ButtonCloseWindow" FontFamily="{StaticResource IconFont}" Content="&#xeaf2;"  Width="24" Height="24"
        WindowChrome.IsHitTestVisibleInChrome="True"
        Command="{x:Static SystemCommands.CloseWindowCommand}"></Button>
美化按钮

美化按钮

默认的按钮还是会显示,个他加给背景,就看不到了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<Border Grid.Row="0" Height="30" Margin="0 -29 0 0">
    <DockPanel Background="Red">
        <StackPanel Orientation="Horizontal" DockPanel.Dock="Right">
            <Button Name="ButtonMinimizeWindow" FontFamily="{StaticResource IconFont}"  Content="&#xe629;"  Width="24" Height="24"
                    WindowChrome.IsHitTestVisibleInChrome="True"
                    Command="{x:Static SystemCommands.MinimizeWindowCommand}"></Button>
            <Button Name="ButtonMaximizeWindow" FontFamily="{StaticResource IconFont}" Content="&#xe653;"  Width="24" Height="24"
                    WindowChrome.IsHitTestVisibleInChrome="True"
                    Command="{x:Static SystemCommands.MaximizeWindowCommand}"></Button>
            <Button Name="ButtonRestoreWindow" FontFamily="{StaticResource IconFont}" Content="&#xe677;"  Width="24" Height="24"
                    Visibility="Collapsed"
                    WindowChrome.IsHitTestVisibleInChrome="True"
                    Command="{x:Static SystemCommands.RestoreWindowCommand}"></Button>
            <Button Name="ButtonCloseWindow" FontFamily="{StaticResource IconFont}" Content="&#xeaf2;"  Width="24" Height="24"
                    WindowChrome.IsHitTestVisibleInChrome="True"
                    Command="{x:Static SystemCommands.CloseWindowCommand}"></Button>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBlock
                Text="自定义的标题栏"
                VerticalAlignment="Center"></TextBlock>
        </StackPanel>
    </DockPanel>
</Border>
红色背景

红色背景

三、完整代码

3.1 XAML 代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
<Window x:Class="WindowChromeExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="450" Width="800">

    <!--字体资源-->
    <Window.Resources>
        <ResourceDictionary>
            <FontFamily x:Key="IconFont">pack://application:,,,/WindowChromeExample;component/#iconfont,</FontFamily>
        </ResourceDictionary>
    </Window.Resources>

    <!--自定义标题栏 开始-->
    <Window.Template>
        <ControlTemplate TargetType="Window">
            <Border Name="RootBorder"  Padding="0 30 0 0" BorderBrush="Transparent" BorderThickness="4 0 4 4">
                <Grid Background="{TemplateBinding Background}">
                    <AdornerDecorator>
                        <ContentPresenter/>
                    </AdornerDecorator>
                </Grid>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="WindowState" Value="Maximized">
                    <Setter TargetName="RootBorder" Property="BorderThickness" Value="8" />
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Window.Template>
    <!--自定义标题栏 结束-->

    <WindowChrome.WindowChrome>
        <WindowChrome GlassFrameThickness="0 30 0 0" NonClientFrameEdges="Left,Bottom,Right" UseAeroCaptionButtons="False"/>
    </WindowChrome.WindowChrome>

    <!--标题栏按钮事件 开始-->
    <Window.CommandBindings>
        <CommandBinding Command="{x:Static SystemCommands.CloseWindowCommand}" CanExecute="CommandBinding_OnCanExecute" Executed="CommandBinding_OnExecuted_CloseWindow"/>
        <CommandBinding Command="{x:Static SystemCommands.MaximizeWindowCommand}" CanExecute="CommandBinding_OnCanExecute" Executed="CommandBinding_OnExecuted_MaximizeWindow"/>
        <CommandBinding Command="{x:Static SystemCommands.MinimizeWindowCommand}" CanExecute="CommandBinding_OnCanExecute" Executed="CommandBinding_OnExecuted_MinimizeWindow"/>
        <CommandBinding Command="{x:Static SystemCommands.RestoreWindowCommand}" CanExecute="CommandBinding_OnCanExecute" Executed="CommandBinding_OnExecuted_RestoreWindow"/>
    </Window.CommandBindings>
    <!--标题栏按钮事件 结束-->


    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <!--标题栏 开始-->
        <Border Grid.Row="0" Height="30" Margin="0 -29 0 0">
            <DockPanel Background="White">
                <StackPanel Orientation="Horizontal" DockPanel.Dock="Right">
                    <Button
                        Name="ButtonMinimizeWindow"
                        WindowChrome.IsHitTestVisibleInChrome="True"
                        FontFamily="{StaticResource IconFont}"
                        Width="32"
                        Content="&#xe629;"
                        ToolTip="最小化"
                        Command="{x:Static SystemCommands.MinimizeWindowCommand}"></Button>
                    <Button
                        Name="ButtonMaximizeWindow"
                        WindowChrome.IsHitTestVisibleInChrome="True"
                        FontFamily="{StaticResource IconFont}"
                        Width="32"
                        Content="&#xe653;"
                        ToolTip="最大化"
                        Command="{x:Static SystemCommands.MaximizeWindowCommand}"></Button>
                    <Button
                        Name="ButtonRestoreWindow"
                        WindowChrome.IsHitTestVisibleInChrome="True"
                        FontFamily="{StaticResource IconFont}"
                        Width="32"
                        Content="&#xe677;"
                        ToolTip="向下还原"
                        Visibility="Collapsed"
                        Command="{x:Static SystemCommands.RestoreWindowCommand}"></Button>
                    <Button
                        Name="ButtonCloseWindow"
                        WindowChrome.IsHitTestVisibleInChrome="True"
                        FontFamily="{StaticResource IconFont}"
                        Width="32"
                        Content="&#xeaf2;"
                        ToolTip="关闭"
                        Command="{x:Static SystemCommands.CloseWindowCommand}"></Button>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock
                        Text="自定义标题栏"
                        VerticalAlignment="Center"></TextBlock>
                </StackPanel>
            </DockPanel>
        </Border>
        <!--标题栏 结束-->
        <Grid Grid.Row="1">
            <TextBlock Text="主体内容"></TextBlock>
        </Grid>
    </Grid>
</Window>

3.2 C#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
using System;
using System.Windows;
using System.Windows.Input;

namespace WindowChromeExample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            StateChanged += MainWindow_StateChanged;
        }

        private void MainWindow_StateChanged(object? sender, EventArgs e)
        {
            if (WindowState == WindowState.Maximized)
            {
                ButtonRestoreWindow.Visibility = Visibility.Visible;
                ButtonMaximizeWindow.Visibility = Visibility.Collapsed;
            }
            else
            {
                ButtonRestoreWindow.Visibility = Visibility.Collapsed;
                ButtonMaximizeWindow.Visibility = Visibility.Visible;
            }
        }

        private void CommandBinding_OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        private void CommandBinding_OnExecuted_MinimizeWindow(object sender, ExecutedRoutedEventArgs e)
        {
            SystemCommands.MinimizeWindow(this);
        }

        private void CommandBinding_OnExecuted_MaximizeWindow(object sender, ExecutedRoutedEventArgs e)
        {
            SystemCommands.MaximizeWindow(this);
        }

        private void CommandBinding_OnExecuted_RestoreWindow(object sender, ExecutedRoutedEventArgs e)
        {
            SystemCommands.RestoreWindow(this);
        }

        private void CommandBinding_OnExecuted_CloseWindow(object sender, ExecutedRoutedEventArgs e)
        {
            SystemCommands.CloseWindow(this);
        }
    }
}

3.3 最终效果

最终效果

最终效果

源码地址

到这里,已经完整的介绍了在 WPF 中如何使用 WindowChrome 自定义标题栏。

源码地址:https://github.com/astrid9527/WpfWindowChromeExample

参考

(完)