2010-11-16 11 views
6

Lo que pregunté originalmente no indicaba claramente mi pregunta/problema, así que lo explicaré mejor. Tengo un JButton que establece un JDialog en visible. El JDialog tiene un WindowListener que lo configura como NO visible en el evento windowDeactivated(), que se activa cada vez que el usuario hace clic fuera del cuadro de diálogo. El botón ActionListener comprueba si el diálogo es visible, lo oculta si es verdadero, lo muestra si es falso.Crear una ventana de propiedades de inspección, botón accionado como un JDialog

windowDeactivated() siempre se activará si se hace clic en el botón o no, siempre que el usuario haga clic fuera del cuadro de diálogo. El problema que tengo es cuando el usuario hace clic en el botón para cerrar el diálogo. El cuadro de diálogo se cierra con el WindowListener y luego el ActionListener intenta mostrarlo.

Si windowDeactivated() no es setVisible(false), el cuadro de diálogo sigue abierto, pero detrás de la ventana principal. Lo que estoy pidiendo es cómo obtener acceso a la ubicación del clic dentro de windowDeactivated(). Si sé que el usuario hizo clic en el botón y windowDeactivated() puede omitir el ocultamiento del cuadro de diálogo, de modo que el botón ActionListener verá que todavía está visible y lo ocultará.

 
public PropertiesButton extends JButton { 

    private JDialog theWindow; 

    public PropertiesButton() { 
     theWindow = new JDialog(); 
     theWindow.setUndecorated(true); 
     theWindow.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 
     theWindow.add(new JMenuCheckBoxItem("Something")); 
     theWindow.addWindowListener(new WindowListener() { 
      // just an example, need to implement other methods 
      public void windowDeactivated(WindowEvent e) { 
       theWindow.setVisible(false); 
      } 
     }); 
     this.addActionListener(new ActionListener() { 
      public void actionPerformed(ActionEvent e) { 
       if (theWindow.isVisible()) { 
        theWindow.setVisible(false); 
       } else { 
        JButton btn = (JButton)e.getSource(); 
        theWindow.setLocation(btn.getLocationOnScreen.x,btn.getLocationOnScreen.x-50); 
        theWindow.setVisible(true); 
       } 
      } 
     }); 
     theWindow.setVisible(false); 
    } 

} 
+0

No estoy seguro de cuál es la pregunta. Parece que parece que lo has descubierto. Eso se ve bien (A primera vista) – jjnguy

+0

Lo que he mencionado arriba hará todo lo que yo desee, excepto cuando haga clic fuera del diálogo. Al hacer clic afuera se cierra el diálogo, lo cual está bien, pero cuando hago clic en el botón para abrir el cuadro de diálogo, no se abre la primera vez.Por lo que entiendo, el WindowListener se dispara antes que el ActionListener y aunque el diálogo NO es realmente visible cuando el ActionListener se activa, la llamada .isVisible() devuelve un verdadero. Por lo tanto, el botón mostrará .setVisible (false) aunque no esté visible. – Brian

+2

Brian, puede usar un 'WindowAdapter' en lugar de un oyente de ventana. Entonces solo tienes que implementar los métodos que quieras. – jjnguy

Respuesta

0

Usted podría tratar de usar un JPanel en lugar de un JDialog de la lista desplegable propiedad. Algo como esto:

public class PropertiesButton extends JButton { 

    private JPanel theWindow; 

    public PropertiesButton() { 
     theWindow = new JPanel(); 
     theWindow.add(new JMenuCheckBoxItem("Something")); 

     this.addActionListener(new ActionListener() { 
      public void actionPerformed(ActionEvent e) { 
       if (theWindow.isVisible()) { 
        theWindow.setVisible(false); 
        getParent().remove(theWindow); 
       } else { 
        JButton btn = (JButton)e.getSource(); 
        getParent().add(theWindow);    
        theWindow.setBounds(
         btn.getX(), 
         btn.getY() + btn.getHeight(), 100, 100); 

        theWindow.setVisible(true); 
       } 
      } 
     }); 
     theWindow.setVisible(false); 
    } 

} 

El uso de componentes ligeros en lugar de los pesos pesados ​​que son como el JDialog es siempre preferible en Swing, y tiene menos efectos indeseables como el que usted informe. El único problema de este enfoque es que la posición y el tamaño del panel podrían verse afectados por el administrador de disposición activo en el elemento primario.

0

Una forma fácil, aunque un poco hackosa, de resolver esto, es dejar que PropertiesButton tenga un indicador booleano que indique si debemos ocuparnos de manejar la siguiente acción del botón. Volteamos esta bandera si el cuadro de diálogo está oculto debido a un evento desactivado por la ventana.

public PropertiesButton extends JButton { 

    private JDialog theWindow; 
    private boolean ignoreNextAction; 

(SNIP)

theWindow.addWindowListener(new WindowAdapter() { 
     @Override 
     public void windowDeactivated(WindowEvent e) { 
      ignoreNextAction = true; 
      theWindow.setVisible(false); 
     } 
    }); 
    this.addActionListener(new ActionListener() { 
     public void actionPerformed(ActionEvent e) { 
      if (ignoreNextAction) { 
       ignoreNextAction = false; 
       return; 
      } 
      // ...normal action handling follows 
     } 
    }); 

Tenga en cuenta que no estoy 100% cómodo con este truco: puede haber algún caso sutil echaba de menos cuando falla el enfoque.

0

Ampliando el consejo de awheel, he escrito el siguiente ejemplo que usa la funcionalidad de cristal de Swing. El enfoque es un poco complicado, pero eso no es raro cuando intentas algo moderadamente avanzado en Swing.

La idea es mostrar un panel de superposición transparente (un panel de cristal que cubre todo el contenido de la ventana) cuando se hace clic en el botón y desecharlo cuando el usuario hace clic en cualquier lugar de la ventana o presiona una tecla.

En la parte superior de este panel de vidrio, muestro otro JPanel ("ventana emergente") e intento colocarlo justo encima del botón que activa su visibilidad.

Este enfoque tiene una limitación que no tiene la solución original basada en cuadros de diálogo: lo que se dibuje en la parte superior del panel de vidrio debe caber dentro del área de contenido del marco (después de todo, no es una ventana). Es por eso que en el siguiente código hago algunos ajustes para asegurar que las coordenadas de la ventana emergente < estén dentro de los límites del panel de contenido (de lo contrario, JLabel simplemente se recortaría en los bordes del marco).

También tiene la limitación de que las prensas de ratón atrapadas por el cristal no se delegan en ningún componente subyacente. Por lo tanto, si hace clic en un botón mientras el panel de cristal está visible, el panel de cristal desaparecerá pero también consumirá el clic y el botón que usted pensó que hizo clic no reaccionará. Es posible dar la vuelta a esto si se quiere, pero luego se vuelve aún más complicado y quería mantener mi ejemplo relativamente simple. :-)

import java.awt.Color; 
import java.awt.Container; 
import java.awt.FlowLayout; 
import java.awt.KeyEventDispatcher; 
import java.awt.KeyboardFocusManager; 
import java.awt.Point; 
import java.awt.Window; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.awt.event.KeyEvent; 
import java.awt.event.MouseAdapter; 
import java.awt.event.MouseEvent; 
import javax.swing.JButton; 
import javax.swing.JDialog; 
import javax.swing.JFrame; 
import javax.swing.JLabel; 
import javax.swing.JPanel; 
import javax.swing.JRootPane; 
import javax.swing.SwingUtilities; 
import javax.swing.border.BevelBorder; 
import javax.swing.border.CompoundBorder; 
import javax.swing.border.EmptyBorder; 

public class GlassPaneTest extends JFrame { 

    public static class PropertiesButton extends JButton { 

     /** The currently displayed glass pane. 
     * Should be null if nothing is displayed. */ 
     private JPanel theGlassPane; 
     /** Root pane of connected window. Used to attach the glass pane. */ 
     private final JRootPane rootPane; 
     /** Content pane of the connected window. Used for coordinate calculation. */ 
     private final Container contentPane; 
     /* A "key hook" that allows us to intercept any key press when the glass pane is visible, 
     * so we can hide the glass pane. */ 
     private final KeyEventDispatcher keyHook = new KeyEventDispatcher() { 

      public boolean dispatchKeyEvent(KeyEvent e) { 
       if (theGlassPane == null || e.getID() != KeyEvent.KEY_PRESSED) { 
        return false; 
       } 
       setGlassPaneVisible(false); 
       return true; 
      } 
     }; 

     public PropertiesButton(Window parentWindow) { 
      if (!(parentWindow instanceof JFrame || parentWindow instanceof JDialog)) { 
       throw new IllegalArgumentException("only JFrame or JDialog instances are accepted"); 
      } 
      if (parentWindow instanceof JDialog) { 
       rootPane = ((JDialog) parentWindow).getRootPane(); 
       contentPane = ((JDialog) parentWindow).getContentPane(); 
      } else { 
       rootPane = ((JFrame) parentWindow).getRootPane(); 
       contentPane = ((JFrame) parentWindow).getContentPane(); 
      } 

      addActionListener(new ActionListener() { 

       public void actionPerformed(ActionEvent e) { 
        setGlassPaneVisible(theGlassPane == null); 
       } 
      }); 
     } 

     private JPanel createGlassPane() { 
      // Create the glass pane as a transparent, layout-less panel 
      // (to allow absolute positioning), covering the whole content pane. 
      // Make it go away on any mouse press. 
      JPanel gp = new JPanel(); 
      gp = new JPanel(); 
      gp.setOpaque(false); 
      gp.setLayout(null); 
      gp.setBounds(contentPane.getBounds()); 
      gp.addMouseListener(new MouseAdapter() { 

       @Override 
       public void mousePressed(MouseEvent e) { 
        setGlassPaneVisible(false); 
       } 
      }); 

      // Create the "popup" - a component displayed on the transparent 
      // overlay. 
      JPanel popup = new JPanel(); 
      popup.setBorder(new CompoundBorder(
        new BevelBorder(BevelBorder.RAISED), 
        new EmptyBorder(5, 5, 5, 5))); 
      popup.setBackground(Color.YELLOW); 
      popup.add(new JLabel("Some info for \"" + getText() + "\".")); 
      // Needed since the glass pane has no layout manager. 
      popup.setSize(popup.getPreferredSize()); 

      // Position the popup just above the button that triggered 
      // its visibility. 
      Point buttonLocationInContentPane = SwingUtilities.convertPoint(this, 0, 0, contentPane); 
      int x = buttonLocationInContentPane.x; 
      int horizOverlap = x + popup.getWidth() - contentPane.getWidth(); 
      if (horizOverlap > 0) { 
       x -= horizOverlap; 
      } 
      int y = buttonLocationInContentPane.y - popup.getHeight(); 
      if (y < 0) { 
       y = 0; 
      } 
      popup.setLocation(x, y); 

      gp.add(popup); 

      return gp; 
     } 

     private void setGlassPaneVisible(boolean visible) { 
      KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); 
      if (visible) { 
       theGlassPane = createGlassPane(); 
       rootPane.setGlassPane(theGlassPane); 
       theGlassPane.setVisible(true); 
       kfm.addKeyEventDispatcher(keyHook); 
      } else { 
       theGlassPane.setVisible(false); 
       kfm.removeKeyEventDispatcher(keyHook); 
       theGlassPane = null; 
      } 

     } 
    } 

    // A simple test program 
    public GlassPaneTest() { 
     setTitle("A glass pane example"); 
     setLayout(new FlowLayout(FlowLayout.CENTER)); 
     for (int i = 1; i <= 10; ++i) { 
      PropertiesButton pb = new PropertiesButton(this); 
      pb.setText("Properties button " + i); 
      add(pb); 
     } 
     setSize(400, 300); 
    } 

    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 

      public void run() { 
       JFrame f = new GlassPaneTest(); 
       f.setDefaultCloseOperation(EXIT_ON_CLOSE); 
       f.setVisible(true); 
      } 
     }); 

    } 
} 
0

puedo sugerir que en lugar de utilizar un WindowListener, se utiliza un WindowStateListener y, a continuación, comprobar la WindowEvent aprobada en tanto WINDOW_DEACTIVATED y WINDOW_LOST_FOCUS. Esto debería cubrir la posibilidad de que el diálogo esté detrás de la ventana principal.

0

Tenía curiosidad, así que decidí probar este problema. Como se enteró, es más difícil de lo que parece porque cualquier código que escriba en el WindowAdapter, este siempre se disparará antes de la ventana primaria y el botón se enfocará, y por lo tanto, el diálogo ya estará cerrado.

Creo que la solución es asegurarse de que el botón esté desactivado hasta que el diálogo se haya cerrado por un tiempo, y eso es lo que he hecho. Desactivo el botón mientras el diálogo se está cerrando. El segundo desafío fue encontrar una manera de habilitar el botón nuevamente, pero solo después de que se haya procesado el evento del mouse hacia abajo; de lo contrario, se presiona el botón y el cuadro de diálogo se volverá a mostrar inmediatamente.

Mi primera solución usó un javax.swing.Timer que se activó una vez que el diálogo perdió foco, con un retraso de 100ms, que luego volvería a habilitar el botón. Esto funcionó porque la pequeña demora de tiempo aseguraba que el botón no estaba habilitado hasta que el evento de clic ya había pasado al botón, y dado que el botón todavía estaba desactivado, no se hizo clic.

La segunda solución, que publico aquí, es mejor, porque no se requieren temporizadores ni demoras. Simplemente envuelvo la llamada para volver a habilitar el botón en SwingUtilities.invokeLater, que empuja este evento al FIN de la cola del evento. En este punto, el evento del mouse hacia abajo ya está en la cola, por lo que la acción para habilitar el botón se garantiza después de esto, ya que Swing procesó los eventos estrictamente en orden. La desactivación y la activación del botón se producen de forma tan repentina que es poco probable que lo vea suceder, pero es suficiente para evitar que haga clic en el botón hasta que desaparezca el diálogo.

El código de ejemplo tiene un método principal que pone el botón en JFrame. Puede abrir el cuadro de diálogo y luego hacer que pierda el foco haciendo clic en el botón o haciendo clic en la barra de título de la ventana. Refactoreé su código original para que el botón solo sea responsable de mostrar y ocultar el cuadro de diálogo especificado, de modo que pueda volver a utilizarlo para mostrar el cuadro de diálogo que desee.

import java.awt.Component; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.awt.event.WindowAdapter; 
import java.awt.event.WindowEvent; 

import javax.swing.JButton; 
import javax.swing.JCheckBox; 
import javax.swing.JDialog; 
import javax.swing.JFrame; 
import javax.swing.SwingUtilities; 
import javax.swing.WindowConstants; 

public class QuickDialogButton extends JButton { 

    private final JDialog dialog; 

    public QuickDialogButton(String label, JDialog d) { 
     super(label); 

     dialog = d; 

     dialog.addWindowListener(new WindowAdapter() { 
      public void windowDeactivated(WindowEvent e) { 
       // Button will be disabled when we return. 
       setEnabled(false); 
       dialog.setVisible(false); 
       // Button will be enabled again when all other events on the queue have finished. 
       SwingUtilities.invokeLater(new Runnable() { 
        @Override 
        public void run() { 
         setEnabled(true); 
        } 
       }); 
      } 
     }); 

     addActionListener(new ActionListener() { 
      public void actionPerformed(ActionEvent e) { 
       Component c = (Component) e.getSource(); 
       dialog.setLocation(c.getLocationOnScreen().x, c.getLocationOnScreen().y + c.getHeight()); 
       dialog.setVisible(true); 
      } 
     }); 
    } 

    public static void main(String[] args) { 
     JFrame f = new JFrame("Parent Window"); 
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 

     JDialog d = new JDialog(f, "Child Dialog"); 
     d.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); 
     d.add(new JCheckBox("Something")); 
     d.setUndecorated(true); 
     d.pack(); 

     f.add(new QuickDialogButton("Button", d)); 
     f.pack(); 
     f.setLocationRelativeTo(null); 
     f.setVisible(true); 
    } 

} 
0

Aquí hay una solución de trabajo. Básicamente, queremos evitar mostrar la ventana si se acaba de cerrar haciendo clic en el botón que también desactiva y oculta la ventana. El mouseDown y el windowDeactivated se procesan en el mismo evento de entrada, aunque los tiempos del evento difieren ligeramente. El tiempo de acción puede ser mucho más tarde ya que se genera en el mouseUp. Usar WindowAdapter es conveniente para WindowListener y usar la anotación @Override es bueno para evitar que no funcione el material debido a un error tipográfico.


public class PropertiesButton extends JButton { 

    private JDialog theWindow; 
    private long deactivateEventTime = System.currentTimeMillis(); 
    private long mouseDownTime; 

    public PropertiesButton(String text, final Frame launcher) { 
     super(text); 

     theWindow = new JDialog(); 
     theWindow.getContentPane().add(new JLabel("Properties")); 
     theWindow.pack(); 
// theWindow.setUndecorated(true); 
     theWindow.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 
// theWindow.add(new JMenuCheckBoxItem("Something")); 
     theWindow.addWindowListener(new WindowAdapter() { 
      // just an example, need to implement other methods 
      @Override 
      public void windowDeactivated(WindowEvent e) { 
       deactivateEventTime = EventQueue.getMostRecentEventTime(); 
       theWindow.setVisible(false); 
      } 
     }); 
     this.addActionListener(new ActionListener() { 
      public void actionPerformed(ActionEvent e) { 
       boolean alsoDeactivated = Math.abs(deactivateEventTime - mouseDownTime) < 100; 
       if (theWindow.isVisible()) { 
        theWindow.setVisible(false); 
       } else if (!alsoDeactivated) { 
//     JButton btn = (JButton)e.getSource(); 
//     theWindow.setLocation(btn.getLocationOnScreen().x,btn.getLocationOnScreen().x+50); 
        theWindow.setVisible(true); 
       } 
      } 
     }); 
     theWindow.setVisible(false); 
    } 
    public void processMouseEvent(MouseEvent event) { 
     if (event.getID() == MouseEvent.MOUSE_PRESSED) { 
      mouseDownTime = event.getWhen(); 
     } 
     super.processMouseEvent(event); 
    } 
} 
Cuestiones relacionadas