Пора раскрыть секреты родословной фреймов, которые я собираюсь использовать в проекте Data Keeper for Android.
TFrame // базовый класс
TfraBaseFrame // базовый фрейм
TfraStartFrame // *) стартовая страница
TfraHelpFrame // *) отображение помощи
TfraViewFrame // просмотр и редактирование
TfraListFrame // элемент отображения - список (TListView)
TfraDBaseList // *) список баз
...
TfraTreeFrame // элемент отображения - дерево (TTreeView)
TfraClassTree // *) дерево классов
...
TfraEditFrame // форма редактирования
TfraClassEdit // *) форма редактирования класса
...
Code language: Delphi (delphi)
TBaseFrame был создан в прошлый раз. Символами *) отмечены фреймы, которые будут создаваться и отображаться во время выполнения приложения. А для этого нам понадобится небольшой класс, который будет являться генератором необходимых нам объектов.
TFrameRepository
Я считаю, что глобальные переменные/процедуры/функции – это зло. Однако в данном случае зло будет давать нам очевидную пользу – снижать зависимость между модулями. Для этого каждый фрейм, который нам нужно создать в коде, мы предварительно будем регистрировать глобальной процедурой RegFrame(), которая будет вызываться в секции initialization модуля фрейма. А затем, когда нам понадобится экземпляр класса, он будет создан функцией TFrameRepository.GetFrame(), которая в качестве параметра принимает строковое значение – зарегистрированное название класса и форму, на которой должен быть размещен создаваемый фрейм.
unit FrameRepository;
interface
uses
System.SysUtils, System.Classes, System.Contnrs, FMX.Forms;
type
TFrameRepository = class
private
fClassList: TClassList;
fNames: TStringList;
public
procedure AddFrame(AClassName: string; AClass: TClass);
function GetFrame(AClassName: string; AOwner: TComponent): TFrame;
destructor Destroy; override;
constructor Create;
end;
var
FrameRep: TFrameRepository;
procedure RegFrame(AClassName: string; AClass: TClass);
implementation
procedure RegFrame(AClassName: string; AClass: TClass);
begin
if FrameRep = nil then
FrameRep := TFrameRepository.Create;
FrameRep.AddFrame(AClassName, AClass);
end;
{ TFrameRepository }
procedure TFrameRepository.AddFrame(AClassName: string; AClass: TClass);
begin
fClassList.Add(AClass);
fNames.Add(Uppercase(AClassName));
end;
constructor TFrameRepository.Create;
begin
inherited;
fClassList := TClassList.Create;
fNames := TStringList.Create;
end;
destructor TFrameRepository.Destroy;
begin
fClassList.Clear;
fClassList.free;
fNames.Clear;
fNames.free;
inherited;
end;
function TFrameRepository.GetFrame(AClassName: string; AOwner: TComponent): TFrame;
var
i: integer;
tmpClass: TClass;
begin
i := fNames.IndexOf(Uppercase(AClassName));
if i = -1 then
raise Exception.Create('Фрейм "' + AClassName + '" не зарегистрирован.')at@TFrameRepository.GetFrame;
tmpClass := fClassList.Items[i];
if tmpClass.NewInstance is TFrame then
Result := (tmpClass.NewInstance as TFrame).Create(AOwner)
else
raise Exception.Create('Объект "' + AClassName + '" не является фреймом.')at@TFrameRepository.GetFrame;
end;
end.
Code language: Delphi (delphi)
Подобный репозиторий можно сделать универсальным, хранить в нем образцы классов для дальнейшего “воспроизводства”.
Навигация и стек
Переходы между фреймами бывают двух видов: вниз по дереву навигации и вверх (назад). Для управления такой навигацией идеально подходит стек LIFO, в который записываются отображаемые фреймы. Стек и методы управления им я разместил в модуле данных damUIData.
TdamUIData
Для этого я доработал модуль данных, добавив у него несколько публичных методов:
- ShowFrame – предыдущий фрейм скрыть, создать и показать новый фрейм
- GoBack – вернуться назад: удалить текущий фрейм и показать предыдущий
- HelpURL – получить URL для контекстной помощи
- Init – инициализировать модуль
Чтобы не передавать каждый раз главную форму приложения в качестве параметра, ссылка на неё добавляется в модуль при инициализации.
unit UIData;
interface
uses
System.SysUtils, System.Classes, System.ImageList, FMX.ImgList, FMX.Forms;
type
// режим работы формы редактирования: редактирование, просмотр, добавление
TEditFrameMode = ( efmEdit, efmView, efmNew );
TdamUIData = class(TDataModule)
imlMain: TImageList;
procedure DataModuleCreate(Sender: TObject);
procedure DataModuleDestroy(Sender: TObject);
private
fFrameList: TList;
fForm:TForm;
public
procedure ShowFrame( AName:string );
procedure GoBack;
function HelpURL: string;
procedure Init( AForm:TForm );
end;
var
damUIData: TdamUIData;
implementation
{%CLASSGROUP 'FMX.Controls.TControl'}
uses FrameRepository, BaseFrame;
{$R *.dfm}
{ TdamUIData }
procedure TdamUIData.DataModuleCreate(Sender: TObject);
begin
fFrameList := TList.Create;
end;
procedure TdamUIData.DataModuleDestroy(Sender: TObject);
begin
fFrameList.Clear;
fFrameList.Free;
end;
procedure TdamUIData.GoBack;
begin
if fFrameList.Count > 1 then
begin
TFrame(fFrameList.Items[ fFrameList.Count - 1 ]).Free;
fFrameList.Delete( fFrameList.Count - 1 );
TFrame(fFrameList.Items[ fFrameList.Count - 1 ]).Visible := True;
end;
end;
function TdamUIData.HelpURL: string;
begin
if fFrameList.Count > 1 then // скрываем предыдущий фрейм, если он есть
Result := TfraBaseFrame(fFrameList.Items[ fFrameList.Count - 2 ]).HelpURL // индекс обусловлен последовательностью в процедуре ShowFrame: Add - Init;
else
Result := ''; // это ошибочная ситуация...
end;
procedure TdamUIData.Init(AForm: TForm);
begin
fForm := AForm;
end;
procedure TdamUIData.ShowFrame(AName: string);
var
tmpFrame:TfraBaseFrame;
begin
tmpFrame := TfraBaseFrame(FrameRep.GetFrame(AName,fForm));
if fFrameList.Count > 0 then // скрываем предыдущий фрейм, если он есть
TFrame(fFrameList.Items[ fFrameList.Count - 1 ]).Visible := False;
tmpFrame.Parent := fForm;
fFrameList.Add( tmpFrame );
tmpFrame.Init;
end;
end.
Code language: Delphi (delphi)
❗❓ В процедуре TdamUIData.GoBack есть какая-то заковыка, приводящая к тому, что при запуске программы под Windows после выполнения этой процедуры главная форма становится… недоступной? Её нельзя сдвинуть, изменить размер или использовать кнопки на заголовке. Однако, если перевести фокус на другое приложение, а потом вернуть его, то форма “оживает”.
Переключение фреймов
Семена фреймов реализуется в методах обработчиках событий onExcecute у объектов TAction. При этом используются методы класса damUIData.
TfraBaseFrame
![](https://k245.ru/wp-content/uploads/2024/06/image-11.png)
В базовом фрейме реализован вызов подсистемы помощи и возврата к предыдущему фрейму. Также в нем добавлено свойство HelpURL, в котором хранится адрес станицы для on-line помощи.
unit BaseFrame;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls, FMX.Objects, FMX.Controls.Presentation, FMX.ImgList, System.Actions, FMX.ActnList;
type
TfraBaseFrame = class(TFrame)
tbrTop: TToolBar;
labCaption: TLabel;
sbtBack: TSpeedButton;
alsMain: TActionList;
actBack: TAction;
actHelp: TAction;
sbtHelp: TSpeedButton;
glyHelp: TGlyph;
glyBack: TGlyph;
tbrBottom: TToolBar;
procedure actHelpExecute(Sender: TObject);
procedure actBackExecute(Sender: TObject);
private
protected
FHelpURL:string;
public
property HelpURL:string read FHelpURL;
procedure Init; virtual;
end;
implementation
{$R *.fmx}
uses UIData;
procedure TfraBaseFrame.actBackExecute(Sender: TObject);
begin
damUIData.GoBack;
end;
procedure TfraBaseFrame.actHelpExecute(Sender: TObject);
begin
damUIData.ShowFrame('HelpFrame')
end;
procedure TfraBaseFrame.Init;
begin
end;
end.
Code language: Delphi (delphi)
TfraStartFrame
Стартовый фрейм будет отображать сводную информацию о текущей базе данных, с него можно будет перейти непосредственно к работе, а также к интерфейсам выбора базы данных и настройки её логической структуры.
![](https://k245.ru/wp-content/uploads/2024/06/image-12.png)
Код модуля лаконичен: для навигации используется метод damUIData.ShowFrame(), в методе Init указывается страница с подсказкой, а в секции initialization производится регистрация данного фрейма в репозитории.
unit StartFrame;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls, BaseFrame, System.Actions, FMX.ActnList, FMX.ImgList, FMX.Controls.Presentation;
type
TfraStartFrame = class(TfraBaseFrame)
sbtSetup: TSpeedButton;
glySetup: TGlyph;
sbtSelectBase: TSpeedButton;
glySelectBase: TGlyph;
actSetup: TAction;
actSelectBase: TAction;
sbtStart: TSpeedButton;
glyStart: TGlyph;
actStart: TAction;
labTmpInfo: TLabel;
procedure actSetupExecute(Sender: TObject);
procedure actSelectBaseExecute(Sender: TObject);
private
{ Private declarations }
public
procedure Init; override;
end;
implementation
{$R *.fmx}
uses FrameRepository, UIData;
{ TfraStartFrame }
procedure TfraStartFrame.actSelectBaseExecute(Sender: TObject);
begin
inherited;
damUIData.ShowFrame('DBaseList');
end;
procedure TfraStartFrame.actSetupExecute(Sender: TObject);
begin
inherited;
damUIData.ShowFrame('ClassTree');
end;
procedure TfraStartFrame.Init;
begin
inherited;
FHelpURL := 'https://k245.ru/mvm/vozvrashhenie-delphi.html';
end;
initialization
RegFrame('StartFrame',TfraStartFrame);
end.
Code language: Delphi (delphi)
Главная форма
Как же теперь выглядит главная форма? Совершенно пустой!
![](https://k245.ru/wp-content/uploads/2024/06/image-13.png)
TfrmMain
Всё, что делает главная форма при своем запуске, так это инициализирует модуль данных и отображает стартовый фрейм. Скорей всего про главную форму можно теперь забыть, так как все интересное будет добавляться на фреймах и модуле данных.
unit Main;
interface
uses FMX.Forms;
type
TfrmMain = class(TForm)
procedure FormShow(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
{$R *.fmx}
uses UIData;
{$R *.LgXhdpiPh.fmx ANDROID}
{$R *.Windows.fmx MSWINDOWS}
procedure TfrmMain.FormShow(Sender: TObject);
begin
damUIData.Init(Self);
damUIData.ShowFrame('StartFrame');
end;
end.
Code language: Delphi (delphi)