JDC Tech Tips (20 de Diciembre de 2001)

Bienvenido a los Consejos Técnicos de la Conexión del Desarrollador Java (JDC), del 20 de Diciembre de 2001. Esta edición cubre:

  • Crear Modal Internal Frames, Aproximación I.
  • Crear Modal Internal Frames, Aproximación II.

Estos consejos fueron desarrollados usando Java(tm) 2 SDK, Standard Edition v 1.3

Se puede ver esta edición (en su original en inglés) de los Consejos Técnicos en formato html en http://java.sun.com/jdc/JDCTechTips/2001/tt1220.html

Crear Modal Internal Frames, Aproximación I

El conjunto de componentes Java Foundation Classes (JFC) Project Swing proporciona el componente JOptionPane para mostrar simples díalogos estándards. Un diálogo es una ventana que muestra información, pero también espera una respuesta del usuario. Por ejemplo, un diálogo podría avisar al usuario de un problema potencial, y también mostar un boton OK para que el usuario pueda reconocer el aviso. Podemos mostrar un diálogo en una ventana popup o un marco interno, es decir, una ventana dentro de otra ventana.

Para mostrar un diálogo en una ventana de popup, usamos uno de los métodos JOptionPaneshowXXXDialog como showMessageDialog. La ventana desplegable en este caso es modal. Esto significa que el usuario debe responder a la ventana antes de que el programa continúe su ejecución. Pero hay más -- modal también significa que el usuario no puede interactuar con otras partes del programa. Para mostrar un diálogo en un frame interno, usamos uno de los métodos showInternalXXXDialog de JOptionPane. Estos diálogos de marco interno no son modales. Sin embargo hay veces que podríamos querer que un diálogo interno sea modal. Este truco mostrará como crear un diálogo modal en un marco interno.

Hay un límite para soportar la modalidad en marcos internos creados por un JOptionPane. Para aprovecharnos de este límite, necesitamos situar el frame interno en el glass pane del frame donde aparece el desktop pane.

Si hemos trabajado con frames internos, sabemos que normalmente añadimos los frames internos, es decir ejemplares de JInternalFrame, a un desktop pane, es decir, un ejemplar de JDesktopPane. Un desktop pane es un panel de capas que maneja múltiples frames internos solapados. Glass pane es parte del panel raíz con el que trata una ventana de alto nivel. Un root pane se compone de tres partes (el glass pane, el layered pane, y el content pane), y una cuarta parte opcional (la barra de menú). El content pane contiene los componentes visibles del root pane. La barra de menú opcional contiene los menús del root pane. Y el layered pane posiciona los contenidos del content pane y del menu bar opcional. El glass pane es útil en la intercepción de eventos que de otra forma podrían pasar a través de los componentes subyacentes.

Por eso, para repetir, podemos crear un diálogo modal de alguna forma en un frame interno creado por JOptionPane. Para hacer esto, ponemos el internal frame en el glass pane del marco donde aparezca el desktop pane. Esta técnica restringe la entrada sólo a ese marco especificado. El internal frame en este caso no es realmente modal. Para ser realmente modal, un internal frame necesita bloquearse una vez que se ha mostrado. Sin embargo, la aproximación no restringe la entrada a un sólo componente.

El primer paso de esta técnica es crear un diálogo dentro de un internal frame. JOptionPane, y luego usar los métodos createInternalXXX para crear y mostrar los componentes message necesarios. Por ejemplo, lo siguiente crea un mensaje de diálogo dentro de un internal frame:

  
  JOptionPane optionPane = new JOptionPane();
  optionPane.setMessage("Hello, World");
  optionPane.setMessageType(
    JOptionPane.INFORMATION_MESSAGE);
  JInternalFrame modal = 
    optionPane.createInternalFrame(desktop, "Modal");

El siguiente paso es situar el componente en el glass pane de la ventana donde está localizado el desktop pane. El glass pane puede ser cualquier componente. Por eso, la forma más fácil de hacer esto es crear un JPanel transparente:

  JPanel glass = new JPanel();
  glass.setOpaque(false);
  glass.add(modal);
  frame.setGlassPane(glass);
  glass.setVisible(true);
  modal.setVisible(true);

Los últimos pasos son configurar el glass pane para que intercepte los eventos, y para ocultarse cuando el internal frame se cierre. Para que el glass pane intercepte eventos, debemos adjuntar un MouseListener y un MouseMotionListener. Para ocultar el glass pane cuando se cierra el internal frame, necesitamos adjuntar un InternalFrameListener al internal frame:

  class ModalAdapter extends InternalFrameAdapter {
    Component glass;

    public ModalAdapter(Component glass) {
this.glass = glass;

// Associate dummy mouse listeners
// Otherwise mouse events pass through
MouseInputAdapter adapter = new MouseInputAdapter(){};
glass.addMouseListener(adapter);
glass.addMouseMotionListener(adapter);
    }

    public void internalFrameClosed(InternalFrameEvent e) {
glass.setVisible(false);
    }
  }

Aquí tenemos un programa que pone todas las piezas juntas. Crea un JDesktopPane con un sólo internal frame. En este marco hay un botón. Cuando se pulsa el botón, aparece el diálogo de mensaje que bloquea el internal frame. Mientras que está visible, no podemos pulsar el primer botoón. Una vez pulsado el boton OK de la ventana del mensaje, podemos interactúar con el primer internal frame.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

public class Modal  {

  static class ModalAdapter 
extends InternalFrameAdapter {
    Component glass;

    public ModalAdapter(Component glass) {
this.glass = glass;

// Associate dummy mouse listeners
// Otherwise mouse events pass through
MouseInputAdapter adapter = 
  new MouseInputAdapter(){};
glass.addMouseListener(adapter);
glass.addMouseMotionListener(adapter);
    }

    public void internalFrameClosed(
  InternalFrameEvent e) {
glass.setVisible(false);
    }
  }

  public static void main(String args[]) {
    final JFrame frame = new JFrame(
"Modal Internal Frame");
    frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);

    final JDesktopPane desktop = new JDesktopPane();

    ActionListener showModal = 
  new ActionListener() {
public void actionPerformed(ActionEvent e) {

  // Manually construct a message frame popup
  JOptionPane optionPane = new JOptionPane();
  optionPane.setMessage("Hello, World");
  optionPane.setMessageType(
    JOptionPane.INFORMATION_MESSAGE);
  JInternalFrame modal = optionPane.
    createInternalFrame(desktop, "Modal");

  // create opaque glass pane
  JPanel glass = new JPanel();
  glass.setOpaque(false);

  // Attach modal behavior to frame
  modal.addInternalFrameListener(
    new ModalAdapter(glass));

  // Add modal internal frame to glass pane
  glass.add(modal);

  // Change glass pane to our panel
  frame.setGlassPane(glass);

  // Show glass pane, then modal dialog
  glass.setVisible(true);
  modal.setVisible(true);

  System.out.println("Returns immediately");
}
    };

    JInternalFrame internal = 
new JInternalFrame("Opener");
    desktop.add(internal);

    JButton button = new JButton("Open");
    button.addActionListener(showModal);

    Container iContent = internal.getContentPane();
    iContent.add(button, BorderLayout.CENTER);
    internal.setBounds(25, 25, 200, 100);
    internal.setVisible(true);

    Container content = frame.getContentPane();
    content.add(desktop, BorderLayout.CENTER);
    frame.setSize(500, 300);
    frame.setVisible(true);
  }
}

Crear Modal Internal Frames, Aproximación II

Aunque la aproximación anterior proporcionan marcos internos que bloqueaban las entradas de otros marcos internos, los marcos no eran realmente modales. Para ser realmente modal, un internal frame necesita bloquearse una vez que se ha mostrado. El internal frame de arriba no hace esto.

Para poder hacer un internal frame realmente modal, debemos hacernos con el despacho de eventos cuando el frame se ha mostrado. Esto sería además de mostrar el frame en el glass pane. Todavía podemos usar el JOptionPane para crear los diálogos de mensaje y de entrada, pero también necesitamos añadir algún comportamiento que normalmente es manejado por nosotros cuando usamos uno de los métodos showInternalXXX. Debido a la necesidad de un comportamiento personalizado, y cuándo es necesitado, es necesario crear una subclase de JInternalFrame. Hacer esto también nos permite mover dentro de la subclase mucho del comportamiento que hacíamos anteriormente en el ActionListener.

La mayoría del trabajo que necesitamos para crear un internal frame realmente modal implica completar el constructor de la subclase. Simplemente copiando el código del ActionListener de la aproximación anterior al constructor proporcionamos un marco de trabajo en el que podemos construirlo. Pasandolo en el JRootPane, podemos usar este JInternalFrame modal en un JApplet así como en un JFrame.

  public ModalInternalFrame(String title, 
JRootPane rootPane, Component desktop, 
JOptionPane pane) {
    super(title);

    // create opaque glass pane
    final JPanel glass = new JPanel();
    glass.setOpaque(false);

    // Attach mouse listeners
    MouseInputAdapter adapter = 
new MouseInputAdapter(){};
    glass.addMouseListener(adapter);
    glass.addMouseMotionListener(adapter);

    // Add in option pane
    getContentPane().add(pane, BorderLayout.CENTER);

    // *** Remaining code to be added here ***

    // Add modal internal frame to glass pane
    glass.add(this);

    // Change glass pane to our panel
    rootPane.setGlassPane(glass);

    // Show glass pane, then modal dialog
    glass.setVisible(true);
  }

Observa que el único código no copiado desde el ActionListener es la llamada final a setVisible(true) del internal frame.

Alguna de las otras tareas que realizan los métodos showInternalXXX de JOptionPane incluyen la sección de un diálogo de cierre una vez que el botón se ha seleccionado (o se ha introducido una entrada), y algunas tareas relacionadas con la apariencia que casi siempre tienen algo que ver con el tamaño. Debido a que no estámos usando el método showInternalXXX, debemos realizar otras tareas nosotros mismos.

La forma de configurar el cierre del internal frame es adjuntar un PropertyChangeListener al option pane. En el JOptionPane, cuando se selecciona un botón o se introduce una entrada, dispara la generación de un PropertyChangeEvent. Podemos cerrar el marco cuando suceda este evento. Aquí tenemos el código para este comportamiento:

    // Define close behavior
    PropertyChangeListener pcl = 
  new PropertyChangeListener() {
public void propertyChange(
    PropertyChangeEvent event) {
  if (isVisible() && 
    (event.getPropertyName().equals(
JOptionPane.VALUE_PROPERTY) ||
     event.getPropertyName().equals(
JOptionPane.INPUT_VALUE_PROPERTY))) {
    try {
setClosed(true);
    } catch (PropertyVetoException ignored) {
    }
    ModalInternalFrame.this.setVisible(false);
    glass.setVisible(false);
  }
}
    };
    pane.addPropertyChangeListener(pcl);

Hay tres tareas relacionadas con la apariencia que necesitamos realizar. Los diálogos de marcos internos están definidos para tener un borde diferente al borde normal de los internal frames. Por eso, necesitamos configurar una propiedad cliente para el marco. La segunda tarea es inicializar el tamaño y la posición del internal frame. Podríamos codificar "a mano" un tamaño (sin embargo, el siguiente código centra el marco). La última tarea es marcar el internal frame como el seleccionado. Aquí está el código encargado de realizar estas tres tareas:

    // Change frame border
    putClientProperty("JInternalFrame.frameType", 
"optionDialog");

    // Size frame
    Dimension size = getPreferredSize();
    Dimension rootSize = desktop.getSize();

    setBounds((rootSize.width - size.width) / 2,
  (rootSize.height - size.height) / 2,
  size.width, size.height); 
    desktop.validate(); 
    try {
setSelected(true);
    } catch (PropertyVetoException ignored) {
    }

Añadiendo estos dos bloques de código en el medio de nuestro constructor completamos la inicialización de la subclase de JInternalFrame.

Lo último que tenemos que hacer es tomar el despacho de eventos después de que el internal frame se haya mostrado. Normalmente el despacho de eventos se maneja en la clase EventQueue. Sin embargo, como estámos bloqueando el thread de manejo de eventos cuando hacemo modal al internal frame, el EventQueue nunca verá los eventos, Por eso debemos reemplazar su funcionalidad.

Para poder despachar eventos nosotros mismos, todo lo que tenemos que hacer es copiar el código del método dispatchEvent() de EventQueue. Si el internal frame se hace visible desde un thread distinto al thread de despacho de eventos, incluso ni necesitamos copiar el código del método dispatchEvent(). En este caso, todo lo que tenemos que hacer es llamar a wait() para bloquear. Luego, cuando se cierre el marco, necesitamos ser notificados. Aquí está el código de despacho de eventos:

   public void setVisible(boolean value) {
    super.setVisible(value);
    if (value) {
startModal();
    } else {
stopModal();
    }
  }

  private synchronized void startModal() {
    try {
if (SwingUtilities.isEventDispatchThread()) {
  EventQueue theQueue = 
    getToolkit().getSystemEventQueue();
  while (isVisible()) {
    AWTEvent event = theQueue.getNextEvent();
    Object source = event.getSource();
    if (event instanceof ActiveEvent) {
((ActiveEvent)event).dispatch();
    } else if (source instanceof Component) {
((Component)source).dispatchEvent(
  event);
    } else if (source instanceof 
  MenuComponent) {
((MenuComponent)source).dispatchEvent(
  event);
    } else {
System.err.println(
  "Unable to dispatch: " + event);
    }
  }
} else {
  while (isVisible()) {
    wait();
  }
}
    } catch (InterruptedException ignored) {
    }
  }

  private synchronized void stopModal() {
    notifyAll();
  }

Aquí hay un ejemplo que lo pone todo junto. En lugar de simplemente mostrar un mensaje de diálogo, pide al usuario una respuesta a una pregunta Si/No cuando se muestra el internal frame modal. Observa que todo lo que tenemos que hacer después de crear el internal frame es mostrarlo:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.beans.*;

public class ModalInternalFrame extends JInternalFrame {

  public ModalInternalFrame(String title, JRootPane 
rootPane, Component desktop, JOptionPane pane) {
    super(title);

    // create opaque glass pane
    final JPanel glass = new JPanel();
    glass.setOpaque(false);

    // Attach mouse listeners
    MouseInputAdapter adapter = 
new MouseInputAdapter(){};
    glass.addMouseListener(adapter);
    glass.addMouseMotionListener(adapter);

    // Add in option pane
    getContentPane().add(pane, BorderLayout.CENTER);

    // Define close behavior
    PropertyChangeListener pcl = 
  new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent 
    event) {
  if (isVisible() && 
    (event.getPropertyName().equals(
JOptionPane.VALUE_PROPERTY) ||
     event.getPropertyName().equals(
JOptionPane.INPUT_VALUE_PROPERTY))) {
    try {
setClosed(true);
    } catch (PropertyVetoException ignored) {
    }
    ModalInternalFrame.this.setVisible(false);
    glass.setVisible(false);
  }
}
    };
    pane.addPropertyChangeListener(pcl);

    // Change frame border
    putClientProperty("JInternalFrame.frameType",
"optionDialog");

    // Size frame
    Dimension size = getPreferredSize();
    Dimension rootSize = desktop.getSize();

    setBounds((rootSize.width - size.width) / 2,
  (rootSize.height - size.height) / 2,
   size.width, size.height); 
    desktop.validate(); 
    try {
setSelected(true);
    } catch (PropertyVetoException ignored) {
    }

    // Add modal internal frame to glass pane
    glass.add(this);

    // Change glass pane to our panel
    rootPane.setGlassPane(glass);

    // Show glass pane, then modal dialog
    glass.setVisible(true);
  }

  public void setVisible(boolean value) {
    super.setVisible(value);
    if (value) {
startModal();
    } else {
stopModal();
    }
  }

  private synchronized void startModal() {
    try {
if (SwingUtilities.isEventDispatchThread()) {
  EventQueue theQueue = 
    getToolkit().getSystemEventQueue();
  while (isVisible()) {
    AWTEvent event = theQueue.getNextEvent();
    Object source = event.getSource();
    if (event instanceof ActiveEvent) {
((ActiveEvent)event).dispatch();
    } else if (source instanceof Component) {
((Component)source).dispatchEvent(
  event);
    } else if (source instanceof MenuComponent) {
((MenuComponent)source).dispatchEvent(
  event);
    } else {
System.err.println(
  "Unable to dispatch: " + event);
    }
  }
} else {
  while (isVisible()) {
    wait();
  }
}
    } catch (InterruptedException ignored) {
    }
  }

  private synchronized void stopModal() {
    notifyAll();
  }

  public static void main(String args[]) {
    final JFrame frame = new JFrame(
"Modal Internal Frame");
    frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);

    final JDesktopPane desktop = new JDesktopPane();

    ActionListener showModal = 
  new ActionListener() {
Integer ZERO = new Integer(0);
Integer ONE = new Integer(1);
public void actionPerformed(ActionEvent e) {

  // Manually construct an input popup
  JOptionPane optionPane = new JOptionPane(
    "Print?", JOptionPane.QUESTION_MESSAGE, 
    JOptionPane.YES_NO_OPTION);

  // Construct a message internal frame popup
  JInternalFrame modal = 
    new ModalInternalFrame("Really Modal", 
    frame.getRootPane(), desktop, optionPane);

  modal.setVisible(true);

  Object value = optionPane.getValue();
  if (value.equals(ZERO)) {
    System.out.println("Selected Yes");
  } else if (value.equals(ONE)) {
    System.out.println("Selected No");
  } else {
    System.err.println("Input Error"); 
 }
}
    };

    JInternalFrame internal = 
new JInternalFrame("Opener");
    desktop.add(internal);

    JButton button = new JButton("Open");
    button.addActionListener(showModal);

    Container iContent = internal.getContentPane();
    iContent.add(button, BorderLayout.CENTER);
    internal.setBounds(25, 25, 200, 100);
    internal.setVisible(true);

    Container content = frame.getContentPane();
    content.add(desktop, BorderLayout.CENTER);
    frame.setSize(500, 300);
    frame.setVisible(true);
  }
}

Para aprender más sobre internal frames, root panes, y su glass pane puedes ver la lecciones del Java Tutorial "How to Use Internal Frames" y "How to Use Root Panes".

Copyright y notas de la traducción

Nota respecto a la traducción

El original en inglés de la presente edición de los JDC Tech Tips fue escrita por Glen McCluskey, la traducción no oficial fue hecha por Juan A. Palos (Ozito), cualquier sugerencia o corrección hágala al correo [email protected] , sugerencia respecto a la edición original a mailto:[email protected]

Nota (Respecto a la edición via email)

Sun respeta su tiempo y su privacidad. La lista de correo de la Conexión del desarrollador Java se usa sólo para propósitos internos de Sun Microsystems(tm). Usted ha recibido este email porque se ha suscrito a la lista. Para desuscribirse vaya a la página de suscripciones, desmarque casilla apropiada y haga clic en el botón Update.

Suscripciones

Para suscribirse a la lista de correo de noticias de la JDC vaya a la página de suscripciones, elija los boletines a los que quiera suscribirse, y haga clic en Update.

Realimentación

¿Comentarios?, envie su sugerencias a los Consejos Técnicos de la JDC a mailto:[email protected]

Archivos

Usted encontrará las ediciones de los Consejos Técnicos de la JDC (en su original en inglés) en http://java.sun.com/jdc/TechTips/index.html

Copyright

Copyright 2001 Sun Microsystems, Inc. All rights reserved. 901 San Antonio Road, Palo Alto, California 94303 USA.

Este documento esta protegido por las leyes de autor. Para mayor información vea http://java.sun.com/jdc/copyright.html

Enlaces a sitios fuera de Sun

Los Consejos Técnicos de la JDC pueden dar enlaces a otros sitios y recursos. Ya que Sun no tiene control sobre esos sitios o recursos usted reconoce y acepta que Sun no es responsable por la disponibilidad de tales sitios o recursos, y no se responsabiliza por cualquier contenido, anuncios , productos u otros materiales disponibles en tales sitios o recursos. Sun no será responsable, directa o indirectamente, por cualquier daño o pérdida causada o supuestamente causada por o en relación con el uso de o seguridad sobre cualquier tal contenido, bienes o servicios disponibles en o através de cualquier sitio o recurso.

El original en Ingles de esta edición de los Consejos técnicos fue escrita por Glen McCluskey.

JDC Tech Tips December 20, 2001

Sun, Sun Microsystems, Java y Java Developer Connection (JDC) son marcas registradas de Sun Microsystems Incs. en los Estados Unidos y cualquier otro país.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP
SIGUIENTE ARTÍCULO