AcrylicUtil 你好 关于 亚克力 和 窗口状态的问题

鑫 李 0 信誉分
2025-12-03T03:57:51.1966667+00:00

使用 未公开的 SetWindowCompositionAttribute (SWCA) 和公开的 DwmSetWindowAttribute ,

完成的亚克力窗口 Python pyside6 第一步 QT 设置无边框窗口 第二步 win32 设置new_style ,窗口为 1 直角窗口 此时 亚克力效果正常显示 , 但是 关闭亚克力 之后 窗口的 样式 变得 很奇怪, 上部分圆角 下部分 直角 边框 和 阴影 都消失了 , 但是 设置 大圆角 和 小圆角 又显示正常 屏幕截图 2025-12-03 113039

屏幕截图 2025-12-03 113032

屏幕截图 2025-12-03 113738

目前 我使用的 直角窗口 亚克力 关闭 回退到大圆角 运行一切正常 但是 无法设置窗口直角了 除非 一开始就完全的不启用 亚克力 不然 启用一次 直角窗口的 边框的阴影 就失效 窗口变为图2 用户的图像

就是无法维持这个边框加阴影的直角样式


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# ——————————————————————————————————————————————————————————————————————————————
# 文件名:Acrylic_Control_Final.py
# 功能:PySide6 无边框窗口 + 亚克力开关 + 圆角/直角切换 + 阴影 + 鼠标修复
# 更新:添加了控制按钮和亚克力状态切换逻辑
# ——————————————————————————————————————————————————————————————————————————————

import sys
import ctypes
from ctypes import wintypes, c_int, c_uint, c_bool, byref, sizeof, Structure, POINTER
from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButton, QHBoxLayout
from PySide6.QtCore import Qt, QTimer
from PySide6.QtGui import QCursor, QPainter, QColor

# ==============================================================================
# 第一部分:Win32 API 底层定义
# ==============================================================================

# --- 常量定义 ---
GWL_STYLE = -16
WS_CAPTION = 0x00C00000
WS_THICKFRAME = 0x00040000
WS_SYSMENU = 0x00080000
WS_MINIMIZEBOX = 0x00020000
WS_MAXIMIZEBOX = 0x00010000

SWP_NOSIZE = 0x0001
SWP_NOMOVE = 0x0002
SWP_NOZORDER = 0x0004
SWP_NOACTIVATE = 0x0010
SWP_FRAMECHANGED = 0x0020
SWP_NOOWNERZORDER = 0x0200

# DWM 属性
DWMWA_USE_IMMERSIVE_DARK_MODE = 20
DWMWA_WINDOW_CORNER_PREFERENCE = 33 # Win11 圆角属性 ID

# 圆角枚举值
DWMWCP_DEFAULT = 0
DWMWCP_DONOTROUND = 1 # 直角
DWMWCP_ROUND = 2      # 圆角
DWMWCP_ROUNDSMALL = 3 # 小圆角

WCA_ACCENT_POLICY = 19
ACCENT_DISABLED = 0
ACCENT_ENABLE_ACRYLICBLURBEHIND = 4

# --- 结构体定义 ---
class MARGINS(Structure):
    _fields_ = [("cxLeftWidth", c_int), ("cxRightWidth", c_int), 
                ("cyTopHeight", c_int), ("cyBottomHeight", c_int)]

class ACCENT_POLICY(Structure):
    _fields_ = [("AccentState", c_uint), ("AccentFlags", c_uint), 
                ("GradientColor", c_uint), ("AnimationId", c_uint)]

class WINDOWCOMPOSITIONATTRIBDATA(Structure):
    _fields_ = [("Attribute", c_int), ("Data", POINTER(ACCENT_POLICY)), ("SizeOfData", c_uint)]

# --- DLL 绑定 ---
user32 = ctypes.windll.user32
dwmapi = ctypes.windll.dwmapi

SetWindowLongPtrW = user32.SetWindowLongPtrW
SetWindowLongPtrW.argtypes = [wintypes.HWND, c_int, ctypes.c_longlong]
SetWindowLongPtrW.restype = ctypes.c_longlong

GetWindowLongPtrW = user32.GetWindowLongPtrW
GetWindowLongPtrW.argtypes = [wintypes.HWND, c_int]
GetWindowLongPtrW.restype = ctypes.c_longlong

SetWindowPos = user32.SetWindowPos
SetWindowPos.argtypes = [wintypes.HWND, wintypes.HWND, c_int, c_int, c_int, c_int, c_uint]
SetWindowPos.restype = wintypes.BOOL

DwmExtendFrameIntoClientArea = dwmapi.DwmExtendFrameIntoClientArea
DwmExtendFrameIntoClientArea.argtypes = [wintypes.HWND, POINTER(MARGINS)]
DwmExtendFrameIntoClientArea.restype = c_int

DwmSetWindowAttribute = dwmapi.DwmSetWindowAttribute
DwmSetWindowAttribute.argtypes = [wintypes.HWND, c_int, ctypes.c_void_p, c_int]
DwmSetWindowAttribute.restype = c_int

SetWindowCompositionAttribute = user32.SetWindowCompositionAttribute
SetWindowCompositionAttribute.argtypes = [c_int, POINTER(WINDOWCOMPOSITIONATTRIBDATA)]
SetWindowCompositionAttribute.restype = c_bool

# ==============================================================================
# 第二部分:Win32 功能封装 (添加了关闭和圆角设置)
# ==============================================================================

def 设置亚克力状态(hwnd: int, enable: bool, hex_color: str = "CC202020"):
    """ 开关亚克力效果 """
    hwnd = int(hwnd)
    
    # 扩展边框 (无论开关都需要保证框架完整)
    margins = MARGINS(-1, -1, -1, -1)
    DwmExtendFrameIntoClientArea(hwnd, byref(margins))

    accent = ACCENT_POLICY()
    if enable:
        # 开启
        try:
            gradient_col = int(hex_color, 16)
        except ValueError:
            gradient_col = 0xCC202020
        accent.AccentState = ACCENT_ENABLE_ACRYLICBLURBEHIND
        accent.AccentFlags = 0x20 | 0x40 | 0x80 | 0x100
        accent.GradientColor = gradient_col
    else:
        # 关闭 (设置为 DISABLED)
        accent.AccentState = ACCENT_DISABLED
        accent.AccentFlags = 0
        accent.GradientColor = 0

    data = WINDOWCOMPOSITIONATTRIBDATA()
    data.Attribute = WCA_ACCENT_POLICY
    data.SizeOfData = sizeof(accent)
    data.Data = ctypes.cast(ctypes.pointer(accent), POINTER(ACCENT_POLICY))

    SetWindowCompositionAttribute(hwnd, byref(data))

def 设置窗口圆角(hwnd: int, mode_id: int):
    """
    mode_id: 
    1 = DWMWCP_DONOTROUND (直角)
    2 = DWMWCP_ROUND (圆角)
    3 = DWMWCP_ROUNDSMALL (小圆角)
    """
    hwnd = int(hwnd)
    val = c_int(mode_id)
    DwmSetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, byref(val), sizeof(val))

def 窗口框架子类化(hwnd):
    """ 初始化窗口样式以支持阴影 """
    hwnd = int(hwnd)
    old_style = GetWindowLongPtrW(hwnd, GWL_STYLE)
    new_style = (old_style | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU)
    SetWindowLongPtrW(hwnd, GWL_STYLE, new_style)
    
    # 开启暗色模式标题栏
    dark_mode = c_int(1)
    DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, byref(dark_mode), sizeof(dark_mode))

    SetWindowPos(hwnd, None, -1, -1, -1, -1,  
                 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | 
                 SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED)

# ==============================================================================
# 第三部分:Qt 主窗口类
# ==============================================================================

class ControlWindow(QWidget):
    
    全局边框宽度 = 5

    def __init__(self):
        super().__init__()
        
        self.hwnd = None
        self.亚克力开启中 = True # 状态标志位
        
        self.resize(1000, 600)
        self.setWindowTitle("控制面板")
        self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.Window)
        self.setAttribute(Qt.WA_TranslucentBackground)
        
        self.setup_ui()

    def setup_ui(self):
        # 使用垂直布局
        layout = QVBoxLayout(self)
        
        # 【关键】设置上边距为 80,避开顶部 60px 的标题栏区域
        # 否则按钮会被 nativeEvent 判定为标题栏,导致点击变成拖动
        layout.setContentsMargins(50, 80, 50, 50)
        
        # 信息标签
        self.label_status = QLabel("当前状态:亚克力开启 | 圆角模式")
        self.label_status.setStyleSheet("color: white; font-size: 18px; font-weight: bold; background: transparent;")
        self.label_status.setAlignment(Qt.AlignmentFlag.AlignCenter)
        layout.addWidget(self.label_status)
        
        layout.addSpacing(20)

        # --- 按钮区域 ---
        btn_layout = QHBoxLayout()
        
        # 按钮样式
        btn_style = """
            QPushButton {
                background-color: rgba(255, 255, 255, 0.15);
                color: white;
                border: 1px solid rgba(255,255,255,0.4);
                padding: 12px;
                border-radius: 6px;
                font-size: 14px;
            }
            QPushButton:hover { background-color: rgba(255, 255, 255, 0.3); }
            QPushButton:pressed { background-color: rgba(255, 255, 255, 0.1); }
        """

        # 1. 开关亚克力按钮
        self.btn_acrylic = QPushButton("切换亚克力 (开/关)")
        self.btn_acrylic.setStyleSheet(btn_style)
        self.btn_acrylic.clicked.connect(self.切换亚克力模式)
        btn_layout.addWidget(self.btn_acrylic)

        # 2. 圆角按钮
        btn_round = QPushButton("设置圆角 (Win11)")
        btn_round.setStyleSheet(btn_style)
        btn_round.clicked.connect(lambda: self.设置圆角模式("round"))
        btn_layout.addWidget(btn_round)

        # 3. 直角按钮
        btn_rect = QPushButton("设置直角 (Rect)")
        btn_rect.setStyleSheet(btn_style)
        btn_rect.clicked.connect(lambda: self.设置圆角模式("rect"))
        btn_layout.addWidget(btn_rect)

        layout.addLayout(btn_layout)
        
        # 底部关闭按钮
        layout.addStretch()
        btn_close = QPushButton("关闭程序")
        btn_close.setStyleSheet("QPushButton { background-color: #d84343; color: white; border-radius: 5px; padding: 10px; font-weight: bold; } QPushButton:hover { background-color: #ff5555; }")
        btn_close.clicked.connect(self.close)
        layout.addWidget(btn_close)

    def showEvent(self, event):
        super().showEvent(event)
        if not self.hwnd:
            self.hwnd = int(self.winId())
            QTimer.singleShot(50, self.初始化Win32效果)

    def 初始化Win32效果(self):
        if not self.hwnd: return
        窗口框架子类化(self.hwnd)
        # 默认开启
        设置亚克力状态(self.hwnd, True, "CC202020")
        # 默认圆角
        设置窗口圆角(self.hwnd, DWMWCP_ROUND)

    # --- 槽函数 ---

    def 切换亚克力模式(self):
        if not self.hwnd: return
        
        self.亚克力开启中 = not self.亚克力开启中
        
        if self.亚克力开启中:
            # 开启:设置亚克力 API,更新状态文字
            设置亚克力状态(self.hwnd, True, "CC202020")
            self.label_status.setText("当前状态:亚克力开启")
        else:
            # 关闭:关闭亚克力 API
            设置亚克力状态(self.hwnd, False)
            self.label_status.setText("当前状态:亚克力关闭 (实色背景)")
            
        # 强制重绘,这一步会触发 paintEvent
        # paintEvent 会根据 self.亚克力开启中 决定画透明还是画实色
        self.update()

    def 设置圆角模式(self, mode):
        if not self.hwnd: return
        
        if mode == "rect":
            设置窗口圆角(self.hwnd, DWMWCP_DONOTROUND) # 直角
            self.label_status.setText(self.label_status.text().split("|")[0] + " | 直角模式")
        else:
            设置窗口圆角(self.hwnd, DWMWCP_ROUND) # 圆角
            self.label_status.setText(self.label_status.text().split("|")[0] + " | 圆角模式")

    def paintEvent(self, event):
        """
        核心绘制逻辑
        """
        painter = QPainter(self)
        
        if self.亚克力开启中:
            # 【亚克力模式】
            # 绘制几乎透明的像素 (Alpha=1),让 Windows 拦截鼠标,同时展示背后的模糊
            painter.fillRect(self.rect(), QColor(0, 0, 0, 1))
        else:
            # 【关闭模式】
            # 必须绘制一个不透明(或半透明)的实色背景。
            # 因为我们开启了 WA_TranslucentBackground,如果不画背景,关闭亚克力后窗口就是完全透明(隐形)的。
            # 这里画一个深灰色背景,模拟普通窗口
            painter.fillRect(self.rect(), QColor(30, 30, 30, 255))

    def nativeEvent(self, eventType, message):
        """ Windows 消息处理 """
        try:
            msg = wintypes.MSG.from_address(int(message))
            if msg.message == 0x0084: # WM_NCHITTEST
                p = self.mapFromGlobal(QCursor.pos())
                x, y = p.x(), p.y()
                w, h = self.width(), self.height()
                b = self.全局边框宽度
                
                # 边缘缩放判定
                if x <= b and y <= b: return True, 13
                if x >= w - b and y <= b: return True, 14
                if x <= b and y >= h - b: return True, 16
                if x >= w - b and y >= h - b: return True, 17
                if y <= b: return True, 12
                if y >= h - b: return True, 15
                if x <= b: return True, 10
                if x >= w - b: return True, 11
                
                # 标题栏判定 (Top 60px)
                # 注意:因为我们的 UI 内容区 margin top 设为了 80,
                # 所以 60px 以内的点击不会点到按钮,放心返回 HTCAPTION (拖拽)
                if y <= 60:
                    return True, 2 
                
                return True, 1 # HTCLIENT
                
        except Exception:
            pass
        return super().nativeEvent(eventType, message)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = ControlWindow()
    window.show()
    sys.exit(app.exec())
Windows 开发 | Windows API - Win32
{count} 票

1 个答案

排序依据: 非常有帮助
  1. Harry Vo (WICLOUD CORPORATION) 3,820 信誉分 Microsoft 外部员工 仲裁人
    2025-12-04T10:19:52.01+00:00

    Hi @鑫 李 , Thank you for the detailed explanation and screenshots.

    When acrylic is activated using ACCENT_ENABLE_ACRYLICBLURBEHIND, the window enters a partially layered/composited state. After this point, disabling acrylic by setting AccentState = 0 does not restore the original Win32 frame. Instead, Windows 11 falls back to its default window rules: the top corners become rounded, the standard shadow may disappear, and the border from WS_THICKFRAME may not redraw correctly.

    Microsoft documents Windows 11 corner styling behavior here: https://learn.microsoft.com/windows/apps/desktop/modernize/apply-rounded-corners and explains that rounded corners are the system default.
    If you find my answer useful, please kindly mark it as final answer! Thanks


    您好@鑫 李 ,感谢您提供详细的说明和截图。

    当使用 ACCENT_ENABLE_ACRYLICBLURBEHIND 激活亚克力效果时,窗口会进入部分分层/合成状态。此后,通过将 AccentState 设置为 0 来禁用亚克力,并不会恢复原始的 Win32 窗框。相反,Windows 11 会回退到默认窗口规则:顶部角变为圆角、标准阴影可能消失,且 WS_THICKFRAME 的边框可能无法正确重绘。

    微软在此文档中说明了 Windows 11 的圆角样式行为,并指出圆角是系统默认样式:

    https://learn.microsoft.com/windows/apps/desktop/modernize/apply-rounded-corners

    如果您觉得我的回答有帮助,请将其标记为最终答案,谢谢!

    0 个注释 无注释

你的答案

提问者可以将答案标记为“已接受”,版主可以将答案标记为“已推荐”,这有助于用户了解答案是否解决了提问者的问题。