Russian Qt Forum

Qt => Пользовательский интерфейс (GUI) => Тема начата: ksergey85 от Июль 08, 2015, 13:57



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


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: Bepec от Июль 08, 2015, 14:43
По моему мнению вы заблуждаетесь.
Главное заблуждение тут в том, что у вас перерисовка зависит от чтения данных.
Разделяйте GUI и модель.

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

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

И таки да, зависимость перерисовки от чтения - это бред :D Свернули-развернули окно, опять будете читать? :D


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: ksergey85 от Июль 08, 2015, 14:53
По моему мнению вы заблуждаетесь.
Главное заблуждение тут в том, что у вас перерисовка зависит от чтения данных.
Разделяйте GUI и модель.

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

И таки да, зависимость перерисовки от чтения - это бред Веселый Свернули-развернули окно, опять будете читать?
Конечно, а как вы думаете? Думаете он один раз срендерил и дальше вам из буфера бахает? =)


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


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: Bepec от Июль 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? :D


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: Igors от Июль 08, 2015, 15:44
...который бы при вызове repaint() перерисовывал только одного себя любимого,
см WA_OpaquePaintEvent. Без этого флага да, будут сначала рисоваться все виджеты что под данным - но это обычно неопасно, до перерисовки таблицы дело дойти не должно, ведь она текущую область перерисовки не перекрывает.

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


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: Igors от Июль 08, 2015, 15:49
Так же и с model-view. View отрисовывает, основываясь на информации в модели. Но никоим образом оно не изменяет информацию в момент отрисовки.
Это, мягко говоря, спорно. В самом Qt много ф-ционала включается/активируется только в момент рисования. См напр initializeGL которое ой как много информации меняет.


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: ksergey85 от Июль 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 по рассказанному уже мной механизму перерисовки всех грязных областей окна. Как этого избежать?
 


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: ksergey85 от Июль 08, 2015, 16:01
...который бы при вызове repaint() перерисовывал только одного себя любимого,
см WA_OpaquePaintEvent. Без этого флага да, будут сначала рисоваться все виджеты что под данным - но это обычно неопасно, до перерисовки таблицы дело дойти не должно, ведь она текущую область перерисовки не перекрывает.
В том то и дело, что доходит. Внутри обработчика события содержимое таблицы меняется. А перерисовать ее хотелось бы только после выхода из обработчика. А вот метку надо перерисовать сейчас и еще и не один раз =)
Какие еще есть способы внутри обработчика события перерисовать какой то элемент?


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: Bepec от Июль 08, 2015, 16:13
Вы маетесь ерундой.
Потому что система рисования в Qt именно такая какая есть, она не требует контроля со стороны программиста и завязана на сигнал-слотах.
Чтобы сделать то, что вы хотите - вам придётся написать собственную систему.

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

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

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

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

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


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: ksergey85 от Июль 08, 2015, 16:20
После выхода из обработчика будет значение "ccc" так как поставленное в очередь событие на перерисовку наконец то будет обработано.
Понимаю, что ситуация абсурдна и "маюсь ерундой", но не могу найти способа обновления маленькой метки, отображающей статус внутри обработчика события, активно работающего с данными таблицы. Любая попытка перерисовать эту метку (а метку надо перерисовывать по стадиям внутри обработчика чтобы пользователь видел стадии) ведет к перерисовке и таблицы с данными, которую хотелось бы перерисовать один раз по выходу из обработчика.


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: ksergey85 от Июль 08, 2015, 16:24
PPPS интерес - сможете привести ситуацию (не абсурдную), когда отображение и хранимое значение должно быть рассинхронизовано?

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


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: Igors от Июль 08, 2015, 16:26
В том то и дело, что доходит. Внутри обработчика события содержимое таблицы меняется. А перерисовать ее хотелось бы только после выхода из обработчика.
Тогда пользуйтесь штатным QWidget::setUpdatesEnabled (для таблицы).

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


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: ksergey85 от Июль 08, 2015, 16:31
О том что repaint в общем случае не гарантирует немедленной перерисовки уже говорили. Добавлю еще что беда может прийти и с др стороны - слишком частое обновление (эффект мигания). Пользуйтесь processEvents вместо repaint

ProcessEvents() перерисует и таблицу. А мне этого нельзя допустить. В принципе можно воспользоваться sendPostetEvents для событий лейбла, но вопроса о нежелательной перерисовке таблицы вместе с лейблом это не снимает.


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: Bepec от Июль 08, 2015, 16:46
Ну в общем фигней страдаете. Ну боритесь с системой. Правда она обычно потуг не замечает :D


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: Индус от Июль 08, 2015, 17:08
Кароче зачем устраивать холивар? Если перед человеком стоит нестандартная задача, то дадим не стандартное решение:
1. создаем два лейбла
2. один лейбл суем в статус бар
3. второй лейбл делаем фреймлесс, и реализуем на нем не стандартную задачу с repaint-ом
4. за одно обвешиваем его костылями чтоб следил за поведением лейбла в статус баре, чтоб всегда рисовался поверх него по его координатам
5. PROFIT!!!  :D

P.S. да я мастер костылинга  ;D


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: Igors от Июль 08, 2015, 17:34
ProcessEvents() перерисует и таблицу. А мне этого нельзя допустить.
Пример
Код
C++ (Qt)
#include <QtWidgets>
 
class MyWin : public QWidget {
public:
MyWin( void ) : testNo(0)
{
resize(320, 240);
QLayout * layout = new QVBoxLayout(this);
setLayout(layout);
lab1 = new QLabel("Lab 1", this);
lab2 = new QLabel("Lab 2", this);
QPushButton * btn = new QPushButton("Test", this);
QObject::connect(btn, &QPushButton::pressed, this, &MyWin::Test);
 
layout->addWidget(lab1);
layout->addWidget(lab2);
layout->addWidget(btn);
}
 
void Test( void )
{
lab2->setUpdatesEnabled(false);
for (int i = 0; i < 5; ++i) {
++testNo;
lab1->setText("Lab1 Test " + QString::number(testNo));
lab2->setText("Lab2 Test " + QString::number(testNo));
qApp->processEvents();
QThread::sleep(1);
}
lab2->setUpdatesEnabled(true);
}
 
// data
int testNo;
QLabel * lab1, * lab2;
};
 
int main( int argc, char **argv )
{
QApplication app(argc, argv);
MyWin * win = new MyWin;
win->show();
app.exec();
}


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: ksergey85 от Июль 08, 2015, 18:35
Надо понимать, что приведенные мной здесь задачи являются абстрактными, реальный проект гораздо сложнее и отвечающий за обработку событий код раскидан по многим модулям. Обновление метки выполняется внутри слота, который вызывается при срабатывании сигнала изменения состояния одного из классов. В момент работы слота мы не знаем всех таблиц, данные которых были уже обновлены внутри обработчика события. Выявить все виджеты, которым надо проставить setUpdatesEnabled() для меня не представляется возможным к сожалению. Важным моментом является то, каким образом внутри одного обработчика события (то есть не возвращаясь в цикл обработки событий) перерисовать ОДИН КОНКРЕТНЫЙ элемент (виджет) главного окна, так чтобы другие элементы главного окна не были задеты.
Всем кто пытался помочь спасибо. Буду искать обходное решение.


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: Bepec от Июль 08, 2015, 19:11
И скорее всего это всё богатство на си? Нужно переписывать и это печальный факт. Всё же разные времена и инструменты требуют разных решений. Кардинально разных.


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: GreatSnake от Июль 08, 2015, 19:26
Имхо, все проблемы из-за неправильного дизайна.
Все данные на момент отрисовки уже должны быть доступны.
Ведь получается, что при банальном интерактивном изменении размера главного окна модель опять полезет за данными куда-то очень далеко. Это разве нормально?
Коли не менять дизайн, то придётся ваять костыли. QWidget::repaint() изначально является его частью.

Но коли очень хочется слепить костыль, то нужно заходить со стороны backing store.
Т.е. заливать содержимое QLabel непосредственно в него через QLabel::render().
Детали зависят от версии Qt.


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: Igors от Июль 09, 2015, 11:16
Выявить все виджеты, которым надо проставить setUpdatesEnabled() для меня не представляется возможным к сожалению.
"Элементарно, Ватсон" - получить список всех виджетов и задизаблить все кроме "нужного" и всех его парентов.

Важным моментом является то, каким образом внутри одного обработчика события (то есть не возвращаясь в цикл обработки событий) перерисовать ОДИН КОНКРЕТНЫЙ элемент (виджет) главного окна, так чтобы другие элементы главного окна не были задеты.
Это легко достижимо как сказано выше, но вряд ли Вы обойдетесь без событийного цикла. Др словами Вы упорно хотите получить "замороженное UI" даже без кнопки Cancel

Имхо, все проблемы из-за неправильного дизайна.
Все данные на момент отрисовки уже должны быть доступны.
Ведь получается, что при банальном интерактивном изменении размера главного окна модель опять полезет за данными куда-то очень далеко. Это разве нормально?
Ну вообще-то да, нормально. Напр (hex) viewer так и делает, загружает только то что видимо в данный момент. И блокировка промежуточных перерисовок - нормальная практика, еще в старых OC можно было убрать "dirty region". И в Qt это есть - но человек не может смириться с мыслью что просто-напросто "недочитал букварь" и не увидел простую вещь. Отсюда и "надо понимать", "пытался помочь"  :)


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: GreatSnake от Июль 09, 2015, 11:40
Это легко достижимо как сказано выше, но вряд ли Вы обойдетесь без событийного цикла.
Как я выше сказал, вполне себе можно, если напрямую играться с double-buffer.


Название: Электромонтажные работы в Москве и ближнем Подмосковье.
Отправлено: TimothyFado от Июль 12, 2015, 02:57
<a href=http://allelektrika.pro/service/teplypol/>стоимость подключенич и укладки тёплого пола</a>
 
Электрические приборы присутствуют в любом доме и занимают неотъемлемую часть человеческой жизни, Но и с этим не всё легко. Для работоспособности привычных устройств и оборудования требуется провести колоссальную электромонтажную работу, доступную лишь специалистам.
На помощь вам придет команда профессиональных специалистов, работающих в Москве и Подмосковье. Специалисты способны сделать практически всё: от замены розетки и подключения бытовой техники до монтажа электропроводки и электрических полов, а на официальном сайте компании можно приобрести нужные детали для электромонтажа.
 
<a href=http://allelektrika.pro/service/elektroprovodka/>инструмент для монтажа электропроводки</a>
 
Полный список предоставляемых компанией услуг:
Установка светильников;
Установка электроточек;
Монтаж электропроводки;
Установка, подключение и настройка бытовой техники;
 
<a href=http://allelektrika.pro/service/shtroblenie/>штробление стен под проводку цены</a>
 
Комплексные электромонтажные работы.
А также остальные услуги:
Установка теплых полов;
Установка молниезащиты;
Установка заземления;
Установка видеонаблюдения;
Установка слаботочных систем;
Установка противообледенительных систем.
 
<a href=http://allelektrika.pro/service/teplypol/>стоимость подключенич и укладки тёплого пола</a>
 
Специалисты компании – настоящие профессионалы, которым вы можете доверить работы по электромонтажу в квартире. Компания проводит регулярный контроль качества услуг, поэтому вы можете быть полностью спокойны за результат. После нашей установки все приборы будут работать максимально эффективно и долго. Стиральная машина не затопит соседей, посудомоечная машина будет работать бесшумно и мягко.


Название: Re: QWidget::repaint() и "грязные" области окна.
Отправлено: Racheengel от Август 22, 2015, 02:28
Есть view, он отображает то, что лежит в модели. И есть контроллер, который каким-то образом заполняет модель. Это операции друг с другом не связанные. В принципе контроллер может триггеровать отрисовку view, но view не должен менять содержимое модели иначе, как после редактирования пользователем. Т.е вкратце, отрисовка view должна отображать то, что уже занесено в модель. Понимайте модель в этом случае как нечто константное.