Por favor, activa JavaScript y desactiva tu adblock para este sitio

El Javatar

Blog dedicado a la Programación en Java, C, PHP, Phyton, HTML, SQL y Mucho Más

jueves, 14 de septiembre de 2017

SwingUtils - Sincronizar datos de un JTextField con el Controlador (Java Swing)

En este artículo veremos como sincronizar un JTextField de java swing con un atributo del controlador para que los datos se actualicen automáticamente tanto en la vista (JTextField) como en el modelo de datos (atributo independiente u atributo de objeto compuesto declarados en el controlador), usando la librería SwingUtils.

La librería SwingUtils no sólo permite hacer esta sincronización con componentes de tipo javax.swing.JTextField; también es posible usarla con los otros componentes que heredan de la clase javax.swing.JTextComponent:

javax.swing.JTextField
javax.swing.JFormattedTextField
javax.swing.JPasswordField
javax.swing.JTextArea
javax.swing.JEditorPane
javax.swing.JTextPane

Lo primero que debemos hacer es descargar la librería SwingUtils; para ésto puedes ver las instrucciones en el siguiente enlace:

==> SwingUtils - Librería de Utilidades para Java Swing

Anotaciones para usar en el Controlador

En el controlador podemos usar dos anotaciones según sea el caso que necesitemos:

- ModelBean
- PropertyController

Veamos cómo podemos usar cada una de estas:

public class MiControlador extends AbstractObserverController {

    @PropertyController
    private String texto1;

    @PropertyController(name = "miTexto")
    private String texto2;

    @ModelBean
    private MiModelo miModelo1;

    @ModelBean(name = "pruebaModelo")
    private MiModelo miModelo2;

    private final MiVista miVista;

    public MiControlador(MiVista miVista) {
        this.miVista = miVista;
        this.miModelo1 = new MiModelo();
        this.miModelo2 = new MiModelo();
        super.setListeners(this, this.miVista);
    }

    public String getTexto1() {
        return texto1;
    }

    public void setTexto1(String texto1) {
        this.texto1 = texto1;
    }

    public String getTexto2() {
        return texto2;
    }

    public void setTexto2(String texto2) {
        this.texto2 = texto2;
    }

    public MiModelo getMiModelo1() {
        return miModelo1;
    }

    public void setMiModelo1(MiModelo miModelo1) {
        this.miModelo1 = miModelo1;
    }

    public MiModelo getMiModelo2() {
        return miModelo2;
    }

    public void setMiModelo2(MiModelo miModelo2) {
        this.miModelo2 = miModelo2;
    }
   
}

A continuación explicaré éste código:

Línea 1: Nuestro controlador debe extender de la clase AbstractObserverController de la librería JavaSwing.

Línea 3: Anotación @PropertyController sin ningún atributo. Esto quiere decir que para acceder a esta propiedad en la vista, debemos hacerlo usando su mismo nombre, es decir, para este caso: "texto1".

Línea 6: Anotación @PropertyController con atributo name definido. Es decir, que para acceder a esta propiedad en la vista, debemos hacerlo usando el valor que hemos definido en name, que para este caso es "miTexto".

Línea 9: Anotación @ModelBean sin ningún atributo. Esto quiere decir que para acceder a este objeto en la vista, debemos hacerlo usando su mismo nombre, es decir, para este caso: "miModelo1.atributo". Los atributos en un objeto compuesto, debemos de llamarlos tal cual están definidos en la clase.

Línea 12: Anotación @PropertyController con atributo name definido. Es decir, que para acceder a esta propiedad en la vista, debemos hacerlo usando el valor que hemos definido en name, que para este caso es "pruebaModelo.atributo". Como en el caso anterior, los atributos en un objeto compuesto, debemos de llamarlos como están definidos en la clase.

Línea 21: Seteamos los objetos del Controlador y la Vista que escucharán los cambios. Lo más recomendable es asignarlos al final del constructor.

El resto de métodos son los Getter y Setter de las propiedades a las que les hemos asignado las anotaciones. Estos métodos son obligatorios para asignar y obtener los datos.

Veamos a continuación el modelo que usaremos:

public class MiModelo {
    
    private String textoModelo;
    private Date fechaModelo;
    private Integer numeroModelo;

    public String getTextoModelo() {
        return textoModelo;
    }

    public void setTextoModelo(String textoModelo) {
        this.textoModelo = textoModelo;
    }

    public Date getFechaModelo() {
        return fechaModelo;
    }

    public void setFechaModelo(Date fechaModelo) {
        this.fechaModelo = fechaModelo;
    }

    public Integer getNumeroModelo() {
        return numeroModelo;
    }

    public void setNumeroModelo(Integer numeroModelo) {
        this.numeroModelo = numeroModelo;
    }
    
}

Recuerda que en una clase de modelo, al igual que en el controlador, también son obligatorios los métodos Getter y Setter para cada uno de los atributos definidos.

Anotaciones para usar en un JTextField de Java Swing


La librería SwingUtils en su actual versión, permite asignar tres tipos de anotaciones a un componente de tipo JTextField:

- TextView
- DateTextView
- NumberTextView

Estas anotaciones pueden ser usadas en cualquier campo de tipo texto de java swing, sin embargo cada una tiene ciertas particularidades; @TextView puede ser usada para sincronizar el JTextField con cualquiera de los siguientes tipos de datos del API de Java:

String
Character o char
Byte o byte
Short o short
Integer o int
Long o long
Float o float
Double o double
BigInteger
BigDecimal
Boolean o boolean
java.util.Date
java.sql.Date
java.sql.Time
java.sql.Timestamp
java.time.LocalDate
java.time.LocalTime
java.time.LocalDateTime

Como podemos ver, también es posible usar tipos de datos primitivos.

@DateTextView es conveniente usarlo para sincronizar el JTextFiel con tipos de datos que heredan de la clase java.util.Date y java.time.temporal.Temporal, ya que esta anotación nos permite definir un atributo pattern en caso que queramos personalizar el formato de fecha, y un locale mediante la clase LocaleEnum de la misma librería SwingUtils.

@NumberTextView es conveniente usarlo para sincronizar el JTextField con tipos de datos que heredan de la clase java.lang.Number, ya que esta anotación nos permite definir un atributo pattern en caso que queramos personalizar el formato de número, y un locale mediante la clase LocaleEnum de la misma librería SwingUtils.

En cada una de estas tres anotaciones es posible usar también los atributos required y requiredMessage. El primero, es una propiedad booleana mediante la cual podemos indicar si queremos que el valor a ingresar sea obligatorio o no; y el segundo, es un mensaje personalizado que se mostrará al usuario en formato de error, en caso de que la propiedad required sea true y no se haya ingresado ningún valor en el campo de texto.

Veamos cómo podemos usar estas anotaciones:

@TextView(name = "miModelo1.textoModelo", required = true, requiredMessage = "Debe ingresar un valor")
private javax.swing.JTextField jTextField1;

@DateTextView(name = "miModelo1.fechaModelo", pattern = "dd-MM-yyyy", locale = LocaleEnum.UK, required = true requiredMessage = "Debe ingresar un valor")
private javax.swing.JTextField jTextField2;

@NumberTextView(name = "miModelo1.numeroModelo", pattern = "#,###", locale = LocaleEnum.US, required = true, requiredMessage = "Debe ingresar un valor")
private javax.swing.JTextField jTextField3;

En éste código sólo hemos tomado el objeto miModelo1 de la clase MiControlador para simplificar un poco el ejemplo, pero si quisiéramos usar las otras propiedades lo haríamos así:

@TextView(name = "texto1")
private javax.swing.JTextField jTFtexto1;

@TextView(name = "miTexto")
private javax.swing.JTextField jTFtexto2;

@TextView(name = "pruebaModelo.textoModelo")
private javax.swing.JTextField jTFtexto3;

Veamos ahora cómo deberíamos definir entonces el controlador en nuestra vista y cómo deberíamos inyectarle la dependencia de ésta (la vista) para poder sincronizarlos:

public class MiVista extends javax.swing.JFrame {

    private final MiControlador miControlador;
    
    public MiVista() {
        initComponents();
        
        this.miControlador = new MiControlador(this);
    }
    
}

Cabe aclarar que no es obligatorio que nuestras vistas extiendan de javax.swing.Frame u otro componente de java swing. En este caso yo lo tengo así, ya que el editor de interfaces de NetBeans es quien me crea la clase así.

Con esto es más que suficiente en cuanto a declarar nuestras variables se trata. Ahora vamos a crear dos funcionalidades sencillas mediante dos botones las cuales serán Guardar y Limpiar. La primera nos mostrará por consola los datos ingresados en el formulario, y la segunda deja vacíos los campos del formulario.

La idea de trabajar con un patrón MVC es que los cambios que hagamos en la Vista se vean reflejados en el Modelo a través del Controlador, y del mismo modo, que cuando tengamos cambios en el Modelo y se quieran mostrar al usuario, éstos se vean reflejados en la Vista también a través del Controlador.

En este ejemplo, crearemos los 3 siguientes métodos en el Controlador:

// Método para guardar los datos
public void guardar() {
    super.changeData(TipoUpdateEnum.MODEL);
    printConsole();
}

// Método para limpiar el formulario
public void limpiar() {
    miModelo1 = new MiModelo();
    super.changeData(TipoUpdateEnum.VIEW);
}

// Método para imprimir los datos por consola
public void printConsole() {
    System.out.println("Texto Modelo 1: " + miModelo1.getTextoModelo());
    System.out.println("Fecha Modelo 1: " + miModelo2.getFechaModelo());
    System.out.println("Numero Modelo 1: " + miModelo2.getNumeroModelo());
}

En éste código cabe destacar las líneas 3 y 10. En el método guardar(), lo primero que hacemos es indicar que tenemos datos en la vista que deben ser actualizados en el modelo de datos, por lo tanto, con la sentencia super.changeData(TipoUpdateEnum.MODEL) estamos sincronizando todos los datos de la Vista con el o los modelos de datos o atributos independientes que tengamos en nuestro Controlador, de tal manera que al llamar el método printConsole() se mostrará todo lo que haya escrito en el formulario.

En el método limpiar(), como lo que queremos hacer es vaciar los campos del formulario, lo primero que deberíamos hacer es limpiar dichos valores en nuestro modelo de datos, y como en este caso sólo estamos tomando el objeto miModelo1, lo único que deberíamos de hacer es re-inicializar dicho objeto. De esta forma, a continuación con la sentencia super.changeData(TipoUpdateEnum.VIEW) sincronizamos todos los datos del o los modelos de datos o atributos independientes que tengamos en nuestro Controlador con la Vista, y en este caso lo que haría es mostrar vacíos los campos de texto, ya que nuestro modelo de datos no tiene valores.

Podemos ver que con TipoUpdateEnum.MODEL indicamos que queremos actualizar el modelo de datos, y con TipoUpdateEnum.VIEW indicamos que queremos actualizar la vista con los datos que están en el modelo de datos.

SwingUtils - Sincronizar datos de un JTextField con el Controlador (Java Swing) - Formulario

¿Y en caso de que no queramos actualizar todo el modelo de datos?

Como vimos, a través de la sentencia super.changeData(), es que se logra la actualización inmediata de datos entre la Vista y el Modelo de datos, y para este ejemplo se hacía una actualización a nivel de todos los modelos de datos y/o atributos independientes que tengamos declarados en nuestro Controlador.

Sin embargo, pueden haber casos en los que sólo deseamos actualizar, un modelo con todos sus atributos (en caso de que tengamos declarados varios modelos), un sólo atributo que tengamos declarado con la anotación @PropertyController, un sólo atributo de alguno de nuestros modelos, o incluso una pequeña lista de datos que queramos actualizar en el Modelo o en la Vista.

Para esto, el método super.changeData() está preparado para recibir dos tipos de objetos. Uno es de tipo enum TipoUpdateEnum el cual ya vimos como usarlo; y el otro es de tipo ObjectUpdate, el cual tiene tres constructores con los cuales puede ser instanciado:

new ObjectUpdate(TipoUpdateEnum tipoUpdateEnum)

new ObjectUpdate(TipoUpdateEnum tipoUpdateEnum, String component)

new ObjectUpdate(TipoUpdateEnum tipoUpdateEnum, List<string> listComponents)

El primer constructor funciona de la misma forma que si pasáramos sólo el enum al método changeData(). El segundo constructor, nos pide el enum con el tipo de actualización y adicionalmente el componente a actualizar; y el tercer constructor nos pide el enum con el tipo de actualización y una lista de componentes que deseemos actualizar.

Veamos distintos ejemplos de como podemos usar estas instancias usando los atributos que declaramos al principio de este artículo. En los siguientes ejemplos, actualizaremos los datos en el modelo de datos, pero si lo que deseamos es actualizar los valores que se muestra en la vista, lo haríamos de la misma forma pero usando TipoUpdaeEnum.VIEW:

// Indicamos que sólo queremos que se actualice el modelo miModelo2.
// Al ser declarado con la anotación @ModelBean y con un nombre
// personalizado (pruebaModelo), SwingUtils reconoce que éste es
// un modelo de datos y debe actualizar todos los atributos que
// tenga éste objeto
super.changeData(new ObjectUpdate(TipoUpdateEnum.MODEL, "pruebaModelo"));


// Indicamos que sólo queremos que se actualice el atributo numeroModelo
// del objeto miModelo1. Al ser declarado con la anotación @ModelBean,
// SwingUtils reconoce que éste es un modelo de datos y actualizará sólo
// el atributo de este objeto que le indiquemos
super.changeData(new ObjectUpdate(TipoUpdateEnum.MODEL, "miModelo1.numeroModelo"));


// Indicamos que sólo quieremos que se actualice el atributo texto2.
// Al estar declarado con la anotación @PropertyController y con un
// nombre personalizado (miTexto), SwingUtils reconoce que éste es
// un atributo independiente en el controlador y sólo debe actualizar
// dicho atributo
super.changeData(new ObjectUpdate(TipoUpdateEnum.MODEL, "miTexto"));


// En caso que queramos pasar una lista de componentes, pasamos un List
// de tipo String con los nombres de los componentes que deseemos
// actualizar de la misma forma como lo vimos en los casos anteriores
super.changeData(new ObjectUpdate(TipoUpdateEnum.MODEL, Arrays.asList("texto1", "pruebaModelo", "miModelo1.fechaModelo"));

Con éstos ejemplos, damos por concluido éste artículo en donde hemos visto como podemos sincronizar fácilmente datos de un JTextField con el Controlador usando la librería SwingUtils para que la información de nuestra vista se actualice rápidamente en el modelo de datos y viceversa.

No hay comentarios.:

Publicar un comentario