Con la entrada de hoy vamos a crear una aplicación demo para explicar el uso del componente TMultiView y crear el típico menú de dispositivos móviles. Además, la demo tendrá como requisito que los formularios mostrados en ella sean embebidos dentro de un TPanel del formulario principal. Para ello, una vez creado el formulario, vamos a cambiar el parent de todos sus componentes directos asignándoles como parent el TPanel del formulario principal.
Otro requisito de la aplicación, será poder navegar hacia atrás por los diferentos formularios que se vayan creando. Para ellos definiremos una «pila» LIFO (Last In First Out).
El componente TMultiView
El componente TMultiView nos permite crear interfaces master-detail en cualquier plataforma. En el panel master podemos visualizar cualquier conjunto de componentes visuales (botones, etiquetas, listas,….). Estos controles se pueden lincar a una vista concreta del panel detalle.
Sus propiedades principales son:
- Mode: establece el modo de presentación del panel maestro. Los diferentes modos son los siguiente:
- Drawer: en este modo, el panel maestro puede estar oculto o superponerse al panel detalle.
- Panel: el panel maestro siempre se muestra anclado a derecha o izquierda.
- PlatformBehaviour: la aplicación selecciona el modo de presentación dependiendo de la orientación y del tipo de dispositivo.
Aplicaciones móviles Tipo de dispositivo Tipo de Orientación Presentación panel maestro Phone Landscape, Portrait Drawer (push/overlap) Tablet Landscape Docked panel Tablet Portrait Drawer (push/overlap) Aplicaciones de escritorio Versión del S.O. Presentación panel maestro Windows 10 Navigation panel Windows 8 or earlier Docked panel OS X Docked panel - Popover: el panel maestro se muestra en un menú desplegable.
- NavigationPane: el panel maestro se muestra con una anchura mínima definida en la propiedad CollapsedWidth.
- Custom: personalizado (para más info, leer este enlace).
- MasterButton: especifica qué control servirá para mostrar/ocultar el panel maestro. Imprescindible para los modos Drawer, Popover y NavigationPane.
La aplicación
En el ejemplo de hoy, usaremos el TMultiView para crear el menú de aplicación típico de dispositivos móbiles y los formularios de la aplicación se visualizarán en un TPanel (de nombre pContent). Para ello pondremos en el formulario principal los siguientes componentes:
– Un TToolBar con alineación Top que nos hará de cabecera.
Dentro de éste, pondremos 2 botones, uno alineado a derecha (botón bBack) y otro a izquierda (botón bMultiview). El primero nos hará la navegación inversa o cerrará la aplicación si no quedan pantallas por volver a mostrar. Para estandarizarlo su visualización según plataforma, definiremos su propiedad StyleLookup a backtoolbutton. El segundo será el encargado de mostrar o ocultar el menú de la aplicación. Al igual que el bBack, estableceremos la propiedad StyleLookup a drawertoolbuttonbordered para estandarizar su visualización según plataforma.
– Un TMultiView (mvMenu), al que estableceremos la propiedad MasterButton con el botón bMultiView; y la propiedad Mode a Drawer.
Dentro del MultiView, añadiremos dos botones para mostrar un formulario en cada uno de ellos.
– Un TPanel (pContent) al que estableceremos la propiedad Align a Client.
Para terminar la parte visual, crearemos un par de formularios con algún control dentro, el que queramos, sólo será para poder comprobar como van cambiando. En el ejemplo se ha puesto un botón en cada uno, en el primer formulario alineado arriba y en el segundo abajo.
Ahora que ya tenemos todo el diseño, vamos a ver el código.
Lo primero será definir la «pila» de formularios. Para ello usaremos genéricos y declararemos la variable privada FFrmList de la siguiente manera:
FFrmList: TObjectList<TCustomForm>;
Esta lista la crearemos en el constructor de la clase y la destruiremos en el destructor.
También definiremos dos métodos para apilar y desapilar formularios.
procedure PushForm(AForm: TCustomForm); procedure PopForm;
El primero, PushForm, recibirá por parámetro el formulario a apilar. Además de apilarlo, necesitaremos limpiar el área en el caso de que ya se estubiera visualizando un formulario. Ésto lo haremos devolviendo el parent a su formulario original. Luego, sólo quedará cambiar el parent de los componentes del formulario pasado por parámetro.
El segundo, PopForm, borrará el formulario actual, y en caso de que haya formularios apilados, mostrará el último.
Y para terminar, definiremos un método para crear los formularios.
procedure CreateForm(ClassForm: TFmxObjectClass);
Este método recibirá por parámetro la clase del formulario a crear. Además, para evitar nombres de objetos repetidos, le asignará un nombre basado en la hora del sistema (algo a mejorar). Una vez creado el formulario, hará una llamada a PushForm.
Para terminar, tendremos que programar los eventos OnClick del botón bBack para desapilar los formularios, y de los botones de menú para crear los formularios, los cuales harán una llamada al método CreateForm.
Veamos cómo queda el código
procedure TForm2.bBackClick(Sender: TObject); begin // if exist any visible form, do a pop if FFrmList.Count > 0 then PopForm else // else, close the application Close; end; procedure TForm2.Button1Click(Sender: TObject); begin // create a child form CreateForm(TForm3); end; procedure TForm2.Button2Click(Sender: TObject); begin // create a child form CreateForm(TForm4); end; constructor TForm2.Create(AOwner: TComponent); begin inherited; FFrmList := TObjectList<TCustomForm>.Create; end; procedure TForm2.CreateForm(ClassForm: TFmxObjectClass); var aForm: TCustomForm; begin inherited; aForm := ClassForm.Create(Self) as TCustomForm; aForm.Name := aForm.Name + FormatDateTime('hhnnssmm', Now); PushForm(aForm); mvMenu.HideMaster; end; destructor TForm2.Destroy; begin if Assigned(FFrmList) then FreeAndNil(FFrmList); inherited; end; procedure TForm2.PopForm; var AForm: TCustomForm; begin // if don't have stack forms, bye bye if FFrmList.Count = 0 then Exit; // we return parent references while pContent.ChildrenCount > 0 do pContent.Children[0].Parent := FFrmList.Items[FFrmList.Count - 1]; // unstack last shown form FFrmList.Delete(FFrmList.Count - 1); // if any form is into the stack if FFrmList.Count > 0 then begin // get last form AForm := FFrmList.Items[FFrmList.Count - 1]; // put new references to the principal container while AForm.ChildrenCount > 0 do AForm.Children[0].Parent := pContent; end; end; procedure TForm2.PushForm(AForm: TCustomForm); begin // if exists an active form, we return the references if FFrmList.Count > 0 then begin while pContent.ChildrenCount > 0 do pContent.Children[0].Parent := FFrmList.Items[FFrmList.Count - 1]; end; // adds new form to the stack FFrmList.Add(AForm); // we assign new references to the principal container while AForm.ChildrenCount > 0 do AForm.Children[0].Parent := pContent; end;
Como siempre, aquí tenéis los fuentes de la demo.