[software development] 自己写了一个标题栏,但是快速拖动会导致按压位置变动,该怎么修改
Tofloor
poster avatar
把一切操作变成GUI
deepin
Backbone of ecological co-construction group
2024-09-24 17:41
Author

如题,当我按压鼠标拖动标题栏并且快速来回挪动时,鼠标的位置会变动(此时我并没有松开鼠标)

录屏_选择区域_20240924173336.gif

部分代码如下:



    def mouseMoveEvent(self, event):
        if self.isPressed:
            if self.win.isMaximized:
                self.win.showNormal()

            movePos = event.globalPos() - self.startPos
            self.startPos = event.globalPos()
            self.win.move(self.win.pos() + movePos)

完整代码文件:
deepin发帖.zip

有什么修改的好办法吗?

Reply Favorite View the author
All Replies
1 / 2
To page
嘘...
deepin
2024-09-24 17:58
#1
        this->move(mapFromGlobal(QCursor::pos()) - d->dragPos);

dragPos 是你鼠标按下时,光标在窗口内的位置。

Reply View the author
嘘...
deepin
2024-09-24 17:59
#2

把当前光标的全局位置转换为你的窗口坐标中的位置,然后就是拖拽点和光标点的差值,做平移就好了。很简单。

Reply View the author
把一切操作变成GUI
deepin
Backbone of ecological co-construction group
2024-09-24 18:58
#3
嘘...

把当前光标的全局位置转换为你的窗口坐标中的位置,然后就是拖拽点和光标点的差值,做平移就好了。很简单。

def mouseMoveEvent(self, event):
    if self.isPressed:
        if self.win.isMaximized:
            self.win.showNormal()

        movePos = event.globalPos() - self.startPos
        self.startPos = event.globalPos()
        self.win.move(self.win.pos() + movePos)

我上面这段代码就是跟你一样的思路(但是是用全局鼠标位置减去鼠标初始按压的点击位置),但是就是快速挪动会造成偏移。

而且从最大化拖动变成还原窗口时,鼠标位置也是不对的。

Reply View the author
嘘...
deepin
2024-09-24 19:22
#4
把一切操作变成GUI
def mouseMoveEvent(self, event):
    if self.isPressed:
        if self.win.isMaximized:
            self.win.showNormal()

        movePos = event.globalPos() - self.startPos
        self.startPos = event.globalPos()
        self.win.move(self.win.pos() + movePos)

我上面这段代码就是跟你一样的思路(但是是用全局鼠标位置减去鼠标初始按压的点击位置),但是就是快速挪动会造成偏移。

而且从最大化拖动变成还原窗口时,鼠标位置也是不对的。

你的代码始终有个成员变量一直要给赋值。我以前也是像你这样写,慢速拖动还OK,快速拖动就会飞。

后来改了之后就非常丝滑了。

把光标全局位置转换为窗口内位置,始终是窗口内固定一点做平移。

Reply View the author
把一切操作变成GUI
deepin
Backbone of ecological co-construction group
2024-09-24 20:27
#5
嘘...
        this->move(mapFromGlobal(QCursor::pos()) - d->dragPos);

dragPos 是你鼠标按下时,光标在窗口内的位置。

这里没有写错吧?move里面不是要全局坐标吗?

Reply View the author
把一切操作变成GUI
deepin
Backbone of ecological co-construction group
2024-09-24 20:51
#6

貌似我改成了这样之后真的变丝滑了!

    def mousePressEvent(self, event):
        self.isPressed = True
        self.startPos = event.pos()
        return QWidget().mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        self.isPressed = False
        return QWidget().mouseReleaseEvent(event)

    def mouseMoveEvent(self, event):
        if self.isPressed:
            if self.win.isMaximized:
                self.win.showNormal()
    

            movePos =self.win.pos() + ( event.pos() - self.startPos)
  
            self.win.move(movePos)
            # self.startPos = event.globalPos()
Reply View the author
燕子大王来也!
deepin
2024-09-24 21:07
#7

哦莫进来学习学习

Reply View the author
zccrs
deepin
2024-09-25 09:38
#8

标题栏的移动不建议用窗口的mouse move事件实现,这样做很很多弊端,比如无法把窗口的任何区域移动到屏幕外面,无法拖拽窗口到屏幕边缘后自动最大化等。

推荐的方式是调用 x11 的接口,把窗口移动交给窗口管理器来做,可以参考:https://github.com/linuxdeepin/qt5platform-plugins/blob/master/xcb/utility_x11.cpp#L307

Reply View the author
zccrs
deepin
2024-09-25 09:39
#9
zccrs

标题栏的移动不建议用窗口的mouse move事件实现,这样做很很多弊端,比如无法把窗口的任何区域移动到屏幕外面,无法拖拽窗口到屏幕边缘后自动最大化等。

推荐的方式是调用 x11 的接口,把窗口移动交给窗口管理器来做,可以参考:https://github.com/linuxdeepin/qt5platform-plugins/blob/master/xcb/utility_x11.cpp#L307

附上一个图:

企业微信截图_17272283552641.png

Reply View the author
把一切操作变成GUI
deepin
Backbone of ecological co-construction group
2024-09-25 11:31
#10
zccrs

标题栏的移动不建议用窗口的mouse move事件实现,这样做很很多弊端,比如无法把窗口的任何区域移动到屏幕外面,无法拖拽窗口到屏幕边缘后自动最大化等。

推荐的方式是调用 x11 的接口,把窗口移动交给窗口管理器来做,可以参考:https://github.com/linuxdeepin/qt5platform-plugins/blob/master/xcb/utility_x11.cpp#L307

是的,昨晚我也发现了这些问题,没法移动到屏幕外等等


这是不是意味着未来切换到treeland,这个功能就没法用了?😂

假如用QT起码我不用担心,

但是我自己调用xcb貌似就会可能未来版本要重新适配了


不过我可以在后续的版本中试一下修改为调用x11的接口看看


话说使用这个接口需要获取当前标题栏所在窗口ID,好像比较麻烦😥

Reply View the author
zccrs
deepin
2024-09-25 13:18
#11
把一切操作变成GUI

是的,昨晚我也发现了这些问题,没法移动到屏幕外等等


这是不是意味着未来切换到treeland,这个功能就没法用了?😂

假如用QT起码我不用担心,

但是我自己调用xcb貌似就会可能未来版本要重新适配了


不过我可以在后续的版本中试一下修改为调用x11的接口看看


话说使用这个接口需要获取当前标题栏所在窗口ID,好像比较麻烦😥

是的,wayland下的话需要调用另外的接口,不过你也可以用Qt的封装,调用Qt的私有接口:这样就不用担心x11和wayland需要分别实现了

QPlatfromWindow::startSystemMove

Reply View the author
zccrs
deepin
2024-09-25 13:23
#12

可喜可贺,从Qt5.15开始,已经添加了 QWindow::startSystemMove,你可以直接用了。

Reply View the author
把一切操作变成GUI
deepin
Backbone of ecological co-construction group
2024-09-25 13:37
#13
zccrs

是的,wayland下的话需要调用另外的接口,不过你也可以用Qt的封装,调用Qt的私有接口:这样就不用担心x11和wayland需要分别实现了

QPlatfromWindow::startSystemMove


    def mouseDoubleClickEvent(self, event):
        self.ShowRestoreWindow()
        return QWidget().mouseDoubleClickEvent(event)

    def mousePressEvent(self, event):
        self.isPressed = True
        # self.startPos = event.pos()
        return QWidget().mousePressEvent(event)
        # self.win.windowHandle().startSystemMove()

    def mouseReleaseEvent(self, event):
        self.isPressed = False
        return QWidget().mouseReleaseEvent(event)

    def mouseMoveEvent(self, event):
        if self.isPressed:
            if self.win.isMaximized:
                self.win.showNormal()
          
        self.win.windowHandle().startSystemMove()
            # movePos =self.win.pos() + ( event.pos() - self.startPos)
      
            # self.win.move(movePos)
                # self.startPos = event.globalPos()


        return QWidget().mouseMoveEvent(event)

原来就加一行代码这么省事就可以搞定了😧

self.win.windowHandle().startSystemMove()

当初我还去算什么鼠标坐标差值......

但是现在又引发新的bug了,鼠标按住标题栏拖动之后,我所有按钮的hover效果都没有了,必须要点击一下才能再现:

录屏_选择区域_20240925133546.gif

貌似是鼠标释放事件没有被触发

Reply View the author
把一切操作变成GUI
deepin
Backbone of ecological co-construction group
2024-09-25 15:02
#14

https://github.com/wangwenx190/framelesshelper/issues/75

https://stackoverflow.com/questions/76665812/qwindow-startsystemmove-stops-hover-events-on-child-widgets

Reply View the author
zccrs
deepin
2024-09-25 16:47
#15
把一切操作变成GUI

https://github.com/wangwenx190/framelesshelper/issues/75

https://stackoverflow.com/questions/76665812/qwindow-startsystemmove-stops-hover-events-on-child-widgets

这个问题参见:https://github.com/linuxdeepin/qt5platform-plugins/blob/master/xcb/utility_x11.cpp#L330
原因是mouse press后鼠标实际上是被当前窗口grab了,这个grab是x11中自动的,会持续到mouse release,是用来确保在哪个窗口上按下,鼠标释放时无论鼠标是否还在窗口内,这个窗口都可以收到mouse release事件。

但是,触发 system move 操作之后,鼠标的grab会释放,接着由窗口管理器进行grab,后续的鼠标事件都给了窗口管理器,也就导致最终的mouse release 不会被收到,这样Qt程序可能会一直以为鼠标还在按下状态,这时候就不能正确的计算hover了,因为QWidget也是会把事件传递给mouse press时的那个QWidget。

Reply View the author
zccrs
deepin
2024-09-25 16:54
#16
zccrs

这个问题参见:https://github.com/linuxdeepin/qt5platform-plugins/blob/master/xcb/utility_x11.cpp#L330
原因是mouse press后鼠标实际上是被当前窗口grab了,这个grab是x11中自动的,会持续到mouse release,是用来确保在哪个窗口上按下,鼠标释放时无论鼠标是否还在窗口内,这个窗口都可以收到mouse release事件。

但是,触发 system move 操作之后,鼠标的grab会释放,接着由窗口管理器进行grab,后续的鼠标事件都给了窗口管理器,也就导致最终的mouse release 不会被收到,这样Qt程序可能会一直以为鼠标还在按下状态,这时候就不能正确的计算hover了,因为QWidget也是会把事件传递给mouse press时的那个QWidget。

https://github.com/linuxdeepin/qt5platform-plugins/commit/fb00e2668f2d50d0276a480b8b2f0effc62196e4 这是 DTK 里的解决方案,是在收到 ungrab 的事件后,从x11那里重新查询下mouse button的按下状态,如果和Qt记录的状态不一致,就补发一个mouse release事件给Qt。

你这里简单实现的话,可以在调用 startSystemMove 后自己主动补发一个 mouseRelease 事件。

Reply View the author
把一切操作变成GUI
deepin
Backbone of ecological co-construction group
2024-09-25 17:35
#17
zccrs

https://github.com/linuxdeepin/qt5platform-plugins/commit/fb00e2668f2d50d0276a480b8b2f0effc62196e4 这是 DTK 里的解决方案,是在收到 ungrab 的事件后,从x11那里重新查询下mouse button的按下状态,如果和Qt记录的状态不一致,就补发一个mouse release事件给Qt。

你这里简单实现的话,可以在调用 startSystemMove 后自己主动补发一个 mouseRelease 事件。

貌似手动补发事件也不行😥

    def mouseDoubleClickEvent(self, event):
        self.ShowRestoreWindow()
        return QWidget().mouseDoubleClickEvent(event)

    def mousePressEvent(self, event):
        self.isPressed = True
        # self.startPos = event.pos()

        # self.win.windowHandle().startSystemMove()
        return QWidget().mousePressEvent(event)

  
    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            print("鼠标左键释放")
        elif event.button() == Qt.RightButton:
            print("鼠标右键释放")
        # 其他按钮处理
        self.isPressed=False
        super().mouseReleaseEvent(event)  # 调用基类的事件处理

    def triggerMouseReleaseEvent(self, button=Qt.LeftButton):
        # 创建一个QMouseEvent
        event = QMouseEvent(QEvent.MouseButtonRelease, QPoint(), Qt.NoButton, button, Qt.NoModifier)
  
        # 发送事件到你的窗口
        QApplication.sendEvent(self, event)

    def mouseMoveEvent(self, event):
        print("鼠标按键移动")
        if self.isPressed:
            if self.win.isMaximized:
                self.win.showNormal()
  
        self.win.windowHandle().startSystemMove()
        # self.triggerMouseReleaseEvent( Qt.LeftButton)#
  
            # 假设我们需要在移动结束后触发鼠标释放事件
       self.triggerMouseReleaseEvent()
  
        # self.win.setStyleSheet(self.win.styleSheet())

        # self.win.activateWindow() 
            # movePos =self.win.pos() + ( event.pos() - self.startPos)
  
            # self.win.move(movePos)
                # self.startPos = event.globalPos()
        # event.accept()  
        # self.win.update() 
        return QWidget().mouseMoveEvent(event)
Reply View the author
zccrs
deepin
2024-09-25 17:37
#18
把一切操作变成GUI

貌似手动补发事件也不行😥

    def mouseDoubleClickEvent(self, event):
        self.ShowRestoreWindow()
        return QWidget().mouseDoubleClickEvent(event)

    def mousePressEvent(self, event):
        self.isPressed = True
        # self.startPos = event.pos()

        # self.win.windowHandle().startSystemMove()
        return QWidget().mousePressEvent(event)

  
    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            print("鼠标左键释放")
        elif event.button() == Qt.RightButton:
            print("鼠标右键释放")
        # 其他按钮处理
        self.isPressed=False
        super().mouseReleaseEvent(event)  # 调用基类的事件处理

    def triggerMouseReleaseEvent(self, button=Qt.LeftButton):
        # 创建一个QMouseEvent
        event = QMouseEvent(QEvent.MouseButtonRelease, QPoint(), Qt.NoButton, button, Qt.NoModifier)
  
        # 发送事件到你的窗口
        QApplication.sendEvent(self, event)

    def mouseMoveEvent(self, event):
        print("鼠标按键移动")
        if self.isPressed:
            if self.win.isMaximized:
                self.win.showNormal()
  
        self.win.windowHandle().startSystemMove()
        # self.triggerMouseReleaseEvent( Qt.LeftButton)#
  
            # 假设我们需要在移动结束后触发鼠标释放事件
       self.triggerMouseReleaseEvent()
  
        # self.win.setStyleSheet(self.win.styleSheet())

        # self.win.activateWindow() 
            # movePos =self.win.pos() + ( event.pos() - self.startPos)
  
            # self.win.move(movePos)
                # self.startPos = event.globalPos()
        # event.accept()  
        # self.win.update() 
        return QWidget().mouseMoveEvent(event)

这里发没用,要用 QWindowSystemInterface::handleMouseEvent 发才行

Reply View the author
把一切操作变成GUI
deepin
Backbone of ecological co-construction group
2024-09-26 00:25
#19
zccrs

这里发没用,要用 QWindowSystemInterface::handleMouseEvent 发才行

在C++里面貌似可以用,但是在Pyqt5里面貌似没法手动补发😢

Reply View the author
Oli
deepin
2024-09-26 23:09
#20

膜拜大佬

Reply View the author
1 / 2
To page