martes, 31 de mayo de 2016

Java Swing: Event Dispatch Thread (EDT) - Parte 3

En este artículo consiste en un pequeño programa que permite comprobar algunas de las cosas que he comentado sobre el Event Dispatch Thread de Java Swing en artículos anteriores.

Haciendo pruebas con el Event Dispatch Thread


El pequeño programa EventDispatchThreadTest (ver código fuente completo aquí) ilustrar fácilmente algunos de los efectos que se producen al bloquear el EDT. Se compone de 2 ventanas (JFrame) llamadas One y Two:
  • En la ventana One hay un botón llamado Long-running task que ejecuta una tarea larga que bloquea el EDT.
  • En la ventana Two se deberían mostrar los efectos de la pulsación de dicho botón.
El botón Long-running task simplemente imprime por consola una línea cada cierto número de iteraciones del bucle. De este modo es una tarea medianamente intensiva en operaciones de entrada/salida y no sólo en consumo de procesador (no conviene hacer pruebas de bloqueo con un simple bucle vacío porque se consumiría el procesador sin facilitar la conmutación entre varios hilos de ejecución).

Al comienzo del oyente del botón se establece el texto del JLabel de la ventana Two a "Left button pressed. Task running...". Sin embargo, jamás se llega a ver ese texto porque hasta que no termina el oyente del botón, no se repinta y al final del evento, se ejecuta el método labelAutoText() para pintar el número de veces que se ha realizado al tarea en ese mismo JLabel.

Además, durante todo ese tiempo, el botón parece pulsado, aunque se haya soltado el botón del ratón y se esté haciendo otras cosas con él.

Otra cosa que se puede comprobar es que el resto de componentes de la GUI no responde mientras la tarea está en ejecución. Por ejemplo, no se puede pulsar el botón Nothing.

Sí se puede mover, maximizar/minimizar y cambiar el tamaño de ambas ventanas. Si se agranda cualquiera de ellas, se verá como el nuevo área queda sin pintar hasta que finaliza la tarea. En este caso, el tamaño, posición y estado (minimizado/maximizado) de las ventanas es controlado por el gestor de ventanas del sistema operativo, pero el repintado no se puede realizar hasta que termine el oyente del botón.

Otra de las cosas que permite comprobar el programa es que mientras la tarea se está ejecutando, no se reciben eventos (y por tanto no se podrán disparar otros oyentes). En especial, si se minimiza y se vuelve a maximizar una de las ventanas mientras se está ejecutando la tarea, los mensajes respectivos se verán todos juntos al final, pero no se recibirán en el momento en el que ocurren.

El programa es muy sencillo y se puede retocar muy fácilmente.

Enlaces


Los enlaces de interés relacionados con esta serie de artículos sobre el EDT están aquí, en el primero de los artículos sobre este tema.


(Actualizado 31/05/2016)

Source code: EventDispatchThreadTest

Código fuente del programa EventDispatchThreadTest

Ver la entrada correspondiente en el siguiente enlace:


Java Swing: Event Dispatch Thread (EDT) - Parte 3

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.event.WindowStateListener;
import java.util.Date;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;


public class EventDispatchThreadTest {

    private JFrame myOne;
    private JFrame myTwo;
    private JButton myButtonTask;
    private JButton myButtonReset;
    private JButton myButtonNothing;
    private JLabel myLabel;
    private int myClickCount;
    private int myResetCount;

    public EventDispatchThreadTest() {
        super();

        JFrame one= new JFrame("One");
        one.setName( one.getTitle() );
        one.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        one.setSize(300, 400);
        one.setLocation(0, 0);
        JButton buttonTask= new JButton("Long-running task");
        JButton buttonReset= new JButton("Reset click count");
        JPanel onePanel= new JPanel();
        onePanel.setOpaque(true);
        onePanel.setBackground(Color.BLUE);
        onePanel.add(buttonTask);
        onePanel.add(buttonReset);
        one.setContentPane(onePanel);

        WindowListener auxWindowListener= EventDispatchThreadTest.createWindowListener();
        one.addWindowListener(auxWindowListener);

        WindowStateListener auxWindowStateListener= EventDispatchThreadTest.createWindowStateListener();
        one.addWindowStateListener(auxWindowStateListener);

        MouseListener auxMouseListener= EventDispatchThreadTest.createMouseListener();
        onePanel.addMouseListener(auxMouseListener);

        MouseMotionListener auxMouseMotionListener= EventDispatchThreadTest.createMouseMotionListener();
        onePanel.addMouseMotionListener(auxMouseMotionListener);

        JFrame two= new JFrame("Two");
        two.setName( two.getTitle() );
        two.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        two.setSize(300, 400);
        two.setLocation(400, 0);
        JLabel label= new JLabel("Never pressed");
        JButton buttonNothing= new JButton("Nothing");
        JPanel twoPanel= new JPanel();
        twoPanel.setOpaque(true);
        twoPanel.setBackground(Color.BLUE);
        twoPanel.add(label);
        twoPanel.add(buttonNothing);
        two.setContentPane(twoPanel);

        buttonTask.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //This text is never shown.
                EventDispatchThreadTest.this.myLabel.setText("Left button pressed. Task running...");

                for (int i= 0; i < 99999; i++) {
                    String auxString= String.format("Working... %s %s%n", i, new Date(System.currentTimeMillis()));
                    if ((i % 2) == 0) {
                        System.out.print(auxString);
                    }
                }

                EventDispatchThreadTest.this.myClickCount++;
                EventDispatchThreadTest.this.labelAutoText();
                System.out.format("isEventDispatchThread=%s%n", SwingUtilities.isEventDispatchThread());
            }
        });

        buttonReset.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                EventDispatchThreadTest.this.myClickCount= 0;
                EventDispatchThreadTest.this.myResetCount++;

                EventDispatchThreadTest.this.labelAutoText();
            }
        });

        this.myOne= one;
        this.myTwo= two;
        this.myButtonTask= buttonTask;
        this.myButtonReset= buttonReset;
        this.myButtonNothing= buttonNothing;
        this.myLabel= label;

        this.myClickCount= 0;
        this.myResetCount= 0;
        this.myOne.setVisible(true);
        this.myTwo.setVisible(true);
    }

    private void labelAutoText() {
        this.myLabel.setText(String.format("Click count: %s. Reset count= %s"
            , this.myClickCount
            , this.myResetCount
        ));
    }

    private static WindowListener createWindowListener() {
        WindowListener auxWindowListener= new WindowListener() {
            @Override
            public void windowOpened(WindowEvent e) {
                System.out.format("WindowEvent (EDT? %s): %s%n", SwingUtilities.isEventDispatchThread(), e);
            }

            @Override
            public void windowClosing(WindowEvent e) {
                System.out.format("WindowEvent (EDT? %s): %s%n", SwingUtilities.isEventDispatchThread(), e);
            }

            @Override
            public void windowClosed(WindowEvent e) {
                System.out.format("WindowEvent (EDT? %s): %s%n", SwingUtilities.isEventDispatchThread(), e);
            }

            @Override
            public void windowIconified(WindowEvent e) {
                System.out.format("WindowEvent (EDT? %s): %s%n", SwingUtilities.isEventDispatchThread(), e);
            }

            @Override
            public void windowDeiconified(WindowEvent e) {
                System.out.format("WindowEvent (EDT? %s): %s%n", SwingUtilities.isEventDispatchThread(), e);
            }

            @Override
            public void windowActivated(WindowEvent e) {
                System.out.format("WindowEvent (EDT? %s): %s%n", SwingUtilities.isEventDispatchThread(), e);
            }

            @Override
            public void windowDeactivated(WindowEvent e) {
                System.out.format("WindowEvent (EDT? %s): %s%n", SwingUtilities.isEventDispatchThread(), e);
            }
        };

        return auxWindowListener;
    }

    private static WindowStateListener createWindowStateListener() {
        WindowStateListener auxWindowStateListener= new WindowStateListener() {
            @Override
            public void windowStateChanged(WindowEvent e) {
                System.out.format("Window[State]Event (EDT? %s): %s%n", SwingUtilities.isEventDispatchThread(), e);
            }
        };

        return auxWindowStateListener;
    }

    private static MouseListener createMouseListener() {
        MouseListener auxMouseListener= new MouseListener() {
            @Override
            public void mouseClicked(MouseEvent e) {
                System.out.format("MouseEvent (EDT? %s): %s%n", SwingUtilities.isEventDispatchThread(), e);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                System.out.format("MouseEvent (EDT? %s): %s%n", SwingUtilities.isEventDispatchThread(), e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                System.out.format("MouseEvent (EDT? %s): %s%n", SwingUtilities.isEventDispatchThread(), e);
            }

            @Override
            public void mouseEntered(MouseEvent e) {
                System.out.format("MouseEvent (EDT? %s): %s%n", SwingUtilities.isEventDispatchThread(), e);
            }

            @Override
            public void mouseExited(MouseEvent e) {
                System.out.format("MouseEvent (EDT? %s): %s%n", SwingUtilities.isEventDispatchThread(), e);
            }
        };

        return auxMouseListener;
    }

    private static MouseMotionListener createMouseMotionListener() {
        MouseMotionListener auxMouseMotionListener= new MouseMotionListener() {

            @Override
            public void mouseDragged(MouseEvent e) {
                System.out.format("MouseMotionEvent (EDT? %s): %s%n", SwingUtilities.isEventDispatchThread(), e);
            }

            @Override
            public void mouseMoved(MouseEvent e) {
                System.out.format("MouseMotionEvent (EDT? %s): %s%n", SwingUtilities.isEventDispatchThread(), e);
            }
        };

        return auxMouseMotionListener;
    }

    public static void main(String[] args) {
        System.out.format("isEventDispatchThread=%s%n", SwingUtilities.isEventDispatchThread());
        //GUI code should be executed in the EDT.
        SwingUtilities.invokeLater(new Runnable()  {
            @Override
            public void run() {
                System.out.format("isEventDispatchThread=%s%n", SwingUtilities.isEventDispatchThread());
                new EventDispatchThreadTest();
            }
        });
    }

}

domingo, 29 de mayo de 2016

Recapitulando: año 3

Repasando los artículos del blog escritos a lo largo de este tercer año de existencia, puedes descubrir:


Si has leído alguno, muchas gracias y espero que te haya gustado o que te haya resultado útil.

Y recordar que los comentarios son bienvenidos.

Si quieres ver la recopilación de los años anterior, puedes hacerlo a partir de este enlace:



(Actualizado 29/05/2016)