GUI mit Qt5 - Werteübergabe in Thread

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
salu44
User
Beiträge: 6
Registriert: Sonntag 22. Dezember 2019, 17:59

Hallo zusammen,

ich bin absoluter Neuling in Python und bräuchte deswegen eure Unterstützung.
Ich will eine Benutzeroberfläche in Python programmieren, um eine Dauermessung zu überwachen. Dazu will ich eingegebene Werte von der GUI einlesen und diese in den Thread übergeben. Im Thread läuft die Endlosschleife zur Messung und bei einem abweichenden Wert soll dies auf der GUI kenntlich gemacht werden.

Mein Problem ist jetzt wie ich die Werte 'einst_1' und 'einst_2' aus der Ui_MainWindow in MyThread übergeben kann.

Vielen Dank schon mal im Voraus:)


Hier mal mein Beispiel-Code:

Code: Alles auswählen


from PyQt5 import QtCore, QtWidgets
import time
from PyQt5.QtCore import QThread, pyqtSignal


class MyThread(QThread):
    # Create a counter thread

    change_text = pyqtSignal(str)

    def run(self):
        x = 0
        while x < 1000:
            x += 1
            time.sleep(0.1)
            if x == 50:
                self.change_text.emit('FEHLER')





class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(698, 363)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.btn_start = QtWidgets.QPushButton(self.centralwidget)
        self.btn_start.setGeometry(QtCore.QRect(290, 220, 151, 61))
        self.btn_start.setObjectName("btn_start")
        self.comboBox = QtWidgets.QComboBox(self.centralwidget)
        self.comboBox.setGeometry(QtCore.QRect(180, 60, 91, 31))
        self.comboBox.setObjectName("comboBox")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        self.comboBox_2 = QtWidgets.QComboBox(self.centralwidget)
        self.comboBox_2.setGeometry(QtCore.QRect(180, 120, 91, 31))
        self.comboBox_2.setObjectName("comboBox_2")
        self.comboBox_2.addItem("")
        self.comboBox_2.addItem("")
        self.comboBox_2.addItem("")
        self.btn_einstellungen = QtWidgets.QPushButton(self.centralwidget)
        self.btn_einstellungen.setGeometry(QtCore.QRect(310, 80, 171, 41))
        self.btn_einstellungen.setObjectName("btn_einstellungen")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 698, 26))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)


        self.btn_einstellungen.clicked.connect(self.Einstellungen_uebernehmen)
        self.btn_start.clicked.connect(self.Messung_starten)


        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.btn_start.setText(_translate("MainWindow", "Start"))
        self.comboBox.setItemText(0, _translate("MainWindow", "5"))
        self.comboBox.setItemText(1, _translate("MainWindow", "10"))
        self.comboBox.setItemText(2, _translate("MainWindow", "15"))
        self.comboBox_2.setItemText(0, _translate("MainWindow", "20"))
        self.comboBox_2.setItemText(1, _translate("MainWindow", "30"))
        self.comboBox_2.setItemText(2, _translate("MainWindow", "40"))
        self.btn_einstellungen.setText(_translate("MainWindow", "Einstellungen übernehmen"))



    def Einstellungen_uebernehmen(self):
        '''Diese Funktion liest die Einstellungswerte vom GUI ein.'''

        self.einst_1 = int(self.comboBox.currentText())
        self.einst_2 = int(self.comboBox_2.currentText())

        print('Einstellung 1: ', self.einst_1)
        print('Einstellung 2: ', self.einst_2)

        self.btn_einstellungen.setStyleSheet("background-color: rgb(0,220,0)")



    def Messung_starten(self):
        self.thread = MyThread()
        self.thread.change_text.connect(self.setText)
        self.thread.start()

    def setText(self, text):
        self.btn_start.setText(text)



if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

Benutzeravatar
__blackjack__
User
Beiträge: 14002
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Die Frage hat im Grunde weder etwas mit GUIs noch mit Threads zu tun. Wenn man Werte innerhalb einer Methode eines Objektes nutzen will, muss man die als Argumente übergeben. Entweder beim erstellen des Objekts und dann an das Objekt binden, damit die Methode da später drauf zugreifen kann, oder beim Aufruf der Methode. Hier kommt jetzt so ein ganz kleines bisschen Threading ins Spiel, weil man der `run()`-Methode von `QThread` nichts übergeben kann, muss man die Werte beim erstellen übergeben, also eine eigene `__init__()` schreiben, welche die QThread-`__init__()` aufruft und die Werte die Du da haben willst an `self` bindet.

Die Vorsilbe `My` ist unsinnig solange es nicht auch `OurThread` oder `TheirThread` gibt. Weg damit.

Importe gehören an den Anfang des Moduls, nicht irgendwo kurz vor Ende versteckt. Alles was in in dem ``if __name__ == …``-Konstrukt steckt, sollte in einer eigenen Funktion stehen.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Und wenn externe Bibliotheken die in anderen Programmiersprachen geschrieben sind, etwas anderes machen, wie beispielsweise Qt, das camelCase für Namen verwendet, allerdings auch MixedCase für Klassen. Also `MainWindow` für etwas das keine Klasse ist, wäre nach beiden Konventionen falsch. Ebenso wie `Einstellungen_uebernehmen` oder `Messung_starten` was ebenfalls keiner der beiden Konventionen entspricht.

Namen sollten keine kryptischen Abkürzungen enthalten. Wenn man `button` meint, sollte man nicht `btn` schreiben, oder `einst` wenn man `einstellung` meint. Ebenfalls sollte man Namen nicht durchnummerieren. Dann will man entweder bessere Namen finden oder gar keine Einzelnamen sondern eine Datenstruktur verwenden. Oft eine Liste.

Das sieht nach generiertem Code aus der in eigenen Code kopiert und dann verändert wurde. Eine ganz schlechte Idee, weil man jetzt die GUI nicht mehr mit dem Designer bearbeiten kann, um zum Beispiel die absolute Platzierung der Widgets durch ein ordentliches Layout zu ersetzen. In dem generierten Code steht ganz oben ein Kommentar mit der Warnung nichts manuell am Code zu ändern. Es ist sowieso ungewöhnlich heutzutage überhaupt noch Code zu generieren statt das `PyQt5.uic`-Modul zu verwenden und einfach die *.ui-Datei zur Laufzeit des Programms zu laden.

Ausserhalb der `__init__()` sollten keine neuen Attribute eingeführt werden. Das ist mit dem recht eigenartigen Ansatz den Du gewählt hast die `Ui_MainWindow`-Klasse einzubinden nicht möglich aber auch da sollte man die Einführung von neuen Attributen auf die Methode beschränken die zum initialisieren aufgerufen wird. In diesem Fall also `setupUi()`. Bei `einst_1` und `einst_2` sehe ich auch gar keinen Grund warum man das an das Objekt binden sollte. Bei `thread` sollte man mit `None` initialisieren. Man muss ja auch irgendwie noch Regeln das nur *ein* Thread gestartet werden kann, und nicht immer wieder neue wenn man den Start-Button mehrfach hintereinander klickt.

Die ``while``-Schleife in `run()` sollte eine ``for``-Schleife sein.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import sys
import time

from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import QThread, pyqtSignal


class Thread(QThread):
    change_text = pyqtSignal(str)

    def run(self):
        for x in range(1000):
            time.sleep(0.1)
            if x == 50:
                self.change_text.emit("FEHLER")


class Ui_MainWindow:
    def setupUi(self, main_window):
        main_window.setObjectName("MainWindow")
        main_window.resize(698, 363)
        self.centralwidget = QtWidgets.QWidget(main_window)
        self.centralwidget.setObjectName("centralwidget")
        self.start_button = QtWidgets.QPushButton(self.centralwidget)
        self.start_button.setGeometry(QtCore.QRect(290, 220, 151, 61))
        self.start_button.setObjectName("start_button")
        self.einstellung_a_combobox = QtWidgets.QComboBox(self.centralwidget)
        self.einstellung_a_combobox.setGeometry(QtCore.QRect(180, 60, 91, 31))
        self.einstellung_a_combobox.setObjectName("einstellung_a_combobox")
        self.einstellung_a_combobox.addItem("")
        self.einstellung_a_combobox.addItem("")
        self.einstellung_a_combobox.addItem("")
        self.einstellung_b_combobox = QtWidgets.QComboBox(self.centralwidget)
        self.einstellung_b_combobox.setGeometry(QtCore.QRect(180, 120, 91, 31))
        self.einstellung_b_combobox.setObjectName("einstellung_b_combobox")
        self.einstellung_b_combobox.addItem("")
        self.einstellung_b_combobox.addItem("")
        self.einstellung_b_combobox.addItem("")
        self.einstellungen_uebernehmen_button = QtWidgets.QPushButton(
            self.centralwidget
        )
        self.einstellungen_uebernehmen_button.setGeometry(
            QtCore.QRect(310, 80, 171, 41)
        )
        self.einstellungen_uebernehmen_button.setObjectName(
            "einstellungen_uebernehmen_button"
        )
        main_window.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(main_window)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 698, 26))
        self.menubar.setObjectName("menubar")
        main_window.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(main_window)
        self.statusbar.setObjectName("statusbar")
        main_window.setStatusBar(self.statusbar)

        self.einstellungen_uebernehmen_button.clicked.connect(
            self.uebernehme_einstellungen
        )
        self.start_button.clicked.connect(self.starte_messung)

        self.retranslateUi(main_window)
        QtCore.QMetaObject.connectSlotsByName(main_window)
        self.thread = None

    def retranslateUi(self, main_window):
        translate = QtCore.QCoreApplication.translate
        main_window.setWindowTitle(translate("MainWindow", "MainWindow"))
        self.start_button.setText(translate("MainWindow", "Start"))
        self.einstellung_a_combobox.setItemText(
            0, translate("MainWindow", "5")
        )
        self.einstellung_a_combobox.setItemText(
            1, translate("MainWindow", "10")
        )
        self.einstellung_a_combobox.setItemText(
            2, translate("MainWindow", "15")
        )
        self.einstellung_b_combobox.setItemText(
            0, translate("MainWindow", "20")
        )
        self.einstellung_b_combobox.setItemText(
            1, translate("MainWindow", "30")
        )
        self.einstellung_b_combobox.setItemText(
            2, translate("MainWindow", "40")
        )
        self.einstellungen_uebernehmen_button.setText(
            translate("MainWindow", "Einstellungen übernehmen")
        )

    def uebernehme_einstellungen(self):
        einstellung_a = int(self.einstellung_a_combobox.currentText())
        einstellung_b = int(self.einstellung_b_combobox.currentText())

        print('Einstellung 1: ', einstellung_a)
        print('Einstellung 2: ', einstellung_b)

        self.einstellungen_uebernehmen_button.setStyleSheet(
            "background-color: rgb(0,220,0)"
        )

    def starte_messung(self):
        if self.thread is None:
            self.thread = Thread()
            self.thread.change_text.connect(self.setText)
            self.thread.finished.connect(self.on_thread_finished)
            self.thread.start()

    def on_thread_finished(self):
        self.thread = None

    def setText(self, text):
        self.start_button.setText(text)


def main():
    app = QtWidgets.QApplication(sys.argv)
    main_window = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(main_window)
    main_window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
Die Buttonfarbe zu ändern ist IMHO nicht sinnvoll, denn ob das dann sichtbar ist, hängt vom System/Theme ab. Bei mir sind aktive Buttons beispielsweise blau, das heisst wenn ich auf „Einstellungen übernehmen“ drücke, dann sehe ich das grün erst einmal gar nicht. Erst wenn ich auf ein anderes GUI-Element klicke und der Button damit nicht mehr das Element mit dem Eingabefokus ist. Wenn das etwas ist was man beispielsweise vor dem Start machen muss, dann sollte ganz einfach der Startbutton solange deaktiviert sein bis alle Voraussetzungen für einen Start erfüllt sind. Während die Messung läuft, sollte der Start-Button auch deaktiviert sein.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
salu44
User
Beiträge: 6
Registriert: Sonntag 22. Dezember 2019, 17:59

Vielen Dank __blackjack__ für die sehr ausführliche und hilfreiche Antwort!
Antworten