首页 > 系统相关 >PyQt5 / PySide 2 + Pywin32 自定义标题栏窗口 + 还原 Windows 原生窗口边框特效(2)

PyQt5 / PySide 2 + Pywin32 自定义标题栏窗口 + 还原 Windows 原生窗口边框特效(2)

时间:2024-08-24 16:58:23浏览次数:6  
标签:窗口边框 自定义 PySide title self height maxbutton QPushButton win32con

前言:

已修复上一篇文章中提到的 Bug,增加状态切换动画:

PyQt5 / PySide 2 + Pywin32 自定义标题栏窗口 + 还原 Windows 原生窗口边框特效-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/2402_84665876/article/details/141487635?spm=1001.2014.3001.5501

仍然存在的问题:

打开窗口时窗口标题栏会加载零点几秒才会显示。

完整代码:

main.py

import ctypes.wintypes
import sys
from ctypes import c_int,cast
from ctypes.wintypes import POINT,HWND,UINT,RECT,MSG
import win32api
import win32con
import win32gui
from PyQt5.QtCore import Qt, QSize, QTimer
from PyQt5.QtGui import QCursor, QCloseEvent, QFont, QIcon
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QLabel, QMainWindow
from PyQt5.QtWinExtras import QtWin
from ctypes import Structure, POINTER


class MINMAXINFO(ctypes.Structure):
    _fields_ = [
        ("ptReserved", POINT),
        ("ptMaxSize", POINT),
        ("ptMaxPosition", POINT),
        ("ptMinTrackSize", POINT),
        ("ptMaxTrackSize", POINT),
    ]
class PWINDOWPOS(ctypes.Structure):
    _fields_ = [
        ('hWnd',            HWND),
        ('hwndInsertAfter', HWND),
        ('x',               c_int),
        ('y',               c_int),
        ('cx',              c_int),
        ('cy',              c_int),
        ('flags', UINT)
    ]


class NCCALCSIZE_PARAMS(Structure):
    _fields_ = [
        ('rgrc', RECT*3),
        ('lppos', POINTER(PWINDOWPOS))
    ]



class Window(QMainWindow):
    BORDER_WIDTH = 3

    def __init__(self, *args, **kwargs):
        super(Window, self).__init__(*args, **kwargs)
        # 主屏幕的可用大小(去掉任务栏)
        self._rect = QApplication.instance().desktop().availableGeometry(self)
        self.title_height = 30
        self.setWindowFlags(Qt.Window |
                            Qt.FramelessWindowHint |
                            Qt.WindowMinimizeButtonHint |
                            Qt.WindowMaximizeButtonHint |
                            Qt.WindowCloseButtonHint |
                            Qt.WindowSystemMenuHint)
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.maxornormal2)
        self.timer.start(50)
        self.setStyleSheet("Window{background:transparent}")
        # 增加薄边框
        win32gui.SetWindowLong(int(self.winId()), win32con.GWL_STYLE,
                               win32con.WS_THICKFRAME
                               | win32con.WS_MINIMIZEBOX
                               | win32con.WS_MAXIMIZEBOX
                               | win32con.WS_CAPTION
                               | win32con.CS_DBLCLKS)
        if QtWin.isCompositionEnabled():
            # 加上 Aero 边框阴影
            QtWin.extendFrameIntoClientArea(self, -1, -1, -1, -1)
        else:
            QtWin.resetExtendedFrame(self)

    def setWindowTitle(self, title: str):
        super().setWindowTitle(title)
        self.titlebackground = QLabel(self)
        self.titlebackground.setStyleSheet("QLabel {"
                                           "background-color:rgba(30,30,30,1);"
                                           "}")
        self.titleBar = QLabel(f" {title}", self)
        self.titleBar.setStyleSheet("QLabel {"
                                    'font: normal normal 15px "微软雅黑";'
                                    "background-color:rgba(0,0,0,0);"
                                    "color:rgb(255,255,255)"
                                    "}")
        self.titlebackground.setGeometry(0,0,self.size().width(), self.title_height)
        self.titleBar.setGeometry(self.title_height, 0, self.size().width(), self.title_height)
        self.raiseEvent()
        self.setMinimumHeight(self.title_height)
        self.setthreebutton()

    def setWindowIcon(self, icon):
        super().setWindowIcon(icon)
        self.iconimage = QPushButton(self)
        self.iconimage.setIcon(icon)
        self.iconimage.setIconSize(QSize(self.title_height - 10, self.title_height - 10))
        self.iconimage.setFixedSize(self.title_height, self.title_height)
        self.iconimage.setStyleSheet("QPushButton {"
                                     "border:none;"
                                     "background-color:rgba(0,0,0,0);}")
        self.iconimage.move(0,0)
        self.raiseEvent()

    def setthreebutton(self):
        clqss = ("QPushButton {"
               "background-color:rgba(0,0,0,0);"
               "border:none;"
               "}"
               "QPushButton:hover {"
               "background-color:rgba(255,0,0,1);"
               "}"
               "QPushButton:pressed {"
               "background-color:rgba(255,100,100,1);"
               "}"
               )
        blqss = ("QPushButton {"
                 "background-color:rgba(0,0,0,0);"
                 "border:none;"
                 "}"
                 "QPushButton:hover {"
                 "background-color:rgba(60,60,60,1);"
                 "}"
                 "QPushButton:pressed {"
                 "background-color:rgba(100,100,100,1);"
                 "}"
                 )
        psize = QSize(self.title_height + 15, self.title_height)
        isize = QSize(self.title_height - 15, self.title_height - 15)
        self.exitbutton = QPushButton(self)
        self.exitbutton.setIcon(QIcon("close.png"))
        self.exitbutton.setIconSize(isize)
        self.exitbutton.resize(psize)
        self.exitbutton.setStyleSheet(clqss)
        self.exitbutton.clicked.connect(self.close)
        self.maxbutton = QPushButton(self)
        self.maxbutton.setIcon(QIcon("max.png"))
        self.maxbutton.setIconSize(isize)
        self.maxbutton.resize(psize)
        self.maxbutton.setStyleSheet(blqss)
        self.maxbutton.clicked.connect(self.maxornormal)
        self.minbutton = QPushButton(self)
        self.minbutton.setIcon(QIcon("min.png"))
        self.minbutton.setIconSize(isize)
        self.minbutton.resize(psize)
        self.minbutton.setStyleSheet(blqss)
        self.minbutton.clicked.connect(self.showMinimized)

    def showNormal(self):
        super().showNormal()
        self.maxbutton.setIcon(QIcon("max.png"))
        QCursor.setPos(QCursor.pos().x(), QCursor.pos().y() - 1)

    def showMaximized(self):
        super().showMaximized()
        self.maxbutton.setIcon(QIcon("normal.png"))
        QCursor.setPos(QCursor.pos().x(), QCursor.pos().y() - 1)

    def maxornormal(self):
        if self.isMaximized():
            self.showNormal()
        else:
            self.showMaximized()

    def maxornormal2(self):
        if self.isMaximized():
            self.maxbutton.setIcon(QIcon("normal.png"))
        else:
            self.maxbutton.setIcon(QIcon("max.png"))

    def raiseEvent(self):
        try:
            self.titlebackground.raise_()
            self.titleBar.raise_()
            self.exitbutton.raise_()
            self.maxbutton.raise_()
            self.minbutton.raise_()
        except:
            pass
        try:
            self.iconimage.raise_()
        except:
            pass

    def resizeEvent(self, a0):
        super().resizeEvent(a0)
        try:
            self.titlebackground.setGeometry(0, 0, self.size().width(), self.title_height)
            self.titleBar.setGeometry(self.title_height, 0, self.size().width(), self.title_height)
            self.exitbutton.move(self.size().width() - self.exitbutton.size().width(), 0)
            self.maxbutton.move(self.exitbutton.pos().x() - self.maxbutton.size().width(), 0)
            self.minbutton.move(self.maxbutton.pos().x() - self.minbutton.size().width(), 0)
        except:
            pass

    def isWindowMaximized(self, hWnd) -> bool:
        """ 判断窗口是否最大化 """
        # 返回指定窗口的显示状态以及被恢复的、最大化的和最小化的窗口位置,返回值为元组
        windowPlacement = win32gui.GetWindowPlacement(hWnd)
        if not windowPlacement:
            return False
        return windowPlacement[1] == win32con.SW_MAXIMIZE

    def nativeEvent(self, eventType, message):
        """ 处理windows消息 """
        msg = MSG.from_address(message.__int__())
        pos = QCursor.pos()
        x = pos.x() - self.frameGeometry().x()
        y = pos.y() - self.frameGeometry().y()
        if msg.message == win32con.WM_NCHITTEST:
            xPos = win32api.LOWORD(msg.lParam) - self.frameGeometry().x()
            yPos = win32api.HIWORD(msg.lParam) - self.frameGeometry().y()
            w, h = self.width(), self.height()
            lx = xPos < self.BORDER_WIDTH
            rx = xPos + 9 > w - self.BORDER_WIDTH
            ty = yPos < self.BORDER_WIDTH
            by = yPos > h - self.BORDER_WIDTH
            if lx and ty:
                return True, win32con.HTTOPLEFT
            elif rx and by:
                return True, win32con.HTBOTTOMRIGHT
            elif rx and ty:
                return True, win32con.HTTOPRIGHT
            elif lx and by:
                return True, win32con.HTBOTTOMLEFT
            elif ty:
                return True, win32con.HTTOP
            elif by:
                return True, win32con.HTBOTTOM
            elif lx:
                return True, win32con.HTLEFT
            elif rx:
                return True, win32con.HTRIGHT
            elif self.pos().y() <= QCursor.pos().y() <= self.pos().y() + self.title_height and self.childAt(x,y) == self.titleBar:
                return True, win32con.HTCAPTION
        elif msg.message == win32con.WM_NCCALCSIZE:
            if self.isWindowMaximized(msg.hWnd):
                self.monitorNCCALCSIZE(msg)
            return True, 0
        elif msg.message == win32con.WM_GETMINMAXINFO:
            if self.isWindowMaximized(msg.hWnd):
                window_rect = win32gui.GetWindowRect(msg.hWnd)
                if not window_rect:
                    return False, 0
                # 获取显示器句柄
                monitor = win32api.MonitorFromRect(window_rect)
                if not monitor:
                    return False, 0
                # 获取显示器信息
                monitor_info = win32api.GetMonitorInfo(monitor)
                monitor_rect = monitor_info['Monitor']
                work_area = monitor_info['Work']
                # 将lParam转换为MINMAXINFO指针
                info = cast(msg.lParam, POINTER(MINMAXINFO)).contents
                # 调整窗口大小
                info.ptMaxSize.x = work_area[2] - work_area[0]
                info.ptMaxSize.y = work_area[3] - work_area[1] + 10
                info.ptMaxTrackSize.x = info.ptMaxSize.x
                info.ptMaxTrackSize.y = info.ptMaxSize.y
                # 修改左上角坐标
                info.ptMaxPosition.x = abs(window_rect[0] - monitor_rect[0])
                info.ptMaxPosition.y = abs(window_rect[1] - monitor_rect[1])
                return True, 1
        elif msg.message == win32con.WM_SYSKEYDOWN:
            if msg.wParam == win32con.VK_F4:
                QApplication.sendEvent(self, QCloseEvent())
                return False, 0
        return QMainWindow.nativeEvent(self, eventType, message)

    def monitorNCCALCSIZE(self, msg: MSG):
        """ 处理 WM_NCCALCSIZE 消息 """
        monitor = win32api.MonitorFromWindow(msg.hWnd)
        # 如果没有保存显示器信息就直接返回,否则接着调整窗口大小
        if monitor is None and not self.monitor_info:
            return
        elif monitor is not None:
            self.monitor_info = win32api.GetMonitorInfo(monitor)
        # 调整窗口大小
        params = cast(msg.lParam, POINTER(NCCALCSIZE_PARAMS)).contents
        params.rgrc[0].left = self.monitor_info['Work'][0]
        params.rgrc[0].top = self.monitor_info['Work'][1]
        params.rgrc[0].right = self.monitor_info['Work'][2]
        params.rgrc[0].bottom = self.monitor_info['Work'][3]


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = Window()
    w.setWindowTitle("Win32PyQtFramelessWindow 示例")
    w.setWindowIcon(QIcon("icon.png"))
    w.setMinimumWidth(450)
    w.resize(800,450)
    w.show()
    sys.exit(app.exec_())

标签:窗口边框,自定义,PySide,title,self,height,maxbutton,QPushButton,win32con
From: https://blog.csdn.net/2402_84665876/article/details/141501729

相关文章

  • elasticsearch整合自定义词库实现自定义分词
            在进行分词时es有时没有办法对人名或者其他新词、偏词进行精准的分词,这时候就需要我们进行自定义分词。前置:        1).安装完成ik分词器,安装流程可以参考:ik安装流程        2).自定义的远程库我们使用nginx,所以需要提前安装nginx1.进入到......
  • Superset BI封装自定义组件(堆叠柱状图)
    目录前言封装步骤一、创建组件文件夹二、预设组件信息三、使用组件往期回顾前言Superset是一个现代化的、易于使用的、轻量级的数据可视化工具,它允许用户通过简单的点击操作来创建和分享图表。如果你想在Superset中创建自定义组件,你可能需要进行一些扩展工作。......
  • @Scheduled 定时任务自定义
    简介@Scheduled定时任务自定义可以通过SchedulingConfigurer实现。SchedulingConfigurer是SpringFramework中的一个接口,用于配置定时任务。当你需要对定时任务进行更高级别的定制时,这个接口就显得非常有用。可以通过SchedulingConfigurer接口来自定义一些高级配置......
  • PyQt5 / PySide 2 + Pywin32 自定义标题栏窗口 + 还原 Windows 原生窗口边框特效
    Bug:当窗口不处于顶层时,如果点击窗体试图将其置于顶层,窗体自带的白边框会突然显示,最长两秒。完整性:尚未添加窗口状态的过渡动画和淡入、淡出动画。其他问题:由于Qt官方在版本6去掉了QtWin,目前暂未找到PyQt6/PySide6的解决方案。准备工作:在同目录下放四张照片:m......
  • 注册一种自定义文件类型
     网页端代码<ahref="sppcexe:PI;242700623010">PI配置</a> 类调用    new注册文件类型();=====================================================================操作类:usingMicrosoft.Win32;usingSystem;usingSystem.Diagnostics;publicclass注册文......
  • 应用程自定义协议与序列化反序列化
        本篇将主要介绍在应用层中自定义的协议,自定义协议的同时还需要将我们的数据继续序列化和反序列化,所以本篇的重点为序列化、反序列化的原因以及如何自定义协议,接着探讨了关于为什么tcp协议可以支持全双工协议。还根据用户自定义协议结合tcpsocket编写了一份三......
  • 【pytest】 在启动任务时将自定义参数传入代码中
    1.使用 pytest_addoption 钩子函数你可以在 conftest.py 文件中使用 pytest_addoption 钩子函数来定义自定义命令行参数。然后,你可以在你的测试文件中通过 request fixture来访问这些参数。conftest.py#contentofconftest.pyimportpytestdefpytest_ad......
  • Flutter 自定义日期范围选择组件,使用更加灵活,满足UI设计需要
    一、实现的效果图二、虽然Flutter也为我们提供了日期范围选择组件showDateRangePicker,但是毕竟系统的UI不符合我们的设计风格,所以被迫只能自己实现一个了系统样式三、日历整体实现逻辑其实也很简单,如下:首先获取每个月份具体有多少天int_getMonthDays(DateTimetim......
  • 在 Monaco Editor 中自定义右键菜单并支持多级菜单
    在MonacoEditor中自定义右键菜单能够提供更灵活的功能选项。以下是如何在MonacoEditor中实现自定义右键菜单,并支持多级菜单的步骤及关键代码示例。1.初始化MonacoEditor实例首先,需要初始化MonacoEditor实例,并设置基本的编辑器配置。constinitEditor=()=......
  • 自定义安装Nginx
    nginx下载地址:https://nginx.org/download/1.下载wgethttps://nginx.org/download/nginx-1.18.0.tar.gz2.安装依赖yum-yinstallgccgcc-c++makeautomakeautoconfpcrepcre-develzlibzlib-developensslopenssl-devellibtool3.解压tar-vxfnginx-1.18.0.tar.......