Sigo... más o menos desde donde lo dejé...
Cada ítem de un menú por su utilidad puede tener una funcionalidad más acentuada que otro, respecto de alguna característica, lo que viene a indicarnos que hay que considerar diferentes tipos de ítems de menú... Voy a considerar una lista que suele ser habitual, y en casos concretos puede haber otros o descartarse algunos de aquí... es normal. Mi texto debe servir de base a las necesidades de cada uno.., no hay porqué seguirlo a rajatabla.
Básicamente hay 2 categorías de tipos de menús: 'Título' y 'Acción', el primero es el 'conductor', sirve para mantener orden y conducir al usuario... la categoría 'acción' sirve para realizar la acción cuya funcionalidad es la que espera y se ofrece al usuario...
Todos los tipos los encajamos en uno u otra categoría:
* Titulo: Es un tipo que actúa de padre de otros, los ítems que tiene bajo sí son sus hijos. Hay 3 formas de este tipo de menús a la hora de implementarlos y según donde se precise usarlo.
---- Título: Describe sus ítems debajo, cuando se pulsa despliega u oculta los ítems bajo él. En consola es algo más complejo y laborioso de usar... Suele llevar algún indicador tipo flecha arriba/abajo a su derecha para indicar que que al pulsarse despliega/repliega su lista.
---- Submenú: Como el anterior, pero a la hora de desplegar, a sus hijos, los ubica en una nueva ventana. Tratándose de consola, implica dibujar esos ítems que pasan a ser el menú activo. Suele llevar algún indicador tipo flecha derecha, a su derecha para indicar que que al pulsarse despliega su lista. en este tipo de menú el repliege suele hacerse cuando la ventana pierde el foco, al tratarse de consola exige un ítem específico.
---- Delimitador: Es un tipo de ítem que delimita donde terminan los ítems de un tipo dado, por ejemplo, los hijos de un submenú o los ítems de un tipo específico. Como tal puede ser visible o invisible.
A veces es visible y simplemente contiene una línea que aclara al usuario el límite 'físico' de los ítems previso.
Este ítem si se acepta en un menú podría hacer innecesario el campo 'cantidad de hijos', se entendería que un menú empieza en el índice que señale el padre y acaba cuando encuentre un ítem de este tipo. No importa tnto si se usa este método o el otro, siempre que se sea consecuente y se elija uno y solo un método para el caso.
En el caso de consola este mismo puede ser el que una vez pulsado cierre el menú actual y retorno al previo, en este caso el texto debe ser claro 'Volver <' y en caso del menú raíz 'Salir <', nótese la flechita (el sufijo) a la derecha del texto.
* Acción: En los menús típo Pull-Down, un menú se cierra siempre que se pulse un ítem de 'accion', el código de inmediato deriva a la acción pulsada...
---- Botón: Hace lo que dice. Cuando se pulsa ejecuta una acción y ya. Ejemplo: 'Copiar' (copiaría el textou objeto seleccionado, al objeto al que da soporte.
---- Activación: (Check) Permite que se active o desactive con cada pulsación, alternando entre uno y otro estado. Habitualmente es de tipo buleano y por tanto mantiene solo dos estados. Ejemplo: 'Guardar opciones al salir'. Que viene a indicar que: cuando se cierre la aplicación se consulta el estado de este ítem, y si está activado, se guarde a fichero las modificaciones que se hubieren realizado en la configuración de un programa.
---- Opcion (option/radio button): Cuando se pulsa, se activa previamente desactiva el ítem del mismo grupo que estuviere activo. Requiere pués o bien tener un indicador de cual estaba activo, o bien ahorrarse el indicador y recorrer sus 'hermanos' para determinar cual estaba activo. Un grupo para consdierarse como tal, debe estar contiguo, es decir ser del mismo tipo, si un menú es demasiado complejo, y hay listas seguidas de estos: podría interponerse algún separador (un menú titulo) o bien un campo grupo y que cada menú del mismo grupo tengan el mismo valor. Ejemplo: una lista de Lunes, Martes .. *Viernes ... donde solo uno a la vez está seleccionado.
---- Vínculo: Es básicamente un botón, pero tiene como sufijo '...' y viene a indicar que es complejo y que se abre una ventana donde hay más opciones. Como botón, su acción será pués abrir dicha ventana... En consola, este tipo no suele tener cabida, pués puede complicar demasiado el tratamiento gráfico...
---- Elección/Scroll: Es un tipo que es idéntico a Activación, excepto que admite múltiples estados, no solo 2. El valor elegido en cada ocasión se hará constar a la derecha. por ejemplo: "Dia de la Semana": 5 - Viernes'. Es fácil comprender que este modelo es un modelo resumido de 'opcion' cuando es posible (sus valores son contiguos y elegibles secuencialmente, y solo uno entre todos puede estar elegido a la vez).
---- Texto: es un tipo de menú que básicamente es un botón, su funcionalidad es la misma, tiene la particularidad extra de que el contenido textual del mismo puede cambiar (el texto, no lo que llamamos su valor) Ejemplo: cuando se abre un fichero, se guarda en una pequeña lista el nombre de los x últimos ficheros distintos abiertos, el contenuido textual así es el nombre dle fichero, durante diseño, puede pués no conocerse siguiqera el texto que tendrá.
Una vez decidido que tipos de menú son los que encajan en nuestro diseño y modificando los que fueren de un tipo pero que se haya elegido otros en su remplazao, hay que ser cogerentes con la decisión para hacer la funcionalidad correcta tanto en diseño como en ejecución.
Finalmente solo faltan las funciones que hacen el trabajo.
La más simple es borrar el menú previo, pero lo normal es que partamos de 0, así que la primera acción sería mostrar el menú base, como usamos un array, el índice 0 del array es la raíz. Luego si abirmos el menú completo, se invocará justo ese índice al empezar...
funcion Main
CargarItems // los datos del array se cargan aquí.
DibujarSubmenu( 0)
fin funcion
Al reusar el menú en otras aplicaciones si somos coherentes con las decisiones tomadas y por tanto el diseño final, lo único que encesitaremos cambiar en futuros proyectos, será precisamente los datos de la función 'CargarItems'
Para no hablar en hueco, vamos a simular un menú ficticio, pero muy real, que no sea demasiado extendo, pero que tenga ejemplos de cada cosa... resulta al caso muy útil el menú de la calculadora: Pongo los ítems en árbol, primeramente solo el texto, para reunir todos y considerarlos en conjunto, cuantos son y cuales, etc... pero véase en cada paso como vamos completando los datos...
(pulsa en la interfaz en 'hex', para cambiar el menú que se muestra en view, pués hay menús ocultos que cambian según la opción elegida, no es necesario complicarlo con grados, radianes...)
----------------------------------
Edit
Copy
Paste
View
Standard
Scientific
-
Hex
Decimal
Octal
Binary
-
Qword
Dword
Word
Byte
-
Digit Grouping
Help
Help topics
-
About calculator
----------------------------------
Lo primero que se ve es que no haun menú raíz... y faltan menús títulos, porque es algo intuitivo, así mismo, los separadores, no tienen aclaración se deja una vez más a la intuición del usuario entender cada grupo.
Podemos canonizarlos mejor así (señalo a la derecha los ítems añadidos o modificados notoriamente):
----------------------------------
Menu Raiz <---------
Edit
Copy
Paste
Volver <---------
View
Tipo <---------
Standard
Scientific
Volver <---------
Base Decimal <---------
Hex
Decimal
Octal
Binary
Volver <---------
Tipo de dato <---------
Qword
Dword
Word
Byte
Volver <---------
Extra <---------
Digit Grouping
Volver <---------
Volver <---------
Help
Help topics
About <---------
About calculator
Volver <---------
Volver <---------
Exit <---------
----------------------------------
1 - Como se ve, de entrada hemos desplazado a la derecha todos los ítems, para añadir el menú raíz.
El menú raíz, podría ser el índice -1, pero determinados lenguajes no permiten índices negativos, luego el índice 0, también es acorde y lo consideraremos así...
2 - Lo otro que vemos, es que donde habúa un separador lo hemos remplazado por un título, para recoger más debidamente los ítems que tiene debajo, después de todo en consola no es práctico mostrar más de una docena de ítems. De este modo los subítems de cada 'padre' se desplazan a su derecha, y así el menú más grande no tendrá más de 4 ó 5 ítems.
3 - Algo que también se es que al 'canonizar' así el menú, queda suficientemente claro qué ítems son hijos de cual y qué items son padres de otros. Entonces ahora vamos a ir señalando el tipo que es de cada menú, primero los ponemos en una enumeración, y luego indicaremos en la lista de qué tipo es cada uno.
4 - Otra notariedad es que se ha añadido los ítems 'volver'
5 - Finalmente se ve que he añadido un ítem más al menú raíz 'Exit'... que es en el mismo 'volver que en raíz, conviene que tenga ese texto diferente.
Enumeracion TiposDeMenu
MENU_TIPO_BOTON = 0
MENU_TIPO_ACTIVACION = 1
MENU_TIPO_OPCION = 2
MENU_TIPO_VINCULO = 3
MENU_TIPO_TITULO = 8
MENU_TIPO_LIMITE = 15
fin enumeracion
Como se ve, no he hecho constar todos los que decíamos más arriba, cámbiese según uno elija.
Los menús de tipo acción están arriba y los menús de tipo organizador, debajo... estos actúan como separadores, si hubiera un menú más complejo que contuviera demasiados hijos y más de una lista de opciones podría verse útil añadir un MENU_TIPO_SEPARADOR = 9 (por ejemplo), también si uno elige remplazar la funcionalidad del campo 'Hijos', por la cuenta que haya entre 'primerhijo' y un menú tipo separador... (éste sería hijo también de aquel, pero no cuenta sería oculto, no se dibujaría, o bien se dibujaría como una línea '-----------------------'.
Para las explicaciones he elegido que la separación sea con menús tipo título.
Ahora pongamos a la izquierda el tipo de cada menú.
----------------------------------
08 Menu Raiz
08 Edit
00 Copy
00 Paste
15 Volver
08 View
08 Tipo
02 Standard
02 Scientific
15 Volver
08 Base Decimal
02 Hex
02 Decimal
02 Octal
02 Binary
15 Volver
08 Tipo de dato
02 Qword
02 Dword
02 Word
02 Byte
15 Volver
08 Extra
01 Digit Grouping
15 Volver
15 Volver
08 Help
00 Help topics
08 About
03 About calculator
15 Volver
15 Volver
15 Exit
----------------------------------
Hay al menos un ejemplo de cada tipo de menú en la enumeración...
Ahora que queda también claro quien es padre y quien hijo de qien, puede indicarse el índice de cada uno. También a la izquierda dle todo, de cada menú. Recordar que cada hijo va contiguo. Se puede, escribir directamente como arriba, o se pueden ya desmontar en grupos, con su padre encima:
-------------------------------
00 08 Menu Raiz
01 08 Edit
02 08 View
03 08 Help
04 15 Exit
-------------------------------
01 08 Edit
05 00 Copy
06 00 Paste
07 15 Volver
-------------------------------
02 08 View
08 08 Tipo
09 08 Base Decimal
10 08 Tipo de dato
11 08 Extra
12 15 Volver
-------------------------------
08 08 Tipo
13 02 Standard
14 02 Scientific
15 15 Volver
-------------------------------
09 08 Base Decimal
16 02 Hex
17 02 Decimal
18 02 Octal
19 02 Binary
20 15 Volver
-------------------------------
10 08 Tipo de dato
21 02 Qword
22 02 Dword
23 02 Word
24 02 Byte
25 15 Volver
-------------------------------
11 08 Extra
26 01 Digit Grouping
27 15 Volver
-------------------------------
03 08 Help
28 00 Help topics
29 08 About
30 15 Volver
-------------------------------
29 08 About
31 03 About calculator
32 15 Volver
-------------------------------
Es fácil ver si hay errores, comprobando algunas cosas:
Cada menú de tipo titulo '08', encabeza siempre un submenú, contiene ítems y debe acabar en un tipo limite '15'
Níngún índice puede estar repetido, es decir cada índice aloja solo a un ítem, no equivocarse.
No es imprescindible que vayan seguidos, puede haber índices sin usar entre un submenú y otro, especialmente si uno considera que a futuro pudiera añadir más ítems.
Falta indicar algunas cosas, para cada tipo 'titulo' por ser padre, cual es el índice de su primer hijo así como la cantidad de hijos que tiene. Pondré solo 2 submenús como ejemplo..., dichos valores, los pondremos a la derecha del tipo:, así de momento van:
item = indice tipo numhijos indexprimerhijo texto
-------------------------------
00 08 04 01 Menu Raiz
01 08 03 05 Edit
02 08 05 08 View
03 08 03 28 Help
04 15 00 00 Exit
-------------------------------
Como se ve si no tiene hijos, porque el menú no e spadre el valor será 0, y por lo mismo indexprimerhijo, no importa, pero se pone 0. pogo dos cifras, solo para mantener la verticalidad y con ello una alineación visual adecuada, en el código no precisaos 0 a la derecha.
Otro ejemplo (que a diferencia del previo que casi todos sun subitems son submenús), cuyos hijos no son submenús:
-------------------------------
10 08 05 21 Tipo de dato
21 02 00 00 Qword
22 02 00 00 Dword
23 02 00 00 Word
24 02 00 00 Byte
25 15 00 00 Volver
-------------------------------
Podemos ver la línea BNF de interés:
item = indice tipo numhijos indexprimerhijo texto
Todavía nos falta fijar algunas cosas, como el estado inicial de algunos ítems si están seleccionados.
Es tan simple como reservar un espacio más a la derecha de esos valores e indicar con '*' el ítem que esté seleccionado y con '-' el ítem que no tenga su estado seleccionado (incluso aunque el tipo no admita un estado de selección)
-------------------------------
10 08 05 21 - Tipo de dato
21 02 00 00 - Qword
22 02 00 00 - Dword
23 02 00 00 * Word
24 02 00 00 - Byte
25 15 00 00 - Volver
-------------------------------
Como se ve, en este submenú, el ítem seleccionado cuan do se dibuje por vez primera el menú, será el 23 'Word'....
Actualizamos la línea que explica cada ítem:
item = indice tipo numhijos indexprimerhijo seleccionado texto
Y con esto básicamente podemos definir en texto todoel menú. Ahora la función 'CargarItems', podrá entenderse fácilmente:
Devuelve ´la cantidad de ítems que tiene el menú.
entero = funcion CargarItems
entero numItems = 33
Array de ItemMenu Items(0 a numItems -1)
Items(0 ) = GetItem("0 8 4 1 -:Menu Raiz")
Items(1 ) = GetItem("1 8 3 5 -:Edit")
Items(2 ) = GetItem("2 8 5 8 -:View")
Items(3 ) = GetItem("3 8 3 28 -:Help")
Items(4 ) = GetItem("4 15 0 0 -:Exit")
.
.
.
Items(23) = GetItem("23 02 0 0 *:Word")
Items(24) = GetItem("24 02 0 0 -:Byte")
Items(25) = GetItem("25 15 0 0 -:Volver <")
Items(26) = GetItem("26 01 0 0 *:Digit Grouping")
.
.
.
Items(32) = GetItem("32 15 0 0 -:Volver <")
devolver numItems
fin funcion
La función 'getitem' toma un string en el formato 'item' y lo convierte en un elemento de la estructura ItemMenu:
item = indice tipo numhijos indexprimerhijo seleccionado texto
ItemMenu = funcion GetItem(string txtItem)
ItemMenu im
array partes(0 a 1) = split(txtItem, ":") // es necesario porque el texto puede tener más de una palabra.
array string datos()
datos = Split(parte(0), " ") // crea un array cortando el texto entrado en tantos elementos como espacios haya entre medias...
im.Indice = datos (0).ParseInteger
im.Tipo = datos (1).ParseInteger
si im.Tipo = MENU_TIPO_TITULO
im.Hijos = datos (2).ParseInteger
im.PrimerHijo = datos (3).ParseInteger
sino
im.Hijos = 0
im.PrimerHijo = 0
fin si
Si (datos (4) = "*") im.Seleccionado = TRUE
si im.Tipo = MENU_TIPO_LIMITE
im.Texto = partes(1) + " < "
Sino
im.Texto = partes(1)
fin si
devolver im
fin funcion
Yendo un poquito más lejos, la función 'CargarItems', podría ser más abstracta, y en vez de reterner ahí el texto del menú, leer un fichero que contiene el texto, se lle una línea de cada vez, esa línea es el índice para el array que se va incrementando, si la línea está vacía se omite y se lee la siguiente (por claridad sería adecuado dejar en un fichero líneas en blanco).
Así dicha función recibe como entrada la ruta del fichero a leer, y cada línea será el contenido de 1 ítem. con esto el códig de un menú prácticamente se puede reusar en otros proyectos sin más cambios que señalar la ruta del fichero que contiene el menú deseado que se debe cargar... Si no se quiere que el fichero esté suelto ante la posibilidad de que un usuario lo elimine, se adjunta como recurso al programa...
Como ya queda un mensaje largo, corto, y a ver si mañana saco otro tiempito para explicar por encima las funciones que manejan el menú.