Planteamiento de la necesidad
Hay ocasiones en las que nos encontramos con la necesidad de incorporar en el separador de un formulario un numero elevado de subformularios, además es habitual que los textos de las pestañas no sean cortos para poder describir con precisión su contenido. En estos casos la interfaz de la aplicación plantea un problema de usabilidad al usuario final ya que dependiendo de la resolución de su pantalla es posible que no tenga a la vista de forma directa todas las pestañas por lo que le resultará más complicado acceder a una de las ocultas. Incluso debemos tener en cuenta que la organización de pestañas horizontalmente resulta más difícil de leer cuando el número de pestañas es elevado.
Objetivo a conseguir
Con el fin de mejorar la usabilidad de la aplicación para el usuario final vamos a intentar sustituir las pestañas por una lista de opciones ubicada en un control de tipo listbox. De esta forma el usuario tendrá muchas opciones a la vista, con la posibilidad de acceder directamente a cada una de ellas e incluso las opciones podrán tener textos largos más descriptivos.
Piezas de la solución
Para conseguir programar una solución práctica y sencilla con un código fácilmente reutilizable vamos a crear un formulario que contenga solo 2 subcontroles: El primero que estará situado a la izquierda será el listbox (MEN_CFG) que contendrá las lista de opciones (subformularios), a su derecha ubicaremos un control de tipo pila de formularios (FRM_CFG) que será el contenedor de todos los subformularios que el usuario tendrá visibles.
Listbox sincronizado con la pila de formularios
La idea se basa en que solo nos tengamos que preocupar en añadir subformularios al control pila de formularios en el orden que deseemos aparezcan en el control listbox que hará de menú. A partir de ahí, al ejecutar la aplicación el código que vamos a ver a continuación se encarga de leer los subformularios de la pila y añadir al listbox los elementos correspondientes basados en los nombre de los objeto formulario incrustados en la pila.
Realmente no se necesita nada más porque el orden de elementos del listbox y el de los subformularios en la pila es el mismo, por lo que cuando el usuario selecciona un elemento del listbox, la posición de ese elemento nos sirve para conocer el índice del subformlulario que debemos activar. De la misma forma podemos marcar como seleccionado el elemento del listbox que se corresponda con el formulario en curso de la pila, es decir que se garantiza la sincronización en ambas direcciones.
Otro aspecto a tener en cuenta de esta solución es que contempla los puntos de inserción que serán totalmente funcionales, por lo que la personalización de estos controles para incluir nuevos formularios es realmente sencillo, ya que una vez incluido el subformulario a través del punto de inserción el elemento se incorpora automáticamente al listbox igual que el resto de formulario incrustados por el programador directamente en el control pila de formularios.
2 conexiones y 2 manejadores de evento para controlarlo todo
Para programar esta vitamina necesitamos crear 2 manejadores de evento y sus correspondientes conexiones de evento:
Conexión de evento OnShow formulario para carga del listbox
Manejador de evento CAR_LIS_BOX
/**
* Devuelve el icono de un formulario
*
* @param {VObjectInfo} formulario Objeto de la clase VObjectInfo del formulario
* @return {VImage} icono Objeto de la clase VImage con el icono del formulario
*/
var iconoFormulario = function (formulario)
{
if (formulario)
{
importClass("VImage");
var icono = new VImage();
var iconoIdRef = formulario.propertyData(6).replace("@", "/");
icono.loadResource(iconoIdRef);
return icono;
};
};
// ------------------------------
// Cargar formularios en listbox
// ------------------------------
var controlListBox = theRoot.dataView().control("MEN_CFG");
var controlPila = theRoot.dataView().control("FRM_CFG");
// -------------------------------------------------------------------
// Leer los subformularios y dar de alta los registros en el listbox
// -------------------------------------------------------------------
// Limpiamos el listbox antes de cargarlo
controlListBox.clear();
var numFormularios = controlPila.count;
for (var numFormulario = 0; numFormulario < numFormularios; numFormulario++)
{
var formulario = controlPila.form(numFormulario);
var nombre = formulario.objectInfo().name();
var icono = iconoFormulario(formulario.objectInfo());
controlListBox.addItem(icono, nombre, numFormulario);
};
// Marcamos el primer elemento como seleccionado
controlListBox.currentRow = controlPila.currentIndex;
Es muy importante cargar los elementos del listbox en el mismo orden es que están declarados los formularios en el control, y más aún teniendo en cuenta que podemos usar puntos de inserción.
La última línea del script se encarga de dejar seleccionada la líneas del listbox que se corresponde con el formulario abierto, esto es necesario tanto para la carga inicial como sobre todo si cargamos el listbox en el OnShow, ya que la primera vez estará cargado el primer formulario de la pila, pero si salimos de la pestaña y luego regresamos el formulario que veremos será el último seleccionado, y como en ese punto recargados el listbox es necesario volver a posicionarlo. Si no queremos que se cargue en el OnShow podemos hacerlo en la conexión de evento Post-inicializado, perdiendo dinamismo y ganando tiempos de ejecución ya que se solo se carga la primera vez.
Otro punto a tener en cuenta en el script es que añade al listbox el icono que se corresponda con la propiedad icono declarada en cada uno de los formularios añadidos a la pila. Para que el listbox quede homogéneo o bien le ponemos icono a todos los formularios o a ninguno.
Conexión de evento ítem de cambio seleccionado en el listbox
Manejador de evento CAR_FRM_SEL
// ---------------------------------
// Abrir el formulario seleccionado
// ---------------------------------
var controlListBox = theRoot.dataView().control("MEN_CFG");
var controlPila = theRoot.dataView().control("FRM_CFG");
controlPila.setCurrentIndex(controlListBox.currentRow);
controlPila.form(controlPila.currentIndex).setFocus();
En la última línea de este código fijamos el foco en el formulario de la pila, por lo que una vez seleccionado el formulario el usuario ya puede comenzar a escribir en los controles del formulario sin necesidad de usar el ratón. Si no deseas esa funcionalidad puedes comentar o quitar la última línea.
Código reutilizable
Lo único que tenemos que hacer para utilizarlo en otro formulario es asegurarnos de que o bien coinciden los identificadores de los controles listbox (MEN_CFG) y pila de formularios (FRM_CFG) o cambiamos dichos identificadores en el código.
Si quieres ver como crear una interfaz similar pero con un control combobox consulta la VITAMINA - 14 - Organiza los subformularios con un combobox.
Luis Palomo dice
Hola Jesús,
Buen aporte, como siempre.
Comentarte una cosa sobre esta vitamina; si incluyes el formulario que contiene el listBox en una pestaña, cada vez que seleccionas dicha pestaña te va añadiendo todos los formularios de nuevo, repitiéndolos. En la conexión a Evento le he puesto Post-Inicializado y solo hace la carga una sola vez.
Saludos, Luis.
jarboleya dice
Hola Luis.
Gracias por la información.
He revisado el artículo y ya está corregido y mejorado con la selección de elemento del listbox y el icono del formulario. En concreto la línea que evita que se duplique es la siguiente:
// Limpiamos el listbox antes de cargarlo
controlListBox.clear();
Saludos.
Luis Palomo dice
Otra cosa Jesús,
Como puedo dejar seleccionado el primer item de la lista «ListBox»
Gracias
jarboleya dice
Hola Luis.
La línea que hace esa selección es la siguiente:
// Marcamos el primer elemento como seleccionado
controlListBox.currentRow = controlPila.currentIndex;
En realidad seleccionar el primer elemento solo es válido si el control listbox solo se carga en la conexión de evento Post-inicializado, porque solo se hace una vez. Pero es mejor el sistema empleado porque así siempre después de cargase se sincroniza en función del formulario en curso, lo que permite que funcione correctamente incluso al cambiar de pestaña con la conexión de evento OnShow activa para la carga del listbox.
Saludos.
Luis Palomo dice
Otra cosa Jesús,
Como puedo incluir un icono de un proyecto en la lista “ListBox”?
Gracias
jarboleya dice
Hola Luis.
Para incluir un icono al listbox se usan las siguientes líneas:
var icono = iconoFormulario(formulario.objectInfo());
controlListBox.addItem(icono, nombre, numFormulario);
En realidad es más algo más complicado, ya que la función iconoFormulario que vemos al principio del primer script es la que se encarga de atrapar el icono del formulario incrustado en la pila. Gracias a esa función, podemos hacer que cada elemento del listbox asuma tanto el texto como el icono de cada formulario.
Saludos.
Luis Palomo dice
Gracias Jesús,
Fantástico!, excepto una cosa. Solo carga el último icono del ListBox
Se puede solucionar?
Gracias
jarboleya dice
A mi me los carga todos correctamente.
Como digo en el artículo se cargan los iconos de la propiedad icono «de los formularios», no de los subobjetos de la pila, sino del objeto formulario.
Revísalos.
Luis Palomo dice
Hola de nuevo,
No he dicho nada. Creía que te cargaría el icono que le asignamos al subcontrol de la Pila de formularios.
Gracias
Saludos, Luis
jarboleya dice
Hola Luis.
Ok, era lo que suponía, como puse en el comentario anterior.
Es mucho mejor que cargue el del formulario, porque si hay formulario incrustados por herencia inversa no habría icono en el subobjeto de la pila, y de esa forma el formulario incrustado tendría el icono declarado en la solución superior.
Saludos.
Luis Palomo dice
Gracias Jesús.
Saludos, Luis
Antonio Vela dice
Gracias Jesús
Luis Palomo dice
Hola de nuevo Jesús,
Una cuestión relacionada con el control de tipo listbox. Hay alguna forma de pasarle una imagen que tengamos en alguno de nuestros proyectos cuando damos de alta con controlListBox.addItem(icono, nombre, numFormulario);
Es para crear un listBox sin depender de la pila y que me lance un formulario.
Gracias
jarboleya dice
En el ejemplo se usa.
Revisa la clase VImage y en concreto la función loadResource(iconoIdRef);
Luis Palomo dice
Hola Jesús,
Como podríamos hacer para que el nombre del listBox fuera el «Título opcional» que le damos al formulario?, se puede hacer?
Gracias