【WPF】 WindowChrome 自定义窗体

导读

【XAML】WindowChrome的功能详解

环境:vs2022+.net6

参考:https://blog.csdn.net/qq_43562262/article/details/133244427

背景

 WPF有两种主流的自定义Window窗体的方案,都各有缺点。

方法一、

《WPF编程宝典》介绍了使用WindowStyle="None"AllowsTransparency="True"创建无边框。

这种方法的原理是从Window中删除non-client area(即chrome,包括 标题栏和frame),再由用户自定义Window的所有外观和部分行为。这种方式的自由度很高,但也有不少问题:

  • Window没有阴影导致很难看,但添加自定义的DropShadowEffect又十分影响性能;
  • 没有弹出、关闭、最大化、最小化动画,尤其当启动了大量任务将任务栏堆满的情况下没有最小化动画很容易找不到自己的程序;
  • 没有动画很麻烦,自定义的动画做得不好也十分影响使用;
  • 需要写大量代码实现Window本来的拖动、改变大小、最大化等行为;
  • 各种其它细节的缺失;
  • 渲染新能差

大部分自定义Window或多或少都有上面所说的问题,幸好WPF提供了WindowChrome这个类用于创建自定义的Window,这个类本身处理了上面部分问题。请相信阅读WindowChrome的功能详解,在来看这篇文章。

方法二、

单纯的使用WindowChrome,失去灵活性,WindowChrome本身有bug。

因此考虑将两种方法结合起来使用,保留两种方法的部分优点。

自定义WPF窗体

采用WindowChrome +WindowStyle="None"AllowsTransparency="True"创建WPF窗体。

优点:

保留默认窗体的部分功能、减少很多代码。

缺点:

拖拽左边和下边边框进行调整窗体大小,会出现闪烁,主要是因为AllowsTransparency="True" 渲染造成闪烁。

比默认窗体样式占用内存,也是窗体渲染造成的。

功能:

1、标题栏:拖拽、双击最大化、双击正常、系统菜单(WindowChrome的功能)

2、边框功能:调整边框(WindowChrome的功能)、边框阴影(要自定义)

具体操作

去掉window窗体默认样式

 

WindowStyle="None"
AllowsTransparency="True"

 

包括去掉标题栏功能(最大化按钮、最小化按钮、关闭按钮、系统菜单、拖拽)、边框功能(调整、阴影)

<Window x:Class="CustomerWindowWithWindowChromeDemo.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CustomerWindowWithWindowChromeDemo"
        mc:Ignorable="d"
        Background="{x:Null}"
        WindowStartupLocation="CenterScreen"
        Topmost="True"
        WindowStyle="None"
        AllowsTransparency="True"
        ResizeMode="CanResizeWithGrip"
        Title="MainWindow" Height="800" Width="1400">
</Window>
    

 

添加WindowChrome.WindowChrome附加属性

给窗体添加WindowChrome.WindowChrome附加属性,该附加属性具有窗体标题栏、glassFrame、扩大工作区到整个窗体的功能。

WindowChrome.GlassFrameThickness=0 时,隐藏了glassFrame(去掉毛玻璃框架最大按钮、最小按钮、关闭按钮)。

剩下标题栏(双击最大化、双击最小化、拖拽、系统菜单)、调整窗体边框功能 、扩大工作区到整个窗体的功能。

    <!--GlassFrameThickness=0 去掉毛玻璃框架(最大按钮、最小按钮、关闭按钮)-->
    <WindowChrome.WindowChrome>
        <WindowChrome GlassFrameThickness="0"></WindowChrome>
    </WindowChrome.WindowChrome>
 

自定义边框阴影

该功能需要自定义,需要修改窗体控件模板(ControlTemplate ),在控件模板中添加两个Border,外层的border起到坐标的作用,为了让内层border的margin起作用,内层border控制阴影和边框样式。效果如下:

样式代码:

     <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Window}">
                        <!-- 阴影效果:一定要设置Border的Margin ,否制不会出现阴影效果  -->
                        <Border>
                        <Border BorderThickness="1" BorderBrush="#d9d9d9" Background="White" Margin="5" CornerRadius="8">
                            <Border.Effect>
                                <DropShadowEffect Color="Gray" ShadowDepth="0" BlurRadius="5" Opacity="0.3" Direction="0"/>
                            </Border.Effect> 
                            <Grid>
                          <!--其他代码  <!--窗体内容区域 必须要添加AdornerDecorator ,验证信息会在这一层显示--> 省略 -->
                                <AdornerDecorator Grid.Row="1">
                                    <ContentPresenter  ClipToBounds="True" />
                                </AdornerDecorator>
                            </Grid>
                            </Border>
                        </Border>
                       
                    </ControlTemplate>
                </Setter.Value>
                
            </Setter>

注意:

AdornerDecorator 为可视化树中的子元素提供 AdornerLayer,如果没有它的话一些装饰效果不能显示(例如下图Button控件的Focus效果),Window的 ContentPresenter 外面套个 AdornerDecorator 是 必不能忘的

WindowChrome 设置效果不理想。所有不采用这种方式了。

调整全屏大小

因为 设置WindowStyle="None"  AllowsTransparency="True"会导致窗体最大化时,遮挡任务栏,同时也要加上5*2阴影宽度,因此要修改全屏时候窗体的大小,所有要添加值转换器。
    MaxWidth="{Binding Source={x:Static SystemParameters.WorkArea}, Path=Width ,Converter={StaticResource ConvertToDouble}}"
    MaxHeight="{Binding Source={x:Static SystemParameters.WorkArea}, Path=Height ,Converter={StaticResource ConvertToDouble}}"
<Window x:Class="CustomerWindowWithWindowChromeDemo.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CustomerWindowWithWindowChromeDemo"
        mc:Ignorable="d"
        Background="{x:Null}"
        WindowStartupLocation="CenterScreen"
        MaxWidth="{Binding Source={x:Static SystemParameters.WorkArea}, Path=Width ,Converter={StaticResource ConvertToDouble}}"
    MaxHeight="{Binding Source={x:Static SystemParameters.WorkArea}, Path=Height ,Converter={StaticResource ConvertToDouble}}"
        WindowStyle="None"
        AllowsTransparency="True"
        ResizeMode="CanResizeWithGrip"
        Title="MainWindow" Height="800" Width="1400">
</Window>

 

 完整代码

MainWindow.xaml

<Window x:Class="CustomerWindowWithWindowChromeDemo.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CustomerWindowWithWindowChromeDemo"
        mc:Ignorable="d"
        Background="{x:Null}"
        WindowStartupLocation="CenterScreen"
        WindowStyle="None"
        AllowsTransparency="True"
        ResizeMode="CanResizeWithGrip"
        Title="MainWindow" Height="800" Width="1400">

    <Window.Style>
        <Style TargetType="{x:Type Window}">
            <Setter Property="FontFamily" Value="Microsoft YaHei"/>
            <Setter Property="FontWeight" Value="ExtraLight"/>
            <Setter Property="MaxHeight" Value="{Binding Source={x:Static SystemParameters.WorkArea}, Path=Height ,Converter={StaticResource ConvertToDouble}}"/>
            <Setter Property="MaxWidth" Value="{Binding Source={x:Static SystemParameters.WorkArea}, Path=Width,Converter={StaticResource ConvertToDouble}}"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Window}">
                        <!--一定要设置Border的Margin ,否制不会出现阴影效果  -->
                        <Border>
                            <Border BorderThickness="1" BorderBrush="#d9d9d9" Background="White" Margin="5" CornerRadius="8">
                                <Border.Effect>
                                    <DropShadowEffect Color="Gray" ShadowDepth="0" BlurRadius="5" Opacity="0.3" Direction="0"/>
                                </Border.Effect>
                                <Grid>
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="40"/>
                                        <RowDefinition Height="*" MinHeight="200"/>
                                        <RowDefinition Height="40"/>
                                    </Grid.RowDefinitions>

                                    <WrapPanel Grid.Row="0" Margin="10,0,10,0" VerticalAlignment="Center"  >
                                        <Image Source="{StaticResource  logo}" Width="32" Height="32"/>
                                        <TextBlock Text="{TemplateBinding Title}"  VerticalAlignment="Center" Margin="10,0,0,0"></TextBlock>
                                    </WrapPanel>

                                    <WrapPanel Grid.Row="0" Margin="10,0,10,0" VerticalAlignment="Stretch" HorizontalAlignment="Right"   >
                                        <Button Style="{DynamicResource WindowMaxButtonStyle }" Width="50" Height="40"  VerticalAlignment="Stretch"  >
                                            <Path Width="10" Height="2" Stretch="Fill" Fill="Black" Data="{StaticResource  MiniIco}"/>

                                        </Button>

                                        <Button Width="50" Style="{DynamicResource WindowMaxButtonStyle }" VerticalAlignment="Stretch" >
                                            <Path Width="10" Height="10" Stretch="Fill" Fill="Black" Data="{StaticResource  MaxIco}"/>
                                        </Button>

                                        <Button Width="50" Style="{DynamicResource  WindowCloseButtonStyle }"   VerticalAlignment="Stretch" >
                                            <Path Width="10" Height="10" Stretch="Fill" Fill="Black" Data="{StaticResource CloseIco}"/>
                                        </Button>
                                    </WrapPanel>

                                </Grid>
                            </Border>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Style>
    <!--GlassFrameThickness=0 去掉毛玻璃框架(最大按钮、最小按钮、关闭按钮)-->
    <WindowChrome.WindowChrome>
        <WindowChrome CaptionHeight="50" GlassFrameThickness="1"></WindowChrome>
    </WindowChrome.WindowChrome>

    <Grid x:Name="maingrid">
        <Grid.RowDefinitions>
            <RowDefinition Height="20"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Button></Button>
    </Grid>
</Window>

 

GlobalWindowStyle.xaml 样式

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:convert="clr-namespace:CustomerWindowWithWindowChromeDemo.Theme.ValueConverter"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <SolidColorBrush x:Key="Window.Static.MinButton.Fill" Color="Black"/>
    <SolidColorBrush x:Key="Window.Static.NormalButton.Fill" Color="Black"/>
    <SolidColorBrush x:Key="Window.Static.MaxButton.Fill" Color="Black"/>
    <SolidColorBrush x:Key="Window.Static.Title.Foreground" Color="White"/>

    <SolidColorBrush x:Key="Window.Static.CloseButton.Fill" Color="#6d769e"/>
    <SolidColorBrush x:Key="Window.CloseButton.Press.Fill" Color="#f1707a"/>
    <SolidColorBrush x:Key="Window.CloseButton.MouseOver.Fill" Color="#e81123"/>
    <SolidColorBrush x:Key="Window.CloseButton.IsDefaulted.Fill" Color="Transparent"/>
    <!--以下是 值转换器-->
    <convert:ConvertToDoubleConverter x:Key="ConvertToDouble" Increment="10"/>

    <!--以下是窗口 最小、最大、关闭按钮ICO-->
    <Geometry x:Key="CloseIco" >M507.168 473.232L716.48 263.936a16 16 0 0 1 22.624 0l11.312 11.312a16 16 0 0 1 0 22.624L541.12 507.168 750.4 716.48a16 16 0 0 1 0 22.624l-11.312 11.312a16 16 0 0 1-22.624 0L507.168 541.12 297.872 750.4a16 16 0 0 1-22.624 0l-11.312-11.312a16 16 0 0 1 0-22.624l209.296-209.312-209.296-209.296a16 16 0 0 1 0-22.624l11.312-11.312a16 16 0 0 1 22.624 0l209.296 209.296z</Geometry>
    <Geometry x:Key="MiniIco"  >M772.963422 533.491105l-528.06716 0c-12.38297 0-22.514491-10.131521-22.514491-22.514491l0 0c0-12.38297 10.131521-22.514491 22.514491-22.514491l528.06716 0c12.38297 0 22.514491 10.131521 22.514491 22.514491l0 0C795.477913 523.359584 785.346392 533.491105 772.963422 533.491105z</Geometry>
    <Geometry x:Key="MaxIco"  >M1140.982158 0C1171.511117 0 1196.259731 24.748613 1196.259731 55.277574L1196.259731 481.802069C1196.259731 512.331031 1171.511117 537.079644 1140.982158 537.079644 1110.453196 537.079644 1085.704583 512.331031 1085.704583 481.802069L1085.704583 55.277574 1140.982158 110.555148 707.290659 110.555148C676.761697 110.555148 652.013084 85.806535 652.013084 55.277574 652.013084 24.748613 676.761697 0 707.290659 0L1140.982158 0ZM223.896216 1024.028434C193.367257 1024.028434 168.618642 999.279821 168.618642 968.75086L168.618642 542.226364C168.618642 511.697403 193.367257 486.94879 223.896216 486.94879 254.425178 486.94879 279.17379 511.697403 279.17379 542.226364L279.17379 968.75086 223.896216 913.473286 657.587715 913.473286C688.116677 913.473286 712.865289 938.221898 712.865289 968.75086 712.865289 999.279821 688.116677 1024.028434 657.587715 1024.028434L223.896216 1024.028434Z</Geometry>
 

    <!--以下是正常按钮图片-->
    <DrawingImage x:Key="WindowNormal">
        <DrawingImage.Drawing>
            <DrawingGroup>
                <GeometryDrawing  Brush="{StaticResource Window.Static.NormalButton.Fill}"  >
                    <GeometryDrawing.Geometry>
                        <PathGeometry    Figures="M796 481H581.5c-20.2 0-36.5-16.4-36.5-36.5V228.7c0-20.3 16.5-37.1 36.7-36.7 19.5 0.4 35.3 16.4 35.3 36v166.3c0 8.1 6.6 14.7 14.7 14.7h163.5c20.3 0 37.1 16.4 36.7 36.7-0.3 19.6-16.3 35.3-35.9 35.3zM228 744.1l175.4-175.4c14-14 36.9-14 50.9 0s14 36.9 0 50.9L278.9 795c-14 14-36.9 14-50.9 0s-14-36.9 0-50.9z"/>
                    </GeometryDrawing.Geometry>
                </GeometryDrawing>
                <GeometryDrawing  Brush="{StaticResource Window.Static.NormalButton.Fill}"  >
                    <GeometryDrawing.Geometry>
                        <PathGeometry    Figures="M228 543h214.3c20.3 0 36.7 16.4 36.7 36.7v215.5c0 20.3-16.5 37.1-36.7 36.7-19.5-0.4-35.3-16.4-35.3-36V615H228.7c-20.3 0-37.1-16.4-36.7-36.7 0.4-19.6 16.4-35.3 36-35.3z"/>
                    </GeometryDrawing.Geometry>
                </GeometryDrawing>
                <GeometryDrawing  Brush="{StaticResource Window.Static.NormalButton.Fill}"  >
                    <GeometryDrawing.Geometry>
                        <PathGeometry    Figures="M189.3 135.9c-29.7 0-53.8 24.1-53.8 53.7v644.7c0 29.7 24.1 53.7 53.8 53.7h645.4c29.7 0 53.8-24.1 53.8-53.7V189.6c0-29.7-24.1-53.7-53.8-53.7H189.3z m-13-71.1h671.5c61.8 0 111.9 50.1 111.9 111.8v670.8c0 61.7-50.1 111.8-111.9 111.8H176.3c-61.8 0-111.9-50-111.9-111.8V176.6c0-61.7 50.1-111.8 111.9-111.8z"/>
                    </GeometryDrawing.Geometry>
                </GeometryDrawing>
            </DrawingGroup>
        </DrawingImage.Drawing>
    </DrawingImage>

    <!--以下是最大窗体按钮 样式-->
    <Style x:Key="WindowMaxButtonStyle" TargetType="{x:Type  Button}" >
        <Setter Property="Padding"  Value="12,12,12,12"/>
        <Setter Property="Height" Value="40"/>
        <Setter Property="Width" Value="40"/>
        <Setter Property="Background"  Value="Transparent"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True"/>
        <Setter Property="Cursor" Value="Hand"/>
    </Style>

    <!--以下是正常窗体按钮 样式-->
    <Style x:Key="WindowNormalButtonStyle" TargetType="{x:Type ToggleButton}">
        <Setter Property="Padding"  Value="12,12,12,12"/>
        <Setter Property="Height" Value="40"/>
        <Setter Property="Width" Value="40"/>
        <Setter Property="Background"  Value="Transparent"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True"/>
        <Setter Property="Cursor" Value="Hand"/>
    </Style>

    <!--以下是关闭窗体按钮 样式-->
    <Style x:Key="WindowCloseButtonStyle" TargetType="{x:Type Button}">
        <Setter Property="Padding"  Value="12,12,12,12"/>
        <Setter Property="Height" Value="40"/>
        <Setter Property="Cursor" Value="Hand"/>
        <Setter Property="Width" Value="40"/>
        <Setter Property="Background"  Value="Transparent"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Button}">
                    <Border x:Name="border"  Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="true">
                        <ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                   
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsDefaulted" Value="true">
                            <Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                        </Trigger>
                        
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter Property="Background" TargetName="border" Value="{StaticResource Window.CloseButton.MouseOver.Fill}"/>
                       
                        </Trigger>
                        <Trigger Property="IsPressed" Value="true">
                            <Setter Property="Background" TargetName="border" Value="{StaticResource Window.CloseButton.Press.Fill}"/>
                      

                        </Trigger>

                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    
    <!--应用程序Logo-->
    <DrawingImage x:Key="logo">
        <DrawingImage.Drawing>
            <DrawingGroup >
                <GeometryDrawing Brush="#70FBA3">
                    <GeometryDrawing.Geometry>
                        <CombinedGeometry>
                            <CombinedGeometry.Geometry1>
                                <PathGeometry    Figures="M836.4 848.8l-349.2-324V50.1h1l-0.5-0.5C225 49.6 12 262.6 12 525.3S225 1001 487.7 1001c137.8 0 261.8-58.6 348.7-152.2z"/>
                            </CombinedGeometry.Geometry1>
                            <CombinedGeometry.Geometry2>
                                <PathGeometry    Figures="M538.6 25h-1v474.7l349.7 324.4c78.8-84.9 126.9-198.6 126.9-323.5C1014.3 238 801.3 25 538.6 25z"/>
                            </CombinedGeometry.Geometry2>
                        </CombinedGeometry>
                    </GeometryDrawing.Geometry>
                </GeometryDrawing>
            </DrawingGroup>
        </DrawingImage.Drawing>
    </DrawingImage>

</ResourceDictionary>

 

ConvertToDoubleConverter.cs

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;

namespace CustomerWindowWithWindowChromeDemo.Theme.ValueConverter
{

    internal class ConvertToDoubleConverter : IValueConverter
    {
        private double increment;
        public double Increment { get =>increment; set=>increment=value; }

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
           return increment+(double)value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

 

 

最终效果

 

 

特殊形状窗口

 

<Window.Clip>
    <PathGeometry Figures="M0,0 200,0A75 75 0 0 1 200 150L0,150z"/>
</Window.Clip>

 

表示将图形之外的内容都裁剪掉

 

  • 必须配合AllowsTransparency="True" WindowStyle="None"使用
  • 不能和WindowChrome一起使用,因为给裁剪了
  • 若要实现拖拽效果,必须在某个控件下实现MouseLeftButtonDown事件

 

private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    this.DragMove();
}

 

posted @ 2022-11-11 04:32  小林野夫  阅读(1810)  评论(0编辑  收藏  举报
原文链接:https://www.cnblogs.com/cdaniu/