Чтение онлайн

на главную - закладки

Жанры

О чём не пишут в книгах по Delphi

Григорьев Антон Борисович

Шрифт:

Затем начинает обрабатываться событие

WM_NOTIFY
, которое уведомляет программу о том, что пользователь нажал на кнопку компонента
TUpDown
. Именно при обработке этого сообщения VCL вызывает событие
TUpDown.OnClick
, в котором открывается модальное окно. Всё это происходит очень быстро, поэтому кнопку мыши пользователь отпускает тогда, когда модальное окно уже оказалось на экране. В результате сообщение
WM_LBUTTONUP
либо попадает в очередь открывшегося диалогового окна, если мышь находилась над ним, либо вообще никуда не попадает, если мышь была вне модального окна. На время существования модального окна система "забывает" о том, что мышь захвачена для монопольного использования, но "вспоминает"
об этом, как только модальное окно закрывается. Монопольное использование мыши компонентом
TUpDown
должно отменяться при обработке сообщения
WM_LBUTTONUP
, но оно, как было сказано ранее, в очередь не попадает, поэтому после закрытия окна мышь остается захваченной данным компонентом. Поэтому любое нажатие кнопки мыши воспринимается системой как относящееся к
UpDown1
, и снова приводит к помещению в очередь сообщений
WM_LBUTTONDOWN
и
WM_NOTIFY
, которые обрабатываются описанным образом. Так получается порочный круг, из которого при нормальной работе программы нет выхода. Этот круг может быть разорван, например, отладчиком, который отменяет монопольное использование мыши компонентами программы, чтобы иметь возможность работать.

В этой проблеме виновата VCL, которая зачем-то назначает компоненту

TUpDown
стиль
csCaptureMouse
. Данный компонент реализуется не средствами VCL, — это стандартное окно системного класса
UPDOWN_CLASS
, а компонент
TUpDown
— это только оболочка для него. Поэтому все необходимые перехваты мыши выполняются самой системой. VCL нет нужды в это вмешиваться. Чтобы избавиться от проблемы, нужно убрать
csCaptureMouse
из списка стилей компонента. Делается это так:

UpDown1.ControlStyle := UpDown1.ControlStyle - [csCaptureMouse];

Этот код достаточно выполнить один раз (например, в обработчике события

OnCreate
формы), и проблемы с зацикливанием исчезнут (в примере UpDownDlg эта строка закомментирована).

Отметим, что в Windows предусмотрено специальное сообщение —

WM_CANCELMODE
, — посылаемое при открытии диалогового окна тому окну, которое захватило мышь, чтобы оно ее освободило. Один из способов решения проблемы — добавление в
UpDown1
обработчика этого сообщения (для этого можно написать наследника
TUpDown
или же воспользоваться свойством
WindowProc
см. разд. 1.1.8), который отменит захват мыши. Отсутствие этого обработчика — тоже явная ошибка VCL.

3.4.3. Access violation при закрытии формы с перекрытым методом WndProc

Чтобы увидеть этот "подводный камень", создадим проект, содержащий две формы: главную

Form1
и вспомогательную
Form2
. В
Form1
добавим код, который по нажатию кнопки открывает
Form2
.

Во второй форме напишем обработчик события

OnClose
таким образом, чтобы он устанавливал по закрытию действие
caFree
. Добавим поле строкового типа, перекроем конструктор и метод
WndProc
так, чтобы окончательный код выглядел следующим образом (листинг 3.52, пример CloseAV на компакт- диске).

Листинг 3.52. Код класса
TForm2

type

 TForm2 = class(TForm)

procedure FormClose(Sender: TObject; var Action: TCloseAction);

 private

S: string;

 protected

procedure WndProc(var Message: TMessage); override;

 public

constructor Create(AOwner: TComponent); override;

 end;

.... 

constructor TForm2.Create(AOwner: TComponent);

begin

 S := 'abc';

 inherited;

end;

procedure TForm2.WndProc(var Message: TMessage);

begin

 inherited;

 S[2] := 'x'; { * }

end;

procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);

begin

 Action := caFree;

end;

Обратите

внимание, что в конструкторе сначала присваивается значение полю
S
, и лишь потом вызывается унаследованный конструктор. Это сделано потому, что по умолчанию
S
содержит пустую строку, т.е.
nil
, а уже при вызове унаследованного конструктора окно получит сообщения, для обработки которых будет вызван метод
WndProc
. Если в этот момент
S
будет по-прежнему
nil
, попытка обратиться ко второму символу строки вызовет Access violation. Поэтому еще до начала работы унаследованного конструктора поле
S
должно получить подходящее значение.

Запустим программу и попытаемся закрыть второе окно. Возникнет исключение Access Violation: Write of address 00000001. Проблема будет в строке, отмеченной

{*}
. При этом любые другие манипуляции с окном никаких исключений вызывать не будут.

При

Action = caFree
после завершения работы метода FormClose VCL вызывает метод
TCustomForm.Release
. Проблема именно в нем: если попытаться закрыть
Form2
с помощью
Release
, возникнет то же самое исключение. В справке
Release
позиционируется как безопасный способ удаления формы из ее собственного метода. К сожалению, в действительности это не так: реализация этого удаления оставляет желать лучшего и может приводить к попыткам работать с объектом тогда, когда его уже не существует.

При вызове

Release
в очередь помещается сообщение
CM_RELEASE
, адресатом которого является сама удаляемая форма. В очередном цикле петли сообщений
CM_RELEASE
извлекается из очереди и передается на обработку. Так как сообщение адресовано форме, она же его и обрабатывает. Рассмотрим более подробно, как это происходит. (Детально механизм обработки сообщений в VCL описан в разд. 1.1.8; мы здесь рассмотрим только ту часть, которая относится к обработке
CM_RELEASE
.)

Система передает управление оконной процедуре. Для каждого экземпляра визуального компонента VCL создает свою оконную процедуру с помощью

MakeObjectInstance
. Эта процедура вызывает метод объекта
MainWndProc
, передающий управление тому методу, на который указывает свойство
WindowProc
. По умолчанию это
WndProc
.
WndProc
не обрабатывает
CM_RELEASE
самостоятельно, а передает его методу
Dispatch
.
Dispatch
пытается найти для этого сообщения специальный обработчик (метод с директивой
message
) и, т.к. в
TCustomForm
такой обработчик описан (он называется
CMRelease
), передаёт управление ему.

Поделиться:
Популярные книги

Ретроградный меркурий

Рам Янка
4. Серьёзные мальчики в форме
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Ретроградный меркурий

Я еще не барон

Дрейк Сириус
1. Дорогой барон!
Фантастика:
боевая фантастика
попаданцы
аниме
5.00
рейтинг книги
Я еще не барон

Полковник Империи

Ланцов Михаил Алексеевич
3. Безумный Макс
Фантастика:
альтернативная история
6.58
рейтинг книги
Полковник Империи

Бремя империи

Афанасьев Александр
Бремя империи - 1.
Фантастика:
альтернативная история
9.34
рейтинг книги
Бремя империи

Инферно

Кретов Владимир Владимирович
2. Легенда
Фантастика:
фэнтези
8.57
рейтинг книги
Инферно

Адмирал южных морей

Каменистый Артем
4. Девятый
Фантастика:
фэнтези
8.96
рейтинг книги
Адмирал южных морей

Защитник

Астахов Евгений Евгеньевич
7. Сопряжение
Фантастика:
боевая фантастика
постапокалипсис
рпг
5.00
рейтинг книги
Защитник

Я – Орк. Том 3

Лисицин Евгений
3. Я — Орк
Фантастика:
юмористическое фэнтези
попаданцы
5.00
рейтинг книги
Я – Орк. Том 3

Мир-о-творец

Ланцов Михаил Алексеевич
8. Помещик
Фантастика:
альтернативная история
5.00
рейтинг книги
Мир-о-творец

Кодекс Крови. Книга II

Борзых М.
2. РОС: Кодекс Крови
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Кодекс Крови. Книга II

Я – Орк. Том 4

Лисицин Евгений
4. Я — Орк
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Я – Орк. Том 4

Хозяйка лавандовой долины

Скор Элен
2. Хозяйка своей судьбы
Любовные романы:
любовно-фантастические романы
6.25
рейтинг книги
Хозяйка лавандовой долины

Разбуди меня

Рам Янка
7. Серьёзные мальчики в форме
Любовные романы:
современные любовные романы
остросюжетные любовные романы
5.00
рейтинг книги
Разбуди меня

Курсант: назад в СССР 9

Дамиров Рафаэль
9. Курсант
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Курсант: назад в СССР 9