前言

在之前的博客《如何在pyqt中通过调用SetWindowCompositionAttribute实现Win10亚克力效果》中,我们实现了窗口的亚克力效果,同时也用SetWindowCompositionAttribute() 给亚克力窗口加上了阴影。但是更多时候我们用不到亚克力效果,但又需要给无边框窗口加上阴影。一种方法是在当前窗口外嵌套一层窗口,然后用 QGraphicsDropShadowEffect 给里面的窗口加上阴影,还有一种就是重写 paintEvent()来绘制阴影。下面来讨论一下使用 dwmapi 来给无边框窗口添加阴影的方法。效果如下 (硝子太美啦٩(๑>◡<๑)۶ ):

实现过程

接口函数

为了实现DWM 环绕阴影,需要调用dwmapi 中的两个函数:

  • HRESULT DwmSetWindowAttribute (HWND hwnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute),用来设置窗口的桌面窗口管理器(DWM)非客户端呈现属性的值,可以参见文档 DwmSetWindowAttribute函数
  • HRESULT DwmExtendFrameIntoClientArea (HWND hWnd, const MARGINS *pMarInset),用来将窗口框架扩展到工作区,参见文档DwmExtendFrameIntoClientArea函数DWM模糊概述

在调用这两个函数之前,我们需要先在WindowEffect的构造函数中声明一下他们的函数原型

self.dwmapi = WinDLL("dwmapi")
self.DwmExtendFrameIntoClientArea = self.dwmapi.DwmExtendFrameIntoClientArea
self.DwmSetWindowAttribute = self.dwmapi.DwmSetWindowAttribute
self.DwmExtendFrameIntoClientArea.restype = LONG
self.DwmSetWindowAttribute.restype = LONG
self.DwmSetWindowAttribute.argtypes = [c_int, DWORD, LPCVOID, DWORD]
self.DwmExtendFrameIntoClientArea.argtypes = [c_int, POINTER(MARGINS)]

结构体和枚举类

从MSDN文档可以得知,传入 DwmExtendFrameIntoClientArea() 的第二个参数 pMarInset 是一个结构体 MARGIN 的指针,所以我们下面定义一下 MARGIN ,同时定义一些要用到的枚举类(其他关于亚克力效果的结构体和枚举类见《如何在pyqt中通过调用SetWindowCompositionAttribute实现Win10亚克力效果》):

# coding: utf-8
from ctypes import Structure, c_int
from enum import Enum class DWMNCRENDERINGPOLICY(Enum):
DWMNCRP_USEWINDOWSTYLE = 0
DWMNCRP_DISABLED = 1
DWMNCRP_ENABLED = 2
DWMNCRP_LAS = 3 class DWMWINDOWATTRIBUTE(Enum):
DWMWA_NCRENDERING_ENABLED = 1
DWMWA_NCRENDERING_POLICY = 2
DWMWA_TRANSITIONS_FORCEDISABLED = 3
DWMWA_ALLOW_NCPAINT = 4
DWMWA_CAPTION_BUTTON_BOUNDS = 5
DWMWA_NONCLIENT_RTL_LAYOUT = 6
DWMWA_FORCE_ICONIC_REPRESENTATION = 7
DWMWA_FLIP3D_POLICY = 8
DWMWA_EXTENDED_FRAME_BOUNDS = 9
DWMWA_HAS_ICONIC_BITMAP = 10
DWMWA_DISALLOW_PEEK = 11
DWMWA_EXCLUDED_FROM_PEEK = 12
DWMWA_CLOAK = 13
DWMWA_CLOAKED = 14
DWMWA_FREEZE_REPRESENTATION = 25
DWMWA_LAST = 16 class MARGINS(Structure):
_fields_ = [
("cxLeftWidth", c_int),
("cxRightWidth", c_int),
("cyTopHeight", c_int),
("cyBottomHeight", c_int),
]

WindowEffect 类

准备工作完成,我们来看一下 WindowEffect 中拿来给无边框窗口添加环绕阴影的函数:

def addShadowEffect(self, hWnd):
""" 给窗口添加阴影 Parameter
----------
hWnd: int or `sip.voidptr`
窗口句柄
"""
hWnd = int(hWnd)
self.DwmSetWindowAttribute(
hWnd,
DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY.value,
byref(c_int(DWMNCRENDERINGPOLICY.DWMNCRP_ENABLED.value)),
4,
)
margins = MARGINS(-1, -1, -1, -1)
self.DwmExtendFrameIntoClientArea(hWnd, byref(margins))

下面给出整个 WindowEffect 类的代码,这里面包括了设置亚克力效果的方法、给窗口添加阴影的方法和移动窗口的方法:

# coding:utf-8

from ctypes import POINTER, c_bool, c_int, pointer, sizeof, WinDLL, byref
from ctypes.wintypes import DWORD, HWND, LONG, LPCVOID from win32 import win32api, win32gui
from win32.lib import win32con from .c_structures import (
ACCENT_POLICY,
ACCENT_STATE,
MARGINS,
DWMNCRENDERINGPOLICY,
DWMWINDOWATTRIBUTE,
WINDOWCOMPOSITIONATTRIB,
WINDOWCOMPOSITIONATTRIBDATA,
) class WindowEffect:
""" 调用windows api实现窗口效果 """ def __init__(self):
# 调用api
self.user32 = WinDLL("user32")
self.dwmapi = WinDLL("dwmapi")
self.SetWindowCompositionAttribute = self.user32.SetWindowCompositionAttribute
self.DwmExtendFrameIntoClientArea = self.dwmapi.DwmExtendFrameIntoClientArea
self.DwmSetWindowAttribute = self.dwmapi.DwmSetWindowAttribute
self.SetWindowCompositionAttribute.restype = c_bool
self.DwmExtendFrameIntoClientArea.restype = LONG
self.DwmSetWindowAttribute.restype = LONG
self.SetWindowCompositionAttribute.argtypes = [
c_int,
POINTER(WINDOWCOMPOSITIONATTRIBDATA),
]
self.DwmSetWindowAttribute.argtypes = [c_int, DWORD, LPCVOID, DWORD]
self.DwmExtendFrameIntoClientArea.argtypes = [c_int, POINTER(MARGINS)]
# 初始化结构体
self.accentPolicy = ACCENT_POLICY()
self.winCompAttrData = WINDOWCOMPOSITIONATTRIBDATA()
self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_ACCENT_POLICY.value[0]
self.winCompAttrData.SizeOfData = sizeof(self.accentPolicy)
self.winCompAttrData.Data = pointer(self.accentPolicy) def setAcrylicEffect(self, hWnd: int, gradientColor: str = "F2F2F230",
isEnableShadow: bool = True, animationId: int = 0):
""" 给窗口开启Win10的亚克力效果 Parameters
----------
hWnd: int
窗口句柄 gradientColor: str
十六进制亚克力混合色,对应rgba四个分量 isEnableShadow: bool
控制是否启用窗口阴影 animationId: int
控制磨砂动画
"""
# 亚克力混合色
gradientColor = (
gradientColor[6:]
+ gradientColor[4:6]
+ gradientColor[2:4]
+ gradientColor[:2]
)
gradientColor = DWORD(int(gradientColor, base=16))
# 磨砂动画
animationId = DWORD(animationId)
# 窗口阴影
accentFlags = DWORD(0x20 | 0x40 | 0x80 |
0x100) if isEnableShadow else DWORD(0)
self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_ENABLE_ACRYLICBLURBEHIND.value[
0
]
self.accentPolicy.GradientColor = gradientColor
self.accentPolicy.AccentFlags = accentFlags
self.accentPolicy.AnimationId = animationId
# 开启亚克力
self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) def setAeroEffect(self, hWnd: int):
""" 给窗口开启Aero效果 Parameter
----------
hWnd : 窗口句柄
"""
self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_ENABLE_BLURBEHIND.value[0]
# 开启Aero
self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) def moveWindow(self, hWnd: int):
""" 移动窗口 Parameter
----------
hWnd : 窗口句柄
"""
win32gui.ReleaseCapture()
win32api.SendMessage(
hWnd, win32con.WM_SYSCOMMAND, win32con.SC_MOVE + win32con.HTCAPTION, 0
) def addShadowEffect(self, hWnd):
""" 给窗口添加阴影 Parameter
----------
hWnd: int or `sip.voidptr`
窗口句柄
"""
hWnd = int(hWnd)
self.DwmSetWindowAttribute(
hWnd,
DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY.value,
byref(c_int(DWMNCRENDERINGPOLICY.DWMNCRP_ENABLED.value)),
4,
)
margins = MARGINS(-1, -1, -1, -1)
self.DwmExtendFrameIntoClientArea(hWnd, byref(margins))

测试

# coding:utf-8
import sys from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget from my_window_effect import WindowEffect class Demo(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.resize(500, 500)
self.windowEffect = WindowEffect()
# 取消窗口边框
self.setWindowFlags(Qt.FramelessWindowHint)
# 添加环绕阴影
self.windowEffect.addShadowEffect(self.winId()) def mousePressEvent(self, QMouseEvent):
self.windowEffect.moveWindow(self.winId()) if __name__ == "__main__":
app = QApplication(sys.argv)
demo = Demo()
demo.show()
sys.exit(app.exec_())

后记

关于如何给无边框窗口添加DWM环绕阴影的介绍到此结束,有帮助的话就点个赞吧 []~( ̄▽ ̄)~*。当然正如我在《如何在pyqt中在实现无边框窗体的同时保留Windows窗口动画效果(一)》所言,无边框窗口意味着窗口动画的消失,要解决这个问题参见《如何在pyqt中在实现无边框窗口的同时保留Windows窗口动画效果(二)》 和《如何在pyqt中自定义无边框窗口》。以上~~

如何在pyqt中给无边框窗口添加DWM环绕阴影的更多相关文章

  1. 如何在pyqt中自定义无边框窗口

    前言 之前写过很多关于无边框窗口并给窗口添加特效的博客,按照时间线罗列如下: 如何在pyqt中实现窗口磨砂效果 如何在pyqt中实现win10亚克力效果 如何在pyqt中通过调用SetWindowCo ...

  2. Delphi中拖动无边框窗口的5种方法

    1.MouseMove事件中加入: // ReleaseCapture;// Perform(WM_SYSCOMMAND, $F017 , 0); 2.MouseDown事件中加入: // POSTM ...

  3. 如何在pyqt中在实现无边框窗口的同时保留Windows窗口动画效果(一)

    无边框窗体的实现思路 在pyqt中只要 self.setWindowFlags(Qt.FramelessWindowHint) 就可以实现边框的去除,但是没了标题栏也意味着窗口大小无法改变.窗口无法拖 ...

  4. 如何在pyqt中通过调用 SetWindowCompositionAttribute 实现Win10亚克力效果

    亚克力效果 在<如何在pyqt中实现窗口磨砂效果>和<如何在pyqt中实现win10亚克力效果>中,我们调用C++ dll来实现窗口效果,这种方法要求电脑上必须装有MSVC.V ...

  5. 如何在 pyqt 中捕获并处理 Alt+F4 快捷键

    前言 如果在 Windows 系统的任意一个窗口中按下 Alt+F4,默认行为是关闭窗口(或者最小化到托盘).对于使用了亚克力效果的窗口,使用 Alt+F4 最小化到托盘,再次弹出窗口的时候可能出现亚 ...

  6. Qt5:无边框窗口拖动

    在窗口程序中,无边框窗口程序一般需要特殊处理才能拖动 Qt中,要实现无边框窗口的拖动,需要重新实现 mousePressEvent 和 mouseMoveEvent 俩虚函数 void Widget: ...

  7. 如何在pyqt中实现窗口磨砂效果

    磨砂效果的实现思路 这两周一直在思考怎么在pyqt上实现窗口磨砂效果,网上搜了一圈,全都是 C++ 的实现方法.正好今天查python的官方文档的时候看到了 ctypes 里面的 HWND,想想倒不如 ...

  8. 如何在pyqt中实现win10亚克力效果

    亚克力效果的实现思路 上一篇博客<如何在pyqt中实现窗口磨砂效果> 中实现了win7中的Aero效果,但是和win10的亚克力效果相比,Aero还是差了点内味.所以今天早上又在网上搜了一 ...

  9. 让Qt的无边框窗口支持拖拽、Aero Snap、窗口阴影等特性

    环境:Desktop Qt 5.4.1 MSVC2013 32bit 需要的库:dwmapi.lib .user32.lib 需要头文件:<dwmapi.h> .<windowsx. ...

随机推荐

  1. 【死磕Java并发】-----Java内存模型之重排序

    在执行程序时,为了提供性能,处理器和编译器常常会对指令进行重排序,但是不能随意重排序,不是你想怎么排序就怎么排序,它需要满足以下两个条件: 在单线程环境下不能改变程序运行的结果: 存在数据依赖关系的不 ...

  2. 解决opencv:AttributeError: 'NoneType' object has no attribute 'copy'

    情况一: 路径中有中文,更改即可 情况二:可以运行代码,在运行结束时显示 AttributeError: 'NoneType' object has no attribute 'copy' 因为如果是 ...

  3. [JNI开发]使用javah命令生成.h的头文件

    第一步:进入对应的.java目录 javac xxx.java 生成对应的xxx.class文件 第二步:退回到/java目录 javah -classpath . -jni 包名.类名

  4. 低成本CH7511芯片方案|CH7511电路设计参考|CS5211替代CH7511

    CH7511是主要用于设计eDP转LVDS转换器,怎么样设计一款低成本低BOM简单的DP转LVDS的转接设置,目前有一款可以替代兼容CH7511的方案电路,并且其设计电路整体BOM成本较低,并且设计简 ...

  5. vue中使用JSX报错,如何解决

    Support for the experimental syntax 'jsx' isn't currently enabled (32:12): 30 | }, 31 | render() { & ...

  6. SpringCloud创建Config Client配置读取

    1.说明 本文详细介绍配置中心客户端使用方法, 即Config Client到Config Server读取配置, 这里以创建Config Client服务为例, 基于已经创建好的Config Ser ...

  7. HAproxy开启日志记录

    1.说明 HAproxy在默认情况不会记录日志, 不仅要在haproxy.conf中配置日志输出, 还需要修改系统日志的配置文件. 2.修改haproxy.conf 在haproxy.conf文件中增 ...

  8. Eclipse远程调试Java代码的三种方法

    Eclipse远程调试Java代码的三种方法, 第1种方法是用来调试已经启动的Java程序,Eclipse可以随时连接到远程Java程序进行调试, 第2种方法可以调试Java程序启动过程,但是Ecli ...

  9. 基于GO语言的PBFT共识算法

    最近采用GO语言实现了一个PBFT共识算法的demo,主要用于展示算法的核心逻辑 github地址:https://github.com/w3liu/consensus/tree/master/pbf ...

  10. springboot 开启事务回滚

    在数据库操作时如果发生异常,回滚的方法 在方法上添加注解@Transactional,作用域是方法级的 参考资料: https://www.cnblogs.com/c2g5201314/p/13163 ...