JNotas Gui贸n de pr谩cticas
Table of Contents Descripción del proyecto............................................................................................................4 P1 – La clase Nota......................................................................................................................4 P2 – La clase BussinessObject....................................................................................................4 P3 – Notas.Save(): guardar Nota en fichero...............................................................................5 P4 – Notas.Get(): Leer Nota del fichero.....................................................................................5 P5 – JUnit: test de guardar nota en fichero.................................................................................5 P6 – newId: autoincremento en el fichero de notas....................................................................5 P7 – Windowbuilder: ventana de visualización de notas............................................................5 P8 – Base de datos: recuperar notas de BD................................................................................6 P9 – Base de datos: Actualizar notas en BD (update).................................................................6 P10 – Mantenimiento de notas: buscar, crear, editar, borrar,......................................................7 P11 – División en paquetes: Modelo, Vista y Controladora y JNotasTest..................................7 P12 – JNotasTest: testGuardarNotaEnBD()................................................................................7 P14 – MainFrm: panel de búsqueda y panel de datos.................................................................8 P15 – ResizeableFrm: Formulario de pruebas redimensionable.................................................8 P16 – MainForm: Formulario redimensionable con barra de herramientas y barra de estado...8 P17 – NotaSelFrm: Formulario de selección de nota. Cargar notas...........................................9 P18 – NotaSelFrm: Formulario de selección de nota. Filtrar notas..........................................10 P18.a. Generación manual de eventos.............................................................................10 P18.b. Buscar por titulo, descripción e id........................................................................10 P19 – Mejorar apariencia..........................................................................................................11 P19.a: Mejorar el área de system icon.............................................................................11 P19.b: Mejorar la botonera..............................................................................................12 P19.c: Más mejoras a la botonera. Texto de los botones debajo del icono......................12 P19.d: Margen interior en el panel de datos de la nota....................................................13 P19.e: Margen interior del buscador/selector de notas....................................................13 P20 – MainCCU y SeleccionarNotaCCU: esqueleto................................................................14 MainCCU: controladora principal...................................................................................14 SeleccionarNotaCCU.......................................................................................................14 MainFrm..........................................................................................................................15 P21 – MostrarNotaCCU: esqueleto...........................................................................................15 P22 – Interconectar controladoras.............................................................................................15 P23 – Borrar una nota...............................................................................................................16 P24 – Eliminar Warning asociados a la generación de eventos................................................17 P25 – Formulario modal crear nueva nota................................................................................18 P26 – Controladora CrearEditarNotaCCU: crear nota..............................................................19 P27 – Controladora CrearEditarNotaCCU: editar nota existente.............................................20 P28 – Nota: descripción multilínea + campo resuelta...............................................................20 P29 – Bloqueamos controles en el modo visualización............................................................21 P30 – Doble click sobre nota en selector de notas edita la nota...............................................21 P31 – Atajos de teclado en el selector de notas: INTRO edita, SUPR elimina .......................22 P32 – Atajos de teclado en el selector de notas: cursor actualiza nota seleccionada................23 P33 – Cuando creamos/editamos una nota, que quede seleccionada ésta.................................23 P34 – Tabla de carpetas.............................................................................................................25 P35 – Clase carpeta...................................................................................................................25 P36 – Colección de carpetas: CarpetasList...............................................................................25 P37 – Mostrar carpetas en el selector de notas.........................................................................25 P38 – Subimos proyecto a Github.............................................................................................25 P39 – Añadimos colección de notas a las carpetas...................................................................26 Solución...........................................................................................................................26 Carpeta...................................................................................................................26
NotasList................................................................................................................26 NotaSelPane...........................................................................................................27 SeleccionarNotaCCU.............................................................................................27 P40 – Incluir botones para carpetas en la barra de herramientas..............................................27 P41 – Volver a hacer funcionar el selector de notas..................................................................27 Solución...........................................................................................................................28 P42 – Ocultar panel de nota cuando seleccionamos carpeta.....................................................28 P43 – Desactivar el warning serialVersionUID........................................................................29 P44 – Crear una nueva carpeta..................................................................................................29 Solución...........................................................................................................................30 P45 – Modificar nombre de una carpeta...................................................................................30 P46 – Eliminar una carpeta.......................................................................................................30 P47 – Atajos de teclado para carpetas.......................................................................................30 Solución...........................................................................................................................31 P48 – Crear notas bajo la carpeta que le toca...........................................................................31 P49 – Cuando borro una nota, se selecciona el anterior...........................................................31 Una pista..........................................................................................................................31
Descripción del proyecto Nuestro guión de prácticas irá enfocado a construir un programa para gestión y dirección de proyectos informáticos. Este proyecto está basado en el proyecto ANotas que utiliza el servicio de informática de la Consejería de Educación del Gobierno de Canarias para dirigir los proyectos de desarrollo de software que allí se desarrollan. El proyecto original ha sido desarrollado por Armando Fumero Fernández, jefe del proyecto Pincel eKade, persona por la que siento gran admiración profesional. Una vez hayamos terminado nuestro proyecto, JNotas será capaz de: •
Crear tareas, hitos e iteraciones de programación
•
Programar reuniones
•
Generar informes de seguimiento del proyecto
•
Publicar las programaciones asociadas a cada iteración del desarrollo
•
Asignar tareas a programadores
•
Generar informes de seguimiento por cada programador
•
Medir la envergadura de nuestro proyecto: número de líneas de código, número de clases, número de tablas en base de datos,...
•
Registrar errores de ejecución en producción
•
…
P1 – La clase Nota •
Atributos
•
Getters y setters de los atributos
•
Constructor
•
Método toString
•
Versión básica: sin carpetas, sin guardar,...
P2 – La clase BussinessObject •
Atributo id
•
Atributos isValid, isEmpty, isNew
•
Método setPropertyChanged
•
Constructor
•
Método toString
•
Método Save: simplemente lanza excepción
•
Método Get(id): simplemente lanza excepción
P3 – Notas.Save(): guardar Nota en fichero •
Vamos a completar el método Save de las notas
•
Con ello vamos a guardar la nota en un fichero de texto
•
Cada campo separado del siguiente por un “;”
•
Utilizamos clase FileWriter para escribir: ◦ Al hacer new FileWriter, utilizar parámetro 2 para abrir en modo append ◦ método write para escribir en el fichero
•
No nos preocupamos si ya existe esa nota, si no existe,... Directamente añadimos al final del fichero una nueva línea con el contenido de la nota
P4 – Notas.Get(): Leer Nota del fichero •
Vamos a completar el método Get de las notas
•
Utilizamos clase Scanner para leer
•
Simplemente leemos la primera nota y nos aseguramos de que carga bien
•
Recuerda que cada nota ocupa una línea
P5 – JUnit: test de guardar nota en fichero •
Crear clase de test NotaTest.java
•
Crear un primer método de test testGuardarEnFichero
•
Ese test elimina el fichero Notas.txt
•
Crear una nueva nota con id=100 y valores en los atributos
•
Guarda la nota en fichero
•
Crea una nueva nota nota2
•
Leemos nota2 de fichero con nota2.get(100)
•
Comparamos campo a campo nota con nota2 para saber si escribió/leyó bien
P6 – newId: autoincremento en el fichero de notas •
Modificar la clase Nota para que su constructor asigne un id automáticamente de forma incremental
•
Para ello, en NotasDB crear un método estático newId que recorra el fichero de notas y busque maxId, que es el valor máximo actual de id que hay en el fichero
•
Devolver maxId + 1
P7 – Windowbuilder: ventana de visualización de notas •
Utilizando el plugin para eclipse Windowbuilder generar el formulario de visualización de
una nota •
Internamente incluye un objeto nota de tipo Nota
•
El método setNota nos permite asignarle la nota que queremos visualizar
•
Para cada atributo de nota, incluye una etiqueta con el nombre del campo y una caja de texto con su valor:
frmNota
txtIdCarpeta txtTitulo ...
P8 – Base de datos: recuperar notas de BD •
Hasta ahora hemos utilizado un fichero llamado Notas.txt.
•
Crear una base de datos llamada JNotas con una única tabla Notas
•
Insertar varias notas de prueba
•
Esta tabla tendrá exactamente los mismos campos que tiene actualmente la clase Nota
•
Modificar el método get para que lea/escriba de base de datos, en lugar que de fichero
•
Para gestionar el acceso a la base de datos, crea una clase DAL con los siguientes métodos estáticos: ◦
public static Connection devuelveConexionAbierta();
◦
public static ResultSet executeQuery(Connection conn, String sql) throws Exception;
P9 – Base de datos: Actualizar notas en BD (update) •
Recuperar de BD una nota
•
Mostrarla en un JFrame (NotaFrm)
•
Agregarle un botón Guardar a ese JFrame
•
Modificar en base de datos según los datos que se hayan insertado en el JFrame
P10 – Mantenimiento de notas: buscar, crear, editar, borrar,... Vamos a tomar el formulario de visualización de notas NotasFrm y le vamos a añadir una barra de herramientas con la que podremos: •
Crear una nueva nota
•
Borrar la nota actual ◦ Añadirle a las notas el método getLast() ◦ Este método recupera de la BD la nota con mayor Id ◦ Después de borrar, cargamos la mayor y la mostramos
•
Editar la nota actual
•
Buscar una nota concreta
•
Ver los datos asociados a una nota en detalle
Debemos editar el formulario NotasFrm con el plugin Windowbuilder de Google.
P11 – División en paquetes: Modelo, Vista y Controladora y JNotasTest Dividimos nuestro proyecto en 3 paquetes: Modelo, vista y controladora Además, creamos un nuevo proyecto para todos los test. Lo llamamos JNotasTest
P12 – JNotasTest: testGuardarNotaEnBD() •
Creamos una nota nota1 y la guardamos
•
Creamos una nota2 y la cargamos de base de datos con el id de nota1
•
Comparamos uno a uno los campos de nota1 y nota2
•
Si hay alguno diferente, las notas NO son iguales, de modo que algo NO habrá ido bien en el guardado/recuperado de base de datos
P14 – MainFrm: panel de búsqueda y panel de datos •
Crearemos un nuevo JFrame llamado MainFrm
•
Será el frame principal de la aplicación
•
A la izquierda tendrá un panel de búsqueda/selección
•
A la derecha un panel de datos de una nota
•
Crear los paneles como objetos independientes (NotaSelPane y NotaDataPane)
•
En esta versión este formulario todavía NO hace nada, solo hemos modificado la capa de presentación
P15 – ResizeableFrm: Formulario de pruebas redimensionable En esta práctica vamos a hacer un ejercicio sencillo de redimensionamiento de formularios. El objetivo es crear un formulario tal que, sus componentes se auto-organicen en función del tamaño del mismo.
Hemos puesto GridBagLayout El botón hemos puesto Anchor NorthWest y widthx a 1.0
P16 – MainForm: Formulario redimensionable con barra de herramientas y barra de estado •
Modificaremos nuestro formulario principal para que:
•
Sea redimensionable
•
Incluya la barra de tareas y la barra de estado
•
Además, debe contener el área de selección/navegación de notas y los datos de una nota
•
Todavía NO interactúa con la base de datos, es únicamente una cuestión gráfica
P17 – NotaSelFrm: Formulario de selección de nota. Cargar notas
•
Vamos a trabajar sobre la funcionalidad completa de la selección y búsqueda de notas
•
Para ello, utilizando el panel NotaSelPane, crearemos un formulario que trabaje de forma autónoma.
•
Cargará todas las notas de la base de datos y las mostrará en la lista
•
De momento es una caja tonta: muestra las notas en el JList de notas.
•
Crear la clase NotasList, que tiene un ArrayList que almacena todas las notas
•
Crear un método get que recupera de base de datos todas las notas
•
Modificar el método toString para devuelva un string que concatena todas las notas leídas de la base de datos y almacenadas en el ArrayList
•
Tanto al notaSelPane como al notaSelFrm agregar un método setNotas que asigne una lista de notas nueva
•
El selPane, agrega los títulos de cada una de las notas de esa lista al JList
•
El formulario debe ser redimensionable
P18 – NotaSelFrm: Formulario de selección de nota. Filtrar notas •
Si escribimos un texto en la caja de búsqueda y pulsamos buscar, filtrará el listado de notas por aquellas que coincidan con el filtro
P18.a. Generación manual de eventos Empieza primero generando de forma manual dentro del notaSelPane un evento personalizado cuando el usuario hace click sobre buscar Recupera ese evento en notaSelFrm Muestra simplemente en consola que el usuario ha pulsado el botón buscar e incluye el texto que había en la caja de texto
P18.b. Buscar por titulo, descripción e id A NotasList le agregamos un nuevo método get(String texto) En la SQL, agregamos al where para que busque en el titulo, en la descripción y en el id
P19 – Mejorar apariencia Éste es el aspecto actual. Ninguno de los paneles es en este momento funcional:
P19.a: Mejorar el área de system icon
Hemos creado la imagen en Gimp. La asociamos a la JLabel que hemos colocado en ese panel JLabel lblSystemIcon = new JLabel(""); imageHeader = new ImageIcon(MainFrm.class.getResource("/images/headerMain.jpg")); lblSystemIcon.setIcon(imageHeader);
Hacemos que la línea de división del splitter coincida justo con el ancho de la imagen splitPane.setDividerLocation(imageHeader.getIconWidth());
P19.b: Mejorar la botonera
Hemos añadido imágenes (icon) a los botones Establecemos su tamaño preferido: JButton btnNuevo = new JButton("Nuevo"); Dimension buttonDimension = new Dimension(120, imageHeader.getIconHeight() - 20); btnNuevo.setIcon(new ImageIcon(MainFrm.class.getResource("/images/newFile_32x32.png")));
P19.c: Más mejoras a la botonera. Texto de los botones debajo del icono
btnNuevo.setMargin(new Insets(0, 0, 0, 0)); btnNuevo.setVerticalTextPosition(SwingConstants.BOTTOM); btnNuevo.setHorizontalTextPosition(SwingConstants.CENTER);
P19.d: Margen interior en el panel de datos de la nota
Hemos modificado los insets de todos los componentes: gbc_btnNuevo.insets = new Insets(10, 5, 5, 0); ... Insets insets = new Insets(5,5,5,5); ... gbc_lblId.insets = insets; ... gbc_txtId.insets = insets;
P19.e: Margen interior del buscador/selector de notas
De nuevo trabajamos sobre los insets de los elementos grรกficos.
También hemos agregado un borde a la caja de selección de notas
P20 – MainCCU y SeleccionarNotaCCU: esqueleto Empezaremos por dos controladoras: MainCCU y NotaSelCCU
MainCCU: controladora principal MainCCU es la controladora principal. Es la que contiene las controladoras de selección y búsqueda de notas y la controladora de visualización de datos de una nota Estará organizada en las siguientes regiones: •
Objetos de negocio
•
Vista
•
Controladoras de inclusión
•
Métodos propios de las CCU
De momento en objetos de negocio no ponemos ninguno Su vista será de tipo MainFrm, el formulario principal que ya habíamos creado Las controladoras de inclusión serán SeleccionarNotaCCU y MostrarNotaCCU. De momento solo incluimos SeleccionarNotaCCU, que crearemos a continuación En métodos propios de las CCU, añadimos el método iniciar Básicamente, inicia las CCU incluidas, recupera sus vistas, y le pasa las vistas de las CCU a su propia vista
SeleccionarNotaCCU Estará organizada en las siguientes regiones: •
Objetos de negocio
•
Vista
•
Controladoras de inclusión
•
Métodos propios de las CCU
•
Gestión de eventos
En objetos de negocio colocamos una lista de notas (NotasList) Su vista será de tipo NotaSelPane Esta CCU debe capturar los eventos del panel, en este caso, el evento ButtonClick Su método iniciar, recupera de base de datos todas las notas y se lo suministra a su vista
En la gestión de eventos, manejamos los eventos de la vista
MainFrm Hay que hacer algunos cambios En primer lugar, ahora tiene 2 paneles que recibe de fuera, de las controladoras incluidas. De modo que tenemos que suministrar dos métodos setNotaSelPane y setNotaDataPane
P21 – MostrarNotaCCU: esqueleto Esta nueva CCU se encargará únicamente de mostrar los datos de una nota
P22 – Interconectar controladoras Ahora mismo, la ventana principal carga 2 controladoras que van a su aire. Es decir, si seleccionamos una nota en la controladora de selección, la que muestra los datos ni se entera, sigue mostrando la nota 1. ¿Cómo las vamos a comunicar? Usuario: Usuario hace click sobre una nota NotaSelPane: Ese click es detectado por el JList NotaSelPane: Disparamos un evento con _raiseNotaClickEvent(...) SeleccionarNotaCCU: El evento es capturado por SeleccionarNotaCCU, que a su vez, lanza un evento hacia afuera: MainCCU: Este nuevo evento es capturado por MainCCU, que lo que hace es pasarle el idNota a MostrarNotaCCU. MostrarNotaCCU: recupera la nota de base de datos y se la suministra a su vista con miVista.setNota(nota): NotaDataPane: La vista MostrarNotaFrm muestra los datos de esa nota IMPORTANTE: Haz que la aplicación reaccione de forma esperada cuando utilizamos la búsqueda de notas por su descripción. listNotas = new JList<String>(datos); listNotas.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); MouseListener mouseListener = new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 1) { int index = listNotas.locationToIndex(e.getPoint()); System.out.println("Double clicked on Item " + index);
... _raiseNotaClickEvent(idNota); } }
}; listNotas.addMouseListener(mouseListener);
Código para NotaSelPane public void notaClick(NotaSelPaneEvent e) { System.out.println("click sobre nota " + e.getIdNota()); _raiseNotaClickEvent(e.getIdNota()); }
Código de SeleccionarNotaCCU public void notaClick(NotaSelPaneEvent ev) { System.out.println("MainCCU: Has pulsado sobre la nota " + ev.getIdNota()); }
Código de MainCCU
P23 – Borrar una nota Vamos a eliminar la nota seleccionada. MainCCU captura el evento de la barra de herramientas Carga un objeto Nota con el id de la seleccionada Invoca su método delete Invoca al método reiniciar el la controladora SeleccionarNotaCCU // #region Gestión de eventos de su vista
private void borrarSelectedNota() { Nota n = new Nota(); try { n.get(seleccionarNotaCCU.getIdSelectedNota()); n.delete(); } catch (Exception e) { e.printStackTrace(); }
seleccionarNotaCCU.reiniciar(); } // ------------------------------------------------------------------------------------public void buttonClick(ToolbarEvent ev) { if (ev.getButtonName().equals("Borrar")) { borrarSelectedNota(); } } // #endregion
P24 – Eliminar Warning asociados a la generación de eventos En la generación de eventos, usamos la clase List pero no con Generics. Modificamos para eliminar los Warning. Cada clase que genera sus propios eventos, tiene un código como este: private List _listeners = new ArrayList();
public synchronized void addListener(SeleccionarNotaCCUListener l ) { _listeners.add(l); }
public synchronized void removeListener(SeleccionarNotaCCUListener l ) { _listeners.remove(l); }
private synchronized void _raiseNotaClickEvent(int idNota) { NotaSelPaneEvent ev = new NotaSelPaneEvent(this, idNota); Iterator listeners = _listeners.iterator(); while( listeners.hasNext() ) { ((SeleccionarNotaCCUListener) listeners.next() ).notaClick(ev); } }
Cambiamos por este otro: private List<SeleccionarNotaCCUListener> _listeners = new ArrayList<SeleccionarNotaCCUListener>();
public synchronized void addListener(SeleccionarNotaCCUListener l ) { _listeners.add(l); }
public synchronized void removeListener(SeleccionarNotaCCUListener l ) { _listeners.remove(l); }
private synchronized void _raiseNotaClickEvent(int idNota) { NotaSelPaneEvent ev = new NotaSelPaneEvent(this, idNota); Iterator<SeleccionarNotaCCUListener> listeners = _listeners.iterator(); while( listeners.hasNext() ) { ((SeleccionarNotaCCUListener) listeners.next() ).notaClick(ev); } }
P25 – Formulario modal crear nueva nota •
Tenemos que capturar el evento del botón Nuevo en la barra de herramientas de MainFrm.
•
Esto provoca que se abra un formulario modal con el panel de datos de la nota vacíos
•
Este formulario debe tener un botón aceptar y otro cancelar
•
De momento no hacemos nada, solamente abrir el formulario, y desde MainCCU detectar si se pulsó Aceptar o cancelar.
•
De momento NO guardamos la nueva nota en base de datos ni nada.
•
Modificamos las clases ToolbarListener, ToolbarEvent,... para llamarlas ButtonListener, ButtonEvent,...
Creamos una ventana para crear notas que se abra en forma modal. Debe heredar de JDialog: public class NuevaNotaFrm extends JDialog { ... public NuevaNotaFrm(JFrame parent, boolean modal) { super(parent, modal); initComponents(); } ... public NuevaNotaFrm() { initComponents(); } ...
Una vez lo tenemos en la mainCCU, debemos lanzar este formulario en forma modal: private void crearNuevaNota() { NuevaNotaFrm nuevaNotaFrm = new NuevaNotaFrm(this.miVista, true); nuevaNotaFrm.setVisible(true); }
Cuando desde este formulario se pulsa aceptar o cancelar, simplemente lo recogemos en MainCCU y mostramos en consola el botón pulsado: // Código de MainCCU en su región de gestión de eventos ... public void buttonClick(ButtonEvent ev) { switch (ev.getButtonName()) { case "Borrar": borrarSelectedNota(); break; case "Nuevo": crearNuevaNota(); break; default: System.out.println("MainCCU: pulsado botón " + ev.getButtonName()); break; } }
P26 – Controladora CrearEditarNotaCCU: crear nota En la práctica anterior hemos hecho las cosas mal. La controladora está manejando eventos de una vista que no es la suya. Debemos crear una nueva controladora CrearEditarNotaCCU, que será la que abra el formulario CrearEditarNotaFrm Esta nueva CCU será la que recogerá los eventos del formulario y la que salvará en base de datos (o no) las nuevas notas o las posibles ediciones de notas
NotaDataPane: Método reverseRefresh(). Vuelca el contenido de las cajas de texto a la Nota CrearEditarNotaFrm: invoca a reverseRefresh cuando captura el evento Aceptar MainCCU: método crearNuevaNota() ahora invoca a crearEditarNotaCCU.iniciar(), y al terminar invoca al reiniciar de la CCU de selección de notas, porque si creó una nueva, que figure en el buscador private void crearNuevaNota() { crearEditarNotaCCU = new CrearEditarNotaCCU(this.miVista); crearEditarNotaCCU.iniciar();
// Cuando termine la controladora anterior, refrescamos la de seleccionar // para que aparezca la nueva seleccionarNotaCCU.reiniciar(); }
CrearEditarNotaCCU:
Métodos setNota y getNota simplemente invocan al setNota y getNota de su vista Cuando capturamos evento, si es Aceptar, grabamos nota public void buttonClick(ButtonEvent ev) { if (ev.getButtonName().equals("Aceptar")) { try { nota.save(); } catch (Exception e) { e.printStackTrace(); } }
miVista.setVisible(false); }
P27 – Controladora CrearEditarNotaCCU: editar nota existente Ahora pulsamos sobre editar, en lugar de sobre Nuevo. Son pocos los cambios, pues nuestra CCU CrearEditarNota ya recibe una nota en sus setters. Simplemente es asignársela private void editarNota() { Nota nota = new Nota(); try { nota.get(seleccionarNotaCCU.getIdSelectedNota());
crearEditarNotaCCU = new CrearEditarNotaCCU(this.miVista); crearEditarNotaCCU.setNota(nota); crearEditarNotaCCU.iniciar();
// Cuando termine la controladora anterior, refrescamos la de seleccionar // para que aparezca la nueva seleccionarNotaCCU.reiniciar(); } catch (Exception e) { } }
P28 – Nota: descripción multilínea + campo resuelta Visualmente hacemos que la descripción sea multilinea y le agregamos un campo buleano de resuelta. Visualmente agregamos un botón “Marcar como resuelta” NotaDataPane: Actualizar métodos refresh y reverseRefresh jno_notas: agregar campo a la tabla
Nota: agregar campo resuelta. Modificar el acceso a datos para que recupere este nuevo campo
P29 – Bloqueamos controles en el modo visualización Cuando estamos en modo de visualización, el usuario NO debe poder modificar ningún atributo de la nota. Para ello, el panel NotaDataPane le agregamos, en su constructor, un atributo readOnly para que bloquee todos los controles
public void setOnlyReadable(boolean readOnly) { for (int i = 0; i < this.getComponentCount(); i++) { Component c = this.getComponent(i);
if (c instanceof JTextField) ((JTextField) c).setEditable(! readOnly); if (c instanceof JTextArea) ((JTextArea) c).setEditable(! readOnly);
if (c instanceof JCheckBox) ((JCheckBox) c).setEnabled(! readOnly); } }
P30 – Doble click sobre nota en selector de notas edita la nota Si hacemos doble click sobre una nota en el selector de notas, editamos la nota.
NotaSelPaneEvent: tipo enumerado para distinguir click simple de doble NotaSelPane: lanzamos un evento u otro según haya sido simple o doble SeleccionarNotaCCU hace lo mismo, envía un evento u otro MainCCU: en función de si ha sido simple o doble, hace una cosa u otra:click public enum TipoAccion{SIMPLE_CLICK, DOUBLE_CLICK};
MouseListener mouseListener = new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 1) { int index = listNotas.locationToIndex(e.getPoint()); _raiseNotaClickEvent(index, TipoAccion.SIMPLE_CLICK); }
if (e.getClickCount() == 2) { int index = listNotas.locationToIndex(e.getPoint()); _raiseNotaClickEvent(index, TipoAccion.DOUBLE_CLICK); } } };
public void notaClick(NotaSelPaneEvent ev) { if (ev.getTipoAccion() == TipoAccion.SIMPLE_CLICK) mostrarNotaCCU.iniciar(ev.getIdNota()); else if (ev.getTipoAccion() == TipoAccion.DOUBLE_CLICK) editarNota(); }
P31 – Atajos de teclado en el selector de notas: INTRO edita, SUPR elimina NotaSelPaneEvent: Aumentamos nuestro tipo TipoAccion NotaSelPane: incluimos handler de tecla pulsada MainCCU: recibe el evento, y lo gestiona Cuidado, porque ahora mismo NO detecta cuando, en el selector, nos movemos con el cursor por las notas, y cree que está seleccionada la 0 (la última en la que hicimos click) public enum TipoAccion{SIMPLE_CLICK, DOUBLE_CLICK, SUPRIMIR, INTRO};
listNotas.addKeyListener(new KeyAdapter() { @Override public void keyTyped(KeyEvent e) { int index = listNotas.getSelectedIndex();
switch ((int) e.getKeyChar()) { case 10: _raiseNotaClickEvent(index, TipoAccion.INTRO); break; case 127: _raiseNotaClickEvent(index, TipoAccion.SUPRIMIR); } } });
public void notaClick(NotaSelPaneEvent ev) { if (ev.getTipoAccion() == TipoAccion.SIMPLE_CLICK) mostrarNotaCCU.iniciar(ev.getIdNota()); else if (ev.getTipoAccion() == TipoAccion.DOUBLE_CLICK) editarNota(); else if (ev.getTipoAccion() == TipoAccion.INTRO) editarNota(); else if (ev.getTipoAccion() == TipoAccion.SUPRIMIR) borrarSelectedNota(); }
P32 – Atajos de teclado en el selector de notas: cursor actualiza nota seleccionada Ahora mismo, si nos movemos por el panel de notas con las teclas de cursor, NO se actualiza la nota seleccionada, y es que la CCU SeleccionarNota NO está enviando ningún evento, ya que no hemos hecho click sobre ninguna nota. Capturamos el evento valueChanged del JList, y lanzamos el evento notaClick event. listNotas.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent arg0) { _raiseNotaClickEvent(listNotas.getSelectedIndex(), TipoAccion.SIMPLE_CLICK); } });
P33 – Cuando creamos/editamos una nota, que quede seleccionada ésta Ahora mismo, cuando creamos/editamos una nota, al terminar, queda siempre seleccionada la primera nota de la lista, y no la que recién hemos creado o la que acaabamos de editar. Para ello, cuando la controladora crearEditarNotaCCU termina, MainCCU, que es quien la ha invocado, deberá acceder a la nota mediante crearEditarNotaCCU.getNota() y forzar a seleccionarNotaCCU que seleccione justo ésa. Se estaba produciendo un problema.
En el panel de selección de notas (NotaSelPane), cuando llamamos a refresh, y eliminamos todos los datos... private void refresh() { System.out.println("Refresh"); desengancharEventos(); datos.removeAllElements(); ...
… se produce un error, y es que cuando eliminamos todos los elementos, se está invocando el evento valueChanged, en el sentido de que el elemento seleccionado ha cambiado, y claro, el código de este evento da error, porque la lista está vacía. Así que, cuando vamos a hacer un refresh, primero debemos desactivar los eventos y luego volver a activarlos: private void refresh() { desactivarEventos(); datos.removeAllElements(); for (int i = 0; i < list.size(); i++) { datos.addElement("[" + String.valueOf(list.getNota(i).getId()) + "] " + list.getNota(i).getTitulo()); } activarEventos();
seleccionarFirstNota(); this.repaint(); }
Así activamos los eventos: // #region Gestión de eventos de elementos gráficos ListSelectionListener listSelectionListener; KeyAdapter keyAdapter; MouseAdapter mouseAdapter;
private void activarEventos() { listSelectionListener = new ListSelectionListener() { public void valueChanged(ListSelectionEvent arg0) { _raiseNotaClickEvent(listNotas.getSelectedIndex(), TipoAccion.SIMPLE_CLICK); } };
keyAdapter = new KeyAdapter() { @Override public void keyTyped(KeyEvent e) { ...
Y así los desactivamos:
public void desactivarEventos() { listNotas.removeListSelectionListener(listSelectionListener); listNotas.removeKeyListener(keyAdapter); listNotas.removeMouseListener(mouseAdapter); }
P34 – Tabla de carpetas Vamos a comenzar a trabajar con carpetas, que contendrán notas. Comencemos creando la tabla que aloja las carpetas.
P35 – Clase carpeta En su versión inicial, hacemos una copia de la clase Nota, con los correspondientes ajustes.
P36 – Colección de carpetas: CarpetasList En su versión inicial, hacemos una copia de la clase NotasList, con los correspondientes ajustes.
P37 – Mostrar carpetas en el selector de notas Vamos a modificar el selector de notas para que muestre las carpetas y las notas. Todavía sin relacionar, quiero decir, no va a mostrar cada carpeta con sus notas asociadas, sino simplemente todas las carpetas y luego, todas las notas SeleccionarNotaCCU: •
Añadimos la lista de carpetas
•
En los métodos iniciar y reiniciar cargamos la lista de carpetas (carpetas.get())
•
Asignamos la lista de carpetas a la vista (miVista.setCarpetasList(carpetas))
•
Al buscar, no buscamos en carpetas, sino en notas
NotaSelPane: •
Añadimos la lista de carpetas y su setter (setCarpetasList)
•
En el método refresh, cargamos sobre datos las carpetas y luego las notas
P38 – Subimos proyecto a Github No he realizado ningún cambio significativo en la aplicación, simplemente quiero probar el Commit and Push desde Eclipse La ruta del repositorio es: https://github.com/moisespd/jnotas.git
P39 – Añadimos colección de notas a las carpetas La clase carpeta contendrá un atributo que es su lista de carpetas asociadas Esta lista de carpetas la cargaremos en modo LazyLoad De momento no vamos a permitir subcarpetas, de modo que una carpeta no tiene una lista de subcarpetas hija, sino únicamente una lista de notas que cuelgan directamente de ella En la visualización del selector de notas, mostraremos cada carpeta y su lista de notas un poco desplazado a la derecha, para que visualmente se aprecie que son las notas contenidas bajo esa carpeta:
De momento no nos preocupemos ni por editar las notas, ni por borrarlas, ni por poder buscar,... Incluso cuando pulsamos una nota, es posible que dé error, ya que
Solución Carpeta
•
misNotas, que es una clase de tipo NotasList, y que será la lista de notas asociadas a la carpeta
•
getNotas()
•
cargarRecordset, hacemos misNotas.get()
NotasList
•
get(int idCarpeta), este método devolverá la lista de notas asociadas a la carpeta idCarpeta
•
Modificamos el procedimiento almacenado para que soporte un idCarpeta como parámetro
NotaSelPane
•
refresh() , dos for anidados, uno que recorre las carpetas y el interior que recorre las notas asociadas a la carpeta actual
SeleccionarNotaCCU
•
Eliminamos la lista de notas, ya solo necesita una lista de carpetas
•
iniciar y reiniciar(), ya solo cargamos las carpetas, no las notas
P40 – Incluir botones para carpetas en la barra de herramientas Incluimos los botones crear, editar y borrar carpetas en la barra de herramientas De momento solo como efecto visual, los botones NO hacen nada Cuidado porque los botones de las notas podrían dejar de funcionar Incluso, como ya hemos dicho, la selección de notas/carpetas de momento tampoco funciona
P41 – Volver a hacer funcionar el selector de notas En el selector de notas ahora conviven carpetas y notas. Por esta razón, ha dejado de funcionar •
la visualización de los datos de la nota seleccionada
•
doble click sobre una nota
•
tecla intro sobre una nota
•
tecla suprimir sobre una nota
•
Botones de crear/editar/borrar notas de la barra de herramientas
•
...
Volvamos a hacer funcionar el selector de notas sencillamente ignorando las carpetas.
Solución ¿Cómo sabemos que hemos hecho click sobre una carpeta? Lo que haremos es que el id de las carpetas les añadiremos una c. delante. Así tendremos [c1], [c2],... en el sentido de carpeta 1, carpeta 2,... de este modo sabremos que el usuario ha hecho click sobre una carpeta, y no sobre una nota. Ten en cuenta que ahora el id de las notas sigue encerrado entre corchetes pero está desplazado en el texto para que parezca que están vinculadas a la carpeta Además, el id de las carpetas tiene una c dentro del corchete y antes de su valor de id
P42 – Ocultar panel de nota cuando seleccionamos carpeta
Tendremos que trabajar con un CardLayout, es decir, un Layout que contiene N paneles superpuestos, y en cada momento solo muestra uno:
NotaDataPane
NoNotaPane
Este nuevo panel lo llamamos NotaPane Ahora, la vista de MostrarNotaCCU será un NotaPane Desde la controladora MainCCU, que sabe si hemos hecho click sobre una carpeta o sobre una nota, indicará a MostrarNotaCCU que se oculte, y ésta invocará a su vista para que muestre el segundo panel, el de “sin datos”
P43 – Desactivar el warning serialVersionUID
P44 – Crear una nueva carpeta Vamos a hacer que el botón “Crear carpeta” funcione. Crear una carpeta básicamente es asignarle un nombre y guardarla en base de datos Ni siquiera necesitamos una vista, sino simplemente un JDialog. Mira el tutorial de cómo crear JDialog en Java: http://docs.oracle.com/javase/tutorial/uiswing/components/dialog.html
Solución La controladora MainCCU detectará el evento de botón pulsado, e invocará al método crearCarpeta() Este método, solicita al usuario el nuevo nombre mediante un JOptionPane.showInputDialog() carpeta.setTitulo(...) Carpeta.save() seleccionarNotaCCU.reiniciar()
P45 – Modificar nombre de una carpeta P46 – Eliminar una carpeta Prohibir la eliminación si contiene notas en su interior Advertir al usuario si no se pudo eliminar Cuando pulsemos este botón, asegúrate que el usuario tiene seleccionada una carpeta, y no una nota
P47 – Atajos de teclado para carpetas INTRO: editar carpeta SUPR: eliminar carpeta SHIFT + INS: crear nueva carpeta INS: Crea una nueva nota Todavía, cuando creamos una nota, no la crea bajo la carpeta que le toca. No te preocupes por eso
aún.
Solución KeyTyped no detecta muchas teclas de control. Utiliza mejor el método KeyPressed del KeyAdapter que estés usando Utiliza el método isShiftDown() del KeyEvent
P48 – Crear notas bajo la carpeta que le toca Hasta ahora, las notas se han creado con un idCarpeta = -1 Vamos a hacer que la nota se cree bajo la carpeta que nos encontremos en el selector de notas Si en el selector de notas, tenemos seleccionada una carpeta, la carpeta padre de nuestra nueva nota estará clara. Sin embargo, si lo que tenemos seleccionado es una nota, la nueva nota tendrá exactamente el mismo idCarpeta que la nota actual, de modo que ése será el valor que devolvamos. Además, aprovechamos para corregir que cuando editamos un elemento (nota o carpeta), al terminar el proceso el selector de notas mantiene seleccionado el elemento que hemos editado. ¡¡¡Cuidado cuando no hay ninguna nota ni ninguna carpeta creada!!! En ese caso, el idCarpeta será -1
P49 – Cuando borro una nota, se selecciona el anterior Cuando eliminamos una nota, ahora mismo se queda seleccionado el primer elemento de la lista. Modificamos este comportamiento para que, cuando elimino una nota, se queda seleccionado el elemento justo por encima. Salvo que no quede ninguno por encima, en cuyo caso se selecciona el que tiene por debajo
Una pista Hasta ahora, hemos resuelto esta cuestión con los métodos public void setIdSelectedNota(int value); public void setIdSelectedCarpeta(int value);
En los que suminstrábamos el Id de la nota/carpeta que queríamos marcar como seleccionado. Estos métodos eran invocados por MainCCU al terminar una operación de Crear/Editar/Borrar Nota/Carpeta. Esto es válido para la edición/creación, ya que sé el Id que edité o creé, de modo que es devolverle el foco a este elemento. Pero cuando borro, ¿cuál es el que debo seleccionar? Lo que hacemos es que el panel NotaSelPane tiene un atributo que almacena la posición del elemento seleccionado (selectedPos).
Este valor coincide con el getIndexSelected del JList, de modo que tenemos que actualizarlo a este valor cada vez que haya un cambio de valor del JList y ya lo tendremos listo. Cuando se invoca al método refresh(), al final me aseguro de marcar como seleccionado el elemento ubicado en la posición selectedPos, en lugar de marcar la primera, tal y como teníamos antes: seleccionarFirstNota();
Cambiamos por: if (datos.size() > 0) { if (selectedPos >= datos.size()) selectedPos = datos.size() -1;
listNotas.setSelectedIndex(selectedPos);
}