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

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

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: QWidget::repaint() и "грязные" области окна.  (Прочитано 12502 раз)
ksergey85
Гость
« : Июль 08, 2015, 13:57 »

Здравствуйте. Отлично понимаю, что вопросы про работу QWidget::repaint() уже наверняка всем надоели, но придется еще разок копнуть в глубь этого метода.
Немного покопавшись в исходниках библиотеки пришел к выводу, что метод repaint() делает немного больше работы, чем я от него ожидал. Если в двух словах, то его работа сводится к тому, что он ищет окно верхнего уровня, которому принадлежит данный виджет и помечает область, занятую виджетом, как "грязную", после чего посылает окну событие на обновление (QEvent::UpdateRequest), обработчик которого выполняет перерисовку всех "грязных" областей. Нюанс как раз заключается в том, что в итоге будут перерисованы ВСЕ области окна, помеченные ранее как "грязные".
В моем случае этот нюанс приводит к тому, внутри вызова QLabel::repaint() для лейбла статус бара, отображающего обычную иконку, может быть перерисовано не только содержимое лейбла, но и содержимое таблицы с данными, также являющейся частью того же окна верхнего уровня (QMainWindow). Такое поведение в моем случае является недопустимым, так как перерисовка содержимого таблицы с данными подразумевает под собой чтение этих самих данных, а это в месте вызова repaint() недопустимо. Получается так, что перерисовка любого даже самого маленького элемента окна может "притащить" за собой целый "паровоз" незапланированных перерисовок.
Вопрос заключается в том, как победить данную оптимизацию и создать виджет (в моем случае способный отобразить иконку), который бы при вызове repaint() перерисовывал только одного себя любимого, но оставался частью статус бара внутри QMainWindow. Или предложите альтернативный подход.
Записан
Bepec
Гость
« Ответ #1 : Июль 08, 2015, 14:43 »

По моему мнению вы заблуждаетесь.
Главное заблуждение тут в том, что у вас перерисовка зависит от чтения данных.
Разделяйте GUI и модель.

PS хотя можете попытаться сломать отлаженный механизм Qt под свои принципы, но готовьтесь к новым проблемам, о которых вы сейчас не знаете Веселый

PPS к тому же ещё 1 заблуждение - пометка области как грязной в большинстве случаев зависит от самого виджета, хотя есть исключения.

И таки да, зависимость перерисовки от чтения - это бред Веселый Свернули-развернули окно, опять будете читать? Веселый
« Последнее редактирование: Июль 08, 2015, 14:45 от Bepec » Записан
ksergey85
Гость
« Ответ #2 : Июль 08, 2015, 14:53 »

По моему мнению вы заблуждаетесь.
Главное заблуждение тут в том, что у вас перерисовка зависит от чтения данных.
Разделяйте GUI и модель.

Извините уважаемый, но где же здесь заблуждение? Таблица с данными сделана по всем правилам MVC. Используются QTableView и QAbstractItemModel (а точнее их потомки). Но при отрисовке QTableView пинает делегата а делегат хочешь не хочешь пинает модель иначе как же он узнает что ему рисовать (информация в ячейках)? Просто в момент перерисовки одного единственного лейбла я никак не ожидаю, что сейчас будут обновлены еще и таблица и еще там что то. Ведь это же логично вызвать label->repaint() и быть уверенным, что перерисован будет именно лейбл а не все то, что было помечено на перерисовку внутри главного окна. Разве нет?

И таки да, зависимость перерисовки от чтения - это бред Веселый Свернули-развернули окно, опять будете читать?
Конечно, а как вы думаете? Думаете он один раз срендерил и дальше вам из буфера бахает? =)
« Последнее редактирование: Июль 08, 2015, 14:58 от ksergey85 » Записан
ksergey85
Гость
« Ответ #3 : Июль 08, 2015, 15:13 »

Да дело даже не в таблице. Представьте ситуацию. У вас имеется два виджета и в результате работы какого то алгоритма они меняют свое содержимое. Пусть для определенности это будет два лейбла с текстом. По выходу из этого алгоритма вы вызываете lable1->repaint(), надеясь на обновление внешнего вида одной метки, а вместо этого будут перерисованы обе, так как setText для label2, вызванный внутри алгоритма переводит его в состояние "грязный" и по выходу из алгоритма обе метки требуют перерисовки. С моей точки зрения здесь имеет место быть некорректная оптимизация. И я бы даже слова не сказал, если бы разработчики библиотеки оставили хоть какой то выход из сложившейся ситуации. Но пока что я не могу его отыскать.
Единственное что можно сделать это создать лейбл как отдельное фреймлесс окно, но как при этом заставить его подчинятся лейаутингу статус бара я ума не приложу. На сколько я знаю без установки пэрента никак, а установка пэрента противоречит понятию окна.
Изменение содержимого виджета (например QLabel::setText()) помечает его "гряхным" и ставит в очередь его перерисовку, которая будет произведена при выходе в основной цикл обработки событий треда (событие QEvent::UpdateLater). Точно также работает update() - просто постит эвент на перерисовку. А repaint() после пометки области виджета "грязной" не постит эвент в очередь а сендит, то есть вызывает обработку события немедленно. Обработчик на перерисовку метки переадресуют всю работу окну, содержащему данный виджет. А окно уже без разбора перерисовывает все грязные области. Вот и все объяснение. Нужен способ перерисовать только один виджет, независимо от того сколько грязных областей имеет окно.
« Последнее редактирование: Июль 08, 2015, 15:30 от ksergey85 » Записан
Bepec
Гость
« Ответ #4 : Июль 08, 2015, 15:23 »

Вы неправы.
При описанном вами алгоритме мы устанавливаем в Label поле text(). И label отрисовывает себя.

Так же и с model-view. View отрисовывает, основываясь на информации в модели. Но никоим образом оно не изменяет информацию в момент отрисовки.

А у вас получается что вы сделали не model-view, а просто View, в paintEvent котором происходит чтение информации и установка в View.

Т.е. по другому выражаясь вам надо данные чтения помещать в Model. И если model написана у вас в соответствии с рекомендациями троллей, вас не должна беспокоить отрисовка.

Отделяйте зёрна от плёвел. Отделите чтение информации в model и обработку информации в model от отрисовки View.

PS А ещё лучше дайте код и я посмотрю(да и другие) как можно его подкорректировать в соответствии с вашими желаниями.

PPS вы ломаете систему, написанную под model-view. Как вы думаете, могут сотни программистов ошибаться и данная система не является model-view? Веселый
« Последнее редактирование: Июль 08, 2015, 15:26 от Bepec » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #5 : Июль 08, 2015, 15:44 »

...который бы при вызове repaint() перерисовывал только одного себя любимого,
см WA_OpaquePaintEvent. Без этого флага да, будут сначала рисоваться все виджеты что под данным - но это обычно неопасно, до перерисовки таблицы дело дойти не должно, ведь она текущую область перерисовки не перекрывает.

Представьте ситуацию. У вас имеется два виджета и в результате работы какого то алгоритма они меняют свое содержимое. Пусть для определенности это будет два лейбла с текстом. По выходу из этого алгоритма вы вызываете lable1->repaint(), надеясь на обновление внешнего вида одной метки, а вместо этого будут перерисованы обе, так как setText для label2, вызванный внутри алгоритма переводит его в состояние "грязный" и по выходу из алгоритма обе метки требуют перерисовки.
Да, это нормально, ведь обе метки были обновлены. Вообще вызов repaint - дурной тон, не на всех платформах гарантируется "немедленное" обновление.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #6 : Июль 08, 2015, 15:49 »

Так же и с model-view. View отрисовывает, основываясь на информации в модели. Но никоим образом оно не изменяет информацию в момент отрисовки.
Это, мягко говоря, спорно. В самом Qt много ф-ционала включается/активируется только в момент рисования. См напр initializeGL которое ой как много информации меняет.
Записан
ksergey85
Гость
« Ответ #7 : Июль 08, 2015, 15:55 »

Вы наверно не так меня где то поняли. Но давайте не будет привязываться к MVC. Вопрос сейчас не в нем. Задам вопрос по другому. Без MVC.
Представьте есть обработчик события нажатия на кнопку, выполняющий следующие действия.
Код:
...
label1->setText("111");
label2->setText("aaa");
label1->repaint();

do_some_work1();
label1->setText("222");
label2->setText("bbb");
label1->repaint();

do_some_work2();
label1->setText("333");
label2->setText("ccc");

...
Ожидаемый результат: пользователь после нажатия на кнопку увидит сначала как label1 поменяет текст на 111, спустя какое то время после выполнения do_some_work1() поменяет текст на 222 и спустя еще какое то время после выполнения do_some_work2() обе метки поменяют текст на 333 и ccc. Промежуточных стадий изменения текста метки2 пользователь видеть не должен, так как ее перерисовка состоится только после возвращения потока в цикл обработки событий.
В действительности же пользователь увидит все стадии изменения текста метки2 по рассказанному уже мной механизму перерисовки всех грязных областей окна. Как этого избежать?
 
Записан
ksergey85
Гость
« Ответ #8 : Июль 08, 2015, 16:01 »

...который бы при вызове repaint() перерисовывал только одного себя любимого,
см WA_OpaquePaintEvent. Без этого флага да, будут сначала рисоваться все виджеты что под данным - но это обычно неопасно, до перерисовки таблицы дело дойти не должно, ведь она текущую область перерисовки не перекрывает.
В том то и дело, что доходит. Внутри обработчика события содержимое таблицы меняется. А перерисовать ее хотелось бы только после выхода из обработчика. А вот метку надо перерисовать сейчас и еще и не один раз =)
Какие еще есть способы внутри обработчика события перерисовать какой то элемент?
« Последнее редактирование: Июль 08, 2015, 16:10 от ksergey85 » Записан
Bepec
Гость
« Ответ #9 : Июль 08, 2015, 16:13 »

Вы маетесь ерундой.
Потому что система рисования в Qt именно такая какая есть, она не требует контроля со стороны программиста и завязана на сигнал-слотах.
Чтобы сделать то, что вы хотите - вам придётся написать собственную систему.

Так что или вы пользуетесь встроенной системой рисования Qt, или же берите и пишите. Ну или ищите другой движок Улыбающийся

PS в Qt некоторые вещи делаются не так, как у вы привыкли. И тут либо учиться новому, либо отказываться от Qt.

PPS в вашем примере с лейбелами ситуация обсурдна. Она вводит пользователя в заблуждение, ибо в label2 значение будет "ccc", а отображаться будет "bbb". Тем самым вы вносите путаницу и не синхронизируете данные и отображение.

В принципе вы можете сделать такое поведение в своих виджетах, C++ это позволяет. Но такой виджет будет потенциальной бомбой Веселый

PPPS интерес - сможете привести ситуацию (не абсурдную), когда отображение и хранимое значение должно быть рассинхронизовано?
Записан
ksergey85
Гость
« Ответ #10 : Июль 08, 2015, 16:20 »

После выхода из обработчика будет значение "ccc" так как поставленное в очередь событие на перерисовку наконец то будет обработано.
Понимаю, что ситуация абсурдна и "маюсь ерундой", но не могу найти способа обновления маленькой метки, отображающей статус внутри обработчика события, активно работающего с данными таблицы. Любая попытка перерисовать эту метку (а метку надо перерисовывать по стадиям внутри обработчика чтобы пользователь видел стадии) ведет к перерисовке и таблицы с данными, которую хотелось бы перерисовать один раз по выходу из обработчика.
Записан
ksergey85
Гость
« Ответ #11 : Июль 08, 2015, 16:24 »

PPPS интерес - сможете привести ситуацию (не абсурдную), когда отображение и хранимое значение должно быть рассинхронизовано?

Уже привел. Внутри обработчика события работаете с данными модели, модель уведомляет вью о том что ему пора кое что перерисовать. Работа идет поэтапно. Эти этапы отображаются с помощью метки. Ее содержимое надо перерисовывать по ходу работы обработчика, иначе пользователь увидит только последнюю надпись. А вот перерисовывать таблицу каждый раз при перерисовке лейбла мне не надо. Важен только конечный результат, который пользователь увидит при выходе из обработчика. Чем не пример?
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #12 : Июль 08, 2015, 16:26 »

В том то и дело, что доходит. Внутри обработчика события содержимое таблицы меняется. А перерисовать ее хотелось бы только после выхода из обработчика.
Тогда пользуйтесь штатным QWidget::setUpdatesEnabled (для таблицы).

А вот метку надо перерисовать сейчас и еще и не один раз =)
О том что repaint в общем случае не гарантирует немедленной перерисовки уже говорили. Добавлю еще что беда может прийти и с др стороны - слишком частое обновление (эффект мигания). Пользуйтесь processEvents вместо repaint
Записан
ksergey85
Гость
« Ответ #13 : Июль 08, 2015, 16:31 »

О том что repaint в общем случае не гарантирует немедленной перерисовки уже говорили. Добавлю еще что беда может прийти и с др стороны - слишком частое обновление (эффект мигания). Пользуйтесь processEvents вместо repaint

ProcessEvents() перерисует и таблицу. А мне этого нельзя допустить. В принципе можно воспользоваться sendPostetEvents для событий лейбла, но вопроса о нежелательной перерисовке таблицы вместе с лейблом это не снимает.
Записан
Bepec
Гость
« Ответ #14 : Июль 08, 2015, 16:46 »

Ну в общем фигней страдаете. Ну боритесь с системой. Правда она обычно потуг не замечает Веселый
Записан
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


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