Crear accesos directos a un menú
Hace tiempo me topé con un tema similar a este, aunque con un enfoque diferente; En aquel caso se trataba de que el usuario pudiera crear su propio menú personalizado dentro de una aplicación. Es decir, que además de las opciones propias de la aplicación, el usuario pudiera configurarse un menú con las opciones que más deseara utilizar o tener más «a mano». En este caso, y a partir de este hilo en los foros del Clubdelphi, se ha planteado la posibilidad de que un usuario pueda crearse sus propios «accesos directos» a opciones del menú.
La solución en aquel momento pasó por «volcar» el contenido del menú a otro componente (en ese caso un TreeView, de forma similar a cómo se ve en este ejemplo) y desde ese, generar la estructura del nuevo punto de menú arrastrando elementos.
Para el problema de generar accesos directos, se me antoja que se pueda usar un sistema similar.
(1) «Volcar» el contenido del menú hasta otro componente que nos permita trabajar con los elementos del menú (ya que ni el menú ni los ítems poseen opciones para arrastrar -Drag & Drop-). Este esta caso vamos a utilizar un componente (TListBox) donde almacenaremos los elementos y los apuntadores a los ítems del menú (propiedad Objects).
procedure TFormMain.Button1Click(Sender: TObject); var i:integer; str:string; // Recursiva para obtener los subItems procedure GetItems(mi:TMenuItem); var i:Integer; begin for i := 0 to (mi.Count - 1) do begin Str := mi.Items[i].Caption; ListBox1.Items.AddObject(Str, mi.Items[i]); // SubItems de este GetItems(mi.Items[i]); end; end; begin // Recorerr menu principal for i := 0 to (MainMenu1.Items.Count - 1) do begin Str := MainMenu1.Items[i].Caption; ListBox1.Items.AddObject(Str, MainMenu1.Items[i]); // SubItems de este GetItems(MainMenu1.Items[i]); end; end; |
Con este código poblamos el ListBox con los Caption(Text) de los elementos del menú, y lo que es más importante, los apuntadores a cada elementos que se guardar al utilizar AddObject.
(2) ¿Cómo crear un acceso directo que permita ejecutar una opción de menú? Para ello podemos utilizar un TImage que sobre el cual programaremos el evento OnDblClick/OnClick.
Crear el componente es sencillo, y se puede ver código de ejemplo de cómo hacerlo en estas entradas:
- Crear/destruir componentes enb runtime y moverlos con el ratón.
- Mover controles de un form en rutime.
La idea es que cada «acceso directo» posea un apuntador al elemento de menú correspondiente para poder ejecutar el código programado en el OnClick o en la TAction asociada a ese elemento del menú. Lo lógico sería utilizar una propiedad del propio componente (Data, Object,….) que nos permitiera enlazar directamente. No es el caso del TImage, así que en el ejemplo utilizaremos el propio ListBox como el «contenedor» de los apuntadores (como una lista intermedia), aunque como he dicho, la solución ideal, sería que cada «acceso directo» tuviera un puntero «directo» al TMenItem asociado.
El código paras crear el componente y gestionar esa asociación podría ser similar a este:
procedure TFormMain.Button2Click(Sender: TObject); var img:TImage; mi:TMenuItem; begin If ListBox1.ItemIndex = -1 then begin MessageDlg('Selecciona un elemento de la llista', mtWarning, [mbOK], 0); Exit; end; // Item del menu mi := TMenuItem(ListBox1.Items.Objects[ListBox1.ItemIndex]); // Tiene asignado el OnClick? if Assigned(mi.OnClick) then begin // Nada end else begin // Tiene asignada la action? if Assigned(mi.Action) then begin //signado OnExecute if Assigned(mi.Action.OnExecute) then begin // Nada end else begin MessageDlg('Ese elemento no tiene nada que hacer asignado', mtWarning, [mbOK], 0); Exit; end; end else begin MessageDlg('Ese elemento no tiene nada que hacer asignado', mtWarning, [mbOK], 0); Exit; end; end; Randomize; // Elemento seleccionado img := TImage.Create(nil); img.Parent := Panel1; img.Height := 32; img.Width := 32; img.Left := Random(panel1.Width - img.Width); img.Top := Random(panel1.Height - img.Height); img.Stretch := True; img.Transparent := True; // El TAG es la posicion en la lista img.Tag := ListBox1.ItemIndex; // Item del menu mi := TMenuItem(ListBox1.Items.Objects[ListBox1.ItemIndex]); // Asignar la imagen ImageList1.GetBitmap(mi.ImageIndex, img.Picture.Bitmap); // Asignar el evento img.OnClick := MyImgClick; end; |
Primero se realizan unas comprobaciones para detectar si posee alguna acción asignada (sea OnClick o TAction) y posteriormente se crea el TImage, se configura y se asigna como TAG el ItemIndex del ListBox (que es este caso estamos utilizando como estructura intermedia para guardar el apuntador al TMenuItem).
Finalmente sólo quedar crear el procedimiento MyImgClick, que ejecutará el código asignado al elemento del menú cuando se presione sobre la imagen asociada. Se incluyen comprobaciones similares a las anteriores, por si el elemento no tiene nada asignado y se tiene en cuenta también que haya código en el OnClick del TMenuItem o exista una TAction asociada.
var i:integer; str:string; mi:TMenuItem; begin // Test del sender if not (sender is TImage) then begin Exit; end else begin i := TImage(Sender).Tag; Str := ListBox1.Items[i]; end; // Acceder a la opción de menú mi := TMenuItem(ListBox1.Items.Objects[i]); // Asignado código? if Assigned(mi.OnClick) then begin mi.OnClick(nil); Exit; end else begin // Tiene asignada la action? if Assigned(mi.Action) then begin // Asignado OnExecute if Assigned(mi.Action.OnExecute) then begin mi.Action.OnExecute(nil); Exit; end; end end; MessageDlg('No hay nada asignado a esa opción...', mtInformation, [mbOK], 0); end; |
Se puede mejorar y «refinar» bastante más, pero creo que la idea queda clara. A partir de aquí cada uno que «añada» lo que quiera. Cualquier sugerencia será bien recibida.
El código del ejemplo se puede descargar desde aquí.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Hola Neftalí,
Entro todas las semanas en este blog y la idea de utilizar ADO con Threads fue muy interesante.
Pero esta vez no entiendo (o no encuentro uso) para que serviría lo de los accesos directos. ¿No sería suficiente con una TToolbar?
La intención con la que escribo es para saber que utilidad tendría esto que publicas, espero no molestar.
Saludos y a seguir con el blog.
Hola Juan.
No molesta, en absoluto. La idea original es la de que un usuario pueda crear sus accesos directos sobre el «fondo» de la aplicación. Algo así como si trabajaras sobre el escritorio, pero dentro de tu aplicación.
De todas formas, no deja de ser un ejemplo; Creo que lo más importante en el ejemplo es la interacción con los elementos del menu, que puede servir para lo que he comentado o para otras cosas.
Un saludo.
Hola Neftalí!
Ahora entiendo mejor el ejemplo. Quizás me confundí al estar pensando en las TToolBar (y TActionToolBar) y la capacidad de configuración sin apenas código.
Me quedo con la idea de escritorio propio de la aplicación.
Además me he decidido a probar con un MDI. Ha sido muy sencillo reutilizar tu ejemplo para convertirlo a una aplicación MDI y gestionar los accesos directos desde otro form.
A partir de aquí se puede crear una interfaz de programa muy personalizada.
Gracias por el ejemplo y hacerme ver su utilidad.
Saludos!
Muchas gracias, funciona a la perfección.