[PySide2] Connecting a signal to setValue does not work when Loading UI through QUiLoader
-
Hi, I am facing the following problem: I am loading a UI designed with Qt Designer in PySide and now want to attach some signals to the ui to interact with the data model. Therefore I created a Signal that I am connecting with a setValue function of a QDoubleSpinBox. But everytime I emit some data into the signal, it does not react and the value of the QDoubleSpinBox. In the search for a cause for this unclear behavior, I also created another QDoubleSpinBox manually and also connected the signal to it. Here the value gets changed. The example code is here:
import sys import time from PySide2 import QtGui, QtCore, QtWidgets import pyqtgraph as pg import numpy as np from PySide2.QtCore import * from PySide2.QtUiTools import QUiLoader from PySide2.QtWidgets import QMainWindow, QDoubleSpinBox class PressureViewModel(QObject): _current_pressure = 0.0 _current_pressure_sig = Signal(object) def __init__(self,parent): QtCore.QObject.__init__(self) ui_file = QFile("scratch.ui") ui_file.open(QFile.ReadOnly) loader = QUiLoader() window = loader.load(ui_file,parent) self.sb = QDoubleSpinBox(parent) window.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.sb) self.ui = window def initialize_signals(self): self._current_pressure_sig.connect(self.ui.current_pressure_spin_box.setValue) #does not seem to work self._current_pressure_sig.connect(self.sb.setValue) #works @property def current_pressure(self): return self._current_pressure @current_pressure.setter def current_pressure(self ,value): print("Setter called") self._current_pressure_sig.emit(value) self._current_pressure = value class PressureController: def __init__(self, view_model:PressureViewModel): self.view_model = view_model def cycle_controller(self): for i in range(20): print(self.view_model.current_pressure) time.sleep(0.1) self.view_model.current_pressure = np.random.normal(loc=10,scale=2) return 0.0 class Worker(QThread): def __init__(self, controller:PressureController): QThread.__init__(self) self.controller = controller def run(self): while True: self.controller.cycle_controller() app = QtGui.QApplication([]) window = QMainWindow() pVM = PressureViewModel(window) pC = PressureController(pVM) window.setCentralWidget(pVM.ui) pVM.initialize_signals() window.show() worker = Worker(pC) worker.start() sys.exit(app.exec_())
The ui file is the following:
<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>Form</class> <widget class="QWidget" name="Form"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>400</width> <height>300</height> </rect> </property> <property name="windowTitle"> <string>Form</string> </property> <layout class="QFormLayout" name="formLayout"> <item row="0" column="0"> <widget class="QLabel" name="current_pressure_label"> <property name="text"> <string>Current Pressure</string> </property> </widget> </item> <item row="0" column="1"> <widget class="QDoubleSpinBox" name="current_pressure_spin_box"/> </item> </layout> </widget> <resources/> <connections/> </ui>
I have the feeling that I somehow "loose connection" to the proper ui elements and am really not connecting the signal to the correct function. Does anybody has an idea where my error could be?
-
Hi @tyde,
I think your issue may be to do with threading. Generally speaking, it's better not to sub-class
QThread
, but to move objects into aQThread
. Also, I think you are making direct calls between threads when you need to use the Signal/Slot mechanism to communicate.The following example uses your UI file and groups the functionality together a little. The
PressureController
is now passed in to aQThread
owned by theQMainWindow
that also constructs the UI. Everything is connected together via signals and slots.import time from PySide2.QtCore import QFile, QObject, qApp, QThread, Signal, Slot, Property from PySide2.QtWidgets import QMainWindow from PySide2.QtUiTools import QUiLoader import numpy as np class PressureController(QObject): currentPressure = Signal(float) stopped = Signal() def __init__(self): super().__init__() self._looping = False @Slot() def run(self): self._looping = True try: while self._looping: self.currentPressure.emit(np.random.normal(loc=10, scale=2)) qApp.processEvents() time.sleep(0.1) except Exception as e: print(e) self.stopped.emit() @Slot() def stop(self): self._looping = False class PressureViewModel(QMainWindow): stopThread = Signal() def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self._currentPressure = 0.0 ui_file = QFile("./scratch.ui") ui_file.open(QFile.ReadOnly) ui_loader = QUiLoader() self.ui = ui_loader.load(ui_file, parent) self.setCentralWidget(self.ui) self.pressureController = PressureController() self.pressureController.currentPressure.connect(self.setCurrentPressure) self.thread = QThread() self.thread.started.connect(self.pressureController.run) self.pressureController.stopped.connect(self.thread.quit) self.pressureController.moveToThread(self.thread) self.stopThread.connect(self.pressureController.stop) self.thread.start() def readCurrentPressure(self): return self._currentPressure @Slot(float) def setCurrentPressure(self, pressure): if self._currentPressure == pressure: return self._currentPressure = pressure self.ui.current_pressure_spin_box.setValue(pressure) currentPressure = Property(float, readCurrentPressure, setCurrentPressure) def closeEvent(self, event): # This stops the thread when the window is closed, otherwise the program # technically crashes because the thread it orphaned. if self.thread.isRunning(): self.stopThread.emit() while not self.thread.isFinished(): qApp.processEvents() event.accept() if __name__ == "__main__": from sys import argv, exit from PySide2.QtCore import QCoreApplication, Qt from PySide2.QtWidgets import QApplication QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts) a = QApplication(argv) pvm = PressureViewModel() pvm.show() exit(a.exec_())
Please feel free to ask questions about anything that I haven't explained.
Hope this helps :o)