Child-Fenster geschlossen (Event)

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
Benutzeravatar
Klip
User
Beiträge: 98
Registriert: Donnerstag 10. August 2006, 20:39

Hallo =)

Ich habe ein kleines Problem. Und zwar entwickele ich gerade eine kleine Chat-Applikation mit PyQt4 und Python 2.6.

Ich habe ein Hauptfenster, welches die Kontaktliste darstellt (mit einem QListWidget). Klickt man auf einen Kontakt, öffnet sich ein Child-Fenster, in dem dann gechattet werden können soll. Das funktioniert alles soweit.

Jetzt möchte ich gerne ermitteln, wann eines dieser Child-Fenster geschlossen wird.

Code: Alles auswählen

class MainWindow(QtGui.QMainWindow):
    ''' Main window of the application. From here, other windows
        are initiated. '''

    def __init__(self, window_title="Title", parent=None):
        QtGui.QMainWindow.__init__(self, parent)
    
    # ...

Innerhalb des Child-Fensters habe ich hierzu closeEvent() definiert. Allerdings wird die Methode nicht ausgeführt (s.u.), wenn ich dieses Chatfenster schließe, sondern erst wenn ich das Kontaktlistenfenster und damit die Applikation schließe. Ich verstehe, nicht, wieso und konnte dazu auch keine Hinweise finden. Hat jemand eine Idee?

Code: Alles auswählen

class ChatWindow(QtGui.QMainWindow, Observed):
    ''' Child-window of the main-window. Is drawn when the user or
        one of his contacts initiates a chat-session. '''

    open_windows = []

    def __init__(self, parent=None, id_=''):
        QtGui.QMainWindow.__init__(self, parent)

        self.id_ = id_
        self.setWindowTitle(self.id_)

        ChatWindow.open_windows.append(self.id_)

        # ...

    def update(self, new_data):
        ''' Called by the XMPP-class when new data arrives.
            Attaches a new message to the textfield. '''

        self.message_output.append('{0}: {1}'.format(new_data[0],
                                                        new_data[1]))

    def closeEvent(self, event):
        ''' Called by QObject if the window is about to be closed. '''

        self.close_window()
        event.accept()

    def close_window(self):
        ''' Remove the window from the windowlist and close the
            window afterwards. '''

        # TODO: Wird aufgerufen wenn das Hauptfenster geschlossen wird, nicht
        # wenn ChatWindow geschlossen wird. WARUM?!
        print("aufgerufen:", end=str(self.id_))
            
        if self.id_ in ChatWindow.open_windows:
            ChatWindow.open_windows.remove(self.id_)
            
            # Prevent deletion of an empty object.
            if ChatWindow.open_windows is None:
                ChatWindow.open_windows = []
                
            self.close()
Ich würde mich sehr über Tips freuen =)

Beste Grüße von Klip
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Was macht das self.close() da unten in close_window()? close_window() wird doch schon via closeEvent getriggert, d.h. ein close() wurde vorher schon abgesetzt.

Auch gefällt mir Dein Design mit der klassen-"globalen" Fensterliste und dem id_ nicht. Wo kommt id_ her? Was passiert, wenn ein neues Exemplar eine schon vergebene id_ beansprucht? Der Wert ist ein bisschen magic, Du magst vllt. gute Gründe haben, den mitzuführen, aber für die Identifikation eines Exemplars ist die Objekt-ID, die Python automatisch vergibt, geeigneter bzw. reicht da ein einfaches ChatWindow.open_windows.append(self), um eine Referenz auf das Exemplar in der Liste vorzuhalten.
Die Fensterliste als Klassenvariable zu definieren, macht Dir spätestens dann Probleme, wenn Du mehrere Fenstergruppen verwalten musst und von Callerseite her die eigenen Subfenster identifizieren musst. Und da Du eh auf Callerseite eine Referenz mitführen musst, ist diese Liste redundant.

Ich würd hier eher Signale nutzen sowie die Verwaltungslogik der Nachbarexemplare aus den Subfenstern raushalten und in eine zentrale Instanz verlagern, einfaches Beispiel:

Code: Alles auswählen

from PyQt4 import QtGui, QtCore

class SubWidget(QtGui.QWidget):
    closing = QtCore.pyqtSignal()
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

    def closeEvent(self, evt):
        self.closing.emit()
        evt.accept()

class AppWidget(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        self.subs = []
        button = QtGui.QPushButton(self)
        button.clicked.connect(self.addWin)

    def addWin(self):
        w = SubWidget()
        w.closing.connect(self.removeWin)
        self.subs.append(w)
        w.show()

    def removeWin(self):
        self.subs.remove(self.sender())

    def closeEvent(self, evt):
        while self.subs:
            self.subs[0].close()
        evt.accept()
Du könntest in den Subfenstern auch mit callbacks anstatt der Signale arbeiten, was praktikabler ist, hängt von den Anforderungen ab.

Grüsse jerch
Benutzeravatar
Klip
User
Beiträge: 98
Registriert: Donnerstag 10. August 2006, 20:39

Hallo jerch,

id_ wird beim erzeugen des Fenster in MainWindow aus dem Kontaktnamen generiert. Ich wollte damit sicherstellen, dass es pro Kontakt nur ein Chatfenster gibt. Bevor so ein neues Fenster erzeugt wird, wird überprüft ob es ein solches bereits gibt. Dein Vorschlag erscheint mir aber wesentlich eleganter.

self.close() in close_window() ist ein Überbleibsel aus der Zeit, in der ich verschiedene Sachen ausprobiert habe, weil es nicht so klappen wollte, wie ich es geplant hatte. Hab vergessen es wieder rauszunehmen, danke für den Hinweis.

Deine Idee sieht spitze aus, werde sie nach der Arbeit direkt ausprobieren. Vielen Dank (:

Beste Grüße,

Klip

EDIT: Klappt ausgezeichnet (:
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Entschuldige bitte die etwas verspätete Antwort.
Für einmalige Erstellen eines bestimmten Exemplares (hier Kontaktperson bzw. Deine ID) ist das natürlich sinnvoll. Um das zu erreichen, brauchst Du das Bsp. nur ein wenig erweitern, hier mal mit einem QListView als Hauptfenster:

Code: Alles auswählen

from PyQt4 import QtGui, QtCore

class SubWidget(QtGui.QWidget):
    closing = QtCore.pyqtSignal()
    def __init__(self, parent=None, contact=None):
        QtGui.QWidget.__init__(self, parent)
        self.setWindowTitle("Chatfenster mit "+contact)
        self.contact = contact

    def closeEvent(self, evt):
        self.closing.emit()
        evt.accept()

    def showEvent(self, evt):
        # aktiviere Fenster und hole es nach vorn
        self.activateWindow()
        self.raise_()

class AppWidget(QtGui.QListView):
    def __init__(self, parent=None):
        QtGui.QListView.__init__(self, parent)
        self.setModel(QtGui.QStringListModel(['Paula', 'Erwin', 'Klaus']))
        self.subs = []
        self.clicked.connect(self.showWin)

    def showWin(self, item):
        contact_id = item.data().toString()
        for widget in self.subs:
            if widget.contact == contact_id:
                widget.showNormal()                  # bei minimierten Fenstern
                widget.showEvent(QtGui.QShowEvent()) # sonst reicht dies
                break
        else:
            widget = SubWidget(contact=contact_id)
            widget.closing.connect(self.removeWin)
            self.subs.append(widget)
            widget.show()

    def removeWin(self):
        self.subs.remove(self.sender())

    def closeEvent(self, evt):
        while self.subs:
            self.subs[0].close()
        evt.accept()

if __name__ == '__main__':
    app = QtGui.QApplication([])
    win = AppWidget()
    win.show()
    app.exec_()
Ein Mapping via Dictionary ist mMn nicht sinnvoll, da die Fensterliste wohl kaum sehr groß werden dürfte.
Das Fensterverhalten mußt Du Deinen Vorstellungen anpassen, das Bsp. hier stellt minimierte Fenster normal dar, aktiviert und holt Fenster nach vorn.
lunar

@jerch: Das hat doch nichts mit der Größe der Liste zu tun. In diesem Beispiel wäre ein Wörterbuch einfach angenehmer zu nutzen.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

lunar hat geschrieben:@jerch: Das hat doch nichts mit der Größe der Liste zu tun. In diesem Beispiel wäre ein Wörterbuch einfach angenehmer zu nutzen.
Hast ja Recht. (War schon etwas spät ;) )
Nochmal mit ID->Referenz Mapping:

Code: Alles auswählen

from PyQt4 import QtGui, QtCore

class SubWidget(QtGui.QWidget):
    closing = QtCore.pyqtSignal()
    def __init__(self, parent=None, contact=None):
        QtGui.QWidget.__init__(self, parent)
        self.setWindowTitle("Chatfenster mit "+contact)
        self.contact = contact

    def closeEvent(self, evt):
        self.closing.emit()
        evt.accept()

    def showEvent(self, evt):
        self.activateWindow()
        self.raise_()

class AppWidget(QtGui.QListView):
    def __init__(self, parent=None):
        QtGui.QListView.__init__(self, parent)
        self.setModel(QtGui.QStringListModel(['Paula', 'Erwin', 'Klaus']))
        self.subs = {}
        self.clicked.connect(self.showWin)

    def showWin(self, item):
        contact_id = item.data().toString()
        widget = self.subs.get(contact_id)
        if not widget:
            widget = SubWidget(contact=contact_id)
            self.subs[contact_id] = widget
            widget.closing.connect(self.removeWin)
        widget.showNormal()
        widget.showEvent(QtGui.QShowEvent())

    def removeWin(self):
        self.subs.pop(self.sender().contact, None)

    def closeEvent(self, evt):
        for widget in self.subs.values():
            widget.close()
        evt.accept()

if __name__ == '__main__':
    app = QtGui.QApplication([])
    win = AppWidget()
    win.show()
    app.exec_()
Die Namen und das StringListModel stehen nur beispielhaft für Deine IDs bzw. Dein Model.

Edit: closing-Signal vergessen und gefixt.
Antworten