Практика
Автоматизация процесса создания дочерних форм

:: Меню ::
:: На главную ::
:: FAQ ::
:: Заметки ::
:: Практика ::
:: Win API ::
:: Проекты ::
:: Скачать ::
:: Секреты ::
:: Ссылки ::

:: Сервис ::
:: Написать ::

:: MVP ::

:: RSS ::

Яндекс.Метрика


Как часто разработчику в своих приложениях приходится создавать формы? Постоянно! И чем крупнее проект, тем больше форм нужно создавать. Как следствие бесконечно множится однотипный код вроде этого:

uses
  {...,} Unit2;

implementation

procedure TForm1.btnCreateFormClick(Sender: TObject);
var
  f: TForm2;
begin
   f := TForm2.Create(Application.MainForm);
   f.Show;
end;

// или

procedure TForm1.btnCreateFormClick(Sender: TObject);
var
  f: TForm2;
begin
   Application.CreateForm(TForm2, f);
   f.Show;
end;

Пора избавиться от этого пережитка, только лишь засоряющего исходные коды!

Решать эту задачу будем путем создания экшена, автоматизирующего процесс создания дочерних форм. Создавать экземпляры форм экшен будет на основе имени класса (которое, в нашем случае, будет храниться в свойстве FormClassName). Но чтобы это стало возможным, классы форм нужно регистрировать в потоковой системе по средствам вызова метода RegisterClass.

initialization
  RegisterClass(TForm2);

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

function TRunForm.Execute: Boolean;
var
  ClassForm: TPersistentClass;
  Form: TComponentClass;
begin
   {...}

   if FSilent then
      ClassForm := GetClass(FFormClassName)
   else
      ClassForm := FindClass(FFormClassName);

   if Assigned(ClassForm) then
   begin
      Application.CreateForm(TComponentClass(ClassForm), FLastCreateForm);
      {...}
   end;

   {...}
end;

Однако мало просто создавать формы, не плохо бы еще и управлять параметрами этого процесса. К примеру - параметр ShowModal будет отвечать за создание формы в модальном или не модальном режиме. Если форма создается в не модальном режиме, можно подумать о ее "отвязке" от приложения (свойство FreeForm) и добавить ей собственную иконку на Taskbar (свойство AppWindow). "Отвязка" от приложения осуществляется за счет смены родителя дочерней на рабочий стол Windows. Это дает нам следующее:
  • главная форма приложения может подниматься выше дочерней по z-order, дочерняя форма не будет сворачиваться;
  • при сворачивании главной формы приложения дочерняя форма не будет сворачиваться.
Параметр AppWindow обрабатывается совместно с параметром FreeForm для корректного переключения между формами в RunTime. Если применить AppWindow к "не отвязанной" форме, то при переключении между основной и дочерней формой через Taskbar, активироваться всегда будет дочерняя форма.

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

procedure TForm1.sbCreateFormClick(Sender: TObject);
begin
   RunForm1.FormCaption := edCaption.Text;
   RunForm1.Execute;
end;

Но если экшен назначен кнопке в дизайнере через соответствующее свойство Action, лучшего места для инициализации параметров, чем событие OnBeforeExecute, просто не найти. Также это очень подходящее место для предотвращения выполнения экшена в случае невыполнения каких либо условий. После создания формы может потребоваться сохранить ссылку на нее для последующих манипуляций (ссылка на только что созданный (последний) экземпляр формы хранится в свойстве LastCreateForm). Для этого идеально подойдет событие OnAfterExecute.

Также неплохо было бы иметь возможность определять, ограничивать ли количество экземпляров формы одной копией или нет (для этого предусмотрим свойство OneExemplar). Возможность редактирование этого свойства будет доступна только в дизайнере (во время разработки), это нужно для предотвращения неоднозначных ситуаций. Когда количество экземпляров ограничено, попытка создания второго экземпляра формы приведет к тому, что будет активирован первый (ранее созданный) экземпляр. А теперь допустим что мы не установили это ограничение в дизайнере. Запустили приложение и создали несколько экземпляров формы. Затем установили ограничение и попытались создать еще одну форму. По логике работы компонента новый экземпляр не должен быть создан, вместо этого нужно активировать уже существующий экземпляр. Но какой экземпляр нужно активировать, если их несколько?

Все сказанное выше в коде выглядит примерно так:

function TRunForm.Execute: Boolean;

  function IsFormExist(f: TComponentClass): TComponentClass;
  var
    i: Integer;
  begin
     Result := nil;
     for i := 0 to Application.ComponentCount-1 do
        if Application.Components[i] is f then
           Exit(TComponentClass(Application.Components[i]));
  end;

  procedure Show(f: TComponentClass);
  begin
     if not FShowModal then
     begin
        if FFreeForm then
        begin
           SetWindowLongPtr(TForm(f).Handle, GWLP_HWNDPARENT, GetDesktopWindow);

           if FAppWindow then
              SetWindowLongPtr(TForm(f).Handle, GWL_EXSTYLE,
                               GetWindowLongPtr(TForm(f).Handle, GWL_EXSTYLE) or WS_EX_APPWINDOW)
           else
              SetWindowLongPtr(TForm(f).Handle, GWL_EXSTYLE,
                               GetWindowLongPtr(TForm(f).Handle, GWL_EXSTYLE) and not WS_EX_APPWINDOW);
        end;
        
        TForm(f).Show;
     end
     else
        TForm(f).ShowModal;
  end;

  procedure Run(f: TComponentClass);
  begin
     Application.CreateForm(f, FLastCreateForm);
     if Trim(FFormCaption) <> '' then
        TForm(FLastCreateForm).Caption := FFormCaption;

     Show(FLastCreateForm);
  end;

var
  ClassForm: TPersistentClass;
  Form: TComponentClass;
  Cancel: Boolean;
begin
   if Assigned(FOnBeforeExecute) then
   begin
      Cancel := False;
      FOnBeforeExecute(Self, Cancel);
   end;

   if Cancel then
      Exit;

   if FSilent then
      ClassForm := GetClass(FFormClassName)
   else
      ClassForm := FindClass(FFormClassName);

   if Assigned(ClassForm) then
   begin
      Form := IsFormExist(TComponentClass(ClassForm));
      case FOneExemplar of
         True: begin
            if not Assigned(Form) then
               Run(TComponentClass(ClassForm))
            else
               Show(Form);
         end;
         False: Run(TComponentClass(ClassForm));
      end;
   end;

   Result := inherited Execute;

   if Assigned(FOnAfterExecute) then
      FOnAfterExecute(Self);
end;

Несколько слов о локализации. Как говорилось выше, вызов функции FindClass приведет к исключению, если искомый класс не зарегистрирован в потоковой системе. При этом сообщение об ошибке будет выглядеть следующим образом:

unit System.RTLConsts;

interface

resourcestring
  {...}
  SClassNotFound = 'Class %s not found';
  {...}

Я не сторонник того, чтобы в приложениях с одной локализацией (к примеру, русской) вдруг появлялись нелокализованные сообщения. Конечно у данной проблемы есть множество путей решения. Можно подавлять исключения, или в их обработчике выдавать сообщение с нужной локализацией. Можно скопировать файл с константами (в данном случае System.RTLConsts.pas) в свой проект и локализовать его. Я предпочитаю подменять строковые ресурсы во время работы приложения, примерно так:

uses
  System.RTLConsts;

procedure ChangeResourceString(PResStr: PResStringRec; PNewStr: PChar);
var
  OldProtect: DWORD;
begin
   VirtualProtect(PResStr, SizeOf(PResStr^), PAGE_EXECUTE_READWRITE, @OldProtect);
   PResStr^.Identifier := Integer(PNewStr);
   VirtualProtect(PResStr, SizeOf(PResStr^), OldProtect, @OldProtect);
end;

initialization
  ChangeResourceString(@SClassNotFound, PChar('Класс %s не зарегистрирован'));

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

На этом все, удобного вам программирования!

.: Пример к данной статье :.


При использовании материала - ссылка на сайт обязательна