【WPF】填坑 - WindowChrome 自定义窗口完美实现

【WPF】填坑 - WindowChrome 自定义窗口完美实现

  • 概述
  • Demo 说明
  • 基本样式资源
  • 布局
    • ShellView Style
    • 最小化、最大、还原、关闭 按钮
    • 界面元素修正
    • Command Binding
    • Command 实现
  • 效果

概述

前面写过一篇关于在 WPF 中通过对 WindowChrome 的操作实现自定义窗口并保留一部分的系统功能。
【WPF】WindowChrome 自定义窗口完美实现
有小伙伴看过之后反应,其中有些功能不够完善,本篇来对前面填坑。

Demo 说明

  • 基于 .net6 WPF MVVM 模式
  • 不在 MainWindow 上进行修改而是新建一个 ShellView 的窗口作为主窗口
  • 必要的 NuGet 包
    • CommunityToolkit.Mvvm
    • Microsoft.Xaml.Behaviors.Wpf

基本样式资源


<Brush x:Key="TitleBar">#F0F8FFBrush>
<Brush x:Key="themeColor">#696969Brush>
<Brush x:Key="MouseOverBackground">#87CEEBBrush>
<Brush x:Key="MouseOverForeground">#F0F8FFBrush>


<Geometry x:Key="Minimize">M928.2 548h-832c-17.7 0-32-14.3-32-32s14.3-32 32-32h832c17.7 0 32 14.3 32 32s-14.3 32-32 32zGeometry>
<Geometry x:Key="Maximize">M812.3 959.4H213.7c-81.6 0-148-66.4-148-148V212.9c0-81.6 66.4-148 148-148h598.5c81.6 0 148 66.4 148 148v598.5C960.3 893 893.9 959.4 812.3 959.4zM213.7 120.9c-50.7 0-92 41.3-92 92v598.5c0 50.7 41.3 92 92 92h598.5c50.7 0 92-41.3 92-92V212.9c0-50.7-41.3-92-92-92H213.7zGeometry>
<Geometry x:Key="Restore">M714.666667 256H138.666667a53.393333 53.393333 0 0 0-53.333334 53.333333v576a53.393333 53.393333 0 0 0 53.333334 53.333334h576a53.393333 53.393333 0 0 0 53.333333-53.333334V309.333333a53.393333 53.393333 0 0 0-53.333333-53.333333z m10.666666 629.333333a10.666667 10.666667 0 0 1-10.666666 10.666667H138.666667a10.666667 10.666667 0 0 1-10.666667-10.666667V309.333333a10.666667 10.666667 0 0 1 10.666667-10.666666h576a10.666667 10.666667 0 0 1 10.666666 10.666666z m213.333334-746.666666v565.333333a21.333333 21.333333 0 0 1-42.666667 0V138.666667a10.666667 10.666667 0 0 0-10.666667-10.666667H320a21.333333 21.333333 0 0 1 0-42.666667h565.333333a53.393333 53.393333 0 0 1 53.333334 53.333334zGeometry>
<Geometry x:Key="Close">M952.311261 921.328619 542.892591 510.919389 950.154131 102.671381c8.53028-8.55177 8.53028-22.416546 0-30.967292-8.532327-8.55177-22.360264-8.55177-30.892591 0l-407.262564 408.248008L104.737436 71.704089c-8.53028-8.55177-22.36231-8.55177-30.892591 0-8.529257 8.55177-8.529257 22.416546 0 30.967292l407.262564 408.248008L71.687716 921.328619c-8.529257 8.55177-8.529257 22.416546 0 30.967292 4.26514 4.27435 9.856485 6.41306 15.446807 6.41306 5.590322 0 11.181667-2.13871 15.446807-6.41306l409.41867-410.409231 409.41867 410.409231c4.266164 4.27435 9.855462 6.41306 15.446807 6.41306 5.591345 0 11.17962-2.13871 15.446807-6.41306C960.841541 943.745165 960.841541 929.879366 952.311261 921.328619zGeometry>


<ImageSource x:Key="icon">pack://SiteOfOrigin:,,,/icon.icoImageSource>

图片自己换,不在这里提供

布局

【WPF】填坑 - WindowChrome 自定义窗口完美实现_第1张图片
主体分为,TitleBar 和 Content 两部分,也就是非用户区和用户区

ShellView Style

(SystemParameters.WindowNonClientFrameThickness).Top 获取系统窗口标题栏可触发窗口移动的高度,标题栏上的按钮也通过这里高度
SystemParameters.SmallIconWidth SystemParameters.SmallIconHeight 图标的宽和高,一般为16*16 Pixel
WindowChrome 的处理,这一块相对来说其实比较固定,下面这样处理即可 不保留三大键

<WindowChrome
	CaptionHeight="{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}"
    GlassFrameThickness="1"
    NonClientFrameEdges="None"
    ResizeBorderThickness="5"
    UseAeroCaptionButtons="False" />

通过自定义 Template 实现前面的布局

<Style x:Key="ShellViewStyle" TargetType="{x:Type Window}">
    "FontFamily" Value="Microsoft YaHei UI" />
    "WindowChrome.WindowChrome">
        
            "{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}"
                GlassFrameThickness="1"
                NonClientFrameEdges="None"
                ResizeBorderThickness="5"
                UseAeroCaptionButtons="False" />
        
    
    "Icon" Value="{DynamicResource icon}" />
    "Template">
        
            "{x:Type Window}">
                x:Name="WindowBorder"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}">

                    x:Name="LayoutRoot">
                        
                            "Auto" />
                            "*" />
                        

                        x:Name="WindowTitlePanel"
                            Grid.Row="0"
                            Height="{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}"
                            Background="{DynamicResource TitleBar}">
                            
                                "*" />
                                "Auto" />
                            
                            "Horizontal">
                                "{x:Static SystemParameters.SmallIconWidth}"
                                    Height="{x:Static SystemParameters.SmallIconHeight}"
                                    Margin="5,0"
                                    Source="{TemplateBinding Icon}"
                                    WindowChrome.IsHitTestVisibleInChrome="True"/>
                                "Center"
                                    VerticalAlignment="Center"
                                    Content="{TemplateBinding Title}"
                                    FontSize="{x:Static SystemFonts.CaptionFontSize}"
                                    IsTabStop="False" />
                            

                            x:Name="WindowCommandButtonsPanel"
                                Grid.Column="1"
                                HorizontalAlignment="Right"
                                Orientation="Horizontal"
                                WindowChrome.IsHitTestVisibleInChrome="True">
                                

最小化、最大、还原、关闭 按钮

<Style x:Key="WindowTitleBarButtonStyle" TargetType="{x:Type Button}">
    "Height" Value="{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}" />
    "Width" Value="40" />
    "Background" Value="Transparent" />
    "Foreground" Value="{DynamicResource themeColor}" />
    "Template">
        
            "{x:Type Button}">
                "{TemplateBinding Background}">
                    "15" Height="15">
                        "{TemplateBinding Tag}" Fill="{TemplateBinding Foreground}" />
                    
                
                
                    "IsMouseOver" Value="True">
                        "Background" Value="{DynamicResource MouseOverBackground}" />
                        "Foreground" Value="{DynamicResource MouseOverForeground}" />
                    
                
            
        
    
Style>

<Style
    x:Key="MinimizeButtonStyle"
    BasedOn="{StaticResource WindowTitleBarButtonStyle}"
    TargetType="{x:Type Button}">
    "Tag" Value="{DynamicResource Minimize}" />
Style>

<Style
    x:Key="MaximizeButtonStyle"
    BasedOn="{StaticResource WindowTitleBarButtonStyle}"
    TargetType="{x:Type Button}">
    "Tag" Value="{DynamicResource Maximize}" />
Style>

<Style
    x:Key="RestoreButtonStyle"
    BasedOn="{StaticResource WindowTitleBarButtonStyle}"
    TargetType="{x:Type Button}">
    "Tag" Value="{DynamicResource Restore}" />
Style>

<Style
    x:Key="CloseButtonStyle"
    BasedOn="{StaticResource WindowTitleBarButtonStyle}"
    TargetType="{x:Type Button}">
    "Tag" Value="{DynamicResource Close}" />
Style>

界面元素修正

  • 窗口在最大化后会溢出 8 Pixel 的边距因此在最大化是需要处理边距
  • 最大化按钮和还原按钮在 Window 的 WindowState 属性改变时需要做显示/隐藏处理
<ControlTemplate.Triggers>
    <Trigger Property="WindowState" Value="Maximized">
        <Setter TargetName="LayoutRoot" Property="Margin" Value="8" />
        <Setter TargetName="RestoreButton" Property="Visibility" Value="Visible" />
        <Setter TargetName="MaximizeButton" Property="Visibility" Value="Collapsed" />
    Trigger>
    <Trigger Property="WindowState" Value="Normal">
        <Setter TargetName="ResizeGrip" Property="Visibility" Value="Visible" />
    Trigger>
ControlTemplate.Triggers>

Command Binding

点击图标显示 SystemMenu 菜单模态框,通过 Behaviors 将鼠标事件 Binding 到 ViewModel 中的 Command

<i:Interaction.Triggers>
    <i:EventTrigger EventName="MouseLeftButtonDown">
        <i:InvokeCommandAction Command="{Binding MouseLeftSystemMenuCommand}" />
    i:EventTrigger>
    <i:EventTrigger EventName="MouseRightButtonDown">
        <i:InvokeCommandAction Command="{Binding MouseRightSystemMenuCommand}" 
rgsToCommand="True" />
    i:EventTrigger>
i:Interaction.Triggers>

最小化、最大化、还原、关闭按钮 Binding Command

<Button
    x:Name="MinimizeButton"
    Command="{Binding MinimizeWindowCommand}"/>
<Button
    x:Name="RestoreButton"
    Command="{Binding RestoreWindowCommand}"/>
<Button
    x:Name="MaximizeButton"
    Command="{Binding MaximizeWindowCommand}"/>
<Button
    x:Name="CloseButton"
    Command="{Binding CloseWindowCommand}"/>

Command 实现

Command 尽可能的使用 WPF 提供的系统命令

public partial class ShellViewModel : ObservableObject
{
    private readonly Window _window;
    
    public ShellViewModel()
    {
        _window = Application.Current.MainWindow;
    }

    [RelayCommand]
    private void CloseWindow()
    {
        SystemCommands.CloseWindow(_window);

        Application.Current.Shutdown();
    }

    [RelayCommand]
    private void MinimizeWindow() => SystemCommands.MinimizeWindow(_window);

    [RelayCommand]
    private void MaximizeWindow() => SystemCommands.MaximizeWindow(_window);

    [RelayCommand]
    private void RestoreWindow() => SystemCommands.RestoreWindow(_window);

    [RelayCommand]
    private void MouseLeftSystemMenu()
    {
        var pointing = _window.PointToScreen(new Point(0, 0));

        if (_window.WindowState is WindowState.Maximized)
            pointing.X += 10;
        else
            pointing.X += 2;

        pointing.Y += SystemParameters.WindowNonClientFrameThickness.Top + 1;

        SystemCommands.ShowSystemMenu(_window, pointing);
    }

    [RelayCommand]
    private void MouseRightSystemMenu(MouseEventArgs e)
    {
        FrameworkElement? element = e.OriginalSource as FrameworkElement;

        var pointing = _window.PointToScreen(Mouse.GetPosition(element));
        pointing.X += 5;
        pointing.Y += 5;
        SystemCommands.ShowSystemMenu(_window, pointing);
    }
}

效果

【WPF】填坑 - WindowChrome 自定义窗口完美实现_第2张图片
【WPF】填坑 - WindowChrome 自定义窗口完美实现_第3张图片
【WPF】填坑 - WindowChrome 自定义窗口完美实现_第4张图片

通过 WindowChrome 对窗口进行高度自定义化,可以最大限度地保留系统功能,比如窗口动画,点击任务栏图标的一些操作等,最主要的我想还是窗口的性能是最接近原生性能的

这篇文章也只是更加完善了前面文章的不足,后面有更完善的部分,同样会以文章的形式更新出来,供大家参考。

你可能感兴趣的:(#,WPF,wpf,microsoft,windows,WindowChrome)