|
![]() |
AWT - Layouts (IV) |
Anterior | Siguiente |
El controlador de posicionamiento BoxLayout es uno de los dos que incorpora Java a través de Swing. Permite colocar los Componentes a lo largo del eje X o del eje Y, y también posibilita que los Componentes ocupen diferente espacio a lo largo del eje principal. En un controlador BoxLayout sobre el eje Y, los Componentes se posicionan de arriba hacia abajo en el orden en que se han añadido. Al contrario en en el caso del GridLayout, aquí se permite que los Componentes sean de diferente tamaño a lo largo del eje Y, que es el esje principal del controlador de posicionamiento, en este caso. En el eje que no es principal, BoxLayout intenta que todos los Componentes seal tan anchos como el más ancho, o tan alto como el más alto, dependiendo de cuál sea el eje principal. Si un Componente no puede incrementar su tamaño, el BoxLayout mira las propiedades de alineamiento en X e Y para determinar donde colocarlo. El controlador de posicionamiento OverlayLayout, es el otro que se incopora a Java con Swing, y es un poco diferente a todos los demás. Se dimensiona para contener el más grande de los Componentes y superpone cada Componente sobre los otros. La clase OverlayLayout no tiene un constructor por defecto, así que hay que crearlo dinámicamente en tiempo de ejecución. A continuación se desarrolla un controlador de posicionamiento de componentes propio, para que el lector compruebe que no es nada complicada la creación de layouts y que Java da esa posibilidad. En este caso se trata de un controlador de posicionamiento absoluto, es decir, es un controlador que posiciona un componente en una determinada posición indicada en coordenadas x e y, y de un anchura y una altura también especificadas, bien de forma explícita o por defecto para todos los componentes. Interfaz LayoutManagerAbsoluto La forma de implementar nuevos controladores de posicionamiento es a través del interfaz LayoutManager. A partir del JDK 1.1, Javasoft introdujo un nuevo interfaz, LayoutManager2, destinado a la implementación de controladores de posicionamiento basados en parámetros, en constraints, de forma que se utilicen objetos de este tipo para controlar el tamaño y posición de los componentes sobre el contenedor. No obstante, la documentación advierte que "no se proporciona soporte total, completo para los controladores de posicionamiento propios basados en constraints". Así que, para evitar esos problemas y hasta que haya soporte completo, se ha creado el interfaz LayoutManagerAbsoluto, LayoutManagerAbsoluto.java, que añade los métodos abstractos setConstraints() y getConstraints() para poder fijar y recuperar estos parámetros. public interface LayoutManagerAbsoluto extends LayoutManager,Cloneable { public abstract Object getConstraints( Component comp ); public abstract void setConstraints( Component comp,Object cons ); public abstract Object clone(); } Antes de poner a trabajar al nuevo controlador de posicionamiento, hay que comenzar definiendo los valores de los parámetros o constraints que se van a asignar a cada uno de los componentes que se quieren situar en el contenedor controlado por el layout recién creado. En la clase LayoutAbsolutoConstraints, LayoutAbsolutoConstraints.java, como se puede ver el el código de la clase que se muestra, los constraints son un modelo simple de objetos que deberían ser clonables. public class LayoutAbsolutoConstraints implements Cloneable { // Los nombres de los parámetros son semejantes a los que se utilizan // en el objeto de tipo GridBagConstraints public static final int NONE = 0; public static final int BOTH = 1; public static final int HORIZONTAL = 2; public static final int VERTICAL = 3; public static final int NORTH = 1; public static final int NORTHEAST = 2; public static final int EAST = 3; public static final int SOUTHEAST = 4; public static final int SOUTH = 5; public static final int SOUTHWEST = 6; public static final int WEST = 7; public static final int NORTHWEST = 8; public int x; public int y; public int width; public int height; public int anchor; public int fill; public Insets insets; // Los valores -1 en width y height significan que se ha de // utilizar el valor preferido para el Componente de que se // trate public LayoutAbsolutoConstraints() { x = 0; y = 0; width = -1; height = -1; anchor = NONE; fill = NONE; insets = new Insets( 0,0,0,0 ); } public Object clone() { LayoutAbsolutoConstraints lCon = new LayoutAbsolutoConstraints(); lCon.x = x; lCon.y = y; lCon.width = width; lCon.height = height; lCon.anchor = anchor; lCon.fill = fill; lCon.insets = (Insets)insets.clone(); return( lCon ); } } Lo que se quiere es que cada Componente esté emplazado en una determinada posición y tenga un tamaño específico, además de algunas capacidades básicas al estilo del GridBagLayout, como pueden ser la posibilidad de anclaje o relleno. En resumen, la información que se quiere parametrizar a través de los constraints, tal como se muestra en la clase, es la siguiente:
Una vez que se tienen los parámetros, o constraints, fijados, ya se puede crear el controlador de posicionamiento que funcione en base a esos valores. La clase LayoutAbsoluto, LayoutAbsoluto.java, implementa el interfaz LayoutManagerAbsoluto y redefine los métodos que se definen en el interfaz y también los que se definen en el interfaz LayoutManager, que implementa el interfaz LayoutManagerAbsoluto, que se van a utilizar en la clase, como son layoutContainer(), minimumLayoutSize() y preferredLayoutSize(). import java.awt.*; import java.beans.*; import java.util.*; public class LayoutAbsoluto implements LayoutManagerAbsoluto { private static final int PREFERRED = 0; private static final int MINIMUM = 1; private Hashtable consTable = new Hashtable(); private LayoutAbsolutoConstraints defaultConstraints = new LayoutAbsolutoConstraints(); public int width; public int height; // Si se indica -1 en los parámetros width o height, se da a entender // que se desea utilizar el tamaño preferido del componente public LayoutAbsoluto(){ width = -1; height = -1; } // Aquí se especifica el ancho y la altura que se desea para el // componente, una vez que se vaya a presentar en el contenedor public LayoutAbsoluto( int w,int h ) { width = w; height = h; } public void addLayoutComponent( String name,Component comp ) { // No se utiliza. El usuario debe llamar a setConstraints() // y después utilizar el método add(comp) genérico, al igual // que se hace en el caso del GridBagLayout } // Se usa para obtener los valores que actualmente tienen // asignados los parámetros que controlan el posicionamiento // de un determinado componente sobre la zola de control del // layout public Object getConstraints( Component comp ) { return( lookupConstraints(comp).clone() ); } // Aquí es donde se controlan todos los parámetros que se // permiten, los constraints, para poder modificar la posición // y tamaño del componente, y para indicarle al contenedor las // características especiales de posicionamiento que se // quieren aplicar al componente public void layoutContainer( Container parent ) { Component comp; Component comps[]; LayoutAbsolutoConstraints cons; Dimension d; Dimension pD; int x; int y; comps = parent.getComponents(); Insets insets = parent.getInsets(); pD = parent.getSize(); for( int i=0; i < comps.length; i++ ) { comp = comps[i]; // Se obtienen los parámetros actuales, que en principio // serán los de defecto, más los que se hayan modificado // en el constructor cons = lookupConstraints( comp ); // Se aplican los desplazamientos "insets" a la posición x = cons.x + cons.insets.left + insets.left; y = cons.y + cons.insets.top + insets.top; // Se obtiene el tamaño preferido para el componente d = comp.getPreferredSize(); // Si no se quiere que el componente se presente con su // tamaño preferido, se modifica su anchura y su altura // con los valores que se indiquen if( cons.width != -1 ) d.width = cons.width; if( cons.height != -1 ) d.height = cons.height; // Ahora se controla el parámetro "fill" de forma que // el componente ocupe todo el espacio que se le // indique, y en la dirección que se le diga, en caso // de que se quiera cambiar. Observar que en este caso // también hay que tener en cuenta los "insets" o // separaciones de los bordes que se hayan indicado, // porque esa es una zona que hay que respetar if( (cons.fill == LayoutAbsolutoConstraints.BOTH) || (cons.fill == LayoutAbsolutoConstraints.HORIZONTAL) ) { x = insets.left + cons.insets.left; d.width = pD.width - cons.insets.left - cons.insets.right - insets.left - insets.right; } if( (cons.fill == LayoutAbsolutoConstraints.BOTH) || (cons.fill == LayoutAbsolutoConstraints.VERTICAL) ){ y = insets.top + cons.insets.top; d.height = pD.height - cons.insets.top - cons.insets.bottom - insets.top - insets.bottom; } // A continuación se controla el parámetro "anchor", para // anclar el componente en alguna de las posiciones que // están permitidas switch( cons.anchor ){ case LayoutAbsolutoConstraints.NORTH: x = ( pD.width - d.width ) / 2; y = cons.insets.top + insets.top; break; case LayoutAbsolutoConstraints.NORTHEAST: x = pD.width - d.width - cons.insets.right - insets.right; y = cons.insets.top + insets.top; break; case LayoutAbsolutoConstraints.EAST: x = pD.width - d.width - cons.insets.right - insets.right; y = ( pD.height - d.height ) / 2; break; case LayoutAbsolutoConstraints.SOUTHEAST: x = pD.width - d.width - cons.insets.right - insets.right; y = pD.height - d.height - cons.insets.bottom - insets.bottom; break; case LayoutAbsolutoConstraints.SOUTH: x = ( pD.width - d.width ) / 2; y = pD.height - d.height - cons.insets.bottom - insets.bottom; break; case LayoutAbsolutoConstraints.SOUTHWEST: x = cons.insets.left + insets.left; y = pD.height - d.height - cons.insets.bottom - insets.bottom; break; case LayoutAbsolutoConstraints.WEST: x = cons.insets.left + insets.left; y = ( pD.height - d.height ) / 2; break; case LayoutAbsolutoConstraints.NORTHWEST: x = cons.insets.left + insets.left; y = cons.insets.top + insets.top; break; default: break; } // Y, finalmente, se fija la posción y dimensión del // componente, una vez tenidos en cuenta todos los // parámentros que permite modificar su posicionamiento comp.setBounds( x,y,d.width,d.height ); } } // Devuelve el tamaño que ocupan todos los componentes en el // contenedor, es decir, devuelve el tamaño que debe tener el // contenedor para poder visulaizar todos los componentes, en // función de su tamaño y posición en el layout private Dimension layoutSize( Container parent,int tipo ) { int ancho; int alto; // En caso de que no se indique que el layout debe // considerar el tamaño preferido, hay que ir calculando // las posiciones y tamaños de los componentes que se van // a posicionar en su interior para saber el tamaño // mínimo que ha de tener para contenerlos a todos if( (width == -1) || (height == -1) ){ Component comp; Component comps[]; LayoutAbsolutoConstraints cons; Dimension d; int x; int y; ancho = alto = 0; comps = parent.getComponents(); for( int i=0; i < comps.length; i++ ) { comp = comps[i]; cons = lookupConstraints( comp ); if( tipo == PREFERRED ) d = comp.getPreferredSize(); else d = comp.getMinimumSize(); if( cons.width != -1 ) d.width = cons.width; if( cons.height != -1 ) d.height = cons.height; if( cons.anchor == LayoutAbsolutoConstraints.NONE ) { x = cons.x; y = cons.y; } else { x = cons.insets.left; y = cons.insets.top; } if( (cons.fill != LayoutAbsolutoConstraints.BOTH) && (cons.fill != LayoutAbsolutoConstraints.HORIZONTAL) ) ancho = Math.max( ancho,x + d.width ); else ancho = Math.max( ancho,d.width + cons.insets.left + cons.insets.right ); if( (cons.fill != LayoutAbsolutoConstraints.BOTH) && (cons.fill != LayoutAbsolutoConstraints.VERTICAL) ) alto = Math.max( alto,y + d.height ); else alto = Math.max( alto,d.height + cons.insets.top + cons.insets.bottom ); } if( width != -1 ) ancho = width; if( height != -1 ) alto = height; } else { ancho = width; alto = height; } // Una vez que se sabe el tamaño necesario para contener // a todos los componentes o se ha indicado un tamaño fijo, // se aplican los desplazamientos desde los bordes, para // devolver el tamaño definitivo que debe tener el // contenedor Insets insets = parent.getInsets(); return( new Dimension( ancho + insets.left + insets.right, alto + insets.top + insets.bottom ) ); } // Devuelve los valores de los parámetros que se han indicado // para el componente, o en caso de que no se haya indicado // ninguno en especial, devuelve los valores por defecto que // se asginan a todos los componentes private LayoutAbsolutoConstraints lookupConstraints( Component comp ) { LayoutAbsolutoConstraints p = (LayoutAbsolutoConstraints)consTable.get( comp ); if( p == null ) { setConstraints( comp,defaultConstraints ); p = defaultConstraints; } return( p ); } // Devuelve el mínimo tamaño que se ha especificado para el // contendor que está controlado por el layout public Dimension minimumLayoutSize( Container parent ) { return( layoutSize( parent,MINIMUM ) ); } // Devuelve el tamaño preferido public Dimension preferredLayoutSize( Container parent ) { return( layoutSize( parent,PREFERRED ) ); } // Elimina un componente del Layout, haciendo que desaparezca // también de la visualización en pantalla o donde se esté // mapenado el contenedor public void removeLayoutComponent( Component comp ) { consTable.remove( comp ); } // Aquí se aplican los valores que contiene el objeto de // tipo Constraints al componente que se desea controlar // en posición y tamaño sobre el contenedor que está siendo // manejado por el layout que se ha creado public void setConstraints( Component comp,Object cons ) { if( (cons == null) || (cons instanceof LayoutAbsolutoConstraints) ){ LayoutAbsolutoConstraints pCons; // Si no se indica ningún objeto que contenga los valores de // posicionamiento, se aplican los valores de defecto if( cons == null ) pCons = (LayoutAbsolutoConstraints)defaultConstraints.clone(); else pCons = (LayoutAbsolutoConstraints) ( (LayoutAbsolutoConstraints)cons ).clone(); // Una vez fijados los valores de los parámetros, se incluye // el componente en la lista de componentes que están siendo // manejados por el layout manager consTable.put( comp,pCons ); // Lo siguiente es necesario para el caso en qhe se aniden // layout del tipo absoluto que estamos creando. Cuando los // constraints del componente están destinados a ser elásticos // o no-eláticos, es cuando se comprueba para ver si el // componente es en sí mismo un contenedor, con otro layout // absoluto como controlador de posicionamiento de los // componentes y, si es así, fijar el layout para que sea // elástico o no-elástico, según sea necesario if( Beans.isInstanceOf( comp,Container.class ) ) { if( ( (Container)Beans.getInstanceOf( comp, Container.class ) ).getLayout() instanceof LayoutAbsoluto ) { LayoutAbsoluto layout; layout = (LayoutAbsoluto) ( (Container)Beans.getInstanceOf( comp, Container.class ) ).getLayout(); layout.width = pCons.width; layout.height = pCons.height; } } } } // Devuelve un objeto igual, pero ya con el ancho y alto determinado // pos los valores de los parámetros correspondientes, sean los de // defecto o los que se indiquen específicamente public Object clone() { LayoutAbsoluto p = new LayoutAbsoluto( width,height ); return( p ); } } Por repasar las partes más interesantes del código, lo primero que cabe destacar son los métodos minimumLayoutSize() y preferredLayoutSize(), que llaman al mismo método layoutSize(). Este hace un recorrido por todos los componentes que hay en el contenedor , comprobando los valores de los parámetros y manteniendo el total de anchura y altura que debe tener el contenedor para permitir la visualización de todos los componentes. Tiene en cuenta todas las posibilidades, desde que los valores de width y height puedan ser -1 para utilizar los valores preferidos para el Componente en cuestión, hasta la cantidad de pixels que se determinen para separar el Componente del borde del contenedor. El siguiente trozo de código interesante corresponde a la implementación de los métodos getConstraints() y setConstraints(). Los objetos que almacenan los valores de los parámetros se guardan en una Hastable, de forma que en el método setConstraints() se fijan los pares clave:valor en la tabla. En caso de que no se especifique ningún valor válido para los parámetros, se utilizan los valores de defecto y se tiene cuidado especial en asegurarse de que los valores de los parámetros que se introducen en la tabla son únicos, y no se introducen también los de los clones. public Object getConstraints( Component comp ) { return( lookupConstraints(comp).clone() ); } . . . public void setConstraints( Component comp,Object cons ) { if( (cons == null) || (cons instanceof LayoutAbsolutoConstraints) ){ LayoutAbsolutoConstraints pCons; // Si no se indica ningún objeto que contenga los valores de // posicionamiento, se aplican los valores de defecto if( cons == null ) pCons = (LayoutAbsolutoConstraints)defaultConstraints.clone(); else pCons = (LayoutAbsolutoConstraints) ( (LayoutAbsolutoConstraints)cons ).clone(); // Una vez fijados los valores de los parámetros, se incluye // el componente en la lista de componentes que están siendo // manejados por el layout manager consTable.put( comp,pCons ); // Lo siguiente es necesario para el caso en qhe se aniden // layout del tipo absoluto que estamos creando. Cuando los // constraints del componente están destinados a ser elásticos // o no-eláticos, es cuando se comprueba para ver si el // componente es en sí mismo un contenedor, con otro layout // absoluto como controlador de posicionamiento de los // componentes y, si es así, fijar el layout para que sea // elástico o no-elástico, según sea necesario if( Beans.isInstanceOf( comp,Container.class ) ) { if( ( (Container)Beans.getInstanceOf( comp, Container.class ) ).getLayout() instanceof LayoutAbsoluto ) { LayoutAbsoluto layout; layout = (LayoutAbsoluto) ( (Container)Beans.getInstanceOf( comp, Container.class ) ).getLayout(); layout.width = pCons.width; layout.height = pCons.height; } } } } Otro trozo de código interesante corresponde al método lookupConstraints(), que como se puede observar, es utilizado por el método getConstraints(), y también es común a layoutSize() y layoutContainer(). Este método, que se reproduce a continuación, simplemente recorre la tabla en donde se encuentran los parámetros y los devuelve. private LayoutAbsolutoConstraints lookupConstraints( Component comp ) { LayoutAbsolutoConstraints p = (LayoutAbsolutoConstraints)consTable.get( comp ); if( p == null ) { setConstraints( comp,defaultConstraints ); p = defaultConstraints; } return( p ); } De nuevo, si no existe ningún constraint para un Compopnente, se utilizan los de defecto. Esto es ahora así porque Javasoft no proporciona soporte completo para los controladores de posicionamiento creados por usuarios, seguro que cuando esto se proporcione, el mecanismo de la hashtable estará encapsulado y no habrá que manejarlo del modo que aquí se hace. Al igual que los constraints para los Componentes que están manejados por el controlador de posicionamiento, este mismo controlador dispone de sus propios parámetros de ancho y alto. Si sus valores se fijan a -1, esto significa que el contenedor será elástico, es decir, que se expandirá o encogerá para adaptarse al tamaño preferido de todos los componentes que contiene en su interior. Si se proporciona un ancho y un alto determinados, el contenedor padre siempre tendrá el mismo tamaño, independientemente del tamaño y posición de los componentes que contenga. El último trozo de código que merece la pena resaltar es precisamente, el fragmento que define el control del caso en que se fije o indique un tamaño preferido de un componente que es a la ver un contenedor y tiene su propia posición. En este caso, se asegura que el tamaño preferido del layout anidado coincide con su correspondiente contenedor. if( Beans.isInstanceOf( comp,Container.class ) ) { if( ( (Container)Beans.getInstanceOf( comp, Container.class ) ).getLayout() instanceof LayoutAbsoluto ) { LayoutAbsoluto layout; layout = (LayoutAbsoluto) ( (Container)Beans.getInstanceOf( comp, Container.class ) ).getLayout(); layout.width = pCons.width; layout.height = pCons.height; } } Ahora ya está completo el nuevo controlador de posicionamiento y está listo para ser utilizado. En el ejemplo java1333.java, se hace uso de él, creando una serie de botones que se colocan en posiciones determinadas y con tamaños específicos, utilizando el LayoutAbsoluto. Así, se pueden colocar los componentes de la forma tradicional de los compiladores antiguos, especificando sus coordenadas X e Y. import java.awt.*; import java.awt.event.*; public class java1333 extends Frame { public java1333() { super(); setSize( 400,300 ); setTitle( "Tutorial de Java, AWT" ); // Se crea y se fija un layout Absoluto LayoutAbsoluto posLayout = new LayoutAbsoluto(); setLayout(posLayout); // Se crean varios botones con distintos valores para los // parámetros que los controlan sobre el layout LayoutAbsolutoConstraints pCons; pCons = new LayoutAbsolutoConstraints(); pCons.x = 250; pCons.y = 210; Button botonTamanoPreferido = new Button( "x=250, y=210" ); posLayout.setConstraints( botonTamanoPreferido,pCons ); add( botonTamanoPreferido ); pCons = new LayoutAbsolutoConstraints(); pCons.x = 75; pCons.y = 50; pCons.width = 220; pCons.height = 60; Button botonTamanoFijo = new Button("x=75, y=50, w=220, h=60"); posLayout.setConstraints( botonTamanoFijo,pCons ); add( botonTamanoFijo ); pCons = new LayoutAbsolutoConstraints(); pCons.anchor = LayoutAbsolutoConstraints.SOUTHWEST; Button botonAnclado = new Button( "Anclaje southwest" ); posLayout.setConstraints( botonAnclado,pCons ); add( botonAnclado ); pCons = new LayoutAbsolutoConstraints(); pCons.anchor = LayoutAbsolutoConstraints.NORTH; pCons.fill = LayoutAbsolutoConstraints.HORIZONTAL; pCons.insets = new Insets(10, 10, 10, 10); Button botonInsets = new Button( "Anclaje north, fill horizontal, inset 10" ); posLayout.setConstraints( botonInsets,pCons ); add( botonInsets ); pCons = new LayoutAbsolutoConstraints(); pCons.fill = LayoutAbsolutoConstraints.BOTH; Button botonFill = new Button( "Fill both" ); posLayout.setConstraints( botonFill,pCons ); add( botonFill ); // Esta es una clase anidada anónima que se utiliza para // concluir la ejecución del programa cuando el usuario // decide cerrar el Frame addWindowListener( new WindowAdapter() { public void windowClosing( WindowEvent evt ) { System.exit( 0 ); } } ); } public static void main( String args[] ) { // De una tacada se crea el objeto y se hace visible ( new java1333() ).setVisible( true ); } } La apariencia que toman estos botones en la ventana es la que reproduce la figura siguiente, en donde se observa que aparece como título de cada uno de los botones, los valores de los parámetros o constraints que se han modificado y que hacen que aparezca de esa forma y en esa posición el botón correspondiente. |
|