Fenster mit QHBoxLayout schließen

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

BlackJack hat geschrieben:@Sophus: Das `MDIFormular`-Objekt muss dafür zu viel von den Fenstern wissen die darin dargestellt werden. Dass das Objekt eine Schaltfläche zum Schliessen besitzt und das dieses Attribut `BClose` heisst, sollte das `MDIFormular`-Objekt nicht wissen müssen. Und dann funktioniert das *so* ja gerade mal mit *einem* Fenster. So einen MDI-Bereich benutzt man ja weil man mehrere Fenster haben möchte.
Da fiel mir noch ein, dass ich mit dem MenuBar und dem ToolBar und ähnlich verfahre. Beide lagere ich in ein Modul aus, und sie werden dann in das Modul MDIForm.py importiert und anschließend in die Klasse "MDIFormular" hinzugefügt. Und wenn ich deine Logik verfolge, dann müsste ich das mit dem 'QObject' und mit dem sogenannten "Objektbaum" genauso verfahren, da sonst das Widget 'QMdiArea' nicht nur zu viel von den ganzen QWidgets und deren Widgets weiß, sondern auch noch zu viel von QMenuBar und QToolBar und deren Widgets. Denn immerhin weiß QMdiArea ja welche Buttons die QToolBar und MenuItems die QMenuBar haben, um dadurch Funktionen aufrufen zu können.
BlackJack

@Sophus: Dss gesagte ergibt für mich absolut keinen Sinn. Sorry. Du wirfst Begrifflichkeiten durcheinander und soweit ich den letzten Quelltext von dem ganzen in Erinnerung habe, weiss das `QMdiArea`-Exemplar nichts von dem Toolbox-Exemplar. Warum sollte es auch?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

BlackJack hat geschrieben:@Sophus: Dss gesagte ergibt für mich absolut keinen Sinn. Sorry. Du wirfst Begrifflichkeiten durcheinander und soweit ich den letzten Quelltext von dem ganzen in Erinnerung habe, weiss das `QMdiArea`-Exemplar nichts von dem Toolbox-Exemplar. Warum sollte es auch?
Ich werfe Begrifflichkeiten durcheinander? Welche schon wieder?

Hier ein kleiner Ausschnitt:

Code: Alles auswählen

import sys  

from PyQt4.QtGui import QMainWindow, QApplication, QAction, QMenuBar, QToolBar, QIcon, QMdiArea, QScrollBar, \
    QMdiSubWindow, QMessageBox, QWorkspace, QScrollArea

from PyQt4.QtCore import Qt

from MDIForm_Menue import MainWindow_MenuBar
from MDIForm_ToolBar import ToolBar_Manage, ToolBar_Close

class MDIFormular(QMainWindow):
    # Vererbung aktivieren, angeben, dass es keine Elternform hat
    def __init__ (self, parent=None):
        QMainWindow.__init__(self, parent)

        [...]
        self.MNUL_Verwaltung = MainWindow_MenuBar() # Hier wird der Klasse "MDIFormular" eine Instanz der Klasse "MenueLeiste" aus der Datei
                                             # MenueLeiste.py hinzufuegt.
        self.setMenuBar(self.MNUL_Verwaltung)# Anschließend wird durch die Methode "setMenuBar" der Klasse "MDIFormular"
                                             # die Instanz self.mnuMenueleiste übergeben.
                                             # Hier muss  self.MNUL_Verwaltung als Paramter übergeben werden
                                             # --> also self.setMenuBar(self.MNUL_Verwaltung)

        [...]
        # Hier beispielsweise drei MenuItems
        self.MNUL_Verwaltung.DoShowmnuClose.triggered.connect(self.MnuCloseMain) # Funktion "MnuClose" wird aufgerufen
        self.MNUL_Verwaltung.menuLanguage.triggered.connect(self.MnuCloseMain) # Funktion "MnuClose" wird aufgerufen 
        self.MNUL_Verwaltung.DoShowmnuFilm.triggered.connect(self.CreateNewMovie) # Funktion "CreateNewMovie" wird aufgerufen

        # Die Klasse ToolBar_Manage() wird hinzugefügt
        self.DoShowToolBar = ToolBar_Manage()
        self.addToolBar(self.DoShowToolBar)

        # Die Toolbar links anordnen
        self.addToolBar(Qt.LeftToolBarArea,self.DoShowToolBar)

        self.DoShowToolBar.actAddMovie.triggered.connect(self.MnuCloseMain)
        [...]
Ich muss mich entschuldigen, das Widget QMdiArea weiß natürlich nichts von QMenuBar und QToolBar, aber die Klasse "MDIFormular" (abgeleitet von QMainWindow) weiß eine jede Menge über QToolBar und QMenuBar. Hier weiß "MDIFormular" welche MenuItms die QMenuBar hat, und welche Buttons QToolBar hat. Wäre das an dieser Stelle nicht genauso weniger gut wie im Falle mit dem Exemplar "QMdiArea"? Oder geht das hier in Ordnung? Ich frage nur deshalb, weil du meintest, dass die QMdiArea zu viel über die Widgets von "MyForm" (abgeleitet von QWidget). Sprich die QMdiAre wusste zu viel über den PushButton (Bclose) bescheid. Und parallel dachte ich, wäre das auch mit den QToolBar und QMenuBar genauso.
BlackJack

@Sophus: Auf jeden Fall ist es ungünstig das die Aktionen jeweils zweimal verbunden werden müssen. Normalerweise erstellt man die Aktionen als `QAction`-Exemplare und benutzt dann das gleiche Exemplar sowohl für das Menü als auch für die Toolbar.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

BlackJack hat geschrieben:@Sophus: Auf jeden Fall ist es ungünstig das die Aktionen jeweils zweimal verbunden werden müssen. Normalerweise erstellt man die Aktionen als `QAction`-Exemplare und benutzt dann das gleiche Exemplar sowohl für das Menü als auch für die Toolbar.
Beziehst du dich gerade auf Zeile 26 und 27?
BlackJack

@Sophus: Kann ich aus dem Quelltextausschnitt nicht sagen. Vielleicht ja, vielleicht nein. Wahrscheinlich eher ja.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

BlackJack hat geschrieben:@Sophus: Kann ich aus dem Quelltextausschnitt nicht sagen. Vielleicht ja, vielleicht nein. Wahrscheinlich eher ja.
Nun, diese beiden Zeilen

Code: Alles auswählen

        self.MNUL_Verwaltung.DoShowmnuClose.triggered.connect(self.MnuCloseMain) # Funktion "MnuClose" wird aufgerufen
        self.MNUL_Verwaltung.menuLanguage.triggered.connect(self.MnuCloseMain) # Funktion "MnuClose" wird aufgerufen
sind nur spielerisch angewendet worden. Das nur ein MenuItem vollkommen ausreicht um eine Anwendung zu schließen ist mir durchaus bewusst, auch wenn man es mir vielleicht nicht zutrauen mag. :-) Mir ging es hierbei viel mehr um die Fragestellung, ob dieses Verhalten mit dem Fall QMdiArea nicht einhergeht? Im Falle der Werkzeugleiste (QMenuBar), welche in einem Modul ausgelagert worden ist, und daher in das Modul "MDIForm.py" importiert und anschließend in die Klasse "MDIFormular" des Moduls "MDIForm.py" hinzugefügt werden muss, weiss die Klasse "MDIFormular" auch die ganzen MenuItems. In meinem Fall weiß die Klasse "MDIFormular" momentan zwei folgende MenuItems (DoShowmnuFilm, und DoShowmnuClose):

Code: Alles auswählen

        self.MNUL_Verwaltung.DoShowmnuFilm.triggered.connect(self.CreateNewMovie) # Funktion "CreateNewMovie" wird 
        self.MNUL_Verwaltung.DoShowmnuClose.triggered.connect(self.MnuCloseMain) # Funktion "MnuClose" wird aufgerufen
Man weiß ja auch, dass eine Werkzeugleiste im Verlauf der Entwicklung zunehmend mehrere MenuItems bekommen kann und auch wird. Demzufolge wäre das ein gleicher Effekt wie beim QMdiArea-Objekt. Wie bei diesem Objekt weiß am Ende die Klasse "MDIFormular" auch zu viel über die MenuBar. Und das war auch der Knackpunkt meiner Überlegung, ob man hierbei auch dieses QObject und diesen Objektbaum anwenden soll, da sonst die Klasse "MDIFormular" am Ende nachher zu viel weißt? Denn beim Objekt QMdiArea hast du es ja kritisiert.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
Vllt. hilft Dir ein rundimentäres Beispiel weiter?

Das Beispiel setzt ein ein QMdiArea als centralWidget eines QMainWindow. Die Aktion createNew erstellt ein neues SubWindow, die Aktion closeActive schliesst das derzeit aktive SubWindow. Das Flag QtCore.Qt.WA_DeleteOnClose sorgt dafür, dass beim Schliessen eines SubWindows die Resourcen freigegeben werden.

Code: Alles auswählen

from PyQt4 import QtGui, uic, QtCore


class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self, parent)
        uic.loadUi('main.ui', self)
        self.setCentralWidget(QtGui.QMdiArea())
        self.actionCreateNew.triggered.connect(self.createNew)
        self.actionCloseActive.triggered.connect(self.closeActive)

    def createNew(self):
        w = QtGui.QWidget()
        self.centralWidget().addSubWindow(w)
        w.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        w.show()

    def closeActive(self):
        active = self.centralWidget().activeSubWindow()
        if active:
            active.close()


if __name__ == '__main__':
    app = QtGui.QApplication([])
    w = MainWindow()
    w.show()
    app.exec_()

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="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>499</width>
    <height>378</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget"/>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>499</width>
     <height>24</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
  <widget class="QToolBar" name="toolBar">
   <property name="windowTitle">
    <string>toolBar</string>
   </property>
   <attribute name="toolBarArea">
    <enum>TopToolBarArea</enum>
   </attribute>
   <attribute name="toolBarBreak">
    <bool>false</bool>
   </attribute>
   <addaction name="actionCreateNew"/>
   <addaction name="actionCloseActive"/>
  </widget>
  <action name="actionCloseActive">
   <property name="text">
    <string>closeActive</string>
   </property>
  </action>
  <action name="actionCreateNew">
   <property name="text">
    <string>createNew</string>
   </property>
  </action>
 </widget>
 <resources/>
 <connections/>
</ui>
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo jerch, vielen Dank für dein rudimentäres Beispiel, aber du gibst der Klasse "MainWindow" die MenuItems (actionCreateNew und actionCloseActive) bekannt, so weiß das MainWindow am Ende doch auch zu viel. Hier dein Code.

Code: Alles auswählen

        [...]
        self.actionCreateNew.triggered.connect(self.createNew)
        self.actionCloseActive.triggered.connect(self.closeActive)
        [...]
Warum ich das anspreche? BlackJack hat beim QMdiArea-Objekt kritisiert, dass er einfach zu viel wisse, was nicht sein muss und soll(?). Hier mein Code in kurzer Fassung: Konzentriere dich mal auf Zeile 20 und auf die Aktion in Zeile 24.

Code: Alles auswählen

import sys
from PyQt4.QtGui import QMainWindow, QApplication, QAction, QMenuBar, QToolBar, QIcon, QMdiArea, QScrollBar, \
    QMdiSubWindow, QMessageBox, QWorkspace, QScrollArea
 
from PyQt4.QtCore import Qt
from Xarphus.TestDialog import MyForm
 
class MDIFormular(QMainWindow):
    # Vererbung aktivieren, angeben, dass es keine Elternform hat
    def __init__ (self, parent=None):
        QMainWindow.__init__(self, parent)
        [...]
        self.MDIFenster = MyForm()
 
        self.mdiArea = QMdiArea()                                       # Widget QMdiArea hinzufügen
        self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) # Horizontalen Scrollbalken hinzufügen
        self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)   # Vertikalen Scrollbaken hinzufügen
        self.setCentralWidget(self.mdiArea)
 
        self.MDIFenster.BClose.clicked.connect(self.CloseQWidget) # Widget "PushButton" aus dem Modul "TestDialog.py" wird in die Klasse "MDIFormular" hinzugefügt.      
        [...]
 
# Widget Pushbutton "Bclose" ist mit dieser Funktion verknüpft.
    def CloseQWidget(self):
        self.subwindow.close()
        print "Dialog wurde soeben erfolgreich geschlossen."
 
if __name__ == "__main__":
    print 'Dieses Programm läuft als Main.'
else:
    print 'Das Programm wird von einem anderen Modul importiert.'
        #MDIFormular
app = QApplication(sys.argv)
app.setAttribute(Qt.AA_DontShowIconsInMenus, False)
MDIWindow = MDIFormular()
MDIWindow.showMaximized()
sys.exit(app.exec_())
Nach BlackJacks Aussage sei diese Lösung nicht gut - mit folgender Begründung:
BlackJack hat geschrieben:@Sophus: Das `MDIFormular`-Objekt muss dafür zu viel von den Fenstern wissen die darin dargestellt werden. Dass das Objekt eine Schaltfläche zum Schliessen besitzt und das dieses Attribut `BClose` heisst, sollte das `MDIFormular`-Objekt nicht wissen müssen. Und dann funktioniert das *so* ja gerade mal mit *einem* Fenster. So einen MDI-Bereich benutzt man ja weil man mehrere Fenster haben möchte. Und zwar normalerweise auch vom selben Typ. Weshalb ich diese Einschränkung auf ein Fenster nicht so ganz verstehe. Zumal der Umweg über den Fenstertitel umständlich ist. Und eine Testmethode die entweder `True` oder `None` statt `True` oder `False` zurück gibt hat eine unschöne API.
Nun sehe ich in deinem rudimentären Beispiel als auch in meinem Vorgehen parallelen. Wieso darf die Klasse "MainWindow" in deinem Fall viel über die MenuItems wissen, aber die QMdiArea soll am besten nicht viel über die Widgets auf einem QWidget wissen (hier wäre es dann der Schließ-Button auf einem QWidget, um das Fenster zu schließen). Und genau das verwirrt mich gerade.
BlackJack

@Sophus: jerch gibt dem `MainWindow` keine Menüpunkte bekannt. Die Aktionsobjekte kennt das Fenster bereits, die gehören zum Fenster. Das Verbindet seine eigenen Aktionen mit eigenen Methoden. Und das Fenster muss auch wissen was man mit/in ihm anstellen kann. Bei Deinem Code hat das Fenster aber auf ein Unterwidget eines anderen Fensters zugegriffen das dieses andere Fenster schliessen sollte. Dazu musste das Fenster die innere Struktur des Unterfensters kennen und wissen das es da eine Schaltfläche zum Schliessen gibt, und wie die heisst. In Jerch's Code kommt so eine Schaltfläche innerhalb des Unterfensters gar nicht vor.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
Wegen der Aktionen siehe BlackJacks Antwort.

Was Du mit Deinem Design verletzt, sind die Zuständigkeiten innerhalb der aufgespannten Objektbäume. Zum einen sollte die Sichtbarkeit von Objekten nur soweit gehen, wie unbedingt nötig für die Funktionalität, zum anderen sollte das Design den Vorgaben des Frameworks/Toolkits folgen und nicht dagegen programmiert sein.
Qt ist ein C++ Framework, wenn Du in C++ mit den Objektreferenzen/-pointer wild umherwedelst, wird der Code unwartbar und Segfaults sind spätestens beim Abräumversuch der Objekte vorprogrammiert. Das ist u.a. ein Grund, warum Qt diese Sache über das `parent`-Argument versucht zu normieren und einen Objektbaum vorhält. Auch werden die höheren Metakonzepte wie Eventbubbling hierüber realisiert.

Ich gebe zu, dass es einiger Erfahrung bedarf, die Erstellung und die Zuständigkeiten der Objekte richtig zu verwalten, die Designpattern zu OOP füllen Bücherregale. ;)
Sichere Hinweise auf ein überdenkenswertes Design in Bezug auf den Objektbaum von Qt sind z.B. (was mir auf die Schnelle einfällt):
1) Du brauchst Referenzen sowohl in child als auch parent-Richtung (ausser bei speziellen Datenstrukturen ist das meist nicht nötig, `parent()` muss man defacto nie anprogrammieren und widerspricht der Orchestrierung von oben)
2) Du brauchst Referenzen von Nachbarn über 2 Ebenen
3) Du brauchst Referenzen von irgendwo aus dem Baum (worst case für die Wartbarkeit und ein Hinweis auf falsche Aufgabenverteilung/Design bzw. Signal/Slot nicht verstanden)
In Deinem Code sind Anteile von 2) und 3) vorhanden.
BlackJack

@jerch: Wobei ich 1) als Lösung für das Schliessen verwenden würde wenn man nicht von `MdiSubWindow` erben möchte sondern ein allgemeines `QWidget` als Basis für das Unterfenster nimmt. Also ja eigentlich für den Inhalt.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

BlackJack hat geschrieben:@Sophus: jerch gibt dem `MainWindow` keine Menüpunkte bekannt. Die Aktionsobjekte kennt das Fenster bereits, die gehören zum Fenster. Das Verbindet seine eigenen Aktionen mit eigenen Methoden. Und das Fenster muss auch wissen was man mit/in ihm anstellen kann. Bei Deinem Code hat das Fenster aber auf ein Unterwidget eines anderen Fensters zugegriffen das dieses andere Fenster schliessen sollte. Dazu musste das Fenster die innere Struktur des Unterfensters kennen und wissen das es da eine Schaltfläche zum Schliessen gibt, und wie die heisst. In Jerch's Code kommt so eine Schaltfläche innerhalb des Unterfensters gar nicht vor.
Um zu sehen, ob ich nun was von dir gelernt habe. Übrigens, wenn du Kritik übst, kannst du mir dabei auch die Zeilennummern erwähnen, damit ich auch mitschneiden kann, was du da wirklich kritisierst? Das wäre nämlich klasse. Ich werde jetzt mal in sehr kleinen Schritten vorgehen, damit ich auch mitschneiden kann, wenn man mir den Fehler aufzeigen will. Dabei werde ich immer im Wechsel jerchs Code und meinen Code gegenüberstellen. Hier nun die Codes:

jerch #1

Code: Alles auswählen

from PyQt4 import QtGui, uic, QtCore
Sophus #1

Code: Alles auswählen

import sys 
 
from PyQt4.QtGui import QMainWindow, QApplication, QAction, QMenuBar, QToolBar, QIcon, QMdiArea, QScrollBar, \
    QMdiSubWindow, QMessageBox, QWorkspace, QScrollArea, QWidget

from PyQt4.QtCore import Qt

from Xarphus.TestDialog import MyForm

jerch #2

Code: Alles auswählen

class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self, parent)
        uic.loadUi('main.ui', self)
        self.setCentralWidget(QtGui.QMdiArea())
Sophus #2

Code: Alles auswählen

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

        UnterFenster = MyForm()

        self.mdiArea = QMdiArea()                                                  # Widget QMdiArea hinzufügen
        self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) # Horizontalen Scrollbalken hinzufügen
        self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)    # Vertikalen Scrollbaken hinzufügen
        self.setCentralWidget(self.mdiArea)

jerch #3

Code: Alles auswählen

        self.actionCreateNew.triggered.connect(self.createNew)
        self.actionCloseActive.triggered.connect(self.closeActive)
Sophus #3

Code: Alles auswählen

        self.UnterFenster.BClose.clicked.connect(self.CloseQWidget)  

jerch #4

Code: Alles auswählen

    def createNew(self):
        w = QtGui.QWidget()
        self.centralWidget().addSubWindow(w)
        w.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        w.show()
Sophus #4

Code: Alles auswählen

    def CreateNewMovie(self):
        if  self.existUnterfenster(self.Dict_Language.Dict_TestDialog["Title"]):
            reply = QMessageBox.critical(self, (self.Dict_Language.Dict_Message_WindowExists["MessageTitle"]),
            self.Dict_Language.Dict_Message_WindowExists["MessageText"])
            print "MessageBox wurde ausgegeben."
        else:
            MDIFenster = QWidget() # NameError: global name 'QWidget' is not defined
            self.centralWidget().addSubWindow(MDIFenster)
            MDIFenster.setAttribute(Qt.WA_DeleteOnClose)             # Flag setzen
            MDIFenster.show()
            print "Dialog lädt"

jerch #5

Code: Alles auswählen

    def closeActive(self):
        active = self.centralWidget().activeSubWindow()
        if active:
            active.close()
Sophus #5

Code: Alles auswählen

    def CloseQWidget(self):
        active = self.centralWidget().activeSubWindow()
        if active:
            active.close()
            print "Dialog wurde soeben erfolgreich geschlossen."

jerch #6

Code: Alles auswählen

if __name__ == '__main__':
    app = QtGui.QApplication([])
    w = MainWindow()
    w.show()
    app.exec_()
Sophus #6

Code: Alles auswählen

if __name__ == "__main__":
    print 'Dieses Programm läuft als Main.'
else:
    print 'Das Programm wird von einem anderen Modul importiert.'
        #MDIFormular
app = QApplication(sys.argv)
app.setAttribute(Qt.AA_DontShowIconsInMenus, False)
MDIWindow = MDIFormular()
MDIWindow.showMaximized()
sys.exit(app.exec_())
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

Dank der Anmerkung Blackjacks und jerchs habe ich versucht, das Problem wie folgt zu lösen - natürlich in der Hoffnung, dass es diesmal richtig ist. Kritik und Anmerkungen sind wie immer erwünscht. In meinem Code werden die ausgelagerten MenuBar und die ToolBar importiert. Dazu noch das Modul TestDialog.py. In der Klasse 'MDIFormular' werden zwei Funktionen/Methoden aufgerufen, einmal createToolBar und createMenuBar. Durch ein 'triggered'-Ereignis wird dann die Funktion/Methode 'createNew' aufgerufen. Damit möchte ich verhindern, dass die Klasse so wenig wie möglich von den anderen Klassen weiß (Stichwort: Objektbaum).

Aber ich habe noch ein paar Fragen. Die beiden Funktionen/Methoden (createToolBar und createMenuBar) werden nahezu zeitgleich aufgerufen. Da der Interpreter wie üblich von oben nach unten liest, wird natürlich die Funktion aufgerufen die an oberster Stelle steht. Wie würdet ihr es machen? Ich dachte zuerst daran, erst die Funktion/Methode "createMenuBar" aufzurufen, und in eben dieser eben aufgerufenen Funktion dann die weitere Funktion "createToolBar" aufrufen. Quasi prozedual? Des Weiteren habe ich diesen beiden eben genannten Funktionen/Methoden die magischen 'self()'-Argumenten vorangestellt. Wäre das an dieser Stelle nicht eher überflüssig? Schließlich werden in den Funktionen die Parameter self als Referenz übergeben, richtig? bleiben wir noch bei diesem magischen self. Innerhalb der Funktion 'createMenuBar' habe ich folgendes geschrieben "self.MNUL_Verwaltung". In jerchs Beispiel wurde vor seiner referenz 'w' kein self-Argument gesetzt. Hier sein Beispiel:

Code: Alles auswählen

 def createNew(self):
        w = QtGui.QWidget()
        self.centralWidget().addSubWindow(w)
        w.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        w.show()
Hier mein Code:

MDIForm.py

Code: Alles auswählen

# -*- coding: utf-8 -*-
import sys  

from PyQt4.QtGui import QMainWindow, QApplication, QAction, QMenuBar, QToolBar, QIcon, QMdiArea, QScrollBar, \
    QMdiSubWindow, QMessageBox, QWorkspace, QScrollArea, QWidget

from PyQt4.QtCore import Qt

from MDIForm_ToolBar import ToolBar_Manage, ToolBar_Close
from Setting.About import InfoApp
from TestDialog import MyForm

class MDIFormular(QMainWindow):
    # Vererbung aktivieren, angeben, dass es keine Elternform hat
    def __init__ (self, parent=None):
        QMainWindow.__init__(self, parent)

        self.createToolBar() # Funktion aufrufen
        self.createMenuBar() # Funktion aufrufen
        [...]
        self.mdiArea = QMdiArea()                                       # Widget QMdiArea hinzufügen
        self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) # Horizontalen Scrollbalken hinzufügen
        self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)   # Vertikalen Scrollbaken hinzufügen
        self.setCentralWidget(self.mdiArea)  
        [...]
    def createMenuBar(self):
        self.MNUL_Verwaltung = MainWindow_MenuBar() # Hier wird der Klasse "MDIFormular" eine Instanz der Klasse "MenueLeiste" aus der Datei
                                             # MenueLeiste.py hinzufuegt.
        self.setMenuBar(self.MNUL_Verwaltung)# Anschließend wird durch die Methode "setMenuBar" der Klasse "MDIFormular"
                                             # die Instanz self.mnuMenueleiste übergeben.
                                             # Hier muss  self.MNUL_Verwaltung als Paramter übergeben werden
                                             # --> also self.setMenuBar(self.MNUL_Verwaltung)



        self.MNUL_Verwaltung.DoShowmnuClose.triggered.connect(self.MnuCloseMain)
        self.MNUL_Verwaltung.menuLanguage.triggered.connect(self.MnuCloseMain)
        self.MNUL_Verwaltung.DoShowmnuFilm.triggered.connect(self.createNew) # Funktion "CreateNewMovie" wird aufgerufen



    def createToolBar(self):
        self.DoShowToolBar = ToolBar_Manage()
        self.addToolBar(self.DoShowToolBar)
        # Die Toolbar links anordnen
        self.addToolBar(Qt.LeftToolBarArea,self.DoShowToolBar)

        #self.DoShowToolBar.actAddMovie.triggered.connect(self.MnuCloseMain)

        self.DoShowToolBar1 = ToolBar_Close()
        self.addToolBar(self.DoShowToolBar1)

        self.DoShowToolBar1.actAddExit.triggered.connect(self.MnuCloseMain)

        #self.MDIFenster.BClose.clicked.connect(self.CreateNewMovie)


    def createNew(self):
        if  self.existUnterfenster(self.Dict_Language.Dict_TestDialog["Title"]):
            reply = QMessageBox.critical(self, (self.Dict_Language.Dict_Message_WindowExists["MessageTitle"]),
            self.Dict_Language.Dict_Message_WindowExists["MessageText"])
            print "MessageBox wurde ausgegeben."
        else:
            self.w = MyForm(self)
            self.SuWindow = self.centralWidget().addSubWindow(self.w)
            self.w.setAttribute(Qt.WA_DeleteOnClose)
            self.w.BClose.clicked.connect(self.closeActive)
            self.w.show()

    def closeActive(self):
        active = self.centralWidget().activeSubWindow()
        if active:
            active.close()
        print "Code durchgeführt"
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
Das ist immernoch von hinten durch die Brust programmiert. Aber zunächst der Reihe nach:
Sophus hat geschrieben:... Ich dachte zuerst daran, erst die Funktion/Methode "createMenuBar" aufzurufen, und in eben dieser eben aufgerufenen Funktion dann die weitere Funktion "createToolBar" aufrufen. Quasi prozedual? ...
Beide Funktionen übernehmen Konstruktoraufgaben und sind nicht voneinander abhängig. Man könnte die Blöcke daher auch in __init__ packen. Als Subaufruf innerhalb der jeweils anderen macht keinen Sinn - es verschleiert die Unabhängigkeit und macht die Nachvollziehbarkeit des Kontrollflusses unnötigerweise anstrengender. Eine Funktion/Methode sollte eine Funktionseinheit sein, wenn Du z.B. createToolBar in createMenuBar aufrufst, wäre der Name für letzteres "falsch" - die Methode müsste dann eigentlich createMenuAndToolBar heissen.
Sophus hat geschrieben:... Des Weiteren habe ich diesen beiden eben genannten Funktionen/Methoden die magischen 'self()'-Argumenten vorangestellt. Wäre das an dieser Stelle nicht eher überflüssig? ...
Nein. Das self brauchst Du hier, da Python Attribute und Methoden auch innerhalb der Klasse/Exemplare nur über eine Objektreferenz auflöst (C++ z.B. löst das automagisch auf). Das ist der erste Parameter in Methoden, der konventionell mit self angegeben wird.
Sophus hat geschrieben:... Schließlich werden in den Funktionen die Parameter self als Referenz übergeben, richtig? bleiben wir noch bei diesem magischen self. Innerhalb der Funktion 'createMenuBar' habe ich folgendes geschrieben "self.MNUL_Verwaltung". In jerchs Beispiel wurde vor seiner referenz 'w' kein self-Argument gesetzt...
An self ist nichts magisch - es ist einfach der erste Parameter in Methoden, der auf ein bestimmtes Objekt zeigt. Bei normalen Methoden ist es das Exemplar selbst (daher der Name). Eine Zuweisung innerhalb einer Methode mit vorangestelltem self führt dazu, dass das zubindende Objekt auf der rechten Seite objektweit sichtbar wird (Attribut). Dadurch kann man es auch in anderen Methoden oder von aussen benutzen. Ohne self ist die Variable (besser Name) lokal, d.h. verliert die Gültigkeit nach Verlassen des Blocks (+ Markieren zum Aufräumen des Objektes in Python). Das sind aber doch Pythongrundlagen, bitte lies Dir das über ein Tutorial an.
Warum ich kein self vor dem w habe - ich brauche ausserhalb von createNew keinen direkten Zugriff auf das Widget-Exemplar. Zusätzlich weiss ich, dass Python das Exemplar trotz lokaler Bindung nicht abräumt, da es über addSubWindow am centralWidget hängt. Gleiches gilt auch für `self.setCentralWidget(QtGui.QMdiArea())` in meinem Bsp. oben.

Nun zu Deinem Design/Code:
Ich würde die Menü- und Toolbarsache eher im MainWindow halten und die Qt-Standardklassen dafür verwenden. Dann ist das sehr hübsch über den Designer realisierbar ohne viel Quellcode. Was können Deine Kindklassen denn mehr, das Dir am Qt-Portfolio fehlte?

In Zeile 64 und 65 bindest Du Exemplare von MyForm und dem umgebenden SubWindow an Attribute. Da Du im gezeigten Code sonst nichts weiter damit in anderen Methoden machst, brauchst Du die nicht, da sich Qt darum kümmert. Ohne Qt musst Du sie objektweit binden, sonst räumt Python das Objekt auf. Dann solltest Du aber einen Containertypen nutzen, um alle Referenzen zu merken. So wie Du es jetzt umgesetzt hast, sind mit einem neuen Subfenster die Referenzen (und ohne Qt auch die Objekte) älterer noch offener Subfenster weg.

Zeile 67 + 70-73:
Arrgh! Bitte überleg Dir mal, was Du da machst. Was ich lese: "Ein Button auf w ruft `closeActive` auf, welches das derzeit aktive Fenster der MdiArea schliesst." Wolltest Du das? Oder eher das eigene Subfenster schliessen? Falls letzteres - da ist zuviel Magie drin. Was passiert beim Klicken eines Buttons wohl mit dem Fokus der Subfenster? Ändert der sich? Vor oder nach Singal/Slot-Verarbeitung? Ist das verlässlich?
Mir ist dieser Button eh schleierhaft - wofür brauchst Du den? Die Subfenster bringen doch alles dafür mit.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo jerch,
irgendwie verstehe ich dich besser als bei Blackjack. Ich möchte seine Fähigkeiten bezüglich der Pythonprogrammierung nicht in Frage stellen, aber in Puncto Erklärung bleibe ich bei BlackJacks Erklärung immer auf der Strecke. Daher möchte ich dir für deine Geduld danken :-) Nun gehen wir mal deine Anmerkungen Stück für Stück durch.
jerch hat geschrieben: Beide Funktionen übernehmen Konstruktoraufgaben und sind nicht voneinander abhängig. Man könnte die Blöcke daher auch in __init__ packen. Als Subaufruf innerhalb der jeweils anderen macht keinen Sinn - es verschleiert die Unabhängigkeit und macht die Nachvollziehbarkeit des Kontrollflusses unnötigerweise anstrengender. Eine Funktion/Methode sollte eine Funktionseinheit sein, wenn Du z.B. createToolBar in createMenuBar aufrufst, wäre der Name für letzteres "falsch" - die Methode müsste dann eigentlich createMenuAndToolBar heissen.
Hier muss ich dich mal ganz naiv fragen: Was meinst du mit "pack die Methoden in die __init__? Du meinst bestimmt in def __init__(self, parent=None):? Und wie "packt" man dort eine Methode/Funktion rein?
jerch hat geschrieben: Nun zu Deinem Design/Code:
Ich würde die Menü- und Toolbarsache eher im MainWindow halten und die Qt-Standardklassen dafür verwenden. Dann ist das sehr hübsch über den Designer realisierbar ohne viel Quellcode. Was können Deine Kindklassen denn mehr, das Dir am Qt-Portfolio fehlte?
Ich möchte erst einmal versuchen Benutzeroberflächen von Hand zu schreiben, und QT-Designer vorerst aus dem Weg gehen. Und weshalb ich die MenuBar ausgelagert habe und nicht im MainWindow halte? Nun, die MenuBar nimmt an MenuItems zu, und ich will nicht, dass allein durch die MenuBar der Quelltext im MainWindow unendlich lang wird. Im Modul MDI_Menu.py sind schon rund 230 Zeilen an Code. Und dabei ist die MenuBar nach meinen Vorstellungen noch nicht komplett. Und wenn ich das alles im MainWindow halten würde, wäre es ganz schön unübersichtlich.
jerch hat geschrieben: In Zeile 64 und 65 bindest Du Exemplare von MyForm und dem umgebenden SubWindow an Attribute. Da Du im gezeigten Code sonst nichts weiter damit in anderen Methoden machst, brauchst Du die nicht, da sich Qt darum kümmert.
Diese Zeilen hast du angesprochen. Wer sind Exemplare von MyForm?

Code: Alles auswählen

[...]
            self.w = MyForm(self)
            self.SuWindow = self.centralWidget().addSubWindow(self.w)
[...]
Und mit Attribute meinst du in diesem Fall diese Zeile (setAttribute)? Und diese Zeile kann ich komplett löschen? Ich dachte, nur so kann und soll ich den Flag setzen, damit aufgeräumt und Resourcen freigegeben werden.

Code: Alles auswählen

            self.w.setAttribute(Qt.WA_DeleteOnClose)
jerch hat geschrieben: Ohne Qt musst Du sie objektweit binden, sonst räumt Python das Objekt auf. Dann solltest Du aber einen Containertypen nutzen, um alle Referenzen zu merken. So wie Du es jetzt umgesetzt hast, sind mit einem neuen Subfenster die Referenzen (und ohne Qt auch die Objekte) älterer noch offener Subfenster weg.
Zwei Fragen: Was meinst du mit "ohne Qt"? Ich importiere eingangs Qt (from PyQt4.QtCore import Qt). Containertypen? Kannst du mir mal bitte ein kleines Pseudocode als Beispiel präsentieren, was du damit meinst?
jerch hat geschrieben: Zeile 67 + 70-73:
Arrgh! Bitte überleg Dir mal, was Du da machst. Was ich lese: "Ein Button auf w ruft `closeActive` auf, welches das derzeit aktive Fenster der MdiArea schliesst." Wolltest Du das? Oder eher das eigene Subfenster schliessen? Falls letzteres - da ist zuviel Magie drin. Was passiert beim Klicken eines Buttons wohl mit dem Fokus der Subfenster? Ändert der sich? Vor oder nach Singal/Slot-Verarbeitung? Ist das verlässlich?
Mir ist dieser Button eh schleierhaft - wofür brauchst Du den? Die Subfenster bringen doch alles dafür mit.
Du hast Recht, ich will das eigene SubWindow schließen. Und ich habe die Angelegenheit wie folgt gelöst:

Code: Alles auswählen

[...]
    def createNew(self):
        if  self.existUnterfenster(self.Dict_Language.Dict_TestDialog["Title"]):
            reply = QMessageBox.critical(self, (self.Dict_Language.Dict_Message_WindowExists["MessageTitle"]),
            self.Dict_Language.Dict_Message_WindowExists["MessageText"])
            print "MessageBox wurde ausgegeben."
        else:
            self.w = MyForm(self)
            self.SuWindow = self.centralWidget().addSubWindow(self.w)
            self.w.setAttribute(Qt.WA_DeleteOnClose)
            self.w.BClose.clicked.connect(self.closeSubWindow)
            self.w.show()
            
    def closeSubWindow(self):
        self.SuWindow.close()
    [...]
Wozu ich den Schließ-Button brauche? Ich weiß, in der Titelleiste eines jeden QWidgets und QDialoges kann man auf einen tollen X drücken. Aber ich möchte, dass der Anwender auch über einen Button das Fenster schließen kann. Mir ist schleierhaft, weshalb es dir schleierhaft sei? Es gibt sehr viele Anwendungen, die eben genauso arbeiten. Und außerdem denke ich ja auch schon einen Schritt weiter. Da es eine Datenbank-Anwendung werden soll, so sollen im Anschluss auf dem Klick "Speichern" die Datensätze in die Datenbank und das Fenster geschlossen werden und eine MessageBox ausgegeben werden, wo dann in Etwa steht "Erfolgreich gespeichert". Aber verlegen wir die Geschmacksfrage erstmal nach hinten.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Sophus hat geschrieben:Ich möchte erst einmal versuchen Benutzeroberflächen von Hand zu schreiben, und QT-Designer vorerst aus dem Weg gehen. Und weshalb ich die MenuBar ausgelagert habe und nicht im MainWindow halte? Nun, die MenuBar nimmt an MenuItems zu, und ich will nicht, dass allein durch die MenuBar der Quelltext im MainWindow unendlich lang wird. Im Modul MDI_Menu.py sind schon rund 230 Zeilen an Code. Und dabei ist die MenuBar nach meinen Vorstellungen noch nicht komplett. Und wenn ich das alles im MainWindow halten würde, wäre es ganz schön unübersichtlich.
Das im MainWindow zu halten, hat echte Vorteile - z.B. sind die Aktionsobjekte in beiden Leisten nutzbar. Das geht in Deinem Falle auch, allerdings bringst Du dann doch eine Abhängigkeit rein, wo eigentlich keine sein sollte - Du müsstest die Aktionen in einer Leiste erstellen und diese Leiste der anderen bekannt geben, um sie auch dort nutzen zu können.
Mehr Übersichtlichkeit schafft Deine Variante mMn nicht, da Du zum einem auf class-Ebene wunderbar mit Methoden strukturieren kannst (man kann diese idR auch ausblenden in gängigen Editoren) und auf der anderen Seite eh die Aktionen an Methoden im MainWindow bindest. Damit arbeitest Du wieder mit Implementationsdetails, wo es eigentlich nicht nötig wäre.
Die Sache mit dem zunächst Selbstschreiben lasse ich gelten - allerdings läufst Du hier Gefahr, ein Codemonster zu bauen, was Du in 3 Monaten nicht mehr anschauen magst. Mit dem Designer dagegen würdest Du Flexibilität gewinnen. Und für paradigmatische Fingerübungen sind kürzere Sachen geeigneter. Dabei lernt man auch eher, die Wiederverwendbarkeit als kostbar einzuschätzen und die Programme entsprechend zu strukturieren. 3 Probleme später kommt dann häufig der Aha-Moment "Das hab ich da doch schonmal ähnlich umgesetzt" und man kann sich aus dem eigenen Baukasten bedienen und gleich noch Verbesserungen vornehmen.
Sophus hat geschrieben:Diese Zeilen hast du angesprochen. Wer sind Exemplare von MyForm?
Sophus hat geschrieben:Und mit Attribute meinst du in diesem Fall diese Zeile (setAttribute)? Und diese Zeile kann ich komplett löschen? Ich dachte, nur so kann und soll ich den Flag setzen, damit aufgeräumt und Resourcen freigegeben werden.
Ehm weisst Du, wie man zu einem Exemplar kommt? Bzw. was das meint? Unterschied zu Klasse? Ich will Dir nicht zu nahe treten, aber Du scheinst eigentlich nicht verstanden zu haben, was da genau passiert. Die Begriffe sind z.T. verwaschen und ich habe nicht die Zeit und Lust, dass alles mit Dir durchzukauen. Ein gutes Buch zu OOP könnte helfen.
Sophus hat geschrieben:Zwei Fragen: Was meinst du mit "ohne Qt"? Ich importiere eingangs Qt (from PyQt4.QtCore import Qt). Containertypen?
Qt hält einen eigenen Objektbaum vor. Das "rettet" die Objekte im Zusammenspiel mit Python vorm Abräumen durch den GC. Wenn Du nicht weisst, wovon ich rede, dann solltest Du den Pythonweg gehen und das spezielle Verhalten durch den Qt-Objektbaum ignorieren. Wenn Dir das Python-Standardverhalten nicht klar ist - Python-Tutorial. Zum Thema Containertypen: http://lmgtfy.com/?q=container+types+programming&l=1 und für Python: http://lmgtfy.com/?q=container+types+python&l=1
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

jerch hat geschrieben: Ehm weisst Du, wie man zu einem Exemplar kommt? Bzw. was das meint? Unterschied zu Klasse? Ich will Dir nicht zu nahe treten, aber Du scheinst eigentlich nicht verstanden zu haben, was da genau passiert. Die Begriffe sind z.T. verwaschen und ich habe nicht die Zeit und Lust, dass alles mit Dir durchzukauen. Ein gutes Buch zu OOP könnte helfen.
Also, im Falle von:

Code: Alles auswählen

self.SuWindow = self.centralWidget().addSubWindow(self.w)
Ist SubWindow und w ein Exemplar, richtig? Denn über SubWindow komme ich an meinem QWidget heran, und mit w komme ich an die Klasse heran. So verstehe ich das.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Nein, leider völlig falsch. Deine Erklärung im 2. Satz ist Unsinn. `SubWindow` und `w` sind Attributnamen an `self`.

Um zu verstehen, was hier abläuft, geb ich Dir ein paar Fragen mit, welche hilfreich sein könnten:
- Was macht der Zuweisungsoperator in Python?
- Was macht eigentlich `Klassenname()` in Python?
- Unterschied zu `funktionsname()`?
- Was macht der Punktoperator in Python?
- Was sind Attribute?
- Was sind Methoden?
- Was macht die Methode `centralWidget`?
- Was macht die Methode `addSubWindow`?

Mit Rästelraten kommst Du nicht weiter.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

jerch hat geschrieben:Nein, leider völlig falsch. Deine Erklärung im 2. Satz ist Unsinn. `SubWindow` und `w` sind Attributnamen an `self`.

Um zu verstehen, was hier abläuft, geb ich Dir ein paar Fragen mit, welche hilfreich sein könnten:
- Was macht der Zuweisungsoperator in Python?
- Was macht eigentlich `Klassenname()` in Python?
- Unterschied zu `funktionsname()`?
- Was macht der Punktoperator in Python?
- Was sind Attribute?
- Was sind Methoden?
- Was macht die Methode `centralWidget`?
- Was macht die Methode `addSubWindow`?

Mit Rästelraten kommst Du nicht weiter.
- Was macht der Zuweisungsoperator in Python?
Ein Zuweisungsoperator ist ein Gleichheitszeichen. Eine Zuweisung besteht demzufolge aus einem Namen, gefolgt von einem Gleichheitszeichen und einem Wert. Zum Beispiel: x = 1 Hier ist x ein Name und 1 der Wert. Würde ich in einem Interaktionsmodus nun x eingeben, so würde ich den 1 bekommen. Ich stelle mir Variablen wie ein Schuhkarton vor. Der Name der Variable (hier: x) ist sozusagen die Aufschrift des Schuhkartons. Und der Wert 1 sind dann die Schuhe im Karton. Über den Namen der Variablen kann ich also auf meine Schuhe zugreifen.
- Was macht eigentlich `Klassenname()` in Python?
Ein Klassenname schreibt man mit großen Anfangsbuchstaben.
- Unterschied zu `funktionsname()`?
Damit eine Funktion im Programmteil aufgerufen werden kann, braucht es einen Namen, also Funktionsnamen. Im Gegensatz zu einer Name der Klasse beginnen Funktionsnamen mit kleinen Buchstaben.
- Was macht der Punktoperator in Python?
Nun, ich kenne in VB6 die Punktoperatoren als einen Zugriffoperator. Allerdings weiß ich nicht, ob du das meinst. In Python würde ich den Punktoperator sehr deutlich an einem Beispiel der MenuBar zeigen, den dort wird sehr viel mit Punktoperator gearbeitet. Zum Beispiel: self.Attribut.Menuitem.triggered.connect(self.Funktion/Methode).
- Was sind Attribute?
Zum Beispiel kann ein Attribut ein Attribut einer Instanz der Klasse sein und wird bzw. muss dann deshalb mit Hilfe von "self" mit der Instanz verknüpft werden.
- Was sind Methoden?
Man bedient sich an Methoden um eine Funktionalität zu erbringen. In meinem Fall ist die Methodenaufruf self.createToolBar. Mit diesem Aufruf wurde dann die Funktion def createToolBar(self) aufgerufen.
- Was macht die Methode `centralWidget`?
Hierbei ist es wichtig, dass ein QWidget vom QMainWindow erben muss, da QMainWindow diese Methode besitzt. Diese Methode dient dazu, dass man sie mit entsprechendem Inhalt füllen muss, da man auf einem Hauptfenster, welcher von der QMainWindow abgeleitet wurde, nicht einfach so Elemente hinzufügen kann.So Kann man zum Beispiel ein Objekt QWidget auf QMainWindow platzieren und als centralWidget setzen.
- Was macht die Methode `addSubWindow`?
Diese Methode macht es möglich ein QWidget oder auch ein QDialog zum MdiArea-Widget zum Inhalt zu machen. Vorher wird aber ein QWidget zum Inhalt von der Methode ‚addSubWindow()‘. Nachdem dies geschehen ist, wird das QWidget über diese Methode an die Methode ‚centralWidget()‘ gehängt.



Na komm, sag schon, dass ich alles falsch beantwortet habe oder irgendwas durcheinander gebracht habe. Mittlerweile bin ich es ja gewohnt :-)
Antworten