[Qt] Close-Signal bei QMainWindow?



  • So, noch eine Frage:

    Gibt es eigentlich kein Close-Event und ähnliches für QMainWindow? Muss man da die closeEvent-Funktion reimplementieren?



  • Das müsste mit close() gehen.



  • close() ist laut Doku ein Slot. Ich suchte aber nach einem close-Signal. Anscheinend ist man ja dazu gezwungen, von QMainWindow zu erben und closeEvent() zu überschreiben, um überhaupt vom Schließen eines Fensters etwas mitzukriegen.



  • das ist doch aber immer so? Jede von QWidget geerbte Klasse muss man - sofern mal die defaultimplementation von "return true;" umgehen will, entsprechend ableiten - oder einen EventFilter installieren? Siehe http://doc.qt.nokia.com/4.7-snapshot/qobject.html#installEventFilter und das ganze halt mit dem CloseEvent ... hth



  • Sorry, bin noch neu bei Qt, aber die Frage ist: Kann ich ohne Ableiten ein QCloseEvent empfangen (außer mit der EventFilter-Methode)? Bei einem Button gibt es ja ein clicked()-Signal, warum gibt es bei einem Fenster kein closed()-Signal?



  • An einem Signal hängt in der Regel ein Sender (weshalb man da auch über sender() dran käme - aber nicht sollte). Wenn sich ein Window close'ed ist es kurz davor (unter Umständen) nicht mehr da zu sein -> deleted. Bis das Signal in der EventQueue dran ist, kanns also sein das das Fenster schon weg ist. Wenn du ein Signal !brauchst! könntest du vielleciht mit destroyed() arbeiten, aber dann ist ja alles zu spät. Dasselbe gälte für das closed() signal. Deshalt wird das IMHO über events geregelt, so kannst du BEVOR das Fenster weg ist, auf das event reagieren, Zeug speichern, sonstwas machen und eventuell sogar verhindern das das Fenster geschlossen wird (das Event nicht accepten). Ist in der API halt so geregelt - muss man mit leben.



  • Könnte ich dann so etwas in der Art benutzen:

    events.h

    #include <QMainWindow>
    #include <QCloseEvent>
    
    class EventManager : public QObject
    {
        signals:
        void WindowClosed(QCloseEvent *event);
        //...
    
        public slots:
        void WindowCloseSlot(QCloseEvent *event);
    };
    
    extern EventManager evt_mgr_instance;  //Kann man natürlich auch mit einem Singleton lösen
    
    class CloseSignalWindow : public QMainWindow
    {
        protected:
        virtual void closeEvent(QCloseEvent *event);
    
        public:
        CloseSignalWindow(QWidget *parent = 0, Qt::WindowFlags flags = 0) : QMainWindow(parent, flags) {}
    };
    

    events.cpp

    #include "events.h"
    
    EventManager evt_mgr_instance;
    
    void CloseSignalWindow::closeEvent(QCloseEvent *event)
    {
        emit evt_mgr_instance.WindowClosed(event);  //Wenn ein slot event->ignore() setzt, wird das Fenster nicht geschlossen
    }
    

    So, und jetzt müsste man ja Folgendes schreiben können:

    #include "events.h"
    #include <QApplication>
    #include <QMessageBox>
    
    void EventManager::WindowCloseSlot(QCloseEvent *event)
    {
        //irgendwas erfundenes:
        if(isFileSaved()) event->accept();
        else
        {
            QMessageBox(...);
            //und jetzt noch eine Abfrage...
        }
    }
    
    int main(int argc, char **argv)
    {
        QApplication app(argc, argv);
        CloseSignalWindow win;
        QObject::connect(&evt_mgr_instance, SIGNAL(WindowClosed(QCloseEvent*)), &evt_mgr_instance, SLOT(WindowCloseSlot(QCloseEvent*)));
        win.show();
        return app.exec();
    }
    


  • void QWidget::closeEvent ( QCloseEvent * event ) [virtual protected]

    This event handler is called with the given event when Qt receives a window close request for a top-level widget from the window system.

    By default, the event is accepted and the widget is closed. You can reimplement this function to change the way the widget responds to window close requests. For example, you can prevent the window from closing by calling ignore() on all events.

    Main window applications typically use reimplementations of this function to check whether the user's work has been saved and ask for permission before closing. For example, the Application Example uses a helper function to determine whether or not to close the window:

    void MainWindow::closeEvent(QCloseEvent *event)
    {
    if (maybeSave()) {
    writeSettings();
    event->accept();
    } else {
    event->ignore();
    }
    }

    See also event(), hide(), close(), QCloseEvent, and Application Example.

    Ich denke nicht, Grund:

    1. Du leitest die Klasse ab und reimplementierst das CloseEvent - alles was du also machen willst kannst, solltest, musst du dort machen - ist einfacher.
    2. Wenn du nicht ableiten willst, bau dir einen EventFilter, installkier den via setEventFilter(QObject * ) und lass den zB eine aboutToClose() Methode seines parents aufrufen
    3. Überleg dir mal was passiert und lies vorher nochmal das Zitierte.
    - QWidget erhält ein clsoeEvent. im CloseEvent schmeisst du ein Signal. Das CloseEvent ist zuende, das Event nicht ignored, damit darf das QWidget geschlossen werden.
    - irgendwann ist die EventQueue dran und reagiert auf dein Signal ... und du hast uU ein nicht mehr existentes Widget von dem du noch irgendwas retten/speichern willst
    4, Sagen wir mal du ignorierst das Event zusätzlich - dann wird das Widget nicht geschlossen und selbst dann wird das was du da versuchst vermutlich nicht funktionieren - der ObjectHandler weiss ja nicht was in dem Widget vorgeht das da geschlossen werden soll - du hast ja keine Verbindung (ausser vielleicht über sender() und gecaste das dann irgendwelche speichereMeineDate() Methoden des Widgets aufrufen soll - das ist irgendwie murksig.

    Leite die QMainWindow einfach ab und mach deine Überprüfungen in der closeEvent() Methode oder setzt einen EventHandler analog dem Beispiel im Link oben...



  • Hm... dass ich nicht für jedes Fenster eine neue Klasse erstellen will, hat schon seinen Grund. Was ich mich gerade frage, ist, dass Qt doch im Event-Loop sein müsste, wenn es closeEvent() aufruft. Warum sendet Qt nicht, wie z.B. wxWidgets auch, ein closed()-Signal aus, arbeitet danach alle Slots ab und schaut erst im Anschluss, ob das Fenster geschlossen werden soll?

    D.h. im Umkehrschluss müsste das jetzt bedeuten, dass bei installEventFilter() nicht ein Event in die Queue gepusht wird, sondern die entsprechende Funktion sofort aufgerufen wird?

    Ich will lediglich ein Signal-Slot-Like Verfahren anwenden können, um später niemanden zu verwirren.



  • EventFilter ist ein Bandwurm.

    Isst du was, gehen die Nahrungsbestandteile durch Mund/Magen und den Darm ins Blut und stehen dann deinem Körper zur Verfügung. Haste nen Bandwurf gehts von Mund/Magen/Darm, dann isst der Bandwurm das was er essen will (das kriegst du dann nimmer) den Rest geht in dein Blut und steht dir zu Verfügung. Du brauchst nen Bandwurm EventFilter der halt closeEvents() frisst. Der EventFilter erbt von QObject, hat also volle Signal/Slot Möglichkeiten ... d.h. du kannst sowas machen:

    #include <QLineEdit>
    #include <QPushButton>
    #include <QCloseEvent>
    #include <QHBoxLayout>
    #include <QDebug>
    
    class SafelyClose : public QObject
    {
        Q_OBJECT
    
    signals:
        void closeSafely();
    
    protected:
        bool eventFilter(QObject *obj, QEvent *event)
        {
            if (event->type() == QEvent::Close)
            {
                if (m_dirty)
                {
                    qDebug() << Q_FUNC_INFO << " Data is DIRTY -> SIGNAL(closeSafely) && eat closeEvent";
                    emit closeSafely();
    
                    return true;
                }
                else
                {
                    qDebug() << Q_FUNC_INFO << " Data not DIRTY -> dont eat closeEvent";
                    return false;
                }
            }
    
            // standard event processing
            return QObject::eventFilter(obj, event);
        }
    
    public:
        explicit SafelyClose(QObject * parent = 0) : QObject(parent), m_dirty(false) {}
    
        void makeDirty() { qDebug() << Q_FUNC_INFO; m_dirty = true; }
        void makeClean() { qDebug() << Q_FUNC_INFO; m_dirty = false; }
    
    private:
        bool m_dirty;
    };
    
    class MW : public QWidget
    {
        Q_OBJECT
    public:
        MW (QWidget * parent = 0, Qt::WindowFlags f = 0) : QWidget(parent,f)
        {
            qDebug() << Q_FUNC_INFO;
    
            m_sf = new SafelyClose(this);
            installEventFilter(m_sf);
    
            QHBoxLayout * lbox = new QHBoxLayout(this);
            QLineEdit * edit = new QLineEdit("text", this);
            QPushButton * save = new QPushButton("save",this);
            lbox->addWidget(edit);
            lbox->addWidget(save);
    
            connect(m_sf,SIGNAL(closeSafely()),this,SLOT(saveAndClose()));
            connect(edit,SIGNAL(textEdited(QString)), this, SLOT(preciousChanged()));
            connect(save,SIGNAL(clicked()), this, SLOT(saveMyPrecious()));
        }
    
    public slots:
        void preciousChanged()
        {
            qDebug() << Q_FUNC_INFO;
            // change data
            m_sf->makeDirty();
        }
        void saveMyPrecious()
        {
            qDebug() << Q_FUNC_INFO;
            // code zum saven
            m_sf->makeClean();
        }
    
    private slots:
        void saveAndClose()
        {
            qDebug() << Q_FUNC_INFO;
            saveMyPrecious();
            close();
        }
    private:
        SafelyClose * m_sf;
    };
    

    Damit hast du eine (wiederverwendbare) eventFilter-Klasse, musst in deinen vielen dutzend (wofür brauchst du soviele (verschiedene Windows) denn ?) ein CONNECT, und einen private SLOT machen und daran denken das bei Userinput der eventFilter eingedreckt bzw bei save-Aktionen gesäubert wird. Feddich. Der EventFilter muss nix über die Klassen wissen und gut iss.

    Warum sendet Qt nicht, wie z.B. wxWidgets auch, ein closed()-Signal aus, arbeitet danach alle Slots ab und schaut erst im Anschluss, ob das Fenster geschlossen werden soll?

    Warum nimmst du nicht wxWidgets? Warum fragst du dich wie es andere machen statt dich in der Implementation und den Möglichkeiten von Qt zu bewegen? Ist übrigens alles pures Signal-Slot-Like Verfahren 😃



  • Danke, aber worin unterscheidet sich das jetzt genau vom Punkt 4 in deinem vorherigen Beitrag, außer, dass du noch einen EventFilter benutzt?
    Ich meine, im dirty-Fall brichst du das Schließen ja auch ab und wartest darauf, dass der Slot das dann evtl. macht.

    padreigh schrieb:

    Warum sendet Qt nicht, wie z.B. wxWidgets auch, ein closed()-Signal aus, arbeitet danach alle Slots ab und schaut erst im Anschluss, ob das Fenster geschlossen werden soll?

    Warum nimmst du nicht wxWidgets? Warum fragst du dich wie es andere machen statt dich in der Implementation und den Möglichkeiten von Qt zu bewegen? Ist übrigens alles pures Signal-Slot-Like Verfahren 😃

    Das soll Teil einer Library werden (steckt allerdings noch mehr dahinter), und der Benutzer soll nicht irgendwelche Klassen ableiten (müssen), sondern das so benutzen. Und da ich eine Mischung aus EventFilter und Signal-Slot-Verfahren unintuitiv fände, will ich es halt in dem Fall "emulieren", oder wie du das auch immer nennen würdest 😃 . Du hast natürlich Recht, dass es bei wxWidgets kein Signal-Slot-System gibt, aber es funktioniert ja trotzdem ähnlich. Und da Qt ja einige andere Vorteile bietet, will ich deshalb nicht darauf verzichten.

    "Nichts für ungut, ihr Idealisten da draußen!" 😃



  • Der Unterschied zu meinem 4 besteht darin, das mein 4 auf das was du als extern/singleton vorschlugst 'aufbaut' *schäumaugenrollenthusiier*. Du hast keine Verbindung zwischen EventHandler und Widget und musst dann wild rumcasten um die Objektspezifischen 'save' Methoden aufrufen zu können (soweit ich das verstehe).

    In Qt kann fast alles ein "TopLevelWindow" werden was von QWidget erbt - vom QMainWindow über QLabel bis QGLWidgets. Mein Code braucht kein gecaste, da der 'generische' SavelyClose Handler und das QWidget nur über Signal/Slot kommunizieren - der Handler ruft keine QWidget-Methode auf sondern sendet ein Signal, das Widget kümmert sich selbst ums saven und schließt sich dann nochmals - das dirty im SavelyClose regelt ob das event gefressen oder weitergegeben wird. Mir fällt spontan kein Weg ein wie du -ohne einen Haufen Klassen abzuleiten- diese Funktionalität einfach anbieten kannst. Wenn du in der Lib natürlich nur eine begrenzte auswahl an TopLevelWidgets hast, könntest du nur die erweitern. Wäre schön wenn sich hier ein QtCrack einschalten würde, aber dieses Subforum ist recht _tot_. Vielleicht findest du bei qtforum.de oder den qtforum.org jemanden mit mehr Erfahrung der dir weiterhelfen kann 🙂



  • Sorry, ich verstehe dich nicht ganz, hast du mich richtig verstanden?

    Zusammenfassung:
    Mein Code (mit Senden eines Signals in der überschriebenen closeEvent()-Funktion) sendet ein Signal aus, das anscheinend womöglich erst verarbeitet wird, wenn das Fenster schon zerstört wurde.

    Lösungsvorschlag 4, den du nicht gut fandest, war, in der closeEvent()-Funktion das Event erst einmal zu ignorieren mittels event->ignore(), um dann im Signal immer noch das Fenster schließen zu können.

    In deinem Beispiel gibst du in der eventFilter()-Methode zumindest manchmal true zurück, um dann das Fenster im Slot doch noch zu schließen. Warum filterst du erst das Event und machst diese ganze Verarbeitung nicht gleich in der closeEvent()-Funktion?

    Noch eine Frage:
    Kann ein Slot herausfinden, welches QWidget das Event ausgesendet hat, oder muss man in dem Fall für jedes QWidget ein neues Empfänger-Objekt erstellen? (wenn man die Events an ein anderes Objekt senden will) (Du sagtest etwas von sender(), davon stand aber nichts in der QEvent-Doku)



  • wxSkip schrieb:

    Sorry, ich verstehe dich nicht ganz, hast du mich richtig verstanden?

    Ich vermute mal wir reden aneinander vorbei 😉

    wxSkip schrieb:

    Mein Code (mit Senden eines Signals in der überschriebenen closeEvent()-Funktion) sendet ein Signal aus, das anscheinend womöglich erst verarbeitet wird, wenn das Fenster schon zerstört wurde.

    Mh ja - und ist unnötig. Du bist im Objekt, in der Membermethode closeEvent(.) und kannst dort direkt die Memberfunktion zum Speichern aufrufen - für DIESES Objekt. (erfordert Reimplementation in jeder Klasse)

    wxSkip schrieb:

    Lösungsvorschlag 4, den du nicht gut fandest, war, in der closeEvent()-Funktion das Event erst einmal zu ignorieren mittels event->ignore(), um dann im Signal immer noch das Fenster schließen zu können.

    Wenn du das event da ignorierst und ein Signal sendest wird nichts geschlossen. Dann landest du im Slot der mit dem Signal verbunden ist und kannst da wieder close()'en wodurch eine Schleife entsteht - aufbruch durch m_dirty boolean und es funzt. (erfordert Reimplementation in jeder Klasse)

    wxSkip schrieb:

    In deinem Beispiel gibst du in der eventFilter()-Methode zumindest manchmal true zurück, um dann das Fenster im Slot doch noch zu schließen. Warum filterst du erst das Event und machst diese ganze Verarbeitung nicht gleich in der closeEvent()-Funktion?

    true im EventHandler FRISST das Event, das wird nicht weiter propagiert also wird nix geschlossen - stattdessen emittieren ich ein Signal das das Objekt zu speichern/erneutSchließen auffordert. Das kann man auch alles in der Objekteigenen closeEvent(.) Methode machen. Habe ich aber drei(ßig) verschiedene Hauptfenster, in einem kann man Texte [HText] reinschreiben, im zweiten kann man ein Bild [HBild] laden und Pixelwerte ändern, im dritten man eine Sql-Datenbank [HSql] modifizieren. In jedem soll vor dem zumachen getestet werden ob veränderte Daten da sind, wenn ja, sollen diese Gespeichert werden und erst dann zugenmacht werden.
    - Du brauchst in jeder Klasse spezifische saveMethoden()
    - Zusätzlich brauchst du entweder 3 Boolmember (+evtl SLOTS für setDirty(); setClean(); des booleans) und musst 3 closeEvents() reimplementieren in die du die Logik baust für "schau ob dirty, wenn ja, frag save, wenn nein, close".
    - Oder du installierst 3mal den SafelyClose Handler, connectest den mit Signals/Slots und fertig.

    wxSkip schrieb:

    In deinem Beispiel gibst du in der eventFilter()-Methode zumindest manchmal true zurück, um dann das Fenster im Slot doch noch zu schließen. Warum filterst du erst das Event und machst diese ganze Verarbeitung nicht gleich in der closeEvent()-Funktion?

    Das Ergebnis ist äquivalent. Der Vorteil des EventHandlers ist, dass du die Logik/Mechanik nur einmal machst (dafür hast du einige connects-Einzeiler statt mehrzeiligen Methoden)

    Ablauf mit closeEvent() Reimplementation sieht in etwa so aus:

    Jemand schickt dem Widget ein closeEvent -> Widget bekommt closeEvent -> in reimpl. closeEvent(): Abfrage ob speichern -> evtl. speichern -> close
    

    eventHandler eher so:

    Jemand schickt dem Widget ein closeEvent -> Widget leitet Event an EventFilterObj weiter -> EventHandler bekommt closeEvent -> m_dirty? (A) : (B)
        (A) -> event fressen -> SIGNAL schicken -> Widget bekommt SIGNAL -> Widgetslot speichert, setClean()'ed und schickt sichj selbst close() erneut -> EventHandler bekommt CloseEvent() -> m_dirty ? (nicht möglich) : (B)
        (B) -> event durchlassen -> widget bekommt closeEvent() -> default true -> wird geschlossen
    

    Das Problem beim DEINER Version (extern/Singleton - abgesehen davon dass er so nicht tut da keine enventFilter(QObject*,QEvent*) Methode implementiert ist und es so nicht als Filter gesetzt wird) wie willst du unterschiedliche Daten saven ohne Zugriff auf die Klasse zu haben, dem dieser EventHandler zugewiesen wurde - du weisst doch garnicht WAS gespeichert werden soll - wie willst du das dann machen? Du könntest jede deiner Klasse von nem Interface erben lassen die eine save() vorschreibt und dann aufs Interface casten - dazu musst du aber auch alle Klassen ableiten und damit sowieso an deren Innereien an, dann kannst du auch gleich den closeEvent() selbst überladen oder den EventFilter direkt installieren (samt connects) - also ist das Interface hinfällig - denke ich.

    wxSkip schrieb:

    Kann ein Slot herausfinden, welches QWidget das Event ausgesendet hat, oder muss man in dem Fall für jedes QWidget ein neues Empfänger-Objekt erstellen? (wenn man die Events an ein anderes Objekt senden will) (Du sagtest etwas von sender(), davon stand aber nichts in der QEvent-Doku)

    SIGNAL/SLOT != EVENT. In einem SLOT der von einem SIGNAL getriggert wird, kann man über sender() an das SIGNAL-schickende Objekt herankommen - siehe auch http://doc.qt.nokia.com/latest/signalsandslots.html#advanced-signals-and-slots-usage . Ob ein EventFilterObjekt auf mehreren Widgets gleichzeitig installiert werden kann ... kE. Die API sagt dazu nichts (kein Hinweis auf re Parenting oder so) - probiers aus 🙂



  • So, vielen Dank erstmal! 👍
    1. Beim connect() steht ja, dass per Default Qt::AutoConnection benutzt wird. D.h. wenn ich das connect() vom GUI-Thread aus mache, wird eine Qt::DirectConnection benutzt. D.h. bei meinem ersten Vorschlag müsste ja im Prinzip gleich der Slot ausgeführt werden und nicht erst später im EventLoop?!

    2. Falls das doch nicht der Fall ist, mache ich dir nochmal einen Vorschlag (es geht mir ja nicht nur ums Speichern, sondern ums Allgemeine). Deine Idee also leicht abgewandelt:

    class EventManager : public QObject
    {
        public slots:
        void WindowCloseSlot(QCloseEvent *event);
    };
    
    EventManager instance;
    
    class MyWindow : public QMainWindow
    {
        bool ignore_event_handlers;
    protected:
        virtual void closeEvent(QCloseEvent *event)
        {
            if(ignore_event_handlers)
            {
                event->accept();
            }
            else
            {
                //vom empfangenden slot aus kann dann closeReally() aufgerufen werden, um das Fenster ohne ein erneutes Event zu schließen
                emit closed(event);
                event->ignore();
            }
        }
    
    signals:
        void closed(QCloseEvent*);
    
    public:
        MyWindow(QWidget * parent = 0, Qt::WindowFlags f = 0) : QMainWindow(parent,f), ignore_event_handlers(false) {}
        virtual ~MyWindow(){}
    
        void closeReally()
        {
            ignore_event_handlers = true;
            close();  //jetzt wird in closeEvent() wirklich geschlossen
            ignore_event_handlers = false;
        }
    };
    
    MyWindow *win;
    
    int main(int argc, char **argv)
    {
        QApplication app(argc, argv);
        win = new MyWindow();
        QObject::connect(&win, SIGNAL(closed(QCloseEvent*)), &instance, SLOT(WindowClosedSlot(QCloseEvent*)));
        win.show();
        return app.exec();
    }
    
    void EventManager::WindowClosedSlot(QCloseEvent *event)
    {
        //erfundenes Anwendungsbeispiel
        if(!saved)
        {
            int result = ShowSaveQuestion();
            if(result != CANCEL)
            {
                if(result == SAVE) save();
                win->closeReally();
            }
        }
        else win->closeReally();
    }
    

    Sorry, ich bin jetzt mit EventLoop und direktem Aufruf etwas verwirrt, sag mir einfach, was davon stimmt.

    Zur Frage, woher ich meine Daten zum Speichern herkriege: Die Daten sind in der EventManager-Klasse oder sonst wo gespeichert, je nach Anwendungsfall.

    Jetzt habe ich nur noch eine Frage, die ich so nicht in der Qt-Doku gefunden habe:
    Wenn ich mehrere Slots an ein Signal connecte, werden sie dann in der umgekehrten Reihenfolge abgearbeitet, wie sie connected wurden?
    Bsp.:

    QObject::connect(ptr1, SIGNAL(sig()), ptr2, SLOT(slot1()));
    QObject::connect(ptr1, SIGNAL(sig()), ptr2, SLOT(slot2()));
    QObject::connect(ptr1, SIGNAL(sig()), ptr2, SLOT(slot3()));
    
    emit ptr1->sig();  //wird hier zuerst slot3(), dann slot2() und dann slot1() aufgerufen?
    


  • wxSkip schrieb:

    1. Beim connect() steht ja, dass per Default Qt::AutoConnection benutzt wird. D.h. wenn ich das connect() vom GUI-Thread aus mache, wird eine Qt::DirectConnection benutzt. D.h. bei meinem ersten Vorschlag müsste ja im Prinzip gleich der Slot ausgeführt werden und nicht erst später im EventLoop?!

    Im Prinzip ja, wenn der Signalemitter zur Zeit der Emittierung noch im selben Thread lebt wie der Signalempfänger. (kein zB QThread::moveToThread )

    - Syntaktisch klappt an deinem Code nix - es fehlt zB durchgehebnd das Q_OBJECT und noch mehr - verrät dir alles der Compiler 🙂 )

    Ich hab (ca 3h) rumprobiert und kam auf sowas ... das kompiliert zumindest und läuft:

    mw.pro

    TARGET = CloseTest
    TEMPLATE = app
    SOURCES += main.cpp\
            mw.cpp
    HEADERS  += mw.h
    

    main.cpp

    #include <QtGui/QApplication>
    #include <QtGui>
    #include "mainwindow.h"
    
    int main(int argc, char *argv[])
    {
        EventManager instance;
    
        QApplication a(argc, argv);
        QWidget w;
        {
            QVBoxLayout * vbl = new QVBoxLayout(&w);
    
            MWbool * mwbool = new MWbool;
            MWreal * mwreal = new MWreal;
            MWint * mwint = new MWint;
    
            mwbool->setEventManager(&instance);
            mwreal->setEventManager(&instance);
            mwint->setEventManager(&instance);
    
            vbl->addWidget(mwbool);
            vbl->addWidget(mwreal);
            vbl->addWidget(mwint);
        }
        w.show();
    
        return a.exec();
    }
    

    mw.h

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QWidget>
    #include <QString>
    class QCloseEvent;
    
    // helper //////////////////////////////////////////////////////////
    class MWbasic;
    
    namespace Helper {
        void fillWidget(MWbasic * m, const QString & txt);
    }
    
    // interface //////////////////////////////////////////////////////////
    class MWsavedata {
    public:
        MWsavedata();
        virtual ~MWsavedata();
    
        virtual void saveData() = 0;
        bool saved;
        bool ignoreUnsavedData;
    };
    
    // basic //////////////////////////////////////////////////////////
    class EventManager;
    
    class MWbasic : public QWidget, public MWsavedata
    {
        Q_OBJECT
    public:
        MWbasic(QWidget * parent);
        virtual ~MWbasic();
    
        void setEventManager(EventManager * m);
    
    protected slots:
        void setSaved(bool to)          { saved = to; }
        void setIngnoreUnsaved(bool to) { ignoreUnsavedData = to; }
    
    protected:
        EventManager * manager;
    };
    
    // bool  //////////////////////////////////////////////////////////
    class MWbool : public MWbasic
    {
        Q_OBJECT
        bool data;
    
    public:
        MWbool (QWidget *parent = 0);
        virtual ~MWbool();
    
        virtual void saveData();
    
    signals:
        void aboutToClose();
    
    protected:
        void closeEvent(QCloseEvent *event);
    };
    
    // int  //////////////////////////////////////////////////////////
    class MWint : public MWbasic
    {
        Q_OBJECT
        qint16 data;
    
    public:
        MWint(QWidget *parent = 0);
        virtual ~MWint();
    
        virtual void saveData();
    
    signals:
        void aboutToClose();
    
    protected:
        void closeEvent(QCloseEvent *event);
    };
    
    // real  //////////////////////////////////////////////////////////
    class MWreal : public MWbasic
    {
        Q_OBJECT
        qreal data;
    
    public:
        MWreal(QWidget *parent = 0);
        virtual ~MWreal();
    
        virtual void saveData();
    
    signals:
        void aboutToClose();
    
    protected:
        void closeEvent(QCloseEvent *event);
    };
    
    // eventManager  //////////////////////////////////////////////////////////
    class EventManager : public QObject
    {
        Q_OBJECT
    public:
        EventManager(QObject * parent = 0);
    
    public slots:
        void WindowCloseSlot();
    };
    #endif // MAINWINDOW_H
    

    mw.cpp

    #include "mainwindow.h"
    
    #include <QtDebug>
    
    #include <QtCore/QTime>
    
    #include <QtGui/QCheckBox>
    #include <QtGui/QCloseEvent>
    #include <QtGui/QGridLayout>
    #include <QtGui/QLabel>
    #include <QtGui/QMessageBox>
    #include <QtGui/QPushButton>
    
    // helper ////////////////////////////////////////////////////////////////////////
    
    void Helper::fillWidget(MWbasic *widget, const QString &txt)
    {
        const QFont headline = QFont("Arial",14,200,true);
        QGridLayout * lay = new QGridLayout(widget);
    
        QLabel * idLabel = new QLabel(txt + " " + QTime::currentTime().toString("hh:mm:ss (zzz)"));
        idLabel->setEnabled(false);
        idLabel->setFont(headline);
        idLabel->setAlignment(Qt::AlignCenter);
    
        QPushButton * savedButton = new QPushButton("SAVE");
        QCheckBox * saved = new QCheckBox("Saved: ");
        saved->setChecked(false);
        saved->setEnabled(false);
        QWidget::connect(savedButton,SIGNAL(clicked()),saved,SLOT(toggle()));
        QWidget::connect(saved,SIGNAL(toggled(bool)), widget, SLOT(setSaved(bool)));
    
        QPushButton * ignoreButton = new QPushButton("IGNORE UNSAVED");
        QCheckBox * ignoreunsaved = new QCheckBox("Ignore unsaved: ");
        ignoreunsaved->setChecked(false);
        ignoreunsaved->setEnabled(false);
        QWidget::connect(ignoreButton,SIGNAL(clicked()),ignoreunsaved,SLOT(toggle()));
        QWidget::connect(ignoreunsaved,SIGNAL(toggled(bool)), widget, SLOT(setIngnoreUnsaved(bool)));
    
        QPushButton * closeButton = new QPushButton("CLOSE");
        QWidget::connect(closeButton,SIGNAL(clicked()),widget,SLOT(close()));
    
        lay->addWidget(idLabel,0,0,1,2);
    
        lay->addWidget(savedButton,1,0);
        lay->addWidget(saved,1,1);
    
        lay->addWidget(ignoreButton,2,0);
        lay->addWidget(ignoreunsaved,2,1);
    
        lay->addWidget(closeButton,3,0,1,2);
    }
    
    // interface ////////////////////////////////////////////////////////////////////////
    
    MWsavedata::MWsavedata() : saved(false), ignoreUnsavedData(false) {}
    
    MWsavedata::~MWsavedata() {}
    
    // basis ////////////////////////////////////////////////////////////////////////
    
    MWbasic::MWbasic(QWidget * parent) : QWidget(parent) {}
    
    MWbasic::~MWbasic() {}
    
    void MWbasic::setEventManager(EventManager *m)
    {
        manager = m;
        connect(this,SIGNAL(aboutToClose()), manager, SLOT(WindowCloseSlot()));
    }
    
    // bool ////////////////////////////////////////////////////////////////////////
    
    MWbool::MWbool (QWidget *parent) :
            MWbasic(parent), data(true)
    {
        qDebug() << Q_FUNC_INFO;
        Helper::fillWidget(this,"bool");
    }
    
    MWbool::~MWbool() {qDebug() << Q_FUNC_INFO;}
    
    void MWbool::saveData()
    {
        qDebug() << Q_FUNC_INFO <<"\n\tbool saved :" << data;
    }
    
    void MWbool::closeEvent(QCloseEvent *event)
    {
        qDebug() << Q_FUNC_INFO;
        if (! saved && ! ignoreUnsavedData)
        {
            emit aboutToClose(); // directly connected !
        }
    
        MWbool * newMw = new MWbool;
        newMw->setEventManager(manager);
        parentWidget()->layout()->addWidget(newMw);
    
        event->accept();
    }
    
    // int ////////////////////////////////////////////////////////////////////////
    
    MWint::MWint(QWidget *parent) :
            MWbasic(parent), data(12345)
    {
        qDebug() << Q_FUNC_INFO;
        Helper::fillWidget(this,"int");
    }
    
    MWint::~MWint()
    {
        qDebug() << Q_FUNC_INFO;
    }
    
    void MWint::saveData()
    {
        qDebug() << Q_FUNC_INFO <<"\n\tint saved :" << data;
    }
    
    void MWint::closeEvent(QCloseEvent *event)
    {
        qDebug() << Q_FUNC_INFO;
        if (! saved && ! ignoreUnsavedData)
        {
            emit aboutToClose(); // directly connected !
        }
    
        MWint * newMw = new MWint;
        newMw->setEventManager(manager);
        parentWidget()->layout()->addWidget(newMw);
    
        event->accept();
    }
    
    // real ////////////////////////////////////////////////////////////////////////
    
    MWreal::MWreal(QWidget *parent) :
            MWbasic(parent), data(1.618033989)
    {
        qDebug() << Q_FUNC_INFO;
        Helper::fillWidget(this,"real");
    }
    
    MWreal::~MWreal()
    {
        qDebug() << Q_FUNC_INFO;
    }
    
    void MWreal::saveData()
    {
        qDebug() << Q_FUNC_INFO <<"\n\treal saved :" << data;
    }
    
    void MWreal::closeEvent(QCloseEvent *event)
    {
        qDebug() << Q_FUNC_INFO;
        if (! saved && ! ignoreUnsavedData)
        {
            emit aboutToClose(); // directly connected !
        }
    
        MWreal * newMw = new MWreal;
        newMw->setEventManager(manager);
        parentWidget()->layout()->addWidget(newMw);
    
        event->accept();
    }
    
    // eventManager  //////////////////////////////////////////////////////////
    
    EventManager::EventManager(QObject * parent) :
            QObject(parent)
    {
        qDebug() << Q_FUNC_INFO;
    }
    
    void EventManager::WindowCloseSlot()
    {
        MWsavedata *savedata = dynamic_cast<MWsavedata*>(sender());
        if (savedata) // cast-able
        {
            qDebug() << Q_FUNC_INFO;
    
            QWidget * wid = qobject_cast<QWidget*>(sender());
            const QMessageBox::StandardButtons
                    answer =  QMessageBox::question(wid,"Save before close?", "Save before close?",  // title, question
                                                    QMessageBox::Yes|QMessageBox::No,  // buttons
                                                    QMessageBox::Yes); // default button
    
            if(answer == QMessageBox::Yes) // Save
            {
                savedata->saveData();
            }
        }
    }
    

    Das Beispiel tut in etwa das was du wolltest (für DirectConnectedSignals - nicht Threadsicher) - jede Klasse die das machen will muss das Interface implementieren und es bleibt ein unschöner sender()-cast drin. Wenn du damit leben willst, ginge es so in etwa.

    wxSkip schrieb:

    Jetzt habe ich nur noch eine Frage, die ich so nicht in der Qt-Doku gefunden habe:
    Wenn ich mehrere Slots an ein Signal connecte, werden sie dann in der umgekehrten Reihenfolge abgearbeitet, wie sie connected wurden?
    Bsp.:

    QObject::connect(ptr1, SIGNAL(sig()), ptr2, SLOT(slot1()));
    QObject::connect(ptr1, SIGNAL(sig()), ptr2, SLOT(slot2()));
    QObject::connect(ptr1, SIGNAL(sig()), ptr2, SLOT(slot3()));
    
    emit ptr1->sig();  //wird hier zuerst slot3(), dann slot2() und dann slot1() aufgerufen?
    

    http://doc.qt.nokia.com/latest/signalsandslots.html#advanced-signals-and-slots-usage Runterscrollen bis "SIGNALs" da steht es 😉


Anmelden zum Antworten