Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 !

like image 672
yakiang Avatar asked Dec 03 '13 13:12

yakiang


2 Answers

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();
}
like image 114
hyde Avatar answered Nov 02 '22 01:11

hyde


If you can't use a modal window (or dialog) than you have a little more work, you'll need to subclass QWidget (or QDialog or other QWidget derived class, whatever you need) and override QWidget::closeEvent (for class A) and from here call a method of the parent (i suppose a will be a child of this in your code: a = new A(this);) or code your own signal/slot.

And if you decide to use a modal dialog, the code becomes simpler, see example code in the QDialog's documentation.

like image 34
Zlatomir Avatar answered Nov 02 '22 01:11

Zlatomir