PyQt5可调整大小无框窗口拖拽卡顿问题求助
问题:Qt无框Widget拖拽调整大小卡顿/闪烁
我编写了一段代码,实现Qt无框Widget支持拖拽边缘调整大小,但调整窗口大小和移动窗口时,界面出现卡顿/闪烁现象(查看下方动图,注意右侧和底部边缘):

以下是我的代码:
import sys from typing import Dict, Tuple from PyQt5.QtCore import QPoint, QRect, Qt, QMargins from PyQt5.QtGui import QMouseEvent from PyQt5.QtWidgets import QApplication, QWidget dragMargin = 8 top = 0 bot = 1 left = 2 right = 3 c = Qt.CursorShape class ScaleableWindowFrame(QWidget): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.setMouseTracking(True) self.prevDir = (None, None) self.lastPos : QPoint= None directionCursor: Dict[Tuple[int, int], Qt.CursorShape] = { (top, left): c.SizeFDiagCursor, (top, None): c.SizeVerCursor, (top, right): c.SizeBDiagCursor, (None, left): c.SizeHorCursor, (None, right): c.SizeHorCursor, (bot, left): c.SizeBDiagCursor, (bot, None): c.SizeVerCursor, (bot, right): c.SizeFDiagCursor, (None, None): c.ArrowCursor, } def mousePressEvent(self, a0: QMouseEvent) -> None: if a0.buttons() == Qt.MouseButton.LeftButton: self.lastPos = a0.globalPos() a0.accept() def mouseMoveEvent(self, a0: QMouseEvent) -> None: if a0.buttons() == Qt.MouseButton.NoButton: self.handleHover(a0) elif a0.buttons() == Qt.MouseButton.LeftButton: self.handleDrag(a0) a0.accept() def handleDrag(self, a0:QMouseEvent): diff = a0.globalPos() - self.lastPos dx = diff.x() dy = diff.y() methods = { (top, left): lambda: (self.setGeometry(QRect(self.pos() + QPoint(dx, dy), self.size().grownBy(QMargins(0,0, -dx, -dy))))), (top, None): lambda: (self.setGeometry(QRect(self.pos() + QPoint(0, dy), self.size().grownBy(QMargins(0,0, 0, -dy))))) , (top, right):lambda: (self.setGeometry(QRect(self.pos() + QPoint(0, dy), self.size().grownBy(QMargins(0,0, dx, -dy))))) , (None, left):lambda: (self.setGeometry(QRect(self.pos() + QPoint(dx, 0), self.size().grownBy(QMargins(0, 0, -dx, 0))))), (None, right):lambda: (self.setGeometry(QRect(self.pos(), self.size().grownBy(QMargins(0,0, dx, 0))))), (bot, left): lambda: (self.setGeometry(QRect(self.pos() + QPoint(dx, 0), self.size().grownBy(QMargins(0,0, -dx, dy))))), (bot, None):lambda: (self.setGeometry(QRect(self.pos(), self.size().grownBy(QMargins(0,0, 0, dy))))), (bot, right): lambda: (self.setGeometry(QRect(self.pos(), self.size().grownBy(QMargins(0,0, dx, dy))))), (None, None): lambda: (), } methods[self.prevDir]() self.lastPos = a0.globalPos() a0.accept() def handleHover(self, a0: QMouseEvent): # first determine which edge the mouse cursor is on p = a0.pos() # vertical direction if p.y() < dragMargin: v = top elif self.height() - p.y() < dragMargin: v = bot else: v = None # horizontal direction if p.x() < dragMargin: h = left elif self.width() - p.x() < dragMargin: h = right else: h = None direction = (v, h) if direction != self.prevDir: self.setCursor(ScaleableWindowFrame.directionCursor[(v, h)]) self.prevDir = direction a0.accept() if __name__ == '__main__': a = QApplication(sys.argv) w = ScaleableWindowFrame() w.setWindowFlag(Qt.FramelessWindowHint, True) w.resize(400, 400) w.show() sys.exit(a.exec())
请问我是否存在处理错误?或者有更优的实现方式?
问题分析与优化方案
卡顿原因
- 频繁触发重绘:每次鼠标移动都直接调用
setGeometry修改窗口参数,导致界面频繁重绘,引发闪烁。 - 无最小尺寸限制:拖拽时可能让窗口尺寸过小,触发异常重绘逻辑。
- 冗余计算逻辑:用lambda表达式封装几何计算,每次拖拽都重复创建匿名函数,效率偏低。
优化后的代码
import sys from typing import Dict, Tuple from PyQt5.QtCore import QPoint, QRect, Qt, QMargins from PyQt5.QtGui import QMouseEvent from PyQt5.QtWidgets import QApplication, QWidget dragMargin = 8 top = 0 bot = 1 left = 2 right = 3 c = Qt.CursorShape class ScaleableWindowFrame(QWidget): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.setMouseTracking(True) self.prevDir = (None, None) self.lastPos: QPoint = None # 设置窗口最小尺寸,避免拖拽过度缩小 self.setMinimumSize(100, 100) # 启用双缓冲,减少界面闪烁 self.setAttribute(Qt.WA_StyledBackground, True) directionCursor: Dict[Tuple[int, int], Qt.CursorShape] = { (top, left): c.SizeFDiagCursor, (top, None): c.SizeVerCursor, (top, right): c.SizeBDiagCursor, (None, left): c.SizeHorCursor, (None, right): c.SizeHorCursor, (bot, left): c.SizeBDiagCursor, (bot, None): c.SizeVerCursor, (bot, right): c.SizeFDiagCursor, (None, None): c.ArrowCursor, } def mousePressEvent(self, a0: QMouseEvent) -> None: if a0.buttons() == Qt.MouseButton.LeftButton: self.lastPos = a0.globalPos() a0.accept() def mouseMoveEvent(self, a0: QMouseEvent) -> None: if a0.buttons() == Qt.MouseButton.NoButton: self.handleHover(a0) elif a0.buttons() == Qt.MouseButton.LeftButton: self.handleDrag(a0) a0.accept() def handleDrag(self, a0: QMouseEvent): diff = a0.globalPos() - self.lastPos dx = diff.x() dy = diff.y() current_rect = self.geometry() new_rect = current_rect.copy() v_dir, h_dir = self.prevDir # 处理垂直方向拖拽 if v_dir == top: new_rect.setTop(current_rect.top() + dy) if new_rect.height() < self.minimumHeight(): new_rect.setTop(current_rect.bottom() - self.minimumHeight()) elif v_dir == bot: new_rect.setBottom(current_rect.bottom() + dy) if new_rect.height() < self.minimumHeight(): new_rect.setBottom(current_rect.top() + self.minimumHeight()) # 处理水平方向拖拽 if h_dir == left: new_rect.setLeft(current_rect.left() + dx) if new_rect.width() < self.minimumWidth(): new_rect.setLeft(current_rect.right() - self.minimumWidth()) elif h_dir == right: new_rect.setRight(current_rect.right() + dx) if new_rect.width() < self.minimumWidth(): new_rect.setRight(current_rect.left() + self.minimumWidth()) # 仅当矩形变化时更新,减少重绘次数 if new_rect != current_rect: self.setGeometry(new_rect) self.lastPos = a0.globalPos() a0.accept() def handleHover(self, a0: QMouseEvent): p = a0.pos() v = None if p.y() < dragMargin: v = top elif self.height() - p.y() < dragMargin: v = bot h = None if p.x() < dragMargin: h = left elif self.width() - p.x() < dragMargin: h = right direction = (v, h) if direction != self.prevDir: self.setCursor(self.directionCursor[direction]) self.prevDir = direction a0.accept() if __name__ == '__main__': a = QApplication(sys.argv) w = ScaleableWindowFrame() w.setWindowFlag(Qt.FramelessWindowHint, True) w.resize(400, 400) w.show() sys.exit(a.exec())
优化点说明
- 减少重绘触发:先计算完整的新窗口矩形,仅当矩形确实变化时才调用
setGeometry,避免不必要的界面更新。 - 添加尺寸限制:设置窗口最小尺寸,防止拖拽时窗口过度缩小导致异常。
- 启用双缓冲:通过
WA_StyledBackground属性启用Qt内置双缓冲机制,直接缓解闪烁问题。 - 简化计算逻辑:直接操作
QRect的边界属性,替代原有的lambda表达式,代码更清晰且执行效率更高。
内容的提问来源于stack exchange,提问作者goldenotaste
相关产品推荐
相关产品推荐

