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

news2024/11/24 18:52:11

【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">#F0F8FF</Brush>
<Brush x:Key="themeColor">#696969</Brush>
<Brush x:Key="MouseOverBackground">#87CEEB</Brush>
<Brush x:Key="MouseOverForeground">#F0F8FF</Brush>

<!--字体图标-->
<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 32z</Geometry>
<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.7z</Geometry>
<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.333334z</Geometry>
<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.328619z</Geometry>

<!--图片-->
<ImageSource x:Key="icon">pack://SiteOfOrigin:,,,/icon.ico</ImageSource>

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

布局

布局
主体分为,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}">
    <Setter Property="FontFamily" Value="Microsoft YaHei UI" />
    <Setter Property="WindowChrome.WindowChrome">
        <Setter.Value>
            <WindowChrome
                CaptionHeight="{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}"
                GlassFrameThickness="1"
                NonClientFrameEdges="None"
                ResizeBorderThickness="5"
                UseAeroCaptionButtons="False" />
        </Setter.Value>
    </Setter>
    <Setter Property="Icon" Value="{DynamicResource icon}" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Window}">
                <Border
                    x:Name="WindowBorder"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}">

                    <Grid x:Name="LayoutRoot">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>

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

                            <StackPanel
                                x:Name="WindowCommandButtonsPanel"
                                Grid.Column="1"
                                HorizontalAlignment="Right"
                                Orientation="Horizontal"
                                WindowChrome.IsHitTestVisibleInChrome="True">
                                <Button
                                    x:Name="MinimizeButton"
                                    Style="{DynamicResource MinimizeButtonStyle}" />
                                <Grid Margin="1,0">
                                    <Button
                                        x:Name="RestoreButton"
                                        Style="{DynamicResource RestoreButtonStyle}"
                                        Visibility="Collapsed" />
                                    <Button
                                        x:Name="MaximizeButton"
                                        Style="{DynamicResource MaximizeButtonStyle}" />
                                </Grid>
                                <Button
                                    x:Name="CloseButton"
                                    Style="{DynamicResource CloseButtonStyle}" />
                            </StackPanel>
                        </Grid>

                        <Grid Grid.Row="1" Background="{TemplateBinding Background}">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*" />
                                <RowDefinition Height="28" />
                            </Grid.RowDefinitions>
                            <AdornerDecorator KeyboardNavigation.IsTabStop="False">
                                <ContentControl
                                    HorizontalAlignment="Stretch"
                                    VerticalAlignment="Stretch"
                                    Content="{TemplateBinding Content}"
                                    KeyboardNavigation.TabNavigation="Cycle" />
                            </AdornerDecorator>
                            <Grid Grid.Row="1" Background="{DynamicResource themeColor}">
                                <ResizeGrip
                                    x:Name="ResizeGrip"
                                    HorizontalAlignment="Right"
                                    VerticalAlignment="Bottom"
                                    IsTabStop="False"
                                    Visibility="Collapsed" />
                            </Grid>
                        </Grid>
                    </Grid>
                </Border>                
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</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);
    }
}

效果

效果
效果
效果

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1036932.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

虚幻4学习笔记(13)用户UI 交互动画、制作2D UI、制作3D UI

虚幻4学习笔记 制作2D UIUI 主菜单制作UI动画 和 暂停游戏 制作3D UI B站UP谌嘉诚课程&#xff1a;https://www.bilibili.com/video/BV164411Y732 制作2D UI 导入图片 新建 用户界面-控件蓝图 双击打开 拖入image 参数设置 SizeX1920 SizeY1080 选择对焦居中点 右下角平铺 参…

【Acwing1027】方格取数(动态规划)题解

题目描述 思路分析 错误思路&#xff1a; 贪心法&#xff0c;先走一次求出最大值&#xff0c;把走过的路上面的数值清零&#xff0c;然后用同样的方法再走一遍求最大值&#xff0c;然后让这两个最大值相加就是最后的结果。 很多人在看到这个题目的时候会有上面的思路&#x…

微信小程序开发学习

模板与绑定 1.数据绑定 1&#xff09;在data中定义数据 在页面对应的.js文件中把数据定义到data对象中即可 2&#xff09;在WXML中使用数据 把data中的数据绑定到页面中渲染&#xff0c;使用Mustache&#xff08;双大括号{{}}&#xff09;将变量名包起来即可&#xff0c;格式…

AHK c++ 禁用鼠标设备实现完美息屏(不是休眠)

如何实现完美息屏呢&#xff1f;虽然可以用ahk实现息屏&#xff0c;但桌子一晃动&#xff0c;鼠标稍微偏移一下&#xff0c;又亮了&#xff0c;导致息屏无效&#xff01; win10 更新了设备管理器&#xff0c;现在可以禁用鼠标设备了。以前这里是灰色的。 c 禁用鼠标 那么&…

第二章 进程与线程 十七、用信号量实现进程互斥、进程同步、进程的前驱关系

一、实现进程互斥 1、过程 &#xff08;1&#xff09;分析并发进程的关键活动&#xff0c;划定临界区&#xff08;如:对临界资源打印机的访问就应放在临界区) &#xff08;2&#xff09;设置互斥信号量mutex&#xff0c;初值为1 &#xff08;3&#xff09;在进入区P(mutex)…

网工基础知识——以太网

1972年Bob Metcalfe“以太网之父”被Xerox雇佣为网络专家&#xff0c;Bob Metcalfe 来到Xerox公司的Palo Alto研究中心&#xff08;PARC&#xff09;的第一个任务是把Palo Alto的计算机连接到ARPANET&#xff08;Internet的前身&#xff09;上。1972年底Bob Metcalfe以ALOHA系统…

基于OSATE环境的AADL项目——简单的项目构建与分析示例

一、背景 本文描述了一个非常简单的AADL项目的构建&#xff0c;以及一个示例项目的分析过程。本文主要记录了OSATE工具环境的一些基本操作&#xff0c;适用于刚刚了解OSATE之后&#xff0c;对于整个工具环境无从下手的小白。 因为基于OSATE环境的AADL项目的构建和分析的详细示…

VRRP DHCP ACL NAT 网络核心路由技术综述 (第十课)

VRRP DHCP ACL NAT 网络核心技术综述 (第十课) 六大路由基础技术 简单的利用思维导图回顾 1 浮动路由 2 VRRP 技术==>目的是备份网关

2023-09-20 LeetCode每日一题(拿硬币)

2023-09-20每日一题 一、题目编号 LCP 06. 拿硬币二、题目链接 点击跳转到题目位置 三、题目描述 桌上有 n 堆力扣币&#xff0c;每堆的数量保存在数组 coins 中。我们每次可以选择任意一堆&#xff0c;拿走其中的一枚或者两枚&#xff0c;求拿完所有力扣币的最少次数。 示…

Lnmp架构之mysql数据库实战2

4、mysql组复制集群 一主多从的请求通常是读的请求高于写 &#xff0c;但是如果写的请求很高&#xff0c;要求每个节点都可以进行读写&#xff0c;这时分布式必须通过&#xff08;多组模式&#xff09;集群的方式进行横向扩容。 组复制对节点的数据一致性要求非常高&#xff…

Python多重继承

前面介绍的大部分的继承都是单继承&#xff0c;既一个子类只有一个父类&#xff0c;但是Python也支持多重继承&#xff0c;即一个子类可以有多个父类。多继承有复杂的父类冲突问题&#xff0c;大部分的面向对象语言都仅仅支持单继承&#xff0c;Python是为数不多支持多继承的语…

Python 判断回文数

"""判断输入的数是否为回文数介绍&#xff1a;回文数&#xff1a;数字从高位到低位正序排列和低位到高位逆序排列都是同一数值例如&#xff1a;数字 1221 无论正序还是逆序都是 1221知识点&#xff1a;1、获取字符串长度函数len()2、条件语句if/elif/else3、循环…

MySQL 高级(进阶) SQL 语句(二) -----存储过程

目录 1 存储过程 1.1 创建存储过程​ 1.2 调用存储过程 1.3 查看存储过程 1.4 存储过程的参数 1.5 修改存储过程 1.6 删除存储过程 2 条件语句 3 循环语句 1 存储过程 存储过程是一组为了完成特定功能的SQL语句集合。 存储过程在使用过程中是将常用或者复杂的工作预…

常用的软件项目管理工具有哪些?

在软件项目管理中&#xff0c;项目计划是工作中非常重要的一环&#xff0c;因此在选择软件项目管理工具时&#xff0c;除了任务管理、进度跟踪外&#xff0c;还需要关注软件的项目计划能力。 软件项目管理的工具有哪些&#xff1f;有什么好用的软件项目管理工具吗&#xff1f;…

windows 深度学习环境部署

1. 根据显卡配置安装适合的CUDA,查看显卡配置可在显卡控制面板上查看,安装是否成功可通过nvidia-smi查看&#xff1b;注意安装路径 https://developer.nvidia.com/cuda-toolkit-archive 2. 根据cuda安装合适的cudnn&#xff0c;需要注册NVIDIA cuDNN Archive | NVIDIA Devel…

记录一次关于嵌套事务传播机制的bug

1、具体问题 这段代码是A嵌套B&#xff0c;B嵌套C&#xff0c;遇到的bug是C代码发生异常进行事务标记&#xff08;因为使用的传播行为是默认的REQUIRED所以要等A事务进行rollback,C加入了A事务只能暂时进行标记&#xff09;,但是由于B代码进行了异常捕获返回给了A信息并没有抛…

[C]精炼分析状态机FSM

FSM&#xff1a;finite state machine 【有限状态机】&#xff0c;用通俗的语言来表达就是逻辑流程图。 当前状态满足触发条件时&#xff0c;就会切换到下一个状态&#xff0c;并执行对应的任务操作。传统代码做法是用if-else 或者 switch-case来处理。若要做到可扩展性良好的…

【车联网/自动驾驶仿真学习】VEINS_CARLA安装指南

VEINS_CARLA安装指南 这是Veins团队开发的一个接口&#xff0c;能够实现veins和carla之间的数据传输&#xff0c;通过veins通信模块能够实现carla中感知决策等相关数据的传输。 github:veins_carlapaper:Poster: A Case for Heterogenous Co-Simulation of Cooperative and A…

MySQL 高级(进阶) SQL 语句(二) -----连接查询、union联集、case、正则表达式

目录 1 连接查询 1.1 内连接 1.2 左连接 1.3 右连接 2 UNION ----联集 2.1 交集值 2.2 无交集值 3 case 4 正则表达式 1 连接查询 准备工作&#xff1a; create database k1; use k1; create table location (Region char(20),Store_Name char(20)); insert into loca…

最新时间注入攻击和代码分析技术

点击星标&#xff0c;即时接收最新推文 本文选自《web安全攻防渗透测试实战指南&#xff08;第2版&#xff09;》 点击图片五折购书 时间注入攻击 时间注入攻击的测试地址在本书第2章。 访问该网址时&#xff0c;页面返回yes&#xff1b;在网址的后面加上一个单引号&#xff0c…