psilogic: (Default)
psilogic ([personal profile] psilogic) wrote2009-03-14 05:58 pm

WM_SETREDRAW - оптимизация мелькания окон

(пост для программистов)

Наткнулся здесь на одну неочевидную фишку - может, "виндузятникам" пригодится. Сам по себе прием достаточно известный, но вот подводные камни, которые там обнаруживаются...

Бывает так, что необходимо сделать много изменений в каком-нибудь контроле. Например, "напихать" в List Box 9000 элементов. Тогда окно "лочится", делаются изменения, а потом оно "разлочивается" и перерисовывается один раз. Обычно рекомендуется так:

SendMessage(hwnd, WM_SETREDRAW, FALSE, 0);
-- здесь вносятся изменения --
SendMessage(hwnd, WM_SETREDRAW, TRUE, 0);
InvalidateRect(hwnd, NULL, TRUE);

Сейчас неважно, как там все устроено внутри системы, но эксперимент показывает, что при больших объемах изменений, ускорение получается значительное. Ту же технику можно использовать и для окон собственного изготовления: в "залоченном" состоянии не пытаться делать лишних телодвижений, например не пересчитывать строку максимальной ширины.

А теперь о подводных камнях.

Я здесь пишу на Platform SDK, но во многих библиотеках-"обертках" принцип схожий и могут возникать аналогичные глюки.

Первый камень - иногда изменения затрагивают frame, а InvalidateRect требует перерисовки только клиентской области. Так что рамку и скроллеры могут оказаться подзагаженными. Поэтому, если ваше окошко имеет рамку, лучше добавить еще:
RedrawWindow(handle, NULL, NULL, RDW_FRAME|RDW_INVALIDATE);

Вторая подлянка связана с оптимизацией. Можно вызывать перерисовку, только если знаете, что реально что-то поменяли (чтобы меньше мелькало). Любое изменение взводит флаг, а в конце вызывается перерисовка, только если изменения были.

Подлянка возникает из-за того, что WM_SETREDRAW убивает предыдущие invalidate-ы. И может возникнуть такая ситауция:

lock
- что-то изменили, установили флаг = true
unlock
invalidate
флаг = false

lock
- ничего не меняли, флаг = false
unlock
- ничего не вызываем, так как флаг = false

Второй lock отменяет invalidate, так что перерисовки не будет. А должна быть. Вот как это решается. Перед lock надо проверить, не установлено ли уже требование перерисовки. Если установлено, то поднять флаг:

if (GetUpdateRect(handle, NULL, FALSE))
флаг = true

И третья подлянка. Эта техника несовместима с видимостью окна. Если окно невидимое, технику применять нельзя, так как второй WM_SETREDRAW сделает окно видимым. Invalidate проблем не создает.

[identity profile] odysseos.livejournal.com 2009-03-14 08:16 pm (UTC)(link)
Ох же ж, блин... Несмотря на то, что я обычно пользую Borland VCL, тем не менее- регулярно приходится "спускаться" до WinAPI. Так что - спасибо :)

[identity profile] kakadoo.livejournal.com 2009-03-16 12:31 pm (UTC)(link)
Спасибо. Как-то всё время было достаточно наплевать на мелькание отрисовки в TreeView, после этого поста - добавлю сию фичу.

(Anonymous) 2009-11-12 06:29 am (UTC)(link)
Да, тоже раздражал TreeView в купе с Visual Styles