Beginning Sistemas de Información Java Marcos Listado siguiente
PresentaciónEntrada/SalidaBibliotecasSwingMarcosBotonesCuadros de texto
ListasImágenesMenúsDiálogosArchivosGráficos 2D 


Paneles
Una vez creada una ventana principal, es preciso disponer de contenedores adecuados para construir la interfaz gráfica de usuario. Esta interfaz va a estar formada por botones (JButton), etiquetas (JLabel), campos de texto (JTextField, JTextArea) y algunos elementos más. Estos elementos, Components todos ellos, se disponen de forma adecuada mediante el uso de un nuevo contenedor: el JPanel, que sin llegar a ser una ventana principal admite disposiciones y por tanto es la herramienta ideal. Veamos un primer ejemplo, un programa que muestra en pantalla una colección de botones (inactivos de momento)

JPanelBotones Las dos clases siguientes residen en el mismo archivo, JPanelBotones.java. Tb podrían residir en archivos independientes, de nombre igual al nombre de la clase. El objetivo es crear una aplicación cuyo aspecto es similar al que puede verse en la figura.
/** 
 * JPanelBotones.java
 *
 * Descripción: 
 * @author      Coti
 * @version     1.0
 */

public class JPanelBotones {
  static public void main(String[] args) {
    JPBotones jpb = new JPBotones();
  }
  
}

import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;


public class JPBotones {
  JFrame fr;
  String[] dia = {  "Lunes", "Martes",
                    "Miércoles", "Jueves",
                    "Viernes", "Sábado",
                    "Domingo"};
  JButton[] b = new JButton[dia.length];
  JPBotones() {
    int i;
    fr = new JFrame("Botones");
    fr.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }});
    
    JPanel jp = new JPanel();
    for(i=0;i<dia.length;i++)
      {
        b[i] = new JButton(dia[i]);
        jp.add(b[i]);
      }
    jp.setPreferredSize(new Dimension(150,500));
    
    fr.setContentPane(jp);
    fr.pack();
    fr.setVisible(true);
  }
  
}

Este programa muestra una forma sencilla de crear una botonera. Obsérvese que la botonera no hace nada, puesto que no se han asociado acciones a los botones. Pero esto es fácil de cambiar, como se verá. La asociación de acciones a botones pasa por especificar la clase que contiene el método actionPerformed() ejecutado como respuesta a la acción, en este caso un clic del usuario en el botón.
Un panel sencillo Este ejemplo muestra la forma de personalizar el comportamiento de un panel, haciendo que muestre una frase en pantalla. Obsérvese que el entorno se encarga de todas las tareas de refresco automáticamente; nosotros nos limitamos a dar paso al refresco genérico (super.paintComponent(g) para después generar nuestra propia salida gráfica, que es simplemente una frase en pantalla. Este ejemplo y el siguiente muestran la forma de crear un componente cuyo contenido gráfico sea el que nosotros deseamos.
//
//  JPanelSencillo.java
//
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;


public class JPanelSencillo {
 
 public static void main(String args[]) {
   //
   // Se crea un nuevo panel por derivación de JPanel
   //
   JPanel jp = new JPanel() {
     //
     // Se redefine paintComponent() para que
     // a) ejecute el paintComponent() original
     // b) ejecute las nuevas instrucciones gráficas
     //
     protected void paintComponent(Graphics g) {
       super.paintComponent(g);
       g.drawString("ABRACADABRA.", 20,50);
     };
   };
   //
   // Se fija el tamaño preferido del panel
   //
   jp.setPreferredSize(new Dimension(200,200));
   //
   // Se crea un JFrame, se le añade jp
   // y se registra una clase anónima para que
   // al cerrar todo vaya bien
   //
   JFrame jf = new JFrame("JPanelSencillo");
   jf.setContentPane(jp);
   jf.addWindowListener(new WindowAdapter() {
     public void windowClosing(WindowEvent e) {
       System.exit(0);
     }
   });
   //
   // Se pide que el JFrame se adapte a su contenido
   //
   jf.pack();
   //
   // Con todo preparado, se hace visible
   //
   jf.setVisible(true);
 }
}

Un panel gráfico Otro posible ejemplo interesante es el que se muestra a continuación, y que permite representar en pantalla las conocidas curvas de Lissajous. Este ejemplo es algo más ambicioso que el anterior, y muestra la forma de generar segmentos de recta en pantalla. Si los puntos considerados están suficientemente próximos, podremos crear una buena aproximación de una curva interpolando mediante rectas. El algoritmo que se muestra es completamente general; el único requisito es la lógica necesidad de disponer de una nube de puntos ordenados cronológicamente.

Estas curvas se obtienen combinando dos movimientos oscilatorios, uno en la dirección horizontal y otro en la vertical. Este programa muestra la curva generada para dos frecuencias fijas (7 y 11). ¿Sería muy complicado lograr que el programa admitiese las frecuencias a través de la línea de órdenes? ¿No quedaría mejor centrado en pantalla?
//
//  JPanelGraficos.java
//
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.Color;



public class JPanelGraficos {
  
  public static void main(String args[]) {
     JPanel jp = new JPanel() {
      //
      // Se redefine paintComponent() para que
      // a) ejecute el paintComponent() original
      // b) ejecute las nuevas instrucciones gráficas
      //
      protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        int iFrecuencia1, iFrecuencia2, iDesfase;
        iFrecuencia1 = 7;
        iFrecuencia2 = 11;
        iDesfase = 0;
        int iPosX1 = 0; int iPosY1 = 0;
        int iPosX2 = 0; int iPosY2 = 0;
    
        int iAnchoPanel = getSize().width;
        int iAltoPanel = getSize().height;
    
        double t = 0.0D;
        double dIncrementoT = 1.0D / (2.0D*Math.max( iFrecuencia1, iFrecuencia2 ));
    
        // Quedará una línea amarilla sobre fondo azul (el fondo es el del JPanel)
        g.setColor( Color.yellow );
        g.translate( iAnchoPanel/2, iAltoPanel/2 );
    
        // Calculamos el punto inicial de la primera línea a pintar (primer intervalo)
        iPosX2 = 0;
        iPosY2 = (int)(((iAltoPanel - 10) * Math.sin((iDesfase * Math.PI) / 180)) / 2);
    
        // Generamos varios intervalos, incrementados seg˙n <dIncrementoT>
        while(t < 360) 
        {
          // Calculamos el primer punto de la línea a pintar en este intervalo
          iPosX1 = (int)(((iAnchoPanel - 10) * Math.sin((iFrecuencia1 * t * Math.PI) / 180)) / 2);
          iPosY1 = (int)(((iAltoPanel - 10) * Math.sin(((iFrecuencia2 * t * Math.PI+ iDesfase)) / 180)) / 2);
    
          // Dibujamos la línea correspondiente al primer intervalo
          g.drawLine(iPosX2, iPosY2, iPosX1, iPosY1);
    
          // El punto inicial de la línea a pintar en el siguiente intervalo es el punto
          // final de la línea que acabamos de pintar en este intervalo
          iPosX2 = iPosX1;
          iPosY2 = iPosY1;
    
          // Pasamos al siguiente intervalo
          t += dIncrementoT;
        }
    
        // Pintamos la ˙ltima línea
        iPosX1 = 0;
        iPosY1 = (int)(((iAltoPanel - 10) * Math.sin((iDesfase * Math.PI) / 180)) / 2);
        g.drawLine(iPosX2, iPosY2, iPosX1, iPosY1);
      };
    };
    //
    // Se fija el tamaño preferido del panel
    //
    jp.setPreferredSize(new Dimension(200,200));
    //
    // Se fija el color de fondo del panel, azul
    //
    jp.setBackground(Color.blue);
    //
    // Se crea un JFrame, se le añade jp
    // y se registra una clase anónima para que
    // al cerrar todo vaya bien
    //
    JFrame jf = new JFrame("Lissajous");
    jf.setContentPane(jp);
    jf.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });
    //
    // Se pide que el JFrame se adapte a su contenido
    //
    jf.pack();
    //
    // Con todo preparado, se hace visible
    //
    jf.setVisible(true);
  }
}
Se adjunta a continuación un breve resumen de los métodos ofrecidos por la clase Graphics. Estos métodos permiten construir gráficos relativamente sencillos con poco esfuerzo. Obsérvese que existe también la clase Graphics2D, más avanzada que la anterior, y que ofrece versiones orientadas a objetos de las primitivas anteriores, así como otros métodos adicionales.
Resumen de Graphics
/*
Métodos
abstract void clearRect(int x, int y, int width, int height) abstract void clipRect(int x, int y, int width, int height) abstract void copyArea(int x, int y, int width, int height, int dx, int dy) abstract Graphics create() Graphics create(int x, int y, int width, int height) abstract void dispose() void draw3DRect(int x, int y, int width, int height, boolean raised) abstract void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) void drawBytes(byte[] data, int offset, int length, int x, int y) void drawChars(char[] data, int offset, int length, int x, int y) abstract boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) abstract boolean drawImage(Image img, int x, int y, ImageObserver observer) abstract boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) abstract boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) abstract boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer) abstract boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) abstract void drawLine(int x1, int y1, int x2, int y2) abstract void drawOval(int x, int y, int width, int height) abstract void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) void drawPolygon(Polygon p) abstract void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) void drawRect(int x, int y, int width, int height) abstract void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) abstract void drawString(AttributedCharacterIterator iterator, int x, int y) abstract void drawString(String str, int x, int y) void fill3DRect(int x, int y, int width, int height, boolean raised) abstract void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) abstract void fillOval(int x, int y, int width, int height) abstract void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) abstract void fillRect(int x, int y, int width, int height) abstract void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) void finalize() Disposes of this graphics context once it is no longer referenced. abstract Shape getClip() abstract Rectangle getClipBounds() Rectangle getClipBounds(Rectangle r) abstract Color getColor() abstract Font getFont() FontMetrics getFontMetrics() abstract FontMetrics getFontMetrics(Font f) boolean hitClip(int x, int y, int width, int height) abstract void setClip(int x, int y, int width, int height) abstract void setClip(Shape clip) abstract void setColor(Color c) abstract void setFont(Font font) abstract void setPaintMode() abstract void setXORMode(Color c1) String toString() abstract void translate(int x, int y) */
JTabbedPane
El ejemplo siguiente ilustra dos aspectos interesantes de Swing
  1. El uso de solapas (JTabbedPane). Este componente resulta muy útil al admitir múltiples JPanel con sus propios nombres en un mismo Container. La situación de las solapas está bajo control del programador; hemos optado por hacer que se encontraran en la parte superior del cuadro, pero podrían ubicarse en cualquier borde del mismo.
  2. El concepto de JPanel como componente reutilizable. Todos los ejemplos anteriores están basados en un JPanel que se añadía a un JFrame, lo cual sugiere una idea brillante: construir JPanel destinados a su uso en múltiples lugares (por ejemplo, se podría hacer una variante del generador de curvas de Lissayous que admitiera cualquier nube de puntos a través del constructor. Entonces dispondríamos de un componente para representar cualquier tipo de gráficas, y bastaría añadir un módulo de lectura de archivos para disponer de un programa de generación de gráficos.

Un panel con solapas
/** 
 * JTabbedSencillo.java
 *
 * Descripción:  
 * @author      coti
 * @version     1.1
 */

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;

public class JTabbedSencillo {

  static public void main(String[] args) {
    JPanel pt = new PanelTexto();
    JPanel pg = new PanelGrafico();
    JPanel pb = new PanelBotones();
    JTabbedPane jtp = new JTabbedPane();
    jtp.addTab("Gráfico", null, pg, "Esta solapa muestra un gráfico");
    jtp.addTab("Botones", null, pb, "Esta solapa muestra botones");
    jtp.insertTab("Texto", null, pt, "Esta solapa muestra un texto",0);
    JFrame fr = new JFrame("Demostración de panel con solapas");
    fr.getContentPane().add(jtp, BorderLayout.CENTER);
    fr.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent we) {
        System.exit(0);
      }
    });
    fr.pack();
    fr.setVisible(true);
  }
  
}
***************************
import javax.swing.JPanel;
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.Color;

public class PanelTexto extends JPanel {
  PanelTexto() {
    setPreferredSize(new Dimension(400,200));
    setToolTipText("Esta tarjeta muestra un texto.");
  }
  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.drawString("Verba volant,scripta manent.", 20,100);
  }
}
***************************
import javax.swing.JPanel;
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.Color;


public class PanelGrafico extends JPanel {
  PanelGrafico() {
    setPreferredSize(new Dimension(200,200));
    setToolTipText("Esta tarjeta muestra una curva de Lissajous");
  }
  protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    int iFrecuencia1, iFrecuencia2, iDesfase;
    iFrecuencia1 = 7;
    iFrecuencia2 = 11;
    iDesfase = 0;
    int iPosX1 = 0; int iPosY1 = 0;
    int iPosX2 = 0; int iPosY2 = 0;

    int iAnchoPanel = getSize().width;
    int iAltoPanel = getSize().height;

    double t = 0.0D;
    double dIncrementoT = 1.0D / (2.0D*Math.max( iFrecuencia1, iFrecuencia2 ));

    // Quedará una línea amarilla sobre fondo azul (el fondo es el del JPanel)
    g.setColor( Color.yellow );
    g.translate( iAnchoPanel/2, iAltoPanel/2 );

    // Calculamos el punto inicial de la primera línea a pintar (primer intervalo)
    iPosX2 = 0;
    iPosY2 = (int)(((iAltoPanel - 10) * Math.sin((iDesfase * Math.PI) / 180)) / 2);

    // Generamos varios intervalos, incrementados según <dIncrementoT>
    while(t < 360) 
    {
      // Calculamos el primer punto de la línea a pintar en este intervalo
      iPosX1 = (int)(((iAnchoPanel - 10) * Math.sin((iFrecuencia1 * t * Math.PI) / 180)) / 2);
      iPosY1 = (int)(((iAltoPanel - 10) * Math.sin(((iFrecuencia2 * t * Math.PI+ iDesfase)) / 180)) / 2);

      // Dibujamos la línea correspondiente al primer intervalo
      g.drawLine(iPosX2, iPosY2, iPosX1, iPosY1);

      // El punto inicial de la línea a pintar en el siguiente intervalo es el punto
      // final de la línea que acabamos de pintar en este intervalo
      iPosX2 = iPosX1;
      iPosY2 = iPosY1;

      // Pasamos al siguiente intervalo
      t += dIncrementoT;
    }

    // Pintamos la última línea
    iPosX1 = 0;
    iPosY1 = (int)(((iAltoPanel - 10) * Math.sin((iDesfase * Math.PI) / 180)) / 2);
    g.drawLine(iPosX2, iPosY2, iPosX1, iPosY1);
  };
}
***************************
import javax.swing.JButton;
import javax.swing.JPanel;
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.Color;

public class PanelBotones extends JPanel {
  PanelBotones() {
    String[] dia = {  "Lunes", "Martes",
              "Miércoles", "Jueves",
              "Viernes", "Sábado",
              "Domingo"};
    JButton[] b = new JButton[dia.length];
    int i;
    
    setPreferredSize(new Dimension(400,200));

    for(i=0;i<dia.length;i++)
      {
        b[i] = new JButton(dia[i]);
        add(b[i]);
      }
    setToolTipText("Esta tarjeta muestra una colección de botones");
  }
}

Ejercicios propuestos

  1. Crear una ventana con un panel que contenga la palabra Google empleando un tipo de letra grande y distintos colores. Pista: consultar la documentación relativa a Font y FontMetrics
  2. Consultar la documentación de la clase JTextField y modificar el ejercicio que representa curvas de Lissajous para que admita frecuencias (se necesitarán dos campos, uno para la frecuencia horizontal y otro para la vertical). El usuario dispondrá de un botón de ejecución que dará lugar a la visualización del gráfico correspondiente. Obsérvese que es preciso fijar el valor de las frecuencias desde el programa principal, en el cual se leen los valores insertados por el usuario.
  3. Generar un programa basado en solapas dotado de paneles que contengan los campos adecuados para dos matrices 3x3. El programa dispondrá de paneles adicionales que contengan la suma, resta y producto de las matrices anteriores. Considérese la conveniencia de utilizar la arquitectura MVC. ¿Sería posible generalizar el problema para matrices NxN?
  4. Se desea construir una aplicación didáctica destinada a ilustrar el concepto de integral. Para ello se necesitan varios paneles que mostrarán un curva previamente seleccionada (en un JComboBox, por ejemplo). El primer panel permitirá seleccionar la curva, el intervalo de integración y el número de subintervalos. Los paneles segundo y tercero ilustrarán gráficamente el valor de la integral como área bajo una curva; se utilizarán rectángulos en un caso y trapecios en otro. El programa debe mostrar en otro color las zonas que realmente pertenecen a la integral y no se tienen en cuenta en el cálculo, o que no pertenecen a la integral y se suman al resultado. El valor numérico de la integral aparecerá en pantalla junto al valor exacto de la misma y el número de subintervalos utilizados. Se recomienda utilizar funciones sencillas, de integral conocida, para poder comparar los resultados numéricos con los verdaderos.