Логин: Пароль:    Регистрация Всеми возможностями сайта можно пользоваться
только после авторизации.
   Забыли пароль?

Поиск
L



Статистика
u
Пользователи онлайн: нет
Гостей онлайн: 3
Всего онлайн: 3
Зарегистрировано юзеров: 6362
Комментариев на сайте: 645
Новый юзер: Pseulairrerve



Последние комментарии
c
Aqel прокомментировал "Урок 71 - Работа с сжатыми файлами":
GZip псевдоархив, на мой вопрос тоже не отвечают, как распаковать отдельные файлы/папки...
Pingitrus прокомментировал "Урок 71 - Работа с сжатыми файлами":
Есть еще кто нибудь живой на этом сайте, кто сможет помочь? Так то все работает, zip и rar распаковывает, но у меня есть файл dat который сжат GZip, почему то его не удается распаковать или так не получится так как этот файл не является архивом или является?)) пишет ошибку что неверная функция



Мы в соцсетях
c
Delphi
Применение фреймов для написания правильных приложений

Не знаю как вы, а я в свое время часто встречался со следующей ситуацией. При разработке довольно больших проектов количество форм с временем разрасталось. И чем далее, тем хуже. Но даже это не было большой проблемой до тех пор, пока я работал сам – все таки себя можно самодисциплинировать – заставить использовать единообразные наименования форм, методов, переменных. Но после того, как я стал работать в команде, проблема стала во весь рост – рефакторинг зачастую стал сводиться к “переписать все”, так как у каждого программиста свое понимание “правильно написаного кода”.После некоторых раздумий я решил создать некий “движок”, который облегчит написания немаленьких проектов. В основу этого движка я поставил такие принципы:
1. Все без исключения объекты для работы с базами данных должны находится в модулях данных, причем количество объектов в базе данных не должно превышать некий критический предел (для меня – до 50 объектов) – дальше стает сложно ориентироваться;
2. Все операции по работе с данными з БД должны также описываться в модулях данных, в соответствующих событиях или ActionList;
3. В главной форме не должно содержаться кода по работе с режимами, только вызов режима и вызов абстрактных, общих для всех методов, которые будут переопределяться в каждом соответствующем режиме.
4. Пользовательский интерфейс всех режимов должен быть полностью единообразен.
5. Режим должен иметь “право” изменения главного окна.
6. Режим не должен знать о существовании других режимов и и других форм вообще, для режима должно быть доступны только главная форма и модули данных.
7. Модули данных не должны знать о существовании режимов.
8. Режимы должны создаваться динамически, дабы не занимать лишнюю память.

Для реализации поставленной задачи наиболее подходили фреймы. Используя фреймы, удалось добиться создания единообразного интерфейса, т.к. у нас была одна главная форма, на которой просто менялись кадры - “фреймы”. Однако первая реализация была неудачная, так как для работы со специфическими функциями вынуждала делать все фреймы на главной форме и переключаться между ними с помощью свойства Visible.

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

Потому было решено отойти от “тяжелого наследия” :) процедурного программирования и использовать основные принципы ООП.

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

unit UnitFrameBase;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  shellapi, DB, DBGrids, UnitConstTypes_etc;

type
  TFrameBase = class(TFrame)
  private
    {описание фрейма - для вывода в заголовке программы}
    FrameDescription: string;
    {основной источник данных}
    FrameCurrentDataSet: TDataSet;
    {флаг загрузки данных}
    LoadingData: Boolean;
    {флаг выполнения длительной работы - например генерации очень большого отчёта}
    MakeLongTimeWork: Boolean;
  public
    { функция, выполняющаяся сразу после создания фрейма}
    procedure InitialisationFrame(Param1, Param2: Integer); virtual;
    { виртуальная функция фильтра данных }
    procedure Filter(Param1, Param2: Integer); virtual;
    { виртуальная функция показа дополнительной информации}
    procedure ShowAdditionalInformation(); virtual;
    { виртуальная функция скрытия/показа наследуемых контролов}
    procedure ShowContols(); virtual;
    { виртуальная функция открытия наборов данных (подключения к уже открытым}
    procedure OpenNeededTables(Param1, Param2: Integer); virtual;
    {процедуры работы с наборами данных}
    {добавление}
    function AddRecord(): TFunctionResult; virtual;
    {редактирование}
    function EditRecord(): TFunctionResult; virtual;
    {удаление}
    function DeleteRecord(): TFunctionResult; virtual;
    {Сохранить}
    function PostRecord(): TFunctionResult; virtual;
    {Отменить}
    function CancelRecord(): TFunctionResult; virtual;
    {Открыть свойства}
    function GetProperty(): boolean; virtual;
    {функции экспорта в различные форматы}
    function ExportData(Parameter: string): TFunctionResult; virtual;
    {функции импорта из различных форматов}
    function ImportData(Parameter: string): TFunctionResult; virtual;
    {Комментарий: функции записи/чтения заголовка фрейма}
    function GetDesc: string;
    procedure SetDesc(Description: string);
    {Процедуры перехода к следующей, предыдущей, первой, последней записи}
    procedure GotoNextElement; virtual;
    procedure GotoPrevElement; virtual;
    procedure GotoFirstElement; virtual;
    procedure GotoLastElement; virtual;
    {функция, возвращающая главный Grid фрейма}
    function GetMainGrid: TDBGrid;virtual;
   {функции установки состояния загрузки данных}
    procedure SetLoadingData(LD: boolean);
    function GetLoadingData: boolean;
   {функции установки состояния длительного процесса}
    procedure SetMakeLongTimeWork (LTW: boolean);
    function GetMakeLongTimeWork : boolean;
    {функции возвращающие текущий источник данных}
    procedure SetFrameCurrentDataSet(DS: TDataSet);
    function GetFrameCurrentDataSet: TDataSet;
    {виртуальный деструктор}
    destructor Destroy;override;
    {процедуры записи/чтения настроек фрейма в файлы}
    {эти процедуры не вызываются автоматически в конструкторах/деструкторах}
    {вызов этих функций лучше покласть на процедуру вывода фрейма}
    function SaveFrameToFile(FileName: TFileName): TFunctionResult; virtual;
    function LoadFromFileToFrame(FileName: TFileName): TFunctionResult; virtual;
  published
    {описание фрейма – для отображения режима в заголовке}
    property FrameDesc: string read GetDesc write SetDesc;
  end;

implementation

uses UnitFormMain, MyDBGrid;

{$R *.dfm}

procedure TFrameBase.ShowAdditionalInformation();
begin
//---в принципе часто нужно что-то помещать в статусбар
end;


procedure TFrameBase.Filter(Param1, Param2: Integer);
begin
//---фильтрация текущего набора данных
//---два параметра передаются через SendMessage
end;

procedure TFrameBase.ShowContols();
begin
//---чтобы скрывать/отображать нужные в текущем режиме контролы
end;

procedure TFrameBase.OpenNeededTables(Param1, Param2: Integer);
begin
// по умолчанию откроем текущий датасет фрейма
 if Assigned(FrameCurrentDataSet) then
  FrameCurrentDataSet.Active := true;
end;

procedure TFrameBase.InitialisationFrame(Param1, Param2: Integer);
begin
//-------------
  SetLoadingData(true);
//откроем нужные таблицы
  OpenNeededTables(Param1, Param2);
//отфильтруем их
  Filter(Param1, Param2);
//покажем нужные контролы
  ShowContols;
  SetLoadingData(false);
end;

{процедуры работы с наборами данных}

{добавление}
function TFrameBase.AddRecord(): TFunctionResult;
var
 E: Exception;
begin
//попытаемся добавить запись в текущий датасет фрейма
 Result.Successful := false;
 if Assigned(FrameCurrentDataSet) then
  begin
  try
  FrameCurrentDataSet.Append;
  Result.Successful := true;
  except on E:Exception do
    Result.MessageOnError := E.Message;
  end;
  end;
end;

{редактирование}
function TFrameBase.EditRecord(): TFunctionResult;
var
 E: Exception;
begin
//попытаемся изменить запись в текущем датасете фрейма
 Result.Successful := false;
 if Assigned(FrameCurrentDataSet)then
  begin
  try
  FrameCurrentDataSet.Edit;
  Result.Successful := true;
  except on E:Exception do
    Result.MessageOnError := E.Message;
  end;
  end;
end;

{удаление}
function TFrameBase.DeleteRecord(): TFunctionResult;
var
 E: Exception;
begin
//попытаемся удалить запись из текущего датасета фрейма
 Result.Successful := false;
 if Assigned(FrameCurrentDataSet) then
  begin
  try
  FrameCurrentDataSet.Delete;
  Result.Successful := true;
  except on E:Exception do
    Result.MessageOnError := E.Message;
  end;
  end;
end;


{Сохранить}
function TFrameBase.PostRecord(): TFunctionResult;
var
 E: Exception;
begin
//попытаемся послать Post
 Result.Successful := false;
 if Assigned(FrameCurrentDataSet) then
  begin
  try
  FrameCurrentDataSet.Post;
  Result.Successful := true;
  except on E:Exception do
    Result.MessageOnError := E.Message;
  end;
  end;
end;


{Отменить}
function TFrameBase.CancelRecord(): TFunctionResult;
var
 E: Exception;
begin
//попытаемся послать Cancel
 Result.Successful := false;
 if Assigned(FrameCurrentDataSet) then
  begin
  try
  FrameCurrentDataSet.Cancel;
  Result.Successful := true;
  except on E:Exception do
    Result.MessageOnError := E.Message;
  end;
  end;
end;


{Открыть свойства}
function TFrameBase.GetProperty(): boolean;
begin
  Result := false;
   if Assigned(FrameCurrentDataSet) then
     if not FrameCurrentDataSet.IsEmpty then
      Result := true;
end;

{функции экспорта в различные форматы}
function TFrameBase.ExportData(Parameter: string): TFunctionResult;
var
 FResult: TFunctionResult;
begin
  Result.Successful := False;
  Result.MessageOnError := 'Not Save';
//если передан параметр AsIs
//то сохранить текущий грид в xls
  if (Parameter = 'AsIs') then
  begin
    with TSaveDialog.Create(Self) do
    try
      begin
        Filter := 'Файли гіпертексту|*.htm';
        Title := 'Вкажіть назву файлу';
        DefaultExt := 'htm';
        Options := Options + [ofPathMustExist];
        if Execute then
        begin
          FResult := TMyDBGrid(GetMainGrid).SaveToHTML(FileName, false);
          Result := FResult;
          ShellExecute(Self.Handle, 'open', PChar(FileName), nil, nil, SW_SHOW);
        end
      end;
    finally
      Free;
    end;
  end;
end;

function TFrameBase.ImportData(Parameter: string): TFunctionResult;
begin
  Result.Successful := False;
  Result.MessageOnError := 'Not implemented method';
end;

{Комментарий: функции записи/чтения заголовка фрейма}
function TFrameBase.GetDesc: string;
begin
  Result := FrameDescription
end;

procedure TFrameBase.SetDesc(Description: string);
begin
  FrameDescription := Description
end;

procedure TFrameBase.GotoNextElement;
begin
   if Assigned(FrameCurrentDataSet) then
    if not FrameCurrentDataSet.Eof then
     FrameCurrentDataSet.Next;
end;

procedure TFrameBase.GotoPrevElement;
begin
   if Assigned(FrameCurrentDataSet) then
    if not FrameCurrentDataSet.Bof then
     FrameCurrentDataSet.Prior;
end;

procedure TFrameBase.GotoFirstElement;
begin
   if Assigned(FrameCurrentDataSet) then
    if not FrameCurrentDataSet.Bof then
     FrameCurrentDataSet.First;
end;

procedure TFrameBase.GotoLastElement;
begin
   if Assigned(FrameCurrentDataSet) then
    if not FrameCurrentDataSet.Eof then
     FrameCurrentDataSet.Last;
end;


procedure TFrameBase.SetLoadingData(LD: boolean);
begin
  LoadingData := LD;
end;

function TFrameBase.GetLoadingData: boolean;
begin
  Result := LoadingData;
end;

function TFrameBase.GetMainGrid: TDBGrid;
begin
 Result := nil;
end;

procedure TFrameBase.SetMakeLongTimeWork (LTW: boolean);
begin
  MakeLongTimeWork := LTW;
end;

function TFrameBase.GetMakeLongTimeWork : boolean;
begin
  Result := MakeLongTimeWork;
end;

{функции возвращающие текущий источник данных}
procedure TFrameBase.SetFrameCurrentDataSet(DS: TDataSet);
begin
  FrameCurrentDataSet := DS;
end;

function TFrameBase.GetFrameCurrentDataSet: TDataSet;
begin
  Result := FrameCurrentDataSet;
end;


destructor TFrameBase.Destroy;
begin
  if Assigned(FrameCurrentDataSet) then
   FrameCurrentDataSet.Active := false;
  inherited Destroy;
end;

{процедуры записи/чтения настроек фрейма в файлы}
function TFrameBase.SaveFrameToFile(FileName: TFileName): TFunctionResult;
var
  E: Exception;
  ms: TMemoryStream;
  fs: TFileStream;
begin
  try
  fs := TFileStream.Create(FileName, fmCreate or fmOpenWrite);
  ms := TMemoryStream.Create;
  try
    ms.WriteComponent(self);
    ms.Seek(0, soFromBeginning);
    ObjectBinaryToText(ms, fs);
  finally
    ms.Free;
    fs.free;
  end;
  Result.Successful := true;
  Except on E:Exception do
   begin
    Result.Successful := false;
    Result.MessageOnError := E.Message;
   end;
  end;
end;

function TFrameBase.LoadFromFileToFrame(FileName: TFileName): TFunctionResult;
var
 ComponentIdx: integer;
  ms: TMemoryStream;
  fs: TFileStream;
begin
//уничтожим все существующие на фрейме компоненты
//чтобы не было конфликтов
 for ComponentIdx := self.ComponentCount-1 downto 0 do
  self.Components[ComponentIdx].Free;
 try
//загрузим фрейм из файла
  ms := TMemoryStream.Create;
  fs := TFileStream.Create(FileName, fmOpenRead);
  try
    ObjectTextToBinary(fs, ms);
    ms.Seek(0, soFromBeginning);
    ms.ReadComponent(self);
  finally
    ms.Free;
    fs.free;
  end;
  Result.Successful := true;
 except on E:Exception do
  begin
   Result.Successful := false;
   Result.MessageOnError := E.Message;
  end;
 end;
end;
end.
Как можно заметить, многие функции базового фрейма возвращают значение типа TFunctionResult. Эта структура определена в модуле UnitConstTypes_etc, в который в будущем будут добавляться другие типы, константы. Функции возвращают флаг успешного завершения операции, а случае возникновения ошибки – текст сообщения об ошибке.

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

Кроме того, в процедуре экспорта данных вызывается метод SaveToHTML текущего грида фрейма. Этот метод определён в модуле MyDBGrid.

Перейдем к отображению фреймов на главной форме приложения.

Главные формы приложений у меня имеют примерно такой вид: слева – дерево меню, внизу лог приложения, сверху – панель инструментов, остальное пространство пустое, его занимает панель, на которой будут отображаться фреймы.

Сначала создадим свой тип
 
type
  TFrameClass = class of TFrameBase;
  Теперь нужно создать правильно работающую главную форму. В uses добавим использование модуля UnitFrameBase, в раздел public внесем объект MainFrame класса TFrameBase. Теперь нужно написать функцию, которая будет корректно отображать нужный фрейм при открытии нужного режима.

function TFormMain.ProcShowFrame(FrameClassName: AnsiString; ParentPanel: TWinControl): TFunctionResult;
var
  FrameClass: TClass;
  FunctionResult: TFunctionResult;
  E: Exception;
begin
  Result.Successful := False;
  FrameClass := GetClass(FrameClassName);
  if FrameClass = nil then //если такой тип фрейма незарегистрирован
   begin
    Result.MessageOnError := Format('Class %s not registered',[FrameClassName]);
    Exit;
   end;
  //запретить прорисовку контейнера фреймов
  try
    begin
      LockWindowUpdate(ParentPanel.Handle); // не будем перерисовывать подложку, чтобы не было мерцаний
  //если фрейм не пуст, очистим его
      if Assigned(MainFrame) then
        if MainFrame.ClassType = FrameClass then
        begin
          Result.Successful := true;
          Exit; //если мы пытамся пересоздать текущий фрейм ним же, то выход
        end
        else
          begin
           FunctionResult := MainFrame.SaveFrameToFile(Format('%s.dat',[MainFrame.ClassName]));
           if not FunctionResult.Successful then
              ListBoxLog.Items.Add('Error on Save Frame: '+FunctionResult.MessageOnError);
           MainFrame.Destroy;
          end;
  //создать фрейм по указанному типу
      try
        MainFrame := TFrameClass(FrameClass).Create(FormMain);
        if FileExists(Format('%s.dat',[MainFrame.ClassName])) then
         begin
        FunctionResult := MainFrame.LoadFromFileToFrame(Format('%s.dat',[MainFrame.ClassName]));
        if not FunctionResult.Successful then
         ListBoxLog.Items.Add('Error on Load Frame: '+FunctionResult.MessageOnError);
       end;
      except on E:Exception do
        begin
          Result.MessageOnError := E.Message;
          MainFrame := nil;
          Exit;
        end;
      end;
      MainFrame.Parent := ParentPanel;
      MainFrame.Align := alClient;
    end;
  finally
    LockWindowUpdate(0); //разрешить прорисовку контейнера фреймов
  end;
  Result.Successful := true;
end;
Как же вызвать данную функцию? Создадим дополнительно сообщение

const FILTER_EVENT = WM_USER + 101, – для вызова процедур фильтрации. Param1 и Param2 используются для формирования нужных запросов в однотипных фреймах. Теперь нужно написать обработчик сообщения FILTER_EVENT.
procedure TFormMain.CX_FILTER(var Msg: TMessage);
begin
  if Assigned(MainFrame) and (not FormMain.isShutdown) then
  begin
    MainFrame.Filter(Msg.wParam, Msg.LParam);
    MainFrame.ShowAdditionalInformation;
  end;
end;
Перейдем к отображению меню фреймов. В качестве источника хранения структуры древовидного меню можно использовать xml файл (это удобно, если приложение не использует БД), таблицу в используемой в приложении БД или же хранить структуру прямо в приложении (очень удобно это делать в dxTreeList от DevExpress, однако эти компоненты платные). Так как во всех моих приложениях используются базы данных (в основном СУБД Oracle или Firebird), то я выбрал второй вариант. Создадим таблицу следующей структуры
CREATE TABLE TECH_APP_MENU (
    MENU_ID       IDENTIFIER NOT NULL,
    ITEM_TYPE     VARCHAR(10) DEFAULT 'item' NOT NULL,
    ITEM_CAPTION  VARCHAR(32) NOT NULL,
    FRAME_NAME    VARCHAR(32) DEFAULT 'TFrameUnknown' NOT NULL,
    PARAM1        NONIDENTIFIER,
    PARAM2        NONIDENTIFIER,
    PARENT_ID     NONIDENTIFIER NOT NULL,
    ITEM_ICON     NONIDENTIFIER
);
Кроме того, для обеспечения целостности дерева, с таблицей связано несколько триггеров и ограничений. Полностью структуру таблицы и тексты ограничений вы можете помотреть в исходном тексте БД.

Из этой реляционной таблицы довольно легко создать дерево с помощью рекурсивной процедуры, текст которой вы также можете посмотреть в исходном тексте БД. Заметим, что принципы создания такой процедуры взяты из книги “Мир InterBase”.

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

Процедура отображения дерева приведена ниже. Сначала определим структуру элементов меню
type TMenuNodes = record
    MENU_ID: integer;
    ITEM_TYPE: WideString;
    ITEM_CAPTION:  WideString;
    FRAME_NAME:    WideString;
    PARAM1: integer;
    PARAM2: integer;
    PARENT_ID: integer;
    LEVEL: integer;
    ISLEAF: boolean;
    ParentNode: TTreeNode;
   end;
PTMenuNodes = ^TMenuNodes;
Естественно, что структура элемента меню совпадает со структурой таблицы в БД. Создадим меню следующей функцией
procedure TFormMain.GenerateMenu(Tree: TTreeView);
var
  vData : PTMenuNodes;
  Node, LastNode : TTreeNode;
begin
 try
 LockWindowUpdate(Tree.Handle);
 SendMessage(Tree.Handle, TVM_DELETEITEM, 0, Longint(TVI_ROOT)); //очистим дерево
 if not DataModuleMain.DBMain.Connected then
  Exit; 
 with DataModuleMain.GET_MENU do
  begin
   LastNode := nil;
   Active := True;
   First;
   while not Eof do
    begin
     New(vData);
      with vData^ do
       begin
     MENU_ID := FieldByName('MEM_ID').AsInteger;
     ITEM_TYPE := FieldByName('ITEM_TYPE').AsString;
     ITEM_CAPTION := FieldByName('ITEM_CAPTION').AsString;
     FRAME_NAME := FieldByName('FRAME_NAME').AsString;
     PARAM1 := FieldByName('PARAM1').AsInteger;
     PARAM2 := FieldByName('PARAM2').AsInteger;
     PARENT_ID := FieldByName('MEM_PID').AsInteger;
     LEVEL := FieldByName('OUTLEVEL').AsInteger;
     ISLEAF := boolean(FieldByName('IS_LEAF').AsInteger);
      end;
     if vData.LEVEL = 1 then
      begin
     Node := TreeViewMenu.Items.Add(nil,vData^.ITEM_CAPTION);
     vData.ParentNode := nil;
      end
       else if PTMenuNodes(LastNode.Data)^.LEVEL<vData.LEVEL then
      begin
     Node := TreeViewMenu.Items.AddChild(LastNode,vData^.ITEM_CAPTION);
     vData.ParentNode := LastNode;
      end
       else if PTMenuNodes(LastNode.Data)^.LEVEL=vData.LEVEL then
      begin
     Node := TreeViewMenu.Items.AddChild(LastNode.Parent,vData^.ITEM_CAPTION);
     vData.ParentNode := LastNode.Parent;
      end
       else if PTMenuNodes(LastNode.Data)^.LEVEL>vData.LEVEL then
      begin
     while PTMenuNodes(LastNode.Data)^.LEVEL>=vData.LEVEL do
      LastNode := LastNode.Parent;
     Node := TreeViewMenu.Items.AddChild(LastNode,vData^.ITEM_CAPTION);
     vData.ParentNode := LastNode.Parent;
      end;
{здесь компилятор выдает сообщение, что Node может быть неинициализированной.
 Однако при используемой в программе схеме хранения данных
 данная переменная обязательно будет инициализирована}
     Node.Data := vData;
     Node.ImageIndex := FieldByName('ITEM_ICON').AsInteger;
     Node.SelectedIndex := FieldByName('ITEM_ICON').AsInteger;
     LastNode := Node;
     Next;
    end;
  end;
  finally
   LockWindowUpdate(0);
  end;
end;
Обработчик изменения элемента в меню будет иметь следующий вид
procedure TFormMain.TreeViewMenuChange(Sender: TObject; Node: TTreeNode);
var
  vData : PTMenuNodes;
  CurrentNodeIcon: TIcon;
  FunctionResult: TFunctionResult;
begin
 vData := Node.Data;
 FunctionResult := ProcShowFrame(vData^.FRAME_NAME,PanelFrame);
 if FunctionResult.Successful then
   begin
  MainFrame.InitialisationFrame(vData^.PARAM1,vData^.PARAM2);
  Caption := Format('%s - %s',[Application.Title,MainFrame.FrameDesc]);
  CurrentNodeIcon := TIcon.Create;
  ImageListApp.GetIcon(Node.SelectedIndex,CurrentNodeIcon);
  FormMain.Icon := CurrentNodeIcon;
  CurrentNodeIcon.Free;
   end
   else
    ListBoxLog.Items.Add(Format('Error on show frame %s: %s',[vData^.FRAME_NAME,FunctionResult.MessageOnError]));
end;
Создадим панель инструментов с кнопками, которые будут выполнять нужные функции. Как мы определили, обязательно нужны функции добавления, изменения, удаления, просмотра, экспорта в Excel и других отчетов, импорта из внешних источников. Код обработчиков нажатия на эти кнопки будет максимально прост. Например код для добавления
procedure TFormMain.ToolButtonAddClick(Sender: TObject);
var
 FResult: TFunctionResult;
begin
 if Assigned(MainFrame) then
  begin
  FResult := MainFrame.AddRecord;
  if not FResult.Successful then
   ListBoxLog.Items.Add('Error on Add: '+FResult.MessageOnError);
  end;
end;
Аналогично и для других. Кроме того, в главной форме нужно еще создать общие элементы для фильтрации. Это могут быть элементы для фильтрации по датам, по рассчетным счетам и т.д. Однако в обработчике изменения по этим элементам нужно всего-лишь послать сообщение CX_FILTER. Как обработать полученное сообщение, будет решать конкретный фрейм.

Кроме вышеперчисленного, в главной форме нужно будет зарегистрировать все типы фреймов. Делается это в секции initialization функцией RegisterClasses.

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

В простейшем случае нужно переопределить конструктор фрейма, к котором переопределить описание фрейма, вызвав SetDesc и определить текущий датасет фрейма, вызвав SetFrameCurrentDataSet. Если на фрейме есть грид, то нужно переопределить функцию GetMainGrid, чтобы она возвращала нужный.

Таким образом для добавления нового режима в простейшем случае нужно прописать всего 9 строчек кода!

Однако его изменение (улучшение) фрейма абсолютно не приведет к никаким правкам главной формы. Функции работы с данными вызывают всего лишь методы соответствующих DataSet. В обработчике которых могут вызываться дополнительные модальные формы или диалоги.

Что дает такой подход? Написав каркас приложения в самом начале разработки проекта, далее все изменения (при появлении новых требований) сводится к включению нового фрейма (унаследованного от базового фрейма), добавлении записи в таблице меню (можно, кстати, создать визуальный редактор этой таблицы) и регистрации типа в в процедуре RegisterClasses. И все! Таким образом режимы могут разрабатываться разными разработчиками, которым не нужно согласовывать свои стили программирования (хотя, все же, это желательно) – все нужные функции определены, нужно их только переопределить и наполнить необходимым содержанием.

Вот и всё, Удачи!

iprotechno.net
Источник: www.thedelphi.ru
Автор: Савельев Александр
Опубликовано: 29 Декабря 2014
Просмотров:


Зарегистрируйтесь или авторизуйтесь, чтобы добавлять комментарии.