Tabla de Contenidos
Interfaces Gráficas de Usuario
Las interfaces gráficas de usuario son la parte de un programa que sirve para interactuar con él. Cuando empezamos a programar, las interfaces para manejar el programa suelen ser menus de texto mostrados en un terminal, que nos permiten seleccionar diferentes opciones a través del teclado.
A la hora de crear un programa más accesible necesitamos crear una interfaz de manejo del programa mas visual, que permita utilizar el ratón para seleccionar las opciones, responda a los eventos producidos por el usuario (click de ratón, pulsación de teclas), y utilice elementos gráficos para representar la información. La parte visual de un programa que sirve de intermediario entre el usuario y el programa se conoce como Interfaz Gráfica de Usuario ó GUI (Graphical User Interface).
Menu de consola y Menu GUI
Librerías gráficas
Las Java Foundation Classes (JFC) es un framework para desarrollar interfaces gráficos en Java. Constan principalmente de 3 librerías:
- AWT (Abstract Window Toolkit): es la API más antigua dependiente de la plataforma.
- Swing: API de Java independiente de la plataforma
- Java2D: API de Java para dibujar gráficos
AWT
Las librerias Abstract Window ToolKit son las más antiguas. Cuando apareció Java, estas librerías ofrecían los componentes gráficos disponibles para el desarrollo de GUI.
Las clases de estas librerías se crearon utilizando código nativo de una plataforma concreta. Ya que la idea de Java es crear una tecnología de desarrollo independiente de la plataforma que la ejecute, esto generaba algunas limitaciones: al utilizar estas librerías se debía limitar el la funcionalidad a las comunes a todas las plataformas (Windows, Mac, Linux) en las que se quería usar AWT.
Además de los componentes Gráficos, AWT contiene también las clases que representan los eventos producidos en la GUI.
A pesar de estas características, AWT sigue siendo imprescindible para la existencia de Swing, ya que todos los componentes de Swing se han creado a partir de sus homólogos en AWT.
Las clases pertenecientes a las librerías AWT se encuentran en el paquete java.awt Las clases relativas a los eventos AWT se encuentran en java.awt.event
Swing
Java junto con la ayuda de NetScape desarrollaron las librerías Swing. Como hemos visto antes, cada componente de Swing está basado en un componente AWT. La finalidad de las librerías Swing es crear interfaces gráfiacas que muestren el mismo aspecto independientemente de la plataforma en la que se ejecuten: las clases Swing están completamente escritas en Java por lo que la postabilidad es total. A diferencia de AWT no necesitan una ventana del sistema operativo para mostrar sus componentes, sino que es el lenguaje Java quien crea su propio elemento ventana.
Elementos principales
A continuación examinamos diferentes componentes de Swing. Todas las referencias están obtenidas de la documentación oficial de Java: https://docs.oracle.com/javase/tutorial/uiswing/components/index.html
Para mostrar los ejemplos utilizaremos la interfaz de WindowBuilder para Eclipse.
Contenedores de alto nivel
- JFrame: Ventana principal para una aplicación de escritorio. Contiene los botones de cerrar maximinar y minimizar. Cuando está en ejecución mantiene un elemento en la barra de tareas.
- JApplet: Ventana principal para una aplicación Web. Contiene los botones de cerrar maximinar y minimizar. Cuando está en ejecución mantiene un elemento en la barra de tareas. Los applets están en desuso.
- JDialog: Se usa para crear ventanas secundarias de dialogo. La diferencia respecto a los contenedores anteriores, es que no muestra un elemento en la barra de tareas. En la categoría de este tipo de contenedores tenemos JOptionPane cuadros de dialogo sencillos, o JFileChooser una ventana de dialogo para seleccionar ficheros. Si queremos crear un cuadro de dialogo personalizado usamos JDialog.
Cualquier programa tiene una jerarquía de contenedores, siendo JFrame, JApplet o JDialog la raíz de la jerarquía, ya que cada uno tendrá una clase propia donde será definido. Si una aplicación tiene 3 ventanas, tendrá 3 jerarquías de contendores y 3 contenedores de alto nivel (un JFrame y 2 JDialog).
A cada ventana podemos especificarle un título, un icono, y si la ventana será redimensionable o no, entre otras opciones que podemos establecer en las propiedades del elemento en la interfaz de WindowBuilder.
Un contenedor de alto nivel tiene algunas propiedades:
- Título de la ventana
- Posición de la ventana
- Operación de cerrado por defecto
- …
En el siguiente código podemos ver como se establecen estas propiedades:
public class Ventana extends JFrame{ ... //Al pulsar la X terminamos la ejecución de la aplicación //Tambien puedo indicar DO_NOTHING_ON_CLOSE, o DISPOSE_ON_CLOSE setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //Titulo de la ventana setTitle("Mi ventana"); //Establecemos la posición de la ventana //En este caso (null) indica que aparece en el centro de la pantalla setLocationRelativeTo(null); //Establezo un icono para la barra de título de la ventana setIconImage(new ImageIcon(imgURL).getImage()); //Establecemos el contenedor de segundo nivel JPanel contentPane = new JPanel(); setContentPane(contentPane); //Hago visible la ventana setVisible(true); ... }
Todos estos métodos son comunes a los contendores de alto nivel (JFrame o JDialog).
Contenedores de 2º nivel
Todo contenedor de alto nivel tiene un elemento ContentPane (Container) para emplazar el resto de componentes (etiquetas, cuadros de texto, botones, etc). El contentPane por defecto es un elemento JPanel, aunque podemos usar otros similares. Estos contenedores se conocen como contenedores de segundo nivel.
Los componentes gráficos se añaden y visualizan sobre el contentPane, no sobre un JFrame.
Desde un contenedor principal JFrame o JDialog puedo invocar al método getContentPane() que me devolvera el contenedor de segundo nivel. Del mismo modo puedo indicar a un contenedor de alto nivel (Jframe) cual es su contenedor principal de segundo nivel invocando a setContentPane(contenedor).
Barra de Menus - JMenuBar
Una aplicación está formada por un contenedor de alto nivel, el contenedor de segundo nivel contentPane y opcionalmente una barra de menús. Normalmente una barra de menús solo aparece en las ventanas principales (JFrames).
https://docs.oracle.com/javase/tutorial/uiswing/components/menu.html
Para crear una barra de menú usaremos el elemento JMenuBar. Cada Pestaña desplegable de ese menú será un elemento JMenu. Cada elemento de ese menú deplegable será un JMenuItem. Además, podemos incluir elementos de tipo CheckBox, o RadioButtons.
Si queremos incluir una pestaña Salir, la instrucción que debemos ejecutar es: System.exit(0).
Eventos Principales: ActionPerformed
Componentes para contenedores de 2º nivel
JLabel
Se usa para mostrar una etiqueta con texto o con una imagen en nuestra aplicación. Para incluir un texto o una imagen podemos usar las propiedades del JLabel:
Text
: texto que muestra.Icon
: imagen que queremos que contenga.
Métodos Principales:
setText(String texto)
: Permite modificar el texto que muestra la etiqueta.
JButton
Hace referencia a un botón que puede ser pulsado. Los siguientes elementos hacen referencia a los botones y heredan de la clase AbstractButton:
JButton
: Botón convencionalJToggleButton
: Botón que puede estar presionado o no presionado.JCheckBox
: Casilla de verificaciónJRadioButton
: Parecido a la casilla de verificación. Tiene la peculiaridad que se suele usar alternativamente con otros JRadioButtons.
Evento Principal: ActionPerformed
Además, los botones que pueden estar seleccionados o no (Checkbox, RadioButton), tienen dos métodos importantes para trabajar con ellos:
isSelected()
: devuelve true o false si está seleccionado o no.setSelected(boolean estado)
: se usa para marcar o desmarcar el botón.
JRadioButton
En el caso de los JRadioButton
es interesante permitir la selección de un solo botón entre varios del mismo tipo.
Para hacer esto seleccionaremos todos los elementos JRadioButton
que queremos que estén agrupados y pulsamos botón derecho, opción Set ButtonGroup
y creamos un nuevo grupo standard
.
JComboBox
Elemento que permite seleccionar distintas opciones en un menú desplegable. Cuando creamos uno debemos establecer un modelo para él. En la sección de propiedades pulsaremos sobre la opción model y se nos desplegará una ventana en la que podremos indicar qué opciones queremos que tenga el ComboBox:
Es interesante remarcar que al modelo podemos añadirle elementos desde un array.
Métodos principales:
getSelectedIndex()
: devuelve un entero indicando el elemento seleccionado.getSelectedElement()
: devuelve el objeto seleccionado.
También permite ser usado con un modelo, que facilita el trabajo con los objetos que puede mostrar: Clase DefaultComboBoxModel
JTextField
Cuadro de texto convencional. Puede contener un texto por defecto, mostrar texto u obtener texto para gestionarlo.
Métodos principales:
setText(String texto)
: Muestra el texto en el cuadro.getText()
: Devuelve un String con el texto del cuadro.
JPasswordField
Es un cuadro de texto que no muestra los caracteres que se teclean.
Métodos principales:
getPassword()
: Devuelve un array de char con el texto del cuadro.
Si queremos convertir el array a String podemos usar el método estático String.valueOf(array)
.
JScrollPane
Se usa principalmente para que los elementos que contiene tengan barras de desplazamiento en caso de que su contenido supere las dimensiones de su contenedor.
Para usarlo simplemente crearemos un elemento JScrollPane
en la ventana principal. Posteriormente emplazaremos dentro del JScrollPane el elemento que queramos que tenga barras de desplazamiento, por ejemplo un JList
, o un JTextArea
.
JTextArea
Funciona de la misma forma que un JTextField pero se usa para contener más cantidad de texto, o pasa mostrarlo. Puede ser editable o no, lo que quiere decir que podremos escribir texto y modificarlo o no se podrá escribir desde teclado, y solo se mostrará desde el código de nuestro programa.
Métodos principales:
setText(String texto)
: Mostrará el texto que contiene el String que le pasamos.getText()
: Devuelve un String con el texto que contiene.append(String texto)
: Añade al cuadro más texto, sin eliminar el texto actual.
JSpinner
Es un elemento similar a un ComboBox que permite seleccionar valores entre un rango. Permiten establecer un límite máximo y mínimo y un valor por defecto en sus propiedades.
Métodos principales:
setValue(Object valor)
: Establece un objeto como valor para el spinner. Si queremos indicar un número debes usar el tipo envoltorio Integer.getValue()
: Devuelve el objeto con el valor establecido. Si se trata de un número debemos hacer un cast a int.
Evento principal: stateChanged
- se produce cuando hay un cambio en su valor.
JSlider
Es una barra de desplazamiento para indicar valores numéricos dentro de un rango. Permite establecer un máximo, minimo, valor por defecto, y una escala, en sus propiedades.
Propiedades:
majorTickSpacing
: sirve para indicar las etiquetas en las que se dividirá la barra del slider.Maximun
: límite superior.Minimun
: límite inferior.paintLabels
: true para que aparezca una leyenda de valores.Value
: valor por defecto con el que comienza.
Métodos Principales:
setValue(int valor)
: El slider se posiciona en dicho valor.getValue()
: Devuelve un entero con el valor de su posición.
Eventos: stateChanged
- se produce cuando hay un cambio en su valor.
Relacionar JSlider y JSpinner
Como son dos elementos usados de forma parecida podemos relacionarlos:
spinner.setValue(slider.getValue()); //Para el spinner slider.setValue((int)spinner.getValue()); //Para el slider
JList
Es una lista de elementos de tipo objeto: Pueden ser Strings o cualquier tipo de objeto. Permite seleccionarlos, de uno en uno o varios a la vez. También se combina con JScrollBar para que tenga barras de desplazamiento.
La peculiaridad que tiene, al igual que los ComboBox o las tablas, es que trabaja con un modelo (Model), y también que es un elemento de tipo “Raw”. Esto quiere decir que al igual que los ArrayLists p.e. se debe definir el tipo de datos que contiene.
JList<Cliente> lista = new JList<Cliente>();
Para trabajar con un JList de forma sencilla, se debe hacer uso de un elemento de tipo Model, el cual también es de tipo Raw. Primero crearemos un Modelo para la lista y posteriormente trabajamos directamente con dicho modelo.
DefaultListModel<Persona> listModel = new DefaultListModel(); JList<Persona> listaPersonas = new JList(listModel); listModel.addElement(new Persona("Jane Doe")); listModel.addElement(new Persona("John Smith")); listModel.addElement(new Persona("Kathy Green"));
Para añadir el contenido de un ArrayList:
Jlist<Cliente> lista = new JList<Cliente>(); DefaultListModel<Cliente> modelo = new DefaultListModel<Cliente>(); lista.setModel(modelo); //Accedo a la lista de clientes, y añado sus elementos for (Cliente miCliente : listaClientes) { modelo.addElement(miCliente); }
Para obtener el directamente el objeto seleccionado:
lista.getSelectedValue()
En las propiedades del JList podemos indicar si queremos que se permitan seleccionar diferentes elementos o solo uno (SINGLE_SELECTION
)
Evento: ListSelectionEvent
- se produce al seleccionar o deseleccionar elementos del JList
JProgressBar
Es una barra de progreso, se suele emplear para indicar el avance de las tareas.
Métodos principales:
setValue(int valor)
: Establece el valor que mostrará la barra.
JSeparator
JTabbedPane
Permite crear un contenedor de pestañas, separando en cada pestaña los elementos que sean necesarios en cada caso.
Para crear un JTabbedPane con varias pestañas, debemos emplazar un elemento JTabbedPane en el lugar de nuestro contentPane en el que lo queramos. Posteriormente, para añadir pestañas simplemente añadiremos elementos de tipo JPanel a nuestro JTabbedPane.
Cada una de las pestañas se trata igual que el contentPane
principal (como un JPanel), por lo que debemos asignarle un layout y posteriormente colocar elementos en él.
DatePicker
Java no tiene un selector nativo de fechas (calendario) para Swing. Sin embargo, existen diversos proyectos particulares de componentes personalizados. Esto se conoce en Java como JavaBeans. Los JavaBeans o Beans son clases empaquetadas, reutilizables en nuestro código.
Para obtener un selector de fechas que trabaje con las clases del paquete java.time
(LocalDate, LocalTime, LocalDateTime a partir de la versión 8 de Java) podemos usar el Bean creado por el proyecto LGoodDatePicker
.
Para usar los beans, debemos tener el fichero Jar con los componentes que queremos utilizar y añadirlo al ClassPath de nuestro proyecto como cualquier otro JAR. Posteriormente desde el editor gráfico de WindowBuilder, en la sección System pinchamos sobre Choose component y buscamos la clase DatePicker (LGoodDatePicker). Una vez que podamos acceder a ese elemento bastará con arrastrarlo a la parte de nuestra ventana en la que queramos emplazarlo.
Métodos:
getDate()
: Devuelve un tipo LocalDate con la fecha seleccionada.setDate(LocalDate fecha)
: Establece la fecha indicada en el DatePicker.
Links del proyecto: https://mvnrepository.com/artifact/com.github.lgooddatepicker/LGoodDatePicker Pulsamos sobre la versión que queramos y descargamos el fichero Jar.
JToolBar
Se usa para crear una barra de herramientas, con botones. Se añaden los botones y se tratan como JButtons.
https://docs.oracle.com/javase/tutorial/uiswing/components/toolbar.html
JTable
Se usa para mostrar la información mediante filas y columnas. Muy útil para cargar información de las tablas de una base de datos. Cada fila de la tabla se añade con un array de Objetos.
Para facilitar el manejo podemos usar un modelo DefaultTableModel
y añadirselo al componente JTable.
https://docs.oracle.com/javase/tutorial/uiswing/components/table.html
JTextPane, JEditorPane
Se usan para mostrar texto con formato.
https://docs.oracle.com/javase/tutorial/uiswing/components/editorpane.html
Diseñadores de interfaces
WindowBuilder para Eclipse
WindowBuilder es un plugin de código abierto para Eclipse destinado a crear interfaces gráficas de usuario (GUI) de forma rápida y sencilla. Como otros muchos sistemas de este tipo, contiene una paleta de elementos que podemos añadir mediante “drag and drop”, arrastrar y soltar. En lugar de tener que escribir el código a medida que vamos incluyendo elementos gráficos en nuestra ventana, WindowBuilder nos genera el código en nuestras clases.
Aunque WindowBuilder sea una herramienta de gran ayuda, necesitamos comprender la estructura con la que WindowBuilder crea clases para poder modificarla y darle un formato a las clases que nos ayude a separar y entender el programa.
Instalación de WindowBuilder
Para poder descargar WindowBuilder iremos en Eclipse a la pestaña Help
y ahí a Eclipse MarketPlace…
. Buscamos el Plugin por su nombre (WindowBuilder) en el campo buscar, y una vez que lo haya encontrado, pulsamos sobre instalar.
Al terminar la instalación, reiniciamos Eclipse y ya podremos trabajar con él.
Crear un proyecto con ventanas
WindowBuilder está compuesto de varias herramientas para crear interfaces gráficas de usuario. Nosotros vamos a trabajar únicamente con los componentes Swing. Para ello usaremos la herramienta Swing Designer de WindowBuilder.
Por convenio, en Java una aplicación de escritorio
empleará un contenedor principal de tipo JFrame
. Si además contiene otras ventanas secundarias dentro del programa emplearemos contenedores de tipo JDialog
para estas ventanas secundarias.
Creamos un proyecto de la misma forma que lo hacemos siempre:
Creamos una clase JFrame para una ventana. Dentro de nuestro proyecto, en algún package
haremos lo siguiente. Debemos usar los componentes de Swing o de la sección Swing Designer de WindowBuilder:
También podemos acceder de forma rápida al menú de WindowBuilder mediante su nuevo botón, siempre seleccionando Swing como componentes gráficos:
Una vez creada la clase nos aparecerá una nueva vista, desde la cual podemos acceder al código o al diseño a través de las pestañas inferiores.
Ventana sin Layout Manager
Para poder emplazar elementos en una ventana de cualquier tipo debemos indicar una organización (Layout). Si al principio no queremos complicarnos demasiado con un layout podemos trabajar de forma sencilla con AbsoluteLayout
. Con este Layout podemos emplazar los elementos en el lugar que queramos, aunque no quedará tan bién a la hora de redimensionar la ventana. Usando AbsoluteLayout los elementos se emplazan atendiendo a coordenadas. Esto se desaconseja en el futuro, ya que no mantiene un orden al redimensionar la ventana.
Por defecto toda aplicación JFrame emplea un JPanel principal de contenido contentPane. En el iremos arrastrando todos los elementos que queramos emplear. Para aplicar un layout a nuestra ventana tan solo tenemos que pinchar sobre el layout que queremos usar y pinchar después sobre la ventana.
Añadir manejadores de eventos
Para añadir un manejador de eventos a nuestra aplicación, bastará con pulsar clic derecho sobre el elemento (p.e. botón) sobre el cual queremos controlar un evento. Se nos desplegará un menú en el que podemos seleccionar el evento que queremos controlar.
En algunos componentes deberemos crear un manejador de eventos para otro tipo de eventos (KeyEvent
, StateChanged
, p.e.), aunque como se ha comentado, en la mayoría de los casos bastará con el método actionPerformed()
.
Cuando creemos un manejador de eventos, este se asociará al elemento sobre el cual se realiza el evento (p.e. botón). Este código se creará justo debajo de la definición del componente, por lo que deberemos moverlo al lugar en el que lo queremos organizar (por ejemplo un método para inicializar los manejadores de eventos).
UIDesigner para IntelliJ
Cuando trabajamos con el IDE IntelliJ, también haremos uso de su diseñador de interfaces UIDesigner. La principal diferencia con WindowBuilder, es que UIDesigner no crea elementos de tipo JFrame o JDialog, sino que crea contenedores de segundo nivel (JPanel).
Este elemento posteriormente debemos añadirlo a un contenedor de alto nivel, usando el método setContentPane().
Para crear una GUI utilizando IntelliJ, haremos lo siguiente:
- Ir a File → New
- Seleccionar GUI Form (o Dialog)
- Dar nombre al formulario y seleccionar el Layout Manager
- Marcar el checkbox Create bound class, e indicar el nombre
En la siguiente videoclase se enseña la creación de una aplicacion MVC desde 0, con el IDE IntelliJ.
Manejadores de Eventos
Una interfaz gráfica se maneja mediante eventos. Los eventos le indican al programa como debe actuar: qué codigo debe ejecutar. La parte del programa que está atenta de esos eventos se conoce como manejador de eventos o listener.
Eventos
Un evento es una acción, provocada en la mayoría de los casos por el usuario, y que supone un cambio en el estado de algún componente (por ejemplo, botón). Los eventos, permiten llevar a cada la funcionalidad de las aplicaciones basadas en una GUI.
Para que nuestro programa pueda responder (ejecutar código) ante un evento, debemos usar un manejador de eventos.
El manejador de eventos es un objeto que implementa los métodos necesarios para responder a ciertos tipos de eventos. Por ejemplo, para responder a los eventos de tipo ActionEvent
necesitamos un objeto que haya implementado el método actionPerformed(ActionEvent event)
.
public void actionPerformed(ActionEvent evento) //El código de este método se ejecuta cuando se poduce un evento }
Después debemos vincular los elementos gráficos que queremos que respondan a los eventos. Para esto añadimos el objeto manejador de eventos al componente que queramos.
JButton btnNuevo = new JButton("Nuevo"); btnNuevo.addActionListener(manejadorEventos);
Para tener un objeto que maneje eventos, podemos usar dos planteamientos:
- utilizando una clase anónima que implemento el método actionPerformed()
- creando una clase concreta que implemente la interface ActionListener.
Usando Clases Anónimas
En alguna situaciones, dado que hay muy pocos componentes sobre los que manejar eventos, podemos evitar crear una clase concreta para manejar eventos. Simplemente implementaremos el método para manejar eventos actionPerformed()
directamente a la hora de contruir un objeto. Este se realiza mediante la instanciación de una clase anónima:
JButton btnNuevo = new JButton("Nuevo"); //Usamos una clase anónima que implementa la interface ActionListener btnNewNuevo.addActionListener(new ActionListener() { //Y por lo tanto está obligada a implementar sus métodos @Override public void actionPerformed(ActionEvent evento) { //código que se ejecuta al producirse el evento } }); //Fin de la llamada al método addActionListener()
Si nos fijamos, el parámetro que pasamos al método addActionListener()
no es una variable que contiene el objeto de tipo ActionListener, sino que dentro de los paréntesis creamos un nuevo objeto que implementa la interface ActionListener
. Estamos definiendo una clase anónima.
Usando Interfaces
Cuando tenemos que manejar eventos en muchos componentes (varios botones, diferentes eventos, etc) la forma más correcta es crear una clase que actúe como manejador de eventos, de modo que podamos crear un objeto de dicha clase y el mismo objeto se encargue de atender todos los eventos (objeto listener).
Para ello debemos crear una clase que implemente las interfaces de cada tipo de evento. En el siguiente punto se indican las principales interfaces para manejar los eventos más habituales.
public class ManejadorEventos implements ActionListener{ ... // código de la clase //Método de la interface ActionListener que se ejecutará al capturar un evento @Override public void actionPerformed(ActionEvent arg0) { //Código del manejador de eventos } }
Una vez que tengamos una clase que sirva de manejador de eventos, asociamos un objeto de dicha clase a cada componente sobre el que queramos responder a sus eventos:
ManejadorEventos listener = new ManejadorEventos(); boton.addActionListener(listener);
Tipos Eventos y sus Interfaces Listener
A continuación se indican los tipos de eventos más habituales y las interfaces que implementan métodos listener:
Tipo Evento | Interface | Descripción | Métodos |
---|---|---|---|
ActionEvent | ActionListener | Al pulsar botones | actionPerformed() |
KeyEvent | KeyListener | Al pulsar teclas | keyTyped(); keyPressed(); keyReleased() |
ListSelectionEvent | ListSelectionListener | Al seleccionar o deseleccionar en una lista | valueChanged() |
WindowEvent | WindowListener | Al abrir o cerrar ventanas, o cambiar el icono | windowActivated(); windowClosed(); windowClosing(); windowDeactivated(); windowDeiconified(); windowIconified(); windowOpened() |
FocusEvent | FocusListener | Cuando un elemento recibe el foco | focusGained(); focusLost() |
MouseEvent | MouseListener | Cuando uso el ratón en algún componente | mouseClicked(); mouseEntered(); mouseExited(); mousePressed(); mouseReleased() |
ContainerEvent | ContainerListener | Cuando añado o quito elementos de un contenedor | componentAdded(); componentRemoved() |
TableModelEvent | TableModelListener | Al modificar valores de celdas | tableChanged() |
PropertyChangeEvent | PropertyChangeListener | Al cambiar alguna propiedad del elemento | propertyChange() |
ChangeEvent | ChangeListener | Al realizar cualquier cambio en el elemento | stateChange() |
ComponentEvent | ComponentListener | Al cambiar la propiedad visible o redimensionar elementos | componentHidden(); componentMoved(); componentResized(); componentShown() |
Patrón de diseño: Modelo-Vista-Controlador
Cuando se diseña una aplicacion aplicando el patrón de diseño modelo-vista-controlador, se persigue separar la aplicación en 3 capas diferenciadas:
- La vista hace referencia a la ventana principal de la aplicación, la que contiene la mayoría de los elementos sobre los que el usuario va a interactuar. En MVC, la vista solo contiene el código referente a la construcción y organización de los componentes gráficos. Esta clase no contiene ningún manejador de eventos ni ningun código que haga alguna operación. Tampoco tiene ninguna interacción con el modelo, y es independiente del modelo y del controlador; de este modo es un componente reutilizable.
- El modelo es la capa que contiene los datos de la aplicación y los gestiona. Es la que se encarga de satisfacer las peticiones del usuario que se indican en la vista. Recordemos que el usuario solo interactua con la vista. El modelo contiene todos los métodos para realizar las operaciones de nuestra aplicación. Dar de alta elementos, eliminar, buscar, guardar, cargar, etc. Es completamente independiente de la vista y del controlador.
- El controlador es la capa que comunica a las otras dos. Al llamar a su constructor, se le pasa como parámetro una instancia de la vista, y otra del modelo. El controlador es quien tiene implementados los manejadores de eventos y se los añade a los componentes de la vista indicados, y también es quien ejecuta las operaciones del modelo en respuesta a esos eventos.
Resulta aconsejable que al menos las clases que representan la vista y el controlador estén dentro del mismo paquete (package
). De este modo podemos hacer uso del modificador package-private
, también conocido como default.
Vista
Todos los componentes gráficos (botones que se puedan pulsar, campos de texto de los que obtener datos, cajas de texto, etc) que quiera gestionar desde el controlador deben tener visibilidad default también llamada package-private.
public class Vista extends JFrame { JPanel contentPane; JTextField textNombre; JTextField textApellidos; ... //Todos los componentes swing que gestione desde el controlador public Vista(){ initComponents(); } public void initComponentes(){ setResizable(false); setTitle("Mi Aplicacion"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ... //resto del código generado por WindowBuilder set visible(true); } }
Modelo
El modelo es la clase que maneja los datos de la aplicación y permite las operaciones. Los puede obtener desde un fichero o desde una base de datos. Puede cargar esos datos en una estructura como el ArrayList o leerlos cada vez. No tiene ningún conocimiento ni interacción con la vista, y devuelve y recibe los datos tal cual se los pide el controlador.
public class Modelo{ //Lista con los datos que gestiona mi aplicación private ArrayList<Persona> listaPersonas; ... public Modelo(){ listaPersonas = new ArrayList<Persona>; } //Métodos para acceder a los datos public ArrayList<Persona> obtenerPersonas(){ return listaPersonas; } //Operaciones sobre los datos public void altaPersona(String nombre, int edad){ Persona nuevaPersona = new Persona(nombre, edad); listaPersonas.add(nuevaPersona); } ... }
Controlador básico
La clase Controlador añade los manejadores de eventos (listeners) a cada elemento de la vista que lo provoque. En el momento de añadir los listeners también los creamos, o sea generamos el código de respuesta a ese evento. Qué pasa cuando se pulsa un botón.
public class Controlador{ private Vista vista; private Modelo modelo; //Constructor recibe instancia de la vista y del modelo public Controlador(Vista vista, Modelo modelo){ this.vista = vista; this.modelo = modelo; initEventHandlers(); } //Método para añadir los listeners a los elementos que las generan (botones) public void initEventHandlers(){ vista.btnAceptar.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { ... //Código que quiero que ocurra al pulsar el botón de aceptar } }); //Añadir tantos manejadores de eventos como acciones pueda realizar ... } }
Controlador implementando interfaces
Otra forma mucho más elegante de implementar la clase Controlador es haciendo que trabaje como Listener de ciertos tipos de eventos. Para eso haremos que la clase implemente las distintas interfaces de cada tipo de evento. En el caso siguiente implementamos la interface ActionListener
, que ofrece soporte para los eventos de tipo ActionEvent
(acción principal sobre un botón):
public class Controlador implements ActionListener{ private Vista vista; private Modelo modelo; //Constructor recibe instancia de la vista y del modelo public Controlador(Vista vista, Modelo modelo){ this.vista = vista; this.modelo = modelo; asociarListeners(this); } //Metodo que asocia esta clase como listener de los botones public void asociarListeners(ActionListener listener){ vista.botonNuevo.addActionListener(listener); vista.botonEliminar.addActionListener(listener); } //Metodo que la interface ActionListener obliga a implementar //Es el encargado de gestionar los eventos ActionEvent de los botones @Override public void actionPerformed(ActionEvent evt) { //El ActionCommand es una propiedad que se define en cada boton String comando = evt.getActionCommand(); //Dicha propiedad nos sirve para saber qué boton se ha pulsado switch(comando){ case "nuevo": //codigo para el boton nuevo break; case "eliminar": //codigo para el boton eliminar break; ... } } }
Lanzar la aplicación
Por último desde una clase con un método main lanzo la aplicación, creando una isntancia de cada clase.
public class Principal(){ public static void main(String[] args){ Vista vista = new Vista(); Modelo modelo = new Modelo(); Controlador controlador = new Controlador(vista, modelo); } }
Layout Managers
Todo contenedor de 2º nivel (JPanel) tiene una organización para emplazar los elementos. Los tipos de organización son los Layout Managers. Existen diferentes Layouts aunque nos cetraremos en los principales. Pulsando sobre cada uno de los siguientes enlaces, accedemos a su documentación:
Para establecer un Layout, basta con seleccionar alguno en la paleta y pulsar después sobre el contenedor de 2º nivel sobre el que queremos vincularlo.
El layout más sencillo de usar es el AbsoluteLayout
, con el que indicamos que realmente no usamos layout, y podemos emplazar los componentes donde queramos.
panel.setLayout(null); // Sin layout o AbsoluteLayout panel.add (boton); // Añadimos el botón boton.setBounds (10,10,40,20); //Tenemos que indicar su tamaño y coordenadas
Como hemos dicho, no usar layout viene bien al iniciarnos, pero no es recomendable: los elementos se fijan en unas coordenadas absolutas y no responden al redimensionamiento de la ventana. Ademas, al no haber layot la ventana no tiene tamaño adecuado, y debemos indicarle un tamaño explícitamente.
Aun así, el AbsoluteLayout puede usarse en un panel concreto dentro de una ventana organizada con otros layouts.
Border Layout
El BorderLayout divide el contenedor en 5 partes: norte, sur, este, oeste y centro.
- Hará que los componentes que pongamos arriba y abajo ocupen el alto que necesiten, pero los estirará horizontalmente hasta ocupar toda la ventana.
- Los componentes de derecha e izquierda ocuparán el ancho que necesiten, pero se les estirará en vertical hasta ocupar toda la ventana.
- El componente central se estirará en ambos sentidos hasta ocupar toda la ventana.
El BorderLayout es adecuado para ventanas en las que hay un componente central importante (una tabla, una lista, etc) y tiene menús o barras de herramientas situados arriba, abajo, a la derecha o a la izquierda.
Para diseñar ventanas utilizando este layout, se añade otro contenedor (JPanel) a cada sección del BorderLayout. Este nuevo contenedor tendrá su propia disposición de componentes utilizando otros layouts mas apropiados.
JButton boton1 = new JButton(); JButton boton2 = new JButton(); JButton boton3 = new JButton(); JButton boton4 = new JButton(); JButton boton5 = new JButton(); JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); panel.add(boton1,BorderLayout.CENTER); panel.add(boton2,BorderLayout.NORTH); panel.add(boton3,BorderLayout.SOUTH); panel.add(boton4,BorderLayout.EAST); panel.add(boton5,BorderLayout.WEST); frame.setContentPane(panel);
Flow Layout
Se utiliza cuando queremos emplazar componentes en fila de izquierda a derecha, uno detrás de otro. Los elementos podemos alinearlos a la izquierda, derecha, o en el centro. Se utiliza para filar de botones o barras de herramientas.
Por defecto, los componentes de cada fila se centrarán cuando se use el constructor FlowLayout()
sin argumentos. Puede indicarse la alineación por medio del constructor mediante las variables de clase:
FlowLayout.LEFT
FlowLayout.RIGHT
FlowLayout.CENTER
También se puede indicar el espacio vertical y horizontal entre componentes en el constructor (5px por defecto).
JPanel panel = new JPanel(); //Por defecto los elementos se alinean en el centro panel.setLayout(new FlowLayout()); //También le puede indicar la alineación de componentes panel.setLayout(new FlowLayout(FlowLayout.LEFT); //O la separación vertical y horizontal entre componentes panel.setLayout(new FlowLayout(FlowLayout.RIGHT, 3, 3); panel.add(boton1); panel.add(boton2); panel.add(boton3); panel.add(boton4); panel.add(boton5);
Wrap Layout
El Wrap Layout es una modificación del FlowLayout creada por Rob Camick y está disponible en la siguiente web.
FlowLayout dispone los elementos en una nueva linea cuando el ancho de la ventana no es suficiente para emplazarlos en una misma linea. Pero cuando anidamos este layout manager dentro de otro, pierde la funcionalidad y puede que nuestros componentes dejen de verse.
El Wrap Layout permite estas funcionalidades, y funciona de forma identica al FlowLayout.
Para utilizarlo simplemente descargamos la clase java y la añadimos a nuestro proyecto, utilizandola como cualquier otra:
//Indicamos la orientación, y la separación entre componentes panel.setLayout(new WrapLayout(FlowLayout.LEFT, 3, 2));
Grid Layout
GridLayout dispone los componentes en forma de matriz (cuadrícula) estirándolos para que tengan todos el mismo tamaño. El GridLayout es adecuado para hacer tableros o calculadoras en que todos los botones son iguales, etc. Tan solo debemos indicar el número de filas y de columnas del layout.
- Si el constructor no recibe ningún argumento, se utiliza una columna por componente, en una sola fila.
- Si el constructor recibe dos argumentos, el primero indicará el número de filas y el segundo el de columnas.
- Existe la posibilidad de utilizar otro constructor en el que además de filas y columnas se indica la separación entre ellas.
GridBag Layout
GridBag Layout es uno de los layouts managers más flexibles y complejos que Java proporciona. Posiciona los componentes en una rejila de filas y columnas (igual Grid Layout), pero con la diferencia de que no todas las filas o las columnas deban tener las mismas dimensiones. Los componentes se emplazan en celdas y será la propiedad preferred size
de cada componente la que determine como de grande será su celda.
Box Layout
Cuando queremos alinear botones verticalmente utilizamos el BoxLayout. Actua de forma similar al FlowLayout pero nos permite organizar elementos de forma horizontal y también vertical.
El constructor del BoxLayout es más complejo que el del FlowLayout. Debemos pasarle el contenedor al que lo estamos añadiendo, es decir, el parámetro panel (en el ejemplo). También debemos pasarle si queremos orientación vertical BoxLayout.Y_AXIS
u orientación horizontal BoxLayout.X_AXIS
.
© 2024 Fernando Valdeón