Reputation: 3465
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
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