@Bodo96: Sternchen-Importe sind Böse™. Das macht Programme unnötig unübersichtlicher und fehleranfälliger und es besteht die Gefahr von Namenskollisionen. Und obwohl *alles* aus dem Modul importiert wird, werden manche Sachen dann doch über das Modul angesprochen. Warum so inkonsistent?
`Thread`, `Timer`, `QtGui`, `QtCore`, `QTimer` werden importiert, aber nirgends verwendet.
`window` wäre ein passender Name für ein Fenster, aber nicht für eine Funktion. Funktionen und Methoden werden üblicherweise nach Tätigkeiten benannt, damit der Leser weiss was die tun und um sie von eher passiven Werten unterscheiden zu können. Wobei die Hauptfunktion traditionell `main()` heisst.
Die Argumente bei `super()` sind überflüssig. Ebenso die `initUI()`. Es macht keinen Sinn eine so gut wie leere `__init__()` zu haben die eine andere Methode aufruft in der dann die eigentliche Initialisierung steht.
Bitte mal gleich so Sachen wie `setGeometry()`, `move()`, und `resize()` zum festlegen von absoluten Grössen und Positionen in Qt vergessen und sich mit Layouts vertraut machen.
Namen sollten keine kryptischen Abkürzungen enthalten oder gar nur daraus bestehen. Der Name soll dem Leser vermitteln was der Wert dahinter im Programm bedeutet, nicht zum rätseln zwingen.
Man nummeriert keine Namen. Dann will man sich entweder bessere Namen überlegen, oder gar keine Einzelnamen/-werte verwenden, sondern eine Datenstruktur. Oft eine Liste.
Ausserhalb der `__init__()` führt man keine neuen Attribute ein. Die `__init__()` sollte das Objekt komplett initialisieren, so dass man es benutzen kann. Man muss auch nicht alles an das Objekt binden.
`dialog_1` ist falsch geschrieben für eine Klasse, die 1 macht keinen Sinn, und das ganze ist auch gar keine Klasse, sondern eine sehr umständlich geschriebene Funktion. `SecondWindow` ist als Argumentname falsch geschrieben. Und an der Stelle sollte auch niemanden interessieren das wievielte Fenster das ist.
`self.i` ist was anderes als nur `i`. `self.i` wird nirgends verändert nachdem es auf 0 gesetzt wurde.
In den lokalen Funktionen immer noch mal `self` zu übergeben macht keinen Sinn, denn `self` ist ja bereits Teil des Closures. Allerdings würde man auch keine Funktionen lokal in einer Funktion oder Methode auf diese Art definieren. Das macht bei Closures Sinn, die dann die umgebene Funktion tatsächlich als Rückgabewert verlassen, aber hier…
`run()` ist auch in jedem Fall unsinnig weil man die beiden Zeilen auch einfach an der Aufrufstelle schreiben könnte.
`update()` als Thread zu starten macht keinen Sinn. Das wird ja schon regelmässig von dem anderen Thread aufgerufen.
Dialoge zeigt man nicht einfach nur an, die führt man aus. Sonst hätte da auch einfach ein `QWidget` gereicht. So wie das auch anstelle des `QMainWindow` ausreicht wenn man überhaupt nichts verwendet was ein `QMainWindow` bietet.
Zwischenstand, der allerdings ernsthaft kaputt ist:
Code: Alles auswählen
#!/usr/bin/env python3
import sys
import time
from threading import Thread
from gpiozero import Button, Device
from gpiozero.pins.mock import MockFactory
from PyQt5.QtWidgets import (
QApplication,
QDialog,
QLabel,
QPushButton,
QVBoxLayout,
QWidget,
)
Device.pin_factory = MockFactory()
def timer(label):
for i in range(10, 0, -1):
print(i)
label.setText(str(i))
time.sleep(1)
class MainWindow(QWidget):
def __init__(self):
super().__init__(windowTitle="Test Button Fotobox")
layout = QVBoxLayout()
layout.addWidget(QLabel(self, text="First Label!"))
button = QPushButton("Button", self, clicked=self.button_clicked)
layout.addWidget(button)
self.setLayout(layout)
physical_button = Button(24)
physical_button.when_pressed = self.button_clicked
def button_clicked(self):
print("Button wurde gedrückt!")
dialog = QDialog(self)
layout = QVBoxLayout()
label = QLabel(dialog)
layout.addWidget(label)
dialog.setLayout(layout)
Thread(target=timer, args=(label,), daemon=True).start()
dialog.exec()
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
GUIs sind in der Regel nicht threadsicher. Das gilt auch für Qt. Auch falls das hier jetzt scheinbar funktionieren mag, es ist undefiniertes Verhalten, weil man Qt-Objekte nicht einfach so von anderen Threads aus verwenden darf.
Das fängt im Grunde schon bei dem `when_pressed()` an, weil das auch schon ein zusätzlicher Thread ist, von dem ein Rückruf passiert, der in die laufende GUI eingreift. Hier geht es ans eingemachte, weil man etwas machen muss, was eher selten vorkommt: ein eigenes Event mit `postEvent()` von dem anderen Thread aus aufrufen, dass dann beispielsweise auf Qt-Seite ein Signal abgibt, auf das dann reagiert werden kann.
Und den Countdown würde man einfach über einen `QTimer` lösen.
Ungetestet:
Code: Alles auswählen
#!/usr/bin/env python3
import sys
from gpiozero import Button, Device
from gpiozero.pins.mock import MockFactory
from PyQt5.QtCore import QEvent, QTimer
from PyQt5.QtWidgets import (
QApplication,
QDialog,
QLabel,
QPushButton,
QVBoxLayout,
QWidget,
)
Device.pin_factory = MockFactory()
class Countdown:
def __init__(self, parent, label):
self.label = label
self.number = 10
self.timer = QTimer(parent)
self.timer.timeout.connect(self.on_tick)
self.update_ui()
def start(self):
self.timer.start(1000)
def update_ui(self):
self.label.setText(str(self.number))
def on_tick(self):
if self.number <= 0:
self.timer.stop()
else:
self.number -= 1
self.update_ui()
class MainWindow(QWidget):
def __init__(self):
super().__init__(windowTitle="Test Button Fotobox")
layout = QVBoxLayout()
layout.addWidget(QLabel(self, text="First Label!"))
button = QPushButton("Button", self, clicked=self.button_clicked)
layout.addWidget(button)
self.setLayout(layout)
physical_button = Button(24)
physical_button.when_pressed = (
lambda: QApplication.instance().postEvent(
self, QEvent(QEvent.User)
)
)
def button_clicked(self):
print("Button wurde gedrückt!")
dialog = QDialog(self)
layout = QVBoxLayout()
label = QLabel(dialog)
layout.addWidget(label)
dialog.setLayout(layout)
Countdown(dialog, label).start()
dialog.exec()
def event(self, event):
if event.type() == QEvent.User:
self.button_clicked()
return True
return super().event(event)
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()