2014-02-09 65 views
3

我很难理解必须向大屏幕上显示大量数据的应用程序的最佳方法,该屏幕正在以高速更新。我正在为Qt编写这个应用程序。我不会介绍实际应用的细节,但我已经在下面编写了一个示例应用程序来演示此问题。Qt中很多小部件的快速更新GUI

在这我有一个线程是计算值。在这种情况下,这是一个简单的计数器。在真正的应用程序中有很多值。这每毫秒更新一次。该比率是数据所需的计算速率,而不是GUI所需的更新速率。如上所述,这是在它自己的线程中完成的。这个线程的想法是它只是关于数据的计算,并不关心它的显示。

现在要更新此示例中的数据显示,我使用QLabels网格多次显示该值(模拟许多不同值的显示)。我从Qt文档中了解到,Widgets的更新必须在主GUI线程中完成。所以我在这里做的是我得到的线程计算值发出一个信号与计算值每次重新计算(1毫秒)。然后连接到主GUI,然后依次更新每个小部件以显示值。

从这样得出的结论是:

  1. GUI线程是由从数据线1ms的更新淹没,所以显示变得更新很慢。在我的机器上,有100个小部件更新,我估计更新大约为4fps。
  2. 图形用户界面的所有其他方面,如移动,调整大小和按下按钮都在努力获得处理器时间。
  3. 看来只是用文本更新一个简单的QLabel似乎很慢。这是正常的吗?
  4. 我已经为此添加了时间代码,并且GUI线程中的1ms更新大约1.8ms运行于avergae上,最大增量时间为40-75ms。所以即使它落后,其运行速度也非常快。但大概在幕后,其他事件正在GUI线程事件队列中,以实际将更新画到屏幕上,而这些事情真的很挣扎。
  5. 我真的不明白什么是确定实际的屏幕更新率? Qt如何决定何时更新屏幕?

最重要的是我不确定更新要显示的数据的正确方法是什么。很明显,屏幕不需要在1毫秒内更新,即使显示器无法快速刷新。另一方面,我不希望我的数据线程与屏幕更新速度有关。有没有更好的方式从数据线程获取数据到GUI而不需要淹没GUI事件队列?

任何深入了解这个问题的Qt方法将不胜感激。

下面是在自己的线程运行在数据发生器:

class Generator : public QObject 
{ 
    Q_OBJECT 
public: 
    explicit Generator(QObject *parent = 0); 

signals: 
    void dataAvailable(int val); 
public slots: 
    void run(bool run); 
    void update(void); 
private: 
    QTimer *timer; 
    int  value; 
}; 

void Generator::run(bool run) 
{ 
    if(run) 
    { 
     value = 0; 
     timer = new QTimer; 
     connect(timer, SIGNAL(timeout()), this, SLOT(update())); 
     timer->start(1); 
    } else 
    { 
     timer->stop(); 
     delete timer; 
    } 
} 

void Generator::update() 
{ 
    value++; 
    emit dataAvailable(value); 
} 

而这里的主界面类更新dislay:已为我在过去的工作

class MainWindow : public QMainWindow 
{ 
    Q_OBJECT 

public: 
    explicit MainWindow(QWidget *parent = 0); 

private: 
    QLabel  *labels[ROW_MAX][COL_MAX]; 
    Generator *dataGenerator; 

public slots: 
    void dataAvailable(int val); 

signals: 
    void runGenerator(bool run); 
}; 

MainWindow::MainWindow(QWidget *parent) : 
    QMainWindow(parent) 
{ 
    QGridLayout *layout = new QGridLayout; 
    for(int iCol=0; iCol<COL_MAX; iCol++) 
    { 
     for(int iRow=0; iRow<ROW_MAX; iRow++) 
     { 
      QLabel *label = new QLabel("Hello"); 
      labels[iRow][iCol] = label; 
      layout->addWidget(labels[iRow][iCol], iRow, iCol); 
     } 
    } 
    centralWidget->setLayout(layout); 

    dataGenerator = new Generator; 
    QThread *dgThread = new QThread; 
    dataGenerator->moveToThread(dgThread); 
    dgThread->start(QThread::HighestPriority); 

    connect(this, SIGNAL(runGenerator(bool)), dataGenerator, SLOT(run(bool))); 
    connect(dataGenerator, SIGNAL(dataAvailable(int)), this, SLOT(dataAvailable(int))); 

    emit runGenerator(true); 

} 

void MainWindow::dataAvailable(int val) 
{ 
    for(int iCol=0; iCol< COL_MAX; iCol++) 
    { 
     for(int iRow=0; iRow<ROW_MAX; iRow++) 
     { 
      labels[iRow][iCol]->setText(QString::number(val)); 
     } 
    } 
} 

回答

2

一种方法是将accessor方法构建到worker对象中,然后让视图根据自己的更新周期“拉”数据。

要使用示例代码,添加这样的方法来Generator

// TODO For a more complex class, you probably want one "get all the stats" 
// accessor rather than lots of little methods -- that way all the data is 
// synced up 
int Generator::getCurrentValue() 
{ 
    QMutexLocker(mutex); // (also add a QMutex member to the class) 
    return value; 
} 

然后给主窗口自身的更新计时器不会锤系统很辛苦:

MainWindow::MainWindow(QWidget *parent) : 
    QMainWindow(parent) 
{ 
    // ... 
    // Replace dataAvailable code with this: 
    updateTimer = new QTimer; 
    connect(updateTimer, SIGNAL(timeout()), this, SLOT(updateDisplayedValues()); 
    updateTimer->start(MY_MAIN_WINDOW_UPDATE_RATE); // some constant; try 200 ms? 
    // ... 
} 

void MainWindow::updateDisplayedValues() 
{ 
    int val = dataGenerator->getCurrentValue(); 

    // TODO You might this more efficient by checking whether you *need to* 
    // repaint first here; generally Qt is pretty good about not wasting cycles 
    // on currently-hidden widgets anyway 

    for(int iCol=0; iCol< COL_MAX; iCol++) 
    { 
     for(int iRow=0; iRow<ROW_MAX; iRow++) 
     { 
      labels[iRow][iCol]->setText(QString::number(val)); 
     } 
    } 
} 
+0

感谢您的建议亚历克斯。是的这是有道理的。我其实只是尝试了类似的地方,我原来的(1ms)插槽只是保存了值,但不更新显示。然后我有第二个计时器,就像您建议以更合理的速率更新显示器一样。我必须承认,我非常喜欢使用信号/插槽机制来处理线程交叉问题,而不是互斥锁,这些互斥锁在发生锁定时通常会隐藏情况。不是我可以想到任何可能与你的建议有关的错误。 – SteveC

+0

最后一个问题,没有人知道当你尝试和更新小部件的速度比他们可以绘制,或者如何知道实际的屏幕更新率是什么?或者换句话说,除了视觉效果之外,你怎么知道你已经超越了性能限制? – SteveC

+0

如果更多的事情发生得比视频缓冲区允许的更快,Qt会将多个'update'调用合并到一个'repaint'中。 – phyatt

1

如果您只是将您的计算放入一个运行在计时器上的生成器中,则它的事件循环与GUI所在的线程相同。

如果您将发生器移到自己的线程,然后设置一个定时器来检查发生器每秒最多30次,那么应该会看到大部分问题都消失了。告诉它比人眼可以感知的更快地更新显示或者显示器刷新速率甚至可以做到这一点通常是矫枉过正的。就常见的视频速率而言,NTSC可以达到30 fps,而PAL可以达到25 fps。对于数据的文本标签,我通常需要在一秒钟内采样/平均,并每秒更新一次以保持可读性,而不仅仅是数字模糊。

当您开始使用线程时,您应该会看到您的计算机使用不同的内核来管理应用程序中的不同负载。如果你不使用多线程,你可以非常快速地进行任何苛刻的计算。

此外,当你连接到你的线程函数时,一定要使用QueuedConnection而不是AutomaticConnection,因为大多数时候,如果你没有把它拼出来,它会在线程之间选择错误的。

希望有所帮助。