Clase 16 -Interfaz gráfica swing

SWING


Para la implementación de un graficador de funciones de una variable real primero necesitamos definir una interfaz gráfica, es decir, los componentes necesarios y cómo acomodarlos. Después de crear un método de graficación, necesitamos definir el  manejo de eventos de botón, de campo de texto y de ratón para interactuar de manera adecuada con el usuario.
Esta presentación  se inicia describiendo la manera en se pueden crear y acomodar los componentes del graficador usando la interfaz gráfica de usuario (GUI) Swing. Luego se implementa un método  que lee una función introducida por el usuario  y construye su gráfico en el dominio de pantalla usando Java2D. Por último  se maneja los eventos de mouse para interactuar con el gráfico.




Interfaz gráfica. Administradores de diseño.
Una primera vista de nuestro graficador se puede apreciar en la siguiente figura
Este graficador tiene una zona en la que aparecerá el gráfico, un panel para el logo, un panel de control donde aparecen los botones  "Graficar" y "Ayuda" y un campo de texto para introducir la función. También hay  un panel para poner dos barras de desplazamiento (sliders) para manejar la escala en los ejes.
Todos estos componentes están dentro de un 'Container' llamado aquí  "Contenedor". Para manejar de una manera adecuada los bordes,  algunos componentes se agrupan en un componente más grande, como se detalla en la figura que sigue


O sea, el "contenedor" incluye el panel "DisplayPanel1" donde estará el panel de graficación "ZG" y el panel "DisplayPanel2" donde estarán los paneles "LogoPanel", "ControlPanel" y "SliderPanel"
El código para generar este applet requiere  importar algunas bibliotecas de Swing. Los comentarios del código están inmediatamente después.



Un 'parser' para leer la función
Para graficar una función necesitamos
  1. Leer la función
  2. Calcular los pares  ordenados y unirlos con un segmento de recta
Para leer la función debemos usar un 'parser', es decir una clase que nos permita leer y evaluar la fórmula que define a la función. Aquí, en vez de implementar un 'parser' (que sería bastante laborioso), vamos a usar uno que está disponible de manera gratuita  en internet: JEP (Java Expression Parser,http://sourceforge.net/projects/jep/). Al momento de esta publicación, la versión actual es la 2.3.1
Primero vamos a implementar un pequeño applet para ver como usar el evaluador
La manera fácil para poder usar JEP con el IDE  JCreator (http://www.jcreator.com/)  es  descargar JEP y poner la carpeta 'org' (que viene en la subcarpeta 'build')    en la subcarpeta  'classes' del proyecto.
Si usa otro IDE, deberá hacer los cambios adecuados para que Java pueda encontrar el archivo jep231.jar.
En nuestro programa, se deben importar dos bibliotecas
import org.nfunk.jep.*;
import org.nfunk.jep.type.*;
La segunda biblioteca nos permite un buen manejo de los números complejos, para trabajar con funciones como 




Método de graficación.
Ahora que podemos leer funciones y evaluarlas, ya podemos implementar el método que construye el gráfico de la función.
Coordenadas de pantalla y coordenadas reales
Primero debemos establecer un factor de escala para el eje X y el eje Y. Digamos 'escalaX = 30 pixeles' y  'escalaY=30 pixeles'. 
Ahora debemos manejar la conversión entre coordenadas en números reales y coordenadas de pantalla. Supongamos que la variable 'aReal' corresponde al valor como número real de la coordenada de pantalla 'a'
  • Conversión: números reales a coordenadas de pantalla
a = (int)Math.round(escalaX * (aReal ) );
  • Conversión: coordenadas de pantalla a números reales
aReal = 1.0*a/escalaX;




Manejo de eventos
Bien, ya tenemos casi todos los ingredientes. Sólo falta agregar el manejo de eventos, es decir,
  1. si el usuario presiona el botón graficar o da enter en el campo de texto de la función, se debe dibujar el gráfico
  2. si el usuario arrastra el mouse sobre la zona gráfica, se debe arrastrar  el gráfico
  3. si el usuario usa los deslizadores (sliders) se ejecuta el cambio de escala en el eje respectivo
  4. si el usuario entra al panel del logo (el mouse entra al panel) entonces el curso cambia a una "manita" indicando que, dando un clic o doble clic, irá al sitio web del CRV.
  5. un frame de ayuda (acerca de la sintaxis de las fucniones)
Para hacer estas tareas debemos implementar los manejadores de eventos respectivos.

I. Manejador de eventos para campo de texto y para botón
Agregamos un auditor (listener) al campo de texto y al botón que "escucha" los eventos de  'dar enter' en el campo de texto o presionar el botón. Cuando esto sucede, solamente ejecutamos  ZG.repaint(); con lo que se ejecuta el método gráfico en el panel ZG
... public void init()
 { ...

   ManejadorDeEvento ManejadorDevt = new ManejadorDeEvento();
   Tffun.addActionListener(ManejadorDevt);
   BtnGraficar.addActionListener(ManejadorDevt);
 }//

private class ManejadorDeEvento implements ActionListener
{
  public void actionPerformed (ActionEvent evt)
  {
    Object source = evt.getSource ();
    // si se presiona el botón o se da 'enter' en algún campo de texto
    if ( source == BtnGraficar || source == Tffun)
    {
      ZG.repaint();
    }
  }
}

 II. Arrastre del mouse
Para implementar la respuesta al arrastre del mouse, debemos localizar el punto inicial de arrastre A y el punto final B y redefinir el origen (x0,y0) de acuerdo al vector B-A. Esto lo hacemos en la  clase interna 'ZonaGrafica'

class ZonaGrafica extends JPanel  implements MouseListener, MouseMotionListener
{
  int offsetX, offsetY;
  boolean dragging;

  ZonaGrafica()
  {
   offsetX=x0; offsetY=y0;
   addMouseListener(this);       //auditores para eventos de mouse
   addMouseMotionListener(this);
  }
  //manejo de eventos de mouse
  public void mousePressed(MouseEvent evt)
  {
   if (dragging)
   return;
   int x = evt.getX(); // clic inicial
   int y = evt.getY();
   offsetX = x - x0;
   offsetY = y - y0;
   dragging = true;
  }

  public void mouseReleased(MouseEvent evt)
  {
   dragging = false;
   repaint();
  }

  public void mouseDragged(MouseEvent evt)
  {
    if (dragging == false)
    return;
    int x = evt.getX(); // posición del mouse
    int y = evt.getY();
    x0 = x - offsetX;  // mover origen usando suma vectorial
    y0 = y - offsetY;
    repaint();
  }

   //el resto hace nada
  public void mouseMoved(MouseEvent evt) {}
  public void mouseClicked(MouseEvent evt) { }
  public void mouseEntered(MouseEvent evt) { }
  public void mouseExited(MouseEvent evt) { }

  public void paintComponent(Graphics g)
  { ...

III. Deslizadores (sliders)
Agregar un par de deslizadores en el panel SP (instancia de SliderPanel) y un manejador de eventos que escuchan si hay algún cambio en cada deslizador. Si se escucha algún cambio, se redefine la variable escalaX o la variable escalaY, según corresponda y se ejecuta ZG.repaint() para actualizar en 'tiempo real'.

import javax.swing.event.*;...
class SliderPanel extends JPanel
{
  JSlider xSlider,ySlider; // Manejo de escala

  SliderPanel()
  {
   setLayout(new GridLayout(1,2));
   SliderListener auditor = new SliderListener();
   //escala X
   xSlider = new JSlider(JSlider.VERTICAL, 1, 200, 20);
   xSlider.addChangeListener(auditor);
   add(xSlider);
   //escalaY
   ySlider = new JSlider(JSlider.VERTICAL, 1, 200, 20);
   ySlider.addChangeListener(auditor);
   add(ySlider);

   //xSlider.setLabelTable(xSlider.createStandardLabels(20));
   //xSlider.setMajorTickSpacing(200);
   xSlider.setMinorTickSpacing(20);
   xSlider.setPaintTicks(true);
   xSlider.setPaintLabels(true);

   //ySlider.setMajorTickSpacing(200);
   ySlider.setMinorTickSpacing(20);
   ySlider.setPaintTicks(true);
   ySlider.setPaintLabels(true);
  }//

  class SliderListener implements ChangeListener
  {
    public void stateChanged(ChangeEvent e)
    {
      JSlider source = (JSlider)e.getSource();
      ajusteEscala();

    }
  }

  public void ajusteEscala()
  { // se ejecuta si se 'oyó' algún cambio en algún Slider
   escalaX =(int) xSlider.getValue();
   escalaY =(int) ySlider.getValue();
   ZG.repaint();
  }//
} //fin class

IV. Implementar LogoPanel
 El manejo propuesto en LogoPanel no requiere mucho detalle, solo agregar una propiedad que defina comportamiento requerido. Esta propiedad se la podemos agregar en init().

...
public void init()
{ ...
  //Logopanel
  LogoPanel = new JPanel();
  LogoPanel.add(new JLabel(logocrv));
  LogoPanel.addMouseListener (new MouseAdapter ()
  {
   public void mouseClicked (MouseEvent e)
   {
     if(e.getClickCount() >0)
     {
       try{URL elURL= new URL("http://www.tec-digital.itcr.ac.cr/revistamatematica/crv/index.html");
           getAppletContext().showDocument(elURL);
          }catch(MalformedURLException ae){}
     }
   }
    });

   LogoPanel.setCursor(new Cursor(Cursor.HAND_CURSOR));
  //fin logoPanel

...

V. Ventana (JFrame) de ayuda
Para la ventana de ayuda  creamos un JFrame con un JTextArea con algunas  indicaciones acerca d ela sintaxis de las funciones en JEP. Debemos agregar un auditor al botón 'Ayuda' para que levante la ventana cuando es presionado.
...public void init()
{ ...

  BtnAyuda.addActionListener(ManejadorDevt);
}

...
private class ManejadorDeEvento implements ActionListener
{
  public void actionPerformed (ActionEvent evt)
  {
    Object source = evt.getSource ();
    ...
    if(source == BtnAyuda)
    {
      fFrame.setVisible (true);
    }// 

  }
}//

class AyudaJFrame extends JFrame
{

   JTextArea p;

   GraficadorClasico fApplet;

   AyudaJFrame(GraficadorClasico applet)
   {
    super ("Ayuda");
    fApplet=applet;
    Container content_pane = getContentPane ();

    p = new JTextArea(30,40);
    p.setText(information());
    p.setEditable(false);

    JScrollPane sp = new JScrollPane(p);
    content_pane.add(sp,BorderLayout.CENTER);

    pack ();
    setDefaultCloseOperation (JFrame.DISPOSE_ON_CLOSE);
   }

   public void actionPerformed (ActionEvent e)
   {   
     //nada por hoy
   }


  String information(){
    String message =
    " :.\n"
+ " Mover ejes : arrastre el mouse\n\n"
+" ------ ------ EJEMPLO\n"
+ " + suma x+2\n"
+ " - resta x-5\n"
+ " * multiplicación 3*x\n"
+ " / división -1/x\n"
+ " () agrupación (x+2)/(3*x)\n"
+ " ^ potenciación (-3*x)^2\n"
+ " % resto de la división x%5\n"
+ " RAIZ(x) raíz cuadrada RAIZ(x)\n"
+ " sqrt() raíz cuadrada sqrt(x)\n"
+ " mod() resto de la división mod(x,5)\n"
+ " sen() seno 4*sen(x^2)\n"
+ " cos() coseno 6*cos(-3*x)\n"
+ " tan() tangente 3*tan(x)\n"
+ " atan() arcotangente atan(x-3)\n"
+ " asin() arcoseno asen((x+5)/(3^x))\n"
+ " acos() arcocoseno 2-acos(-x+3)\n"
+ " sinh() seno hiperbólico sinh(x)\n"
+ " cosh() coseno hiperbólico -3*cosh(1/x)\n"
+ " tanh() tangente hiperbólica tanh(x)/2\n"
+ " asinh() arcoseno hiperbólico 2*asinh(x)/3\n"
+ " acosh() arcocoseno hiperbólico (2+acosh(x))/(1-x)\n"
+ " atanh() arcotangente hiperbólica atanh(x)*(3-x^(1/x))\n"
+ " ln() logaritmo natural ln(x)+1\n"
+ " log() logaritmo decimal -2*log(x)-1\n"
+ " abs() valor absoluto abs(x-2)\n"
+ " rand() valor aleatorio rand()\n"
+ " re() parte real de un Complejo re(2+9*i)\n"
+ " im() parte imaginaria im(-8+7*i)\n"
+ " angle() ángulo en pos. estándar angle(x,2)\n\n"
+ " pi 3,141592653589793 pi+cos(x)\n"
+ " e 2,718281828459045 e+1\n"
+ " Usa JEP,(Nathan Funk http://sourceforge.net/projects/jep/\n\n";

return message;
}//información

// class AyudaFrame

No hay comentarios:

Publicar un comentario