...или почему простая замена
LockWindowUpdate
на WM_SETREDRAW
не так прямолинейна.Как вы знаете, когда вам нужно избежать мерцания или множественных перерисовок формы во время набора операций обновления, даже хотя это очень заманчиво, но вы не должны использовать
LockWindowUpdate
. Сегодня документация Windows уже обновлена, в отличие от ранних дней, когда она просто манила людей использовать эту функцию для ошибочной цели - как в деталях пояснил Реймонд Чен.Но простая замена
LockWindowUpdate(MyForm.Handle)
на SendMessage(MyForm.Handle, WM_SETREDRAW, 0, 0)
для всей формы также не является решением. Даже хотя это иногда рекомендуют.Не, это отлично заблокирует перерисовку формы - не вопрос. А если это используется только на короткий промежуток времени, то вы можете и не заметить проблему.
Симуляция длительных вычислений с изменением интерфейса
Создайте новое VCL приложение и поместите на его главную форму две кнопки. На обработчик первой кнопки назначьте такой код:procedure TForm1.Button1Click(Sender: TObject); begin Button1.Width := Button1.Width * 2; Repaint; sleep(2000); Button1.Width := Button1.Width div 2; Repaint; end;Если вы нажмёте на кнопку, её размер изменится, затем приложение замрёт на 2 секунды (вроде как аналог длительной работы), а потом размер вернётся к начальному. Если вы щёлкните на форме, пока она "заморожена", то ничего не произойдёт, если только у вас не назначен обработчик её OnClick - в этом случае событие сработает после "отмерзания" формы.
Если вам нужно заблокировать изменения интерфейса
Вы можете обернуть весь код вWM_SETREDRAW
(на манер того, как вы сделали бы это с LockWindoUpdate
):
procedure TForm1.Button1Click(Sender: TObject); begin SendMessage(Handle, WM_SETREDRAW, Ord(False), 0); try Button1.Width := Button1.Width * 2; Repaint; sleep(2000); Button1.Width := Button1.Width div 2; Repaint; finally SendMessage(Handle, WM_SETREDRAW, Ord(True), 0); end; end;Запустите этот код теперь. Вроде всё нормально: размеры кнопок визуально не меняются. Но постойте!
Попробуйте щёлкнуть где угодно на форме, пока она занята.
Оопс! Вы щёлкнули сквозь неё, как если бы она не существовала!
Это даже более очевидно, когда форма расположена поверх редактора кода Delphi: как только вы щёлкните на кнопку формы, курсор изменит форму с обычной стрелки Arrow на текстовый IBeam.
Попробуйте с Блокнотом
Это на случай, если вы думаете, что это какой-то хитрый глюк в VCL - вы можете вставить код ниже на вторую кнопку. Он откроет Блокнот с новым документом и заблокирует его на 3 секунды.Примечание: вам нужно адаптировать заголовок окна в параметрах
FindWindow
.
procedure TForm1.Button2Click(Sender: TObject); var h: THandle; begin ShellExecute(Handle, nil, 'Notepad.exe', '','', SW_SHOWNORMAL); sleep(100); // Вам может понадобится изменить эти параметры h := FindWindow('Notepad', 'Untitled - Notepad'); if IsWindow(h) then begin SendMessage(h, WM_SETREDRAW, Ord(False), 0); try sleep(3000); finally SendMessage(h, WM_SETREDRAW, Ord(True), 0); end; end; end;Попробуйте щёлкнуть где угодно в окне Блокнота, пока оно заморожено - вы "прощёлкнете" сквозь него!
Что если вам нужно запрещать перерисовку формы во время какой-то работы?
Поскольку очень редко бывает ситуация, когда элемент управления, который нужно заблокировать, помещается напрямую на форму, то простейшее решение заключается в блокировке верхнего родителя - часто панели илиTab
/PageControl
(ClientWindow
не сработает).А если у вас такого нет, то вы всегда можете вставить промежуточную
Panel
с alClient
прямо на форму - как главный контейнер, прослойку между формой и компонентами на ней.Полезный совет: в отличие от
DisableControls
/EnableControls
у DataSet
, WM_SETREDRAW
не учитывает вложенные вызовы - не важно сколько раз вы вызовите WM_SETREDRAW
с False
(хотя лишний раз их не слать - вполне желательно). Вам просто нужно убедиться, что вы вызовите WM_SETREDRAW
с True
в конце всего один раз.Почему есть такое странное поведение?
Поведение по умолчанию дляWM_SETREDRAW
- скрытие окна, но без обновления экрана. Остаётся окно-призрак.Это немного было объяснено Реймондом Ченом в последнем посте: Существует реализация WM_SETREDRAW по умолчанию, но вы можете сделать и лучше.
Вполне нормальное поведение: поощряет правильный стиль программирования. А правильный стиль - это: отключать контролы на время работы. Enabled им в False - и готово.
ОтветитьУдалитьПредсталяю, как будет мерцать ваша форма, если при её растягивании/сжатии вы будете постоянно делать Enable/Disable для всех контролов на форме.
УдалитьОбнаружил, что компонент TToolBar неадекватно реагирует на WM_SETREDRAW: после восстановления меняются позиции TToolButton'ов, также появляются артефакты. Капризный компонент! Проблема решилась следующими магическими действиями:
ОтветитьУдалить1) Поместил TToolBar на TPanel, у которой установил FullRepaint := False;
2) После восстанавливающего WM_SETREDRAW потребовалось ещё вызвать TToolBar.Invalidate;
3) Но и это ещё не всё. Некоторые компоненты (например, TComboBox), размещённые не TToolbar'е, требуют для себя персонального Invalidate.
После этого волшебным образом заработало, как надо.