QWindow.startSystemMove执行后子部件Hover事件失效问题求助
Wayland下Qt调用startSystemMove后鼠标事件异常的解决方法
在Wayland环境中,无父级顶层部件调用QWindow.startSystemMove()后,常会出现两类异常:MouseRelease事件不触发、子部件Hover事件失效,直到右键或第三鼠标键点击才恢复。这是因为Wayland窗口管理器会在移动期间接管鼠标输入,导致Qt内部的鼠标跟踪状态、焦点状态出现异常,且移动结束后未自动重置。
以下是几个可行的解决思路:
方案1:移动结束后强制重置鼠标状态
利用WindowActivate事件作为移动结束的触发点,主动重置部件的鼠标跟踪、焦点,并模拟鼠标移动事件来恢复Hover状态。
修改QMovableWidget的event方法:
def event(self, ev): # type: (QEvent | QMouseEvent) -> bool if ( ev.type() == QEvent.Type.MouseButtonPress and ev.button() == Qt.MouseButton.LeftButton ): print(self.window().windowHandle().startSystemMove()) self.___systemMove = True print("Start System Move") if ev.type() == QEvent.Type.WindowActivate: if self.___systemMove: print("System Move Done") self.___systemMove = False self.updateGeometry() # 重置鼠标跟踪 self.setMouseTracking(True) # 强制获取焦点 self.setFocus(Qt.MouseFocusReason) # 模拟鼠标移动,触发Hover事件更新 cursor_pos = QtGui.QCursor.pos() local_pos = self.mapFromGlobal(cursor_pos) move_event = QMouseEvent( QEvent.Type.MouseMove, local_pos, cursor_pos, Qt.NoButton, Qt.NoButton, Qt.NoModifier ) QtGui.QGuiApplication.sendEvent(self, move_event) # 同步更新子部件的Hover状态 for child in self.findChildren(QWidget): child_local_pos = child.mapFromGlobal(cursor_pos) child_move_event = QMouseEvent( QEvent.Type.MouseMove, child_local_pos, cursor_pos, Qt.NoButton, Qt.NoButton, Qt.NoModifier ) QtGui.QGuiApplication.sendEvent(child, child_move_event) return super().event(ev)
方案2:通过NativeEvent监听Wayland移动结束信号
直接捕获Wayland平台的底层事件,更准确地判断移动结束时机,避免依赖WindowActivate的不确定性。
给QMovableWidget添加nativeEvent方法和状态重置方法:
def __init__(self, p, f,): # type: (QWidget | None, WindowTypes,) -> None super().__init__(p, f) self.setAttribute(Qt.WidgetAttribute.WA_MouseTracking, True) self.setAttribute(Qt.WidgetAttribute.WA_NativeWindow, True) self.___systemMove = False self._prev_pos = self.pos() # 记录移动前窗口位置 def nativeEvent(self, eventType: QByteArray | bytes, message: int) -> object: if eventType == b"wl_surface" and self.___systemMove: current_pos = self.pos() # 当窗口位置连续两次不变时,判定移动结束 if current_pos == self._prev_pos: self._resetMouseState() self._prev_pos = current_pos return super().nativeEvent(eventType, message) def _resetMouseState(self): print("System Move Done") self.___systemMove = False self.updateGeometry() self.setMouseTracking(True) self.setFocus(Qt.MouseFocusReason) # 触发鼠标状态更新 cursor_pos = QtGui.QCursor.pos() local_pos = self.mapFromGlobal(cursor_pos) QtGui.QGuiApplication.sendEvent(self, QMouseEvent( QEvent.Type.MouseMove, local_pos, cursor_pos, Qt.NoButton, Qt.NoButton, Qt.NoModifier )) # 更新子部件Hover for child in self.findChildren(QWidget): child_local_pos = child.mapFromGlobal(cursor_pos) QtGui.QGuiApplication.sendEvent(child, QMouseEvent( QEvent.Type.MouseMove, child_local_pos, cursor_pos, Qt.NoButton, Qt.NoButton, Qt.NoModifier )) def event(self, ev): # type: (QEvent | QMouseEvent) -> bool if ( ev.type() == QEvent.Type.MouseButtonPress and ev.button() == Qt.MouseButton.LeftButton ): print(self.window().windowHandle().startSystemMove()) self.___systemMove = True self._prev_pos = self.pos() print("Start System Move") return super().event(ev)
方案3:延迟调用startSystemMove避免事件冲突
将startSystemMove的调用延迟到当前事件循环处理完成后,避免Qt的事件处理流程被窗口管理器的操作打断。
修改QMovableWidget的event方法:
def event(self, ev): # type: (QEvent | QMouseEvent) -> bool if ( ev.type() == QEvent.Type.MouseButtonPress and ev.button() == Qt.MouseButton.LeftButton ): # 用0延迟的Timer,让Qt先处理完当前MousePress事件 QtCore.QTimer.singleShot(0, self._triggerSystemMove) self.___systemMove = True print("Start System Move") if ev.type() == QEvent.Type.WindowActivate: if self.___systemMove: print("System Move Done") self.___systemMove = False self.updateGeometry() return super().event(ev) def _triggerSystemMove(self): print(self.window().windowHandle().startSystemMove())
以上方案中,方案1实现简单,适用于大多数场景;方案2更精准,适合对移动结束时机要求高的场景;方案3则从事件流程层面避免冲突,能解决部分因事件顺序导致的异常。
内容的提问来源于stack exchange,提问作者razor_chk
相关产品推荐
相关产品推荐

