Формы в Delphi

Формы в Delphi

Формы в Delphi и принцип их организации является одной из важнейших тем. В этой статье будет рассматриваться организация форм применительно к библиотеке VCL.

Что такое формы?

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

Вообще формы, сами по себе, являются частью операционной системы WIndows. То есть, класс форм уже описан в операционной системе. То есть, когда мы создаем форму, то мы фактически говорим операционной системе, нарисовать нам форму в таком то месте, такого то размера и просто задаем для этого необходимые параметры. Так работают большинство языков от Microsoft.

То есть, работают многие языки по все еще старому принципу ActiveX. Даже не смотря на то, что у нас теперь есть библиотека .Net. все-равно, она использует компоненты операционной системы.

Delphi же устроен совершенно иначе. И на мой взгляд — это является его неоспоримым преимуществом. Преимущество это заключается в старой доброй библиотеке VCL, которая является независимой от операционной системы WIndows. То есть, компоненты библиотеки встроены, созданные впоследствии как объекты будут встроены в файл формы и будут являться его частью, а не частью операционной системы.

Иначе говоря, вы можете использовать любой стандартный объект в Delphi из палитры компонентов VCL и при этом не беспокоиться о том, есть эли этот компонент на компьютере конечного пользователя вашей программы.

Все ваши используемые компоненты будут вшиты в один исполняемый .exe-файл в отличие от других языков, которые потребуют вместе с exe-файлом распространять и компонент (если его нет в Windows).

То есть, в программу на Delphi я могу встроить любой компонент библиотеки VCL, как стандартный, так и коммерческий, скомпилировать программу и она без проблем будет работать на любой машине.

Все эти компоненты будут являться частью формы, а форма — частью приложения. В итоге все это будет составлять абсолютно не зависимое от чего-либо приложение. В этом, по моему мнению, и есть огромный плюс Delphi.

В каких файлах хранятся формы

Формы приложения Delphi хранятся в двух файлах с расширениями .pas и dfm. Это очень удобно. Это похоже на то, как в языке разметки гипертекстов html присутствует стилевое дополнение к нему в виде языка CSS. Сразу хочется отметить один факт о том, что в том же C# в настоящее время также используется технология разделения, то есть код без форматирования хранится в файлах с расширением .cs, а форматирование (Внешний облик объектов формы) хранится в отдельном файле.

В C# эта технология пришла из Delphi.

Файлы .dfm

В файлах этого типа описывается внешность элементов формы. То есть, происходит их стилизация. Что это значит? Это значит, что когда мы в конструкторе форм добавляем на форму элементы управления, меняем их размер, задаем цвет, шрифт, местоположение на форме и прочее — все это записывается (как макрос) в файл .dfm.

Файлы .pas

В файлах .pas описывается собственно класс формы, но без форматирования. То есть, здесь идет описание классов, процедур (методов), функций класса и их реализация.

Давайте теперь посмотрим на пример. Давайте представим, что у нас есть форма, которая описывается двумя файлами: connection_.dfm и connection_.pas. Внешне форма выглядит так:

Форма подключения к базе данных
Форма подключения к базе данных

О том, как создать подключение к базе данных программным способом вы можете по смотреть в статье Подключение к базе данных в Delphi.

Фрагмент файла .dfm этой формы будет следующим:

object frmConnection: TfrmConnection
  Left = 366
  Top = 214
  BorderIcons = []
  BorderStyle = bsNone
  Caption = #1053#1072#1089#1090#1088#1086#1081#1082#1072' '#1087#1086#1076#1082#1083#1102#1095#1077#1085#1080#1103' '#1082' '#1041#1044
  ClientHeight = 234
  ClientWidth = 304
  Color = clOlive
  TransparentColor = True
  TransparentColorValue = clOlive
  DoubleBuffered = True
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  Position = poMainFormCenter
  OnClose = FormClose
  OnCreate = FormCreate
  PixelsPerInch = 96
  TextHeight = 13
  object Panel1: TPanel
    Left = 0
    Top = 39
    Width = 304
    Height = 195
    Align = alBottom
..................

То есть, здесь задается полностью первоначальный внешний вид класса формы. Это и позиция, и ширина/высота формы, обрамление, шрифты, внешний вид не только самой формы, но и ее объектов.

Теперь давайте посмотрим, на файл .pas:

unit connection_;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Inifiles, ExtCtrls, Vcl.Imaging.jpeg, Vcl.Imaging.pngimage,
  Vcl.Mask, Vcl.Buttons;

type
  TfrmConnection = class(TForm)
    Panel1: TPanel;
    txtPort: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    txtPassword: TEdit;
    txtServer: TEdit;
    Label4: TLabel;
    txtUser: TEdit;
    pnlCaption: TPanel;
    imgMinimaze: TImage;
    imgClose: TImage;
    pnlFirstGroup: TPanel;
    pnlPriorGroup: TPanel;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure pnlCaptionMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure pnlFirstGroupClick(Sender: TObject);
    procedure pnlPriorGroupClick(Sender: TObject);
    procedure pnlFirstGroupMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure pnlPriorGroupMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure pnlFirstGroupMouseLeave(Sender: TObject);
    procedure pnlPriorGroupMouseLeave(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  frmConnection: TfrmConnection;

implementation

uses message_, mdi;

{$R *.dfm}

procedure TfrmConnection.FormCreate(Sender: TObject);
var
   iniF: TIniFile;
begin
   frmMDI.mnuConnection.Enabled:=false;
   //Считываем данные из файла Конфиг.
   //Для этого создаем объект файла Конфиг
   iniF:=TiniFile.Create(sTekPapka+'\Config.ini');
   //Считываем содержимое файла Конфиг и заносим значения в форму
   txtServer.Text:=iniF.ReadString('DB', 'Server','');
   txtPort.Text:=iniF.ReadString('DB', 'Port','');
   txtUser.Text:=iniF.ReadString('DB', 'User','');
   txtPassword.Text:=iniF.ReadString('DB', 'Password','');
   iniF.free;
end;

procedure TfrmConnection.pnlCaptionMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  //Перетаскиваем форму за панель (вместо заголовка)
  if ( Button = mbLeft ) then
  begin
    ReleaseCapture;
    SendMessage( Handle, WM_SYSCOMMAND, $F012, 0 );
  end;
end;

procedure TfrmConnection.pnlFirstGroupClick(Sender: TObject);
var
   iniF: TIniFile;
begin
   //Записываем настройки в Конфиг.
   //Для этого создаем объект файла Конфиг
   iniF:=TiniFile.Create(sTekPapka+'\Config.ini');
   //Записываем в файл Конфиг измененные значения
   iniF.WriteString('DB', 'Server',txtServer.text);
   iniF.WriteString('DB', 'Port',txtPort.text);
   iniF.WriteString('DB', 'User',txtUser.text);
   iniF.WriteString('DB', 'Password',txtPassword.text);
   iniF.free;//Уничтожаем объект INI из памяти.
   Application.CreateForm(TfrmMessage,frmMessage);
   frmMessage.Left:=Round(Left +(Width-frmMessage.Width)/2);
   frmMessage.Top:=Round(Top +(Height-frmMessage.height)/2+20);
   frmMessage.lblMessage.Caption:='Данные успешно сохранены! Перезагрузите программу!';
   frmMessage.ShowModal;
   application.Terminate;
end;

procedure TfrmConnection.pnlFirstGroupMouseLeave(Sender: TObject);
begin
    TPanel(Sender).Color:=$004B494B;
end;

procedure TfrmConnection.pnlFirstGroupMouseMove(Sender: TObject;
  Shift: TShiftState; X, Y: Integer);
begin
    TPanel(Sender).Color:=$002C2B2C;
end;

procedure TfrmConnection.pnlPriorGroupClick(Sender: TObject);
begin
    if lfrmCreate=true then
    begin
       Application.Terminate //завершаем приложение
    end else
    begin
       Close;//и закрываем окно авторизации
    end;
end;

procedure TfrmConnection.pnlPriorGroupMouseLeave(Sender: TObject);
begin
    TPanel(Sender).Color:=$004B494B;
end;

procedure TfrmConnection.pnlPriorGroupMouseMove(Sender: TObject;
  Shift: TShiftState; X, Y: Integer);
begin
    TPanel(Sender).Color:=$002C2B2C;
end;

procedure TfrmConnection.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  frmMDI.mnuConnection.Enabled:=true;
  action:=caFree;
end;
end.

Мы видим здесь непосредственно программный код. А так как мы работаем в основном с файлами .pas (с файлами .dfm мы тоже активно работаем, но визуально — в конструкторе форм), то давайте рассмотрим его более подробнее.

Вверху мы видим строку unit connection_;. Это есть название модуля. Не путать с именем формы, которое задается в свойстве Name.

Дальше идет секция interface. В данной секции описываются пользовательские типы (классы) данных. В частности, в разделе uses мы видим список модулей, перечисленных через запятую, которые используются нашим модулем connection_.

После этого начинается секция type. В нем как раз идет непосредственное описание нашего типа (класса) формы, включая описание его методов (процедур). Заканчивается он ключевым словом end;

Секция var предназначена для объявления глобальных переменных, которые будут доступны во всей программе (в других модулях) Здесь же можно объявить и процедуру, которая также будет доступна в любом месте программы. В секциях private и public, расположенных чуть выше секции var также можно объявлять переменные (здесь уместно будет сказать, поля класса), а также процедуры. Но, процедуры, объявленные в секции private будут доступны только в рамках текущего модуля, а процедуры, объявленные в секции public, будут доступны и в других модулях программы.

И вообще, разница между объявлениями в секциях private/public и var состоит в том, что объявления, находящиеся в секциях private/public принадлежат описанному типу (классу) в данном случае формы, а объявления, которые описаны в разделе var, не будут принадлежать классу формы. Они будут просто объявлены в модуле, как глобальные, но к классу никакого отношения иметь не будут. Это основное отличие. Вызываться из программы они, соответственно, тоже будут по разному.

Давайте посмотрим на пример ниже. Наша форма называется frmConnection. Если мы опишем процедуру DataPost в секции var, то в любом месте нашей формы, а также в любом другом модуле программы мы сможем вызывать нашу процедуру просто по имени, написав DataPost.

private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  frmKl: TfrmKl;
  procedure DataPost;

Если же мы объявим нашу процедуру DataPost, скажем в секции public:

private
    { Private declarations }
  public
    procedure DataPost;
  end;
var
  frmKl: TfrmKl;

то вызвать эту процедуру мы сможем только через вызов указания имени объекта нашего класса, то есть таким образом: frmConnection.DataPost

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

Если же мы объявим DataPost в private, то тогда мы также сможем вызвать эту процедуру написанием frmConnection.DataPost, но только в рамках нашего модуля connection_.

private
    procedure DataPost;
  public
    { Public declarations }
  end;
var
  frmKl: TfrmKl;

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

Ниже var находится секция implementation. Здесь у нас уже начинается реализация описанных процедур класса и пользовательских процедур, описанных в секции var.

Здесь также используется секция uses, где мы видим подключение других модулей к нашему модулю, а именно модулей message_, mdi;

Почему есть две секции uses? Одна в разделе interface, а другая в разделе implementation. Модули, подключаемые в секции uses раздела interface будут доступны во всех модулях программы, к которой мы подключим наш модуль connection_.

Модуль же, подключенный в секции uses раздела implementation будет доступен только в нашем модулей connection_.

После этого идет строка {$R *.dfm}. Это директива, которая подключает к нашему модулю connection_.pas файл визуального дизайна формы connection_.dfm.

Ну а после этих строк идет уже непосредственная реализация процедур и функций.

Создание формы в Delphi

Создание формы в Delphi можно разделить условно на два этапа: это создание класса формы (как бы шаблона) и собственно, создание самой формы (как объекта класса) из нашего созданного класса формы.

Создание класса формы во время разработки

Чтобы создать класс формы в приложении нужно выбрать File / New / VCL Forms — Delphi главного меню Delphi. Откроется окно конструктора форм, которое практически одинаковое в большинстве визуальных сред программирования.

Не буду описывать банальный процесс перетаскивания компонентов на форму. Просто отмечу, что само по себе перетаскивание этих компонентов формирует класс формы и записывает информацию в файл .pas и .dfm.

Стоит остановиться на некоторых основных, на мой взгляд, свойствах формы:

Visible

Это логическое свойство. Оно может принимать значения true или false. Во время выполнения вполне понятно, что сделает это свойство. Что же оно сделает во время проектирования. А во время проектирования оно определит то, как будет вызываться наша форма. Если установить для этого свойства значение true, то наша форма будет создаваться командой:

И после создания она сразу же станет видимой. Бывают случае, когда это не удобно. Допустим, нам нужно вызвать данную форму в модальном режиме, тогда нам нужно будет вызвать окно так:

Но увы, мы получим с вам ошибку, поскольку нельзя сделать модальным уже видимое окно. Для этого нужно его сначала сделать не видимым, а затем снова вызвать как модальное:

Но тогда мы получим мелькание формы перед тем, как она откроется в модальном режиме. Да и вообще этот код выглядит весьма не профессионально. Поэтому следует задать свойству visible значение false и вызвать форму таким образом:

AlphaBlend

Это свойство прозрачности формы. Оно принимает значение от 0 (полная прозрачность) до 255 (полная непрозрачность). По сути, используя это свойство можно добиться того же самого эффекта, что и при использовании свойства Visible.

Но свойство AlphaBlend позволит сделать, например плавное появление формы или сделать из формы, какую-нибудь панель инструментов с эффектом полупрозрачности.

FormStyle

Это свойство устанавливает тип формы. Это может быть родительская MDI форма (fsMDIForm), дочерняя форма (fsMDIChild), не принадлежащая родительской, то есть сама, по себе форма (fsNormal) и форма, которая всегда отображается поверх всех окон (fsStayOnTop).

Position

Это свойство определяет место появления формы после ее создания.

StyleName

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

Здесь скажу лишь, самое основное. Для всего приложения стиль задан по умолчанию в свойствах проекта. У каждой же формы по умолчанию свойство StyleName пустое, что означает тот факт, что наша форма использует стиль, заданный для всего приложения по умолчанию.

Но в этом свойстве можно задать имя другого стиля, созданного вами или используемого в поставке вместе с Delphi. Стиль — это внешнее оформление формы и ее элементов, то есть, то, как они выглядят. Стилизация VCL-компонентов позволила приблизиться к возможностям FMX в отношении оформления приложений.

StyleElements

Это свойство позволяет установить стиль для таких элементов как цвет шрифта, клиентская часть, скроллбар и рамка. Оно описывается как TControl.StyleElements = set of (seFont, seClient, seBorder). Более подробно о стилях я расскажу в своих будущих статьях.

Отдельно нужно сказать о том, что все формы в Delphi, созданные программистом в конструкторе форм создаются автоматически из своих классов при запуске программы. Конечно же это не удобно и этого не нужно делать. Автосоздание каждой формы следует отключить в Project/Optoins/Applications/Forms/

Project Options в Delphi
Project Options в Delphi

В этом окне кнопкой «Вправо» перенесите все формы, кроме главной формы приложения.

    Создание формы во время выполнения программы

    Здесь мы уже создаем готовый объект из экземпляра класса, который мы создали. Создать форму можно, по крайней мере, двумя известными мне способами:

    Способ 1

    Данная команда создает объект-форму с именем frmConnection из класса TfrmConnection. Этот объект-форма принадлежит приложению.

    Способ 2

    Это второй способ записи, делающий абсолютно тоже самое. Здесь мы объектной frmConnection переменной, объявленной в классе формы при ее создании в секции var (это делается автоматически средой Delphi) присваиваем объект, который создаем командой Create из класса TfrmConnection. Объект-форма также будет принадлежать объекту-приложению

    или

    При создании формы последовательно происходят следующие события:

    • onCreate — событие, возникающее при создании формы. При этом форма еще не видима;
    • onShow — событие, возникающее при показе созданной формы;
    • onActivate — событие, возникающее при активизации формы после ее показа;
    • onPaint — событие, возникающее при перерисовке формы.

    Организация форм между собой

    Чтобы из из одной формы вызвать другую, ее модуль следует прописать в секции uses раздела implementation.

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

    Назначить главную форму приложения можно и вручную в файле проекта. Его можно открыть командой Project / View Source, либо просто открыть файл проекта в блокноте. Вообще файл проекта имеет расширение .dproj. Но этот файл описывает проект в xml формате и мне с ним работать не удобно, поэтому я редактирую (в случае необходимости) файл с расширением .dpr.

    Именно этот тип файла в версии Delphi 7 и ранее был файлом проекта. Если мы его откроем, то мы увидим все ровно тоже самое, что и в окне, вызванном командой Project / View Source.

    Интересующие строки находятся в самом низу:

    Строка Application.CreateForm (TfrmMDI, frmMDI); задает нам главную форму приложения. Вообще в этом списке может быть несколько форм (в зависимости от того, сколько мы оставили форм в списке автосоздаваемых).

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

    Закрытие формы

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

    Скрытие формы

    В каких случаях нам может понадобиться просто скрыть (сделать невидимой форму)? Ну, например, когда загрузка этой формы ресурсоемка по времени. То есть, чтобы ее закрыть, а потом вновь открыть, нужно будет ждать. Это замедляет работу программы. В этом случае лучше использовать метод Hide или свойство Visible:=false;

    либо

    Удаление формы из памяти

    Удаление формы из памяти предполагает ее полное уничтожение и всех с ней связанных объектов.

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

    Я считаю, что это не очень хорошо — программист должен контролировать сам, когда и что ему нужно выгрузить. Над Delphi часто смеются в этом отношении, считая его пережитком прошлого. Однако программист обязан следить с утечкой памяти. Современные языки и среды программирования расслабляют программистов и лишают возможности контроля ресурсов.

    Чтобы удалить форму из памяти, нужно вызвать ее метод Close.

    Полностью это будет выглядеть так:

    Но по умолчанию данный метод скроет форму эквивалентно методу Hide, а не уничтожит ее. Скрытую форму не нужно создавать командой CreateForm. Ее нужно будет только показать методом Show.

    А вот, чтобы все-таки выгрузить форму из памяти и освободить занятые ею ресурсы следует прописать в событии OnClose формы запись action:=caFree;

    Такая надпись уже сможет уничтожить форму и ее нельзя будет вызвать методом Show.

    Вы можете также поступить по другому. Например, вызвать метод Free для формы. При этом не нужно будет указывать action:=caFree; в событии OnClose, так как это событие не будет осуществлено.

    Также можно использовать деструктивный метод Destroy.

    Почему вообще этих методов несколько? Сейчас я постараюсь пояснить. В состав любого класса Delphi входят два специальных метода, которые называются конструктор и деструктор. Конструктор (Create — и мы с ним знакомы по созданию нашей формы) создает объект, а деструктор (Destroy) — разрушает объект.

    Когда объект создается (в данном случае форма), то происходит выделение для него оперативной памяти. Для чего это нужно? В этом кусочке памяти хранятся характеристики объекта. Хранятся она, в так называемых полях. Свойство объекта — это как бы это сказать «инструмент» для внесения корректировки в поле. Поле связано со свои свойством. Внесли изменение в свойство, значит внесли изменение в поле. Если убрать абстракцию, то это почти тождественные понятия.

    Деструктор же разрушает объект и после его вызова к полям объекта уже нельзя обратиться. Но деструктор удалит объект из памяти (в нашем случае форму), но не удалит объектную переменную. То есть, если мы напишем frmConnection.destroy;, то мы удалим объект, но не удалим нашу объектную переменную frmConnection, которая была связана с нашим созданным объектом-формой.

    В крупных программах бывает сложно уследить за такими «висячими» переменными (возможно я соглашусь, что в этом есть плюс C# — он автоматически чистит такой мусор) и если вы уже удаленному объекту снова вызовите метод destroy, то получите ошибку, потому что удалять уже нечего.

    В этом случае нужно делать проверку:

    В этом коде написано, что если переменная frmConnection не пустая, то тогда мы удаляем объект-форму, вызывая метод Destroy.

    Чтобы избавить программиста от такой проверки (хоть она и в одну строку) придумали метод Free. Этот метод сам проверяет является ли объектная переменная привязанной к своему объекту, или нет. И если объект существует, то метод Free вызывает метод Destroy. Поэтому не будет никакой ошибки, напишите вы так или по другому. Это просто дополнительное удобство. Следовательно строку:

    можно записать как:

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

    Это наиболее правильная запись. Но здесь нужно сделать оговорку. Возможно вариант с формой не совсем удачный, ведь форма и так при своем уничтожении уничтожает все, связанные с ней объекты (объекты, которые определены в классе формы). Поэтому в случае с формой можно и не писать строку с nil.

    А вот с другими объектами, которые были объявлены в секции var модуля формы дело обстоит иначе. Их нужно уничтожать принудительно до уничтожения формы.

    Лично я вообще люблю использовать метод Close в сочетании с параметром Action:=caFree для закрытия и уничтожения формы, как было описано выше. Close — это такой удобный метод, с помощью которого, указав нужный параметр Action, можно либо уничтожить, либо просто скрыть форму, сделав ее невидимой, как это делает метод Hide.

    Фактически на пути развития Delphi получилось так, что многие методы заменяют друг-друга, вызывают друг-друга. Можно пользоваться тем, что вам нравится.

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

    • onCloseQuery -обычно это событие используется, если перед закрытием формы нужно спросить пользователя о том, хочет ли он, скажем, сохранить данные;
    • onClose — событие, происходящее при закрытии формы. В этом событии еще можно предотвратить закрытие формы, указав нужный параметр Action;
    • onDeactivate — событие, возникающее при деактивации формы;
    • onHide — возникает, когда форма делается невидимой;
    • onDestroy -возникает при уничтожении формы.

    На этом мы закончим наше знакомство с некоторыми аспектами по управлению формами. Конечно в этой статье не делалась попытка осветить полностью все нюансы и приемы по работе с формами. Я привел практические моменты, которые, как мне кажется, являются важными.

    Понравилась статья? Поделиться с друзьями:
    Блог Алексея Иванкова
    Добавить комментарий

    ;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: