(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Об одном методе создания мастеров

Источник: delphiplus
Василий Кругаль

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

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

В качестве реализации страницы я использовал класс TFrame (кадр), т.к. TFrame может быть использован повторно. Таким образом, набор страниц мастера превращается в набор кадров, и тогда навигация превращается в воспроизведение этого набора кадров.

Каждый кадр в наборе должен обеспечить три основные функции:

  1. Ввод некоторого набора данных пользователя
  2. Поддержку механизма навигации
  3. Сбор и сохранение данных, введенных пользователем

Первая функция реализуется в каждом конкретном случае индивидуально, т.к. довольно сложно придумать некую простую единую реализацию для всех видов кадров.

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

 
unit wizardFrameIntf;

interface
uses Classes, XMLIntf;

type  IWizardFrameIntf = interface
     // Подключение кадра. Вызывается мастером при открытии.
     // Метод может быть использован кадром для восстановления
     // настроек, установления значений
     // по умолчанию и т.п.
     procedure connect();

     // Отключение кадра. Вызывается мастером при закрытии.
     // Метод может быть использован кадром для сохранения настроек.
     procedure disconnect();

     // Определение наименования кадра. Вызывается мастером при открытии
     // кадра для определения заголовка окна мастера для открываемого кадра.
     function  getCaption():String;

     // Обработка события Idle приложения. Вызывается всякий раз, когда
     // приложение переходит в состояние idle. Метод может быть использован
     // для определения текущего состояния кадра.
     procedure idle();

     // Обработка события установки фокуса в кадре. Вызывается мастером при
     // открытии кадра. Метод может быть использован для установления
     // начального фокуса ввода в кадре.
     procedure setFrameFocus();

     // Проверка возможности перехода к следующему кадру. Вызывается мастером
     // при попытке пользователя перейти на следующий кадр. Метод может быть
     // использован для проверки правильности ввода данных.
     function  canGoNext():Boolean;

     // Сбор (сериализация) данных кадра. Вызывается мастером при завершении
     // ввода данных и формировании XML документа, содержащего результат
     // работы мастера. Метод должен быть использован для записи результата
     // ввода данных кадра в виде дочерних элементов элемента inode.
     procedure serialize(inode:IXMLNode);
  end;

Последняя процедура (serialize) реализует третью функцию - механизм сбора и сохранения данных, введенных пользователем. В качестве формата хранения данных я использовал XML документ как наиболее подходящий с моей точки зрения. Таким образом, каждый кадр оформляется в виде наследника класса TFrame, реализующего интерфейс IwizardFrameIntf. Вот пример реализации кадра для ввода личных данных (фамилия, имя, отчество и адрес электронной почты):

 
unit nameWizardFrameUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  XMLIntf, Dialogs, ComCtrls, ExtCtrls, StdCtrls, wizardFrameIntf;

const
 _NODE_NAME_             = 'name';         // имя тэга в итоговом XML документе
                                            // для хранения личных данных
 _CAPTION_               = 'Личные данные'; // заголовок кадра
 _NOT_ALL_DATA_TYPED_IN_ = 'Поля "Фамилия" и/или "Имя" должны быть заданы';

type
  TNameWizardFrame = class(TFrame,IWizardFrameIntf)
    pan: TPanel;
    lFirst: TLabel;
    lLast: TLabel;
    lMiddle: TLabel;
    lEmail: TLabel;
    email: TEdit;
    middle: TEdit;
    last: TEdit;
    first: TEdit;
  private
    // методы интерфейса IWizardFrameIntf
    procedure idle();
    function  getCaption():String;
    procedure connect();
    procedure disconnect();
    procedure serialize(inode:IXMLNode);
    procedure setFrameFocus();
    function  canGoNext():Boolean;

  public

  end;

implementation

{$R *.dfm}
// подключение кадра
procedure TNameWizardFrame.connect();
begin
  // здесь можно вставить код для установки значений по умолчанию, 
  // восстановления значений, введенных пользователем при предыдущем
  // вызове мастера и т.п.
end;
// отключение кадра
procedure TNameWizardFrame.disconnect();
begin
  // здесь можно вставить код для сохранения 
  // значений, введенных пользователем
end;
// определение наименования кадра
function TNameWizardFrame.getCaption():String;
begin
  result:= _CAPTION_;
end;
// обработка события Idle приложения
procedure TNameWizardFrame.idle();
begin
  // здесь можно вставить код для определения
  // состояния элементов кадра и его отображения
end;
// обработка события установки фокуса в кадре
procedure TNameWizardFrame.setFrameFocus();
begin
  first.SetFocus();
end;
// проверка возможности перехода к следующему кадру
function TNameWizardFrame.canGoNext():Boolean;
begin
  result:= true;
  // пользователь должен ввести фамилию и имя => проверим, так ли это
  if ((first.text = '') or (last.text = '')) then
  begin
     showMessage(_NOT_ALL_DATA_TYPED_IN_);
     if (first.text = '') then first.SetFocus()
     else
     if (last.text = '') then last.SetFocus();
     result:= false;
  end;
end;
// сериализация данных кадра
procedure TNameWizardFrame.serialize(inode:IXMLNode);
var
  node:IXMLNode;
begin
  // сохраним значения, введенные пользователем в виде XML фрагмента
  if (inode <> nil) then
  begin
     node:= inode.AddChild(_NODE_NAME_,'');
     with node.AddChild(first.name,'')  do NodeValue:= first.Text;
     with node.AddChild(last.name,'')   do NodeValue:= last.Text;
     with node.AddChild(middle.name,'') do NodeValue:= middle.Text;
     with node.AddChild(email.name,'')  do NodeValue:= email.Text;
  end;
end;

end.

Теперь осталось реализовать механизм проигрывания (навигации) кадров, который я выполнил в виде отдельной формы:

 
unit wizardPlayerFormUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
  Forms, Dialogs, ExtCtrls, StdCtrls, wizardFrameIntf, AppEvnts, 
  Contnrs, XMLIntf, XMLDoc;

Const
  // текст подтверждения закрытия мастера
  _CONFIRM_CLOSE_   = 'Вы действительно хотите завершить?';

  // текст подтверждения создания объекта мастера
  _WIZARD_FINISHED_ = 'Сбор данных завершен. Создать ';

  // 
  _NEXT_            = 'Вперед';
  _READY_           = 'Готово';

  // заготовка XML документа с данными пользователя
  _RESULT_XML_      = '<?xml version="1.0" encoding="windows-1251"?>';

type
  TFrameWizardClass = class of TFrame;
  TWizardPlayerForm = class(TForm)
    pBtn: TPanel;
    btnCancel: TButton;
    btnNext: TButton;
    btnBack: TButton;
    ApplicationEvents: TApplicationEvents;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure ApplicationEventsIdle(Sender: TObject; var Done: Boolean);
    procedure btnBackClick(Sender: TObject);
    procedure btnNextClick(Sender: TObject);
    procedure btnCancelClick(Sender: TObject);
  private
    FCurrentFrame:TFrame;          // текущий кадр
    FCurrentFrameIndex:Integer;    // индекс текущего кадра в списке
    FFrameList:TObjectList;        // список кадров
    FResultXml: IXMLDocument;      // XML документ. Содержит результат 
                                   // выполнения мастера
    FObjectTitle:String;           // наименование объекта, данные о котором
                                   // собираются с помощью мастера

    // формирование XML документа
    procedure serialize();
    // переход на кадр с заданным индексом
    function  gotoFrame(index:Integer):TFrame;
    // чтение документа в виде строки
    function  getResultXml():WideString;

  public
    // регистрация (добавление) кадра в список кадров мастера
    function addFrame(frameClass: TFrameWizardClass):TFrame;
    // наименование объекта, данные о котором собираются с помощью мастера
    property objectTitle:String read FObjectTitle write FObjectTitle;
    // результирующий XML документ в виде строки
    property resultXml:WideString read getResultXml;
  end;

implementation

{$R *.dfm}

procedure TWizardPlayerForm.FormCreate(Sender: TObject);
begin
  FCurrentFrame:= nil;
  FCurrentFrameIndex:= -1;
  // создание списка кадров мастера
  FFrameList:= TObjectList.Create();
  FFrameList.OwnsObjects:= true;

  // создание заготовки документа
  FResultXml:= LoadXMLData(_RESULT_XML_);
  FResultXml.Options:= [doNodeAutoIndent];
  FResultXml.ParseOptions:= [];
  FResultXml.Active:= true;
end;

procedure TWizardPlayerForm.FormDestroy(Sender: TObject);
begin
  FResultXml:= nil;
  freeAndNil(FFrameList);
end;

procedure TWizardPlayerForm.FormShow(Sender: TObject);
var
  i:Integer;
  wizardFrameIntf:IWizardFrameIntf;
  frame:TFrame;
begin
  // подключение кадров из списка
  for i:= 0 to FFrameList.Count - 1 do
  begin
     frame:= TFrame(FFrameList.Items[i]);
     wizardFrameIntf:= frame as IWizardFrameIntf;
     wizardFrameIntf.connect();
  end;

  FCurrentFrame:= gotoFrame(FCurrentFrameIndex);
  modalResult:= mrCancel;
end;

procedure TWizardPlayerForm.FormClose(Sender: TObject; 
                                      var Action: TCloseAction);
var
  i:Integer;
  wizardFrameIntf:IWizardFrameIntf;
  frame:TFrame;
begin
  // отключение кадров из списка
  for i:= 0 to FFrameList.Count - 1 do
  begin
     frame:= TFrame(FFrameList.Items[i]);
     wizardFrameIntf:= frame as IWizardFrameIntf;
     wizardFrameIntf.disconnect();
  end;
end;

procedure TWizardPlayerForm.FormCloseQuery(Sender: TObject;
                                           var CanClose: Boolean);
var
  msg:String;
begin
  CanClose:= false;
  msg:= _CONFIRM_CLOSE_;
  if (MessageDlg(msg, mtConfirmation,[mbYes,mbNo],0)= mrYes) then 
    CanClose:= true;
end;

function TWizardPlayerForm.getResultXml():WideString;
begin
  result:= FResultXml.xml.text;
end;

procedure TWizardPlayerForm.ApplicationEventsIdle(Sender: TObject;
                                                  var Done: Boolean);
var
  i:Integer;
  wizardFrameIntf:IWizardFrameIntf;
  frame:TFrame;
begin
  // определение состояния кнопки "Назад"
  btnBack.Enabled:= (FCurrentFrameIndex > 0);

  // обработка события Idle в кадрах
  for i:= 0 to FFrameList.Count - 1 do
  begin
     frame:= TFrame(FFrameList.Items[i]);
     wizardFrameIntf:= frame as IWizardFrameIntf;
     wizardFrameIntf.idle();
  end;
end;

procedure TWizardPlayerForm.btnBackClick(Sender: TObject);
begin
  // переход на предыдущий кадр
  FCurrentFrame.Visible:= false;
  FCurrentFrameIndex:= FCurrentFrameIndex - 1;
  FCurrentFrame:= gotoFrame(FCurrentFrameIndex);
  btnNext.Caption:= _NEXT_;
end;

procedure TWizardPlayerForm.btnNextClick(Sender: TObject);
var
  msg:String;
begin
  // переход на следущий кадр
  // можно ли перейти на следующий кадр?
  if ((FCurrentFrame as IWizardFrameIntf).canGoNext() = true) then
  // да
  begin
     // текущий кадр - последний?
     if (FCurrentFrameIndex < FFrameList.Count - 1) then
     // нет => перейдем на следующий
     begin
        FCurrentFrame.Visible:= false;
        FCurrentFrameIndex:= FCurrentFrameIndex + 1;
        FCurrentFrame:= gotoFrame(FCurrentFrameIndex);
        // если кадр, на который мы перешли последний, то
        // показать кнопку "Готово"
        if (FCurrentFrameIndex = FFrameList.Count - 1) then
           btnNext.Caption:= _READY_;
     end
     else
     // да => выполнить сбор введенных данных и завершить работу 
     begin
        msg:= _WIZARD_FINISHED_+' '+FObjectTitle+'?';
        if (MessageDlg(msg, mtConfirmation,[mbYes,mbNo],0)= mrYes) then
        begin
           serialize();
           self.OnCloseQuery:= nil;
           close();
           modalResult:= mrOk;
        end;
     end;
  end;
end;

procedure TWizardPlayerForm.btnCancelClick(Sender: TObject);
begin
  close();
end;

function TWizardPlayerForm.gotoFrame(index:Integer):TFrame;
var
  wizardFrameIntf:IWizardFrameIntf;
begin
  // переход на кадр с указанным индексом
  result:= TFrame(FFrameList.Items[index]);
  wizardFrameIntf:= result as IWizardFrameIntf;
  result.Visible:= true;
  result.SetFocus();
  self.Caption:= wizardFrameIntf.getCaption();
  wizardFrameIntf.setFrameFocus();
end;

function TWizardPlayerForm.addFrame(FrameClass: TFrameWizardClass):TFrame;
var
  index:Integer;
begin
  // регистрация (добавление) кадра в мастере
  result:= FrameClass.Create(nil);
  result.Visible:= false;
  result.Parent:= self;
  result.Align:= alClient;

  index:= FFrameList.Add(result);
  if (FCurrentFrameIndex = -1) then FCurrentFrameIndex:= index;
end;

procedure TWizardPlayerForm.serialize();
var
  i:Integer;
  wizardFrameIntf:IWizardFrameIntf;
  frame:TFrame;
begin
  // сбор введенных данных по каждому кадру
  for i:= 0 to FFrameList.Count - 1 do
  begin
     frame:= TFrame(FFrameList.Items[i]);
     wizardFrameIntf:= frame as IWizardFrameIntf;
     wizardFrameIntf.serialize(FResultXml.DocumentElement);
  end;
end;

end.

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

 
  with TWizardPlayerForm.Create(nil) do
  begin
     // определим наименование объекта -
     // результата работы мастера (здесь - Контакт)
     objectTitle:= 'Контакт';
     try
        // Добавим кадр "личные данные"
        addFrame(TNameWizardFrame);

        // Добавим кадр "адрес"
        addFrame(THomeWizardFrame);

        // Добавим кадр "Данные о работе"
        addFrame(TWorkWizardFrame);

           . . .

        // Создадим новый контакт с помощью нашего мастера
        if (showModal() = mrOk) then
        begin
           // отобразим результат выполнения мастера
           showMessage(resultXml);
        end;
     finally
        free();
     end;
  end;

Ссылки по теме

Файлы для загрузки


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 04.06.2009 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Delphi Professional Named User
Enterprise Connectors (1 Year term)
ABViewer Professional пользовательская
Stimulsoft Reports.Ultimate Single License Includes one year subscription
IBM Domino Utility Server Processor Value Unit (PVU) License + SW Subscription & Support 12 Months
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
СУБД Oracle "с нуля"
Новые материалы
Каждый день новые драйверы для вашего компьютера!
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100