На некоторой неглавной форме Form2 встречается такой код:
procedure TForm2.Button1Click(Sender: TObject);
begin
Form2.Label1.Caption := 'Кнопка нажата';
end;
Всё ли правильно в этом коде (при условии что компонент Label1: TLabel действительно существует на форме)? Если нет, то что именно?
Варианты ответов:
Всё правильно
У компонента TLabel нет свойства Caption, у него аналогичное свойство называется Text
Здесь нельзя обращаться к компоненту Label1 через переменную Form2
Обработчик не учитывает значение параметра Sender
Комментарий: Ошибка здесь заключается в том, что переменная Form2, которую создаёт VCL, не должна использоваться в методах класса TForm2 для обращения к его внутренним полям. Во-первых, в некоторых случаях может оказаться, что переменная Form2 вообще не ссылается ни на одну форму. Во-вторых, если создано несколько форм класса TForm2 (такое очень характерно, например, для MDI-интерфейса), Form2 ссылается только на одну из них, и подобное обращение к полям и свойствам будет оказывать влияние совсем не на ту форму, метод которой вызван. Таким образом, автоматически создаваемые переменные следует использовать только извне, но не изнутри методов соответствующего класса, несмотря на то, что в простых случаях всё будет работать правильно.
В вопросе речь идёт о неглавной форме, но с главной всё то же самое: изнутри методов её класса не нужно использовать глобальную переменную. Просто с главной формой это гораздо реже приводит к ошибками, потому что переменная практически всегда инициализирована, а других экземпляров этого класса нет. Тем не менее, полностью исключить ошибки подобного рода может только правильное обращение к членам класса без использования глобальной переменной.
Вопрос №2
Создадим новый проект на Delphi с одной формой, положим на неё кнопку Button1. Теперь создадим ещё одну форму Form2. В модуле Unit1 в список uses добавим Unit2 и напишем такой обработчик нажатия Button1
procedure TForm1.Button1Click(Sender: TObject);
begin
Form2.ShowModal;
end;
Легко убедиться, что такой код работает правильно, т.е. при нажатии на Button1 модально открывается форма Form2. Почему мы можем работать с объектом Form2, хотя нигде не вызывали его конструктор?
Варианты ответов:
Формы – это особые классы, которые не нуждаются в создании с помощью конструктора
Код создания формы помещается в главный модуль проекта
Форма создаётся автоматически при чтении её DFM-файла
Комментарий: Если открыть текст главного модуля проекта, легко убедиться, что при добавлении в проект второй формы в нём появляется строка Application.CreateForm(TForm2, Form2). Внутри метода CreateForm и вызывается конструктор TForm2.Create, т.е. форма, как и любой другой объект, создаётся конструктором. По умолчанию новая форма добавляется в список автоматически создаваемых форм. Это приводит к большому перерасходу ресурсов, особенно если у программы много диалоговых форм, которые показываются лишь изредка, а существуют на протяжении всего времени работы программы. Поэтому для таких форм лучше запретить автосоздание. Для этого можно либо вручную удалить указанную строку из главного файла проекта, либо открыть закладку Forms диалогового окна Project/Options и убрать из этого списка формы через него. Формы, не создающиеся автоматически, при необходимости могут быть созданы вызовом конструктора так же, как и любые другие объекты.
Что же касается DFM-файла, то он (точнее, его копия, добавленная в ресурсы приложения) читается самим конструктором формы, т.е. вызов конструктора является причиной чтения этого файла, а не наоборот.
Вопрос №3
На главную форму в режиме проектирования помещена метка Label1 и вручную изменён конструктор так, что код формы выглядит следующим образом.
type
TForm1 = class(TForm)
Label1: TLabel;
private
{ Private declarations }
public
constructor Create(AOwner: TComponent); override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor TForm1.Create(AOwner: TComponent);
begin
Label1.Font.Style := [fsBold];
inherited;
end;
end.
Что произойдёт при запуске этой программы?
Варианты ответов:
Шрифт метки станет жирным
Шрифт метки останется нормальным
Метка не будет видна
Произойдёт исключение Access violation
Комментарий: По умолчанию все поля вновь созданного объекта обнуляются прологом конструктора, т.е. к началу работы TForm1.Create поле Label1 будет иметь значение nil. Создание объектов, положенных на форму в режиме проектирования, выполняется конструктором класса TForm. Таким образом, до вызова унаследованного конструктора мы пытаемся обратиться к свойствам объекта по нулевой ссылке, что приводит к исключению Access violation. Поэтому все манипуляции с вложенными объектами в подобных случаях следует выполнять уже после вызова унаследованного конструктора.
В некоторых случаях всё не так очевидно. Если бы мы попытались аналогичным образом присвоить значение свойству Label1.Caption, программа запустилась бы, хотя присвоенное в конструкторе значение было бы проигнорировано. Это происходит потому, что в некоторых случаях код VCL проверяет Self на равенство nil, и если ссылка оказывается нулевой, просто ничего не делает.
Вопрос №4
На некоторой форме вручную создаются компоненты TLabel и TStaticText на одном и том же месте
«Это TStaticText», потому что TStaticText был создан первым
«Это TLabel», потому что TLabel был создан позже и оказался «над» TStaticText
«Это TStaticText», потому что TStaticText оконный компонент, а TLabel неоконный
«Это TLabel», потому что TLabel перенесён наверх вызовом BringToFront
Комментарий: Все визуальные компоненты делятся на оконные и неоконные. Неоконные рисуются непосредственно на своём родителе, оконные являются самостоятельными окнами, которые находятся «над» своим родителем. TStaticText является оконным компонентом, поэтому он всегда оказывается «над» неоконным TLabel независимо от порядка создания. Метод BringToFront тоже не может изменить это, он выдвигает TLabel наверх только среди других неоконных компонентов.
Чему будет равно значение свойства Tag2 сразу после создания компонента TSomeComponent?
Варианты ответов:
0
5
Некоторой случайной величине
Свойствам, объявленным с директивой default, должно быть присвоено значение в конструкторе, без этого код просто не откомпилируется
Комментарий: Директива default не устанавливает значение свойства по умолчанию. Она всего лишь подсказывает среде, какое значение по умолчанию будет у свойства, чтобы, если вдруг свойство имеет именно такое значение, не тратилось время на сохранение этого свойства в DFM-файл и на его чтение оттуда. Компилятор не проверяет, действительно ли значение, указанное в директиве default, будет значением по умолчанию, программист сам несёт ответственность за это. Так как в нашем случае никакого присваивания кода в конструкторе нет, свойство получит значение по умолчанию 0 (обнуление всех полей объекта при создании гарантирует метод TObject.InitInstance, который вызывается из пролога конструктора).
Вопрос №6
Что неправильно в этом коде?
procedure MyClick(Sender: TObject);
begin
...
end;
...
Button1.OnClick := MyClick;
Варианты ответов:
MyClick имеет неверный набор параметров
MyClick является обычной процедурой, а не методом класса
Назначение обработчиков событий должно выполняться с помощью оператора @
Всё правильно
Комментарий: В некоторых случаях компилятор действительно может принять обращение к процедурной переменной за попытку её вызова, и избежать этого помогает использование оператора @, но в данном случае этого не происходит. Ошибка в другом: тип TNotifyEvent, к которому относится OnClick описан так
TNotifyEvent = procedure(Sender: TObject) of object;
Наличие в конце слов of object указывает на то, что здесь требуется метод объекта, а не обычная процедура (разница между ними заключается в том, что методу передаётся один неявный параметр Self, которого нет у обычных процедур). Следовательно, при попытке установить в качестве обработчика сообщения обычную процедуру мы получим ошибку несоответствия типов.
Вопрос №7
Требуется удалить компоненты, принадлежащие форме и удовлетворяющие некоторому условию. Среди компонентов могут встречаться как созданные при проектировании, так и созданные программно. Для этого в одном из методов формы используется такой код:
var
I: Integer;
for I := 0 to ComponentCount – 1 do
if {Проверка условия} then
Components[I].Free;
Что неправильно в этом коде?
Варианты ответов:
Всё правильно
Компоненты, созданные при проектировании, нельзя удалять с помощью Free
Не гарантируется, что будут удалены все требуемые компоненты
Возникнет исключение, потому что индекс выйдет за границу списка
Комментарий: Так как цикл for вычисляет свои границы только один раз, количество итераций цикла будет равно первоначальному количеству компонентов. Но во время выполнения цикла количество компонентов может уменьшиться. Из-за этого, если хотя бы один компонент удовлетворяет условию, выполнение этого кода приведёт к исключению.
Вопрос №8
Условия – те же, что и в предыдущем вопросе, но теперь для удаления компонентов используется следующий код
var
I: Integer;
I := 0;
while I < ComponentCount do
begin
if {Проверка условия} then
Components[I].Free;
Inc(I);
end;
Что неправильно в этом коде?
Варианты ответов:
Всё правильно
Компоненты, созданные при проектировании, нельзя удалять с помощью Free
Не гарантируется, что будут удалены все требуемые компоненты
Возникнет исключение, потому что индекс выйдет за границу списка
Комментарий: В отличие от цикла for цикл while осуществляет проверку условия на каждой итерации, поэтому выхода за пределы списка здесь не будет. Но гарантии того, что будут удалены все требуемые компоненты, нет. Например, пусть первый компонент, который нужно удалить, имеет индекс 5. Когда мы дойдём до него и удалим, все следующие за ним компоненты уменьшат свой индекс на единицу, и тот, который был шестым, станет пятым. Но далее мы увеличиваем индекс на единицу, переходя сразу к тому компоненту, который был седьмым, а стал шестым. Таким образом, компонент, изначально бывший шестым, мы пропустим, и он не будет удалён даже если удовлетворяет условию. И вообще, из нескольких подряд идущих компонентов, которые удовлетворяют условию, будут удалены не все, а через одного. Чтобы избежать этого, следует увеличивать I только в том случае, если очередной компонент не удовлетворяет условию и, соответственно, не удаляется.
Вопрос №9
Во время выполнения программы создаются два списка рисунков следующим кодом
Список IL1 будет удалён при удалении формы автоматически, IL2 необходимо удалять вручную
Список IL1 попадёт в список Components формы, IL2 – не попадёт
Список IL1 можно найти с помощью FindComponent, IL2 – нельзя
Всё вышеперечисленное
Никакой разницы
При создании списка IL2 возникнет исключение, так как владелец компонента не может быть нулевым
Комментарий: При создании компонента в конструкторе указывается компонент, являющийся владельцем для вновь создаваемого. Владелец помещает созданный компонент в свой список Components. Список IL2, не имеющий владельца, ни в чей список Components не попадает. Метод FindComponent проходит в цикле по списку Components и ищет компонент, чьё свойство Name совпадает с заданным. Так как IL2 не попадает в список Components, метод FindComponent не сможет её найти. И, наконец, каждый компонент при уничтожении удаляет все компоненты, которые помещены в его список Components, поэтому удалять такие компоненты вручную нужно только в том случае, если это необходимо сделать раньше, чем будет удалён компонент-владелец. Список IL2, не попавший ни в один список, никто удалять не будет, поэтому его нужно удалить вручную. Таким образом, всё, что перечислено в первых трёх пунктах, является правильным.
Вопрос №10
На форме во время проектирования размещены компоненты, в том числе панель Panel1 и некоторые компоненты на ней. В одном из методов формы необходимо скрыть все компоненты, принадлежащие этой панели. Для этого написан такой код
var
I: Integer;
for I := 0 to ControlCount – 1 do
if Controls[I].Parent = Panel1 then
Controls[I].Visible := False;
Каков будет результат выполнения этого кода?
Варианты ответов:
Никакого видимого результата
Нужные компоненты будут скрыты
Код возбудит исключение из-за выхода индекса за пределы списка
Код не откомпилируется, так как Controls[I] имеет тип TControl, а свойство Visible у TControl имеет видимость protected
Комментарий: Визуальные компоненты (т.е. TControl и его наследники) имеют свойства Owner (владелец) и Parent (родитель). Свойство Owner унаследовано от TComponent, оно определяет, кто отвечает за удаление компонента, но никак не связано с визуальным расположением компонентов на экране. Этим управляет свойство Parent – компонент визуально размещается на том компоненте, который является для него родителем.
В список Components попадают все компоненты, для которых данный компонент является владельцем. А те, для которых он является родителем, попадают в список Controls.
Владельцем всех компонентов, созданных во время проектирования, становится сама форма. А родителем, соответственно, тот компонент, на котором они лежат. Поэтому в список Controls формы попадают только те визуальные компоненты, которые расположены непосредственно на самой форме. А те, что лежат на панели, попадают в список Controls этой панели. Соответственно, при проходе по списку Controls самой формы мы не найдём ни одного компонента, для которого Parent = Panel1, поэтому никакого видимого результата от выполнения такого кода не будет. Чтобы получить результат, нужно пройтись в цикле по списку Panel1.Controls. При этом условие Panel1.Controls[I].Parent = Panel1 можно не проверять, так как оно будет истинным для всех компонентов из этого списка.
Вопрос №11
В одном из методов формы написан следующий код
var
MyFont: TFont;
begin
MyFont := TFont.Create;
// Здесь идут присваивания свойств MyFont
Canvas.Font := MyFont;
MyFont.Free;
Canvas.TextOut(100, 100, ‘Text’);
end;
Что неправильно в этом коде?
Варианты ответов:
Всё правильно
Экземпляры класса TFont нельзя создавать вручную вне TCanvas
Свойство Canvas.Font нельзя присваивать целиком, только отдельные свойства
Объект MyFont удаляется до того, как будет использован методом TextOut
Комментарий: Не существует никаких ограничений на создание объектов TFont и присваивания свойства Canvas.Font. Удалять MyFont тоже можно без всяких проблем (и даже необходимо удалить, потому что автоматически он удалён не будет). Дело в том, что здесь копируется не ссылка на объект, а сам объект. Canvas.Font – это не поле, а свойство, поэтому операция присваивания в данном случае – это не копирование ссылки, как это было бы в случае обычных переменных, а неявный вызов метода Canvas.SetFont. Указанный метод не копирует ссылки, а вызывает Assign, т.е. копирует состояние из одного объекта в другой. В этом легко убедиться, потому что после такого присваивания Pointer(Canvas.Font) <> Pointer(MyFont), т.е. это будут ссылки на два разных объекта. Поэтому удаление объекта MyFont никак не влияет на метод Canvas.TextOut.
По похожему принципу работают многие классовые свойства в VCL. Если свойство никогда не принимает значение nil, то практически наверняка при присваивании этому свойству объекта выполняется копирование содержимого, а не ссылки.
Вопрос №12
На форме в одном из её методов следующим кодом создана кнопка, не имеющая владельца
Btn := TButton.Create(nil);
// Присваиваем свойства Btn
Btn.Parent := Self;
Какой объект отвечает за удаление объекта Btn при закрытии формы?
Варианты ответов:
Никто не отвечает, программист должен удалить её вручную
Отвечает сама форма
Отвечает объект Application
Создавать кнопки, не имеющие владельца, нельзя
Комментарий: Хотя кнопка не имеет владельца (что вполне допустимо), она не может не иметь родителя, которым в данном случае становится форма. Оконные компоненты VCL отвечают не только за удаление тех компонентов, которыми он владеют, но и тех, для которых они являются родителями, поэтому форма при удалении сама удалит кнопку.
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.