Bruno Vermeulen
Bruno Vermeulen

Reputation: 3465

PyQt: passing information between windows

The question has been asked before and I want to put forward my solution and wonder if this is an acceptable pattern or is there a better alternative.

Working with QGIS I started to work with PyQt as a GUI (previously mostly tkinter) and I have a situation where I make a selection on the canvas that then opens another window to give information on the features. On the canvas I want to mark the selected feature, so information is required from the other window.

I made a minimal working example in PyQt below (required import PyQt5 with Python 3.8) where I have two windows one called Button and the other Display where Display shows how many times the button is pressed.

To make the communication I defined a QTimer in the Display window that polls the Button window (in this case every 250 ms) to get the required information. Question is there a better way to do this in PyQt?

import sys
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QApplication, QPushButton
from PyQt5.QtCore import QTimer

POLL_INTERVAL = 250  # in ms
WINSIZE = (200, 50)  # w, h
BUTTON_WIN_POS = (200, 200)  # w, h
DISPLAY_WIN_POS = (450, 200)  # w, h

class ButtonWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.button_pressed_counter = 0
        self.initUI()

    def initUI(self):
        vbox = QVBoxLayout()
        button = QPushButton('Press me')
        button.clicked.connect(self.press_button)
        vbox.addWidget(button)

        self.setLayout(vbox)
        self.move(*BUTTON_WIN_POS)
        self.setWindowTitle('Button ... ')
        self.resize(*WINSIZE)
        self.show()

    def press_button(self):
        self.button_pressed_counter += 1

    @property
    def button_count(self):
        return self.button_pressed_counter

class DisplayWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.button_window = ButtonWindow()

        self.button_count = 0
        self.button_counter = QTimer()
        self.button_counter.timeout.connect(self.get_button_count)

        self.initUI()

    def initUI(self):
        vbox = QVBoxLayout()
        self.text_lbl = QLabel()
        vbox.addWidget(self.text_lbl)
        self.text_lbl.setText(f'button count: {self.button_count}')

        self.setLayout(vbox)
        self.move(*DISPLAY_WIN_POS)
        self.setWindowTitle('Display ... ')
        self.resize(*WINSIZE)
        self.show()

        self.button_counter.start(POLL_INTERVAL)

    def get_button_count(self):
        new_button_count = self.button_window.button_count
        if self.button_count == new_button_count:
            pass

        else:
            self.button_count = new_button_count
            self.text_lbl.setText(f'button count: {self.button_count}')


def main():
    app = QApplication([])
    _ = DisplayWindow()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

Upvotes: 0

Views: 205

Answers (1)

musicamante
musicamante

Reputation: 48335

No, it's not a good solution, at least in this scenario. Also, relying on a timer to "poll" is rarely a good solution when dealing with interactive elements: it's unreliable, doesn't allow immediate response between the clicked button and the result, and makes the code unnecessarily complex.

Signals and slots exist exactly for this purpose: a common interface to allow communication between objects; so implement a custom signal in the "source" class, and connect it in the "target".

class ButtonWindow(QWidget):
    pressCountChanged = pyqtSignal(int)
    # ...

    def press_button(self):
        self.button_pressed_counter += 1
        self.pressCountChanged.emit(self.button_pressed_counter)


class DisplayWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.button_window = ButtonWindow()
        self.button_window.pressCountChanged.connect(self.get_button_count)

        self.button_count = 0

        self.initUI()

Obviously, everything related to the QTimer must be removed.

Note that if you're using another window to get "features" that should alter the behavior of the main interface, you should consider using a QDialog instead, and eventually decide if using it as modal (usually by means of exec_()), which prevents interaction with other windows until the dialog is closed, and (possibly) returns the values needed.

Upvotes: 2

Related Questions