【问题标题】:qt - setText outside of paint events not ok?qt - 绘制事件之外的 setText 不行吗?
【发布时间】:2019-10-02 15:21:28
【问题描述】:

Qt 4.7.1、Qt Creator 2.1.0、OS X 10.6.8 下:

我在主窗口 ui 中有一个 QLabel,它使用 Courier New / 13,有四行文本的空间。

我创建了四行文本,比水平标签短得多,一般格式:

“我的文字\r\n”

我在发送之前过滤文本。 cstring 中唯一的字符将是 0x0D、0x0A、0x20(空格)以及从那里到小写 z(0x7A'),当然还有终止零。没有其他控制字符 - 如果它们是从源接收的,我将它们替换为 '*'

我通过setText()将四行文本作为一个以零结尾的 cstring 发送到 QLabel

我有时会以相当高的速度执行此操作,至少每秒几次 - 这是来自 FM 电台的 RDBS 数据,因此它会实时变化:

qDebug() << rbl;                    // data keeps coming to console
ui->fourLineLabel->setText(rbl);    // add this, display soon stops updating

这行得通。一阵子。然后显示停止更新。这是有争议的领域:


(来源:fyngyrz.com)

如果我把其他所有东西都留在里面,但取出setText(),则不会出现问题。

我知道对于某些事情,Qt 希望在绘画事件中完成绘画。 setText() 也是这样吗?

阅读关于 qt 小部件的文档,它说小部件在自己的绘制事件中进行自己的绘制......但是这里的行为与实际尝试在外面使用画家时发生的那种故障非常相似的油漆事件。而且肯定和那个setText()有关,所以……喃喃自语。

在我写这篇文章时,应用程序已经运行了几个小时,没有任何显示锁定,通过 qDebug() 将相同的文本输出到控制台。如果我取消注释 setText(),大约需要 5 分钟才能出现问题。它是 100% 可重复的。

有什么我应该做而我没有做的事情,绘画方面的还是类似的?

感谢您的帮助。

【问题讨论】:

  • 是的,ui 线程没有正确绘制您的数据。使用事件监听器,你应该没问题。它发生在我身上好几次了。
  • Sreekar,我不知道您所说的“使用事件侦听器”是什么意思——我是否应该为窗口提供一个绘画事件,即使我没有绘画?是这样吗?
  • “我知道对于某些事情,Qt 希望在绘画事件中完成绘画”。不只是为某些事情,为所有人。但是没有正常的方法(除了像grabToImage这样的例外)做任何绘画,而是在必要时通过QWidget::update()在事件循环中触发重绘。 QLabel::setText() 也是如此。
  • "如果我将其他所有内容都留在里面,但取出 setText(),则不会出现问题。" - 这是什么意思?如果您取出 setText(),则根本没有更新。我假设您验证了 setText() 行在您期望更新发生时一直被调用?
  • @fyngyrz 你是否从非 ui 线程调用 ui-&gt;fourLineLabel-&gt;setText(rbl);?

标签: c++ qt


【解决方案1】:

一般来说,你不应该从非 UI 线程更新 Qt 控件,只允许在非 UI 线程中进行少量关于绘画的事情 - http://doc.qt.io/qt-4.8/threads-modules.html

如果您需要从非 UI 线程更新 UI - 使用信号和插槽(QueuedConnection 或 BlockingQueuedConnection 连接,但请确保不要使用 BlockingQueuedConnection 造成死锁)。或者,如果您不想为一些简单的更新创建额外的信号和插槽 - 使用 invokeMethod(它甚至可以返回值,如果您将它与 BlockingQueuedConnection 连接类型一起使用,您的线程将等待 UI 更新)。

还有一个一般性建议 - 如果您有可能的话 - 拨打一个电话以对 UI 进行大量更新,而不是几个小电话。

【讨论】:

    【解决方案2】:

    始终建议 GUI 线程通过 signal-slot 机制与所有其他对象交互。事实上,不会对主线程进行直接调用。以这种方式,GUI 将响应,我们最终不会等待它回来。当然轮询解决方案并不理想,应该避免,因为它们最终会无缘无故地使用杯子资源。

    如果只使用 QThread 类型的线程,那么更新 GUI 应该使用 signal-slot 机制。当需要对呈现的数据事件进行序列化时,使用Qt::QueuedConnection 就足够了。你的情况是这样。

    如果不使用 that ,则信号可能不会按发出的顺序处理。 Qt::BlockingQueuedConnection 应该只在我们想要在接收器上的槽完成之前限制调用者继续处理时使用。对于在 GUI 线程上进行的处理来说,这种情况很少见。

    当我们想从非 qt 线程连接时必须特别小心,例如一个标准线程,因为创建的对象例如在本机线程中,接收端将不知道。

    从non-ui 线程更新ui 的一种方法是序列化和复制您的消息。执行以下操作(甚至适用于非 QThreads,例如 boost::thread ):

    • 设置一个为 force-emit 提供公共方法的单例 QObject 包含您要发送的数据的信号,例如单身人士
    • 在只接受 value 参数的对象中设置槽
    • 将信号连接到 ui-thread 内对象中的插槽
    • 连接必须是Qt::QueuedConnection

      class timer : public QObject
      { 
      Q_OBJECT
      //... write a singleton here
      
      std::mutex mut;
      
      public signals:
      signal_tic(QString const );
      
      public: 
      void force_emit_tic(QString const s )
      {
         std::lock_guard<std::mutex> l(mut);
         emit signal_tic(s);
      }
      
      timer & ref() 
      {
        static timer This;
        return This;
      }
      private:
      timer(){}
      };
      
      // in a main thread object setup this connection
      
      connect(&timer::ref(),SIGNAL(signal_tic(Qstring 
      const)),this,SLOT(slot_accept_tic(QString const ), Qt::QueuedConnection)
      
      // In any other thread
      timer::ref()::force_emit_tic( string_when_this_happened )
      

    直接调用单例 force-emit 方法会产生所需的行为。 (当然,对象必须可以正确复制才能正常工作)

    按值发送的原因是,如果您将 const 引用传递给临时驻留在另一个线程中,则无法保证其生命周期。此外,您需要在消息实际到达之前将消息序列化到 ui-thread,否则您最终将收到不一致的数据或 SIGSEGV 之一。 Qt::QueuedConnection 保证连接仅在 QThreads 已知的内存空间内进行序列化。

    【讨论】:

    • 我使用队列;当我想要更新某些东西时,我将它传递给一个队列,GUI 线程的一个计时器会监视队列,当有更新要进行时,这就是生成它们的线程。我一直都在这样做。让我印象深刻的是很久以前的一段遗留代码。我没有考虑它,没有寻找它,甚至没有想过它可能与我有关,直到我被刺激。现在效果很好。 :)
    • @fyngyrz 无论你是在做一个队列,你都必须用锁来保护它,并向 vale 传递参数的槽发出信号。我只是提供一种方法,使您的应用程序可以从非 ui 线程全局寻址 umi 线程。
    • @g241 - 队列加载和卸载使用互斥锁进行保护。没有信号发送。如果队列包含要处理的项目,图形线程会在计时器事件期间发现,因为队列头!=队列尾。它会立即卸载项目(在锁定的互斥体的上下文中)并更新它们(在互斥体之外,并且在 GUI 线程的上下文中)。不使用其他信号。这似乎工作正常。我认为没有理由不应该这样做。如果有的话,你可以给我解释一下,我会非常感激,并会奖励你。
    • @fyngyrz ,(1) 您的 GUI 线程阻塞在队列中请求死锁并使您的 GUI 无响应。让计时器在 GUI 线程上等待会使您的 GUI 响应。 (2) 查找模型-视图-控制器。 (3) 由于计时器生成事件,为什么不直接将数据适当地转发给 GUI 线程,而不是发信号通知它,然后让它在阻塞队列上等待以读取可能从一开始就为 GUI 发送的数据作画。 Qt 中的一个标准误解是 GUI 线程必须完成所有工作。不,它只需要发送信号和处理油漆。
    • 它不会阻塞队列。它会将队列锁定足够长的时间以卸载一个事件,假设其中有一个事件,然后开始执行它被告知的事情。是lock,c的三个本地行,和unlock,都是在正常的GUI线程操作过程中。而已。这根本不会影响 GUI。另外,我不发信号。队列就是信号。这些事件是完全异步的。任何一个线程都会停止的唯一时间是另一个线程在同一时间进行加载或卸载,这也只是 c 的几行。纳秒。
    猜你喜欢
    • 1970-01-01
    • 2020-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多