QT "updaten"?

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
onur22
User
Beiträge: 19
Registriert: Freitag 28. Dezember 2018, 21:25

Freitag 3. Mai 2019, 21:12

Hallo,

ich schreibe aktuell ein Programm, indem ein Bild angezeigt werden soll und der Nutzer soll daraufhin sagen, was er sieht. Dann soll eben die Nutzerantwort mit der richtigen Lösung verglichen werden und ausgegeben werden, ob die Antwort richtig oder falsch war. Soweit habe ich es hingekriegt.

Nun möchte ich, dass der Nutzer daraufhin einen Button betätigen kann, um ein nächstes Bild abzurufen. Jetzt ist mir leider nicht klar, wie ich es schaffe, dass erste Bild zu entfernen und anstatt dessen ein anderes anzeigen zu lassen. Das ganze soll in einer while Schleife laufen, also ich würde gerne eine Liste von Verzeichnissen zu Bildern anlegen und diese nach und nach abarbeiten lassen.

Mit welcher Funktion lässt sich das am ehesten bewerkstelligen?

Vielen Dank
alles ist als Einsteiger möglich. Es ist nur die Frage, wie lange es dauert, bis man die nötigen Vorkenntnisse erworben hat.
Benutzeravatar
__blackjack__
User
Beiträge: 4477
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Freitag 3. Mai 2019, 22:07

@onur22: Mit einer Schleife geht das gar nicht weil wenn Deine Schleife läuft, die Qt-Hauptschleife nicht laufen kann. Wenn die Schaltfläche für das nächste Bild geklickt wird, musst Du halt das nächste Bild anzeigen.

Und was die Anzeige angeht: Du ersetzt halt einfach das Bild. Wenn das beispielsweise in einem `QLabel` angezeigt wird, setzt Du mit `setPixmap()` ein neues Bild.
“Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.”
— Terry Pratchett, Jingo
onur22
User
Beiträge: 19
Registriert: Freitag 28. Dezember 2018, 21:25

Freitag 3. Mai 2019, 22:17

__blackjack__ hat geschrieben:
Freitag 3. Mai 2019, 22:07
@onur22: Mit einer Schleife geht das gar nicht weil wenn Deine Schleife läuft, die Qt-Hauptschleife nicht laufen kann. Wenn die Schaltfläche für das nächste Bild geklickt wird, musst Du halt das nächste Bild anzeigen.

Und was die Anzeige angeht: Du ersetzt halt einfach das Bild. Wenn das beispielsweise in einem `QLabel` angezeigt wird, setzt Du mit `setPixmap()` ein neues Bild.
Okay, danke!

Also ich habe entsprechend ein Button erstellt, die dann folgende Funktion abrufen soll:

Code: Alles auswählen

def on_click(self):
        label = QLabel(self)
        pixmap = QPixmap('Pfad vom neuen Bild')
        label.setPixmap(pixmap)
        label.move(900, 50)
So scheint es irgendwie nicht zu klappen. Oder muss das alles in der selben Funktion passieren?
alles ist als Einsteiger möglich. Es ist nur die Frage, wie lange es dauert, bis man die nötigen Vorkenntnisse erworben hat.
Sirius3
User
Beiträge: 10771
Registriert: Sonntag 21. Oktober 2012, 17:20

Freitag 3. Mai 2019, 22:23

"Scheint nicht zu klappen" ist keine gute Fehlerbeschreibung. Was passiert exakt? Gibt es Fehlermeldungen? Wie sieht der gesamte relevante Code aus?
Du willst nicht ein neues Label erzeugen, sondern das vorhanden nur mit einem neuen Bild ausstatten.
onur22
User
Beiträge: 19
Registriert: Freitag 28. Dezember 2018, 21:25

Samstag 4. Mai 2019, 09:17

Sirius3 hat geschrieben:
Freitag 3. Mai 2019, 22:23
"Scheint nicht zu klappen" ist keine gute Fehlerbeschreibung. Was passiert exakt? Gibt es Fehlermeldungen? Wie sieht der gesamte relevante Code aus?
Du willst nicht ein neues Label erzeugen, sondern das vorhanden nur mit einem neuen Bild ausstatten.
Es kommt zu keiner Fehlermeldung, aber die GUI bleibt unverändert(das Bild ändert sich nicht), wenn der entsprechende Button getätigt wird. Und es stimmt, dass ich kein neues erzeugen will, deswegen dachte ich mir schon, dass das so eig. nicht klappen kann. Nur fehlt mir eine Alternative

Code:

Code: Alles auswählen

class App(QMainWindow):


    def __init__(self):
        super().__init__()


        self.title = 'Test'
        self.left = 10
        self.top = 10
        self.width = 800
        self.height = 340
        self.initUI()


    def initUI(self):
        Pictures = ['*', '*']
        Solution = ['*', '*']
        count = 0
        self.setWindowTitle(self.title)


        self.setGeometry(self.left, self.top, self.width, self.height)

        # Create Picture
        label = QLabel(self)
        pixmap = QPixmap(Pictures[count])
        label.setPixmap(pixmap)
        label.move(600, 40)


        # Create textbox
        self.textbox = QLineEdit(self)
        self.textbox.move(20, 20)
        self.textbox.resize(280, 40)
        # Create AnswerTextbox
        self.textbox2 = QLineEdit(self)
        self.textbox2.move(20, 80)
        self.textbox2.resize(280, 40)

        # Create a button in the window
        self.button = QPushButton('Confirm', self)
        self.button.move(20, 150)
        self.button2 = QPushButton('Continue', self)
        self.button2.move(20, 200)

        # connect button to function on_click
        self.button.clicked.connect(self.on_click)
        self.show()

        self.button2.clicked.connect(self.on_click2)
        self.show()



    def on_click(self):
        if self.textbox.text() == "test":
            self.textbox2.setText("True")
        else:
            self.textbox2.setText("false")

    def on_click2(self):
        label = QLabel(self)
        pixmap = QPixmap('*')
        label.setPixmap(pixmap)
        label.move(900, 50)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())


alles ist als Einsteiger möglich. Es ist nur die Frage, wie lange es dauert, bis man die nötigen Vorkenntnisse erworben hat.
__deets__
User
Beiträge: 6625
Registriert: Mittwoch 14. Oktober 2015, 14:29

Samstag 4. Mai 2019, 09:25

Du musst schon das bestehende Label verändern. Und dir dazu in der Klasse eine Referenz merken. Jetzt baust du einfach ein neues Label, das ist falsch.
onur22
User
Beiträge: 19
Registriert: Freitag 28. Dezember 2018, 21:25

Montag 6. Mai 2019, 21:53

Ja, also ich muss das bestehende Label ändern. Wie schaffe ich das mit Qt?
alles ist als Einsteiger möglich. Es ist nur die Frage, wie lange es dauert, bis man die nötigen Vorkenntnisse erworben hat.
Benutzeravatar
__blackjack__
User
Beiträge: 4477
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Montag 6. Mai 2019, 22:07

Ich wiederhole das gerne noch einmal:
Und was die Anzeige angeht: Du ersetzt halt einfach das Bild. Wenn das beispielsweise in einem `QLabel` angezeigt wird, setzt Du mit `setPixmap()` ein neues Bild.
Und das solltest Du auch beim ersten mal machen. Also nicht das erste Bild anders behandeln als alle folgenden.
“Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.”
— Terry Pratchett, Jingo
Benutzeravatar
__blackjack__
User
Beiträge: 4477
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Montag 13. Mai 2019, 10:53

@onur22: Anmerkungen zum Quelltext:

`App` ist kein guter Name für eine Fensterklasse und `ex` ist kein guter Name für ein Exemplar davon. Es gibt in Qt Klassen die die Anwendung repräsentieren (`QApplication`) und deren Exemplaren man die geläufige Abkürzung `app` als Namen geben kann. Dann ist es aber sehr verwirrend wenn man einen Namen `app` hat und eine Klasse `App`, die beiden aber nichts miteinander zu tun haben, obwohl sie gleich heissen. Und was soll der Name `ex` eigentlich bedeuten?

Der Inhalt des ``if __name__ …``-Zweigs sollte in einer Funktion stehen. Sonst sind die Namen `app` und `ex` im ganzen Modul sichtbar und es besteht die Gefahr, dass man die irgendwann mal (aus versehen) irgendwo benutzt.

Der `show()`-Aufruf gehört nicht in die `__init__()` des Fensters. Ein Widget anzuzeigen gehört in den Aufgabenbereich der Stelle die das Objekt erzeugt, nicht in das Widget selbst. Der Aufruf steht auch unnötigerweise zwei mal im Code.

Eine zusätzliche `initUI()` macht keinen Sinn. Das gehört mit in die `__init__()`. Einfach die drei Zeilen – Aufruf, Leerzeile, Methodendefinition – löschen.

Die Fenstergeometrie sollte man nicht setzen. Die meisten Benutzer finden Fenster die sich nicht da öffnen wo Platz auf dem Bildschirm ist, sondern immer an der selben Stelle wo der Programmierer das toll fand, ziemlich nervig. Damit fällt die Position also schon mal weg.

Die Grösse sollte sich automatisch aus dem benötigten Platz des Inhalts ergeben. Womit wir dann beim Thema Layouts wären: Du platzierst alles pixelgenau selbst. Das macht nicht nur unnötig Arbeit, sondern sieht dann auch nur auf Deiner Kombination von Betriebsystem, Monitorauflösung, und Systemeinstellungen so aus wie bei Dir. Auf anderen Systemen, die beispielsweise eine höhere oder niedrigere Monitorauflösung haben, sind Pixel aber kleiner oder grösser.

Um diese Probleme zu umgehen und es auch einfacher zu haben wenn man etwas an der GUI verändert wodurch sich andere Widgets verschieben, verwendet man Layout-Klassen, wo man die Widgets relativ zueinander anordnet, und das Layout dann dafür sorgt das alles an seinem Platz ist, und auch genug Platz hat.

Kommentare sollten dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code tut, denn das steht da bereits als Code, sondern *warum* er das (so) tut. Sofern das nicht offensichtlich ist.

Namen schreibt man in Python klein_mit_unterstrichen. Ausnahmen Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Und wenn externe Bibliotheken die Namenskonventionen von anderen Programmiersprachen oder Projekten übernehmen, wie PyQt die von Qt/C++, dann sieht man auch mal etwas anderes. `Pictures` und `Solution` wäre aber unter beiden Konventionen falsch geschrieben, weil es keine Klassen sind.

`Solution` müsste auch in der Mehrzahl benannt werden. Zudem sollten diese Werte nicht in parallelen Listen stehen sondern Bilder und Lösungen jeweils zu einem Objekt zusammengefasst, beispielsweise als Tupel, in *einer* Liste.

Wenn man später auf die Aufgaben auch in anderen Methoden zugreifen will, muss man sie natürlich an das Objekt binden.

In der `__init__()` sollte man wie gesagt am besten kein Bild auf dem Label setzen, sondern alles vorbereiten so dass man mit der Methode die das jeweils nächste Bild setzt, eben auch das erste Bild setzen kann. Dann muss man das nur an einer Stelle programmieren.

Die supergenerischen und dann auch noch durchnummerierten Namen für Eingabeelemente, Schaltflächen, und Slots sind schlecht. Schöne Stelle an der man das besonders gut sieht ist das Verbinden von Signalen und Slots:

Code: Alles auswählen

        self.button.clicked.connect(self.on_click)
        self.button2.clicked.connect(self.on_click2)
Wenn man wissen will was da passiert, muss man sowohl bei den `Button`\s nachsehen womit die beschriftet sind, als auch bei den Slots was die machen. So sieht man an der Stelle direkt am Code was da passiert:

Code: Alles auswählen

        self.confirmation_button.clicked.connect(self.check_answer)
        self.continue_button.clicked.connect(self.go_to_next_assignment)
Wobei man die `Button`-Objekte auch gar nicht an das Fenster binden muss.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import sys

from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import (
    QApplication, QLabel, QLineEdit, QMainWindow, QPushButton, QHBoxLayout,
    QMessageBox, QVBoxLayout, QWidget,
)


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        self.assignments = iter(
            [('test2.jpg', 'spam'), ('test2.png', 'parrot')]
        )
        self.expected_answer = None
        
        self.setWindowTitle('Test')

        central_widget = QWidget(self)
        vbox = QHBoxLayout()
        
        widget = QWidget(central_widget)
        hbox = QVBoxLayout()
        self.input_edit = QLineEdit(widget)
        hbox.addWidget(self.input_edit)
        self.result_edit = QLineEdit(widget)
        hbox.addWidget(self.result_edit)
        confirmation_button = QPushButton('Confirm', widget)
        hbox.addWidget(confirmation_button)
        continue_button = QPushButton('Continue', widget)
        hbox.addWidget(continue_button)
        hbox.addStretch()
        widget.setLayout(hbox)
        vbox.addWidget(widget)
        
        self.image_label = QLabel(self)
        vbox.addWidget(self.image_label)

        central_widget.setLayout(vbox)
        self.setCentralWidget(central_widget)

        confirmation_button.clicked.connect(self.check_answer)
        continue_button.clicked.connect(self.go_to_next_assignment)
        
        self.go_to_next_assignment()

    def check_answer(self):
        self.result_edit.setText(
            str(self.input_edit.text() == self.expected_answer)
        )

    def go_to_next_assignment(self):
        try:
            image_filename, self.expected_answer = next(self.assignments)
        except StopIteration:
            QMessageBox.information(
                self,
                'Done',
                'Do whatever needs to be done here when there is no assignment'
                ' left.'
            )
        else:
            self.input_edit.setText('')
            self.result_edit.setText('')
            self.image_label.setPixmap(QPixmap(image_filename))


def main():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()
Als nächstes würde ich den Aufbau der GUI aus dem Code heraus nehmen stattdessen mit dem Qt Designer arbeiten. Die *.ui-Datei kann man dann im Programm mit dem `PyQt5.uic`-Modul zur Laufzeit laden. Damit erspart man sich viel unübersichtlichen Code, sowohl zu schreiben, als auch zu lesen.
“Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.”
— Terry Pratchett, Jingo
Benutzeravatar
__blackjack__
User
Beiträge: 4477
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Donnerstag 23. Mai 2019, 08:57

Um das noch mal zu illustrieren, die gleiche GUI nicht als Code, sondern grafisch im Designer zusammen geklickt und als `test.ui` gespeichert:

Code: Alles auswählen

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="windowTitle">
   <string>Test</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QHBoxLayout" name="horizontalLayout">
    <item>
     <widget class="QWidget" name="widget" native="true">
      <layout class="QVBoxLayout" name="verticalLayout">
       <item>
        <widget class="QLineEdit" name="input_edit"/>
       </item>
       <item>
        <widget class="QLineEdit" name="result_edit"/>
       </item>
       <item>
        <widget class="QPushButton" name="confirmation_button">
         <property name="text">
          <string>Confirm</string>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QPushButton" name="continue_button">
         <property name="text">
          <string>Continue</string>
         </property>
        </widget>
       </item>
       <item>
        <spacer name="spacer">
         <property name="orientation">
          <enum>Qt::Vertical</enum>
         </property>
         <property name="sizeHint" stdset="0">
          <size>
           <width>0</width>
           <height>10</height>
          </size>
         </property>
        </spacer>
       </item>
      </layout>
     </widget>
    </item>
    <item>
     <widget class="QLabel" name="image_label"/>
    </item>
   </layout>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>
Dann könnte der Code so aussehen:

Code: Alles auswählen

#!/usr/bin/env python3
import sys

from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QApplication, QMessageBox
from PyQt5.uic import loadUi


class MainWindow:

    def __init__(self):
        self.assignments = iter(
            [('test2.jpg', 'spam'), ('test2.png', 'parrot')]
        )
        self.expected_answer = None
        
        self.ui = loadUi('test.ui')
        self.ui.confirmation_button.clicked.connect(self.check_answer)
        self.ui.continue_button.clicked.connect(self.go_to_next_assignment)
        
        self.go_to_next_assignment()

    def check_answer(self):
        self.ui.result_edit.setText(
            str(self.ui.input_edit.text() == self.expected_answer)
        )

    def go_to_next_assignment(self):
        try:
            image_filename, self.expected_answer = next(self.assignments)
        except StopIteration:
            QMessageBox.information(
                self.ui,
                'Done',
                'Do whatever needs to be done here when there is no assignment'
                ' left.'
            )
        else:
            self.ui.input_edit.setText('')
            self.ui.result_edit.setText('')
            self.ui.image_label.setPixmap(QPixmap(image_filename))


def main():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.ui.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()
“Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.”
— Terry Pratchett, Jingo
Antworten