You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt自定义标题栏如何保留窗口可调整大小功能?

PyQt自定义标题栏后恢复窗口调整大小功能

问题核心

自定义标题栏时使用Qt.FramelessWindowHint会完全移除系统窗口框架,导致默认的边缘调整大小、光标提示功能丢失,需要手动实现这些逻辑,或者换一种不完全移除框架的美化方式。


方案一:修复现有自定义框架的调整大小功能

针对你提供的示例代码,我们可以在Frame类中添加边缘检测、光标切换和窗口大小调整的逻辑,同时修正标题栏的窗口移动逻辑(避免全局变量依赖)。

修改后的完整代码

import sys
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtCore import Qt

class TitleBar(QtWidgets.QDialog):
    def __init__(self, parent=None):
        QtWidgets.QDialog.__init__(self, parent)
        self.setAutoFillBackground(True)
        self.setBackgroundRole(QtGui.QPalette.Highlight)
        self.minimize = QtWidgets.QToolButton(self)
        self.minimize.setIcon(QtGui.QIcon('min.png'))
        self.maximize = QtWidgets.QToolButton(self)
        self.maximize.setIcon(QtGui.QIcon('max.png'))
        close = QtWidgets.QToolButton(self)
        close.setIcon(QtGui.QIcon('close.png'))
        
        self.minimize.setMinimumHeight(10)
        close.setMinimumHeight(10)
        self.maximize.setMinimumHeight(10)
        
        label = QtWidgets.QLabel(self)
        label.setText("Window Title")
        self.setWindowTitle("Window Title")
        
        hbox = QtWidgets.QHBoxLayout(self)
        hbox.addWidget(label)
        hbox.addStretch(1)
        hbox.addWidget(self.minimize)
        hbox.addWidget(self.maximize)
        hbox.addWidget(close)
        hbox.setSpacing(0)
        self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
        
        self.maxNormal = False
        close.clicked.connect(self.close)
        self.minimize.clicked.connect(self.showSmall)
        self.maximize.clicked.connect(self.showMaxRestore)

    def showSmall(self):
        self.parent().showMinimized()

    def showMaxRestore(self):
        parent = self.parent()
        if self.maxNormal:
            parent.showNormal()
            self.maxNormal = False
            self.maximize.setIcon(QtGui.QIcon('max.png'))
        else:
            parent.showMaximized()
            self.maxNormal = True
            self.maximize.setIcon(QtGui.QIcon('max2.png'))

    def close(self):
        self.parent().close()

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            parent = self.parent()
            parent.m_mouse_down = True
            parent.m_old_pos = event.pos()

    def mouseMoveEvent(self, event):
        parent = self.parent()
        if parent.m_mouse_down:
            parent.move(event.globalPos() - parent.m_old_pos)


class Frame(QtWidgets.QFrame):
    def __init__(self, parent=None):
        QtWidgets.QFrame.__init__(self, parent)
        self.m_mouse_down = False
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setMouseTracking(True)
        self.m_titleBar = TitleBar(self)
        self.m_content = QtWidgets.QWidget(self)
        
        # 边缘调整阈值(鼠标离边缘多少像素触发调整)
        self.edge_threshold = 5
        self.current_edge = Qt.NoEdge

        vbox = QtWidgets.QVBoxLayout(self)
        vbox.addWidget(self.m_titleBar)
        vbox.setContentsMargins(0, 0, 0, 0)
        vbox.setSpacing(0)
        
        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.m_content)
        layout.setContentsMargins(5, 5, 5, 5)
        layout.setSpacing(0)
        vbox.addLayout(layout)

    def contentWidget(self):
        return self.m_content

    def titleBar(self):
        return self.m_titleBar

    def mousePressEvent(self, event):
        self.m_old_pos = event.pos()
        self.m_mouse_down = event.button() == Qt.LeftButton

    def mouseMoveEvent(self, event):
        x = event.x()
        y = event.y()
        width = self.width()
        height = self.height()

        # 仅在非标题栏区域处理大小调整
        if not self.m_titleBar.underMouse():
            self.current_edge = Qt.NoEdge
            # 检测鼠标所在边缘并设置对应光标
            if x <= self.edge_threshold:
                self.current_edge |= Qt.LeftEdge
            elif x >= width - self.edge_threshold:
                self.current_edge |= Qt.RightEdge
            
            if y <= self.edge_threshold:
                self.current_edge |= Qt.TopEdge
            elif y >= height - self.edge_threshold:
                self.current_edge |= Qt.BottomEdge

            # 设置光标形状
            if self.current_edge == (Qt.LeftEdge | Qt.TopEdge) or self.current_edge == (Qt.RightEdge | Qt.BottomEdge):
                self.setCursor(Qt.SizeFDiagCursor)
            elif self.current_edge == (Qt.RightEdge | Qt.TopEdge) or self.current_edge == (Qt.LeftEdge | Qt.BottomEdge):
                self.setCursor(Qt.SizeBDiagCursor)
            elif self.current_edge in (Qt.LeftEdge, Qt.RightEdge):
                self.setCursor(Qt.SizeHorCursor)
            elif self.current_edge in (Qt.TopEdge, Qt.BottomEdge):
                self.setCursor(Qt.SizeVerCursor)
            else:
                self.unsetCursor()

            # 拖拽调整窗口大小
            if self.m_mouse_down and self.current_edge != Qt.NoEdge:
                delta = event.pos() - self.m_old_pos
                rect = self.geometry()

                if self.current_edge & Qt.LeftEdge:
                    rect.setLeft(rect.left() + delta.x())
                if self.current_edge & Qt.RightEdge:
                    rect.setWidth(rect.width() + delta.x())
                if self.current_edge & Qt.TopEdge:
                    rect.setTop(rect.top() + delta.y())
                if self.current_edge & Qt.BottomEdge:
                    rect.setHeight(rect.height() + delta.y())

                # 限制窗口最小尺寸
                min_width = self.m_titleBar.minimumWidth() + 20
                min_height = self.m_titleBar.minimumHeight() + 20
                if rect.width() < min_width:
                    rect.setWidth(min_width)
                if rect.height() < min_height:
                    rect.setHeight(min_height)

                self.setGeometry(rect)
                self.m_old_pos = event.pos()
        else:
            # 标题栏区域仅处理窗口移动
            if self.m_mouse_down:
                self.move(event.globalPos() - self.m_old_pos)

    def mouseReleaseEvent(self, event):
        self.m_mouse_down = False
        self.current_edge = Qt.NoEdge
        self.unsetCursor()

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    box = Frame()
    box.move(60,60)
    l = QtWidgets.QVBoxLayout(box.contentWidget())
    l.setContentsMargins(0, 0, 0, 0)
    # 添加测试内容
    l.addWidget(QtWidgets.QLabel("测试内容"))
    box.show()
    app.exec_()

关键修改点

  1. 移除TitleBar中的全局变量box,改用self.parent()引用主窗口,避免耦合
  2. 在Frame中添加edge_threshold变量定义边缘触发区域
  3. 实现mouseMoveEvent中的边缘检测逻辑,根据鼠标位置切换光标
  4. 添加拖拽调整窗口大小的逻辑,同时限制最小窗口尺寸
  5. 区分标题栏区域和内容区域的鼠标事件:标题栏负责移动窗口,内容边缘负责调整大小

方案二:通过样式表美化标题栏(保留系统窗口功能)

如果不想完全自定义框架,可以保留系统窗口的基础功能,仅通过样式表美化标题栏(注意:该方式在Windows平台支持较好,Linux/macOS可能有兼容性问题)。

示例代码(QMainWindow)

import sys
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt

class StyledMainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("样式化标题栏")
        self.resize(600, 400)

        # 设置样式表(Windows下生效)
        self.setStyleSheet("""
            QMainWindow::title {
                background-color: #2c3e50;
                color: white;
                padding-left: 10px;
                padding-top: 4px;
                height: 24px;
            }
            QPushButton#min_btn, QPushButton#max_btn {
                background-color: transparent;
                color: white;
                border: none;
                width: 30px;
                height: 24px;
            }
            QPushButton#close_btn {
                background-color: transparent;
                color: white;
                border: none;
                width: 30px;
                height: 24px;
            }
            QPushButton#close_btn:hover {
                background-color: #e74c3c;
            }
        """)

        # 自定义标题栏按钮(替换原生按钮)
        title_bar = QtWidgets.QWidget()
        title_layout = QtWidgets.QHBoxLayout(title_bar)
        title_layout.addWidget(QtWidgets.QLabel("自定义标题"))
        title_layout.addStretch()
        
        min_btn = QtWidgets.QPushButton("—")
        min_btn.setObjectName("min_btn")
        max_btn = QtWidgets.QPushButton("□")
        max_btn.setObjectName("max_btn")
        close_btn = QtWidgets.QPushButton("×")
        close_btn.setObjectName("close_btn")

        title_layout.addWidget(min_btn)
        title_layout.addWidget(max_btn)
        title_layout.addWidget(close_btn)
        title_layout.setContentsMargins(0, 0, 0, 0)
        self.setMenuWidget(title_bar)

        # 绑定按钮事件
        min_btn.clicked.connect(self.showMinimized)
        max_btn.clicked.connect(self.toggle_maximize)
        close_btn.clicked.connect(self.close)

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

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = StyledMainWindow()
    window.show()
    sys.exit(app.exec_())

注意事项

  • 若要保留系统窗口的调整大小功能,不要使用Qt.FramelessWindowHint,可以用Qt.CustomizeWindowHint配合其他窗口标志保留按钮:
    self.setWindowFlags(Qt.Window | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint | Qt.WindowCloseButtonHint)
    
  • 跨平台场景下,完全自定义框架(方案一)的兼容性更好,样式表美化仅作为补充方案。

内容的提问来源于stack exchange,提问作者Vlad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 08:54:54