Search code examples
pythonpyqt4pyqt5

PyQt5: Notify when attribute's value is changed


First of all, please, look at the code sample, which is given below. How can I access, for example,.setDisabled(...) on QPushButton when the value of attribute self.markup_points inside QGraphicsView is changed? How I can implement this using pyqt signals or... use a singleton?

class ImageView(QtWidgets.QGraphicsView):
    def __init__(self, parent):
        super(ImageView, self).__init__(parent)
        self.markup_points = []
        ...
        ...

    def set_image(self, pixmap):
        foo()

    def mousePressEvent(self, event):
        foo()
        self.markup_points.append(QtCore.QPointF(bar()))
        super(ImageView, self).mousePressEvent(event)
    ...

    def keyPressEvent(self, event):
        key = event.key()
        modifiers = int(event.modifiers())
        if (modifiers and modifiers & MOD_MASK == modifiers and
                key > 0 and key != QtCore.Qt.Key_Control and key != QtCore.Qt.Key_Meta):
            if key == 88:
                self.remove_point()

    def remove_point(self):
        if len(self.markup_points):
            self.markup_points.pop()
    ...

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setupUi(self)
        ...
        self.imageView = ImageView()
        self.btnLoad.clicked.connect(self._load_combination)
        self.btnSkip.clicked.connect(self._skip_combination)
        self.btnSave.clicked.connect(self._save_objects)
        # qpushbutton that I want to access later
        self.btnRemove.clicked.connect(self.imageView.remove_point)
    ...
    def event_if_something_is_changed_in_image_view(self):
        self.btnRemove.setDisabled(True)

Solution

  • Why do you think a singleton is the solution? A singleton is an anti-pattern so it should be avoided and only in certain cases is it necessary, besides it has nothing to do with notifying about the changes, so discard it.

    The solution is to create a signal that is issued when there is a change, and connect it to a slot that receives notifications:

    class ImageView(QtWidgets.QGraphicsView):
        markupPointsChanged = QtCore.pyqtSignal(list) # <---
    
        def __init__(self, parent):
            super(ImageView, self).__init__(parent)
            self.markup_points = []
            # ...
    
        def mousePressEvent(self, event):
            foo()
            self.append_point(QtCore.QPointF(bar()))
            super(ImageView, self).mousePressEvent(event)
    
        def keyPressEvent(self, event):
            key = event.key()
            modifiers = int(event.modifiers())
            if (modifiers and modifiers & MOD_MASK == modifiers and
                    key > 0 and key not in (QtCore.Qt.Key_Control, QtCore.Qt.Key_Meta)):
                if key == QtCore.Qt.Key_X:
                    self.remove_point()
    
        def append_point(self, p):
            self.markup_points.append(p)
            self.markupPointsChanged.emit(self.markup_points)  # <---
    
        def remove_point(self):
            if self.markup_points:
                self.markup_points.pop()
            self.markupPointsChanged.emit(self.markup_points) # <---
        # ...
    
    class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
        def __init__(self):
            super(MainWindow, self).__init__()
            self.setupUi(self)
            # ...
            self.imageView = ImageView()
            self.btnLoad.clicked.connect(self._load_combination)
            self.btnSkip.clicked.connect(self._skip_combination)
            self.btnSave.clicked.connect(self._save_objects)
            self.btnRemove.clicked.connect(self.imageView.remove_point)
            self.imageView.markupPointsChanged.connect(self.on_markupPointsChanged) # <---
    
        @QtCore.pyqtSlot(list)
        def on_markupPointsChanged(self, points):
            print(points)
            self.btnRemove.setDisabled(True)