How can I delay the process in Qt

I'm trying to make a visualization of searching for the max value of the array. The idea of it is to show the pointer position in each iteration. There's a grid layout built in the following way: first line includes ten labels showing the index of each element of the array, in the second line there are ten line edits with array values, and in the third there come ten labels which can have text " " (no pointer) or "^" (pointer on this value). It looks like

0 1 2 3 4 5 6 7 8 9 - the row of indexes

12 23 34 54 78 1 32 26 55 26 - the elements

_ _ _ ^ _ _ _ _ _ _ - the row of pointers

When searching for the maximum the pointer must move to the next element after each iteration. And for comfortable perception it is needed to wait a sec during each iteration if the cycle. The code of the slot is:

void Widget::slotFindMax()
{
    int max = a[0];
    pointer[0]->setText("^");
    maxValue->setText(QString::number(max));
    for (int i = 1; i < 10; i++)
    {
        pointer[i - 1]->setText(" ");
        QThread::sleep(1);
        pointer[i]->setText("^");
        if (a[i] > max)
        {
            max = a[i];
            maxValue->setText(QString::number(max));
        }
    }
    pointer[9]->setText(" ");
}

I see that QThread::sleep() doesn't work correctly here. As far as I understood QTimer::singleShot also isn't suitable, cause I need a delay not before processing the slot but during it. Could anyone who faced such a problem give an example of correct using of the functions I mentioned or give an equivalent example which could work in this case?

Upvotes: 1

Views: 1669

Answers (3)

Blocking and GUIs don't mix. Write in asynchronous style, leverage C++11:

class Widget : ... {
  // use QVector, std::vector or std::array for pointer and a!
  QTimer m_animationTimer { this };
  ...
};

void Widget::slotFindMax()
{
  m_animationTimer.start(1000);
  int i = 0;
  int max = std::numeric_limits<int>::min();
  auto loop = [this, i, max]() mutable {
    if (i > 0) pointer[i-1]->setText(" ");
    if (i == pointer.size()) {
      m_animationTimer.disconnect();
      return m_animationTimer.stop();
    }
    pointer[i]->setText("^");
    if (a[i] > max) {
       max = a[i];
       maxValue->setText(QString::number(max));
    }
    i ++;
  });
  loop(); // run the first iteration
  connect(&m_animationTimer, &QTimer::timeout, loop);
}

The loop functor captures the mutable values of i and max by value, and runs once immediately, then once every second until the entire pointer array has been iterated over. It then stops the timer. The functor is invoked from the QObject::event method of the base QObject, and the call comes as a QTimerEvent is processed from the event loop. Since your code doesn't block the event loop, it remains responsive.

In C++98/Qt 4, you'd have to put the functor into a slot in a QObject-derived class, etc. It'd be more verbose, but the logic would ultimately be the same.

Upvotes: 1

Ilya
Ilya

Reputation: 5557

As far as I understood QTimer::singleShot also isn't suitable, cause I need a delay not before processing the slot but during it.

QTimer::singleShot can be suitable if, instead of your for loop, you have a delayed call (using a timer) do a function with the index as a parameter and containing the body of the loop. That function can then call ifself recursively with an incremented index.

Upvotes: 0

Adrian Maire
Adrian Maire

Reputation: 14865

To summarize if I understood correctly your question:

  • Save initial time
  • Run the calculation
  • If calculation took more than 1s, just go to the next iteration
  • If time elapsed is smaller than 1s, wait up to 1s

I just suggest you to wait remaining time after the calculation is done.

// Get initial time
auto t1 = QDateTime::currentMSecsSinceEpoch();

// Make a complex operation
if (a[i] > max)
{
    max = a[i];
    maxValue->setText(QString::number(max));
}

// Get final time
auto t2 = QDateTime::currentMSecsSinceEpoch();

// Wait until the end of the 1s if required, or directly continue
if (t2-t1 < 1000) QThread::msleep(1000-(t2-t1));

// Update the pointer
if (i>0) pointer[i - 1]->setText(" ");
pointer[i]->setText("^");

Upvotes: 0

Related Questions