当前位置: 首页 > news >正文

使用 SetWindowCompositionAttribute 来控制程序的窗口边框和背景(可以做 Acrylic 亚克力效果、模糊效果、主题色效果等)

Windows 系统中有一个没什么文档的 API,SetWindowCompositionAttribute,可以允许应用的开发者将自己窗口中的内容渲染与窗口进行组合。这可以实现很多系统中预设的窗口特效,比如 Windows 7 的毛玻璃特效,Windows 8/10 的前景色特效,Windows 10 的模糊特效,以及 Windows 10 1709 的亚克力(Acrylic)特效。而且这些组合都发生在 dwm 进程中,不会额外占用应用程序的渲染性能。

本文介绍 SetWindowCompositionAttribute 可以实现的所有效果。你可以通过阅读本文了解到与系统窗口可以组合渲染到哪些程度。


本文内容

    • 试验用的源代码
    • 影响因素
    • 排列组合
      • AccentState=ACCENT_DISABLED
      • AccentState=ACCENT_ENABLE_GRADIENT
      • AccentState=ACCENT_ENABLE_TRANSPARENTGRADIENT
      • AccentState=ACCENT_ENABLE_BLURBEHIND
      • AccentState=ACCENT_ENABLE_ACRYLICBLURBEHIND
      • AccentState=ACCENT_INVALID_STATE
    • 总结
    • 附源代码

试验用的源代码

本文将创建一个简单的 WPF 程序来验证 SetWindowCompositionAttribute 能达到的各种效果。你也可以不使用 WPF,得到类似的效果。

简单的项目文件结构是这样的:

  • [项目] Walterlv.WindowComposition
    • App.xaml
    • App.xaml.cs
    • MainWindow.xaml
    • MainWindow.xaml.cs
    • WindowAccentCompositor

其中,App.xaml 和 App.xaml.cs 保持默认生成的不动。

为了验证此 API 的效果,我需要将 WPF 主窗口的背景色设置为纯透明或者 null,而设置 ControlTemplate 才能彻彻底底确保所有的样式一定是受我们自己控制的,我们在 ControlTemplate 中没有指定任何可以显示的内容。MainWindow.xaml 的全部代码如下:

<Window x:Class="Walterlv.WindowComposition.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="欢迎访问吕毅的博客:blog.walterlv.com" Height="450" Width="800">
    <Window.Template>
        <ControlTemplate TargetType="Window">
            <AdornerDecorator>
                <ContentPresenter />
            </AdornerDecorator>
        </ControlTemplate>
    </Window.Template>
    <!-- 我们注释掉 WindowChrome,是因为即将验证 WindowChrome 带来的影响。 -->
    <!--<WindowChrome.WindowChrome>
        <WindowChrome GlassFrameThickness="-1" />
    </WindowChrome.WindowChrome>-->
    <Grid>
    </Grid>
</Window>

而 MainWindow.xaml.cs 中,我们简单调用一下我们即将写的调用 SetWindowCompositionAttribute 的类型。

using System.Windows;
using System.Windows.Media;
using Walterlv.Windows.Effects;

namespace Walterlv.WindowComposition
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            var compositor = new WindowAccentCompositor(this);
            compositor.Composite(Color.FromRgb(0x18, 0xa0, 0x5e));
        }
    }
}

还剩下一个 WindowAccentCompositor.cs 文件,因为比较长放到博客里影响阅读,所以建议前往这里查看:

  • Walterlv.Packages/WindowAccentCompositor.cs at master · walterlv/Walterlv.Packages

而其中对我们最终渲染效果有影响的就是 AccentPolicy 类型的几个属性。其中 AccentState 属性是下面这个枚举,而 GradientColor 将决定窗口渲染时叠加的颜色。

private enum AccentState
{
    ACCENT_DISABLED = 0,
    ACCENT_ENABLE_GRADIENT = 1,
    ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
    ACCENT_ENABLE_BLURBEHIND = 3,
    ACCENT_ENABLE_ACRYLICBLURBEHIND = 4,
    ACCENT_INVALID_STATE = 5,
}

影响因素

经过试验,对最终显示效果有影响的有这些:

  • 选择的 AccentState 枚举值
  • 使用的 GradientColor 叠加色
  • 是否使用 WindowChrome 让客户区覆盖非客户区
  • 目标操作系统(Windows 7/8/8.1/10)

使用 WindowChrome,你可以用你自己的 UI 覆盖掉系统的 UI 窗口样式。关于 WindowChrome 让客户区覆盖非客户区的知识,可以阅读:

  • [WPF 自定义控件] Window(窗体)的 UI 元素及行为 - dino.c - 博客园

需要注意的是,WindowChromeGlassFrameThickness 属性可以设置窗口边框的粗细,设置为 0 将导致窗口没有阴影,设置为负数将使得整个窗口都是边框。

排列组合

我们依次来看看效果。

AccentState=ACCENT_DISABLED

使用 ACCENT_DISABLED 时,GradientColor 叠加色没有任何影响,唯一影响渲染的是 WindowChrome 和操作系统。


不使用 WindowChrome,在 Windows 10 上:

without WindowChrome in Windows 10


不使用 WindowChrome 在 Windows 7 上:

without WindowChrome in Windows 7


在 Windows 10 上,使用 WindowChrome

<WindowChrome.WindowChrome>
    <WindowChrome />
</WindowChrome.WindowChrome>

with WindowChrome in Windows 10


在 Windows 7 上,使用 WindowChrome

with WindowChrome in Windows 7

当然,以上边框比较细,跟系统不搭,可以设置成其他值:

bold thickness WindowChrome in Windows 7


在 Windows 10 上,使用 WindowChrome 并且 GlassFrameThickness 设置为 -1

<WindowChrome.WindowChrome>
    <WindowChrome GlassFrameThickness="-1" />
</WindowChrome.WindowChrome>

-1 glass frame in Windows 10


而在 Windows 7 上,这就是非常绚丽的全窗口的 Aero 毛玻璃特效:

-1 glass frame in Windows 7

AccentState=ACCENT_ENABLE_GRADIENT

使用 ACCENT_DISABLED 时,GradientColor 叠加色会影响到最终的渲染效果。

还记得我们前面叠加的颜色是什么吗?

叠加的颜色

接下来别忘了然后把它误以为是我系统的主题色哦!


不使用 WindowChrome,在 Windows 10 上:

without WindowChrome in Windows 10

另外,你会注意到左、下、右三个方向上边框会深一些。那是 Windows 10 的窗口阴影效果,因为实际上 Windows 10 叠加的阴影也是窗口区域的一部分,只是一般人看不出来而已。我们叠加了颜色之后,这里就露馅儿了。

另外,这个颜色并不是我们自己的进程绘制的哦,是 dwm 绘制的颜色。

如果不指定 GradientColor 也就是保持为 0,你将看到上面绿色的部分全是黑色的;嗯,包括阴影的部分……

without WindowChrome in Windows 10 - default gradient color


不使用 WindowChrome 在 Windows 7 上:

without WindowChrome in Windows 7

可以看出,在 Windows 7 上,GradientColor 被无视了。


而使用 WindowChrome 在 Windows 10 上,则可以得到整个窗口的叠加色:

<WindowChrome.WindowChrome>
    <WindowChrome GlassFrameThickness="16 48 16 16" />
</WindowChrome.WindowChrome>

with WindowChrome in Windows 10

可以注意到,窗口获得焦点的时候,整个窗口都是叠加色;而窗口失去焦点的时候,指定了边框的部分颜色会更深(换其他颜色叠加可以看出来是叠加了半透明黑色)。

如果你希望失去焦点的时候,边框部分不要变深,请将边框设置为 -1

<WindowChrome.WindowChrome>
    <WindowChrome GlassFrameThickness="-1" />
</WindowChrome.WindowChrome>

使用 WindowChrome 在 Windows 7 上,依然没有任何叠加色的效果:

with WindowChrome in Windows 7

AccentState=ACCENT_ENABLE_TRANSPARENTGRADIENT

使用 ACCENT_ENABLE_TRANSPARENTGRADIENT 时,GradientColor 叠加色没有任何影响,唯一影响渲染的是 WindowChrome 和操作系统。


不使用 WindowChrome,在 Windows 10 上:

without WindowChrome frame in Windows 10

依然左、下、右三个方向上边框会深一些,那是 Windows 10 的窗口阴影效果。


不使用 WindowChrome 在 Windows 7 上:

without WindowChrome in Windows 7

GradientColor 也是被无视的,而且效果跟之前一样。


使用 WindowChrome 在 Windows 10 上,在获得焦点的时候整个背景是系统主题色;而失去焦点的时候是灰色,但边框部分是深色。

with WindowChrome frame in Windows 10

依然可以将边框设置为 -1 使得边框不会变深:

with WindowChrome in Windows 10


使用 WindowChrome 在 Windows 7 上,依然是老样子:

with WindowChrome in Windows 7

AccentState=ACCENT_ENABLE_BLURBEHIND

ACCENT_ENABLE_BLURBEHIND 可以在 Windows 10 上做出模糊效果,就跟 Windows 10 早期版本的模糊效果是一样的。你可以看我之前的一篇博客,那时亚克力效果还没出来:

  • 在 Windows 10 上为 WPF 窗口添加模糊特效(就像开始菜单和操作中心那样) - walterlv

使用 ACCENT_ENABLE_BLURBEHIND 时,GradientColor 叠加色没有任何影响,唯一影响渲染的是 WindowChrome 和操作系统。


在 Windows 10 上,没有使用 WindowChrome

模糊效果

你可能需要留意一下那个“诡异”的模糊范围,你会发现窗口的阴影外侧也是有模糊的!!!你能忍吗?肯定不能忍,所以还是乖乖使用 WindowChrome 吧!


在 Windows 7 上,没有使用 WindowChrome,效果跟其他值一样,依然没有变化:

without WindowChrome in Windows 7


在 Windows 10 上,使用 WindowChrome

with WindowChrome in Windows 10


使用 WindowChrome 在 Windows 7 上,依然是老样子:

with WindowChrome in Windows 7

AccentState=ACCENT_ENABLE_ACRYLICBLURBEHIND

从 Windows 10 (1803) 开始,Win32 程序也能添加亚克力效果了,因为 SetWindowCompositionAttribute 的参数枚举新增了 ACCENT_ENABLE_ACRYLICBLURBEHIND

亚克力效果相信大家不陌生,那么在 Win32 应用程序里面使用的效果是什么呢?


不使用 WindowChrome,在 Windows 10 上:

without WindowChrome in Windows 10

咦!等等!这不是跟之前一样吗?


嗯,下面就是不同了,亚克力效果支持与半透明的 GradientColor 叠加,所以我们需要将传入的颜色修改为半透明:

    var compositor = new WindowAccentCompositor(this);
--  compositor.Composite(Color.FromRgb(0x18, 0xa0, 0x5e));
++  compositor.Composite(Color.FromArgb(0x3f, 0x18, 0xa0, 0x5e));

acrylic without WindowChrome


那么如果改为全透明会怎么样呢?

不幸的是,完全没有效果!!!

    var compositor = new WindowAccentCompositor(this);
--  compositor.Composite(Color.FromRgb(0x18, 0xa0, 0x5e));
++  compositor.Composite(Color.FromArgb(0x00, 0x18, 0xa0, 0x5e));

no acrylic without WindowChrome


接下来是使用 WindowChrome 时:

<WindowChrome.WindowChrome>
    <WindowChrome GlassFrameThickness="16 48 16 16" />
</WindowChrome.WindowChrome>

acrylic with WindowChrome frame

然而周围有一圈偏白色的渐变是什么呢?那个其实是 WindowChrome 设置的边框白,被亚克力效果模糊后得到的混合效果。


所以,如果要获得全窗口的亚克力效果,请将边框设置成比较小的值:

<WindowChrome.WindowChrome>
    <WindowChrome GlassFrameThickness="0 1 0 0" />
</WindowChrome.WindowChrome>

acrylic with thin WindowChrome frame


记得不要像前面的那些效果一样,如果设置成 -1,你将获得纯白色与设置的 Gradient 叠加色的亚克力特效,是个纯色:

acrylic with WindowChrome -1 frame


你可以将叠加色的透明度设置得小一些,这样可以看出叠加的颜色:

    var compositor = new WindowAccentCompositor(this);
--  compositor.Composite(Color.FromRgb(0x18, 0xa0, 0x5e));
++  compositor.Composite(Color.FromArgb(0xa0, 0x18, 0xa0, 0x5e));

acrylic with darker gradient color


那么可以设置为全透明吗?

    var compositor = new WindowAccentCompositor(this);
--  compositor.Composite(Color.FromRgb(0x18, 0xa0, 0x5e));
++  compositor.Composite(Color.FromArgb(0x00, 0x18, 0xa0, 0x5e));

很不幸,最终你会完全看不到亚克力效果,而变成了毫无特效的透明窗口:

acrylic with transparent gradient color

最上面那根白线,是我面前面设置边框为 0 1 0 0 导致的。


如果在这种情况下,将边框设置为 0 会怎样呢?记得前面我们说过的吗,会导致阴影消失哦!

呃……你将看到……这个……

什么都没有……

acrylic with zero WindowChrome frame thickness

是不是找到了一条新的背景透明异形窗口的方法?

还是省点心吧,亚克力效果在 Win32 应用上的性能还是比较堪忧的……

想要背景透明,请参见:

  • WPF 制作高性能的透明背景异形窗口(使用 WindowChrome 而不要使用 AllowsTransparency=True) - walterlv

不用考虑 Windows 7,因为大家都知道不支持。实际效果会跟前面的一模一样。

AccentState=ACCENT_INVALID_STATE

这个值其实不用说了,因为 AccentState 在不同系统中可用的值不同,为了保证向后兼容性,对于新系统中设置的值,旧系统其实就视之为 ACCENT_INVALID_STATE

那么如果系统认为设置的是 ACCENT_INVALID_STATE 会显示成什么样子呢?

答案是,与 ACCENT_DISABLED 完全相同。

总结

由于 Windows 7 上所有的值都是同样的效果,所以下表仅适用于 Windows 10。

效果
ACCENT_DISABLED黑色(边框为纯白色)
ACCENT_ENABLE_GRADIENTGradientColor 颜色(失焦后边框为深色)
ACCENT_ENABLE_TRANSPARENTGRADIENT主题色(失焦后边框为深色)
ACCENT_ENABLE_BLURBEHIND模糊特效(失焦后边框为灰色)
ACCENT_ENABLE_ACRYLICBLURBEHIND与 GradientColor 叠加颜色的亚克力特效
ACCENT_INVALID_STATE黑色(边框为纯白色)

在以上的特效之下,WindowChrome 可以让客户区覆盖非客户区,或者让整个窗口都获得特效,而不只是标题栏。

附源代码

请参见 GitHub 地址以获得最新代码。如果不方便访问,那么就看下面的吧。

  • Walterlv.Packages/WindowAccentCompositor.cs at master · walterlv/Walterlv.Packages
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;

namespace Walterlv.Windows.Effects
{
    /// <summary>
    /// 为窗口提供模糊特效。
    /// </summary>
    public class WindowAccentCompositor
    {
        private readonly Window _window;

        /// <summary>
        /// 创建 <see cref="WindowAccentCompositor"/> 的一个新实例。
        /// </summary>
        /// <param name="window">要创建模糊特效的窗口实例。</param>
        public WindowAccentCompositor(Window window) => _window = window ?? throw new ArgumentNullException(nameof(window));

        public void Composite(Color color)
        {
            Window window = _window;
            var handle = new WindowInteropHelper(window).EnsureHandle();

            var gradientColor =
                // 组装红色分量。
                color.R << 0 |
                // 组装绿色分量。
                color.G << 8 |
                // 组装蓝色分量。
                color.B << 16 |
                // 组装透明分量。
                color.A << 24;

            Composite(handle, gradientColor);
        }

        private void Composite(IntPtr handle, int color)
        {
            // 创建 AccentPolicy 对象。
            var accent = new AccentPolicy
            {
                AccentState = AccentState.ACCENT_ENABLE_ACRYLICBLURBEHIND,
                GradientColor = 0,
            };

            // 将托管结构转换为非托管对象。
            var accentPolicySize = Marshal.SizeOf(accent);
            var accentPtr = Marshal.AllocHGlobal(accentPolicySize);
            Marshal.StructureToPtr(accent, accentPtr, false);

            // 设置窗口组合特性。
            try
            {
                // 设置模糊特效。
                var data = new WindowCompositionAttributeData
                {
                    Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY,
                    SizeOfData = accentPolicySize,
                    Data = accentPtr,
                };
                SetWindowCompositionAttribute(handle, ref data);
            }
            finally
            {
                // 释放非托管对象。
                Marshal.FreeHGlobal(accentPtr);
            }
        }

        [DllImport("user32.dll")]
        private static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data);

        private enum AccentState
        {
            ACCENT_DISABLED = 0,
            ACCENT_ENABLE_GRADIENT = 1,
            ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
            ACCENT_ENABLE_BLURBEHIND = 3,
            ACCENT_ENABLE_ACRYLICBLURBEHIND = 4,
            ACCENT_INVALID_STATE = 5,
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct AccentPolicy
        {
            public AccentState AccentState;
            public int AccentFlags;
            public int GradientColor;
            public int AnimationId;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct WindowCompositionAttributeData
        {
            public WindowCompositionAttribute Attribute;
            public IntPtr Data;
            public int SizeOfData;
        }

        private enum WindowCompositionAttribute
        {
            // 省略其他未使用的字段
            WCA_ACCENT_POLICY = 19,
            // 省略其他未使用的字段
        }
    }
}

我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

相关文章:

  • input 不能为空 js_我用JS刷LeetCode | Day 9 | Implement strStr()
  • 宝塔同时安装苹果cms海洋cms_maccms安装了后接下来做什么?
  • hive 修改表的存储格式_Hive存储格式
  • mac redis 链接_2018 MAC下安装Redis和Redis可视化工具RDM并连接Redis
  • python的lib文件夹在哪_如何导入模块中的lib文件夹
  • heidisql连接不是本地_本地备份与云备份:选择您的备份计划
  • ibaties 更新 数据类型不一致_关于Python 3.9,那些你不知道的事
  • join为什么每个字符都分割了 js_【项目总结】之——JS分割字符串
  • rto净化效率计算公式_吕梁油墨厂RTO焚烧炉设计计算
  • 修改段落内容_为什么论文修改后重复率还变高?
  • windbg找不到pdb文件_使用Windbg时关于符号文件路径设置问题
  • layui open传参_layui 打开新页面,并传入参数
  • cnc加工中心保养表_CNC加工中心有哪些日常保养方法?
  • ffplay拉流时间长卡住_杭州女子崩溃:我吞了个勺子进去,14厘米长!事情要从一条鱼说起…...
  • 多台路由器堆叠_一文了解核心交换机的链路聚合、冗余、堆叠、热备份等专业知识...
  • [case10]使用RSQL实现端到端的动态查询
  • [译]如何构建服务器端web组件,为何要构建?
  • Angular4 模板式表单用法以及验证
  • gops —— Go 程序诊断分析工具
  • HomeBrew常规使用教程
  • JavaSE小实践1:Java爬取斗图网站的所有表情包
  • SpringCloud(第 039 篇)链接Mysql数据库,通过JpaRepository编写数据库访问
  • underscore源码剖析之整体架构
  • WePY 在小程序性能调优上做出的探究
  • 分类模型——Logistics Regression
  • 基于HAProxy的高性能缓存服务器nuster
  • 前端 CSS : 5# 纯 CSS 实现24小时超市
  • 以太坊客户端Geth命令参数详解
  • 最近的计划
  • ​ubuntu下安装kvm虚拟机
  • #我与Java虚拟机的故事#连载15:完整阅读的第一本技术书籍
  • (javascript)再说document.body.scrollTop的使用问题
  • (Java数据结构)ArrayList
  • (Redis使用系列) SpringBoot 中对应2.0.x版本的Redis配置 一
  • (附源码)ssm基于微信小程序的疫苗管理系统 毕业设计 092354
  • (论文阅读31/100)Stacked hourglass networks for human pose estimation
  • (学习日记)2024.01.19
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • (总结)Linux下的暴力密码在线破解工具Hydra详解
  • .MSSQLSERVER 导入导出 命令集--堪称经典,值得借鉴!
  • .net core webapi 部署iis_一键部署VS插件:让.NET开发者更幸福
  • .net core 调用c dll_用C++生成一个简单的DLL文件VS2008
  • .Net Core/.Net6/.Net8 ,启动配置/Program.cs 配置
  • .NET Core中Emit的使用
  • .net中调用windows performance记录性能信息
  • @Import注解详解
  • @transaction 提交事务_【读源码】剖析TCCTransaction事务提交实现细节
  • [20180224]expdp query 写法问题.txt
  • [202209]mysql8.0 双主集群搭建 亲测可用
  • [Angular] 笔记 9:list/detail 页面以及@Output
  • [BUUCTF]-PWN:wustctf2020_number_game解析(补码,整数漏洞)
  • [c++] C++多态(虚函数和虚继承)
  • [COGS 622] [NOIP2011] 玛雅游戏 模拟
  • [JavaScript] JavaScript事件注册,事件委托,冒泡,捕获,事件流
  • [LeetCode] Sort List