Как создать MDI-приложения в FireMonkey

Как создать MDI-приложения в FireMonkey

Статья о том, как создать MDI-приложения в FireMonkey. Наконец то фреймворк FireMonkey, а правильнее уже говорить FMX стал более зрелым и пригодным для промышленной разработки приложений.

Типы пользовательского интерфейса

SDI (Single Document Interface)

SDI (Однодокументный интерфейс) пользователя на сегодняшний день является стандартом. Для него характерно одно главное рабочее окно приложения. Все остальные окна создаются либо в модальном режиме, либо, как сейчас модно, прилипают к какой-либо из границ главного окна. Примерами таких приложений является та же среда разработки Delphi. Тот же пакет прикладах программ MS Officce.

MDI (Multi Document Interface)

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

Примерами таких интерфейсов могут служить старая версия MS Officce 97, 1С: предприятие версии 7.7. Более новые версии уже имеют SDI-интерфейс на основе вкладок. То есть, вместо вызываемых форм из одной общей используются просто вкладки, расположенные на одном главном окне.

Вообще сейчас Microsoft отказался от использования такого типа интерфейса как MDI, считая его устаревшим, но его применение до сих пор крайне удобно, в том числе и для меня. И если вы читаете данную статью, то и для вас тоже. Отказаться отказались, но его поддержка никуда не далась. Вы можете его спроектировать как с помощью C# Windows Forms или WPF, так и с помощью Delphi VCL и FMX.

MDI-приложения в FireMonkey

Фреймворк FMX позволяет создавать действительно впечатляющие интерфейсы пользователя, подобно фреймворку WPF, который есть у C#.

Отличия у них состоят в том, что WPF является фреймворком, с помощью которого можно создавать приложения чисто под Windows. Ну то есть, это конкурент Wondows Forms и VCL. При этом WPF позволяет создавать интерфейсы любой сложности, на сколько только хватит у вас фантазии (или у вашего дизайнера). Конечно, поскольку WPF заточен под Windows, то он может использовать все ее возможности, в том числе и создавать интерфейс в стиле MDI.

FMX же такого себе позволить не может. Почему? Не потому что он хуже или менее развит, а потому, что он является кроссплатформенным. А в других операционных системах такого понятия как MDI — интерфейс не существует. Ну представьте себе Android... Ну где он там такой интерфейс. Нет его и в Linux, нет и в MacOS. Это прерогатива Windows. И, как мне кажется, она была очень удобной.

Ну фишка в том, что поскольку FMX кроссплатформенный фреймворк, то нет в нем возможности создавать MDI-приложения для Windows. Не зная, почему не реализовали такую возможность чисто для проектов, заточенных под Windows. Может быть потому что, это трудно реализуемо, а может быть потому, что это считается устаревшим и просто не стали заморачиваться. А жаль.

Ну все это, друзья, не беда. MDI-приложение создать на FMX можно. Просто это делается по другому. Скажем так, на сегодняшний день, а у меня установлена Delphi 11.2 реализовать это можно обходным путем. По крайней мере мне известен только этот способ. Но он рабочий.

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

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

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

Создание MDI-приложения в FireMonkey

Создайте в Delphi обычный проект FMX с одной единственной формой. Это будет у нас главная форма приложения. Можем назвать модуль этой формы mdi, а саму форму frmMDI. Можете на эту форму разместить привычное горизонтальное меню, а можете пока не размещать, чтобы позже спроектировать на этом месте собственное меню, например с картинками.

Что мы должны теперь делать? В FMX есть такой объект как TLayout. Он не визуальный, но он предназначен для того, чтобы внутри него размещать визуальные компоненты. Он как бы группирует их. Берем и размещаем на нашей форме frmMDI объект TLayout. Назовите его layMainMenu и установите для него свойство Align в Top. Это будет у нас наше будущее горизонтальное меню. Установите для него свойство Height в такое, какое вам нужно для будущего меню.

Я хочу, чтобы высота у него была побольше, мне хочется там разместить не просто надписи, а картинки. Хотя я еще не придумал, но хочется что-то необычное и по возможности красивое.

Теперь разместите в этом добавленном layMainMenu объект, TPanel или TRectangle и установите у его свойства Align значение Client. Покрасьте панель или прямоугольник в любой цвет. Можете сделать градиентом, можете даже в него поместить TImage и сделать фоновый рисунок. Что угодно... В общем — это у нас будет меню.

Можете даже сделать свою шапку главного окна со своими кнопкам, чтобы их вид не зависел от операционной системы. Этого можно добиться двумя способами: убрать рамку у окна и добавить еще один TLayout и TRectangle внутрь него, но уже над нашими ранее добавленными. То есть, установить ему выравнивание в Top. Только сейчас он встанет под меню, а нам нужно над меню. Для этого меняем порядок их расположения. А потом в этот TRectangle. А там уже размещаете заветные кнопки в правом углу ринга и пишите для них процедуры. Но это подойдет, если вы пишите для Windows, потому что шапка окна — это не что иное, как верхняя увеличенная по высоте граница окна, в которой размещены кнопки.

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

Второй вариант (как по мне) так более современный — использовать стили. Но это будет одна из следующих тем в статьях. Давайте далее продолжим по этой теме.

У каждого окна, кроме меню, есть еще и клиентская область. Давайте добавим на форму еще один TLayout, назовем его, например, layClient и свойство Align установим в значение Client.

Давайте теперь в инспекторе объектов или в окне «Структура» выделим объект layMainMenu (TLayout нашего меню), щелкнем правой кнопкой мыши по нем и из контекстного меню выберем команду Control/Bring to Front. Это мы сделали для того, чтобы наше меню было на верхнем уровне. Так наша будущая форма не будет залазить на меню при перемещении этой формы к ее границам, а будет уходить под меню. Если вы хотите, чтобы этого не происходило, тогда оставьте как есть.

Теперь, когда у нас есть клиентская часть окна, мы можем создать нашу первую дочернюю форму.

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

Установим для нее ширину в 750, а высоту в значение 245.

В этот объект frmNastr мы добавим шапку. Конечно это не объект формы, но для нас это форма, поэтому для удобства я добавляю префикс frm.

Добавим снова TLayout, назовем его layHeader, свойство Align установим в Top. Отрегулируем свойство высоты шапки окна. Давайте для этого свойство Height значение установим в 40. Далее добавим TRectangle внутрь layHeader, назвав его recHeader. Свойство Align установим для него в Client. Шапка готова. Давайте еще придадим ей цвет. Давайте поставим цвет #FF252525 в свойстве Fill/Color. Давайте еще нашему recHeader сделаем закругления, установив свойства xRadius и yRadius в значение 20.

Теперь нам нужно добавить заголовок окна. Для этого в объект recHeader поместите объект TCaption и назовите его, например, lblCaption. Свойство Align полученного lblCaption установите в значение Left. А чтобы сделать отступ слева, предлагаю установить свойство Margins/Left в значение 20. Теперь впишем название, например «Настройки». Прелесть FMX в том, что объекты можно вкладывать друг в друга. Разве это можно было сделать в VCL? (Хотя честно признаюсь, VCL люблю больше).

Установите свойства шрифта так, как вам нравится. Добавьте также TImage размером 40 на 40 и выроните его по правому краю. Здесь у нас будет кнопка закрытия окна.

Теперь добавьте внутрь frmNastr объект TLayout и назовите его layGap. Это у нас будет декоративный прозрачный прямоугольник, который мне нравится, но в стандартном Windows-окне его нет. Моя фича :-) Сделайте ему высоту 15 и выровняйте по Top, чтобы он встал ровно под заголовок окна.

А дальше снова добавляем в frmNastr TLayout и называем его layClient. Вообще можете, как угодно все это дело называть. Это я просто такие названия использую, чтобы мне было понятнее. Выровняйте наш layClient по всей клиентской области. Далее в него размещаем TRectangle, называем его, например, recClient. Давайте сделаем ему такой же цвет и закругление, как у заголовка окна.

Еще один нюанс: установите для объекта frmNastr свойство Align в Center, чтобы наша форма в будущем всегда появлялась в центре родительской. Приблизительно у вас должно получиться так, как на рисунке ниже (рисунок программы в runtime):

Дочерняя форма FMX
Дочерняя форма FMX

Единственное, что кнопку закрытия я нарисовал заранее разместил. Серое поле вверху главной формы — это будущее наше меню. Кнопки главного окна есть, но я их в скрин не разместил, чтобы не путать сейчас вас. Ну вот, чем не форма? Теперь мы можем набросать на нее самые обычные элементы управления: поля, надписи и кнопки, а затем мы будем «оживлять» наше чудо инженерной мысли. Давайте разместим остальные компоненты. Также давайте добавим к нашей клиентской части и к заголовку красивую тень.

Для этого используем объект TShadowEffect. Просто перетаскиваем этот объект сначала на заголовок, затем на клиентскую часть и в нем используя свойства выставляем параметры тени формы: цвет размытие, направление, прозрачность тени, удаленность от объекта. У этого объекта в инспекторе объектов всего 11 свойств, поэтому не буду на этом останавливаться, вы и сами сможете попробовать для себя разные значения.

Короче, получается вот так:

Дочерняя форма FMX с добавленными компонентами
Дочерняя форма FMX с добавленными компонентами

Здесь показывается главное меню, но его я пока сделал для удобства, а потом его можно убрать и оставить свое, под которое мы сделали заготовку. А можно и оставить как дублирующее. Теперь пришло время оживляться. Сейчас форма запускается вместе с приложением. Чтобы этого не было, есть два варианта. Первый сложный, но он аналогичен CreateObject. То есть, например, в пункте меню, на команде нужно все, что мы сделали в этой статье по отношению к дочерней форме прописать программно.

Некоторые части этого кода будут общими для всех форм, только лишь будут менять названия, высота и ширина формы, в такую процедуру можно передавать просто параметры этих величин. Но остальные объекты на формах индивидуальны и их создание придется прописывать вручную. Второй вариант заключается в том, чтобы объект frmNastr сделать невидимым, а когда нужно просто установить для него свойство visible в true.

Для наших целей вполне подойдет и этот метод.

Вместо события onCreate можно использовать процедуру пункта меню:

procedure TfrmMDI.mnuNastrClick(Sender: TObject);
var
   iniF: TIniFile;
begin
   frmMDI.mnuNastr.Enabled:=false;
   //Считываем данные из файла Конфиг.
   //Для этого создаем объект файла Конфиг
   iniF:=TiniFile.Create(sTekPapka+'\Config.ini');
   //Считываем содержимое файла Конфиг и заносим значения в форму
   txtNameOrg.Text:=iniF.ReadString('ORG', 'NameOrg','');
   txtUrAdres.text:=iniF.ReadString('ORG', 'UrAdres','');
   txtTelefon.Text:=iniF.ReadString('ORG', 'Telefon','');
   iniF.free;
   frmNastr.Visible:=true;
end;

Кнопка «Сохранить»:

var
   iniF: TIniFile;
begin
   //Записываем настройки в Конфиг.
   //Для этого создаем объект файла Конфиг
   iniF:=TiniFile.Create(sTekPapka+'\Config.ini');
   //Записываем в файл Конфиг измененные значения
   iniF.WriteString('ORG', 'NameOrg',txtNameOrg.text);
   iniF.WriteString('ORG', 'UrAdres',txtUrAdres.text);
   iniF.WriteString('ORG', 'Telefon',txtTelefon.text);
   iniF.free;//Уничтожаем объект INI из памяти.
   MessageCreate(Left,Top,Height,Width,'Данные успешно сохранены!');
   Close;

Кнопка «Отмена» и крестик:

procedure TfrmMDI.cmdCancelClick (Sender: TObject);
begin
mnuNastr.Enabled:=true;
frmNastr.Visible:=false;
end;

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

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

Сначала в класс формы мы добавляем два поля:

...
  private
    { Private declarations }
    FStartPos: TPointF;//Запоминает начальную позицию мыши над объектом
    FPressed: Boolean;//Флаг нажата или нет кнопка мыши
  public
    { Public declarations }
  end;
var
  frmMDI: TfrmMDI;
...

Затем в событии onCreate формы пишем сложнейший код:

FPressed := False;

Для объекта recHeader пишем:

procedure TfrmMDI.recHeaderMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  frmNastr.BringToFront;
  FPressed := True;
  FStartPos := TPointF.Create(X, Y);
end;
procedure TfrmMDI.recHeaderMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Single);
var
  MoveVector: TVector;
begin
  if FPressed then
  begin
     //Вычисляем локальное смещение относительно первоначальной позиции
     MoveVector := TVector.Create(X - FStartPos.X, Y - FStartPos.Y, 0);
     //Вычисляем смещение в координатах формы, чтобы учесть изменение
     //координат при смещении родительских контролов
     MoveVector := frmNastr.LocalToAbsoluteVector(MoveVector);
     if frmNastr.ParentControl <> nil then
     MoveVector := frmNastr.ParentControl.AbsoluteToLocalVector(MoveVector);
     //Перемещаем картинку на вычисленный вектор
     //Для RAD Studio XE5
     //TRectangle(Sender).Position.Point := TRectangle(Sender).Position.Point + MoveVector.ToPointF;
     //Для новых версий
     frmNastr.Position.Point := frmNastr.Position.Point + TPointF(MoveVector);
  end;
end;
procedure TfrmMDI.recHeaderMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  FPressed := False;
end;

Все, получаем то, что у нас ниже на рисунке:

Создание дочерней формы FMX
Создание дочерней формы FMX

Теперь эта форма максимально похожа на наш настоящий объект формы. Конечно мы можем еще сделать стилевой эффект для активной и неактивной формы и прочие фишки.

Таким образом, мы создаем вторую и третью форму, размещаем объекты доступа к данным и все как обычно. С каким то ограничениями вы конечно столкнетесь, но их можно будет обойти. Так что вот так вот: довольно не плохая методика. Конечно она немного более сложна по сравнения с полноценным созданием формы, но за не имением лучшего — она довольно не плохая. Так что, всем удачного программирования и смелых решений!

Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: