[PyQt] CloseEvent einfügen bei einem von Qt Designer erstell

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
ToTTy
User
Beiträge: 12
Registriert: Mittwoch 14. Oktober 2015, 11:30

Hallo Leute,

Ich habe mir mithilfe von Qt Designer eine nette Gui erstellt und diese auch schon fleißig bearbeitet, nun muss ich aber vor dem Schließen der Gui eine Aktion ausführen.

Habe dazu schon etliche Foren durchgelesen und gesehen dass es bei einem QMainWindow problemlos klappt, aber ich muss das jetzt auf den von Qt Designer erstellten Programmcode übertragen.

Grobe Code-Struktur:

Code: Alles auswählen

from PyQt4 import QtCore, QtGui
import time
import sys



class Ui_Dialog(object):
    def setupUi(self, Dialog):
        #Code...
        
        self.retranslateUi(Dialog)

    def retranslateUi(self, Dialog):
        #Code...



if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    Dialog = QtGui.QDialog()
    ui = Ui_Dialog()
    ui.setupUi(Dialog)
    Dialog.show()
    sys.exit(app.exec_())
Wo genau muss ich das "def closeEvent" eingefügt werden?
Habe schon etliche Sachen ausprobiert - erfolglos.

Hoffe ihr könnt mir helfen.
MfG ToTTy
BlackJack

@ToTTy: In dem generierten Quelltext ändert man überhaupt nichts, den fasst man nicht an. Am besten generiert man gar nicht erst Quelltext sondern lädt zur Laufzeit die *.ui-Datei:

Code: Alles auswählen

import sys
from PyQt4.QtGui import QApplication, QDialog
from PyQt4.uic import loadUi


class Dialog(QDialog):

    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
        loadUi('test.ui', self)

    def closeEvent(self, event):
        # Do something...
        event.accept()  # or .ignore()


def main():
    application = QApplication(sys.argv)
    dialog = Dialog()
    dialog.show()
    sys.exit(application.exec_())


if __name__ == '__main__':
    main()
ToTTy
User
Beiträge: 12
Registriert: Mittwoch 14. Oktober 2015, 11:30

Danke erstmal für die schnelle Antwort!

Habe den Programmcode jetzt so umgeschrieben und es funktioniert soweit alles.
Nur, wo verändere ich jetzt den generierten Code so wie ich ihn hatte? (z.B. wenn ich den Text eines labels ändern will etc.)
Und wenn ich die Gui Schließe wird das closeEvent nicht aufgerufen. Woran kann das liegen?:

Code: Alles auswählen

from PyQt4 import QtCore, QtGui, uic
import time
import sys 

class Dialog(QtGui.QDialog):
    def __init__(self, parent = None):
        QtGui.QDialog.__init__(self, parent)
        uic.loadUi('test.ui', self)
        
    def closeEvent(self, event):
        print "Program beendet"


if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    Dialog = Dialog()
    Dialog.show()
    sys.exit(app.exec_())
MfG ToTTy
BlackJack

@ToTTy: Das kann ich nicht navollziehen: Wenn ich Deinen Code ausführe und das Fenster schliesse, dann wird auf der Konsole von der aus ich das Programm gestartet habe der Text von dem ``print`` ausgegeben.

Wenn Du den Text eines Labels ändern willst dann greifst Du das entsprechend über den Namen drauf zu. Wenn das 'label' heisst, dann mit ``self.label.setText('Hallo')`` innerhalb von Methoden vom `Dialog`-Exemplar.
ToTTy
User
Beiträge: 12
Registriert: Mittwoch 14. Oktober 2015, 11:30

Habe das Problem das ich hatte herausgefunden,
Ich habe einen "Exit" button im Programm welcher "Dialog.reject" ausführt und die GUI schließt. Dabei wird aber das closeEvent ignoriert (mit dem X oben geht es wunderbar). Gibt es da eine andere Funktion mit der die GUI geschlossen werden kann und das closeEvent nicht ignoriert wird?

Zum label Thema:
Habe ich es richtig verstanden, dass ich in meiner class eine neue Funktion definiere die die gewünschten Änderungen vornimmt und dass ich diese einfach in der __init__ funktion aufrufe?
BlackJack

@ToTTy: Was willst Du denn da überhaupt machen? Muss das über so etwas ”low-level” wie diesem Ereignis passieren? Um zu erfahren wenn ein Benutzer mit einem Dialog ”fertig” ist, würde man sich eher mit dem `finsihed`-Signal vom Dialog verbinden.
ToTTy
User
Beiträge: 12
Registriert: Mittwoch 14. Oktober 2015, 11:30

Ich habe im Qt Desginer ein Exit Button erstellt welcher das Programm beenden soll (schließen soll).
Dieser wird wie folgt verbunden: (Von QtDesigner Generiert)

Code: Alles auswählen

QtCore.QObject.connect(self.Button_Exit, QtCore.SIGNAL(_fromUtf8("clicked()")), Dialog.reject)
So beendet sich zwar die Oberfläche aber, wie bereits gesagt, das closeEvent wird nicht ausgeführt, nur wenn ich die GUI über das X Symbol beende.
BlackJack

@ToTTy: Das war mir schon klar, die Frage war/ist was Du beim CloseEvent machen willst bzw. warum Du ausgerechnet das verwenden willst, anstelle zum Beispiel des `finished`-Signals von Dialogen. Und dann sollte man nicht `closeEvent()` überschreiben weil das bei Dialogen schon so implementiert ist, dass `reject()` aufgerufen wird wenn man das Dialogfenster über die Fensterverwaltung schliesst.

Die Verbindungszeile sieht falsch aus, denn man verbindet nicht mit einer Methode auf einer Klasse sondern mit einer Methode auf einem Exemplar von `Dialog`. Oder hast Du *das* etwa `Dialog` genannt? Ich sehe gerade Du hast — solltest Du nicht, denn gerade in diesem Fall ist das extrem verwirrend und Du überschreibst damit auch noch die Dialog-Klasse weil das auf Modulebene gemacht wird. Auch das sollte man nicht machen. Also weder so etwas auf Modulebene definieren, noch dann auch noch von Methoden aus darauf zugreifen. Werte sollten Methoden und Funktionen als Argumente betreten und nicht irgendwo magisch aus der ”Umgebung” kommen. Zumal das hier auch noch völlig sinnlos umständlich ist denn `Dialog` ist in diesem Fall das selbe wie `self`. Die Zeile müsste also so lauten:

Code: Alles auswählen

QtCore.QObject.connect(self.Button_Exit, QtCore.SIGNAL(_fromUtf8("clicked()")), self.reject)
Allerdings würde ich diese umständliche Art nicht per Hand schreiben. Das geht einfacher (und etwas sicherer weil man sofort beim Verbinden eine Ausnahme bekommt falls es das Signal nicht gibt):

Code: Alles auswählen

self.Button_Exit.clicked.connect(self.reject)
Jetzt muss man der Schaltfläche nur noch einen „pythonischeren“ Namen geben, denn Namen die mit Grossbuchstaben anfangen sind per Konvention für Klassen vorgesehen.
ToTTy
User
Beiträge: 12
Registriert: Mittwoch 14. Oktober 2015, 11:30

Zu dem CloseEvent,
Warum ich diese Funktion verwende? Weil ich beim beenden eines Programmes noch eine aktion ausführen wollte (ein print) und mir eben nur das CloseEvent bekannt war.
Was genau macht und wann wird jetzt das finished oder reject aufgerufen? (Bin noch nicht so fit im Thema PyQt :shock:)

Zu dem Code,
Dieser wurde so von Qt Designer erstellt und ist, so wie bei allen generierten Codes, natürlich nicht das beste und performanteste.
BlackJack

@ToTTy: Das `finished`-Signal wird gesendet wenn der Dialog mit `accept()`, `reject()`, oder `done()` abgeschlossen wird. Und `closeEvent()` ist von `QDialog` sinnvollerweise bereits so üerschrieben dass es `reject()` aufruft, also auch zu einem `finished`-Signal führt. Das steht aber auch alles in der Dokumentation. Da gibt's zu `QDialog` ja eine recht umfangreiche Beschreibung wie man den verwendet.

Code: Alles auswählen

from PyQt4 import QtGui, uic
import sys

 
class Dialog(QtGui.QDialog):

    def __init__(self, parent=None):
        QtGui.QDialog.__init__(self, parent)
        uic.loadUi('test.ui', self)
        self.label.setText('Hallo')
        self.exit_button.clicked.connect(self.reject)
        self.finished.connect(self.on_finished)

    def on_finished(self, result):
        print 'Dialog fertig', result
 

def main():
    application = QtGui.QApplication(sys.argv)
    dialog = Dialog()
    dialog.show()
    sys.exit(application.exec_())
Falls die Benutzeroberfläche ausschliesslich aus diesem Dialog besteht, könnte man den eventuell auch modal ausführen und die Aktion dann einfach durchführen wenn die `exec_()`-Methode zurückkehrt. Dann braucht man dafür gar keine Ereignisse oder Signale behandeln.

Automatisch generierter Quelltext ist halt in den meisten Fällen nicht dafür gedacht, und oft auch nicht geeignet, um leicht gelesen zu werden. Bei Python hat man oft noch den Vorteil das die Syntax und die Ausdrucksstärke generierten Quelltext leichter lesbar hält als in anderen Sprachen, aber schön ist was anderes. :-) Du konntest das ja auch nur kopieren weil Du den Quelltext mal erstellen lassen hast, was man heute normalerweise ja nicht mehr tut. Und manuell zu schreiben ist die ”neue” Art der Verbindung von Signalen zu Slots sicherlich.
ToTTy
User
Beiträge: 12
Registriert: Mittwoch 14. Oktober 2015, 11:30

Vielen Dank!
Dies hat mein Problem mit dem "Exit" Button gelöst.
Nur die on_finished Funktion wird bei mir 2x ausgeführt (2x print in die console)
Und wenn ich das Programm mit X beende wird Nur das closeEvent ausgeführt und wird nicht in ein finished signal übergeführt. (Kann dass geändert werden?)

EDIT:
on_finished wird deswegen 2x ausgeführt weil ich 1x in meiner .ui Datei den Exit Button mit reject verbunden habe und 1x extra (von deinem Code übernommen)
BlackJack

@ToTTy: Ähm, die `closeEvent()`-Methode wird von `QDialog` überschrieben — das hatte ich doch bereits gesagt. Wenn Du die jetzt nochmal überschreibst, dann wird die von `QDialog` natürlich nicht mehr aufgerufen sondern Deine. Also musst Du mindestens die Methode der Basisklasse noch mal in Deiner Methode aufrufen oder sie halt gar nicht erst überschreiben. Das hatte ich genau deswegen ja aus meinem Beispiel wieder rausgenommen.

Das das `finished`-Signal zweimal ausgelöst wird sollte eigentlich nicht passieren. Das passiert eigentlich nur wenn `accept()`, `reject()`, und/oder `done()` mehr als einmal aufgerufen wird. Konstruiertes Beispiel:

Code: Alles auswählen

class Dialog(QtGui.QDialog):
 
    def __init__(self, parent=None):
        QtGui.QDialog.__init__(self, parent)
        uic.loadUi('test.ui', self)
        self.label.setText('Hallo')
        self.exit_button.clicked.connect(self.on_exit)
        self.finished.connect(self.on_finished)
 
    def on_exit(self):
        self.reject()
        self.reject()

    def on_finished(self, result):
        print 'Dialog fertig', result
Hier wird `on_finished()` zweimal aufgerufen wenn man die Schaltfläche drückt.
ToTTy
User
Beiträge: 12
Registriert: Mittwoch 14. Oktober 2015, 11:30

Das war ja genau die Frage, wie ich dieses CloseEvent wider überschreiben kann und es auf ein reject zurückführen kann.
BlackJack

@ToTTy: Warum willst Du denn `closeEvent()` überschreiben? Es gibt doch das `finished`-Signal das man verwenden kann‽

Ansonsten musst Du halt die Methode der Basisklasse aufrufen, wie man das so macht wenn man eine Methode durch überschreiben erweitern will statt sie komplett zu ersetzen.
ToTTy
User
Beiträge: 12
Registriert: Mittwoch 14. Oktober 2015, 11:30

Ich glaube ich verstehe dich da jetzt nicht richtig.
Mein Exit Button führt ein jecet() aus welches in ein finished signal Überführt, welches ich "abgreifen" kann um vorm beenden noch eine aktion ausführen zu können ( Mit self.finished.connect(self.on_finished) ) --> Funktioniert

So aber wenn ich jetzt das Programm mit dem X (oben am Fenster) beende wird diese connection nicht ausgeführt (sprich self.on_finished nicht ausgeführt) sondern das closeEvent() wird ausgeführt. --> Ich möchte hier aber auch, wenn das Programm so oder anders beendet wird, dass das Programm vorher nochmal in meine Funktion geht (on_finished)
BlackJack

@ToTTy: Ich verstehe in der Tat nicht wo das Problem liegt, denn ich sehe da keins. Wenn ich in meinem letzten Quelltextbeispiel das 'X' zum schliessen klicke dann wird bei mir `finished` gesendet, denn `QDialog.closeEvent()` ruft das auf.

Hast Du noch eine eigene `closeEvent()`-Methode in der Klasse? Falls ja, warum? Weg damit! Oder ruf dort zusätzlich zu dem was Du da machst die Methode der Basisklasse auf, damit deren Verhalten nicht verloren geht.
ToTTy
User
Beiträge: 12
Registriert: Mittwoch 14. Oktober 2015, 11:30

Tatsächlich, das Problem lag an der closeEvent() Methode in der Klasse welche ich aus Testzwecken überschrieben habe.
Diese gelöscht und schon funktioniert alles so, wie es soll 8)

Abgesehen dass die Autokomplettierung nicht mehr Funktioniert, Funktioniert jetzt alles nach Vorstellung :)
Antworten