zweites Fenster schließen

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
logithack
User
Beiträge: 2
Registriert: Montag 20. März 2017, 22:04

Hallo,

ich hab mir mit Qt Creator zwei .ui-Dateien erstellt und diese dann mit puic5 in Python-Dateien übersetzt. Die eine ist mein Hauptfenster, die andere ist ein kleiner Dialog mit einem "Submit"- und einem "Cancel"-Button. Wie kann ich das zweite Fenster beim Klick auf "Cancel" schließen, sodass das Programm wieder zum ersten zurückkehrt? Ich wäre dankbar, wenn mir jemand ein kleines Beispiel zeigen könnte, da ich mit sämtlichen Methoden, die ich gefunden hab, bisher gescheitert bin.

Vielen Dank für eure Hilfe!

Gruß,

logithack
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@logithack:
Du erstellst den Dialog innerhalb einer Methode des Hauptfensters und führst `exec_()` aus, was Dir auch gleich den Wert der Buttonleiste zurückgibt:

Code: Alles auswählen

def someMethod(self):
    ...
    dlg = SomeDialog()
    if dlg.exec_():
        # submit pressed
    ...
logithack
User
Beiträge: 2
Registriert: Montag 20. März 2017, 22:04

Danke für deine Hilfe, jerch!

Ich hab deinen Vorschlag mal in einer kleinen Testanwendung umgesetzt und es funktioniert!

Die Testanwendung zeigt ein Hauptfenster mit einem "Add name"- und einem "Exit"-Button. Klickt man auf "Add name", öffnet sich ein Dialog, in den man einen Namen eintragen kann. Wenn auf "Submit" geklickt wird, wird in einem ausgegrauten Edit-Feld im Hauptfenster die Liste der eingegebenen Namen aktualisiert. Zum Debuggen hab ich noch für jede Aktion eine Ausgabe auf der Konsole erstellt.

Ich hab das Ganze wie folgt umgesetzt:

mainwindow.py (mit Qt Designer erstellt):

Code: Alles auswählen

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created by: PyQt5 UI code generator 5.8.1
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(294, 124)
        self.centralWidget = QtWidgets.QWidget(MainWindow)
        self.centralWidget.setObjectName("centralWidget")
        self.gridLayout_3 = QtWidgets.QGridLayout(self.centralWidget)
        self.gridLayout_3.setContentsMargins(11, 11, 11, 11)
        self.gridLayout_3.setSpacing(6)
        self.gridLayout_3.setObjectName("gridLayout_3")
        self.gridLayout = QtWidgets.QGridLayout()
        self.gridLayout.setContentsMargins(11, 11, 11, 11)
        self.gridLayout.setSpacing(6)
        self.gridLayout.setObjectName("gridLayout")
        self.MainWindow_Label_Name = QtWidgets.QLabel(self.centralWidget)
        self.MainWindow_Label_Name.setObjectName("MainWindow_Label_Name")
        self.gridLayout.addWidget(self.MainWindow_Label_Name, 0, 0, 1, 1)
        self.MainWindow_Edit_Name = QtWidgets.QLineEdit(self.centralWidget)
        self.MainWindow_Edit_Name.setEnabled(False)
        self.MainWindow_Edit_Name.setObjectName("MainWindow_Edit_Name")
        self.gridLayout.addWidget(self.MainWindow_Edit_Name, 0, 1, 1, 1)
        self.gridLayout_3.addLayout(self.gridLayout, 0, 0, 1, 1)
        self.gridLayout_2 = QtWidgets.QGridLayout()
        self.gridLayout_2.setContentsMargins(11, 11, 11, 11)
        self.gridLayout_2.setSpacing(6)
        self.gridLayout_2.setObjectName("gridLayout_2")
        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.gridLayout_2.addItem(spacerItem, 0, 3, 1, 1)
        self.MainWindow_Button_AddName = QtWidgets.QPushButton(self.centralWidget)
        self.MainWindow_Button_AddName.setObjectName("MainWindow_Button_AddName")
        self.gridLayout_2.addWidget(self.MainWindow_Button_AddName, 0, 1, 1, 1)
        spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.gridLayout_2.addItem(spacerItem1, 0, 0, 1, 1)
        self.MainWindow_Button_Exit = QtWidgets.QPushButton(self.centralWidget)
        self.MainWindow_Button_Exit.setObjectName("MainWindow_Button_Exit")
        self.gridLayout_2.addWidget(self.MainWindow_Button_Exit, 0, 2, 1, 1)
        self.gridLayout_3.addLayout(self.gridLayout_2, 1, 0, 1, 1)
        MainWindow.setCentralWidget(self.centralWidget)
        self.menuBar = QtWidgets.QMenuBar(MainWindow)
        self.menuBar.setGeometry(QtCore.QRect(0, 0, 294, 21))
        self.menuBar.setObjectName("menuBar")
        MainWindow.setMenuBar(self.menuBar)
        self.mainToolBar = QtWidgets.QToolBar(MainWindow)
        self.mainToolBar.setObjectName("mainToolBar")
        MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.mainToolBar)
        self.statusBar = QtWidgets.QStatusBar(MainWindow)
        self.statusBar.setObjectName("statusBar")
        MainWindow.setStatusBar(self.statusBar)

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

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.MainWindow_Label_Name.setText(_translate("MainWindow", "Name:"))
        self.MainWindow_Button_AddName.setText(_translate("MainWindow", "Add name"))
        self.MainWindow_Button_Exit.setText(_translate("MainWindow", "Exit"))
addnamedialog.py (mit Qt Designer erstellt):

Code: Alles auswählen

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'addnamedialog.ui'
#
# Created by: PyQt5 UI code generator 5.8.1
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_AddNameDialog(object):
    def setupUi(self, AddNameDialog):
        AddNameDialog.setObjectName("AddNameDialog")
        AddNameDialog.resize(230, 71)
        self.gridLayout_3 = QtWidgets.QGridLayout(AddNameDialog)
        self.gridLayout_3.setContentsMargins(11, 11, 11, 11)
        self.gridLayout_3.setSpacing(6)
        self.gridLayout_3.setObjectName("gridLayout_3")
        self.gridLayout = QtWidgets.QGridLayout()
        self.gridLayout.setContentsMargins(11, 11, 11, 11)
        self.gridLayout.setSpacing(6)
        self.gridLayout.setObjectName("gridLayout")
        self.AddNameDialog_Label_Name = QtWidgets.QLabel(AddNameDialog)
        self.AddNameDialog_Label_Name.setObjectName("AddNameDialog_Label_Name")
        self.gridLayout.addWidget(self.AddNameDialog_Label_Name, 0, 0, 1, 1)
        self.AddNameDialog_Edit_Name = QtWidgets.QLineEdit(AddNameDialog)
        self.AddNameDialog_Edit_Name.setObjectName("AddNameDialog_Edit_Name")
        self.gridLayout.addWidget(self.AddNameDialog_Edit_Name, 0, 1, 1, 1)
        self.gridLayout_3.addLayout(self.gridLayout, 0, 0, 1, 1)
        self.gridLayout_2 = QtWidgets.QGridLayout()
        self.gridLayout_2.setContentsMargins(11, 11, 11, 11)
        self.gridLayout_2.setSpacing(6)
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.AddNameDialog_Button_Cancel = QtWidgets.QPushButton(AddNameDialog)
        self.AddNameDialog_Button_Cancel.setObjectName("AddNameDialog_Button_Cancel")
        self.gridLayout_2.addWidget(self.AddNameDialog_Button_Cancel, 0, 2, 1, 1)
        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.gridLayout_2.addItem(spacerItem, 0, 0, 1, 1)
        self.AddNameDialog_Button_Submit = QtWidgets.QPushButton(AddNameDialog)
        self.AddNameDialog_Button_Submit.setObjectName("AddNameDialog_Button_Submit")
        self.gridLayout_2.addWidget(self.AddNameDialog_Button_Submit, 0, 1, 1, 1)
        spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.gridLayout_2.addItem(spacerItem1, 0, 3, 1, 1)
        self.gridLayout_3.addLayout(self.gridLayout_2, 1, 0, 1, 1)

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

    def retranslateUi(self, AddNameDialog):
        _translate = QtCore.QCoreApplication.translate
        AddNameDialog.setWindowTitle(_translate("AddNameDialog", "AddNameWindow"))
        self.AddNameDialog_Label_Name.setText(_translate("AddNameDialog", "Name:"))
        self.AddNameDialog_Button_Cancel.setText(_translate("AddNameDialog", "Cancel"))
        self.AddNameDialog_Button_Submit.setText(_translate("AddNameDialog", "Submit"))
ui.py:

Code: Alles auswählen

from PyQt5 import QtWidgets
from mainwindow import Ui_MainWindow
from addnamedialog import Ui_AddNameDialog

import sys


class MainWindow(Ui_MainWindow, QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)

        # Create AddNameWindow
        self.AddNameDialog = AddNameDialog()

        # Connect buttons to functions
        self.MainWindow_Button_AddName.clicked.connect(self.add_name)
        self.MainWindow_Button_Exit.clicked.connect(self.exit_program)

        # Create list of names
        self.names = []

    def add_name(self):
        print(self.__class__.__name__ + ": Add name pressed.")
        if self.AddNameDialog.exec_():
            print(self.__class__.__name__ + ": Submit pressed in " +
                  self.AddNameDialog.__class__.__name__)
            print("Text entered in " + self.AddNameDialog.__class__.__name__ + ": " +
                  self.AddNameDialog.AddNameDialog_Edit_Name.text())
            self.names.append(self.AddNameDialog.AddNameDialog_Edit_Name.text())
            self.fill_in_names()
        else:
            print(self.__class__.__name__ + ": Cancel pressed in " +
                  self.AddNameDialog.__class__.__name__)
        self.AddNameDialog.AddNameDialog_Edit_Name.clear()

    def fill_in_names(self):
        name_str = ""
        for name in self.names:
            name_str += name + ", "
        self.MainWindow_Edit_Name.setText(name_str)

    def exit_program(self):
        print(self.__class__.__name__ + ": Exit pressed.")
        sys.exit(0)


class AddNameDialog(Ui_AddNameDialog, QtWidgets.QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)

        # Connect buttons to functions
        self.AddNameDialog_Button_Submit.clicked.connect(self.submit)
        self.AddNameDialog_Button_Cancel.clicked.connect(self.cancel)

    def submit(self):
        print(self.__class__.__name__ + ": Submit pressed.")
        self.accept()

    def cancel(self):
        print(self.__class__.__name__ + ": Cancel pressed.")
        self.reject()
main.py:

Code: Alles auswählen

from PyQt5.QtWidgets import *
from ui import *

import sys


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())
Nun hab ich noch ein paar Fragen:
1. Ist die Art und Weise, wie ich oben alles umgesetzt hab, in Ordnung? Wenn ihr Vorschläge habt, wie man das Ganze besser umsetzen kann, oder wie man Teile anders schreiben könnte, damit sie eher einem pythonic-konformen Ansatz entsprechen, würde ich mich freuen, wenn ihr mir diese mitteilt!
2. Was ist der Unterschied zwischen "sys.exit(app.exec_())" und "app.exec_()"?
3. Wieso wird im Konstruktor von MainWindow "super().__init__(parent)" benötigt, wenn beim Erstellen kein parent übergeben wird?
4. Was passiert bei "self.setupUi(self)"?
5. Wenn ich in AddNameDialog in der Methode "cancel()" anstelle von "self.close()" den Befehl "self.cancel()" einfüge und im Programm im AddNameDialog-Fenster auf "Cancel" klicke, wird auf der Konsole haufenweise "AddNameDialog: Cancel pressed." ausgegeben und das Programm crashed. Was passiert da genau?

Vielen Dank schon mal für eure Hilfe!

Gruß,

logithack
Zuletzt geändert von Anonymous am Mittwoch 29. März 2017, 11:20, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@logithack: Die generierten Python-Dateien würde man sich heutzutage sparen und das `uic`-Modul verwenden um die *.ui-Dateien zur Laufzeit zu laden.

Namen wie `MainWindow_Button_AddName` sind redundant und halten sich an keine Regeln, nicht mal die von Qt. Das `MainWindow` ist überflüssig, weil das schon durch die Klasse beziehungsweise die UI-Datei klar ist, das es sich um das Hauptfenster handelt. Namen die mit Grossbuchstaben anfangen sind üblicherweise Klassen. Unterstriche und Gross-/Kleinschreibung in einem Namen zu mischen ist auch unüblich. Ich würde das `addNameButton` oder `add_name_button` nennen. Beispiel wo man schön sieht wie lang und unhandlich das wird sind ja so sachen wie:

Code: Alles auswählen

self.AddNameDialog.AddNameDialog_Edit_Name.clear()

# ->

self.add_name_dialog.name_edit.clear()
In `fill_in_names()` setzt Du die Namen sehr umständlich und ineffizient zusammen. Dafür gibt es die `join()`-Methode auf Zeichenketten:

Code: Alles auswählen

    def fill_in_names(self):
        name_str = ""
        for name in self.names:
            name_str += name + ", "
        self.MainWindow_Edit_Name.setText(name_str)

# ->

    def fill_in_names(self):
        self.name_edit.setText(', '.join(self.names))
Mit `sys.exit()` sollte man vorsichtig und sehr sparsam umgehen. Die saubere Methode wäre hier einfach das Fenster zu schliessen, denn das normale Verhalten von `QApplication` ist es die Anwendung zu beenden wenn kein Fenster ohne Parent mehr offen ist. So wie Du das beendest, können keine eventuellen Aufräumarbeiten am Ende mehr ausgeführt werden die Beispielsweise über das `lastWindowClosed()`-Signal der Anwendung verbunden sind, und das `sys.exit()` in der `main.py` wird nie ausgeführt. *Das* ist ein `sys.exit()` an der richtigen Stelle.

Die beiden Methoden in `AddNameDialog` existieren bloss wegen der Debug-`print()`-Ausgaben, letztlich sollten diese ganzen `print()`\s und damit auch diese beiden Methoden verschwinden. Wenn Du vorhast solche Ausgaben grundsätzlich im Programm zu behalten, würde ich von `print()` auf `logging` umsteigen. Dann hat man mehr Kontrolle und Möglichkeiten bei den Ausgaben und kann dieses dauernde ``self.__class__.__name__`` auch an *einer* Stelle lösen.

Zu den Fragen:

Ad 2. Das eine gibt den Rückgabewert von `exec_()` als Exit-Code an das Betriebssystem zurück, das andere nicht. Programm die Dein Programm aufrufen können diesen Wert anschauen und etwas damit machen. Üblich ist ein Wert 0 für „alles okay“ und ≠0 um irgendwelche anderen Umstände anzuzeigen.

Ad 3. Weil man eines übergeben könnte. Du kannst das natürlich auch weglassen, dann musst Du aber auch das `parent`-Argument in `MainWindow` weglassen.

Ad 4. Das siehst Du doch im Code. Die Methode(n) stehen da doch‽

Ad 5. Wenn in der `cancel()`-Methode `self.cancel()` aufgerufen wird, dann ruft die `cancel()`-Methode die `cancel()`-Methode auf, die dann die `cancel()`-Methode aufruft, die dann die `cancel()`-Methode aufruft, die dann die `cancel()`-Methode aufruft, die dann die `cancel()`-Methode aufruft, die dann die `cancel()`-Methode aufruft, die dann die `cancel()`-Methode aufruft, die dann die `cancel()`-Methode aufruft, die dann die `cancel()`-Methode aufruft, … bis die maximale Rekursiontiefe erreicht ist und der Unsinn beendet wird.
Antworten