lunes, 18 de octubre de 2010

Uso de SwingWorker en una aplicacion Swing

Me decidi a hacer este post porque la verdad utilizar esta clase es muy util aunque la documentacion encontrada a veces es algo confusa, en este ejemplo vamos a cargar una tabla JTable de utilizando la clase SwingWorker. Ok, para los que no conocen vamos a orientarlos un poco, esta clase basicamente lo que hace es generar un hilo distinto al hilo principal de nuestra aplicacion para ejecutar una determinada accion, en palabras sencillas si conocen de Web Apps es como hacer Ajax en nuestra aplicacion desktop. Algunos usos, por ejemplo su aplicacion tiene varias ventanas, y su usuario quiere dejar cargando en una ventana unos registros de un filtrado y en otra ir editando algunos items de otra tabla.



 Asumiré que saben como hacer una ventana como la mostrada, aunque de todas maneras planeo hacer un post sobre como hacer esto, dado que no es nada dificil.

Vamos al asunto, en la ventana Categorias vamos a agregar en nuestro boton un evento.
Ahora tendremos en el Editor netbeans algo similiar a esto:
Vamos a codear!!!
Para este ejemplo vamos a poner todo el codigo en esta clase [ESTO NO ES LO OPTIMO]
Premisas :
  1. Se utilizará la Base de Datos de ejemplo que viene con Netbeans
  2. Para este ejemplo se cargará en realidad la tabla Products de dicha base
  3. Se asume que conocen un poco de JDBC
Creamos nuestro query y abrimos la conexion a la base de datos:
private void jbtnListarActionPerformed(java.awt.event.ActionEvent evt) {
//Definimos nuestro Query
        final String query = "select * from APP.PRODUCT";
//vamos a crear nuestra conexion a la BD
        conectar();

Este es el codigo para la conexion
private void conectar() {
        try {
            Class.forName("org.apache.derby.jdbc.ClientDriver").newInstance();
            String dbUrl = "jdbc:derby://localhost:1527/sample";//URL DE CONEXION A LA DB            
            con = DriverManager.getConnection(dbUrl,"app","app");
        } catch (InstantiationException ex) {
            Logger.getLogger(FrmProductos.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(FrmProductos.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(FrmProductos.class.getName()).log(Level.SEVERE, null, ex);
        } catch (SQLException ex) {
            Logger.getLogger(FrmProductos.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

Intanciamos un objeto de nuestra clase SwingWorker, al hacerlo nos exige implementar el metodo doInBackground, que es donde se ejecutan nuestras operaciones asincronas.
private void jbtnListarActionPerformed(java.awt.event.ActionEvent evt) {
        SwingWorker worker=new SwingWorker < Void , Void="">() {

            @Override
            protected Void doInBackground() throws Exception {
                throw new UnsupportedOperationException("Not supported yet.");
            }
        };
    }
Bueno vamos a crear algunos campos que seran de uso global dentro de SwingWorker, de manera que nuestra implementacion quedará algo asi:
//donde almacenaremos los datos
            ArraylistTableModel model;
            ArrayList datos;
            String[] cols = new String[8];//son 8 columnas
Ahora vamos poner nuestro propio codigo dentro de doInBackground.
protected Void doInBackground() throws Exception {
                datos = new ArrayList();//instanciamos                
                try {
                    //Creamos un Statement para ejecutar el query                    
                    java.sql.PreparedStatement pstmt = con.prepareStatement(query);
                    //ejecutamos el query                     
                    pstmt.executeQuery();
                    // asignamos el resutaldo a rs
                    java.sql.ResultSet rs = pstmt.getResultSet();
                    //asignamos la metadata
                    java.sql.ResultSetMetaData rm = rs.getMetaData();
                    //creamos un array donde pondremos los nombres de columnas
                    for (int i = 0; i < rm.getColumnCount(); i++) {
                        cols[i] = rm.getColumnName(i+1).toString();
                    }
                    //recorremos el Resultset
                    while (rs.next()) {//mientras halla datos
                        ArrayList fila = new ArrayList();//instanciamos donde almacenaremos los datos de cada fila
                        for (int i = 1; i <= rm.getColumnCount(); i++) {
                            fila.add(rs.getObject(i).toString());
                        }                        
                        datos.add(fila);//agregamos la fila cargada
                        setProgress(datos.size());//actualizamos el progreso
                    }                   

                } catch (SQLException ex) {
                    Logger.getLogger(FrmProductos.class.getName()).log(Level.SEVERE, null, ex);                    
                }

                return null;
            }
Muy bien ya tenemos nuestra operacion Asincrona. Pero debemos llenar la tabla por el momento hemos llenado el modelo, que de acuerdo al patron que utiliza JTable, es quien contiene los datos del mismo.
La Clase ArraylistTableModel es una implementacion de AbstractTableModel que en este caso yo he hecho. Su implementacion aqui. ArraylistTableModel.java
Por ultimo implementaremos el metodo done() que es quien especifica que se debe hacer cuando acabe el procedimiento asincrono.
Este codigo es dentro de la implementacion de SwingWorker
final javax.swing.SwingWorker worker = new javax.swing.SwingWorker() {

            /* implementacion de campos */

            @Override
            protected Void doInBackground() throws Exception {
                /* codigo de doInBackground */            
            }

            @Override
            protected void done() {
                setProgress(100);// hacemos el progreso a 100
                jtblProductos.setModel(new ArraylistTableModel(datos, cols));
            }
        };
Para ir actualizando la barra de progreso agregamos un escuchador
worker.addPropertyChangeListener(new PropertyChangeListener() {//agregarmos un escuchador de cambio de propiedad

            public void propertyChange(PropertyChangeEvent pce) {
                progressBar.setValue(worker.getProgress());//actualizamos el valor del progressBar
            }
        });
Codigo completo
private void jbtnListarActionPerformed(java.awt.event.ActionEvent evt) {

        //iniciamos nuestra barra de progreso
        progressBar.setValue(0);
        
        //Definimos nuestro Query
        final String query = "select * from APP.PRODUCT";
        //vamos a crear nuestra conexion a la BD
        conectar();


        final javax.swing.SwingWorker worker = new javax.swing.SwingWorker() {

            //donde almacenaremos los datos
            ArraylistTableModel model;
            ArrayList datos;
            String[] cols = new String[8];//son 8 columnas

            @Override
            protected Void doInBackground() throws Exception {
                datos = new ArrayList();//instanciamos                
                try {
                    //Creamos un Statement para ejecutar el query                    
                    java.sql.PreparedStatement pstmt = con.prepareStatement(query);
                    //ejecutamos el query                     
                    pstmt.executeQuery();
                    // asignamos el resutaldo a rs
                    java.sql.ResultSet rs = pstmt.getResultSet();
                    //asignamos la metadata
                    java.sql.ResultSetMetaData rm = rs.getMetaData();
                    //creamos un array donde pondremos los nombres de columnas
                    for (int i = 0; i < rm.getColumnCount(); i++) {
                        cols[i] = rm.getColumnName(i+1).toString();
                    }
                    //recorremos el Resultset
                    while (rs.next()) {//mientras halla datos
                        ArrayList fila = new ArrayList();//instanciamos donde almacenaremos los datos de cada fila
                        for (int i = 1; i <= rm.getColumnCount(); i++) {
                            fila.add(rs.getObject(i).toString());
                        }                        
                        datos.add(fila);//agregamos la fila cargada
                        setProgress(datos.size());//actualizamos el progreso
                    }                   

                } catch (SQLException ex) {
                    Logger.getLogger(FrmProductos.class.getName()).log(Level.SEVERE, null, ex);                    
                }

                return null;
            }

            @Override
            protected void done() {
                setProgress(100);
                jtblProductos.setModel(new ArraylistTableModel(datos, cols));
            }
        };

        //agregamos un escuchador de cambio de propiedad
        worker.addPropertyChangeListener(new PropertyChangeListener() {       
            public void propertyChange(PropertyChangeEvent pce) {
                progressBar.setValue(worker.getProgress());//actualizamos el valor del progressBar
            }
        });

        worker.execute();
    }
Codigo completo de la clase Clase de ejemplo utilizando swingworker

Final mente nuestra aplicacion se vera asi

Luego de hacer este post encontré otro que me parece muy bueno de leer Uso avanzado de swingworker

6 comentarios:

  1. Hola, te cuento que probé tu ejemplo con Motor MySql 5, esquema mysql, tabal help_topic que trae 506 registros... y que crees? NO me funcionó como yo epseraba, pues, el worker solamente me devuelve 100 registros y se detiene; además, el jprogressbar no me refleja el progreso de obtencion de datos pues se pasa de 0% a 100% en cuestion de un segundo.

    MIra, como estoy haciendo una aplicacion que maneja muchisimsos registros, debo obligadamente implementar SW y lo he logrado, para ello implementé 2 RS's, el primero es para contar los registros y el segundo es para recorrerlos, así, al JPB (jprogressbar) le asigno un setMaximum dependiendo del total de regs. y en el while le voy agregando +1, de esta manera me funcioan perfecto y el JPB me refleja bien lo que el worker realiza.

    Si tienes una solucion más práctica, por que supongo que la mía no lo es del todo, por favor remitemela y si crees que la mia lo es... estaré gustoso en enviarte un miniproyecto de java + mysql + swingworker para que pueda ser posteado en tu blog.

    De antemano muchas gracias.
    Estaré visitando más seguido tu blog en busca de mas respuestas y soluciones por que creeme que ando en mi proyecto de la Universidad y apenas estoy empezandolo... bueno, saludos desde Ecuador.

    ResponderEliminar
  2. Hola Hamilton, disculpa por la demora en responder y te agradezco la visita, bueno si tienes razon en que el ejemplo puede que no se adecue a todas las circunstancias, yo tb lo he padecido al implementarlo en algunas aplicaciones.
    Mi solucion fue la siguiente, utilizar la clase javax.swing.Timer.

    Timer timer = new Timer(500, new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
    if (getProgress() < 99) {
    setProgress(getProgress() + 1);
    }
    }});
    timer.start();
    //Haz tus procesos
    timer.stop();

    Espero te sirva

    ResponderEliminar

 
Powered by Blogger