2010-11-16 21 views
6

Quello che ho chiesto in origine non indicava chiaramente la mia domanda/problema, quindi lo spiegherò meglio. Ho un JButton che imposta un JDialog visibile. JDialog ha un WindowListener che lo imposta su NON visibile sull'evento windowDeactivated(), che viene attivato ogni volta che l'utente fa clic all'esterno della finestra di dialogo. Il pulsante ActionListener controlla se la finestra di dialogo è Visibile, lo nasconde se è vero, lo mostra se è falso.Creare una finestra delle proprietà di controllo, pulsante come JDialog

windowDeactivated() attiverà sempre se si fa clic sul pulsante o meno, a condizione che l'utente faccia clic fuori dalla finestra di dialogo. Il problema che sto avendo è quando l'utente fa clic sul pulsante per chiudere la finestra di dialogo. La finestra di dialogo viene chiusa dal WindowListener e quindi lo ActionListener tenta di visualizzarlo.

Se windowDeactivated() non è setVisible(false), la finestra di dialogo è ancora aperta, ma dietro la finestra padre. Quello che sto chiedendo è come ottenere l'accesso alla posizione del clic all'interno di windowDeactivated(). Se so che l'utente ha fatto clic sul pulsante e windowDeactivated() può ignorare la finestra di dialogo, in modo che il pulsante ActionListener vedrà che è ancora visibile e lo nasconde.

 
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

Non so quale sia la domanda. Sembra che tu l'abbia capito. Sembra giusto (A prima vista) – jjnguy

+0

Quello che ho sopra farà tutto ciò che volevo, tranne quando clicco fuori dalla finestra di dialogo. Fare clic all'esterno chiude la finestra di dialogo, che va bene, ma quando faccio clic sul pulsante per aprire la finestra di dialogo, non si apre la prima volta.Da quanto ho capito, il WindowListener si innesca prima di ActionListener e anche se la finestra di dialogo NON è effettivamente visibile quando si attiva il trigger ActionListener, la chiamata .isVisible() restituisce un valore vero. Quindi, il pulsante sarà .setVisible (false) anche se non è visibile. – Brian

+2

Brian, puoi usare un 'WindowAdapter' invece di un listener di finestre. Quindi devi solo implementare i metodi che desideri. – jjnguy

risposta

0

Si potrebbe provare a utilizzare un JPanel al posto di un un JDialog per l'elenco delle proprietà discesa. Qualcosa di simile a questo:

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); 
    } 

} 

Utilizzando componenti leggeri invece di quelli pesi massimi come la JDialog è sempre preferibile in Swing, e ha effetti indesiderati meno come quello di segnalare. L'unico problema di questo approccio è che la posizione e le dimensioni del pannello potrebbero essere influenzate dal gestore di layout attivo nel genitore.

0

Un modo facile, anche se un po 'hacker, per risolvere questo problema è lasciare che PropertiesButton abbia un flag booleano che indica se dovremmo preoccuparci di gestire l'azione del pulsante successivo. Sposta questo flag se la finestra di dialogo è nascosta a causa di un evento windowDeactivated.

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 
     } 
    }); 

Nota che io non sono al 100% agio con questo trucco: ci possono essere alcuni casi sottile mi mancava in cui l'approccio fallisce.

0

Ampliamento del consiglio di awheel, ho scritto il seguente esempio che utilizza la funzionalità del pannello di vetro di Swing. L'approccio è un po 'complicato, ma non è raro che tu stia provando qualcosa di moderatamente avanzato in Swing.

L'idea è di visualizzare un pannello di copertura trasparente (una lastra di vetro che copre l'intero contenuto della finestra) quando si fa clic sul pulsante e lo si elimina quando l'utente fa clic in qualsiasi punto della finestra o preme un tasto.

In cima a questo pannello di vetro, visualizzo un altro JPanel ("popup") e provo a posizionarlo appena sopra il pulsante che attiva la sua visibilità.

Questo approccio ha una limitazione della soluzione originale basata su finestre di dialogo: qualsiasi cosa venga disegnata sopra il vetro deve rientrare nell'area del contenuto del frame (dopo tutto, non è una finestra). Questo è il motivo per cui nel codice riportato di seguito vengono apportate alcune modifiche per garantire che le coordinate del popup < siano all'interno dei limiti del riquadro del contenuto (altrimenti la JLabel verrebbe semplicemente ritagliata ai bordi della cornice).

Ha anche la limitazione che le pressioni del mouse catturate dal vetro non sono delegate a nessun componente sottostante. Quindi, se fai clic su un pulsante mentre il pannello di vetro è visibile, il pannello di vetro andrà via ma consumerà anche il clic e il pulsante che pensavi di aver fatto clic non reagirà. È possibile aggirare questo, se lo si desidera, ma poi diventa ancora più disordinato e volevo mantenere il mio esempio relativamente semplice. :-)

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

Posso suggerire che invece di utilizzare un WindowListener, si utilizza un WindowStateListener, e quindi verificare la WindowEvent passata in sia per WINDOW_DEACTIVATED e WINDOW_LOST_FOCUS. Questo dovrebbe coprire la possibilità che la finestra di dialogo sia dietro la finestra principale.

0

Ero curioso quindi ho deciso di provare questo problema. Come hai scoperto, è più difficile di quanto sembri perché qualunque codice tu scrivi nel WindowAdapter, questo verrà sempre attivato prima che la finestra genitore e il pulsante ottengano lo stato attivo, e quindi la finestra di dialogo verrà chiusa già.

Credo che la soluzione sia assicurarsi che il pulsante sia disabilitato fino a quando la finestra di dialogo è stata chiusa per un po ', ed è quello che ho fatto. Disabilito il pulsante mentre la finestra di dialogo si sta chiudendo. La seconda sfida era trovare un modo per abilitare nuovamente il pulsante, ma solo dopo che l'evento del mouse-down è stato elaborato, altrimenti il ​​pulsante verrà cliccato e la finestra di dialogo verrà visualizzata immediatamente.

La mia prima soluzione utilizzava uno javax.swing.Timer che era stato impostato per attivarsi una volta che la finestra di dialogo aveva perso lo stato attivo, con un ritardo di 100 ms, che avrebbe quindi riattivato il pulsante. Ciò ha funzionato perché il breve ritardo assicurava che il pulsante non fosse abilitato fino a quando l'evento click non era già stato premuto sul pulsante e, poiché il pulsante era ancora disabilitato, non veniva cliccato.

La seconda soluzione, che pubblico qui, è migliore, perché non sono richiesti timer o ritardi. Concludo semplicemente la chiamata per riattivare il pulsante in SwingUtilities.invokeLater, che spinge questo evento alla FINE della coda eventi. A questo punto, l'evento mouse-down è già in coda, quindi l'azione per abilitare il pulsante è garantita in seguito, dal momento che Swing ha gestito gli eventi rigorosamente in ordine. La disattivazione e l'attivazione del pulsante avviene così all'improvviso che è improbabile che ciò accada, ma è sufficiente per impedirti di fare clic sul pulsante fino a quando la finestra di dialogo non è terminata.

Il codice di esempio ha un metodo principale che inserisce il pulsante in un JFrame. È possibile aprire la finestra di dialogo e quindi perdere la messa a fuoco facendo clic sul pulsante o facendo clic sulla barra del titolo della finestra. Ho rifattorizzato il codice originale in modo che il pulsante sia solo responsabile della visualizzazione e della visualizzazione della finestra di dialogo specificata, in modo da poterlo riutilizzare per mostrare qualsiasi finestra di dialogo desiderata.

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

Ecco una soluzione di lavoro. In pratica, vogliamo evitare di mostrare la finestra se è stata appena chiusa facendo clic sul pulsante che disattiva e nasconde anche la finestra. Il mouseDown e la finestraDeactivated vengono entrambi elaborati sullo stesso evento di input, sebbene i tempi degli eventi siano leggermente diversi. Il tempo di azione può essere molto più tardi poiché viene generato su mouseUp. L'utilizzo di WindowAdapter è comodo per WindowListener e l'utilizzo dell'annotazione @Override è utile per evitare di non dover lavorare a causa di un errore di digitazione.


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); 
    } 
}