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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 08:41:58