Search code examples
c++qtuser-interfaceqt5

Qt - How to do what I want when another dialog is closed?


I have two classes A and B, here is a snippet in B.h:

#include "A.h"
class B : public QDialog
{
    Q_OBJECT
public:
    void do_something();
private:
    A *a;
}

and in B.cpp:

B::B(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::B)
{
    a = new A();
    a.show();
}

So what should I do if I want to call do_something() when the ui of A is closed (by pressing Alt-F4, for example) ? Seems the way of signal-slot cannot apply here.
Thanks a lot !


Solution

  • Modify class A to emit signal when closed

    If modifying the A widget is option, add a signal to it and override closeEvent or hideEvent and emit the new signal there. This is robust and you have complete control on what happens. However, rest of the answer is for case where, for whaever reason, you are unable or unwilling to mess with A and want a solution in class B.

    Use the Qt signal for QObject deletion

    If you can set Qt::WA_DeleteOnClose attribute on A, then simple way is to make B::doSomething() (note the common Qt method naming convention) a slot and connect destroyed signal of A instance to that.

    B::B(QWidget *parent) : QDialog(parent), ui(new Ui::B)
    {
        a = new A();
        a->setAttribute(Qt::WA_DeleteOnClose);
        connect(a, SIGNAL(destroyed(QObject*)), SLOT(doSomething()));
        a.show();
    }
    

    Of course this works even if you delete a; explicitly, and not automatically with the attribute.

    Note: In this case, when the object may get deleted whenever, you should really use QPointer for the a pointer, to avoid accidentally referencing a dangling pointer.

    Use Qt event filter

    If you don't want to have the A instance deleted when it is closed (and re-created if it is needed again), then other way is to install event filter on the A instance, and detect the closeEvent. This has the potential problem, that the widget can reject the close event, and in any case doSomething is called before the closing. If this is a problem, it can be solved by using QMetaObject::invokeMethod static method to delay the doSomething (it must be slot or invokable method for this!) call to happen after close event is over and program returns to event loop. Then in doSomething check if a is really hidden. Instead of QEvent::Close, detecting QEvent::Hide might work well, too.

    To implement this, override the virtual eventFilter method in B, something like this:

    bool B::eventFilter(QObject *obj, QEvent *event)
    {
        if (event->type() == QEvent::Close) { //or QEvent::Hide maybe
            qDebug("QCloseEvent!");
    
            // check that object is indeed this->a before calling this->doSomething()
            if (qobject_cast<QObject *>(a) == obj) {
                doSomething();
                //alternative, make doSomething to be called later from event loop:
                //QMetaObject::invokeMethod(this, "doSomething", Qt::QueuedConnection);
            } else {
                qDebug("...but the object is not what is expected, a bug?");
            }
            // do not block the event, just detect it
        }
    
        // proceed with standard event processing
        return QObject::eventFilter(obj, event);
    }
    

    Then start listening events of an A instance with installEventFilter, something like:

    B::B(QWidget *parent) : QDialog(parent), ui(new Ui::B)
    {
        a = new A();
        installEventFilter(a);
        a.show();
    }