Пора раскрыть секреты родословной фреймов, которые я собираюсь использовать в проекте 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

В базовом фрейме реализован вызов подсистемы помощи и возврата к предыдущему фрейму. Также в нем добавлено свойство 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

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

Код модуля лаконичен: для навигации используется метод 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)

Главная форма

Как же теперь выглядит главная форма? Совершенно пустой!

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)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *