Öffne zweite Form auch als Unterfenster (MDI)

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute, ich werde hier zum besseren Verständnis ein paar kleine Code-Schnippsel vorstellen, und dann mein Problem schildern.

Modul: mdi.py

Code: Alles auswählen


import os
import sys
from ..modules_ui.ui_pp_test import Test_Window

class Mdi_Main(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)

        self.getPath_mdi = os.path.join(os.path.abspath("."), 'files', "qt_ui", 'pp_mdi.ui')
        self.ui_TestMainWorkSpace = loadUi(self.getPath_mdi, self)

        self.workspace = QWorkspace()
        self.workspace.setScrollBarsEnabled(True)
        self.setCentralWidget(self.workspace)

        self.ui_TestMainWorkSpace.actionTest.triggered.connect(self.show_test_form)

    def show_test_form(self):
        test_form = Test_Window()
        self.workspace.addWindow(test_form)
        test_form.showMaximized()

Hier sehen wir, dass die *.ui-Datei dynamisch geladen wird. Auf der UI ist eine Menuleiste, mit MenuItems. Beim Klick auf eines der Items (actionTest) wird die show_test_form-Funktion aufgerufen. Innerhalb dieser Funktion wird test_form zum Inhalt der addWindow()-Methode. Alles super.

Modul: test.py

Code: Alles auswählen

import os
import sys

class Test_Window(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        self.getPath_test_ui = os.path.join(os.path.abspath("."), 'files', "qt_ui", 'pp_test.ui')
        self.ui_pp_test = loadUi(self.getPath_test_ui, self)

        self.ui_pp_test.pushButtonOpen.clicked.connect(self.open_second_form)

    def open_second_form(self):
        pass
Und hier komme ich rein gedanklich ins Stocken. Die erste Form konnte ich problemlos in das QworkSpace-Widget laden. Auf dieser UI ist ein Button. Mit einem Klick soll das zu öffnende Fenster in die QWorkSpace geladen werden. Aber wie lade ich jetzt die zweite Form auch in dieses Widget? Mein erster Gedanke war, die mdi zu importieren, damit ich sagen kann "lade das Fenster dort in die workspace des mdi". Aber den Gedanke habe ich gleich verworfen. Sonst wäre es am Ende ein KuddelMuddel, wenn man bedenkt, dass man im Verlauf mehrere Formen bekommt, die auch über diesen Weg in die QWorkspace müssen.

Jetzt frage ich mich, was ich nun mit der open_second_form-Funktion anstellen soll?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hmhmh, hat keiner eine Idee? Oder ist die Frage zu banal bzw. zu trivial? Ich jedenfalls bräuchte da einen Denkanstoß.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Ohne Deine Beispiele wirklich durchleuchtet zu haben: Müsste nicht irgend wo in Deinem Code mal ein ``addSubWindow`` auftauchen? Ohne das weiß das ``QMdiArea`` ja gar nichts davon, dass ein Kind hinzugefügt werden soll...
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Hyperion:

Wie du siehst, hat mein mdi-Modul ein QWorkspace. Lassen wir mal die Tatsache beiseite, dass dieses Widget veraltet ist. Die erste Form, die vom mdi aus aufgerufen wird, wird ja auch durch die addWindow()-Methode zum Inhalt des QWorkspaces. Die erste Form kriege ich auch untergebracht. Auf der ersten nun geöffneten Form ist ein Button. Klicke ich drauf, soll die zweite Form geöffnet und in die QWorkspace hinzugefügt werden.

Woran scheitere ich Gedanklich? Wie kriege ich der zweiten Form beigebracht, dass das mdi ein QWorkspace hat? Das erste Fenster, welches im QWorkspace ist ruft nun ein weiteres Fenster auf, jedoch weißt das zweite Fenster nichts über diesen QWorkspace. Wie also komme ich dazu, dass zweite Fenster auch dort hinzupacken? Denke ich hier zu umständlich?

Wie gesagt, mein erster Gedanke war, mdi in das zweite Fenster zu importieren, damit das zweite Fenster nun weißt "Ah ha, mdi hat ein QWorkspace, und dort muss ich rein". Ich habs mal ganz plakativ formuliert.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Sophus hat geschrieben: Woran scheitere ich Gedanklich? Wie kriege ich der zweiten Form beigebracht, dass das mdi ein QWorkspace hat? Das erste Fenster, welches im QWorkspace ist ruft nun ein weiteres Fenster auf, jedoch weißt das zweite Fenster nichts über diesen QWorkspace. Wie also komme ich dazu, dass zweite Fenster auch dort hinzupacken? Denke ich hier zu umständlich?

Wie gesagt, mein erster Gedanke war, mdi in das zweite Fenster zu importieren, damit das zweite Fenster nun weißt "Ah ha, mdi hat ein QWorkspace, und dort muss ich rein". Ich habs mal ganz plakativ formuliert.
Ah... also "importieren" ist hier natürlich die falsche Wortwahl. "Importieren" bezieht sich in der Python Nomenklatur immer auf *Module*, nicht auf *Objekte*. Das führt einen auch die falsche Spur...

Dein erster Gedanke ist doch richtig: Wenn Du ein Objekt benötigst, dann muss es eben erreichbar sein. Mit anderen Worten: Irgend wie muss das neue Window auch per ``addWindow`` an das Workspace-Objekt angefügt werden.

Man kann nun also das Workspace-Objekt an jedes neue ChildWidget übergeben, oder aber umgekehrt die Aktion im ChildWidget an einen Slot im MainWindow binden, welcher dann die Registrierung vornimmt.

Wenn man das Workspace-Objekt häufiger braucht in den ChildWidgets, dann spricht einiges für diesen Ansatz. Ohne dieses Faktum würde ich aus dem Bauch heraus die zweite Variante wählen, denn dann bleibt die Registrierung schön *zentral*.

Ohne es beurteilen zu können: Wenn Du doch weißt, dass die Klasse ``QWorkspace`` veraltet ist, wieso nimmst Du sie dann :K
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hyperion hat geschrieben:Ah... also "importieren" ist hier natürlich die falsche Wortwahl. "Importieren" bezieht sich in der Python Nomenklatur immer auf *Module*, nicht auf *Objekte*. Das führt einen auch die falsche Spur...
Greifen wir mal meinen oberen Code nochmal auf. test.py ist das erste Window, was von mdi aus aufgerufen wurde, und nun im QWorkspace ist. Alles super. Jetzt klicke ich auf den Button. Ach so, und vorher muss ich ja noch fein importieren. pass auf

Modul: test.py

Code: Alles auswählen

import os
import sys

from ..modules_ui.ui_pp_mdi import Mdi_Window
from ..modules_ui.ui_pp_mdi import Second_Window 
class Test_Window(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
 
        self.getPath_test_ui = os.path.join(os.path.abspath("."), 'files', "qt_ui", 'pp_test.ui')
        self.ui_pp_test = loadUi(self.getPath_test_ui, self)
 
        self.ui_pp_test.pushButtonOpen.clicked.connect(self.open_second_form)
 
    def open_second_form(self):
        mdi = Mdi_Window()
        second_form = Second_Window()
        ui_TestMainWorkSpace.workspace.addWindow(second_form)
Dieser Code ist ungetestet. Aber ich denke so wird es nicht klappen? Und das war mein erster Gedanke. Ich importiere erst einmal die mdi im Namensraum, und in der open_second_form-Funktion versuche ich workspace bekannt zu geben, damit die zweite Form (second_form) dort hinein laden kann. Aber wie gesagt, ist ungetestet, und ich wollte dir meinen ersten Gedanken präsentieren. ach ürbigens, Mdi_Window wird doch importiert. Was ich an meiner Wortwahl falsch?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Lös Dich mal davon, daß du die Gui Beschreibung aus einer Datei lädst. Das spielt beim eigentlichen Problem nämlich keine Rolle!

Du musst doch irgendwie Objekte an andere übergeben. Das löst man idR. nicht über Importe, es sei denn bei Singletons.

Ich verstehe gar nicht, wo Du jetzt noch ein Problem hast?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Hyperion: Ich habe über deine Hinweise nachgedacht. Ich präsentiere dir mal einen kleinen Code-Ausschnitt, damit du zum einen sehen kannst, wie ich es umgesetzt habe, und das ich zum Anderen auch Kritikpunkte bekomme. Man will ja besser werden :-)

Modul: mdi.py (MainWindow, soll hier als MDI-Hauptfenster dienen)

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import sys
import os

from PyQt4.QtCore import Qt
from PyQt4.QtGui import QApplication, QMainWindow, QWorkspace, QAction, QMenu

from ..modules_ui.ui_pp_check import Check_Window

class Mdi_Main(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)

        self.getPath_mdi = os.path.join(os.path.abspath("."), 'files', "qt_ui", 'pp_mdi.ui')
        self.ui_TestMainWorkSpace = loadUi(self.getPath_mdi, self)

        self.workspace = QWorkspace()
        self.workspace.setScrollBarsEnabled(True)
        self.setCentralWidget(self.workspace)

        self.ui_TestMainWorkSpace.actionAddContact.triggered.connect(self.open_check)

    def open_check(self):
        self.check_window = Check_Window(self.show_secon_window)
        self.workspace.addWindow(self.check_window)
        self.check_window.show()

    def show_secon_window(self):
        from ..modules_ui.ui_pp_second import Second_Window
        self.second_form = Second_Window()
        self.workspace.addWindow(self.second_form)
        self.second_form.show()

app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
window = Mdi_Main()
sys.exit(app.exec_())

Was habe ich mir dabei gedacht? Also, ich habe hier zwei Funktionen, einmal in Zeile 25 und 30. Diese dienen dazu Formen zu öffnen. Ich hoffe, du meintest das mit der "zentralen" Verwaltung von Formen, Hyperion? In der MDI-Form ist eine Menu-Leiste mit dem MenuItm actionAddContact (Zeile 23). bei einem Auslöser wird die Form geöffnet, die in der open_check-Funktion festgelegt ist. Jedoch füge ich das zu öffnende Fenster (check_window ) nicht einfach der addWindow-Methode zum Inhalt und öffne sie dann mit der show()-Methode. Ich übergebe der Klasse Check_Window einen Parameter/ ein Argument, und zwar den Namen der Funktion show_secon_window().

Modul: check.py

Code: Alles auswählen

import os

from PyQt4.QtCore import Qt
from PyQt4.QtGui import QWidget
from PyQt4.uic import loadUi

class Check_Window(QWidget):
    def __init__(self, open_it, parent=None):
        QWidget.__init__(self, parent)

        self.show_it = open_it

        self.getPath_check = os.path.join(os.path.abspath("."), 'files', "qt_ui", 'pp_check.ui')

        self.ui_pp_check = loadUi(self.getPath_check, self)

        self.ui_pp_check.pushButton.clicked.connect(self.show_it)
Das Fenster check_window ist offen und im Widget QWorkspace. In Zeile 8 übergebe ich der Check_Window-Klasse eine Instanz der Funktions-Klasse als Parameter, damit ich quasi auf die Funktion einer anderen Klasse zugreifen kann. Zunächst weise ich den Parameter open_it dem Attribut show_it zu. In diesem Attribut ist also der Parameter nun gespeichert. In Zeile 17 klickt man auf den pushButton und dadurch wird auf die show_secon_window-Funktion zugegriffen, die sich in der Klasse Mdi_Main befindet.

Es klappt wie ich es erwartet habe. Aber ich hätte hier und da vielleicht einige Kritikpunkte. Oder war das nur Glück das es bei mir klappte? Ach, übrigens, betrachtet meine Beschreibungen nicht als eine Art Belehrung. Ich weiß, dass ihr hohe Kompetenzen in Python habt. Sonst wär ich ja nicht hier, und würde euch fragen :-) Mir ging es darum, dass ihr meinen Gedankengang verfolgen kann. Und vielleicht an meiner Formulierung äußern könnt, ob ich mich etwas missverständlich ausdrücke oder gar Sachen durcheinander werfe.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
Warum schleifst Du den callback zum Öffnen des 2. Subfensters in den Konstruktor des 1. Subfensters? Einfacher und "zentraler" gehts mit mit der Slotverbindung `self.check_window.ui_pp_check.pushButton.clicked.connect(self.show_secon_window)` in `open_check()`.

Du rufst für jedes Subfensteröffnen `self.workspace.addWindow(...)` auf. Zerstörst Du die Fenster beim Schliessen? Ansonsten bleiben die Referenzen in `self.workspace` bestehen. `setParent()` schafft da Abhilfe. Generell solltest Du Dir hier Gedanken über den Lifecycle von Fenstern machen.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@jerch: Vielen Dank für deinen Hinweis bezüglich der Zentralität. Ich habe gleich eine Frage: Ist meine Art, also das herumreichen der callbacks, schlechter bzw. unschöner? Oder wo genau liegt da der Nachteil? Ich frage nur, weil ich auch aus meinen Fehlern lernen möchte. Da das Arbeiten mit QWorkspace wesentlich angenehmer ist, schließe ich die Fenster mit der close()-Methode. Hier in diesem Fall kann man ja die Widgets im Designer so manipulieren, dass man sagen kann "Mache beim Click auf die Schaltfläche dies und jenes", und dort kann man unter anderem auch sagen "Bei Klick auf diese Schaltfläche schließen". Also die close()-Methode habe ich hier aus Faulheit schon im Designer geklärt. Werden dabei die Fenster nicht wirklich zerstört? Oder muss ich mich noch zusätzlich und explizit um die Zerstörung der Fenster kümmern?

Was du mit dem tollen englischen Begriff "Lifecycle" sagen willst, weiß ich nicht. Ich kenne zwar das deutsche Wort "Lebenszyklus" oder "Laufzeit", aber was genau meinst du damit? Ach ja, wieso nicht die deutschen Wörter? Ich hatte diesbezüglich schon mal eine Diskussion gestartet. :-) Deswegen will ich hier auch keine Diskussion starten.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Sophus hat geschrieben: Was du mit dem tollen englischen Begriff "Lifecycle" sagen willst, weiß ich nicht. Ich kenne zwar das deutsche Wort "Lebenszyklus" oder "Laufzeit", aber was genau meinst du damit? Ach ja, wieso nicht die deutschen Wörter? Ich hatte diesbezüglich schon mal eine Diskussion gestartet. :-) Deswegen will ich hier auch keine Diskussion starten.
Wenn Du die Diskussion hier schon geführt hast, dann musst Du ja nicht noch einmal nachsticheln bzw. das Thema auf den Tisch bringen. Die Regulars hier sind da so ziemlich einer Meinung, die das Englische nicht per se verdammt, sondern es als in vielen für Bereichen notwendig und teilweise auch geeigneter als das Deutsche erachtet.

Zum Thema: Du musst bei Qt beachten, dass sich dahinter ein C++-Framework verbirgt. Wenn Du auch in Python programmierst, so macht es die darunter befindliche Technologie notwendig, dass man die jeweiligen Parent-Objekte an die Kinder weiterreicht. Denn nur so kann sicher gestellt werden, dass die Objekte auch wirklich zerstört werden. Genaueres dazu kannst Du hier nachlesen. Ach ja, in Englisch :-D

Also als Faustregel: Übergib immer das Parent-Widget an das Child-Widget!

Und bezüglich des Callbacks: Es ist natürlich weit weniger magisch, wenn man eine Signal-Slot Verbindung auf der selben Ebene vornimmt, wo sich der Slot befindet.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Hyperion und jerch: Wenn ich euch richtig verstanden habe, dann meint ihr das wie folgt:

Vorher

Code: Alles auswählen

class Check_Window(QWidget):
    def __init__(self, open_it, parent=None):
        QWidget.__init__(self, parent)
Nacher

Code: Alles auswählen

class Check_Window(QWidget):
    def __init__(self, parent):
        QWidget.__init__(self, parent)
So muss ich also jedem Unterfenster beim Aufruf jedesmal folgendes mitgeben:

Code: Alles auswählen

    def show_secon_window(self):

            self.check_window = Check_Window(self) # hier
            self.check_window.pushButton.clicked.connect(self.show_secon_window)
            self.workspace.addWindow(self.check_window)
            self.check_window.show()
            print "Dialog lädt"
Ich muss also immer self hinzufügen (dort wo im Quelltext 'hier' steht), damit das Fenster auch als Kind geöffnet wird, richtig?

Damit
Eine Frage hierbei. Wenn ich über den Designer die close()-Methode festlege, muss ich mich dann im Python auch noch darum kümmern? Also ich arbeite noch mit QWorkspace, und Widgets, die als Unterfenster gelten und in das QWorkspace-Widget geladen werden, schließen sich.

Dann noch eine weitere Frage, bezüglich des Designs:

Code: Alles auswählen

    def show_secon_window(self):
        from ..modules_ui.ui_pp_second import Second_Window

        self.second_form = Second_Window(self)
        self.workspace.addWindow(self.second_form)
        self.second_form.pushButtonSearch.clicked.connect(self.print_func)
        self.second_form.show()
Damit ich die Fenster zentral verwalten möchte, und zwar von mdi.py aus, so importiere ich hier eine Klasse in dieser show_secon_window-Funktion. Ich weiß, dass man im Namensraum importieren soll. Aber wenn man am Ende mehrere Unterfenster hat, wird der Namensraum schnell überfüllt sein. Also importiere ich nur zu gegebener Zeit, wenn ich das Fenster brauche. Würdet ihr das auch so lösen oder den Import direkt in den Namensraum packen?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Vom Smartfone aus zu tippen ist grausam, daher nur in kurz.

Nicht immer ``self``, sondern nur dann, wenn Du das Child-Widget auch im Parent-Widget erzeugst. Ansonsten eben das tatsächliche Elternobjekt.

Deine Importe lesen sich irgendwie allgemein schräg... wieso beginnen die mit ``..``? Schreibe einfach mehrere Klassen in ein Modul, dann hast Du nicht so viel verschiedenes zu importieren. Und Importe sollten idR oben zu Beginn eines Moduls stehen.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hyperion hat geschrieben:Vom Smartfone aus zu tippen ist grausam, daher nur in kurz.

Nicht immer ``self``, sondern nur dann, wenn Du das Child-Widget auch im Parent-Widget erzeugst. Ansonsten eben das tatsächliche Elternobjekt.

Deine Importe lesen sich irgendwie allgemein schräg... wieso beginnen die mit ``..``? Schreibe einfach mehrere Klassen in ein Modul, dann hast Du nicht so viel verschiedenes zu importieren. Und Importe sollten idR oben zu Beginn eines Moduls stehen.
Ich zitiere mal deinen Eintrag. Warum ich '..' bei Importe benutze? Weil ich sonst nicht an meine Module komme. Ich programmiere mich PyCharm, der mir gleich zeigt, ob ich Zugriff auf die Module habe. Und das die Importe im Namensraum sein sollen, weiß ich, aber ich will sie nicht importieren, wenn ich die Fenster nicht brauche. Ich will da etwas "sparender" sein, und nur dann importieren, wenn das Fenster gebraucht wird.

Aber zu deiner ersten Aussage. Es ist mir noch so "abstrakt". Wenn ich dich richtig verstanden habe, dann soll ich den self nur dann verwenden, wenn ich ein Widget auch tatsächlich als Unterfenster im QWorkSpace haben will? Es gibt ja auch Fenster, die nicht im QWorkspace geladen werden, Beispiel mein "Über <Prgrammname>"-Fenster, der wird modal geöffnet. Da lasse ich das Parent=None im Konstrukteur. Was ich aber nicht so ganz verinnerlicht habe, und durch vieles Lesen trotzdem nicht dahinter steige, ist, das meine Unterfenster auch vorher in das QWorkspace geladen und angezeigt wurden. Python hat die Unterfenster nicht geräumt. Was ist das nun wichtig, ob bei den Unterklassen nun parent oder parent=None steht? Ich habe da gerade eine Denk-Verständnis-Blockade.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Ich versuche es mal mir selbst zu erklären, und ihr könnt ja sagen, ob ich falsch liege?

Also der Initializer ( __init__()) der Klasse empfängt bzw. bekommt einen zusätzlichen Default-Parameter - hier ist es der parent. Der parent dient hier als ein Argument, was an dem Konstrukteur übergeben wird. Das hat zur Folge, dass die Klasse zum Parent-Objekt der Instanz gemacht wird. In meinem Fall ist die mdi nun ein Eltern-Fenster. Nun stelle ich fest, dass mein mdi ein sogenanntes Top-Level-Fenster ist. Mein check-Fenster wird zum Kind-Fenster, eben weil kein parent=None im Initializer steht, sondern nur parent. Das hat den Vorteil, dass die Verantwortung für das Schließen bzw. Zerstören des Fensters nicht mehr beim Programmierer liegt. Bei Widgets (check-fenster, second-Fenster), die ein Parent-Widget (mdi) haben, sorgt Qt selbst dafür, dass beim Schließen des Parent-Widgets auch alle enthaltenen Child-Widgets geschlossen werden. Dazu ein einfaches Beispiel aus dem Netz:

Code: Alles auswählen

from PyQt4 import QtGui

class Widget(QtGui.QWidget):
    def __init__(self, parent):
        QtGui.QWidget.__init__(self, parent)

        self.destroyed.connect(self.handleDestroyed)

    def __del__(self):
        print ('__del__:', self)

    def handleDestroyed(self, source):
        print ('destroyed:', source)

class Foo(Widget):
    def __init__(self, parent):
        Widget.__init__(self, parent)

class Bar(Widget):
    def __init__(self, parent):
        Widget.__init__(self, parent)

class Window(Widget):
    def __init__(self, parent=None):
        Widget.__init__(self, parent)

        self.foo = Foo(self)
        self.bar = Bar(None)

if __name__ == "__main__":

    app = QtGui.QApplication([__file__, '-widgetcount'])
    window = Window()
    window.show()
    app.exec_()
('__del__:', <__main__.Window object at 0x024C4228>)
('destroyed:', <__main__.Foo object at 0x024C4270>)
('__del__:', <__main__.Bar object at 0x024C42B8>)
Widgets left: 0 Max widgets: 6
Hier sehe ich, dass Foo zerstört wurde. Bar eben nicht, denn Bar unterstand keiner Parent-Child-Beziehung und somit auch kein Bestandteil des Window-Fensters. Soweit habe ich das Beispiel interpretiert.

Was ich aber hierbei nicht ganz verstehe. Mein Check-Fenster war vorher durch parent=None ebenfalls zu einem Top-Level-Fenster und ist theoretisch nicht Bestandteil eines anderen Fensters, also des mdi-Fensters. Check-Fenster und second-Fenster sind dann keine Kinder-Fenster mehr. Die Unterfenster wie check-Fenster oder second-Fenster wurden jedoch geschlossen und hoffentlich zerstört. Denn im Designer kann man die Signal-Slots ebenfalls begrenzt bearbeiten, dazu fällt dann auch diese close-Methode. Und im QWorkspace werden dann die Fenster geschlossen. Im MDIArea muss man selbst Hand anlegen. Das ist auch der Grund, warum ich QWorkspace bevorzuge. Man spart hier einiges. Frage nun, werden die Kinder-Fenster hier nicht sowieso zerstört, auch wenn ich sie im Konstruktur als Top-Level-Fenster angebe?
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
C++ hat normalerweise keinen Aufräummechanismus ala garbage collector oder reference counter (schon wieder englisch, die englischen Begriffe sind in der Programmierdomäne eindeutig belegt, ins Deutsche übertragen, müsste man erst erklären, was gemeint ist). Darum haben die Qt-Leute Sicherheitsnetze eingebaut: 1. über den Objektbaum, welcher mit parent-child-Beziehungen aufgebaut wird. Darüber kann Qt die Objekte ordentlich abräumen (also in der richtigen Reihenfolge children --> parent). Werden QObjects ohne parent erstellt, werden diese spätestens von QApplication abgeräumt (2. Variante, ist u.a. der Grund, warum es ein QApplication-Objekt vor der Benutzung anderer QObjects geben muss).
Beim QWorkspace wirds schwieriger. Die Doku zu `addWindow` gibt indirekt den Hinweis, dass die Elternschaft sich ändert. Was genau passiert, sieht man erst, wenn man den Sourcecode von `addWindow` anschaut: es wird ein Proxyobjekt vom Typen QWorkspaceChild erstellt und das Subfenster dort reingehängt (Objektbaum: QWorkspace->QWorkspaceChild->Subwidget). Diese Verschleierung war offensichtlich selbst den Qt-Leuten zuviel und sie haben QWorkspace fallen gelassen und durch MdiArea ersetzt. Schaut man sich die Schnittstellen beider Klassen an, so unterscheiden sie sich hauptsächlich dadurch, dass MdiArea das vorher undurchsichtige Verhalten durch die Spezifikation von QMdiSubWindow offen legt und Benutzern Kontrolle zurück gibt.

Nun zur Frage `close`:
close macht genau nur das - es schliesst das Fenster. Kurzer Gegencheck:

Code: Alles auswählen

from PyQt4 import QtGui

def showQWidgetsChildren(qobj):
    def wrapped():
        for obj in qobj.children():
            if isinstance(obj, QtGui.QWidget):
                obj.show()
    return wrapped

app = QtGui.QApplication([])
win = QtGui.QWorkspace()
but = QtGui.QPushButton(win)
but.clicked.connect(showQWidgetsChildren(win))
win.addWindow(QtGui.QWidget())
win.show()
app.exec_()
Egal wie oft Du das Subfenster schliesst, es bleibt in QWorkspace.children() und lässt sich per Klick auf den Button wieder herstellen. Und das ist der Punkt, wo PyQt schwierig wird, weil 2 Welten der Objektbehandlung/-aufräumung aufeinander treffen und Du genau wissen solltest, wann Objekte wiederverwendet werden können/sollen oder einfach neue erstellt und zerstört werden (Lifecycle). Der klassische Pythonmechanismus (Markieren des Objektes zur Abräumung, wenn ausserhalb der Sichtbarkeit des Pythonnamens) greift hier nicht mehr automagisch, da Qt die Objektreferenzen hält. Du musst Qt explizit sagen, dass es das Subfenster zerstören soll. Das geht z.B. mit mit dem Windowsattribut Qt::WA_DeleteOnClose.

Du fragst Dich wahrscheinlich, warum das für Dich wichtig ist. Mit MDI kannst Du prinziell unendlich Fenster öffnen und schliessen. Machst Du knappe Ressourcen alter Fenster nicht frei, laufen diese voll (z.B. Speicher) oder blockieren (z.B. offene Datei im Subfenster unter Windows). Das gilt übrigens für alle QWidgets, nicht nur innerhalb QWorkspace. Daher sollte man abwägen, ob ein Fenster wiederbenutzbar ist (neue Daten in Widgets laden + show) oder das Fenster beim Schliessen zerstören und bei Bedarf neu erstellen.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@jerch: Besten Dank für die ausführlich Erklärung. Das muss ich erst einmal alles auf mich wirken lassen. Ich werde die Tage mein Beispiel nochmal überarbeiten, und es hier nochmal präsentieren, und hätte dazu eure Meinung nochmal.

Jedoch hätte ich eine weitere Frage: Du sagtest, ich müsse mich abwägen, ob ich ein Fenster zerstöre oder eben nicht. Ich kann ja leider das verhalten eines Anwenders nicht voraussehen. An welchen Kriterien hält man sich hierbei fest? Oder wäre es eher günstig, die Fenster grundlegend zu zerstören und neu zu erstellen?
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Die Entscheidung liegt nicht beim Anwender Deines Programmes, das musst Du schon selbst festlegen.

Bei nicht modalen Fenstern tendiere ich eher zum Wiederverwenden, sofern das möglich ist. Dann stricke ich aber auch die Anwendung so, dass diese Fensterobjekte hauptfenster-weit sichtbar sind (geht prima mittels initializer lists in C++ bzw. in Python Importe nach oben und in `__init__` Exemplare erstellen). Brauche ich ein nicht modales Fenster selten bis gar nicht, wirds zur Laufzeit erst bei Anforderung kreiert und lazy gelöscht (Nullpointer im Konstruktor, nach Benutzung zeigt dieser aufs Fensterobjekt). Das betrifft aber alles Fenster mit sehr spezieller Funktionalität. Bei MDI würde ich eher Erstellen/Löschen nutzen, da die Exemplare ja prinzipiell nebeneinander extistieren können müssen.

Bei modalen Fenstern (z.B. kleine Dialoge) bleibt alles lokal und damit auch das Zwischenfenster (idR auf dem Stack), also Erstellen/Löschen.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@ jerch: Wie kann ich zu Testzwecke per Print-Anweisung ausgeben lassen, um zu sehen ob das Fenster-Objekt nun wirklich nach dem Schließen gelöscht wurde?
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
Siehe im Bsp. oben, per children()-Methode des parent.
Antworten