【问题标题】:How to force Qt to update GUI from not main thread如何强制 Qt 从非主线程更新 GUI
【发布时间】:2013-03-06 04:23:46
【问题描述】:

自上周以来,我一直在与 QPlainTextEdit 更新引起的问题作斗争。我正在尝试使用 QPlainTextEdit 创建与 QMainWindow Dialog 窗口分开的窗口。当我尝试使用 appendHtml 信号(也尝试使用 appendText)时,问题就开始了,除非用鼠标标记,否则放置的文本是不可见的。重绘或更新导致程序崩溃或没有可见操作。

带有QPlainTextEdit头的QDialog的简化代码:

namespace Ui {
class LogWindow;
}

class LogWriter: public QDialog
{   
Q_OBJECT

QMutex print_lock;

public:

class Log{

    Q_OBJECT

    const static int MAX_SIZE = 100;
    bool to_terminal;
    QString color;
    QMutex *print_lock;
    QPlainTextEdit *text_place;
    QVector< QPair<QString,time_t> > history;
    LogWriter * obj;

    public:
    bool print;

    Log(bool _print,QString _color,LogWriter *obj_ = NULL)
    {print = _print; color = _color; obj = obj_;}
    void setLock(QMutex *print_lock_){print_lock = print_lock_;}
    void setTextField(QPlainTextEdit *_text) {text_place = _text;}
    Log& operator<<(QString &a);
    Log& operator<<(const char* a);
};

static LogWriter* getInstance()
{
    static LogWriter    instance; // Guaranteed to be destroyed.
                              // Instantiated on first use.
    return &instance;
}
~LogWriter();

Log LOW,MEDIUM,HIGH;
Ui::LogWindow *ui;

signals:
void signalLogAppend(QString);
};

方法定义的简化代码:

LogWriter::LogWriter(QWidget * parent): QDialog(parent) {

ui = new Ui::LogWindow;
ui->setupUi(this);

LOW.setLock(&print_lock);
MEDIUM.setLock(&print_lock);
HIGH.setLock(&print_lock);

connect(this,SIGNAL(signalLogAppend(QString)),ui->plainTextEdit, 
SLOT(appendHtml(QString)),Qt::DirectConnection);

}

LogWriter::Log& LogWriter::Log::operator<< (QString &s){
history.push_front(qMakePair(s,time(NULL)));
if(history.size() > MAX_SIZE) history.pop_back();

if(print){
    //print_lock->lock();

    QString text = "<font color=\"";
    text += color + "\">";
    text += s + "</font>";
    //cout << text.toStdString() << endl;
    //text_place->appendHtml(text);
    //text_place->repaint();
    emit (obj)->signalLogAppend(text);
    //print_lock->unlock();

}
return *this;
}

我有两个单独的 ui 文件(第一个用于主窗口,第二个用于日志窗口)。 我必须在整个程序中使用日志窗口(大约 10 个线程),我坚持这个问题。我的问题是 - 是否可以在不使用主线程的情况下强制 GUI 更新,如果没有 - 我还有什么可能。如果可能的话,我宁愿避免重构我的所有代码——这需要我一些时间来完成。现在记录非常简单——我只需要:

 LogWindow *log = LogWindow::getInstance();
 log->MEDIUM << "something";

作为附加信息,我添加 QTCreator 警告:

   QObject::connect: Cannot queue arguments of type 'QTextBlock'
   (Make sure 'QTextBlock' is registered using qRegisterMetaType().)
   QObject::connect: Cannot queue arguments of type 'QTextCursor'
   (Make sure 'QTextCursor' is registered using qRegisterMetaType().)

【问题讨论】:

    标签: c++ qt qt4


    【解决方案1】:

    如果我正确理解您的代码,您是在尝试从后台线程登录并使用直接连接将信号传递给 GUI 线程?这是行不通的,你必须通过默认连接发送信号,这样 Qt 才能确定它是一个跨线程信号并相应地跨线程传递(即通过前台线程上的消息循环)。

    在 Qt 中,任何 GUI 交互都必须发生在主/前台线程中,否则会发生如您所发现的坏事。您当然可以从后台线程发送信号来触发 GUI 更新——我一直这样做——但您需要确保使用正确的连接。直接连接会导致直接函数调用,在这种情况下对您不起作用。

    在您的代码中,问题在于对 connect() 的调用 - 您明确指定了信号到插槽连接的连接模式,而您应该只使用默认设置。如果将连接显式设置为Qt::DirectConnection,则底层代码将执行对指定槽的直接调用,这意味着您最终会在信号的线程上下文中调用槽。您不希望这样,因为信号是在后台线程中触发的。

    【讨论】:

    • 是的 - 我正在尝试从后台线程(甚至很多线程 - 通过操作员
    • 他的意思是在您的 LogWritter 类连接中,您使用 5 个参数调用 connect(....)。删除第 5 个参数 Qt:DirectConnection。直接连接仅适用于相同的线程连接。要么删除参数,让 Qt 弄清楚它需要是 Qt:QueuedConnection 还是自己提一下。我发现最好在跨线程信号槽的连接中明确说明
    • 天哪,它的工作原理......我怎么能错过,四天的搜索,而我只能检查 QT::directconnection 边界是什么......非常感谢您的帮助!!!
    【解决方案2】:

    您不能将任意类型/类传递给信号和插槽。该列表是有限的,并非所有 Qt 类都在列表中。要将类型/类添加到可以传递给信号/插槽的列表中,您必须为该类调用 qRegisterMetaType。我建议在您尝试传递给这样的信号的类的构造函数中调用它:

    MyClass::MyClass() : MyParentClass()
    {
        static int reg = qRegisterMetaType<MyClass>("MyClass");
    }
    

    静态 int 确保注册只被调用一次,并且在任何 MyClass 实例可以被使用之前。

    【讨论】:

      猜你喜欢
      • 2021-05-16
      • 2010-11-24
      • 2011-04-25
      • 1970-01-01
      • 1970-01-01
      • 2014-11-27
      • 2021-01-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多