Schatz, das verdammte Ding klemmt!

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,

entschuldigt, dass ich keinen zutreffenden Betreff gefunden habe. Jedoch weiß ich nicht, wie ich das Problem beim Namen nennen soll. Dazu gibt es ein Bild (http://www.bilder-upload.eu/show.php?fi ... 085686.jpg). Auf dem Bild habe ich drei rote Pfeile an entsprechenden Stellen markiert. Im Bild sehen wir, dass es sich hierbei um das Such-Fenster handelt, welches maximiert geöffnet wurde. Das mdiArea hat die Option (DontMaximizeSubWindowOnActivation). Dieses Phänomen auf dem Bild tritt erst auf, wenn ich zweimal das gleiche Fenster öffne, und sie dann über die eigene Schließ-Schaltfläche schließe. Damit wir nicht raten müssen, folgen einige Quelltext-Ausschnitte:

Modul: mdi.py

Code: Alles auswählen

    def create_search_form(self):
        from ..modules_ui.ui_pp_search import Search_Window

        self.subwindow_search = QMdiSubWindow()
        self.search_form = Search_Window(self.close_sub_form, self.subwindow_search, self)
        self.search_form.setAttribute(Qt.WA_DeleteOnClose)
        self.subwindow_search.setWidget(self.search_form)

        self.mdiArea.addSubWindow(self.subwindow_search)

        self.search_form.showMaximized()

    def close_sub_form(self, form_name):
        self.mdiArea.removeSubWindow(form_name)
        print FILE_NAME + ": Function (close_sub_form) was called"
Wir sehen hier zwei definierte Funktionen. Beim Erstellen des Such-Fenster (create_search_form) übergebe ich der Search_Window-Klasse ein paar Parameter/Argumente - einmal subwindow_search und dann den Funktionsnamen close_sub_form. Warum? Hier ein kleiner Blick in die Search_Window-Klasse.

Modul: ui_pp_search.py

Code: Alles auswählen

class Search_Window(QWidget):
    def __init__(self, close_sub_form, sub_form, parent):
        QWidget.__init__(self, parent)

        self.close_function = close_sub_form # this function is in module named mdi.py
        self.sub_widget_name = sub_form

    def closeEvent(self, event):
        self.close_function(self.sub_widget_name)
Die Signal/Slots habe ich im Search-Window direkt über QT-Designer bearbeitet, also den "Schließ-Knopf". Deswegen greife ich auch hierbei auf das closeEvent-Ereignis zu. Im Attribut (close_function) ist der Name der Schließ-Funktion gespeichert. Diese Funktion findet man auf meiner mdi-form. Und im Attribut sub_widget_name wird der Name der Form gespeichert. Und beim Schließen des Fensters werden diese Parameter wieder an die mdi-Form zurückgegeben, und das Fenster schließt sich. Auf diesem Weg wollte ich verhindern, dass ich nicht für jedes Unterfenster eine Schließ-Funktion schreiben muss, sondern alles zentral über eine Funktion gesteuert werden soll - hierbei wollte ich die Redundanz vermeiden.

Ich habe auch mit der subWindowList-Methode nachgesehen, ob sich die Unterfenster tatsächlich noch im MDI-Bereich befinden, nachdem ich das Suchfenster geschlossen habe. Aber es sind keine weiteren Unterfenster vorhanden - alles leer. Das heißt also, dass meine Fenster allesamt richtig geschlossen und vernichtet werden.

Aber wieso klemmt denn der obere Balken?
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
Deine Eventbehandlung sieht falsch aus. Ob es die Ursache ist, weiss ich nicht.

Das Binden von MDI-Exemplaren an `self` sieht falsch aus, da prinzipiell 1:n möglich sind, mit dem Namen aber nur 1:1 abgebildet werden können. Zusätzlich ist der Zugriff auf ein zerstörtes Exemplar über den Pythonnamen gefährlich - daher besser nur binden, wenn der Lifecycle dem Elternexemplar entspricht oder spätestens mit Zerstören löschen (kA. ob PyQt den Zugriff über den Pythonnamen bei einem zerstörten Objekt sauber abfängt oder es einen segfault erzeugt, wäre mir zuviel Implementationsdetail).
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

jerch hat geschrieben:@Sophus:
Deine Eventbehandlung sieht falsch aus. Ob es die Ursache ist, weiss ich nicht.
Inwiefern? Es sieht nur falsch aus oder ist es falsch? Denn wie gesagt, meine kreierten Unterfenster werden erfolgreich geschlossen.
jerch hat geschrieben: Das Binden von MDI-Exemplaren an `self` sieht falsch aus, da prinzipiell 1:n möglich sind, mit dem Namen aber nur 1:1 abgebildet werden können. Zusätzlich ist der Zugriff auf ein zerstörtes Exemplar über den Pythonnamen gefährlich - daher besser nur binden, wenn der Lifecycle dem Elternexemplar entspricht oder spätestens mit Zerstören löschen (kA. ob PyQt den Zugriff über den Pythonnamen bei einem zerstörten Objekt sauber abfängt oder es einen segfault erzeugt, wäre mir zuviel Implementationsdetail).
Würde ich die Bindung an 'self' weglassen, würden die Unterfenster sich schließen, bzw. von Qt oder spätestens von Python geschlossen werden. Damit das nicht geschieht, wollte ich die Bindungen setzen. Was die Pythonnamen anbelangt. Was genau meinst du damit? Wo sind die Pythonnamen? Etwa das Wort "close"?
BlackJack

@Sophus: Die Event-Behandlung sieht falsch aus weil Du das Event gar nicht behandelst. Schau Dir mal die Dokumentation zu `closeEvent()` an. Das erscheint mir auch der falsche Platz, denn wenn dieses Ereignis auftritt hat ja schon jemand `close()` aufgerufen. Du rufst dann `close()` auf dem Elternwidget auf, was dazu führt dass alle Kind-Windgets auch das Ereignis bekommen, also die Methode aufrufen die das alles gerade ausgelöst hat. Das ist irgendwie verquer. Ruf einfach `close()` auf und gut ist. Also auch das `removeSubWindow()` sein lassen. Wenn auf dem Objekt `close()` aufgerufen wird, dann wird das schon aus dem MDI-Bereicht entfernt ohne das man noch zusätzlich etwas machen muss.

Du nennst da übrigens Werte Namen die keine sind. `form_name` und `subwindow_name` sind Wigdet-Objekte und keine Namen.

Und das an `self` zu binden macht keinen Sinn. Wenn die Fenster verschwinden würden wenn Du das nicht machst, dann müssten die ja tatsächlich auch verschwinden wenn Du versuchst ein zweites der gleichen Art zu öffnen, denn es ist ja immer nur eines an `self` gebunden.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Sophus hat geschrieben:Was die Pythonnamen anbelangt. Was genau meinst du damit? Wo sind die Pythonnamen? Etwa das Wort "close"?
Nein damit meine ich die Variablen- bzw. Attributnamen, die Du mit Referenzen auf Qt-Objekte belegst. Sind diese bereits von Qt zerstört worden aber über die von Python gehaltenen Referenzen noch erreichbar, ist der Speicherbereich ungültig (dangling pointer) und ein Zugriff hierauf fatal.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack: Einfach die close()-Methode anwenden?

Code: Alles auswählen

    def create_search_form(self):
        from ..modules_ui.ui_pp_search import Search_Window

        self.subwindow_search = QMdiSubWindow()
        self.search_form = Search_Window(self.close_sub_form, self.subwindow_search, self)
        self.search_form.setAttribute(Qt.WA_DeleteOnClose)
        self.subwindow_search.setWidget(self.search_form)

        self.sub_search_form = self.mdiArea.addSubWindow(self.subwindow_search)


        self.search_form.show()

        self.search_form.pushButtonClose.clicked.connect(self.just_close_it)

    def just_close_it(self):
        self.sub_search_form.close()
Wenn ich das so handhabe, dann bekomme ich einen gewaltigen Ärger, sobald ich von einem Unterfenster gleich mehrere Unterfenster offen habe. Entweder sagt Python dann "Python reagiert nicht mehr" oder aber die anderen Unterfenster werden nicht geschlossen. Ich habe auch schon daran gedacht, die ID des Fensters zu ermitteln, diese dann an die just_close_it-Funktion zu übergeben, die dann geschlossen werden soll. Jedoch kann man bei der connect-Methode keine Argumente übergeben oder? Zumindest sagt PyCharm bei mir immer:
TypeError: connect() slot argument should be a callable or a signal, not 'NoneType'
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Sophus hat geschrieben:Wenn ich das so handhabe, dann bekomme ich einen gewaltigen Ärger, sobald ich von einem Unterfenster gleich mehrere Unterfenster offen habe. Entweder sagt Python dann "Python reagiert nicht mehr" oder aber die anderen Unterfenster werden nicht geschlossen.
Beides hab ich weiter oben versucht Dir klar zu machen (und nicht zum ersten Male). Lesen, verstehen und umsetzen. Das "Python reagiert nicht mehr" dürfte übrigens ein segfault sein.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@jerch: Ich bin dir auch für jede Hilfe dankbar, jedoch "redest" du für mich zu abstrakt. Von welchen Attributen und Variablen redest du genau? Kannst du das Kind bitte beim Namen nennen? Vielleicht werde ich ja dadurch etwas "schlauer"? :-) Ich habe mal die Referenzen aufgehoben, jedoch besteht das Problem weiterhin:

Code: Alles auswählen

    def create_search_form(self):
        from ..modules_ui.ui_pp_search import Search_Window

        subwindow_search = QMdiSubWindow()
        search_form = Search_Window(self.close_sub_form, subwindow_search, self)
        search_form.setAttribute(Qt.WA_DeleteOnClose)
        subwindow_search.setWidget(search_form)

        self.sub_search_form = self.mdiArea.addSubWindow(subwindow_search)

        search_form.show()
        search_form.pushButtonClose.clicked.connect(self.just_close_it)

        self.create_subwindow_list(subwindow_search)

    def just_close_it(self, sub_widget):

        self.sub_search_form.close()
Nun, in Zeile 4 habe ich subwindow_search kein 'self' gesetzt, weil subwindow_search in Zeile 9 zum Inhalt von addSubWindow() wird und ich somit sicher gehen kann, dass Qt und auch Python nichts löschen wird. Genauso habe ich in Zeile 5 bei search_form kein 'self' gesetzt, weil search_form in Zeile 9 zum Inhalt von setWidget wird, und somit auch nicht gelöscht wird. Das sind also die Gründe, weshalb ich die 'self'-Referenzen gelöscht habe.
Zuletzt geändert von Sophus am Sonntag 5. April 2015, 00:43, insgesamt 1-mal geändert.
BlackJack

@Sophus: Oh man Du bringst da wieder alles mögliche durcheinander. An was ist denn Deiner Meinung nach `self.subwindow_search` und an was `self.sub_search_form` gebunden? Und warum bindest Du das alles immer noch an `self`? Und warum überhaupt die `just_close_it()`-Methode? Du hast doch in der `create_search_form()`-Methode schon das passende Widget dessen `close()`-Methode man nur mit der Schaltfläche verbinden müsste. Unschön wäre dann nur noch dass man von aussen auf die Schaltfläche zugreift um sie zu verbinden statt das alles innerhalb der `Search_Window`-Klasse abzuwickeln. Da wären wir dann aber wieder dabei das man den Objektbaum verstanden haben muss der da entsteht und wie man von einem `Search_Window`-Exemplar an das dazugehörige `QMdiSubWindow`-Exemplar kommen kann. Das geht auch ohne das man Funktionen/Methoden und Widgets selber noch mal übergibt, denn es gibt ja bereits eine direkte Beziehung zwischen diesen beiden Objekten. Wie schon mal in einem anderen Thema geschrieben braucht es in der Methode eigentlich nicht mehr als Code der so aussieht:

Code: Alles auswählen

    def new_child_window(self):
        self.mdi_area.addSubWindow(ChildWindow()).show()
Das verbinden der Schaltfläche in dem Kindfenster mit einer Methode die das Kindfenster schliesst indem es die `close()`-Methode auf dem dazugehörigen `QMdiSubWindow` aufruft kann man in `ChildWindow.__init__()` erledigen, wo es IMHO auch hingehört. Und die Methode die das schliessen erledigt ist auch ein einfacher Einzeiler: das passende Objekt holen und `close()` darauf aufrufen.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack: Warum und wieso ich etwas mit 'self' gebunden habe, erkläre ich im oberen Beitrag, indem ich versuche für mich im Monoton zu erklären, weshalb ich auf bestimmte 'self'-Bindungen verzichte.

Wenn ich das nun so mache wie du, dann sieht mein Code wie folgt aus:

Code: Alles auswählen

    def create_search_form(self):
        from ..modules_ui.ui_pp_search import Search_Window

        self.mdiArea.addSubWindow(Search_Window(self)).show()
Hierbei sehe ich (für mich) ein Problem. Denn wie komme ich an Zeile vier ran? Ich könnte ein Attribut/Variable vorsetzen, aber dieser wäre dann vom Typ ein NoneTyp, und somit kann ich darauf nicht zugreifen, um auf das Objekt die close-Methode aufrufen zu können.

Und im Unterfenster Search sieht es dann so aus:

Code: Alles auswählen

class Search_Window(QWidget):
    def __init__(self, parent):
        QWidget.__init__(self, parent)

        self.setAttribute(Qt.WA_DeleteOnClose, True)
Ich habe dieses WA_DeleteOnClose hierher verlegt. Ich hoffe, dass es so in Ordnung geht?
BlackJack

@Sophus: Das `self`-Argument beim Aufruf von `Search_Window/()` macht keinen Sinn, das sollte da nicht stehen. Steht bei mir ja auch nicht da. Und an Zeile 4 heran kommen ist eine komische Formulierung. Das ist keine Beschreibung von einem Wert der irgendwie im Programmverlauf vorhanden wäre.

In `Search_Window.__init__()` kann man die Schaltfläche mit einer Methode auf `Search_Window` verbinden die dann das Unterfenster schliesst. Dazu muss sie an das dazugehörige `QMdiSubWindow`-Exemplar kommen, was aber ganz einfach ist wenn man das Objektmodell und den Objektbaum von Qt verstanden hat und weiss was `addSubWindow()` macht. Dann weiss man auch warum `self` an `Search_Window` zu übergeben sinnfrei ist, weil das alles zusammenhängt.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack: Irgendwie wiederholst du dich, was ich bereits zuvor versucht habe für mich zu erklären. Aber ich machst trotzdem nochmal:

Zunächst einmal, das 'self'-Argument musste ich mit übergeben. Täte ich das nicht, dann bekomme ich folgende Fehlermeldung:
File "D:\Dan\Python\project_xarphus\files\modules_ui\ui_pp_mdi.py", line 335, in create_search_form
self.mdiArea.addSubWindow(Search_Window()).show()
TypeError: __init__() takes exactly 2 arguments (1 given)
Hier fehlt also ein Argument. So sieht meine Search_Window-Klasse aus (hatte sie schon mal gezeigt):

Modul: ui_pp_search.py

Code: Alles auswählen

class Search_Window(QWidget):
    def __init__(self, parent):
        QWidget.__init__(self, parent)
 
        self.setAttribute(Qt.WA_DeleteOnClose, True)
Und zum anderen: Habe ich etwas verpasst? Hast du nicht gesagt, ich brauche keine QMdiSubWIndow-Klassen-Instanz aufbauen? Aber gut, ich mache das mal so, wie ich dein Geschriebenes verstehe. Da du ja irgendwas von QMdiSubWindow schreibst, muss ich diese Klasse ja erst einmal instanzieren.

Modul: mdi.py

Code: Alles auswählen

    def create_search_form(self):
        from ..modules_ui.ui_pp_search import Search_Window

        subwindow_search = QMdiSubWindow()
        self.mdiArea.addSubWindow(Search_Window(self)).show()
Bingo, jetzt habe ich die QMdiSubWindow-Instanz, die natürlich hier keine Verwendung findet. Aber du redest ja die ganze Zeit von dieser Klasse. Da haben wir sie. Nun, weshalb die Search_Window()-Klasse kein self-Argument braucht, liegt daran, weil diese Klasse zum Inhalt von addSubWindow wurde, und die Referenz somit überflüssig wäre, da man davon ausgehen kann, dass sowohl Qt als auch Python das Unterfenster nicht räumen wird.

Und jetzt redest du die ganze Zeit vom Objektbaum. Nun, ich sehe es so, ich habe ein QWidget (Search_Window) als Kind-Fenster, auf diesem ist ein QPushButton drauf, der irgendwas auslösen soll. Und jetzt deine nächste Zeile. Ich soll also in Search_Window.__init__() die Schaltfläche mit der Methode auf die Klasse Search_Window verbinden, damit das Unterfenster geschlossen wird. Nun schau ich mir mal das mal an:

Modul: ui_pp_search.py

Code: Alles auswählen

class Search_Window(QWidget):
    def __init__(self, parent):
        QWidget.__init__(self, parent) # diese Stelle meintest du?
Und jetzt kommt der Punkt, wo ich dich nicht verstehe. Hier steht ein Fragezeichen über meinen Kopf.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:

Du muss die Qt-Doku schon aufmerksam lesen. Das parent-Argument an QWidget ist optional: QWidget(QWidget * parent = 0, Qt::WindowFlags f = 0) oder aus der PyQt-Doku: QWidget.__init__ (self, QWidget parent = None, Qt.WindowFlags flags = 0). Das Du für parent was übergeben musst, hast Du selbst zu verantworten, da Deine Klasse es erwartet (siehe https://docs.python.org/2/tutorial/cont ... ent-values).

zum Problem QMdiSubWindow:
Du musst die Qt-Doku schon aufmerksam lesen. Da steht unter QMdiArea::addSubWindow:
The widget can be either a QMdiSubWindow or another QWidget (in which case the MDI area will create a subwindow and set the widget as the internal widget).
und weiter unten:
When you create your own subwindow, you must set the Qt::WA_DeleteOnClose widget attribute if you want the window to be deleted when closed in the MDI area. If not, the window will be hidden and the MDI area will not activate the next subwindow.
Du hast also zwei Möglichkeiten, die Subwidgets zur MdiArea hinzuzufügen. Nur beim 1. Weg (selbst Erstellen eines MdiSubWindows) muss das WA_DeleteOnClose-Flag von Hand gesetzt werden, und zwar am MdiSubWindow und nicht wie Du es gemacht hast, am Subwidget. Das dürfte die Ursache für das Verhalten im Screenshot sein.

Nun zu Deinem Code:

Code: Alles auswählen

        def create_search_form(self):
            from ..modules_ui.ui_pp_search import Search_Window
     
            subwindow_search = QMdiSubWindow()
            self.mdiArea.addSubWindow(Search_Window(self)).show()
     
Was soll der import da in Zeile 2? Das gehört nach oben an den Anfang des Moduls. Zeile 4: Überleg mal, was das macht und wo Du es benutzt. Die Zeile ist komplett überflüssig.

Nun zum Auslösen der Schliessen-Aktion. Hierfür musst Du den Objektbaum kennen, der die MDI-Fenster beinhaltet. Das kannst Du wiederum aus der Doku rauslesen: QMdiArea --> QMdiSubWindow --> Dein Widget
Damit weisst Du alles, um aus dem fertig ins MDI platzierten Subwidget das QMdiSubWindow schliessen zu können und kannst entsprechend die Signal-Slot-Verbindung kreieren. Diese Verbindung geht Dein MainWindow nichts an, die Funktionalität wird zwischen dem QMdiSubWindow und Deinem Widget verhandelt (Stichwort: Sichtbarkeit). Aus diesem Grund gehört das entweder ans QMdiSubWindow oder an Dein Widget.

Beispiel:

Code: Alles auswählen

In [1]: from  PyQt4.QtGui import QMdiArea, QWidget, QApplication, QPushButton, QMdiSubWindow

In [2]: class WidgetWithCloseButton(QWidget):
   ...:     def __init__(self, parent=None):
   ...:         QWidget.__init__(self, parent)
   ...:         self.button = QPushButton(self)
   ...:         self.button.setText('Close')
   ...:         self.button.clicked.connect(self.closeParent)
   ...:     def closeParent(self):
   ...:         if isinstance(self.parent(), QMdiSubWindow):
   ...:             self.parent().close()
   ...:             

In [3]: app = QApplication([])

In [4]: win = QMdiArea()

In [5]: win.show()

In [6]: win.addSubWindow(WidgetWithCloseButton()).show()

In [7]: win.addSubWindow(WidgetWithCloseButton()).show()
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@jerch: Wie immer, besten dank. Du hast es methodisch und didaktisch sehr gut erklärt. So gefällt mir das auch. Ich will ja keine fertigen Lösungen, sondern an die Hand genommen werden, und belehrt werden. Genau meinen Geschmack. Aber ich habe noch einige Fragen, Anmerkungen und möchte die Gelegenheit nutzen und dir meine Gedankengänge aufzeigen, damit dir vielleicht klar wird, inwiefern ich nicht weiterkam.

Import immer am Anfang?
In meinem Fall habe ich mit Absicht meinen Import in eine Funktion untergebracht. Immer wenn in Unterfenster kreiert wird, soll die entsprechenden Klassen importiert werden. Ich habe es mir deshalb so angewöhnt, weil ich im Vorfeld schon weiß, dass - gerade im MDI-Bereich - viele verschiedene Unterfenster zustande kommen werden. Und damit ich den Namensraum nicht unnötig vollstopfe, verlege ich sie in die entsprechenden Funktionen. Und außerdem, wenn der Anwender das eine Unterfenster nicht braucht, dann wird auch nicht importiert. Würde ich es weit am Anfang setzen, so wird das Unterfenster importiert, obwohl noch nicht einmal sicher ist, dass das Unterfenster überhaupt benutzt wird.

Unterfenster auf zwei Wege erstellen
Das es zwei Wege gibt, womit man sein Unterfenster erstellen kann, habe ich bereits vernommen. Wie du unschwer erkannt hast, habe ich mit auch für den ersten Weg, und zwar über das Erstellen der QMdiSubWindow-Klasse entschieden. Jedoch bekam ich von eurer Seite aus diesbezüglich Kritik, was ja auch nicht schlimm ist.

Das WA_DeleteOnClose-Attribut
Ich habe spaßeshalber den erste Weg noch einmal aufgegriffen, also die QMdiSubWindow per Hand kreiert und das WA_DeleteOnClose-Attribut anstatt auf self.search_form zu setzen, habe ich sie auf self.subwindow_search gesetzt, also auf QMdiSubWindow und die Anwendung gestartet. Das Verhalten bleibt weiterhin bestehen. Keine Änderung. Der Balken klemmt weiterhin im MDI-Bereich.

Code: Alles auswählen

    def create_search_form(self):
        from ..modules_ui.ui_pp_search import Search_Window

        self.subwindow_search = QMdiSubWindow()
        self.search_form = Search_Window(self.close_sub_form, self.subwindow_search, self)
        self.subwindow_search.setAttribute(Qt.WA_DeleteOnClose)
        self.subwindow_search.setWidget(self.search_form)
        self.mdiArea.addSubWindow(self.subwindow_search)
        self.search_form.showNormal()


Die nutzlose QMdiSubWindow-Klasse
In meinem Quelltext war die QMdiSubWindow-Klasse nutzlos. Ist ja nicht so, dass ich es nicht wüsste :-) Ich schrieb ja selbst, dass diese gerade erstellte Klasse keinen Nutzen bringt. Nur weil BlackJack die ganze Zeit davon schrieb, aber nirgends weiter darauf einging, dachte ich mir, schenke ich ihm die QMdiSubWindow-Klasse, vielleicht passiert ja noch ein Wunder, und ich erfahre dann mehr?

Das singende, klingende Objektbäumchen
Also, ich gehe mal einen Schritt weiter, und betrachte mein Bäumchen wie folgt:
QMainWindow --> QMdiArea --> QMdiSubWindow --> QWidget --> QPushButton.
Alles wunderbar. Jetzt haben wir das Bäumchen, und nun? Genau das denke ich. Wie komme ich vom Anblick des Bäumchen nun auf die von dir vorgestellte Funktion:

Code: Alles auswählen

def closeParent(self):
   ...:         if isinstance(self.parent(), QMdiSubWindow):
   ...:             self.parent().close()
Allein vom Anblick meines Objektbaumes wäre ich niemals auf die Funktion gekommen, die du mir da vorstellst. Mir fehlt also da der Übergang. Denn das Objektbaum habe ich ja bereits verstanden. Aber mein Objektbaum hätte es mir nicht verraten und gesagt "Hey, mach das so und so". Ich sehe nur fünf Ebenen, ja, aber das warst dann auch. Es ist vielleicht für euch so selbstverständlich, weil ich Profis seid, und es so gelernt habt bzw. es euch so gezeigt wurde. Aber von allein, ohne Hilfsmittel wäre ich nicht darauf gekommen. Also ist der Objektbaum per se nicht selbst erklärend. Wie gesagt, ich könnte den Objektbaum Tag und Nacht rauf und runterbeten. Deswegen dachte ich mir immer: "Alder, was pochen sie die ganze Zeit auf dem Objektbaum? Lass den armen Objektbaum in Ruhe, er kann mir auch nicht weiterhelfen". :-)

Aber ein Kind braucht doch seine Eltern
Damit nicht das Gefühl aufkommt, ich werfe nur so mit den parent-Argumenten um mich, möchte ich kurz meine Gedankenwelt präsentieren. Dazu schauen wir mal kurz in mein mdi-Modul:
Modul: mdi.py

Code: Alles auswählen

class Mdi_Main(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
Was sehe ich hier? Hier wird eine Klasse Mdi_Main definiert, die von der Klasse QMainWindow erbt. Letztere wird in Qt benutzt, um das Hauptfenster einer Anwendung zu implementieren. Es folgt mit __init__() der Initializer der Klasse - Konstruktor. Dieser "empfängt" einen zusätzlichen Default-Parameter parent. Das entsprechend übergebene Argument wird dabei zum Parent-Objekt der Instanz gemacht. Somit ist es ein so genanntes Top-Level-Window. Dies wird also durch das parent-Attribut den Wert „None“ ermöglicht. Soweit so gut. Schauen wir doch mal die Search_Window()-Klasse an.

Modul: ui_pp_search.py

Code: Alles auswählen

class Search_Window(QWidget):
    def __init__(self, parent):
        QWidget.__init__(self, parent)
Was sehe ich hier? Das parent-Argument hat diesmal nicht den Wert None. Aber halt, rein theoretisch kann jedes Qt-Widget als Top-Level-Window benutzt werden. Will ich aber nicht. Warum? Weil ein solches Top-Level-Window sonst nicht Bestandteil eines anderen Fensters ist, also die dies QMainWindows, und des Weiteren läge die Verantwortung für das Schließen des Fensters beim Programmierer. Damit sich Qt selbst darum kümmert, habe ich beim parent-Argument einfach den 'None'-Wert weggelassen. Somit sorgt Qt selbst dafür, dass beim Schließen des Parent-Widgets (mein QMainWindows namens Mdi_Main) auch alle enthaltenen Kind-Widgets geschlossen werden. Zur Initialisierung muss anschließend immer der Initializer der Klasse aufgerufen werden.

The End
Betrachtet dies nicht als eine Belehrung oder dergleichen, sondern als eine Art, meine Gedanken lesen zu können. Natürlich hat dies auch einen Selbst-Lern-Effekt für mich :-) Schließlich möchte ich Python lernen, ich will alles verstehen, ich will nicht nur Lösungen vorgekaut bekommen etc. Ich möchte euch die Gelegenheit geben, dass ihr mal meine Gedankenstruktur kennenlernt. Nur so hoffe ich, dass ihr dann entsprechend ansetzen könnt, und sagen könnt "Aber genau da denkst du falsch" oder "genau da fehlt der Sprung" oder was auch immer.

So jetzt gibt es Kaffee.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Eine weitere Sache klemmt wahrscheinlich. Ich habe hier ein Bild hochgeladen:
http://www.bilder-upload.eu/show.php?fi ... 268323.jpg

Auf dem Bild sehen wir zwei Bilder. Links im Bild sehen wir wie das Unterfenster das gleiche "Icon" hat, wie das des Hauptfensters, und im rechten Bild hat das Hauptfenster kein "Icon" und das Unterfenster hat ihr "eigenes" Icon.

Wenn ich also im Qt-Designer dem Hauptfenster das windowIcon entziehe, und somit blank ist, dann wird im Unterfenster das eigene "Icon" angezeigt, und zwar dieses, was ich auch in der setWindowIcon()-Methode der Klasse Search_Window()-Klasse festgelegt habe. Verpasse ich dem Hauptfenster aber ein Icon sowohl über den Qt-Designer als auch über den Quelltext die setWindowIcon()-Methode in der Mdi_Main()-Klasse, dann bekommt das Unterfenster das Icon vom Hauptfenster zugewiesen.
Modul: mdi.py

Code: Alles auswählen

class Mdi_Main(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.getPath_icon= os.path.join(os.path.abspath("."), 'files', 'images', 'img_32x32', 'Logo.png')
        self.ui_TestMainWorkSpace.setWindowIcon(QIcon(self.getPath_icon))
Modul: ui_pp_search.py

Code: Alles auswählen

class Search_Window(QWidget):
    def __init__(self,):
        QWidget.__init__(self)
        self.getPath_search_icon = os.path.join(os.path.abspath("."), 'files', 'images', "img_16X16", 'Search.png')
        self.ui_TestMainWorkSpace.setWindowIcon(QIcon(self.getPath_search_icon ))
Die Pfade wurden alle überprüft und sind korrekt. In beiden Fenstern sollen die unterschiedlichen Icons angezeigt werden. Wenn ich in der Mdi_Main()-Klasse auskommentiere, dann wird das Icon aus der Search_Window()-Klasse angezeigt. Hier habe ich das Gefühl, dass das Hauptfenster "dominiert".
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
Wenn wir Dir helfen sollen, muss Du schon minimalen lauffähigen Code zeigen, der das Problem beinhaltet. Sonst können wir das nicht nachvollziehen und es bleibt beim Rätselraten. Einzelne Codefetzen helfen da genauso wenig wie seitenweise Quelltext, den keiner lesen will.

Mit dem 2. Bild bin ich mir ziemlich sicher, dass Du Dir beide Probleme an anderer Stelle einhandelst. Ich vermute mal, dass Du sowohl das Schliessen der Mdi-Fenster als auch das Setzen des Icons nur auf Deinen Widgets vornimmst. Beides muss am MdiSubWindow passieren, da dieses für die Fensterrahmen/-behandlung zuständig ist. Im Falle des Icons erbt das MdiSubWindow einfach das Icon vom Hauptfenster, Dein Icon ist ja erst eine Ebene darunter gesetzt (schon wieder dieser Objektbaum ;) ).
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@jerch: Ich schwöre dir, zu Weihnachten werde ich diesen Objektbaum persönlich schmücken, mit Lametta, Kugeln und Krone. Und ihr könnt unter dem Objektbaum eure Geschenke platzieren :mrgreen: :lol: :) :D

Nun zum Problem. Das Problem konnte ich insofern lösen, als das ich mich von BlackJacks Alternative abgewandt habe, und wieder zu QMdiSubWindow übergegangen bin. Hier der Ausschnitt:
Modul: mdi.py

Code: Alles auswählen

    def create_search_form(self):
        from ..modules_ui.ui_pp_search import Search_Window
        # self.mdiArea.addSubWindow(Search_Window(self.up_to_date_it)).show() # Its original (BlackJack)
        mdi_subwindow_search = QMdiSubWindow()

        search_widget = Search_Window()
        mdi_subwindow_search.setAttribute(Qt.WA_DeleteOnClose)

        mdi_subwindow_search.setWidget(search_widget)
        self.mdiArea.addSubWindow(mdi_subwindow_search)
        search_widget.show()
Hier sehen wir, dass ich einfach nur BlackJacks Variante auskommentiert habe, und dazu übergegangen bin, die QMdiSubWindow-Klasse zu erstellen.

Modul: ui_pp_search.py

Code: Alles auswählen

class Search_Window(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        self.getPath_search_icon = os.path.join(os.path.abspath("."), 'files', 'images', 'img_16X16', 'Search.png')
        self.ui_pp_search.setWindowIcon(QIcon(self.getPath_search_icon))
Im Grunde habe ich nichts geändert. Wie gesagt, ich habe BlackJacks Variante nur ausgetauscht, und schon waren die Icons wieder da. Würde ich seine Variante wieder benutzen und alles andere auskommentieren, wären die Icons vom Hauptfenster wieder dort.
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: Wenn Du addSubWindow ein QWidget übergibst, wird intern genau das selbe gemacht, wie Du hier umständlich von Hand. Die eine Zeile von BlackJack macht also exakt das selbe, wie die 8 Zeilen von Dir. Warum ist getPath_search_icon ein Attribut? Und warum wird es vom aktuellen Pfad aus referenziert? Da Dateien sowieso immer vom aktuellen Verzeichnis aus gesucht werden, kannst Du das abspath auch weglassen, oder richtig benutzen: "os.path.abspath(os.path.join(...))"
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Danke für den Hinweis bezüglich der Path-Angabe. Wurde gleich umgesetzt.

Ich finde BlackJacks Variante auch wesentlich angenehmer, und übersichtlicher. Aber wenn ich seine anwende, dann werden die Icons nicht korrekt dargestellt. Auf meine Weise bekommen die Unterfenster ihre eigene Icons, und nicht die vom Hauptfenster.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
Das bei Deiner von-Hand-Version die Icons funktionieren, ist ein undokumentiertes Verhalten der QMdiSubWindows (nur aus Quellcode ersichtlich):
- im Konstruktor wird das Icon vom parent geholt (wenn Du dort als parent self übergibst, gehts wieder nicht; die verkürzte Version von BlackJack hat als parent das MdiArea mit dem anderen Icon)
- in setWidget - wenn Icon leer ist (konnte keines im Konstruktor setzen), schau ob das Subwidget eines mitbringt und setze ggf. dieses

Das die Qt-Leute Letzteres implementiert haben, halte ich für problematisch, da nicht mehr konsistent im Verhalten und undokumentiert.

Heisst für die verkürzte Variante - Icon an QMdiSubWindow von Hand setzen (ungetestet):

Code: Alles auswählen

subwindow = mdiArea.addSubWindow(QWidget())
subwindow.setWindowIcon(QIcon())
subwindow.show()
Antworten