欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-07-03 14:51:11
亚克力效果  在我的第一篇博客《如何在pyqt中实现窗口磨砂效果》和第二篇博客《如何在pyqt中实现win10亚克力效果》中,我都是通过调用编译好的dll来实现窗口效果,这种方法要求电脑上必须装有MSVC。Visual Studio装起来确实费时又占C盘空间,所以今天换了一种实现方法——用纯python的方法实现SetWindowCompositionAttribute 这个api的调用。我定义了一个WindowEffect类,里面通过调用SetWindowCompositionAttribute 实现了...

亚克力效果

  在我的第一篇博客《如何在pyqt中实现窗口磨砂效果》和第二篇博客《如何在pyqt中实现win10亚克力效果》中,我都是通过调用编译好的dll来实现窗口效果,这种方法要求电脑上必须装有MSVC。Visual Studio装起来确实费时又占C盘空间,所以今天换了一种实现方法——用纯python的方法实现SetWindowCompositionAttribute 这个api的调用。我定义了一个WindowEffect类,里面通过调用SetWindowCompositionAttribute 实现了Aero和亚克力效果,为了方便演示,我在WindowEffect里面还定义了一个用来移动无边框窗口的函数,关于更多无边框窗体的解决方案可以参见我的上一篇博客《如何在pyqt中在实现无边框窗体的同时保留Windows窗口动画效果》,这里面介绍的很详细,不过还是用了一些C++的东西。下面是这次的亚克力效果(老婆太美了(๑¯∀¯๑)):
如何在pyqt中通过调用SetWindowCompositionAttribute实现Win10亚克力效果

具体代码

  1. 与第二篇博客不同,我在 WindowEffect 的setAcrylicEffect函数中新增了两个形参—— isEnableShadowanimationId,看名字就知道一个控制亚克力窗口的阴影,另一个控制磨砂动画。在demo中是看不出 animationId 的具体效果的,只有窗口最大化和还原时这个动画过程才会显现出来,具体过程可以参见上一篇博客,里面的动图显示得很清楚,下面是这个类的代码:

# coding:utf-8

from ctypes import POINTER, c_bool, sizeof, windll,pointer,c_int
from ctypes.wintypes import DWORD, HWND, ULONG

from win32 import win32api, win32gui
from win32.lib import win32con

from .c_structures import (ACCENT_POLICY, ACCENT_STATE,
                           WINDOWCOMPOSITIONATTRIB,
                           WINDOWCOMPOSITIONATTRIBDATA)


class WindowEffect():
    """ 调用windows api实现窗口效果 """

    def __init__(self):
        # 调用api
        self.SetWindowCompositionAttribute = windll.user32.SetWindowCompositionAttribute
        self.SetWindowCompositionAttribute.restype = c_bool
        self.SetWindowCompositionAttribute.argtypes = [
            c_int, POINTER(WINDOWCOMPOSITIONATTRIBDATA)]
        # 初始化结构体
        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 : 窗口句柄\n
        gradientColor : 十六进制亚克力混合色,对应rgba四个分量\n
        isEnableShadow : 控制是否启用窗口阴影\n
        animationId : 控制磨砂动画
        """
        # 亚克力混合色
        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)

        

这里面还用到了一些结构体,各个结构体的定义如下:

# coding:utf-8

from ctypes import POINTER, Structure
from ctypes.wintypes import DWORD, HWND, ULONG
from enum import Enum


class WINDOWCOMPOSITIONATTRIB(Enum):
    WCA_UNDEFINED = 0,
    WCA_NCRENDERING_ENABLED = 1,
    WCA_NCRENDERING_POLICY = 2,
    WCA_TRANSITIONS_FORCEDISABLED = 3,
    WCA_ALLOW_NCPAINT = 4,
    WCA_CAPTION_BUTTON_BOUNDS = 5,
    WCA_NONCLIENT_RTL_LAYOUT = 6,
    WCA_FORCE_ICONIC_REPRESENTATION = 7,
    WCA_EXTENDED_FRAME_BOUNDS = 8,
    WCA_HAS_ICONIC_BITMAP = 9,
    WCA_THEME_ATTRIBUTES = 10,
    WCA_NCRENDERING_EXILED = 11,
    WCA_NCADORNMENTINFO = 12,
    WCA_EXCLUDED_FROM_LIVEPREVIEW = 13,
    WCA_VIDEO_OVERLAY_ACTIVE = 14,
    WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15,
    WCA_DISALLOW_PEEK = 16,
    WCA_CLOAK = 17,
    WCA_CLOAKED = 18,
    WCA_ACCENT_POLICY = 19,
    WCA_FREEZE_REPRESENTATION = 20,
    WCA_EVER_UNCLOAKED = 21,
    WCA_VISUAL_OWNER = 22,
    WCA_LAST = 23


class ACCENT_STATE(Enum):
    """ 客户区状态枚举类 """
    ACCENT_DISABLED = 0,
    ACCENT_ENABLE_GRADIENT = 1,
    ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
    ACCENT_ENABLE_BLURBEHIND = 3,          # Aero效果
    ACCENT_ENABLE_ACRYLICBLURBEHIND = 4,   # 亚克力效果
    ACCENT_INVALID_STATE = 5


class ACCENT_POLICY(Structure):
    """ 设置客户区的具体属性 """
    _fields_ = [
        ('AccentState',   DWORD),
        ('AccentFlags',   DWORD),
        ('GradientColor', DWORD),
        ('AnimationId',   DWORD),
    ]


class WINDOWCOMPOSITIONATTRIBDATA(Structure):
    _fields_ = [
        ('Attribute',  DWORD),
        ('Data',       POINTER(ACCENT_POLICY)), # POINTER()接收任何ctypes类型,并返回一个指针类型
        ('SizeOfData', ULONG),
    ]

  2. 下面是这次用到的demo文件:

# 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.windowEffect = WindowEffect()
        self.resize(500, 500)
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setAttribute(Qt.WA_NoSystemBackground)
        self.windowEffect.setAcrylicEffect(int(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_())

写在最后

  文中用到的文件我都放在了百度网盘(提取码:m1jr)中,可以自取,喜欢的话就点个赞吧~~

本文地址:https://blog.csdn.net/zhiyiYo/article/details/107891876