简体   繁体   中英

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&)"

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

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);

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:

testQWidgetSetPaletteThreadSafe 的快照(动画)

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.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM