Советы по Delphi


FAQ 5 VCL (1 из 2)


"Каким образом VCL компоненты могут быть созданы "на лету", во время выполнения программы?"

Следующий код создает модальную форму для ввода пароля во время выполнения программы. Тип TPasswordForm является классом-наследником от TForm, определяемым или в текущем, или в отдельном модуле со ссылкой на него в списке uses.

    with TPasswordForm.Create(Application) do begin                                { например, TForm1, TPasswordForm и пр.. } ShowModal;                        { Отображаем форму как модальное окно } Free;                             { Освобождаем форму при закрытии } end;

Для добавления компонента на форму во время выполнения программы необходимо выполнить несколько шагов:

  1. Объявите переменную экземпляра компонентного типа, который вы хотите создать {например, TButton }. Примечание: переменные экземпляра используются для указания на фактический экземпляр объекта. Сами они объектами не являются.
  2. Испрользуйте конструктор компонента Create для создания экземпляра объекта компонента и его назначения переменной экземпляра, объявленной в шаге 1. Конструктору Create любого компонента в качестве параметра необходимо передать ссылку на его владельца. За редким исключением в качестве параметра owner конструктора Create необходимо указывать форму.
  3. Назначьте "родителя" свойству компонента Parent (например, Form1, Panel1 и пр.). Родитель определяет где должен быть отображен компонент и осуществляет привязку его левого верхнего угла относительно его координат. Для того, чтобы поместить компонент в Groupbox, установите его свойство Parent в Groupbox. Чтобы компонент был видим, ему необходимо иметь родителя, это позволит ему быть отображенным в его области.
  4. Установите любые другие необходимые свойства (например, Width, Height).
  5. Наконец, заставьте компонент появиться на форме, сделав его видимым, т.е. установив его свойсто Visible в True.
  6. Если вы создали компонент и задали ему владельца (свойство owner), вам нет необходимости уничтожать компонент самим, это сделает за вас его владелец в момент его уничтожения. Если же при создании компонента владелец ему присвоен не был, то вся ответственность за уничтожение ложится на вас, его нужно освободить когда он больше вам не нужен. Следующий код показывает процесс добавления компонента TButton на текущую форму во время работы программы:

    var TempButton : TButton;  { Это только указатель на TButton } begin TempButton := TButton.Create(Self);       { Назначаем владельца форму } TempButton.Parent := Self;                { Должны назначить Parent (родителя) } TempButton.Caption := 'Время выполнения'; { Устанавливаем здесь свойства } TempButton.Visible := True;               { Показываем кнопку } end;

Поскольку кнопка была создана владельцем, то она будет освобождена автоматически при освобождении владельца (формы).

"Как определить класс компонента, при щелчке на котором появляется контекстное меню?

Используйте свойство PopupComponent компонента PopupMenu для определения компонента, на котором была нажата правая клавиша мыши.

    procedure TForm1.PopupItem1Click(Sender: TObject); begin Label1.Caption := PopupMenu1.PopupComponent.ClassName; end;

Можно использовать свойство формы ActiveControl, но компонент, вызвавший контекстное меню, не обязательно может быть активным элементом управления.

"Какие ограничения у стандартных элементов управления Delphi?"

Любой компонент, использующий TList, может хранить информацию размером до 16368 элементов. Для примера, TTabControl может содержать вплоть до 16368 закладок, а палитра компонентов Delphi до 16368 страниц палитры.

Многие стандартные компоненты Delphi являются оболочками стандартных элементов управления Windows. Windows 3.1 накладывают собственные ограничения на данные компоненты. Для примера: TComboBox и TListbox могут содержать до 5440 элементов, в TMemo или TEdit (и др. соответствующие компоненты) до 32кб текста.

Объем ресурсов Windows 3.1 ограничивает компонент TNoteBook и позволяет ему иметь максимум 570 страниц. (Также трудно получить более чем 500 оконных дескрипторов любому приложению Windows.)

Примечание 1: Превышение лимита возбуждает исключение или может сделать работу Windows неустойчивой.

Примечание 2: Многие ограничения, присущие 16-битной версии Windows существенно уменьшены в Windows NT и Windows 95. В будущих 32-битных версих Delphi эти ограничения практически исчезнут.

"Как мне определить длину строки в пикселах с определенным шрифтом?"

Для определения высоты и ширины строки в пикселах могут применяться два метода Canvas - TextHeigh и TextWidth. Не забудьте назначить шрифт объекту Canvas перед тем, как что-либо нарисовать или провести измерения.

Все визуальные компоненты имеют свойство Canvas, но по умолчанию оно защищено (protected) для того, чтобы доступ к нему имели только прямые потомки. Но, поскольку вы создаете свой код на основе наследников TForm, то у вас практически всегда имеется доступ к унаследованному свойству формы Canvas. Компонент TPaintBox имеет доступное (public) свойство Canvas для того, чтобы в обработчике события компонента OnPaint вы могли бы что-либо нарисовать, но можно им воспользоваться и для наших целей.

Если компонент не имеет свойство Canvas, то следующая функция поможет возвратить вам ширину текста, основанного на определенном шрифте:

    function GetTextWidth(CanvasOwner: TForm; Text : String; TextFont :  TFont): Integer; var OldFont : TFont; begin OldFont := TFont.Create; try OldFont.Assign( CanvasOWner.Font ); CanvasOWner.Font.Assign( TextFont ); Result := CanvasOwner.Canvas.TextWidth(Text); CanvasOWner.Font.Assign( OldFont ); finally OldFont.Free; end; end;

"Почему некоторые визуальные компоненты типа TPanel и TEdit не имеют свойства Canvas?"

Все наследники TCustomControl имеют свойство Canvas, тем не менее, необходимо иметь какой-то механизм защиты для того, чтобы другие "художники" не могли рисовать на компоненте. Наследники компонента всегда имеют доступ к защищенным свойствам, которые они наследуют от компонента (как, например, Canvas), но те же пользователи компонента к ним доступа не имеют.

    type TCanvasPanel = class(TPanel) public property Canvas; end;

Если вы хотите рисовать на компоненте, не имеющим опубликованного свойства canvas, попробуйте использовать другой компонент, позволяющий рисовать на нем (TPaintBox), или компоненты-слои, позволяющие достигнуть того же результата (client-align у TPaintBox внутри TPanel позволяет получить окантуренную область с возможностью рисования).

"Как мне получить горизонтальную полосу прокрутки в компоненте ListBox?"

Пошлите сообщение LB_SetHorizontalExtent оконному дескриптору компонента ListBox. Например, сообщение можен быть послано в обработчике события формы OnCreate:

    procedure TForm1.FormCreate(Sender: TObject); begin SendMessage(Listbox1.Handle, LB_SetHorizontalExtent, 1000, Longint(0)); end;

"Имеет ли Delphi компонент, поддерживающий последовательные коммуникации (порты)?"

Нет. Тем не менее, существуют библиотеки для работы с последовательными портами (и скоро должны появиться компоненты) для Delphi третьих фирм, как, например, TurboPower, SaxComm и других.

"Каким образом можно установить табуляторы в элементе управления TMemo?"

Для установки табулятора в компоненте многострочного редактирования (например, TMemo), пошлите ему сообщение EM_SetTabStops. Массив Tabs указывает на месторасположение табуляторов. Поскольку параметр WParam в SendMessage равен 1, то все табуляторы будут установлены в величину, передаваемую в массиве Tabs. Не забывайте для включения табуляторов устанавливать свойство WantTabs компонента TMemo в True.

    procedure TForm1.FormCreate( Sender : TObject ); const TabInc : LongInt = 10; begin SendMessage( Memo1.Handle, EM_SetTabStops, 1, Longint( @TabInc ) ); end;

"Какое наилучшее место в коде программы, откуда можно вызвать окно с логотипом программы при ее запуске?"

Наилучшее место для показа окна с логотипом - в исходном коде проекта после первого Application.FormCreate и перед Application.Run. Этим мы осуществляем создание формы на лету и показ ее до момента фактического запуска приложения.

    program Project1;
uses Forms, Unit1 in 'UNIT1.PAS' {Form1}, Splash;
{$R *.RES} var SplashScreen : TSplashScreen;  {в модуле Splash} begin Application.CreateForm(TForm1, Form1); SplashScreen := TSplashScreen.Create(Application); try SplashScreen.Show; SplashScreen.Update; {Обрабатываем любые сообщения Windows, касающиеся отрисовки} { осуществите остальные CreatForms или другие действия прежде чем приложение запустится. Если процедура запуска имеет продолжительное время, то для того, чтобы окно реагировало на системные сообщения, необходимо переодически давать команду Application.ProcessMessages. } finally               {Убедитесь что окно с логотипом освобождается} SplashScreen.Free; end; Application.Run; end.

"Почему, когда пользователь нажимает на кнопке SpeedButton, у компонента TEdit не возникает события OnExit? Существует ли способ заставить генерироваться событие OnExit при нажатии на кнопке SpeedButton?"

SpeedButton в действительности никогда не получает фокуса, поэтому активный элемент управления его и не теряет, следовательно, у активного элемента управления события OnExit не происходит.

Имеется единственный способ вызвать событие OnExit у активного элемента управления - вызвать его явно при наступлении события OnClick у кнопки SpeedButton. Для примера:

    procedure TForm1.SpeedButton1Click(Sender: TObject); begin If ActiveControl is TEdit then (ActiveControl as TEdit).OnExit(ActiveControl); end;

"Во время выполнения программы каждое вновь открытое дочернее окно возникает немного ниже и правее предыдущего. Моя проблема заключается в том, что когда я закрываю какое-либо дочернее окно и открываю новое, оно появляется правее и ниже того, которое я закрыл перед этим, даже если оно было единственным, по каким правилам это писалось?"

Так работают дочерние MDI окна. В этой ситуации VCL не перекрывает поведения Windows, такие правила диктует сама система.

Непроверенное предположение: В процедуре FormCreate попробуйте установить необходимые значения свойствам Top, Left, Width и Height. FormCreate дочерней MDI формы будет вызвано прежде, чем будет показано само окно.

"Почему моя программа не может найти ресурсы, упакованные мной в .RES-файл, если .RES-файл имеет то же самое имя, что и модуль формы?"

Если имя используемого вами .RES-файла совпадает с именем .DPR-файла, Delphi перезапишет его, создав собственный .RES-файл с этим именем. Кроме того, проектный RES-файл предназначен только для менеджера Delphi-проекта, не редактируйте и не добавляйте к нему свои ресурсы.

"Существует ли функция прокрутки формы с помощью клавиш? Например, прокрутка вверх и вниз при нажатии PgUp и PgDown соответственно.

Прокрутка формы осуществляется с помощью изменения свойства Position свойства формы VertScrollbar или HorzScrollbar. Следующий код показывает как это можно сделать:

    procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); const PageDelta = 10; begin With VertScrollbar do if Key = VK_NEXT then  Position := Position+PageDelta else if Key = VK_PRIOR then Position := Position-PageDelta; end;

Примечание: Это может не работать, если активный элемент управления (типа TMemo) также использует PgUp & PgDn.

"Существует ли способ заполнения TListbox или TMemo за один проход?"

Для заполнения сразу нескольких строк в компоненте TListbox или TMemo может использоваться метод SetText. Методу SetText передается строка с терминирующим нулем, где каждая строка сопровождается разделителем, символом возврата каретки - #13. Пояснительный пример:

    Listbox1.Items.SetText('aaaaa'#13'bbbbb'#13'ccccc')

При этом ListBox отобразит следующий текст: aaaaa bbbbb ccccc Примечание: Предпочтительным методом заполнения ListBox или Memo является метод Add их соответствующих свойств Items и Lines.

"Возможно ли создание аналога массива элементов управления, применяемого в Visual Basic? Например, я хочу иметь группу кнопок с общим обработчиком события, с помощью которого я мог бы определить нажатую кноку или ее порядковый номер. В Visual Basic это можно было сделать с помощью индекса управляющего массива (Control Array)."

Один из способов решения задачи состоит в присваивании полю Tag уникального числа, создание общего обработчика события для всех кнопок и проверки содержимого поля Tag передаваемого объекта Sender. Назначьте всем кнопкам группы один и тот же обработчик события OnClick. Обработчик события должен выглядеть приблизительно так:

    procedure TForm1.Button1Click(Sender: TObject); var cap: string; begin case TButton(sender).Tag of 1: ShowMessage('Нажата первая кнопка'); 2: ShowMessage('Нажата вторая кнопка'); 2: ShowMessage('Нажата третья кнопка'); end; end;

"Как добавить во время выполнения программы на страницу TTabbedNoteBook компонент? Как мне определить родителя для нового компонента?"

При добавлении компонента на страницу TabbedNotebook во время выполнения программы, ссылка на желаемую страницу должна быть назначена свойству нового компонента Parent прежде, чем она будет показана. Получить доступ к страницам TTabbedNotebook во время выполнения программы можно через элементы-объекты свойства-массива TabbedNotebook Pages. Другими словами, компоненты страницы сохраняются как объекты, "прикрепленные" к имени страницы в списке строк свойства Pages. Вот пример создания кнопки на второй странице компонента TabbedNotebook1:

    var NewButton : TButton; begin NewButton := TButton.Create(Self); NewButton.Parent := TWinControl(TabbedNotebook1.Pages.Objects[1]) ...

Вот как страница TNotebook может быть использована в качестве родителя при создании на ней нового компонента:

    NewButton.Parent := TWinControl(Notebook1.Pages.Objects[1])

Вот как закладка TTabSet может быть использована в качестве родителя при создании на ней нового компонента:

    NewButton.Parent := TWinControl(TabSet1.Tabs.Objects[1])

"Существует ли в природе компонент, позволяющий создавать вертикальные (на левой или правой части страницы) закладки в блокноте, пусть он будет хоть коммерческий, хоть свободный."

Такую характеристику поддерживает продукт фирмы TurboPower Orpheus. Для получения дополнительной информации посетите сервер компании TURBOPOWER.

"Существует ли простой способ использования CopyToClipboard, CutToClipboard с компонентом TEdit, имеющим фокус?

Просто проверьте, имеет ли ActiveControl тип TEdit, и, если да, то выполняйте желаемую операцию: вырезайте, копируйте или вставляйте. Для примера:

    if (ActiveControl is TEdit) then TEdit(ActiveControl).CopyToClipboard;

"Компонент TDBGrid имеет события OnMouseDown, OnMouseUp и OnMouseMove?"

События существуют, но они не опубликованы. Вы можете создать простой наследник TDBGrid и опубликовать их.

"Расходует ли Delphi системные ресурсы при открытии и закрытии модальных окон? Например, следующий код уменьшает системные ресурсы при каждом показе модального диалога:"

    ModalForm1 := TModalForm1.Create(Self); ModalForm1.ShowModal;

В отсутствие обработчика OnClose установите параметр Action в caFree, ваш код создает каждую новую форму вызовом TModalForm1.Create(Self), но предыдущий экземпляр формы не уничтожается. Все предыдущие экземпляры форм "ModalForm1" так и остаются "болтаться" в Windows.

Чтобы форма освобождала свои ресурсы, можно также использовать метод Free. Вот демонстрация этого метода:

    try ModalForm1.ShowModal; { здесь размещается необходимый код } finally ModalForm1.Free; end;

"Какое лучшее решение для создания группы RadioGroup и размещения в ней кнопок RadioButton? Кажется, я просто могу создать компонент RadioGroup и набросать туда несколько RadioButton, или все же лучше создать RadioGroup, задать значение свойству Items, тем самым определив заголовки элементов (RadioButton) и разместив их в группе?"

Если вы собираетесь использовать RadioGroup, то логичным будет для создания RadioButton воспользоваться списком строк Items. При этом RadioButton не должны быть объединены в одну группу, эта причина того, почему к элементу RadioGroup нельзя обратиться как к отдельному компоненту RadioButton.

"Существует ли способ убедить окно, содержащее форму или изображение, в byte-aligned (выравнивании байтов)?"

Перекройте метод CreateParams:

    procedure TMyForm.CreateParams(var Params:TCreateParams); begin inherited CreateParams(Params); Style := Style or CS_BYTEALIGNWINDOW; end;

Примечание: Байтовое выравнивание практически не описано в документации по Windows. Имеет значение только для черно-белого, EGA и 16-цветного VGA видео-режима. Все режимы с более высоким разрешением всегда выравнивают байты.

Какой порядок наступления событий при создании и показе формы?

При создании формы события наступают в следующем порядке: OnCreate, OnShow, OnPaint, OnActivate, OnResize и снова OnPaint.

"Почему, если в обработчике формы OnActivate изменить свойство FormStyle, возникает ошибка 'Cannot change Visible in OnShow or OnHide' (не могу изменить Visible (видимость) в OnShow или OnHide)?

Свойство FormStyle определяет стиль создаваемого окна и обычно устанавливается в обработчике события OnCreate, тем не менее, вы можете его изменить и после создания дескриптора окна, только не в обработчиках событий OnActivate, OnShow или OnHide. Ваша проблема заключается в том, что вы пытаетесь изменить стиль формы при возникновении событий OnShow и OnHide.

"Каким образом я могу сделать у компонента границы типа "sunken" (углубленный) или "raised" (выпуклый)?

Для того, чтобы заставить выглядеть компонент чуть утопленным или приподнятым, разместите его на компоненте TBevel или TPanel, которые имеют свойства соответствующего назначения.

Где я могу найти исходный код страничных компонентов (например, TTabbedNotebook)?

Исходный код VCL не содержит код "tab"-компонентов из-за юридических причин. Тем не менее, исходники интерфейса (interface source) расположены в каталоге DELPHI\DOC и имеют расширение INT.

Примечание: зарегистрированные владельцы исходного кода Delphi RTL могу запросить исходный код TTabSet и TTabbedNotebook у подразделения Borland Corporate Affairs. Инструкция находится в файле readme исходного кода RTL. [000892]




Начало  Назад  Вперед



Книжный магазин