Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Qt: Background thread refreshing UI thread

Tags:

c++

qt4

I have a background thread and the thread calls some methods that update the UI (in order to show progress bars and show additional info in text areas).

If I modify some UI widget values, a "Cannot send events to objects owned by a different thread" assertion error is raised.

Looking at forums, I read that I could use QMetaObject::invokeMethod method but it just works if I pass it the Qt::DirectConnection flag that actually raises the same error shown above.

If I use Qt::QueuedConnection or Qt::AutoConnection, the invokeMethod returns false.

My code looks similar to this:

.h:

class A : public QMainWindow
{
  Q_OBJECT

  QProgressBar* pb;

  public slots:
    bool m(bool, int);
};

class B
{
  A* a;

  public:
    void handleEvent();
};


.cpp:

bool A::m(bool x, int y)
{
  pb->setValue(y);
  return x;
}

void B::handleEvent()
{
  //a->m(true, 12); //raises an assertion error

  bool r;
  //bool ret = QMetaObject::invokeMethod(a, "m", Qt::DirectConnection, Q_RETURN_ARG(bool, r), Q_ARG(bool, true), Q_ARG(int, 12)); //raises the same assertion error error

  bool ret = QMetaObject::invokeMethod(a, "m", Qt::AutoConnection, Q_RETURN_ARG(bool, r), Q_ARG(bool, true), Q_ARG(int, 12)); //is ignored and ret contains false.
}

Do you know what is going on or what i am doing wrong? or maybe, can someone suggest me another approach to deal with my newbie problem?

Thanks in advance,

Ernesto

like image 676
ebasconp Avatar asked Mar 04 '10 04:03

ebasconp


1 Answers

I haven't used invokeMethod() myself, but to do this, I usually just use signals and slots. For instance, you could create a signal as a member of class B that is connected to the slot in class A that updates the progress:

class B : public QObject
{
    Q_OBJECT

    A* a;

signals:
    void update_signal(bool, int);

public:
    void handleEvent();
};

B::B()
{
    //assuming a already points to the correct place...
    connect(this, SIGNAL(update_signal(bool,int), 
            a, SLOT(m(bool,int)), Qt::QueuedConnection);
}

void B::handleEvent()
{
    emit update_signal(true, 12);
}

A::m() will have to return void in this case but that is not a problem because when using a queued connection, you cannot get a return value anyway since the call is asynchronous (emit update_signal(true,12) may return before the slot function is called making it impossible to have a return value ready).

You can actually perform this connection anywhere as long as you have pointers to an object of type A and an object of type B. This makes the signals and slots very flexible since you could completely decouple A from B, yet still allow them to communicate through signals and slots. For instance:

class B : public QObject
{
    Q_OBJECT

signals:
    void update_signal(bool, int);

public:
    void handleEvent();
};

void B::handleEvent()
{
    emit update_signal(true, 12);
}

class A : public QMainWindow
{
    Q_OBJECT

    QProgressBar* pb;

    public slots:
        void m(bool, int);
};

void A::m(bool x, int y)
{
    pb->setValue(y);
}

int main()
{
    A* a = new A();
    B* b = new B();

    QObject::connect(b, SIGNAL(update_signal(bool, int)),
            a, SLOT(m(bool, int)), Qt::QueuedConnection);

    //...
}

In this case, b does not have to store a pointer or know anything about a yet they can communicate through a thin well-defined channel.

like image 118
Jason B Avatar answered Oct 22 '22 19:10

Jason B