WPF自定义界面WindowChrome

WPF自定义界面WindowChrome

默认WPF的界面其实也还行,就是满足不了日渐增长的需求,界面还是需要有更高的自定义程度,包括标题栏也要能够塞下更多的操作控件。

默认窗口介绍

新建WPF项目,给里面内容设置一点颜色:

image-20211223154246966

默认创建的界面(Win10上的效果),能够看到两块区域,一块是以颜色#0078D4内容区,一块是顶部白色的非内容区,按照官方的说法它们依次是客户区非客户区

客户区就是我们的主体内容,目前里面有一排文字,这没什么好说的, 你想放什么内容,就往这里放就行了。

非客户区里面的东西稍微有一点多,一点一点来列吧:

左上角的应用图标标题

image-20211223155013699

右上角的最小化最大化关闭按钮:

image-20211223155128648

还有一个就是我们窗体的边框线,黑色的那条。

以上都是我们能够看到的,还有一些看不见的窗口行为

缩放标题栏的拖动以及鼠标拖到窗口到桌面边缘位置,可以对窗口进行一个大小的调整:

动画

点击应用图标之后弹出的窗口操作:

image-20211223160124239

以及在窗口拖动和在拖动的过程中中,窗口会自动扩充到对应的屏幕:

动画1

其实呢, 还有一些行为,这里就不做叙述了,下面我们来看看如何自定义

自定义界面方式

WPF中自定义的界面的方式可以分为两种,一种是使用 AllowsTransparency="True"WindowStyle="None",这种呢就相当于直接把原生非客户区给干掉了,然后我们在内容区域自己去实现非客户区,就会导致窗口自定的行为如:缩放拖动停靠边界放大。。。这些功能全都没有了,如果需要的话,是需要自己手动代码添加的。

第二种呢就是使用我们的WindowChrome来自定义界面, 这种方式保留了一个窗口基本的行为,只需要我们重新规划一下客户区非客户区就行了。

两种方式都不复杂,不过界面客户区非客户区都是需要自己去定义的,但是WindowChrome的方式保留了窗口的一些基本行为,显示性能也会比第一种强。

至于二者窗口性能的对比,可以看下这个大佬的文章:
(walterlv 吕毅)WPF 制作高性能的透明背景异形窗口(使用 WindowChrome 而不要使用 AllowsTransparency=True)

使用WindowChrome

两种方式自定义界面没什么太大区别, 这里就以WindowChrome来进行举例。

使用WindowChrome的方式也很简单,直接声明一下就行了:

    <WindowChrome.WindowChrome>
        <WindowChrome />
    </WindowChrome.WindowChrome>
    <Grid Background="#0078D4">
        <TextBlock
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="50"
            FontWeight="Bold"
            Foreground="White"
            Text="丑萌气质狗" />
    </Grid>

得到如下的界面:

image-20211223170623630

这个界面拥有默认窗口的所有窗口行为,就是没有应用图标、标题、操作按钮(最大化、最小化、关闭),其实不是没有,只是被当前Window的默认样式给遮挡了

只需要去除当前Window的样式,就可以看到WindowChrome的样式:

    <Window.Template>
        <ControlTemplate TargetType="{x:Type Window}">
            <Border />
        </ControlTemplate>
    </Window.Template>

image-20211227154025409

作为对比看一下默认的Window是什么样子的:

image-20211227154216344

可以看到,仅仅在关闭的地方,WindowChrome的样式就和默认的Window不一致,操作栏都无法靠边界,而且尝试修改这些样式也不好调整到我们想要的样子,所以我们直接抛弃原来的样式,重写WindowChrome,自己定义客户区非客户区,如果对WindowChrome的样式调整有兴趣,可以看一下:

(walterlv 吕毅)WPF 使用 WindowChrome,在自定义窗口标题栏的同时最大程度保留原生窗口样式(类似 UWP/Chrome)

除了需要这些东西以外, 还需要添加我们自定义的操作按钮,下面我们就要重写一下窗口的样式,自定义里面的客户区非客户区

自定义WindowChrome说明

在重写样式之前呢,先来介绍几个WindowChrome的几个属性:

CaptionHeight:标题栏的操作高度,一般跟随非客户区高度,设置为0表示界面无法响应鼠标的任何操作(拖动窗口, 双击标题栏的最大化最小化。。。 仅仅是行为区域,并不影响外观)。

GlassFrameThickness:用来设置距离区域的厚度,设置为-1,可以使得区域覆盖整个界面

ResizeBorderThickness:可以设置窗口缩放的边框厚度,不便于设置过大。下图是设置为50的效果:

image-20211227164038836

可以看到侵入内容区这么多,依然可以进行窗口缩放。

还有一个很重要的属性,不是给WindowChrome设置的,是给界面中的元素设置的,如果你的元素在CaptionHeight的高度内,是无法响应鼠标事件的,此时需要给元素设置属性WindowChrome.IsHitTestVisibleInChrome="True",很常见的问题,在自定义标题按钮的时候,会发现自己点击不了标题按钮!

自定义WindowChrome

既然是要自定义客户区非客户区,那么我们稍微设计一下(下面示例所有属性均写死,自行更改)

根元素使用Border,我们设置它的边框和颜色,内部使用Grid元素,进行上下两行分割,第一行客户区, 第二行非客户区,第一行的高度改为Auto,第二行默认使用剩下全部区域。

为了撑开非客户区,这里绑定了一个窗口的标题。

大概代码如下

<Window.Template>
        <ControlTemplate TargetType="{x:Type Window}">
            <Border
                x:Name="border"
                Background="White"
                BorderBrush="#0078d4"
                BorderThickness="1"
                UseLayoutRounding="True">
                <Grid>
                    <!--  上下区域 上面是非客户区,下面是客户区  -->
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition />
                    </Grid.RowDefinitions>
                    <!--  非客户区  -->
                    <Grid
                        Grid.Row="0"
                        Background="#0078d4">
                        <TextBlock
                            Padding="10,0,0,0"
                            VerticalAlignment="Center"
                            Text="{TemplateBinding Title}" />
                    </Grid>

                    <!--  客户区  -->
                    <AdornerDecorator Grid.Row="1">
                        <ContentPresenter ClipToBounds="True" />
                    </AdornerDecorator>
                </Grid>
            </Border>
        </ControlTemplate>
</Window.Template>

呈现的效果如下(用装饰器AdornerDecorator包装了一下客户区,为了后期方便添加遮罩层,有文章说明,目前没写,后续弄好了链接再贴过来吧):

image-20211227171937366

客户区没啥说的,你自己想怎么写就怎么写, 主要需要说一下非客户区的图标、标题(已经有了)、操作按钮、自定义按钮,直接贴一下代码,样式这么就不加了,采用自己的样式,主要说明下使用系统的几个命令。

                    <!--  非客户区  -->
                    <Grid
                        Grid.Row="0"
                        Background=" #0078d4">
                        <!--  左右区域 左边是窗体图标文字 右边是操作按钮、最小化、最大化、关闭  -->
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition />
                            <ColumnDefinition Width="Auto" />
                        </Grid.ColumnDefinitions>
                        <!--  图片 标题  -->
                        <StackPanel
                            HorizontalAlignment="Left"
                            VerticalAlignment="Center"
                            Orientation="Horizontal"
                            WindowChrome.IsHitTestVisibleInChrome="True">
                            <Button
                                Width="16"
                                Height="16"
                                Margin="10,0,0,0"
                                Command="{x:Static SystemCommands.ShowSystemMenuCommand}"
                                Content="我是图标" />
                            <TextBlock
                                Margin="5,0,0,0"
                                VerticalAlignment="Center"
                                Text="{TemplateBinding Title}" />
                        </StackPanel>

                        <!--  操作按钮  -->
                        <StackPanel
                            Grid.Column="1"
                            HorizontalAlignment="Right"
                            VerticalAlignment="Top"
                            Orientation="Horizontal"
                            WindowChrome.IsHitTestVisibleInChrome="True">
                            <Button>
                                <Path
                                    Width="12"
                                    Height="12"
                                    Data="F1 M 17.412109 9.648438 C 17.412109 9.707031 17.413736 9.765625 17.416992 9.824219 C 17.420246 9.882812 17.421875 9.941406 17.421875 10 C 17.421875 10.058594 17.420246 10.117188 17.416992 10.175781 C 17.413736 10.234375 17.412109 10.292969 17.412109 10.351562 L 19.941406 11.923828 L 18.388672 15.664062 L 15.488281 15 C 15.332031 15.169271 15.169271 15.332031 15 15.488281 L 15.664062 18.388672 L 11.923828 19.941406 L 10.351562 17.412109 C 10.292969 17.412109 10.234375 17.413736 10.175781 17.416992 C 10.117188 17.420248 10.058594 17.421875 10 17.421875 C 9.941406 17.421875 9.882812 17.420248 9.824219 17.416992 C 9.765625 17.413736 9.707031 17.412109 9.648438 17.412109 L 8.076172 19.941406 L 4.335938 18.388672 L 5 15.488281 C 4.830729 15.332031 4.667969 15.169271 4.511719 15 L 1.611328 15.664062 L 0.058594 11.923828 L 2.587891 10.351562 C 2.587891 10.292969 2.586263 10.234375 2.583008 10.175781 C 2.579752 10.117188 2.578125 10.058594 2.578125 10 C 2.578125 9.941406 2.579752 9.882812 2.583008 9.824219 C 2.586263 9.765625 2.587891 9.707031 2.587891 9.648438 L 0.058594 8.076172 L 1.611328 4.335938 L 4.511719 5 C 4.667969 4.830729 4.830729 4.667969 5 4.511719 L 4.335938 1.611328 L 8.076172 0.058594 L 9.648438 2.587891 C 9.707031 2.587891 9.765625 2.586264 9.824219 2.583008 C 9.882812 2.579754 9.941406 2.578125 10 2.578125 C 10.058594 2.578125 10.117188 2.579754 10.175781 2.583008 C 10.234375 2.586264 10.292969 2.587891 10.351562 2.587891 L 11.923828 0.058594 L 15.664062 1.611328 L 15 4.511719 C 15.169271 4.667969 15.332031 4.830729 15.488281 5 L 18.388672 4.335938 L 19.941406 8.076172 Z M 16.269531 10.917969 C 16.282551 10.761719 16.295572 10.607097 16.308594 10.454102 C 16.321613 10.301107 16.328125 10.146484 16.328125 9.990234 C 16.328125 9.840495 16.321613 9.6875 16.308594 9.53125 C 16.295572 9.375 16.282551 9.222006 16.269531 9.072266 L 18.574219 7.636719 L 17.734375 5.605469 L 15.087891 6.220703 C 14.886067 5.973309 14.679361 5.745443 14.467773 5.537109 C 14.256184 5.328776 14.026691 5.120443 13.779297 4.912109 L 14.394531 2.265625 L 12.363281 1.425781 L 10.917969 3.730469 C 10.768229 3.717449 10.615234 3.704428 10.458984 3.691406 C 10.302734 3.678387 10.149739 3.671875 10 3.671875 C 9.84375 3.671875 9.689127 3.678387 9.536133 3.691406 C 9.383138 3.704428 9.228516 3.717449 9.072266 3.730469 L 7.636719 1.425781 L 5.605469 2.265625 L 6.220703 4.912109 C 5.973307 5.113933 5.745442 5.320639 5.537109 5.532227 C 5.328776 5.743816 5.120442 5.973309 4.912109 6.220703 L 2.265625 5.605469 L 1.425781 7.636719 L 3.730469 9.082031 C 3.717448 9.238281 3.704427 9.392904 3.691406 9.545898 C 3.678385 9.698894 3.671875 9.853516 3.671875 10.009766 C 3.671875 10.159506 3.678385 10.3125 3.691406 10.46875 C 3.704427 10.625 3.717448 10.777995 3.730469 10.927734 L 1.425781 12.363281 L 2.265625 14.394531 L 4.912109 13.779297 C 5.113932 14.026693 5.320638 14.254558 5.532227 14.462891 C 5.743815 14.671225 5.973307 14.879558 6.220703 15.087891 L 5.605469 17.734375 L 7.636719 18.574219 L 9.082031 16.269531 C 9.231771 16.282553 9.384766 16.295572 9.541016 16.308594 C 9.697266 16.321615 9.85026 16.328125 10 16.328125 C 10.15625 16.328125 10.310872 16.321615 10.463867 16.308594 C 10.616861 16.295572 10.771484 16.282553 10.927734 16.269531 L 12.363281 18.574219 L 14.394531 17.734375 L 13.779297 15.087891 C 14.026691 14.886068 14.254557 14.679362 14.462891 14.467773 C 14.671224 14.256186 14.879557 14.026693 15.087891 13.779297 L 17.734375 14.394531 L 18.574219 12.363281 Z M 10 6.328125 C 10.507812 6.328125 10.9847 6.424154 11.430664 6.616211 C 11.876627 6.80827 12.265624 7.070313 12.597656 7.402344 C 12.929687 7.734376 13.19173 8.123373 13.383789 8.569336 C 13.575846 9.0153 13.671875 9.492188 13.671875 10 C 13.671875 10.507812 13.575846 10.984701 13.383789 11.430664 C 13.19173 11.876628 12.929687 12.265625 12.597656 12.597656 C 12.265624 12.929688 11.876627 13.191732 11.430664 13.383789 C 10.9847 13.575847 10.507812 13.671875 10 13.671875 C 9.492188 13.671875 9.015299 13.575847 8.569336 13.383789 C 8.123372 13.191732 7.734375 12.929688 7.402344 12.597656 C 7.070312 12.265625 6.808268 11.876628 6.616211 11.430664 C 6.424153 10.984701 6.328125 10.507812 6.328125 10 C 6.328125 9.492188 6.424153 9.0153 6.616211 8.569336 C 6.808268 8.123373 7.070312 7.734376 7.402344 7.402344 C 7.734375 7.070313 8.123372 6.80827 8.569336 6.616211 C 9.015299 6.424154 9.492188 6.328125 10 6.328125 Z M 10 12.578125 C 10.358072 12.578125 10.693359 12.511394 11.005859 12.37793 C 11.318359 12.244467 11.591797 12.060547 11.826172 11.826172 C 12.060547 11.591797 12.244466 11.318359 12.37793 11.005859 C 12.511393 10.693359 12.578125 10.358073 12.578125 10 C 12.578125 9.641928 12.511393 9.306641 12.37793 8.994141 C 12.244466 8.681641 12.060547 8.408203 11.826172 8.173828 C 11.591797 7.939453 11.318359 7.755534 11.005859 7.62207 C 10.693359 7.488607 10.358072 7.421875 10 7.421875 C 9.641927 7.421875 9.306641 7.488607 8.994141 7.62207 C 8.681641 7.755534 8.408203 7.939453 8.173828 8.173828 C 7.939453 8.408203 7.755533 8.681641 7.62207 8.994141 C 7.488606 9.306641 7.421875 9.641928 7.421875 10 C 7.421875 10.358073 7.488606 10.693359 7.62207 11.005859 C 7.755533 11.318359 7.939453 11.591797 8.173828 11.826172 C 8.408203 12.060547 8.681641 12.244467 8.994141 12.37793 C 9.306641 12.511394 9.641927 12.578125 10 12.578125 Z "
                                    Fill="{Binding (TextBlock.Foreground), RelativeSource={RelativeSource AncestorType=Button}}"
                                    RenderTransformOrigin="0.5,0.5"
                                    Stretch="Uniform" />
                            </Button>
                            <Button
                                x:Name="btnMinimizeButton"
                                Command="{x:Static SystemCommands.MinimizeWindowCommand}">
                                <Path
                                    Width="10"
                                    Height="10"
                                    Data="M0,4 L10,4 L10,5 L0,5 z"
                                    Fill="{Binding (TextBlock.Foreground), RelativeSource={RelativeSource AncestorType=Button}}"
                                    RenderTransformOrigin="0.5,0.5"
                                    Stretch="Uniform" />
                            </Button>
                            <Button
                                x:Name="btnMaximizeButton"
                                Command="{x:Static SystemCommands.MaximizeWindowCommand}">
                                <Path
                                    Width="10"
                                    Height="10"
                                    Data="M1,1 L1,9 L9,9 L9,1 z M0,0 L10,0 L10,10 L0,10 z"
                                    Fill="{Binding (TextBlock.Foreground), RelativeSource={RelativeSource AncestorType=Button}}"
                                    RenderTransformOrigin="0.5,0.5"
                                    Stretch="Uniform" />
                            </Button>
                            <Button
                                x:Name="btnRestoreButton"
                                Command="{x:Static SystemCommands.RestoreWindowCommand}"
                                Visibility="Collapsed">
                                <Path
                                    Width="10"
                                    Height="10"
                                    Data="M1,3 L1,9 L7,9 L7,3 z M3,1 L3,2 L8,2 L8,7 L9,7 L9,1 z M2,0 L10,0 L10,8 L8,8 L8,10 L0,10 L0,2 L2,2 z"
                                    Fill="{Binding (TextBlock.Foreground), RelativeSource={RelativeSource AncestorType=Button}}"
                                    RenderTransformOrigin="0.5,0.5"
                                    Stretch="Uniform" />
                            </Button>
                            <Button
                                x:Name="btnCloseButton"
                                Command="{x:Static SystemCommands.CloseWindowCommand}">
                                <Path
                                    Width="10"
                                    Height="10"
                                    Data="M0.7,0 L5,4.3 L9.3,0 L10,0.7 L5.7,5 L10,9.3 L9.3,10 L5,5.7 L0.7,10 L0,9.3 L4.3,5 L0,0.7 z"
                                    Fill="{Binding (TextBlock.Foreground), RelativeSource={RelativeSource AncestorType=ContentPresenter}}"
                                    RenderTransformOrigin="0.5,0.5"
                                    Stretch="Uniform" />
                            </Button>
                        </StackPanel>
                    </Grid>

样式出完的效果是这样的:

image-20211228100417631

这里记得需要给按钮的父组件添加WindowChrome.IsHitTestVisibleInChrome="True",也可以给按钮本身添加,看自己的需要,不然会导致按钮无法点击。

虽然添加了属性,但是发现图中依然有按钮是置灰的,那是因为我们给按钮绑定了系统的命令,但是了这个命令并没有初始化,所以需要到后台,进行命令的初始化

        public MainWindow()
        {
            InitializeComponent();

            CommandBindings.Add(new CommandBinding(SystemCommands.CloseWindowCommand, (_, __) => { SystemCommands.CloseWindow(this); }));
            CommandBindings.Add(new CommandBinding(SystemCommands.MinimizeWindowCommand, (_, __) => { SystemCommands.MinimizeWindow(this); }));
            CommandBindings.Add(new CommandBinding(SystemCommands.MaximizeWindowCommand, (_, __) => { SystemCommands.MaximizeWindow(this); }));
            CommandBindings.Add(new CommandBinding(SystemCommands.RestoreWindowCommand, (_, __) => { SystemCommands.RestoreWindow(this); }));
            CommandBindings.Add(new CommandBinding(SystemCommands.ShowSystemMenuCommand, ShowSystemMenu));

        }

        private void ShowSystemMenu(object sender, ExecutedRoutedEventArgs e)
        {
            var element = e.OriginalSource as FrameworkElement;
            if (element == null)
                return;

            var position = WindowState == WindowState.Maximized ? new Point(0, element.ActualHeight)
                : new Point(Left + BorderThickness.Left, element.ActualHeight + Top + BorderThickness.Top);
            position = element.TransformToAncestor(this).Transform(position);
            SystemCommands.ShowSystemMenu(this, position);
        }

现在窗口是正常了,但是放大之后并没有更换放大区域的图标,可以看到,我们摆放按钮的时候, 有一个按钮被隐藏了, 这就是我们的还原界面按钮,所以需要添加一些触发器,让界面在放大之后,可以更换一下图标,并且我们在放大界面的时候, 明显能够感觉到标题栏超出了屏幕外围(窗口最大化的时候设置BorderThickness=8解决此问题):

image-20211228104516111

上面说的这些都可以放在样式和样式的触发器中去实现,具体的步骤就不说明了,直接贴一下完整的代码,供学习参考(因为很多东西都写死了,所以根据自己的需要,自行进行动态更改)

完整代码的效果:

image-20211228113237884

完整代码下载

最后说一句

AllowsTransparency="True"的这种方式依然是适用的,重在开发快速高效,有时候公司的项目,并不需要过多的默认窗口行为,还有就是有些老哥就是喜欢自己添加这些窗口行为,任何一种方式都有有利有弊,性能之战也无终章。

在满足工作要求的时候,我们也要有更高的追求!

没有银弹!

posted @ 2021-12-28 11:38  丑萌气质狗  阅读(6082)  评论(7编辑  收藏  举报