(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
Enterprise Connectors (1 Year term)
Delphi Professional Named User
JIRA Software Commercial (Cloud) Standard 10 Users
Zend Guard 1 Year Subscription
IBM Domino Messaging Client Access License Authorized User 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 "с нуля"
Утиль - лучший бесплатный софт для Windows
Работа в Windows и новости компании Microsoft
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100