亞克力效果
在《如何在pyqt中實現窗口磨砂效果》和《如何在pyqt中實現win10亞克力效果》中,我們調用C++ dll來實現窗口效果,這種方法要求電腦上必須裝有MSVC。Visual Studio裝起來確實費時又占C盤空間,所以今天在python中直接調用 SetWindowCompositionAttribute
,關於更多無邊框窗體的解決方案可以參見《如何在pyqt中自定義無邊框窗口》。下面是這次的亞克力效果(硝子太美了(๑¯∀¯๑)):

具體代碼
結構體和枚舉類
為了調用 Windows 的 API,我們需要定義一些結構體和枚舉類,具體如下:
# 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),
]
WindowEffect 類
WindowEffect
的 setAcrylicEffect
函數中新增了兩個參數—— isEnableShadow
和 animationId
,一個控制亞克力窗口的陰影,另一個控制磨砂動畫。在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):
""" 開啟亞克力效果
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: int
窗口句柄
"""
self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_ENABLE_BLURBEHIND.value[0]
# 開啟Aero
self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData))
def moveWindow(self, hWnd: int):
""" 移動窗口
Parameter
----------
hWnd: int or `sip.voidptr`
窗口句柄
"""
win32gui.ReleaseCapture()
win32api.SendMessage(hWnd, win32con.WM_SYSCOMMAND,
win32con.SC_MOVE + win32con.HTCAPTION, 0)
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)
# 必須用樣式表使背景透明,別用 setAttribute(Qt.WA_TranslucentBackground),不然界面會卡頓
self.setStyleSheet("background:transparent")
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)中,可以自取,喜歡的話就點個贊吧~~