|
AWT - Menús |
Anterior | Siguiente |
No hay ningún método para diseñar un buen interfaz de usuario, todo depende del programador. Los Menús son siempre el centro de la aplicación, porque son el medio de que el usuario interactúe con esa aplicación. La diferencia entre una aplicación útil y otra que es totalmente frustrante radica en la organización de los menús, pero eso, las reglas del diseño de un buen árbol de menús, no están claras. Hay un montón de libros acerca de la ergonomía y de cómo se debe implementar la interacción con el usuario. Lo cierto es que por cada uno que defienda una idea, seguro que hay otro que defiende la contraria. Todavía no hay un acuerdo para crear un estándar, con cada Window Manager se publica una guía de estilo diferente. Así que, vamos a explicar lo básico, sin que se deba tomar como dogma de fe, para que luego cada uno haga lo que mejor le parezca. En Java, la jerarquía de clases que intervienen en la construcción y manipulación de menús es la que se muestra en la lista siguiente: java.lang.Object MenuShorcut java.awt.MenuComponent java.awt.MenuBar java.awt.MenuItem java.awt.Menu java.awt.CheckboxMenuItem java.awt.PopMenu MenuComponent, es la superclase de todos
los Componentes relacionados con menús. A continuación se exploran una a una las clases que se acaban de citar. Esta clase no contiene campos, solamente tiene un constructor y dispone de una docena de métodos que están accesibles a todas sus subclases. Esta es la clase que se utiliza para construir los menús que se manejan habitualmente, conocidos como menús de persiana (o pull-down). Dispone de varios constructores para poder, entre otras cosas, crear los menús con o sin etiqueta. No tiene campos y proporciona varios métodos que se pueden utilizar para crear y mantener los menús en tiempo de ejecución. En el programa de ejemplo java1317.java, se usarán algunos de ellos. Esta clase se emplea para instanciar los objetos que constituirán los elementos seleccionables del menú. No tiene campos y dispone de varios constructores, entre los que hay que citar a: MenuItem( String, MenuShortcut ); que crea un elemento el menú con una combinación de teclas asociada para acceder directamente a él. Esta clase proporciona una veintena de métodos, entre los que destacan los que se citan ahora: addActionListener( ActionListener ) removeActionListener( ActionListener ) setEnabled( boolean ) isEnabled() El método addActionListener() ya debería resultar familiar al lector. Cuando se selecciona una opción de un menú, bien a través del ratón o por la combinación rápida de teclas, se genera un evento de tipo ActionEvent. Para que la selección de la opción en un menú ejecute una determinada acción, se ha de instanciar y registrar un objeto ActionListener que contenga el método actionPerformed() sobreescrito para producir la acción deseada. En el ejemplo java1317.java, solamente se presenta en pantalla la identificación de la opción de menú que se ha seleccionado; en un programa realmente útil, la acción seguramente que deberá realizar algo más interesante que eso. Esta clase se utiliza para instanciar un objeto que representa un acelerador de teclado, o una combinación de teclas rápidas, para un determinado MenuItem. No tiene campos y dispone de dos constructores. Aparentemente, casi todas las teclas rápidas consisten en mantener pulsada la tecla Control a la vez que se pulsa cualquier otra tecla. Uno de los constructores de esta clase: MenuShortcut( int,boolean ); dispone de un segundo parámetro que indica si el usuario ha de mantener también pulsada la tecla de cambio a mayúsculas (Shift). El primer parámetro es el código de la tecla, que es el mismo que se devuelve en el campo keyCode del evento KeyEvent, cuando se pulsa una tecla. La clase KeyEvent define varias constantes simbólicas para estos códigos de teclas, como son: VK_8, VK_9, VK_A, VK_B. No tiene campos, sólo tiene un constructor público, y es la clase que representa el concepto que todo usuario tiene de la barra de menú que está presente en la mayoría de las aplicaciones gráficas basadas en ventanas. En el programa java1317.java, ilustra algunos de los aspectos que intervienen en los menús. Es una aplicación que coloca dos menús sobre un objeto Frame. Uno de los menús tiene dos opciones y el otro, tres. La primera opción del primer menú también tiene asignada una combinación de teclas rápidas: Ctrl+Shift+K. Cuando se selecciona un elemento del menú, éste genera un evento de tipo ActionEvent, que presenta una línea de texto en pantalla indicando cuál ha sido el elemento del menú que se ha seleccionado, por ejemplo: % java java1317 java.awt.MenuItem[menuitem0,label=Primer Elemento del Menu A, shortcut=Ctrl+Shift+K] java.awt.MenuItem[menuitem1,label=Segundo Elemento del Menu A] java.awt.MenuItem[menuitem0,label=Primer Elemento del Menu A, shortcut=Ctrl+Shift+K] java.awt.MenuItem[menuitem3,label=Segundo Elemento del Menu B] También se instancia y registra un objeto receptor de eventos windowClosing() para terminar la ejecución del programa cuando se cierra el Frame. import java.awt.*; import java.awt.event.*; public class java1317 { public static void main(String args[]){ IHM ihm = new IHM(); } } class IHM { public IHM() { // Se instancia un objeto de tipo Acelerador de Teclado MenuShortcut miAcelerador = new MenuShortcut( KeyEvent.VK_K,true ); // Se instancian varios objetos de tipo Elementos de Menu MenuItem primerElementoDeA = new MenuItem( "Primer Elemento del Menu A",miAcelerador ); MenuItem segundoElementoDeA = new MenuItem( "Segundo Elemento del Menu A" ); MenuItem primerElementoDeB = new MenuItem( "Primer Elemento del Menu B" ); MenuItem segundoElementoDeB = new MenuItem( "Segundo Elemento del Menu B" ); MenuItem tercerElementoDeB = new MenuItem( "Tercer Elemento del Menu B" ); // Se instancia un objeto ActionListener y se registra sobre los // objetos MenuItem primerElementoDeA.addActionListener( new MiGestorDeMenu() ); segundoElementoDeA.addActionListener( new MiGestorDeMenu() ); primerElementoDeB.addActionListener( new MiGestorDeMenu() ); segundoElementoDeB.addActionListener( new MiGestorDeMenu() ); tercerElementoDeB.addActionListener( new MiGestorDeMenu() ); // Se instancian dos objetos de tipo Menu y se les añaden los // objetos MenuItem Menu menuA = new Menu( "Menu A" ); menuA.add( primerElementoDeA ); menuA.add( segundoElementoDeA ); Menu menuB = new Menu( "Menu B" ); menuB.add( primerElementoDeB ); menuB.add( segundoElementoDeB ); menuB.add( tercerElementoDeB ); // Se instancia una Barra de Menu y se le añaden los Menus MenuBar menuBar = new MenuBar(); menuBar.add( menuA ); menuBar.add( menuB ); // Se instancia un objeto Frame y se le asocia el objeto MenuBar. // Observese que esta no es la tipico invocacion del metodo // miFrame.add(), sino que es una forma especial de invocar // al metodo necesaria para poder asociar un objeto Barra de Menu // a un objeto Frame Frame miFrame = new Frame( "Tutorial de Java, AWT" ); // Esto no es el metodo add(), como se podria esperar miFrame.setMenuBar( menuBar ); miFrame.setSize( 250,100 ); miFrame.setVisible( true ); // Se instancia y registra un receptor de eventos de ventana para // concluir el programa cuando se cierre el Farme miFrame.addWindowListener( new Conclusion() ); } } // Clase para instanciar un objeto ActionListener que se registra // sobre los elementos del menu class MiGestorDeMenu implements ActionListener { public void actionPerformed( ActionEvent evt ) { // Presenta en pantalla el elemento que ha generado el evento // de tipo Action System.out.println( evt.getSource() ); } } class Conclusion extends WindowAdapter { public void windowClosing( WindowEvent evt ) { System.exit( 0 ); } } A continuación se revisan los fragmentos de código más interesantes del ejemplo, aunque en el programa se puede observar que hay gran cantidad de código repetitivo, ya que esencialmente se necesitan las mismas sentencias para crear cada opción del menú y registrar un objeto receptor de eventos sobre cada una de ellas. El programa genera dos menús separados con el mismo código básicamente. Quizá lo más interesante no sea el código en sí mismo, sino el orden en que se realizan los diferentes pasos de la construcción del menú. La primera sentencia que merece la pena es la que instancia un objeto MenuShortcut, que se utilizará posteriormente en la instanciación de un objeto MenuItem. MenuShortcut miAcelerador = new MenuShortcut( KeyEvent.VK_K,true ); La lista de argumentos del constructor especifica la combinación de teclas que se van a utilizar y el parámetro true, indica que es necesaria la tecla del cambio a mayúsculas (Shift) pulsada. El primer parámetro es la constante simbólica que la clase KeyEvent define para la tecla K y, aparentemente, la tecla Control siempre debe estar pulsada en este tipo de combinaciones para ser utilizadas las teclas normales como aceleradores de teclado. Cuando se utiliza este constructor, en el menú aparecerá la etiqueta asignada y a su derecha, la combinación de teclas alternativas para activarla directamente. El siguiente fragmento de código que se puede ver es el típico de instanciación de objetos MenuItem, que posteriormente se añadirán al objeto Menu para crear el menú. Se muestran dos tipos de instrucciones, el primer estilo especifica un acelerador de teclado y el segundo no. MenuItem primerElementoDeA = new MenuItem( "Primer Elemento del Menu A",miAcelerador ); MenuItem segundoElementoDeA = new MenuItem( "Segundo Elemento del Menu A" ); Ahora se encuentra el código que instancia y registra un objeto ActionListener sobre la opción del menú, tal como se ha visto en ejemplos anteriores. Aquí solamente se incluye para ilustrar el hecho de que la asociación de objetos ActionListener con opciones de un menú, no difiere en absoluto de la asociación de objetos ActionListener con objetos Button, o cualquier otro tipo de objeto capaz de generar eventos de tipo ActionEvent. primerElementoDeA.addActionListener( new MiGestorDeMenu() ); Las sentencias que siguen son las ya vistas, y que aquí se emplean para instanciar cada uno de los dos objetos Menu y añadirles las opciones existentes. Es la típica llamada al método Objeto.add() que se ha utilizado en programas anteriores. Menu menuA = new Menu( "Menu A" ); menuA.add( primerElementoDeA ); El siguiente fragmento de código instancia un objeto MenuBar y le añade los dos menús que se han definido antes. MenuBar menuBar = new MenuBar(); menuBar.add( menuA ); menuBar.add( menuB ); En este momento ya están creados los dos objetos Menu y colocados en un objeto MenuBar. Sin embargo, no se ha dicho nada sobre el ensamblamiento del conjunto. Esto se hace en el momento de asociar el objeto MenuBar con el objeto Frame. En este caso no se puede utilizar el método add(), sino que se tiene que invocar a un método especial de la clase Frame que tiene la siguiente declaración: public synchronized void setMenuBar( MenuBar mb ) y en este caso concreto se hace en las sentencias que se reproducen seguidamente: Frame miFrame = new Frame( "Tutorial de Java, AWT" ); // Esto no es el metodo add(), como se podria esperar miFrame.setMenuBar( menuBar ); Y hasta aquí lo más interesante del ejemplo, porque el código que resta es similar al que ya se ha visto y descrito en otros ejemplos del Tutorial, y que el lector se habrá encontrado si ha seguido la lectura secuencialmente. Esta clase se utiliza para instanciar objetos que puedan utilizarse como opciones en un menú. Al contrario que las opciones de menú que se han visto al hablar de objetos MenuItem, estas opciones tienen mucho más parentesco con las cajas de selección, tal como se podrá comprobar a lo largo del ejemplo java1318.java. Esta clase no tiene campos y proporciona tres constructores públicos, en donde se puede especificar el texto de la opción y el estado en que se encuentra. Si no se indica nada, la opción estará deseleccionada, aunque hay un constructor que permite indicar en un parámetro de tipo booleano, que la opción se encuentra seleccionada, o marcada, indicando true en ese valor. De los métodos que proporciona la clase, quizá el más interesante sea el método que tiene la siguiente declaración: addItemListener( ItemListener ) Cuando se selecciona una opción del menú, se genera un evento de tipo ItemEvent. Para que se produzca la acción que se desea con esa selección, es necesario instanciar y registrar un objeto ItemListener que contenga el método itemStateChanged() sobrescrito con la acción que se quiere. Por ejemplo, en el programa que se presenta a continuación, la acción consistirá en presentar la identificación y estado de la opción de menú que se haya seleccionado. Cuando se ejecuta el programa java1318.java, aparece un menú sobre un objeto Frame. El menú contiene tres opciones de tipo CheckboxMenuItem. Una opción de este tipo es semejante a cualquier otra, hasta que se selecciona. Cuando se seleccione, aparecerá una marca, o cualquier otra identificación visual, para saber que esa opción está seleccionada. Estas acciones hacen que el estado de la opción cambie, y ese estado se puede conocer a través del método getState(). Cuando se selecciona una opción, se genera un evento de tipo ItemEvent, que contiene información del nuevo estado, del texto de la opción y del nombre asignado a la opción. Estos datos pueden utilizarse para identificar cuál de las opciones ha cambiado y, también, para implementar la acción requerida que, en este caso del ejemplo siguiente, consiste en presentar una línea de texto en la pantalla con la información que contiene el objeto ItemEvent, más o menos semejante a la que se reproduce a continuación: % java java1318 java.awt.CheckboxMenuItem[chkmenuitem0,label=Primer Elemento,state=true] java.awt.CheckboxMenuItem[chkmenuitem2,label=Terecer Elemento,state=true] java.awt.CheckboxMenuItem[chkmenuitem0,label=Primer Elemento,state=false] java.awt.CheckboxMenuItem[chkmenuitem1,label=Segundo Elemento,state=true] java.awt.CheckboxMenuItem[chkmenuitem0,label=Primer Elemento,state=true] Como siempre, se instancia y registra sobre el Frame un objeto receptor de eventos windowClosing() para la conclusión del programa cuando se cierre el Frame. import java.awt.*; import java.awt.event.*; public class java1318 { public static void main( String args[] ) { IHM ihm = new IHM(); } } class IHM { public IHM() { // Instancia objetos de tipo CheckboxMenuItem CheckboxMenuItem primerElementoMenu = new CheckboxMenuItem( "Primer Elemento" ); CheckboxMenuItem segundoElementoMenu = new CheckboxMenuItem( "Segundo Elemento" ); CheckboxMenuItem tercerElementoMenu = new CheckboxMenuItem( "Tercer Elemento" ); // Instancia un objeto ItemListener y lo registra sobre los // objetos CheckboxMenuItem, elementos del menu de seleccion primerElementoMenu.addItemListener( new ControladorCheckBox() ); segundoElementoMenu.addItemListener( new ControladorCheckBox() ); tercerElementoMenu.addItemListener( new ControladorCheckBox() ); // Instancia un objeto Menu y le añade los botones de la caja // de seleccion Menu menuA = new Menu( "Menu A" ); menuA.add( primerElementoMenu ); menuA.add( segundoElementoMenu ); menuA.add( tercerElementoMenu ); // Instancia un objeto MenuBar y le añade el objeto Menu MenuBar barraMenu = new MenuBar(); barraMenu.add( menuA ); // Se instancia un objeto Frame y se le asocia el objeto MenuBar. // Observese que esta no es la tipico invocacion del metodo // miFrame.add(), sino que es una forma especial de invocar // al metodo necesaria para poder asociar un objeto Barra de Menu // a un objeto Frame Frame miFrame = new Frame( "Tutorial de Java, AWT" ); // Esto no es el metodo add(), como se podria esperar miFrame.setMenuBar( barraMenu ); miFrame.setSize( 250,100 ); miFrame.setVisible( true ); // Instancia y registra un receptor de eventos de ventana para // concluir la ejecucion del programa cuando se cierra el Frame miFrame.addWindowListener( new Conclusion() ); } } // Clase para instanciar un objeto ItemListener y registrarlo // sobre los elementos del menu class ControladorCheckBox implements ItemListener { public void itemStateChanged( ItemEvent evt ) { // Presenta en pantalla el elemento del menu que ha // generado el evento System.out.println( evt.getSource() ); } } class Conclusion extends WindowAdapter { public void windowClosing( WindowEvent evt ) { // Termina el programa cuando se cierra la ventana System.exit( 0 ); } } Hay algunos trozos de código en el programa que resultan repetitivos, debido al hecho de que se utiliza básicamente el mismo código para crear cada elemento del menú y registrar un objeto Listener sobre él. El orden en que se instancian y asocian las opciones es importante. La primera sentencia de código interesante es la que se utiliza para instanciar varios objetos CheckboxMenuItem que serán añadidos al objeto Menu para producir un menú con varias opciones. CheckboxMenuItem primerElementoMenu = new CheckboxMenuItem( "Primer Elemento" ); La sentencia que se reproduce ahora es interesante solamente por lo que tiene de novedad, porque hace uso de un tipo de clase Listener que no se ha visto antes. La sentencia, por otro lado, es la típica que se requiere para instanciar y registrar objetos ItemListener sobre cada CheckboxMenuItem. primerElementoMenu.addItemListener( new ControladorCheckBox() ); A continuación se encuentra el código que instancia un objeto Menu y le añade los objetos CheckboxMenuItem, que es semejante al utilizado en los menús normales. El siguiente código interesante corresponde a la clase que implementa el interfaz ItemListener, que vuelve a resultar interesante por lo novedoso. class ControladorCheckBox implements ItemListener { public void itemStateChanged( ItemEvent evt ) { // Presenta en pantalla el elemento del menu que ha // generado el evento System.out.println( evt.getSource() ); } } Y el resto del programa es similar al de ejemplos anteriores, así que no se vuelve más sobre él. Esta clase se utiliza para instanciar objetos que funcionan como menús emergentes o pop-up. Una vez que el menú aparece en pantalla, el procesado de las opciones es el mismo que en el caso de los menús de persiana. Esta clase no tiene campos y proporciona un par de constructores y un par de métodos, de los cuales el más interesante es el método show(), que permite mostrar el menú emergente en una posición relativa al Componente origen. Este Componente origen debe estar contenido dentro de la jerarquía de padres de la clase PopupMenu. El programa siguiente, java1319.java, coloca un objeto PopupMenu sobre un objeto Frame. El menú contiene tres opciones de tipo CheckboxMenuItem, y aparece cuando se pica dentro del Frame, posicionando su esquina superior-izquierda en la posición en que se encontraba el ratón en el momento de pulsar el botón. El resto del funcionamiento es semejante al de los programas de ejemplo vistos en secciones anteriores import java.awt.*; import java.awt.event.*; public class java1319 { public static void main( String args[] ) { IHM ihm = new IHM(); } } class IHM { public IHM() { // Instancia objetos CheckboxMenuItem CheckboxMenuItem primerElementoMenu = new CheckboxMenuItem( "Primer Elemento" ); CheckboxMenuItem segundoElementoMenu = new CheckboxMenuItem( "Segundo Elemento" ); CheckboxMenuItem tercerElementoMenu = new CheckboxMenuItem( "Tercer Elemento" ); // Se instancia un objeto ItemListener y se registra sobre los // elementos de menu ya instanciados primerElementoMenu.addItemListener( new ControladorCheckBox() ); segundoElementoMenu.addItemListener( new ControladorCheckBox() ); tercerElementoMenu.addItemListener( new ControladorCheckBox() ); // Instancia un objeto Menu de tipo PopUp y le añade los objetos // CheckboxMenuItem PopupMenu miMenuPopup = new PopupMenu( "Menu Popup" ); miMenuPopup.add( primerElementoMenu ); miMenuPopup.add( segundoElementoMenu ); miMenuPopup.add( tercerElementoMenu ); Frame miFrame = new Frame( "Tutorial de Java, AWT" ); miFrame.addMouseListener( new ControladorRaton(miFrame,miMenuPopup) ); // Aquí está la diferencia con los Menus de Barra miFrame.add( miMenuPopup ); miFrame.setSize( 250,100 ); miFrame.setVisible( true ); // Instancia y registra un receptor de eventos de ventana para // terminar el programa cuando se cierra el Frame miFrame.addWindowListener( new Conclusion() ); } } // Clase para atrapar los eventos de pulsacion del raton y presentar // en la pantalla el objeto menu Popup, en la posicion en que se // encontraba el cursor class ControladorRaton extends MouseAdapter{ Frame aFrame; PopupMenu aMenuPopup; // Constructor parametrizado ControladorRaton( Frame frame,PopupMenu menuPopup ) { aFrame = frame; aMenuPopup = menuPopup; } public void mousePressed( MouseEvent evt ) { // Presenta el menu PopUp sobre el Frame que se especifique // y en las coordenadas determinadas por el click del raton, // cuidando de que las coordenadas no se encuentren situadas // sobre la barra de titulo, porque las coordenadas Y en // esta zona son negativas if( evt.getY() > 0 ) aMenuPopup.show( aFrame,evt.getX(),evt.getY() ); } } // Clase para instanciar un objeto receptor de eventos de los // elementos del menu que sera registrado sobre estos elementos class ControladorCheckBox implements ItemListener { public void itemStateChanged( ItemEvent evt ) { // Presenta en pantalla el elemento que ha generado el // evento System.out.println( evt.getSource() ); } } class Conclusion extends WindowAdapter { public void windowClosing( WindowEvent evt ) { // termina el programa cuando se cierra la ventana System.exit( 0 ); } } El programa es muy similar al de la sección anterior en que se utilizaban objetos CheckboxMenuItem en un menú de persiana normal, así que solamente se verá el código que afecta al uso del objeto PopupMenu. El primer fragmento de código interesante es el que instancia un objeto PopupMenu y le incorpora tres objetos CheckboxMenuItem. PopupMenu miMenuPopup = new PopupMenu( "Menu Popup" ); miMenuPopup.add( primerElementoMenu ); miMenuPopup.add( segundoElementoMenu ); miMenuPopup.add( tercerElementoMenu ); El siguiente trozo de código es interesante porque el procedimiento en el caso de asociar un objeto PopupMenu con el objeto Frame, es diferente a cuando se utiliza un objeto MenuBar. En el caso de la barra de menú, se utiliza la llamada al método setMenuBar(), mientras que para asociar un menú emergente a un objeto Frame, se utiliza la típica llamada de la forma Objeto.add(), que es el método habitual de añadir muchos otros Componentes a un Contenedor. Frame miFrame = new Frame( "Tutorial de Java, AWT" ); miFrame.addMouseListener( new ControladorRaton(miFrame,miMenuPopup) ); // Aquí está la diferencia con los Menus de Barra miFrame.add( miMenuPopup ); El siguiente trozo de código interesante es el método sobrescrito mousePressed(), del interfaz MouseListener, encapsulado aquí en la clase ControladorRaton. El propósito de esta clase es instanciar un objeto receptor que atrapará eventos de pulsación del ratón sobre el objeto Frame, y mostrará el objeto PopupMenu en la posición en que se encuentre el cursor del ratón en el momento de producirse el evento. public void mousePressed( MouseEvent evt ) { // Presenta el menu PopUp sobre el Frame que se especifique // y en las coordenadas determinadas por el click del raton, // cuidando de que las coordenadas no se encuentren situadas // sobre la barra de titulo, porque las coordenadas Y en // esta zona son negativas if( evt.getY() > 0 ) aMenuPopup.show( aFrame,evt.getX(),evt.getY() ); } Como se puede observar, es lo típico, excepto el uso del método show() de la clase PopupMenu. También se debe notar la referencia al objeto PopupMenu y otra al objeto Frame, que se pasan cuando se instancia el objeto. Estas dos referencias son necesarias para la invocación del método show(), tal como se muestra en el código anterior. La referencia al objeto Frame se utiliza para establecer la posición en donde aparecerá el menú, y la referencia al objeto PopupMenu especifica el menú que se debe mostrar. |
|