Название: Граббить содержимое виджета по его изменению. Отправлено: kuzulis от Февраль 13, 2017, 22:01 Всем доброго времени.
Допустим, есть некий тестовый виждет (чисто для примера), который имеет кнопочку и на котором периодически по таймеру с периодом в 1 секунду рисуется некий текст. Стоит задача поймать момент перерисовки этого виджета, сграббить все его содержимое в пиксмапу и отправить на другой целевой виджет. Вот простой примерчик: Код
Проблема в том, что не получается получить пиксмапу кнопки, т.к. она не рисуется в этом виджете в paintEvent. Если вызывать методы типа grab(), или render() внутри paintEvent(), то оно ругается на рекурсию. Собственно вопрос: как сграббить все содержимое виждета только по его изменениям, без всякого там поллинга и прочего? Облазил все что возможно, но так и не нашел вменяемого решения. Название: Re: Граббить содержимое виджета по его изменению. Отправлено: Racheengel от Февраль 13, 2017, 22:06 поскольку перерисовка может вызваться и без изменения контента, то только поллить...
Название: Re: Граббить содержимое виджета по его изменению. Отправлено: twp от Февраль 13, 2017, 22:24 Как вариант, чтоб получить пиксмап, можно использовать QScreen::grabWindow(). В отличие от QWidget::grab() и QWidget::render() там используются системные вызова, а не QPainter.
Название: Re: Граббить содержимое виджета по его изменению. Отправлено: kuzulis от Февраль 13, 2017, 22:44 Цитата: Racheengel поскольку перерисовка может вызваться и без изменения контента, то только поллить... Не, такой вариант не годится. Цитата: twp Как вариант, чтоб получить пиксмап, можно использовать QScreen::grabWindow(). Хм, да, спссибо. Но оно имеет недостатки: 1. Слишком медленно. Например, при размере ректа 1024х768 граббинг у меня занимает ~13-14 миллисекунд, а на 1920х1080 - ~20 миллисекунд, что при частоте обновления ~20 ФПС дает заметные подлагивания (см. пример ниже). UPD: Хотя, если упираемся в 20 мс, то это теоретически даст ~50 FPS.. Что вроде-как приемлемо. 2. Оно не поддерживается на iOS, судя по доке (http://doc.qt.io/qt-5/qscreen.html#grabWindow). Что уже является весьма опасным. 3. Не граббит, если виджет скрыт. Код
Есть еще какие-нибудь варианты? Название: Re: Граббить содержимое виджета по его изменению. Отправлено: twp от Февраль 13, 2017, 23:15 А кнопка видима? Если так, то надо скрыть и тогда можно спокойно граббить. Но понятно что кнопку нельзя схайдить - ибо на нее надо кликать. А вот с QLabel проблем не должно быть.
Название: Re: Граббить содержимое виджета по его изменению. Отправлено: kuzulis от Февраль 13, 2017, 23:19 Эмм... Не понял.. Да, кнопка видима. Она должна также граббится. Т.е. при наведении на нее,
при клике по ней, все изменения ее состояния также должны отображаться и на целевом виджете. Название: Re: Граббить содержимое виджета по его изменению. Отправлено: twp от Февраль 13, 2017, 23:22 Да, я вверху подправил свой текст. Просто такой подход я видел, но там используется QLabel, которая скрыта. Но вот с кнопкой очевидно уже так не получится.
Название: Re: Граббить содержимое виджета по его изменению. Отправлено: twp от Февраль 13, 2017, 23:35 Я более внимательно глянул код, и первое что пришло в голову - почему бы не перенести граббинг и установку в целевой виджет за пределы paintEvent? например в самом конце paintEvent поставить QTimer::singleShot(0, [=] {....});
Название: Re: Граббить содержимое виджета по его изменению. Отправлено: kuzulis от Февраль 14, 2017, 00:19 В таком случае оно срабатывает вечно:
Код
Таймер singleShot запускает grab(), который вызывает repaint() который вызывает paintEvent(?) который запускает таймер singleShot. Проблема в том, блин, что grab(), что render() вызывают paintEvent()... вот если бы как-то заблокировать рекурсию.. Название: Re: Граббить содержимое виджета по его изменению. Отправлено: twp от Февраль 14, 2017, 01:07 Ну да, тут только граббить через QScreen::grabWindow(). Главное отрисовка будет происходить быстро, а тормознутый вызов QScreen::grabWindow() вынести за ее пределы. Если конечно работа в iOS не предвидится. Вообще, что-то уж очень закручено получается, может стоит пересмотреть саму архитектуру приложения.
Название: Re: Граббить содержимое виджета по его изменению. Отправлено: twp от Февраль 14, 2017, 01:29 Но если все таки хочется использовать grab(), то очевидно надо это делать по событию таймера и фактически отрисовка будет происходить дважды: самого виджета и в пиксмап:
Код
Название: Re: Граббить содержимое виджета по его изменению. Отправлено: kuzulis от Февраль 14, 2017, 09:30 Цитировать а тормознутый вызов QScreen::grabWindow() вынести за ее пределы. Если конечно работа в iOS не предвидится. Не, QScreen::grabWindow() не вариант также, т.к. не работает для скрытого виджета или у виджета с аттрибутами Qt::WA_DontShowOnScreen. Мне нужно чтобы работало у таких виджетов (я не дописал это в начале). Цитировать Вообще, что-то уж очень закручено получается, может стоит пересмотреть саму архитектуру приложения. Куда тут еще что пересматривать? Нужно отправлять/дублировать/перенаправлять новые фреймы из текущего виджета в нужный целевой виджет.. всЁ. Единственный "вменяемый" вариант - это все самому рисовать в paintEvent(), включая кнопочки и прочее, например в пиксмапу, которую потом отрисовывать в виджет. Тогда не нужен grab() вообще и достаточно просто эту пиксмапу также рисовать на целевом виджете.. Блин, но это все гимор какой-то.. :-\ Название: Re: Граббить содержимое виджета по его изменению. Отправлено: GreatSnake от Февраль 14, 2017, 11:42 Проблема в том, блин, что grab(), что render() вызывают paintEvent()... вот если бы как-то заблокировать рекурсию.. Попробуй запускать таймер только в случае, когда на виджете не выставлен атрибут Qt::WA_WState_InPaintEvent.Название: Re: Граббить содержимое виджета по его изменению. Отправлено: Igors от Февраль 14, 2017, 12:01 Проблема в том, что не получается получить пиксмапу кнопки, т.к. она не рисуется в этом виджете в paintEvent. 1) В коде рисования послать сигнал с QueuedConnection, а в нем уже вызвать grab() или render() Если вызывать методы типа grab(), или render() внутри paintEvent(), то оно ругается на рекурсию. 2) Отловить QEvent::UpdateRequest, после его выполнения должен быть готов буфер QImage, содрать оттуда Название: Re: Граббить содержимое виджета по его изменению. Отправлено: kuzulis от Февраль 14, 2017, 12:17 Цитата: GreatSnake Попробуй запускать таймер только в случае, когда на виджете не выставлен атрибут Qt::WA_WState_InPaintEvent. Имхо, не выход все-равно, т.к. рекурсия будет все-равно. Цитата: Igors 1) В коде рисования послать сигнал с QueuedConnection, а в нем уже вызвать grab() или render() А сами то пробовали? Цитата: Igors 2) Отловить QEvent::UpdateRequest, после его выполнения должен быть готов буфер QImage, содрать оттуда Каким же образом содрать? Название: Re: Граббить содержимое виджета по его изменению. Отправлено: GreatSnake от Февраль 14, 2017, 13:10 Да, с атрибутом я погорячился.
Достаточно, как посоветовал Igors всего-лишь поймать UpdateRequest: Код Т.е. никакие таймеры и сигналы не нужны. Название: Re: Граббить содержимое виджета по его изменению. Отправлено: Igors от Февраль 14, 2017, 13:57 А сами то пробовали? А что Вас смущает в этом стандартном/банальном приеме?Каким же образом содрать? В Qt 5 вроде можно и так Код У меня то же самое но через приватные хедеры (для более ранних Qt), этот код рабочий Название: Re: Граббить содержимое виджета по его изменению. Отправлено: kuzulis от Февраль 14, 2017, 14:55 Всем спасибо (я пока не пробовал то что вы предложили),
остановился пока на своем варианте с прорисовкой всех контролов вручную через стили (благо там у меня всего две кнопочки надо отрисовать).. :) Сначала рисую в пиксмапу все что нужно, а потом рисую из этой пиксмапы в виджет, а также передаю эту пиксмапу в целевой виджет. Название: Re: Граббить содержимое виджета по его изменению. Отправлено: kuzulis от Февраль 14, 2017, 15:32 UPD: Попробовал с QEvent::UpdateRequest и всроде оно работает (по крайней мере под windows), спасибо! :)
Но немного медленнее чем с "прямым" рисованием контролов ручками. Название: Re: Граббить содержимое виджета по его изменению. Отправлено: GreatSnake от Февраль 14, 2017, 16:57 Но немного медленнее чем с "прямым" рисованием контролов ручками. Дык за всё нужно платить. Особенно за пиксмапы, которые хранятся на стороне граф.подсистемы.Попробуй вместо QPixmap задействовать QPicture: Код
|