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

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

Жанры

О чём не пишут в книгах по 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
), передаёт управление ему.

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

Моя на одну ночь

Тоцка Тала
Любовные романы:
современные любовные романы
короткие любовные романы
5.50
рейтинг книги
Моя на одну ночь

Черный Маг Императора 8

Герда Александр
8. Черный маг императора
Фантастика:
юмористическое фэнтези
попаданцы
аниме
5.00
рейтинг книги
Черный Маг Императора 8

Измена. Отбор для предателя

Лаврова Алиса
1. Отбор для предателя
Фантастика:
фэнтези
5.00
рейтинг книги
Измена. Отбор для предателя

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

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

Шаг в бездну

Муравьёв Константин Николаевич
3. Перешагнуть пропасть
Фантастика:
фэнтези
космическая фантастика
7.89
рейтинг книги
Шаг в бездну

Часовая битва

Щерба Наталья Васильевна
6. Часодеи
Детские:
детская фантастика
9.38
рейтинг книги
Часовая битва

Вечная Война. Книга II

Винокуров Юрий
2. Вечная война.
Фантастика:
юмористическая фантастика
космическая фантастика
8.37
рейтинг книги
Вечная Война. Книга II

Хроники странного королевства. Вторжение. (Дилогия)

Панкеева Оксана Петровна
110. В одном томе
Фантастика:
фэнтези
9.38
рейтинг книги
Хроники странного королевства. Вторжение. (Дилогия)

Часовой ключ

Щерба Наталья Васильевна
1. Часодеи
Фантастика:
фэнтези
9.36
рейтинг книги
Часовой ключ

Инвестиго, из медика в маги

Рэд Илья
1. Инвестиго
Фантастика:
фэнтези
городское фэнтези
попаданцы
5.00
рейтинг книги
Инвестиго, из медика в маги

Кротовский, может, хватит?

Парсиев Дмитрий
3. РОС: Изнанка Империи
Фантастика:
попаданцы
альтернативная история
аниме
7.50
рейтинг книги
Кротовский, может, хватит?

Драконий подарок

Суббота Светлана
1. Королевская академия Драко
Любовные романы:
любовно-фантастические романы
7.30
рейтинг книги
Драконий подарок

Очешуеть! Я - жена дракона?!

Амеличева Елена
Фантастика:
юмористическая фантастика
5.43
рейтинг книги
Очешуеть! Я - жена дракона?!

Идеальный мир для Лекаря 9

Сапфир Олег
9. Лекарь
Фантастика:
боевая фантастика
юмористическое фэнтези
6.00
рейтинг книги
Идеальный мир для Лекаря 9