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

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

Жанры

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

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

Шрифт:

if Assigned(Items.Objects[I]) then

Dispose(PDateTime(Items.Objects(I]));

 inherited;

end;

procedure TWrongCombo.AddItem(const Title: string);

var

 P: PDateTime;

begin

 New(P);

 P^ := Now;

 Items.AddObject(Title, TObject(P));

end;

Класс

TWrongCombo
с каждым элементом, добавленным с помощью метода
AddItem
, связывает значение типа
TDateTime
, хранящее время добавления элемента. В разд. 3.4.6 мы
уже познакомились с возможностью связывания данных с элементом списка с помощью свойства
Items.Objects
. Но так мы можем связать с элементом только 4-байтное значение, а тип
TDateTime
занимает 8 байтов. Поэтому значение
TDateTime
мы будем хранить в динамической памяти, а с элементом свяжем указатель на него.

Раз мы выделили динамическую память, ее нужно освободить при удалении компонента. Наиболее подходящим местом для этого кажется деструктор, и именно в нем помещен код освобождения выделенной памяти.

Теперь попробуем воспользоваться компонентом. На главной форме программы

ParentWnd
находится кнопка Wrong Combo, при нажатии на которую создается компонент типа
TWrongCombo
(листинг 3.64).

Листинг 3.64. Реакция на кнопку Wrong Combo

procedure TForm1.BtnWrongComboClick(Sender: TObject);

begin

 if FWrongCombo = nil then

 begin

FWrongCombo := TWrongCombo.Create(Self);

FWrongCombo.Left := 10;

FWrongCombo.Top := 10;

FWrongCombo.Parent := Self;

FWrongCombo.AddItem('One');

FWrongCombo.AddItem('Two');

FWrongCombo.AddItem('Three');

 end;

end;

Теперь, если нажать эту кнопку и затем попытаться закрыть форму, в деструкторе

TWrongCombo
возникнет исключение
EInvalidOperation
с сообщением "Control has no parent window". Если откомпилировать программу с включенной опцией Use Debug DCUs, видно, что исключение возникает в методе
TWinControl.CreateWnd
. Одно только это способно обескуражить — действительно, зачем метод создания окна вызывается при его удалении?

Причина заключается в том, что к моменту вызова деструктора окно компонента уже удалено, свойство

Handle
имеет нулевое значение, и свойство
Parent
тоже имеет значение
nil
. Обращение к свойству
Items.Count
приводит к отправке окну сообщения
CB_GETCOUNT
. Отправка осуществляется с помощью функции
SendMessage
, одним из параметров которой является дескриптор окна, в качестве которого, естественно, передается свойство
Handle
. А это свойство, напомним, к этому моменту равно нулю. В разд. 1.1.7 обсуждалось, что обращение к этому свойству в тот момент, когда оно равно нулю, приводит к попытке создания окна (см. листинг 1.8). Именно поэтому вызывается метод
CreateWnd
. И он возбуждает исключение, потому что окно, которое создает компонент
TWrongCombo
, имеет стиль
WS_CHILD
, т.е. не может не иметь родителя. А родитель компоненту не назначен, поэтому и возникает исключение с таким странным, на первый взгляд, сообщением.

Отсюда следует важный вывод, что никакое обращение в деструкторе компонента к тем свойствам и методам, которые требуют наличия окна, невозможно. Окно уже удалено, а попытка создания нового окна приведет к исключению. Поэтому, например, в нашем коде требуется искать другое место, чтобы корректно освободить занятую память.

Поиск этого места оказывается не такой простой задачей, как хотелось бы, потому что разработчики VCL весьма странным образом реализовали удаление дочерних оконных компонентов в деструкторе класса

TWinControl
: сначала вызывается системная функция
DestroyWindow
, которая удаляет и само окно, и, разумеется, все дочерние окна, а потом только дочерние компоненты начинают уведомляться о том, что их удаляют, т.е. к этому моменту они уже не имеют возможности как-то задействовать свои окна.
Соответственно, в нашем случае деструктор формы уничтожает окна всех дочерних компонентов до того, как будут вызваны деструкторы этих компонентов.

Положение спасает то, что об уведомлении окон заботится система Windows: всем окнам, которые удаляются в результате вызова функции

DestroyWindow
, отправляется сообщение
WM_DESTROY
, причем в момент получения этого сообщения ни окно, ни его родитель еще не уничтожены. Это позволяет компоненту как-то реагировать на свое удаление до того, как окно будет уничтожено.

Казалось бы, выход найден: нужно освобождать память в обработчике сообщения

WM_DESTROY
. Но и тут не все так просто. Дело в том, что окно может уничтожаться не только при удалении компонента, но и при изменении некоторых свойств (например,
Parent
). При этом окно удаляется, а вместо него создается новое, и при удалении старого окна компонент тоже получает сообщение
WM_DESTROY
. Что же касается компонента
TComboBox
, он обеспечивает, что при удалении и последующем создании окна все элементы, в том числе связанные с ними значения, восстанавливаются. Таким образом, если мы в наследнике
TComboBox
в обработчике сообщения
WM_DESTROY
всегда будем освобождать выделенную память, после повторного создания окна получим "битые" ссылки в свойстве
Items.Objects
, чего, естественно, хотелось бы избежать. Требуется научиться отличать полное удаление компонента от удаления окна с целью повторного создания.

Вообще говоря, механизм для этого предусмотрен в VCL — это флаг

csDestroying
в свойстве
ComponentState
. Выполнение деструктора
TWinControl.Destroy
начинается с вызова метода
Destroying
, добавляющего этот флаг во всех компонентах, которыми владеет данный компонент. Однако по наличию этого флага у компонента мы не можем в обработчике
WM_DESTROY
узнать, удаляется весь компонент, или только окно для создания заново. Рассмотрим, например, ситуацию, когда на форму во время проектирования разработчик положил панель, а на эту панель — любой оконный компонент, например, кнопку. Владельцем кнопки в этом случае все равно является форма, а панель — только родителем. Если теперь удалить панель, не удаляя форму, метод
Destroying
панели не затронет кнопку, и на момент получения кнопкой сообщения
WM_DESTROY
флаг
csDestroying
у нее еще не будет установлен, несмотря на то, что кнопка удаляется.

Тем не менее флаг

csDestroying
все же может помочь нам. Компонент удаляется в одном из трех случаев:

1. Удаляется непосредственно данный компонент.

2. Удаляется владелец компонента.

3. Удаляется родитель компонента.

В первом случае удаление начинается не с удаления окна, а с вызова деструктора компонента, и окно компонент удаляет уже сам, когда флаг csD

e
stroying установлен деструктором. Во втором случае деструктор владельца, прежде чем удалить окно, заботится о том, чтобы компонент получил флаг
csDestroying
, поэтому даже если владелец является одновременно и родителем, флаг у компонента в момент удаления окна уже будет. И, наконец, остается третья ситуация, в которой флага
csDestroying
у компонента может и не быть. Но в любом случае удаление цепочки компонентов начинается с вызова деструктора "главного" из них. По линии владельца флаг
csDestroying
передается, по линии родителя — нет, но самый верхний из цепочки родителей обязательно имеет такой флаг. Соответственно, чтобы определить, удаляется ли окно из-за уничтожения визуального компонента, нужно искать флаг
csDestroying
не только у самого компонента, но и у всей цепочки его родителей. Если флаг нигде не найден, значит, удаляется только окно, но не сам компонент.

На главном окне примера ParentWnd есть также кнопка Right Combo, которая создает на форме визуальный компонент типа

TRightCombo
. Это правильный вариант класса
TWrongCombo
, в котором деструктор не переопределяется, а обработчик сообщения
WM_DESTROY
реализован в соответствии с тем, что написано ранее (листинг 3.65).

Листинг 3.65. Обработчик сообщения
WM_DESTROY
класса
TRightCombo

procedure TRightCombo.WMDestroy(var Msg: TMessage);

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

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

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

На границе империй. Том 10. Часть 3

INDIGO
Вселенная EVE Online
Фантастика:
боевая фантастика
космическая фантастика
попаданцы
5.00
рейтинг книги
На границе империй. Том 10. Часть 3

Сердце для стража

Каменистый Артем
5. Девятый
Фантастика:
фэнтези
боевая фантастика
9.20
рейтинг книги
Сердце для стража

Жандарм 3

Семин Никита
3. Жандарм
Фантастика:
попаданцы
альтернативная история
аниме
5.00
рейтинг книги
Жандарм 3

Жена на пробу, или Хозяйка проклятого замка

Васина Илана
Фантастика:
попаданцы
фэнтези
5.00
рейтинг книги
Жена на пробу, или Хозяйка проклятого замка

Эволюционер из трущоб. Том 5

Панарин Антон
5. Эволюционер из трущоб
Фантастика:
попаданцы
аниме
фэнтези
фантастика: прочее
5.00
рейтинг книги
Эволюционер из трущоб. Том 5

Отмороженный 11.0

Гарцевич Евгений Александрович
11. Отмороженный
Фантастика:
боевая фантастика
рпг
попаданцы
фантастика: прочее
фэнтези
5.00
рейтинг книги
Отмороженный 11.0

Неучтенный. Дилогия

Муравьёв Константин Николаевич
Неучтенный
Фантастика:
боевая фантастика
попаданцы
7.98
рейтинг книги
Неучтенный. Дилогия

Бастард Императора. Том 7

Орлов Андрей Юрьевич
7. Бастард Императора
Фантастика:
городское фэнтези
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Бастард Императора. Том 7

Бастард Императора. Том 4

Орлов Андрей Юрьевич
4. Бастард Императора
Фантастика:
попаданцы
аниме
фэнтези
фантастика: прочее
5.00
рейтинг книги
Бастард Императора. Том 4

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

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

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

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

Камень. Книга 3

Минин Станислав
3. Камень
Фантастика:
фэнтези
боевая фантастика
8.58
рейтинг книги
Камень. Книга 3

Матабар

Клеванский Кирилл Сергеевич
1. Матабар
Фантастика:
фэнтези
5.00
рейтинг книги
Матабар