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

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

Жанры

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

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

Шрифт:

var

 I: integer;

 FinalDestruction: Boolean;

 P: TControl;

begin

 FinalDestruction := False;

 P := Self;

 while Assigned(P) do

 begin

if csDestroying in F.ComponentState then

begin

FinalDestruction := True;

Break;

end;

P := P.Parent;

 end;

 if FinalDestruction then

for I := 0 to Items.Count - 1 do

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

 inherited;

end;

Такой

компонент корректно освобождает память при его удалении, но не освобождает ее тогда, когда окно создается заново.

Примечание

Есть еще одна очень распространенная причина получения ошибки "Control has no parent window" при разработке собственных компонентов — попытка обращения к свойствам, требующим наличия окна, до назначения свойства

Parent
. Например, такая ошибка появилась бы, если бы мы в наших наследниках
TComboBox
попытались при создании добавить элементы, вызвав в конструкторе метод
AddItem
. Свойство
Items.Objects
в случае
TComboBox
работает через оконные сообщения
CB_GETITEMDATA
и
CB_SETITEMDATA
, при попытке отправить которые будет использовано свойство
Handle
. Это также приведет к попытке создания окна, которая завершится исключением из-за отсутствия родителя. Другими словами, ошибку мы получим не при удалении компонента, а при его создании. Бороться с этой проблемой можно, выполняя начальную инициализацию тогда, когда родитель уже назначен, например, в перекрытом методе
SetParent
после того, как отработает унаследованный
SetParent
. Необходимо только помнить, что
SetParent
может быть вызван не только при создании компонента и при необходимости позаботиться о том, чтобы инициализация выполнялась только при первом вызове
SetParent
с аргументом, отличным от
nil
.

Чтобы убедиться, насколько некорректно реализовано удаление компонентов в VCL, рассмотрим еще один пример (на компакт-диске он называется FrameDel). В этом примере на форму помещается фрейм с одним компонентом типа

TComboBox
. Код фрейма показан в листинге 3.66.

Листинг 3.66. Код фрейма

type

 TFrame1 = class(TFrame)

ComboBox1: TComboBox;

 private

{ Private declarations }

 public

destructor Destroy; override;

procedure AddComboItem;

 end;

destructor TFrame1.Destroy;

var

 I: Integer;

begin

 for I := 0 to ComboBox1.Items.Count - 1 do

if Assigned(ComboBox1.Items.Objects[I]) then

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

 inherited;

end;

procedure TFrame1.AddComboItem;

var

 P: PDateTime;

begin

 New(P);

 P^:= Now;

 ComboBox1.Items.AddObject('Item ' + TimeToStr(P^), TObject(P));

end;

На форму в обработчике события

OnShow
помещается такой фрейм и вызывается его метод
AddComboItem
, чтобы в компоненте
ComboBox1
появился один элемент в списке. Если закрыть такую форму, никаких исключений не возникает, все выглядит нормально. Но при трассировке можно заметить, что цикл внутри деструктора не выполняется ни разу, потому что
ComboBox1.Items.Count
возвращает 0. Это происходит потому, что на момент вызова этого деструктора и окно фрейма, и окно
ComboBox1
уже не существуют, в чем легко убедиться, проверив в деструкторе значение поля
ComboBox1.FHandle
(до обращения к свойству
ComboBox1.Items.Count
оно равно нулю). А при обращении к этому свойству происходит попытка создать окно. Так как свойство
TComboBox1.Parent 
в этот момент еще не обнулено, предпринимается попытка создать заново и фрейм тоже, и эта попытка становится успешной. К этому моменту свойство
Parent
фрейма уже обнулено, но метод
TCustomFrame.CreateParams
реализован таким образом, что родителем всех фреймов, для которых родитель не задан явно, становится невидимое окно приложения, которое на этот момент еще не разрушено. Таким образом, окно фрейма и окно компонента
TComboBox1
успешно создаются заново, и им можно посылать сообщения.

Ранее мы говорили, что код компонента

TComboBox
обеспечивает перенос элементов при удалении и последующем создании окна. Но в данном случае этот код даже не догадывается, что после удаления окно может быть создано ещё раз, и потому механизм переноса не задействуется. Вновь созданное окно компонента
ComboBox1
не получает в свой список ни одного элемента, что и приводит к тому, что свойство
Items.Count
равно нулю. Но динамическая память, выделенная в методе
AddComboItem
остается не освобождённой. В результате имеем утечку памяти вместо исключения. Кроме того, имеем утечку и других ресурсов, т.к. код, ответственный за удаление окна фрейма, на этот момент уже отработал и не будет запущен еще раз, чтобы удалить вновь созданное окно.

Решением проблемы может стать уже опробованный способ: нужно обрабатывать сообщение

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

Примечание

Если бы мы попытались использовать наследника от класса, например,

TPanel
вместо
TFrame
, ошибка при завершении работы программы возникла бы, компоненту
TPanel
не назначается родитель по умолчанию, и попытка создания его окна в деструкторе закончилась бы неудачей. Назначение родителя по умолчанию приводит еще к одному интересному эффекту: мы можем добавлять в
ComboBox1
элементы до того, как будет назначено свойство
Parent
фрейма. Ошибки не возникнет, потому что окно фрейма будет создано успешно, а при последующем назначении свойства
Parent
фрейма в компоненте
ComboBox1
сработает механизм переноса элементов при создании нового окна, и пользователь увидит добавленные элементы.

Главным выводом этого раздела должно стать то, что последовательность удаления визуальных компонентов в VCL очень плохо продумана (видимо, это одно из самых неудачных мест в VCL), поэтому нужно соблюдать особую осторожность в тех случаях, когда освобождение ресурсов удаляемого оконного компонента может быть связано с обращением к его окну. Деструктор для этих целей, как мы убедились, не подходит, удаление следует выполнять в обработчике

WM_DESTROY
, проверяя, действительно ли удаляется сам компонент, а не только его окно.

Глава 4

Разбор и вычисление выражений

Перед программистом нередко возникает задача вычисления арифметических или иных выражений, не известных на этапе компиляции программы. Готовых средств для этого в Delphi нет. В Интернете можно найти компоненты и законченные примеры вычисления выражений, но нередко требуется создать что-то свое. В этой главе мы рассмотрим способы программного разбора и вычисления арифметических выражений. Кроме самих примеров будут изложены основы теории синтаксического анализа, с помощью которой эти примеры написаны. Эти сведения не только помогут лучше понять приведенные примеры, но и позволят легко написать код для синтаксического разбора любых выражений, которые подчиняются некоторому формальному описанию синтаксиса.

Синтаксический анализатор мы будем создавать поэтапно, переходя от простых примеров к сложным. Сначала научимся распознавать вещественное число и напишем простейший калькулятор, который умеет выполнять четыре действия арифметики над числами без учета приоритета операций. Затем наша программа научится учитывать приоритет этих операций, а чуть позже — использовать скобки для изменения этого приоритета. Далее калькулятор обретет способность работать с переменными, вычислять функции и возводить в степень, т.е. станет вполне полноценным. И на последнем этапе мы добавим лексический анализатор — средство, которое формализует разбор выражений со сложной грамматикой и тем самым существенно облегчает написание синтаксических анализаторов.

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

Санек 2

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

Князь Мещерский

Дроздов Анатолий Федорович
3. Зауряд-врач
Фантастика:
альтернативная история
8.35
рейтинг книги
Князь Мещерский

Без Чести

Щукин Иван
4. Жизни Архимага
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Без Чести

Безродный

Коган Мстислав Константинович
1. Игра не для слабых
Фантастика:
боевая фантастика
альтернативная история
6.67
рейтинг книги
Безродный

Огненный князь

Машуков Тимур
1. Багряный восход
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Огненный князь

Ваше Сиятельство 4т

Моури Эрли
4. Ваше Сиятельство
Любовные романы:
эро литература
5.00
рейтинг книги
Ваше Сиятельство 4т

Вторая невеста Драконьего Лорда. Дилогия

Огненная Любовь
Вторая невеста Драконьего Лорда
Любовные романы:
любовно-фантастические романы
5.60
рейтинг книги
Вторая невеста Драконьего Лорда. Дилогия

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

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

Девятое правило дворянина

Герда Александр
9. Истинный дворянин
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Девятое правило дворянина

Цеховик. Книга 1. Отрицание

Ромов Дмитрий
1. Цеховик
Фантастика:
попаданцы
альтернативная история
5.75
рейтинг книги
Цеховик. Книга 1. Отрицание

Скрываясь в тени

Мазуров Дмитрий
2. Теневой путь
Фантастика:
боевая фантастика
7.84
рейтинг книги
Скрываясь в тени

Убивать чтобы жить 2

Бор Жорж
2. УЧЖ
Фантастика:
героическая фантастика
боевая фантастика
рпг
5.00
рейтинг книги
Убивать чтобы жить 2

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

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

Под маской, или Страшилка в академии магии

Цвик Катерина Александровна
Фантастика:
юмористическая фантастика
7.78
рейтинг книги
Под маской, или Страшилка в академии магии