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_()
关键修改点
- 移除
TitleBar中的全局变量box,改用self.parent()引用主窗口,避免耦合 - 在
Frame中添加edge_threshold变量定义边缘触发区域 - 实现
mouseMoveEvent中的边缘检测逻辑,根据鼠标位置切换光标 - 添加拖拽调整窗口大小的逻辑,同时限制最小窗口尺寸
- 区分标题栏区域和内容区域的鼠标事件:标题栏负责移动窗口,内容边缘负责调整大小
方案二:通过样式表美化标题栏(保留系统窗口功能)
如果不想完全自定义框架,可以保留系统窗口的基础功能,仅通过样式表美化标题栏(注意:该方式在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
相关产品推荐
相关产品推荐

