Es probable que quieras añadir un menú de “Ítems Recientes” en tus aplicaciones Desktop de modo que puedas proporcionar un acceso rápido a los últimos archivos abiertos en tu app. Sigue leyendo para ver una forma en la que se puede implementar esta capacidad.
Una vez que hayas creado un nuevo proyecto Xojo Desktop, y dado que queremos crear un menú dinámico, lo primero que haremos será crear una nueva clase. Esta será una subclase de la clase MenuItem
, de modo que podamos implementar en ella el manejador de evento Action
. Este evento es el que se disparará cada vez que el usuario seleccione una entrada de entre las disponibles en el submenú “Recent Items” que colgará del menú File.
Puedes descargar el proyecto de ejemplo de este tutorial desde este enlace.
Por tanto, añade una nueva clase al proyecto utilizando los siguientes datos en el Panel Inspector:
- Nombre de Clase: MyMenuItem
- Super: MenuItem
A continuación, añade un Delegado a la subclase utilizando los siguientes datos en el Panel Inspector:
- Nombre del Delegado: MenuItemCallBack
- Parámetros: Caption As String, Value As String
- Ámbito: Public
A continuación, añade una nueva propiedad a la clase utilizando estos datos en el Panel Inspector:
- Nombre: pMenuItemCallback
- Tipo: MenuItemCallBack
- Ámbito: Public
El propósito de esta propiedad es almacenar una referencia a la dirección del método que se invocará cuando el usuario seleccione el elemento de menú basado en esta clase (es decir, un ítem reciente en el submenú “Ítems Recientes”).
Lo último que nos queda por hacer en la definición de nuestra subclase es añadir el manejador de evento Action
. Una vez añadido, escribe la siguiente línea de código en el Editor de Código asociado:
if pMenuItemCallback <> nil then pMenuItemCallback.Invoke(me.Text, me.Tag)
Como puedes ver, se limita a invocar el método apuntado por la propiedad pMenuItemCallback
pasando como parámetros los contenidos de las propiedades Text
y Tag
de la instancia. En nuestro ejemplo, Text
se corresponderá con el nombre del archivo, mientras que la propiedad Tag
del MenuItem contendrá la ruta nativa (NativePath
) que apunta al archivo real en disco.
Con nuestra subclase ya completa, el segundo paso consiste en proporcionar un modo de crear instancias de MyMenuItem
y añadirlas al elemento de menú deseado. Para mantener las cosas sencillas en nuestra aplicación de ejemplo, nos limitaremos a incluir un botón en la interfaz de usuario de la app que, cuando se pulse, permitirá que el usuario seleccione cualquier archivo. Luego creará una nueva instancia de MyMenuItem
que referenciará a dicho archivo y que se añadirá al submenú Recent Items del menú File en la barra de menús.
Además, mantendremos un registro de los archivos añadidos en un Array de Strings.
Por tanto, selecciona la ventana Window1
en el proyecto para acceder al Editor de Diseño y añade un nuevo botón desde la Librería, utilizando los siguientes valores en el Panel Inspector:
- Nombre: AddItem
- Caption: Add Recent Item
A continuación, incorpora el evento Action
al botón recién añadido y escribe el siguiente código en el Editor de Código asociado:
// Muestra un diálogo para que el usuario pueda seleccionar un archivo Var f As FolderItem = FolderItem.ShowOpenFileDialog("") If f <> Nil Then // Añade el nombre y la ruta nativa del archivo seleccionado, separados mediante el caracter tabulador app.RecentItemsCollection.AddRow(f.DisplayName + Chr(9) + f.NativePath) // Crea una nueva instancia de menú a partir de la clase MyMenuItem, definiendo el nombre, tag // y la propiedad con el método que se invocará. // Luego, añade el nuevo elemento de menú al submenú FileRecentItems. Var mi As New MyMenuItem mi.Text = f.DisplayName mi.Tag = f.NativePath mi.pMenuItemCallback = WeakAddressOf app.recentItemCallback FileRecentItems.AddMenu(mi) End If
Como puedes ver aquí, necesitamos crear una propiedad de array “RecentItemsCollection” bajo el objeto App, y asegurarnos de que el nombre del submenú en el menú File sea “FileRecentItems”.
Selecciona el objeto App y añade una nueva propiedad con el nombre “RecentItemsCollection() As String”. Mantén su ámbito (Scope) con el valor Public.
A continuación, selecciona el item MainMenuBar en el Navegador de Proyecto para acceder al Editor de Menú. Luego, haz clic en el menú File y añade un nuevo elemento de menú haciendo clic en el cuarto botón desde la derecha en la barra de herramientas del editor. Esta acción añadirá un nuevo elemento de menú bajo el menú File.
Usa el panel Inspector para definir los siguientes valores en el elemento de menú recién añadido:
- Nombre: FileRecentItems
- Texto: Recent Items
- Submenu: Enabled
Activar todos los Ítems Recientes
Probablemente querremos que tanto el menú “Recent Items” como todas las entradas de dicho submenú estén activas por omisión cada vez que el usuario haga clic sobre el menú File. Para ello, tenemos que añadir el manejador de evento EnableMenuItems
en el objeto App. Este es el evento que se disparará cada vez que el usuario haga clic en un item de la barra de menú, dándonos así una oportunidad para decidir qué ítems se mostrarán activados y cuales no.
Una vez hayas añadido EnableMenuItems
, escribe el siguiente código en el Editor de Código asociado:
FileRecentItems.Enabled = True For n As Integer = 0 To FileRecentItems.LastRowIndex FileRecentItems.MenuAt(n).Enabled = True Next
Como puedes ver, estamos activando FileRecentItems
y todos los elementos que se puedan haber añadido a dicho submenú. Para ello sólo hemos de iterar sobre todos los elementos descendientes y acceder a ellos mediante el método MenuAt
para definir su propiedad Enabled
también al valor True
.
Cargar y Guardar los Ítems Recientes
Por ahora sabemos cómo crear nuevos ítems de menú y añadirlos de forma dinámica en el submenú deseado; así como activarlos cuando el usuario hace clic sobre la barra de menús. Pero aún nos falta por implementar algunas piezas importantes. Por ejemplo, aun tenemos que añadir el método RecentItemsCallback
al objeto App, dado que este es el método asignado como delegado a cada nueva instancia de MyMenuItem
(es decir, el método que nos permitirá saber qué elemento de menú ha sido seleccionado por el usuario).
Por tanto, selecciona el objeto App en el Navegador de Proyecto y añade un nuevo método usando los siguientes valores en el Panel Inspector:
- Nombre de Método: RecentItemCallback
- Parámetros: Caption as string, Value as String
- Ámbito: Public
A continuación, escribe la siguiente línea de código en el Editor de Código asociado:
MessageBox "Recent Item Selected" + endofline + endofline + "Selected item: " + Caption + EndOfLine + endofline + "File Path: " + Value
Nuestro proyecto de ejemplo se limita mostrar un diálogo con el nombre del item seleccionado y la ruta nativa que apunta al archivo subyacente. Probablemente querrás hacer algo más interesante en tus propios proyectos usando estos datos, como por ejemplo abrir un nuevo documento para editar el archivo seleccionado.
Pero nada de esto sería muy útil si no pudieses guardar las entradas recientes para cargarlas posteriormente cada vez que se ejecutase la aplicación de ejemplo (¡o tus propias apps!). Por lo tanto, esto será lo que hagamos a continuación.
Vamos a ocuparnos en primer lugar de guardar los “Ítems Recientes” existentes. Lo haremos usando un archivo de texto almacenado en la ruta apuntada por SpecialFolder.ApplicationData
; de modo que nos asegure que el archivo se guardará en la ubicación correcta en cada uno de los sistemas operativos soportados (incluso si planeas distribuir tus apps en modo Sandbox). Por supuesto, puedes optar por guardar dicha información en formato JSON, XML o en una base de datos. En tal caso, deberías de modificar el código proporcionado para procesar los datos acorde al formato utilizado.
Guardaremos los ítems recientes cuando el usuario salga de la aplicación, de modo que tiene sentido añadir el código en el manejador de evento Close del objeto App. Una vez añadido, escribe el siguiente fragmento de código en el Editor de Código asociado:
Try Var f As FolderItem = SpecialFolder.ApplicationData.Child("RecentItems") If f.Exists Then f.Remove Var tos As TextOutputStream = TextOutputStream.Open(f) For Each item As String In RecentItemsCollection tos.WriteLine item Next tos.Close Catch e As IOException MessageBox e.Message End Try
No hay nada excepcional en este código. Simplemente creamos un TextOutputStream
sobre el FolderItem
“RecentItems” (que se creará en la ruta apuntada por ApplicationData
), guardando cada una de las entradas almacenadas en RecentItemsCollection
, línea a línea.
¿Cómo podemos recuperar esta información, recuperando los elementos de menú la siguiente vez que el usuario ejecute la app? Esto es algo que haremos en el manejador de evento Open en el objeto App. Selecciónalo en el Navegador de Proyecto y escribe el siguiente fragmento de código en el Editor de Código asociado:
Var f As FolderItem = SpecialFolder.ApplicationData.Child("RecentItems") If f <> Nil And f.Exists Then Try Var tis As TextInputStream = TextInputStream.Open(f) Var recentItems() As String = tis.ReadAll.ToArray(EndOfLine) Var mi As MyMenuItem For Each item As String In recentItems app.RecentItemsCollection.AddRow item If item <> "" Then mi = New MyMenuItem mi.Text = item.NthField(Chr(9),1) mi.Tag = item.NthField(Chr(9),2) mi.pMenuItemCallback = WeakAddressOf RecentItemCallback FileRecentItems.AddMenu(mi) End If Next tis.Close Catch e As IOException MessageBox e.Message End Try End If
Simplemente estamos haciendo la operación opuesta: apuntando al archivo “RecentItems” que debería de encontrarse en el directorio apuntado por ApplicationData
y abriéndolo como un TextInputStream
. A continuación, creamos un array temporal con todos los contenidos del archivo, usando el caracter EndOfLine
como separador para obtener cada una de las entradas en el array.
A continuación iteramos el array temporal para recrear los items de menú y añadirlos al submenú FileRecentItems
. ¡Así de simple!
Conclusión
Esta es una forma de implementar el submenú “Recent Items” en tus apps desktop. Por supuesto hay espacio para la mejora. Por ejemplo, puedes comprobar si un ítem ya ha sido añadido al submenú Recent Items (pista: quizá quieras utilizar el array RecentItemsCollection para ello), o bien limitar la cantidad máxima de elementos a mostrar en el menú de items recientes.