Tutorial de Java

Menús

Anterior | Siguiente

Los menús están mucho mejor desarrollados y son más flexibles en Swing que los que se encuentran habitualmente en otras herramientas, incluyendo paneles y applets. La sintaxis para utilizarlos es la misma que en el AWT, y se conserva el mismo problema que también presentan los menús en el AWT, es decir, que hay que codificar los menús directamente y no hay soporte para, por ejemplo, recursos; que, entre otras cosas, facilitaría la conversión entre lenguajes de los textos de los menús. Así que, hay veces en que el código se hace demasiado largo y feo. En el ejemplo siguiente, java1411.java, se intenta dar un paso hacia la resolución de este problema colocando toda la información de cada menú en un array de elementos de tipo Object de dos dimensiones, para facilitar la introducción de cualquier cosa en el array. Este array está organizado de tal forma que la primera fila representa el nombre del menú y las siguientes filas representan los elementos u opciones del menú y sus características. Las filas del array no tienen por que ser uniformes, porque el código sabe perfectamente donde se encuentra, así que no importa que las filas sean completamente diferentes.

import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;

public class java1411 extends JPanel {
  static final Boolean bT = new Boolean( true ); 
  static final Boolean bF = new Boolean( false );
  static ButtonGroup grupoBotones;
  
  // Clase que se utiliza para crear los distintos tipos de menús que se
  // va a presentar en la ventana
  static class TipoMenu { 
    TipoMenu( int i ) {} 
  };
  
  static final TipoMenu mi = new TipoMenu( 1 ); // Menú con elementos normales
  static final TipoMenu cb = new TipoMenu( 2 ); // Menú con cajas de selección
  static final TipoMenu rb = new TipoMenu( 3 ); // Menú con botones de radio
  JTextField txt = new JTextField( 10 );
  JLabel lbl = new JLabel( "Icono Seleccionado",java1410.imgs[0],
			   JLabel.CENTER );
  ActionListener al1 = new ActionListener() {
    public void actionPerformed( ActionEvent evt ) {
      txt.setText( ((JMenuItem)evt.getSource() ).getText() );
    }
  };
  ActionListener al2 = new ActionListener() {
    public void actionPerformed( ActionEvent evt ) {
      JMenuItem mi = (JMenuItem)evt.getSource();
      lbl.setText( mi.getText() );
      lbl.setIcon( mi.getIcon() );
    }
  };
  // En estas estrcuturas se almacenas los datos de los menús como si se
  // tratara de los típicos recursos de X
  public Object menuArchivo[][] = {
    // Nombre del menú y tecla rápida asociada
    { "Archivo",new Character('A') },
    // Nombre, tipo, tecla rápida, receptor asociado, habilitado o no
    // para cada uno de los elementos del menú
    { "Nuevo",mi,new Character('N'),al1,bT },
    { "Abrir",mi,new Character('b'),al1,bT },
    { "Guardar",mi,new Character('G'),al1,bF },
    { "Guardar como...",mi,new Character('c'),al1,bF },
    { null }, // Separador
    { "Salir",mi,new Character('S'),al1,bT },
  };
  public Object menuEdicion[][] = {
    // Nombre del menú y tecla rápida asociada
    { "Edicion",new Character('E') },
    // Nombre, tipo, tecla rápida, receptor asociado, habilitado o no
    { "Cortar",mi,new Character('t'),al1,bT },
    { "Copiar",mi,new Character('C'),al1,bT },
    { "Pegar",mi,new Character('P'),al1,bT },
    { null }, // Separator
    { "Seleccionar Todo",mi,new Character('S'),al1,bT },
  };
  public Object menuIconos[][] = {
    // Nombre del menú y tecla rápida asociada
    { "Iconos",new Character('I') },
    // Se le añade un último elemento opcional que corresponde al
    // icono que se presenta en medio de la ventana
    { "Icono 0",rb,new Character('0'),al2,bT,java1410.imgs[0] },
    { "Icono 1",rb,new Character('1'),al2,bT,java1410.imgs[1] },
    { "Icono 2",rb,new Character('2'),al2,bT,java1410.imgs[2] },
    { "Icono 3",rb,new Character('3'),al2,bT,java1410.imgs[3] },
    { "Icono 4",rb,new Character('4'),al2,bT,java1410.imgs[4] },
  };
  public Object menuOpciones[][] = {
    // Nombre del menú y tecla rápida asociada
    { "Opciones",new Character('O') },
    // Nombre, tipo, tecla rápida, receptor asociado, habilitado o no
    { "Opcion 1",cb,new Character('1'),al1,bT },
    { "Opcion 2",cb,new Character('2'),al1,bT },
  };
  public Object menuAyuda[][] = {
    // Nombre del menú y tecla rápida asociada
    { "Ayuda",new Character('y') },
    // Nombre, tipo, tecla rápida, receptor asociado, habilitado o no
    { "Indice",mi,new Character('I'),al1,bT },
    { "Contenido",mi,new Character('C'),al1,bT },
    { null }, // Separator
    { "Acerca de...",mi,new Character('A'),al1,bT },
  };
  public Object barraMenu[] = {
    menuArchivo,menuEdicion,menuIconos,menuOpciones,menuAyuda,
  };
  
  static public JMenuBar creaMenuBarra( Object barraMenuDato[] ) {
    JMenuBar barraMenu = new JMenuBar();
    
    for( int i=0; i < barraMenuDato.length; i++ )
      barraMenu.add( creaMenu((Object[][])barraMenuDato[i]) );
    return( barraMenu );
  }
  
  static public JMenu creaMenu( Object[][] menuDato ) {
    JMenu menu = new JMenu();
    
    menu.setText( (String)menuDato[0][0] );
    menu.setMnemonic( ((Character)menuDato[0][1]).charValue() );
    grupoBotones = new ButtonGroup();
    for( int i=1; i < menuDato.length; i++ ) {
      if( menuDato[i][0] == null )
        menu.add( new JSeparator() );
      else
        menu.add( creaMenuItem( menuDato[i] ) );
    }
    return( menu );
  }
  
  static public JMenuItem creaMenuItem( Object[] dato ) {
    JMenuItem m = null;
    TipoMenu tipo = (TipoMenu)dato[1];
    
    if( tipo == mi )
      m = new JMenuItem();
    else if( tipo == cb )
      m = new JCheckBoxMenuItem();
    else if( tipo == rb ) {
      m = new JRadioButtonMenuItem();
      grupoBotones.add( m );
    }
    m.setText( (String)dato[0] );
    m.setMnemonic( ((Character)dato[2]).charValue() );
    m.addActionListener( (ActionListener)dato[3] );
    m.setEnabled( ((Boolean)dato[4]).booleanValue() );
    // Y ahora el caso opcional de los iconos
    if( dato.length == 6 )
      m.setIcon( (Icon)dato[5] );
    return( m );
  }
  
  java1411() {
    setLayout( new BorderLayout() );
    add( creaMenuBarra( barraMenu ),BorderLayout.NORTH );
    JPanel p = new JPanel();
    p.setLayout( new BorderLayout() );
    p.add( txt,BorderLayout.NORTH );
    p.add( lbl,BorderLayout.CENTER );
    add( p,BorderLayout.CENTER );
  }
  
  public static void main(String args[]) {
    java1411 panel = new java1411();
    JFrame ventana = new JFrame();
    
    ventana.getContentPane().add( panel,BorderLayout.CENTER );
    
    ventana.addWindowListener( new WindowAdapter() {
      public void windowClosing( WindowEvent evt ) {
	System.exit( 0 );
      }
    } );
    
    ventana.setSize( 300,200 );
    ventana.setTitle( "Tutorial de Java, Swing" );
    ventana.setVisible( true );
  }
} 

La intención del ejemplo anterior es permitir al programador el uso de tablas simples que representen a cada menú, en vez de que tenga que teclear líneas enteras para la construcción de esos menús. Cada tabla genera un menú, figurando en la primera fila el nombre del menú y el acelerador de teclado y en las siguientes los datos correspondientes a cada opción del menú, que son: la cadena de caracteres que se colocará como opción, el tipo de opción de que se trata, la tecla aceleradora, el receptor de eventos de tipo Action que se disparará cuando la opción sea seleccionada y, finalmente, un indicador para saber si la opción está habilitada o no. Si una fila empieza por un carácter nulo, se tratará como un separador.

Para evitar la tediosa creación de múltiples objetos booleanos y banderas, se han creado los valores estáticos al comienzo de la clase, bT y bF, para representar los booleanos y diferentes objetos de la clase TipoMenu para describir las opciones u elementos normales del menú (mi), elementos con cajas de selección (cb) y elementos de selección única o botones de radio (rb). Hay que tener en cuenta que un array de Object puede contener solamente objetos y no valores primitivos, por lo que no se puede usar int sino Integer, etc.

El ejemplo, cuya imagen en ejecución reproduce la figura anterior, también muestra como JLabels y JMenuItems, y sus descendientes, pueden manejar iconos. Un objeto Icon es colocado en JLabel a través de su constructor y cambiado cuando el correspondiente elemento del menú está seleccionado.

El array barraMenu está ordenado de la misma forma que se quiere que aparezcan los elementos en la barra. Se puede pasar este array al método creaMenuBarra(), que ya lo divide en los arrays individuales de datos del menú, pasando cada uno de estos últimos al método creaMenu(). Este método, por su parte, coge la primera línea del menú y crea un objeto JMenu a partir de ella, luego invoca al método creaMenuItem() para cada una de las siguientes líneas del array. Finalmente, el método creaMenuItem() chequea toda la línea y determina el tipo de menú y sus atributos, creando el elemento o item que corresponda. Al final, como se puede comprobar en el constructor del ejemplo, java1411(), para crear un menú desde estas tablas, es suficiente con la invocación del constructor de la forma:

creaMenuBarra( barraMenu ); 

y ya todo se maneja de forma recursiva.

En el ejemplo no se tienen en cuenta los menús en cascada, pero el lector ya estará en condiciones de añadir esta característica al ejemplo en caso de necesitarla.

Menús Popup

La implementación de JPopupMenu resulta un tanto extraña: antes de nada hay que llamar al método enableEvents() y seleccionar los eventos del ratón, en vez de utilizar un receptor de eventos como sería de esperar. Es decir, es posible añadir un receptor de eventos del ratón, pero el evento asociado, MouseEvent, no es capaz de devolver true desde el método isPopupTrigger(); aunque también puede que sea porque el JDK todavía está en fase beta; pero vamos, el comportamiento es ciertamente raro. A pesar de ello, el ejemplo java1412.java, sí que produce el efecto deseado de presentar el menú en la pantalla tal como se muestra:

ante la llegada del evento correspondiente a la activación del menú.

import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;

public class java1412 extends JPanel {
  JPopupMenu popup = new JPopupMenu();
  JTextField txt = new JTextField( 10 );
  
  public java1412() {
    add( txt );
    
    ActionListener al = new ActionListener() {
      public void actionPerformed( ActionEvent evt ){
        txt.setText( ((JMenuItem)evt.getSource()).getText() );
      }
    };
    
    JMenuItem elemento = new JMenuItem( "Carpanta" );
    elemento.addActionListener( al );
    popup.add( elemento );
    elemento = new JMenuItem( "Rompetechos" );
    elemento.addActionListener( al );
    popup.add( elemento );
    elemento = new JMenuItem( "Otilio" );
    elemento.addActionListener( al );
    popup.add( elemento );
    popup.addSeparator();
    elemento = new JMenuItem( "Mortadelo" );
    elemento.addActionListener( al );
    popup.add( elemento );
    enableEvents( AWTEvent.MOUSE_EVENT_MASK );
  }
  
  protected void processMouseEvent( MouseEvent evt ){
    if( evt.isPopupTrigger() )
      popup.show( evt.getComponent(),evt.getX(),evt.getY() );
    else
      super.processMouseEvent( evt );
  }
  
  public static void main( String args[] ) {
    JFrame frame = new JFrame( "Tutorial de Java, Swing" );
    
    frame.addWindowListener( new WindowAdapter() {
      public void windowClosing( WindowEvent evt ) {
        System.exit( 0 );
      }
    });
    
    frame.getContentPane().add( new java1412(),BorderLayout.CENTER );
    frame.setSize( 200,150 );
    frame.setVisible( true );
  }
} 

El mismo ActionListener es el que se añade a cada JMenuItem, que coge el texto de la etiqueta del menú y la inserta en el JTextField.

Navegador

Home | Anterior | Siguiente | Indice | Correo