Russian Qt Forum
Октябрь 19, 2017, 08:30 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: Граббить содержимое виджета по его изменению.  (Прочитано 1931 раз)
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2571


Просмотр профиля
« : Февраль 13, 2017, 22:01 »

Всем доброго времени.

Допустим, есть некий тестовый виждет (чисто для примера), который имеет кнопочку
и на котором периодически по таймеру с периодом в 1 секунду рисуется некий текст.

Стоит задача поймать момент перерисовки этого виджета, сграббить все его содержимое
в пиксмапу и отправить на другой целевой виджет.

Вот простой примерчик:

Код
C++ (Qt)
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include <QPixmap>
#include <QTimer>
#include <QPainter>
 
class Widget : public QWidget
{
   Q_OBJECT
public:
   explicit Widget(QWidget *parent = 0)
       : QWidget(parent)
       , m_target(new QLabel)
       , m_timer(new QTimer(this))
   {
       m_target->show();
 
       auto pb = new QPushButton(tr("Hello"), this);
       Q_UNUSED(pb);
 
       connect(m_timer, &QTimer::timeout, [this]() {
           m_text = QString("%1").arg(qrand());
           update(); });
       m_timer->start(1000);
   }
 
   void paintEvent(QPaintEvent *event)
   {
       Q_UNUSED(event);
 
       {
           // Рисуем сначала все в нашу пиксмапу.
           QPainter p(&m_pixmap);
           p.drawText(rect(), Qt::AlignBottom, m_text);
       }
 
       // Дублируем пиксмапу на другом целевом виджете.
       m_target->setPixmap(m_pixmap);
 
       {
           // Потом рисуем пиксмапу на нашем главном виджете.
           QPainter p(this);
           p.drawPixmap(m_pixmap.rect(), m_pixmap);
       }
 
       // Потом очищаем пиксмапу.
       m_pixmap = QPixmap(size());
   }
 
   void resizeEvent(QResizeEvent *event)
   {
       QWidget::resizeEvent(event);
       m_pixmap = QPixmap(size());
   }
 
private:
   QLabel *m_target;
   QTimer *m_timer;
   QPixmap m_pixmap;
   QString m_text;
};
 
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   Widget w;
   w.show();
   return a.exec();
}
 
#include "main.moc"
 

Проблема в том, что не получается получить пиксмапу кнопки, т.к. она не рисуется в этом виджете в paintEvent.
Если вызывать методы типа grab(), или render() внутри paintEvent(), то оно ругается на рекурсию.

Собственно вопрос: как сграббить все содержимое виждета только по его изменениям, без всякого
там поллинга и прочего? Облазил все что возможно, но так и не нашел вменяемого решения.
Записан

ArchLinux x86_64 / Win10 64 bit
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2398

чтоб работа волком не казалась :)


Просмотр профиля WWW
« Ответ #1 : Февраль 13, 2017, 22:06 »

поскольку перерисовка может вызваться и без изменения контента, то только поллить...
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

http://sintegrial.com - всякий хороший Qt софт (был Грустный )
twp
Программист
*****
Offline Offline

Сообщений: 595


Просмотр профиля
« Ответ #2 : Февраль 13, 2017, 22:24 »

Как вариант, чтоб получить пиксмап, можно использовать QScreen::grabWindow(). В отличие от QWidget::grab() и QWidget::render() там используются системные вызова, а не QPainter.
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2571


Просмотр профиля
« Ответ #3 : Февраль 13, 2017, 22:44 »

Цитата: Racheengel
поскольку перерисовка может вызваться и без изменения контента, то только поллить...

Не, такой вариант не годится.

Цитата: twp
Как вариант, чтоб получить пиксмап, можно использовать QScreen::grabWindow().

Хм, да, спссибо. Но оно имеет недостатки:

1. Слишком медленно. Например, при размере ректа 1024х768 граббинг у
меня занимает ~13-14 миллисекунд, а на 1920х1080 - ~20 миллисекунд,
что при частоте обновления ~20 ФПС дает заметные подлагивания
(см. пример ниже).

UPD: Хотя, если упираемся в 20 мс, то это теоретически даст ~50 FPS..
Что вроде-как приемлемо.

2. Оно не поддерживается на iOS, судя по доке.
Что уже является весьма опасным.

3. Не граббит, если виджет скрыт.

Код
C++ (Qt)
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include <QPixmap>
#include <QTimer>
#include <QPainter>
#include <QScreen>
#include <QElapsedTimer>
#include <QDebug>
 
class Widget : public QWidget
{
   Q_OBJECT
public:
   explicit Widget(QWidget *parent = 0)
       : QWidget(parent)
       , m_target(new QLabel)
       , m_timer(new QTimer(this))
       , m_screen(QGuiApplication::primaryScreen())
   {
 
       m_target->show();
 
       auto pb = new QPushButton(tr("Hello"), this);
       Q_UNUSED(pb);
 
       connect(m_timer, &QTimer::timeout, [this]() {
           m_text = QString("%1").arg(qrand());
           update(); });
       m_timer->start(50); // ~20 FPS
   }
 
   void paintEvent(QPaintEvent *event)
   {
       Q_UNUSED(event);
 
       QPainter p(this);
       p.drawText(rect(), Qt::AlignBottom, m_text);
 
       QElapsedTimer et;
       et.start();
       const auto pixmap = m_screen->grabWindow(winId());
       qDebug() << "elapsed:" << et.elapsed();
 
       m_target->setPixmap(pixmap);
   }
 
private:
   QLabel *m_target;
   QTimer *m_timer;
   QScreen *m_screen;
   QString m_text;
};
 
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   Widget w;
   w.resize(1920, 1080);
   w.show();
   return a.exec();
}
 
#include "main.moc"
 

Есть еще какие-нибудь варианты?
« Последнее редактирование: Февраль 13, 2017, 23:22 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
twp
Программист
*****
Offline Offline

Сообщений: 595


Просмотр профиля
« Ответ #4 : Февраль 13, 2017, 23:15 »

А кнопка видима? Если так, то надо скрыть и тогда можно спокойно граббить. Но понятно что кнопку нельзя схайдить - ибо на нее надо кликать. А вот с QLabel проблем не должно быть.
« Последнее редактирование: Февраль 13, 2017, 23:20 от twp » Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2571


Просмотр профиля
« Ответ #5 : Февраль 13, 2017, 23:19 »

Эмм... Не понял.. Да, кнопка видима. Она должна также граббится. Т.е. при наведении на нее,
при клике по ней, все изменения ее состояния также должны отображаться и на целевом виджете.
Записан

ArchLinux x86_64 / Win10 64 bit
twp
Программист
*****
Offline Offline

Сообщений: 595


Просмотр профиля
« Ответ #6 : Февраль 13, 2017, 23:22 »

Да, я вверху подправил свой текст. Просто такой подход я видел, но там используется QLabel, которая скрыта. Но вот с кнопкой очевидно уже так не получится.
Записан
twp
Программист
*****
Offline Offline

Сообщений: 595


Просмотр профиля
« Ответ #7 : Февраль 13, 2017, 23:35 »

Я более внимательно глянул код, и первое что пришло в голову - почему бы не перенести граббинг и установку в целевой виджет за пределы paintEvent? например в самом конце paintEvent поставить QTimer::singleShot(0, [=] {....});
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2571


Просмотр профиля
« Ответ #8 : Февраль 14, 2017, 00:19 »

В таком случае оно срабатывает вечно:

Код
C++ (Qt)
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include <QPixmap>
#include <QTimer>
#include <QPainter>
#include <QPaintEvent>
#include <QScreen>
#include <QElapsedTimer>
#include <QDebug>
 
class Widget : public QWidget
{
   Q_OBJECT
public:
   explicit Widget(QWidget *parent = 0)
       : QWidget(parent)
       , m_target(new QLabel)
       , m_timer(new QTimer(this))
       , m_screen(QGuiApplication::primaryScreen())
   {
       //setAttribute(Qt::WA_DontShowOnScreen);
 
       m_target->show();
 
       auto pb = new QPushButton(tr("Hello"), this);
       Q_UNUSED(pb);
 
       connect(m_timer, &QTimer::timeout, [this]() {
           //m_text = QString("%1").arg(qrand());
           //update();
       });
       m_timer->start(5000); // ~20 FPS
   }
 
   void paintEvent(QPaintEvent *event)
   {
       Q_UNUSED(event);
 
       QPainter p(this);
       p.drawText(rect(), Qt::AlignBottom, m_text);
 
       QTimer::singleShot(0, [=](){
           QElapsedTimer et;
           et.start();
           QPixmap pixmap = grab();
           qDebug() << "elapsed:" << et.elapsed();
           m_target->setPixmap(pixmap);
       });
   }
 
private:
   QLabel *m_target;
   QTimer *m_timer;
   QScreen *m_screen;
   QString m_text;
};
 
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   Widget w;
   w.resize(800, 600);
   w.show();
   return a.exec();
}
#include "main.moc"
 

Таймер singleShot запускает grab(), который вызывает repaint() который вызывает paintEvent(?) который запускает таймер singleShot.

Проблема в том, блин, что grab(), что render() вызывают paintEvent()... вот если бы как-то заблокировать рекурсию..
« Последнее редактирование: Февраль 14, 2017, 00:21 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
twp
Программист
*****
Offline Offline

Сообщений: 595


Просмотр профиля
« Ответ #9 : Февраль 14, 2017, 01:07 »

Ну да, тут только граббить через QScreen::grabWindow(). Главное отрисовка будет происходить быстро, а тормознутый вызов QScreen::grabWindow() вынести за ее пределы. Если конечно работа в iOS не предвидится. Вообще, что-то уж очень закручено получается, может стоит пересмотреть саму архитектуру приложения.
Записан
twp
Программист
*****
Offline Offline

Сообщений: 595


Просмотр профиля
« Ответ #10 : Февраль 14, 2017, 01:29 »

Но если все таки хочется использовать grab(), то очевидно надо это делать по событию таймера и фактически отрисовка будет происходить дважды: самого виджета и в пиксмап:
Код
C++ (Qt)
connect(m_timer, &QTimer::timeout, [this]() {
   m_text = QString("%1").arg(qrand());
   update();
   QPixmap pixmap = grab();
   m_target->setPixmap(pixmap);
});
 
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2571


Просмотр профиля
« Ответ #11 : Февраль 14, 2017, 09:30 »

Цитировать
а тормознутый вызов QScreen::grabWindow() вынести за ее пределы. Если конечно работа в iOS не предвидится.

Не, QScreen::grabWindow() не вариант также, т.к. не работает для скрытого виджета или у виджета с аттрибутами Qt::WA_DontShowOnScreen.
Мне нужно чтобы работало у таких виджетов (я не дописал это в начале).

Цитировать
Вообще, что-то уж очень закручено получается, может стоит пересмотреть саму архитектуру приложения.

Куда тут еще что пересматривать? Нужно отправлять/дублировать/перенаправлять новые фреймы из текущего виджета в нужный целевой виджет.. всЁ.

Единственный "вменяемый" вариант - это все самому рисовать в paintEvent(), включая кнопочки и прочее, например в пиксмапу, которую потом
отрисовывать в виджет. Тогда не нужен grab() вообще и достаточно просто эту пиксмапу также рисовать на целевом виджете..
Блин, но это все гимор какой-то..  В замешательстве
Записан

ArchLinux x86_64 / Win10 64 bit
GreatSnake
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2908



Просмотр профиля
« Ответ #12 : Февраль 14, 2017, 11:42 »

Проблема в том, блин, что grab(), что render() вызывают paintEvent()... вот если бы как-то заблокировать рекурсию..
Попробуй запускать таймер только в случае, когда на виджете не выставлен атрибут Qt::WA_WState_InPaintEvent.
Записан

Qt 5.5.1/4.8.7 (X11/Win)
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 9816


Просмотр профиля
« Ответ #13 : Февраль 14, 2017, 12:01 »

Проблема в том, что не получается получить пиксмапу кнопки, т.к. она не рисуется в этом виджете в paintEvent.
Если вызывать методы типа grab(), или render() внутри paintEvent(), то оно ругается на рекурсию.
1) В коде рисования послать сигнал с QueuedConnection, а в нем уже вызвать grab() или render()
2) Отловить QEvent::UpdateRequest, после его выполнения должен быть готов буфер QImage, содрать оттуда
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2571


Просмотр профиля
« Ответ #14 : Февраль 14, 2017, 12:17 »

Цитата: GreatSnake
Попробуй запускать таймер только в случае, когда на виджете не выставлен атрибут Qt::WA_WState_InPaintEvent.

Имхо, не выход все-равно, т.к. рекурсия будет все-равно.

Цитата: Igors
1) В коде рисования послать сигнал с QueuedConnection, а в нем уже вызвать grab() или render()

А сами то пробовали?

Цитата: Igors
2) Отловить QEvent::UpdateRequest, после его выполнения должен быть готов буфер QImage, содрать оттуда

Каким же образом содрать?
Записан

ArchLinux x86_64 / Win10 64 bit
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  

Страница сгенерирована за 0.2 секунд. Запросов: 22.