同时跟踪多个小部件中的鼠标移动

时间:2019-12-01 16:05:40

标签: python pyqt pyqt5

当鼠标习惯于calculation (of values for progress bars)时,我想跟踪draw in one of the two QGraphicsView的鼠标移动。使用下面的代码,您可以在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()

mainwindow.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()

1 个答案:

答案 0 :(得分:2)

问题是,除了覆盖策略的策略之外,mousePressEvent不一定从父窗口小部件传播到子窗口小部件(行为取决于每种类型的窗口小部件,例如QLabel如果它传播鼠标事件)。如果要监听其他小部件的事件,则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)
        # ...