Revisitando los Layouts

La mayoría de nosotros estaremos de acuerdo que el Layout es una de las funcionalidades más importantes provenientes de QT e incorporadas al diseño de formularios en vDevelop. Seguro también que todos usamos los layouts sin mayor problema y de forma automática.

En este tutorial sobre layouts quiero repasar algunos aspectos de su funcionamiento que todavía causan confusión, e intentar aclararlos con ejemplos de uso.

Pero primero os invito a repasar el Tutorial Layouts
en Velneo V7, un objeto para dominarlos a todos
del autor de este mismo
blog y un video resumen
de Jesús Arboleya
. Ambos recursos dejan clara la función que tienen los
layouts y cómo organizan los controles automáticamente en el formulario.

Solo recordar un par de cosas antes de ver con ejemplos
algunas propiedades interesantes de los layouts.

  • Existen otros controles que también pueden funcionar como layouts, son el formulario, la caja de grupo y el dibujo.
    Los layouts solo son visibles en tiempo de diseño, pero podemos enumerarlos con el API mediante la clase VObjectInfo (Tipo de control 22).
  • Los layouts dentro del formulario solo funcionarán cuando el formulario es también un tipo de layout.
    Los layouts pueden anidarse unos dentro de otros, siendo el layout Principal el formulario que los engloba a todos.

Tipo de Layout

El Tipo de layout
determina cómo se distribuyen los controles dentro del Layout. Hasta la versión
24 existían 3 tipos de layout, Horizontal,
Vertical y Grid. En la versión 25 se han añadido 2 nuevos tipos, Flujo Horizontal y Flujo Vertical que nos van a permitir diseño Responsive en nuestros formularios. Disponemos también de espaciador Fijo y espaciador Expandible.

  • En el layout Horizontal los controles se distribuyen horizontalmente de
    izquierda a derecha ordenados por el valor de la propiedad Posición X. Si coincide el valor de X se toma primero el que tiene
    menor valor de Y.
  • En el layout Vertical los controles se distribuyen verticalmente de arriba a abajo ordenados por el valor de la propiedad Posición Y. Si coincide el valor de Y se toma primero el que tiene menor valor de X.
  • El layout Grid
    organiza los controles en una cuadrícula de filas y columnas, pero también nos
    servirá cuando tenemos que superponer
    uno o varios controles encima de otro. Si lo que vamos a superponer son imágenes,
    tendremos que usar el canal Alfa o
    de transparencia.

Velneo permite cargar en el proyecto imágenes con transparencia o crearlas con el editor de imágenes, sin embargo, no podemos guardarlas en un campo dibujo porque solo admite el formato jpeg. Si queremos trabajar con registros de una tabla que contengan imágenes con transparencia no tenemos más remedio que codificarlas en Base64 y usar el API para leer y guardar en un campo de Texto.

El formulario siguiente es de
tipo Grid con 2 layouts: el layout Artículo con los campos y el layout Tampón
con una imagen con fondo transparente.
Para atenuar los controles que hay detrás de la imagen se puede activar la
propiedad «Fondo opaco» del layout Tampón y usar un «Color de
fondo» con transparencia.

Superponiendo el layout Tampón un poco con el layout Artículo, el resultado es un Grid con una sola celda que contiene ambos controles.

Propiedad «Fondo opaco»
del layout Tampón desactivada.

Propiedad «Fondo opaco»
del layout Tampón activada para atenuar los controles del formulario.

Si no lo haces ya, ¡¡hazlo!!, activa la Rejilla de puntos en el editor de formularios. Los controles estarán siempre anclados en múltiplos de 10px. Te aseguro que una vez activado ya no sabrás trabajar sin él.

Ancho y Alto en layout

Las propiedades Ancho
en layout
y Alto en layout  determinan el tamaño de los controles dentro
de un layout y pueden tener 3 valores:

  • Por defecto: el control se pintará en el formulario con el ancho y alto asignado por defecto.
  • Fijo: el control se pintará en el formulario con el ancho y alto especificado en las propiedades Ancho y Alto.
  • Proporcional: el control se pintará en el formulario ocupando el máximo espacio que le permita el layout y en el caso de que comparta el espacio con otro control, lo hará de forma proporcional al tamaño indicado en las propiedades Ancho y Alto.
    Este será el comportamiento habitual de la mayoría de controles, lo que les permite adaptarse a los diferentes tamaños de pantalla.
    Más adelante veremos qué implicaciones tiene esto.

Si no hemos aplicado CSS, siempre que sea necesario, el alto
y ancho del layout del formulario crecerán para albergar todos los controles
que contenga.

El formulario se expande en Horizontal y Vertical

Veamos un ejemplo con controles de tamaño Por defecto y cómo se distribuyen a lo ancho o a lo alto dentro del layout.

El formulario siguiente contiene
en tiempo de diseño 6 textos estáticos
con las letras de Velneo en 2 layouts, uno Horizontal y otro Vertical.
Se han fijado el Espaciado y los Márgenes a 0 en ambos layouts.

En ejecución los textos estáticos se distribuyen (horizontalmente o verticalmente) y expandirán el formulario hasta que se muestren todos los controles a su tamaño Por defecto.

Un efecto negativo de este comportamiento es que el usuario no puede redimensionar la ventana de la aplicación en el caso de que ésta desborde las medidas de la pantalla. Podemos solucionar este problema mediante el siguiente CSS:

QMainWindow
{
   min-width: 200px;
   min-height: 40px;
}

Estableciendo un valor mínimo al ancho y alto de la ventana principal los controles del formulario ya no intentarán redimensionarlo, pero surge otro problema cuando dichos controles desbordan el espacio disponible. En el siguiente formulario vemos lo que ocurre:

En la versión 23 se añadió un nuevo control de formulario área de scroll que soluciona éste y otros escenarios similares. Más información sobre el área de scroll en el siguiente artículo de este mismo blog.

Flujo Horizontal y Vertical para el diseño Responsive

El layout Flujo
Horizontal
distribuye los controles horizontalmente de izquierda a derecha
ordenados por el valor de la propiedad Posición
X
siempre que haya espacio horizontal disponible. El layout Flujo Vertical distribuye verticalmente
los controles de arriba a abajo ordenados por el valor de la propiedad Posición Y siempre que haya espacio
vertical disponible.

Estos layouts de Flujo no modifican el tamaño del formulario, en su lugar distribuyen los controles creando un flujo horizontal (de izquierda a derecha y de arriba a abajo) o vertical (de arriba a abajo y de derecha a izquierda).

En el formulario siguiente tenemos 2 layouts de Flujo, uno
de cada tipo. El layout de Flujo Horizontal contiene 2 layouts de ancho
fijo y otros 2 proporcionales. En el layout de Flujo Vertical hay 2
layouts de alto fijo y otros 2 proporcionales.

En ejecución el formulario no se ha redimensionado para
distribuir los elementos en los layouts de Flujo
Horizontal
(200px de ancho) y Flujo
Vertical (200px de alto).

En el layout de Flujo
Horizontal
los layouts fijos naranja y verde caben en la primera fila
porque miden 160px (70 + 90px) y todavía queda espacio para el layout
proporcional amarillo. La segunda fila se ocupa proporcionalmente por el layout
azul.

En el layout de Flujo
Vertical
el layout fijo naranja ocupa él solo la primera columna con 150px,
el layout fijo amarillo de 70px pasa a la segunda columna porque ya no queda
espacio disponible. Los layouts verde y azul ocupan proporcionalmente el
espacio disponible de la segunda columna.

Cuando aumentamos el tamaño del formulario la distribución
de los elementos del formulario cambia.

El layout de Flujo
Horizontal
ha aumentado a 300px de ancho y ya da cabida al layout azul que
tendrá la mitad del ancho del layout amarillo por la proporción que tienen en
diseño. El layout de Flujo Vertical
aumenta a 220px de alto y mete en la primera columna el layout amarillo con sus
70px de alto. La segunda columna se reparte al 50% entre los layouts verde y
azul que son iguales en diseño.

Los layouts de Flujo solucionan el problema de recolocar los
controles cuando giramos las pantallas de los móviles y tablets.

En el siguiente formulario el layout de Flujo Horizontal de color verde colocará el Dibujo a la derecha o en la parte inferior según la orientación del formulario. Para determinar en qué momento el dibujo fluye de la posición horizontal a la vertical establecemos un control fijo de 140px que forzará el cambio cuando el formulario cambie de tamaño y ya no permita albergar a éste.

En ejecución con orientación horizontal del formulario.

En ejecución con orientación vertical del formulario.

Espaciado y Márgenes

Las propiedades Espaciado
y Márgenes de los layouts tienen un
valor por defecto de -1 que equivale
a 5px y 10px respectivamente.

Usaremos estas propiedades para agrupar o separar visualmente los controles en un determinado formulario. Para aplicar estos valores de manera global usaremos CSS.

El valor «margin» del CSS de los controles se sumará al Espaciado del layout.

El formulario siguiente tiene un margen izquierdo y superior a un valor personalizado de 50px. Los 4 layouts verdes se distribuyen dentro del layout principal con un espaciado de 10px y márgenes personalizados de 20,30,20,30 (sup, der, inf, izq).

En este otro formulario los márgenes se han fijado a 50px y el espaciado entre controles a 30px. Dentro de cada Layout se han definido espaciados y márgenes personalizados.

Ancho y Alto proporcional

El diseño de la mayoría de los formularios se resuelve dejando los valores Por defecto en las propiedades Ancho en layout y Alto en layout.
En la tabla siguiente se listan algunos controles y los valores Por defecto que tendrán el Ancho y Alto dentro de un Layout.

Observamos que el valor más común es Proporcional, pero ¿qué tamaño efectivo tomará el control en tiempo
de ejecución?

Rellenar proporcionalmente el espacio disponible

En el formulario siguiente hemos colocado 2 layouts con la propiedad Alto en layout a valor Por defecto. El layout «cabecera» mide en diseño 240px de alto y el layout «botones» 80px.

En ejecución los layouts se adaptan por defecto a la suma del tamaño de los controles que contienen. El layout «cabecera» mide en pantalla unos 220px de alto y el layout «botones» unos 65px.

Cambiemos en diseño los valores de Alto en layout de ambos layouts a Proporcional. Vemos que ahora la Altura de los layouts es 300px y 100px respectivamente. Los valores se han repartido de arriba a abajo del formulario (400px) de manera Proporcional, según la relación (3/1 = 240px/80px ) que tienen en diseño.

Finalmente pongamos el Alto del layout «botones»
al valor Por defecto.
En este caso el layout «botones» ocupa el espacio mínimo necesario,
unos 65px, y el layout «cabecera» ocupará el resto del formulario.

Resumiendo, cuando establecemos el tamaño de un control en diseño
al valor Proporcional, tendremos 2
casos en tiempo de ejecución:

  1. Si el control es el único Proporcional dentro del layout, entonces se expandirá para ocupar todo el ancho o alto disponible en el layout.
  2. Si comparte layout con otros controles tendrá que adaptar su tamaño proporcionalmente al de dichos controles. Los tamaños en tiempo de ejecución estarán siempre referenciados proporcionalmente al tamaño que tenían en tiempo de diseño.

Mantener Proporción en horizontal y vertical

Compliquemos un poco el diseño de nuestro formulario para jugar con la Proporción en horizontal y vertical. En este caso tenemos 3 layouts que deben tener cada uno la mitad del tamaño que el siguiente.

El formulario es un layout vertical. En diseño creamos 3 layouts Horizontales (azul, naranja y verde) de tamaños proporcionales de 30, 60 y 120px de lado respectivamente. Los 2 layouts Horizontales amarillos son de igual ancho que el layout verde (120px). Los espaciadores Expansibles mantienen la proporción en sentido horizontal.

El layout azul siempre ocupará la cuarta parte del ancho del
formulario, el naranja la mitad y el verde el total.

En ejecución, la relación del área entre los layouts azul,
naranja y verde siempre es constante e igual a 1,4,8.

Los espaciadores expansibles son proporcionales

Los espaciadores Expansibles consiguen anclar controles
en coordenadas exactas y se adaptarán proporcionalmente al tamaño de la
pantalla.

En el siguiente formulario queremos que el marge derecho sea siempre la mitad que el margen izquierdo y el margen superior igual al inferior. En diseño colocamos los espaciadores expansibles y el control con los tamaños proporcionales planteados.

En ejecución el formulario mantiene las mismas proporciones
cuando ha duplicado el tamaño respecto al que tenía en diseño.

Superposición parcial de controles

Ya sabemos que para superponer 2 elementos en el formulario
necesitamos un layout Grid. Pero
necesito ahora superponer elementos de forma parcial, como cuando montamos un puzzle.

Planteamos el siguiente Puzzle con 4 piezas que tienen una
parte de solapamiento:

La 4 piezas en diseño las colocamos en un layout Grid sin
solapamiento. En este caso no encajan.

Si en diseño solapamos las 4 piezas tampoco se obtiene el
resultado deseado.

Hagamos uso de los layouts y espaciadores proporcionales. Planteamos un diseño con 2 layouts proporcionales, uno Horizontal y otro Vertical y 2 espaciadores. El layout Vertical es el que engloba el resto de controles. La pieza es un dibujo proporcional con la propiedad Aspecto en Estirar / Encoger.

Manteniendo en diseño las adecuadas proporciones obtenemos en ejecución la pieza ocupando la cuarta parte del formulario. No olvidemos poner a 0 todos los espaciados y márgenes de los layouts.

Haciendo lo mismo con el resto de piezas y solapando los
cuatro layouts Verticales obtenemos el resultado deseado.

Ya tenemos un marco que pueda encuadrar, por ejemplo, un gráfico de tipo Tarta.

Como siempre encuadramos el gráfico en un layout
proporcional estableciendo los márgenes con espaciadores proporcionales. El
gráfico deberá tener los márgenes a cero y el fondo transparente para que
funcione correctamente el solapamiento con otros layouts del Grid.

Juntamos todo añadiendo leyendas y un título y este es el
resultado final.

Con semejante cantidad de controles solapados entenderemos la importancia de disponer en tiempo de diseño de eficaces herramientas de edición.
En la versión 24 se han incorporado dos nuevas funcionalidades, la selección de layouts solapados y la posibilidad de desactivarlos. Por supuesto, poder agrupar los controles y desactivarlos facilitaría enormemente el trabajo de diseño y edición de formularios complejos.

Conclusiones

Los layouts y espaciadores expansibles son elementos básicos en el diseño y ejecución de nuestros formularios.

Hemos hecho un repaso, quizás algo peculiar, de algunas
aplicaciones de los layouts que considero interesantes. Espero que se haya
entendido el significado de la propiedad «Ancho en layout Proporcional» para que a partir de ahora no
sea un problema colocar y dimensionar los controles del formulario exactamente
dónde y cómo queremos.

Algo de código para terminar

Los layouts son controles cuya propiedad Tipo de control es igual a 22. Los espaciadores expansibles son Tipo de control igual a 23.

Con este código se puede obtener el Id y el tipo de control de todos los elementos de un formulario.

// Obtiene todos los controles del Formulario y Separadores de Formularios
 var oForm = theRoot.dataView()
 var oFormInfo = theRoot.objectInfo()
 var nNumObj = oFormInfo.subObjectCount(VObjectInfo.TypeControl)
 var oListaCtr = [], cCtr = ""
// Los objetos se recorren con subObjectInfo que devuelve otro VObjectInfo
 for ( var numControl = 0; numControl < nNumObj ; numControl++ ) {
     objInfo = oFormInfo.subObjectInfo(VObjectInfo.TypeControl, numControl)
     // La función objInfo.id() devuelve el nombre del Control
     // La función objInfo.propertyData(0) devuelve el Tipo de Control
     cCtr = objInfo.propertyData(0) + " - " + objInfo.id()
     oListaCtr.push(cCtr)
     // Comprobamos el Separador de formularios
     if (objInfo.propertyData(0) == 13) {
         var oSep = oForm.control(objInfo.id())
         // Recorremos los Separadores de formularios
         for (var numSep = 0; numSep < oSep.count ; numSep++ ) {
             var oFormSep = oSep.form(numSep)
             var oFormSepInfo = oFormSep.objectInfo()
             var nNumObjSub = oFormSep.controlCount()
             cCtr = "SubForm - " + oFormSepInfo.id()
             oListaCtr.push(cCtr)
             for ( var nCtrl = 0; nCtrl < nNumObjSub ; nCtrl++ ) {
                 objetoSub = oFormSepInfo.
                     subObjectInfo(VObjectInfo.TypeControl, nCtrl)
                 cCtr = "SubCtrl " + 
                     objetoSub.propertyData(0) + " - " + objetoSub.id()
                 oListaCtr.push(cCtr)
             }
         }
     }
 }
 alert (JSON.stringify(oListaCtr))

Espero que después de estar «master-class» sobre los layouts del maestro Satué no quede ninguna duda al respecto…

Espero tus comentarios mas abajo

La entrada Revisitando los Layouts se publicó primero en AyudaVelneo.