Как я могу отложить процесс в Qt

Я пытаюсь сделать визуализацию поиска максимального значения массива. Идея состоит в том, чтобы показывать положение указателя на каждой итерации. Макет сетки построен следующим образом: первая строка включает десять меток, показывающих индекс каждого элемента массива, во второй строке - десять строк редактирования со значениями массива, а в третьей - десять меток, которые могут содержать текст. (без указателя) или ^ (указатель на это значение). Это выглядит как

0 1 2 3 4 5 6 7 8 9 - ряд индексов

12 23 34 54 78 1 32 26 55 26 - стихии

_ _ _ ^ _ _ _ _ _ _ - ряд указателей

При поиске максимума указатель должен переходить к следующему элементу после каждой итерации. А для комфортного восприятия нужно подождать секунду на каждой итерации цикла. Код слота:

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(" ");
}

Я вижу, что QThread :: sleep () здесь работает некорректно. Насколько я понял, QTimer :: singleShot тоже не подходит, потому что мне нужна задержка не перед обработкой слота, а во время нее. Может ли кто-нибудь, кто сталкивался с такой проблемой, привести пример правильного использования упомянутых мною функций или эквивалентный пример, который мог бы работать в этом случае?


person Юрий Левитан    schedule 04.04.2016    source источник
comment
Ваша проблема в блокировке графического интерфейса. Пока ваш цикл выполняется, взаимодействие с пользователем невозможно, и экран не обновляется. Не блокируйте графический интерфейс.   -  person Kuba hasn't forgotten Monica    schedule 04.04.2016


Ответы (3)


Блокировка и графический интерфейс несовместимы. Пишите в асинхронном стиле, используйте 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);
}

Функтор loop захватывает изменяемые значения i и max по значению и запускается один раз сразу, затем раз в секунду, пока не будет повторен весь массив pointer. Затем он останавливает таймер. Функтор вызывается из метода QObject::event базового QObject, и вызов происходит, когда QTimerEvent обрабатывается из цикла событий. Поскольку ваш код не блокирует цикл событий, он остается отзывчивым.

В C ++ 98 / Qt 4 вам придется поместить функтор в слот в классе, производном от QObject, и т. Д. Это было бы более подробным, но логика в конечном итоге была бы такой же.

person Kuba hasn't forgotten Monica    schedule 04.04.2016
comment
В нем говорится, что «поле m_animationTimer имеет неполный тип». Должен ли он быть определен в заголовке? - person Юрий Левитан; 07.04.2016
comment
@ ЮрийЛевитан Вы должны #include <QTimer> в заголовке, где объявлен класс Widget. Это требование C ++, не зависящее от Qt. - person Kuba hasn't forgotten Monica; 07.04.2016

Подводя итог, правильно ли я понял ваш вопрос:

  • Экономьте начальное время
  • Запустить расчет
  • Если расчет занял более 1 секунды, просто перейдите к следующей итерации.
  • Если истекшее время меньше 1 с, подождите до 1 с.

Я просто предлагаю вам подождать оставшееся время после завершения расчета.

// 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("^");
person Adrian Maire    schedule 04.04.2016
comment
Время расчета очень короткое, не думаю, что это проблема. Думаю, проблема в обновлении экрана. - person Ilya; 04.04.2016

Насколько я понял, QTimer :: singleShot тоже не подходит, потому что мне нужна задержка не перед обработкой слота, а во время нее.

QTimer::singleShot может быть подходящим, если вместо вашего for цикла у вас есть отложенный вызов (с использованием таймера), выполняющий функцию с индексом в качестве параметра и содержащую тело цикла. Затем эта функция может вызывать ifself рекурсивно с увеличенным индексом.

person Ilya    schedule 04.04.2016