:: 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 не зарегистрирован'));
|
В результате всех этих манипуляций мы получили достаточно универсальный инструмент, который берет на себя множество аспектов
создания форм и избавляет нас от необходимости многократного написания однотипного кода.
На этом все, удобного вам программирования!
.: Пример к данной статье :.
|
При использовании материала - ссылка на сайт обязательна
|
|