====== 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). {{ :bloque1:menu-consola.jpg?400 |}} {{ :bloque1:menu-gui.png?400 |}} >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 ==== {{:bloque1:topleveldemometal.png?230 |}} {{ :bloque1:conceptualpane.gif?420| }} * **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 ==== {{ :bloque1:conceptualpane.gif?250|}} {{ :bloque1:jmenubar.png?100|}} 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**.{{ :bloque1:jmenucomponents.png?200|}} 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 ==== {{ :bloque1:jlabel.png?150|}} 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 ==== {{ :bloque1:jbuttons.png?100|}} 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 convencional * ''JToggleButton'' : Botón que puede estar presionado o no presionado. * ''JCheckBox'' : Casilla de verificación * ''JRadioButton'' : 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. {{ :bloque1:buttongroup.png?350|}} === 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 ==== {{:bloque1:jcombo.png?100 |}} 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: {{:bloque1:jcomboproperties.png?200 |}} {{ :bloque1:jcombomodel.png?300 |}} Es interesante remarcar que al modelo podemos añadirle elementos desde un array. {{ :bloque1:jbombo4.png?100|}} __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 ==== {{ :bloque1:jtextfield.png?200|}} 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 ==== {{ :bloque1:jpassfield.png?200|}} 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. {{ :bloque1:jtextareascroll.png?300|}} 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 ==== {{ :bloque1:jspinner.png?80|}} 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 ==== {{:bloque1:jslider.png?150 |}} 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. {{ :bloque1:jsliderproperties.png?200|}} 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 ==== {{ :bloque1:jlist.png?300|}} 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 lista = new JList(); 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 listModel = new DefaultListModel(); JList 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 lista = new JList(); DefaultListModel modelo = new DefaultListModel(); lista.setModel(modelo); //Accedo a la lista de clientes, y añado sus elementos for (Cliente miCliente : listaClientes) { modelo.addElement(miCliente); } {{ :bloque1:jlistselectionmode.png?200|}} 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. {{:bloque1:jpreogressbar.png?150|}} __Métodos principales__: * ''setValue(int valor)'' : Establece el valor que mostrará la barra. ==== JSeparator ==== Se usa para separar partes de la ventana o para separar elementos en un JMenu. {{:bloque1:jseparatormenu.png?80|}} {{:bloque1:jseparator2.png?300 |}} ==== JTabbedPane ==== Permite crear un contenedor de pestañas, separando en cada pestaña los elementos que sean necesarios en cada caso. {{:bloque1:jtabedpane_example.gif}} 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 ==== {{ :bloque1:jdatepicker.png?150|}} 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. {{:bloque1:eclipsemarketplace1.png?300 |}} {{ :bloque1:eclipsemarketplace2.png?300 |}} 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 [[https://es.wikipedia.org/wiki/Swing_(biblioteca_gr%C3%A1fica)|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: {{:bloque1:creaproyecto1.jpg?300 |}} {{ :bloque1:creaproyecto2.jpg?300 |}} **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: {{:bloque1:creaclase1.jpg?300 |}} {{ :bloque1:creaclase2.jpg?300 |}} ---- También podemos acceder de forma rápida al menú de WindowBuilder mediante su nuevo botón, siempre seleccionando Swing como componentes gráficos: {{:bloque1:creaclase3.png?300 |}} {{ :bloque1:creaclase4.jpg?300 |}} 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. {{:bloque1:pestanadiseno.jpg?300 |}} {{ :bloque1:pestanacodigo.jpg?300 |}} === 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.** {{:bloque1:contentpane.png?300 |}} {{ :bloque1:absolutelayout.png?300 |}} 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. {{ vimeo>246942867?medium }} === Añadir manejadores de eventos === {{ :bloque1:addeventhandler.png?350|}} 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()''. {{ :bloque1:addeventhandler2.png?300|}} 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). {{ :bloque1:uidesigner.png?direct&600 |}} 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. {{ vimeo>296086288?medium }} ===== 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 ===== {{ :bloque1:mvc.jpg?300|}} 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 listaPersonas; ... public Modelo(){ listaPersonas = new ArrayList; } //Métodos para acceder a los datos public ArrayList 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: * [[https://docs.oracle.com/javase/tutorial/uiswing/layout/none.html|AbsoluteLayout]] * [[https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html#border|BorderLayout]] * [[https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html#box|BoxLayout]] * [[https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html#card|CardLayout]] * [[https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html#flow|FlowLayout]] * [[https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html#gridbag|GridBagLayout]] * [[https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html#grid|GridLayout]] * [[https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html#group|GroupLayout]] * [[https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html#spring|SpringLayout]] 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. {{ :bloque1:borderlayout.png?direct&400 |}} * Hará que los componentes que pongamos arriba y abajo ocupen el alto que necesiten, pero los estirará horizontalmente hasta ocupar toda la ventana. {{ :bloque1:borderlayoutdemo.png?direct&250|}} * 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. {{:bloque1:usoborderlayout.png?direct&200 |}} 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). {{ :bloque1:flowlayoutdemo.png?direct&300 |}} 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 [[https://tips4java.wordpress.com/2008/11/06/wrap-layout/|siguiente web]]. {{ :bloque1:flowlayout.png?direct&450 |}} 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 ==== {{ :bloque1:calc-gridlayout.png?direct&180|}} **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. {{ :bloque1:gridlayout.jpg?direct&500 |}} ==== 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. {{ :bloque1:gridbaglayoutdemo.png?direct&400 |}} ====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''. {{ :bloque1:boxlayout.jpg?direct&400 |}} ---- (c) {{date> %Y}} Fernando Valdeón