【问题标题】:Track mouse movements in several widgets at the same time同时跟踪多个小部件中的鼠标移动
【发布时间】:2019-12-01 16:05:40
【问题描述】:

我想在鼠标用于draw in one of the two QGraphicsView 时跟踪calculation (of values for progress bars) 的鼠标移动。使用下面的代码,您可以在QGraphicsView 中绘制鼠标在gridLayout 上移动的坐标,但不能同时进行两者。如何做到这一点?


main.py

​​>
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QCursor, QPainterPath, QPen
from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsView, QGraphicsScene, QGraphicsPathItem
from PyQt5.uic import loadUi

app = None


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        loadUi("mainwindow.ui", self)
        self.showMaximized()
        self.setMouseTracking(True)
        self.centralWidget().setAttribute(Qt.WA_MouseTracking)

        self._old_x = QCursor.pos().x()
        self._old_y = QCursor.pos().y()

        self.verticalLayout_top.addWidget(GraphicsView())
        self.verticalLayout_bottom.addWidget(GraphicsView())

    @staticmethod
    def _update_bar(progress_bar, delta):
        current_value = progress_bar.value()
        new_value = current_value + delta
        progress_bar.setValue(new_value)

    def mouseMoveEvent(self, event):
        new_x = event.x()
        new_y = event.y()

        if new_x > self._old_x:
            self._update_bar(self.progressBar_x_plus, new_x - self._old_x)
        if new_x < self._old_x:
            self._update_bar(self.progressBar_x_minus, self._old_x - new_x)

        if new_y > self._old_y:
            self._update_bar(self.progressBar_y_plus, new_y - self._old_y)
        if new_y < self._old_y:
            self._update_bar(self.progressBar_y_minus, self._old_y - new_y)

        self._old_x = new_x
        self._old_y = new_y


class GraphicsView(QGraphicsView):
    def __init__(self):
        super().__init__()
        self.start = None
        self.end = None

        self.setScene(QGraphicsScene())
        self.path = QPainterPath()
        self.item = GraphicsPathItem()
        self.scene().addItem(self.item)

        self.contents_rect = self.contentsRect()
        self.setSceneRect(0, 0, self.contents_rect.width(), self.contents_rect.height())
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

    def mousePressEvent(self, event):
        self.start = self.mapToScene(event.pos())
        self.path.moveTo(self.start)

    def mouseMoveEvent(self, event):
        self.end = self.mapToScene(event.pos())
        self.path.lineTo(self.end)
        self.start = self.end
        self.item.setPath(self.path)


class GraphicsPathItem(QGraphicsPathItem):
    def __init__(self):
        super().__init__()
        pen = QPen()
        pen.setColor(Qt.black)
        pen.setWidth(5)
        self.setPen(pen)


def main():
    global app
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

主窗口.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>1003</width>
    <height>703</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Mouse Pointer</string>
  </property>
  <property name="locale">
   <locale language="English" country="UnitedKingdom"/>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout_2">
    <item row="0" column="0">
     <layout class="QVBoxLayout" name="verticalLayout_top"/>
    </item>
    <item row="1" column="0">
     <layout class="QGridLayout" name="gridLayout">
      <item row="0" column="0">
       <widget class="QLabel" name="label_x_plus">
        <property name="text">
         <string>X+</string>
        </property>
        <property name="alignment">
         <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
        </property>
       </widget>
      </item>
      <item row="0" column="1">
       <widget class="QProgressBar" name="progressBar_x_plus">
        <property name="maximum">
         <number>1000</number>
        </property>
        <property name="value">
         <number>0</number>
        </property>
       </widget>
      </item>
      <item row="1" column="0">
       <widget class="QLabel" name="label_x_minus">
        <property name="text">
         <string>X-</string>
        </property>
        <property name="alignment">
         <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
        </property>
       </widget>
      </item>
      <item row="1" column="1">
       <widget class="QProgressBar" name="progressBar_x_minus">
        <property name="maximum">
         <number>1000</number>
        </property>
        <property name="value">
         <number>0</number>
        </property>
       </widget>
      </item>
      <item row="2" column="0">
       <widget class="QLabel" name="label_y_plus">
        <property name="text">
         <string>Y+</string>
        </property>
        <property name="alignment">
         <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
        </property>
       </widget>
      </item>
      <item row="2" column="1">
       <widget class="QProgressBar" name="progressBar_y_plus">
        <property name="maximum">
         <number>1000</number>
        </property>
        <property name="value">
         <number>0</number>
        </property>
       </widget>
      </item>
      <item row="3" column="0">
       <widget class="QLabel" name="label_y_minus">
        <property name="text">
         <string>Y-</string>
        </property>
        <property name="alignment">
         <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
        </property>
       </widget>
      </item>
      <item row="3" column="1">
       <widget class="QProgressBar" name="progressBar_y_minus">
        <property name="maximum">
         <number>1000</number>
        </property>
        <property name="value">
         <number>0</number>
        </property>
       </widget>
      </item>
     </layout>
    </item>
    <item row="2" column="0">
     <layout class="QVBoxLayout" name="verticalLayout_bottom"/>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>1003</width>
     <height>24</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

更新 1:尝试在整个窗口上跟踪鼠标移动:

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        loadUi("mainwindow.ui", self)
        self.showMaximized()
        self.global_pos = QCursor.pos()

        for lay in (self.verticalLayout_top, self.verticalLayout_bottom):
            view = GraphicsView()
            listener = MouseListener(view.viewport())
            listener.posChanged.connect(self.on_pos_changed)
            lay.addWidget(view)

        window_listener = MouseListener(self)
        window_listener.posChanged.connect(self.on_pos_changed)

更新 2

import sys
from PyQt5.QtCore import Qt, QObject, pyqtSignal, QPoint, QEvent
from PyQt5.QtGui import QCursor, QPainterPath, QPen
from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsView, QGraphicsScene, QGraphicsPathItem, QWidget
from PyQt5.uic import loadUi

app = None


class MouseListener(QObject):
    posChanged = pyqtSignal(QPoint)

    def __init__(self, widget):
        super().__init__(widget)
        self._widget = widget
        self._childrens = []

        self._setup_widget(self._widget)

        for w in self._widget.findChildren(QWidget):
            self._setup_widget(w)
            self._childrens.append(w)

    def _setup_widget(self, w):
        w.installEventFilter(self)
        w.setMouseTracking(True)

    def eventFilter(self, obj, event):
        # if obj in [self._widget] + self._childrens and event.type() == QEvent.MouseMove:
        if event.type() == QEvent.MouseMove:
            self.posChanged.emit(event.globalPos())

        if event.type() == QEvent.ChildAdded:
            obj = event.child()
            if obj.isWidgetType():
                self._setup_widget(obj)
                self._childrens.append(obj)

        if event.type() == QEvent.ChildRemoved:
            c = event.child()
            if c in self._childrens:
                c.removeEventFilter(self)
                self._childrens.remove(c)
        return super().eventFilter(obj, event)


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        loadUi("mainwindow.ui", self)
        self.showMaximized()
        self.global_pos = QCursor.pos()

        for lay in (self.verticalLayout_top, self.verticalLayout_bottom):
            view = GraphicsView()
            listener = MouseListener(view.viewport())
            listener.posChanged.connect(self.on_pos_changed)
            lay.addWidget(view)

        # window_listener = MouseListener(self)
        # window_listener.posChanged.connect(self.on_pos_changed)

    @staticmethod
    def _update_bar(progress_bar, delta):
        current_value = progress_bar.value()
        new_value = current_value + delta
        progress_bar.setValue(new_value)

    def on_pos_changed(self, pos):
        new_x = pos.x()
        new_y = pos.y()
        old_x = self.global_pos.x()
        old_y = self.global_pos.y()
        if new_x > old_x:
            self._update_bar(self.progressBar_x_plus, new_x - old_x)
        if new_x < old_x:
            self._update_bar(self.progressBar_x_minus, old_x - new_x)
        if new_y > old_y:
            self._update_bar(self.progressBar_y_plus, new_y - old_y)
        if new_y < old_y:
            self._update_bar(self.progressBar_y_minus, old_y - new_y)
        self.global_pos = pos


class GraphicsView(QGraphicsView):
    def __init__(self):
        super().__init__()
        self.start = None
        self.end = None

        self.setScene(QGraphicsScene())
        self.path = QPainterPath()
        self.item = GraphicsPathItem()
        self.scene().addItem(self.item)

        self.contents_rect = self.contentsRect()
        self.setSceneRect(0, 0, self.contents_rect.width(), self.contents_rect.height())
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

    def mousePressEvent(self, event):
        if event.buttons() & Qt.LeftButton:
            self.start = self.mapToScene(event.pos())
            self.path.moveTo(self.start)
        # super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if event.buttons() & Qt.LeftButton:
            self.end = self.mapToScene(event.pos())
            self.path.lineTo(self.end)
            self.start = self.end
            self.item.setPath(self.path)
        # super().mouseMoveEvent(event)


class GraphicsPathItem(QGraphicsPathItem):
    def __init__(self):
        super().__init__()
        pen = QPen()
        pen.setColor(Qt.black)
        pen.setWidth(5)
        self.setPen(pen)


def main():
    global app
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

【问题讨论】:

    标签: python pyqt pyqt5


    【解决方案1】:

    问题在于,除了您覆盖如果你想监听其他小部件的事件,mouseMoveEvent 方法是有限的。

    考虑到上述情况,一个可能的解决方案是使用 eventFilter 来监听任何小部件的事件,另一个改进是使用全局位置而不是本地位置,这样当鼠标从 QGraphicsView 更改时不受影响按局部坐标系。

    class MouseListener(QObject):
        posChanged = pyqtSignal(QPoint)
    
        def __init__(self, widget):
            super().__init__(widget)
            self._widget = widget
            self._widget.setMouseTracking(True)
            self._widget.installEventFilter(self)
    
        def eventFilter(self, obj, event):
            if obj is self._widget and event.type() == QEvent.MouseMove:
                self.posChanged.emit(event.globalPos())
            return super().eventFilter(obj, event)
    
    
    class MainWindow(QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
            loadUi("mainwindow.ui", self)
            self.showMaximized()
            self.global_pos = QCursor.pos()
            for lay in (self.verticalLayout_top, self.verticalLayout_bottom):
                view = GraphicsView()
                listener = MouseListener(view.viewport())
                listener.posChanged.connect(self.on_pos_changed)
                lay.addWidget(view)
    
        @staticmethod
        def _update_bar(progress_bar, delta):
            current_value = progress_bar.value()
            new_value = current_value + delta
            progress_bar.setValue(new_value)
    
        def on_pos_changed(self, pos):
            new_x = pos.x()
            new_y = pos.y()
            old_x = self.global_pos.x()
            old_y = self.global_pos.y()
            if new_x > old_x:
                self._update_bar(self.progressBar_x_plus, new_x - old_x)
            if new_x < old_x:
                self._update_bar(self.progressBar_x_minus, old_x - new_x)
            if new_y > old_y:
                self._update_bar(self.progressBar_y_plus, new_y - old_y)
            if new_y < old_y:
                self._update_bar(self.progressBar_y_minus, old_y - new_y)
            self.global_pos = pos
    

    更新:

    class GraphicsView(QGraphicsView):
        def __init__(self):
            super().__init__()
            # ...
    
        def mousePressEvent(self, event):
            if event.buttons() & Qt.LeftButton:
                self.start = self.mapToScene(event.pos())
                self.path.moveTo(self.start)
            super().mousePressEvent(event)
    
        def mouseMoveEvent(self, event):
            if event.buttons() & Qt.LeftButton:
                self.end = self.mapToScene(event.pos())
                self.path.lineTo(self.end)
                self.start = self.end
                self.item.setPath(self.path)
            super().mouseMoveEvent(event)
    

    更新:

    在这种情况下,您不仅必须将 eventFilter 应用于 QGraphicsView 的视口,还必须将其应用于窗口的所有子窗口。

    class MouseListener(QObject):
        posChanged = pyqtSignal(QPoint)
    
        def __init__(self, widget):
            super().__init__(widget)
            self._widget = widget
            self._childrens = []
    
            self._setup_widget(self._widget)
    
            for w in self._widget.findChildren(QWidget):
                self._setup_widget(w)
                self._childrens.append(w)
    
        def _setup_widget(self, w):
            w.installEventFilter(self)
            w.setMouseTracking(True)
    
        def eventFilter(self, obj, event):
            if obj in [self._widget] + self._childrens and event.type() == QEvent.MouseMove:
                self.posChanged.emit(event.globalPos())
    
            if event.type() == QEvent.ChildAdded:
                obj = event.child()
                if obj.isWidgetType():
                    self._setup_widget(obj)
                    self._childrens.append(obj)
    
            if event.type() == QEvent.ChildRemoved:
                c = event.child()
                if c in self._childrens:
                    c.removeEventFilter(self)
                    self._childrens.remove(c)
            return super().eventFilter(obj, event)
    
    
    class MainWindow(QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
            loadUi("mainwindow.ui", self)
            self.showMaximized()
            self.global_pos = QCursor.pos()
            listener = MouseListener(self)
            listener.posChanged.connect(self.on_pos_changed)
    
            for lay in (self.verticalLayout_top, self.verticalLayout_bottom):
                view = GraphicsView()
                lay.addWidget(view)
            # ...
    

    【讨论】:

    • 太棒了!此外,我希望在移动鼠标时始终更新进度条,但仅在按下鼠标左键时才绘制QGraphicsView。我已经在GraphicsView() 类中将if event.button() == Qt.LeftButton 添加到mouseMoveEvent(self, event),但它并没有那样工作。
    • @Atalanttore 你在写你需要的东西时必须直截了当,在接受答案后改变或加深你的要求不是一个好主意。例如,它会导致问答永无止境,这不是 SO 的目标。在这种情况下,我会根据您的新要求更新我的答案,但我只会做一次。
    • @Atalanttore 1)您有更多的好奇心并不意味着您的问题随时都会被修改,如果您有新的疑问,那么您必须创建一个新的出版物(如果您认为相关,那么信号作为上一个问题/答案的先行者)
    • @Atalanttore 2) 当您覆盖父亲的方法并且您不调用 super() 时,您正在消除默认行为,例如,如果您启用由 mouseXEvent 方法处理的拖动,然后通过不调用 super 它可能不再起作用,如果你覆盖按钮的 mousePressEvent 方法并且你不调用 super 那么点击信号将不会触发。
    • @Atalanttore 3)在您的情况下,这部分代码是不必要的,因为当我添加过滤器时您的小部件是空的,但假设在添加几个小部件后只需添加过滤器,以前的小部件是否会是过滤?不,这就是为什么我将过滤器添加到现有子项并使用 ChildX 事件跟踪将添加的小部件。有了以上内容,我结束了 cmets 部分,因为正如我已经指出的那样:在 SO 中,我们不想要无休止的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-09-04
    • 2021-02-06
    • 2013-08-21
    • 1970-01-01
    • 2022-06-22
    • 2011-07-24
    • 1970-01-01
    相关资源
    最近更新 更多