Alex
Alex

Reputation: 385

Qt InvokeMethod on TextEdit setPalette

Similar to C# I have used a working snippet such as:

QString text = "Hello";
QMetaObject::invokeMethod(m_ui.textEdit_ConnectionStatus, "setText", Qt:QueuedConnection, Q_ARG(QString, text));

... in order to change GUI elements not from the main thread. However, it does not appear to be working in the same way with setPalette for the textEdit.

With:

QPalette pal = palette();
pal.setColor(QPalette::Base, Qt:darkGreen);
QMetaObject::invokeMethod(m_ui.textEdit_ConnectionStatus, "setPalette", Qt:QueuedConnection, Q_ARG(const QPalette&, pal));

How does one go about to change the color of this gui element from another thread?

Edit1: I forgot to mention the output spits out:

"QMetaObject::invokeMethod No such Method QTextEdit:setpalette(const QPalette&)"

Upvotes: 1

Views: 218

Answers (3)

Scheff's Cat
Scheff's Cat

Reputation: 20141

The OP used the QMetaObject::invokeMethod() which relies on registered slots (as they were usual in Qt4). QTextEdit::setText() is such a slot but QTextEdit::setPalette() is not.

Hence, the QTextEdit::setPalette() cannot be found at runtime by its name given as string.

With Qt5, the signal-slot concept was extended to support the connection of signals and slots with compile-time checking.

Out of curiosity, I had a look into the doc. and found QMetaObject::invokeMethod() which accepts a functor:

template <typename Functor, typename FunctorReturnType> bool QMetaObject::invokeMethod(QObject *context, Functor function, Qt::ConnectionType type = Qt::AutoConnection, FunctorReturnType *ret = nullptr)

This is an overloaded function.

Invokes the function in the event loop of context. function can be a functor or a pointer to a member function. Returns true if the function could be invoked. Returns false if there is no such function or the parameters did not match. The return value of the function call is placed in ret.

Note: This function is thread-safe.

This function was introduced in Qt 5.10.

Thereby, I would like to emphasize Note: This function is thread-safe.

So, I made an MCVE to check this out:

// standard C++ header:
#include <chrono>
#include <thread>

// Qt header:
#include <QtWidgets>

// main application
int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // setup GUI
  QTextEdit qTextEdit(QString(
    "<p>Hello world.</p>"
    "<p>Hello Qt.</p>"
    "<p>Hello Stack Overflow.</p>"));
  qTextEdit.show();
  // a separate thread to manipulate qTextEdit
  std::thread threadPal([&qTextEdit]() {
    using namespace std::chrono_literals;
    const QColor qColors[] = { Qt::red, Qt::green, Qt::blue, Qt::white };
    QColor qColor;
    for (const QColor &qColor_ : qColors) {
      std::this_thread::sleep_for(1s);
      qColor = qColor_;
      QMetaObject::invokeMethod(&qTextEdit, [&qTextEdit, qColor]() {
        QPalette qPal = qTextEdit.palette();
        qPal.setColor(QPalette::Base, qColor);
        qTextEdit.setPalette(qPal);
      });
    }
  });
  // runtime loop
  const int ret = app.exec();
  // done
  threadPal.join();
  return ret;
}

Output:

snapshot of testQWidgetSetPaletteThreadSafe (animated)

Please, note that I (carefully) did every access to qTextEdit exclusively

  • either in the main thread
  • or inside the lambda which is passed to QMetaObject::invokeMethod().

Qt widgets are by default not thread-safe. So, I have to ensure that the accesses to widgets happen in the GUI thread only (or had to be appropriately guarded).

The reference of qTextEdit is captured in the functor of threadPal. It is used to provide the address of qTextEdit to QMetaObject::invokeMethod() as context. That's necessary to make QMetaObject::invokeMethod() aware that the provided functor has to be executed in a different thread (the GUI thread to which qTextEdit is associated to). (In opposition to qTextEdit itself, the address of qTextEdit is immutable while the thread is running. Hence, an unguarded access is thread-safe.)

Upvotes: 1

Oktalist
Oktalist

Reputation: 14714

If you're using Qt 5.10 or later, you can call invokeMethod on any invokable, such as a lambda:

QPalette pal = palette();
pal.setColor(QPalette::Base, Qt:darkGreen);

auto setPalette = [this, pal] { m_ui.textEdit_ConnectionStatus->setPalette(pal); };
QMetaObject::invokeMethod(m_ui.textEdit_ConnectionStatus, setPalette, Qt:QueuedConnection);

Upvotes: 1

smalik
smalik

Reputation: 373

As per Qt Documentation, invokeMethod() invokes the member (a signal or a slot name) on the object.
Since in the first case, setText() is a slot of QTextEdit and hence it works perfect. However in the second case, setPalette() is neither a signal nor a slot and hence you get "QMetaObject::invokeMethod No such Method QTextEdit:setpalette(const QPalette&)" as output.
Moreover it returns false if there is no such member (a signal or a slot name) or the parameters did not match.

see here

Upvotes: 1

Related Questions