2010-11-16 9 views
6

Was ich ursprünglich fragte nicht eindeutig meine Frage/Problem, so werde ich es besser erklären. Ich habe eine JButton, die eine JDialog sichtbar macht. Der JDialog verfügt über eine WindowListener, die es auf dem windowDeactivated()-Ereignis, das immer dann ausgelöst wird, wenn der Benutzer außerhalb des Dialogfelds klickt, auf NO sichtbar. Die Schaltfläche ActionListener prüft, ob der Dialog sichtbar ist, blendet ihn aus, wenn er wahr ist, zeigt ihn an, wenn er falsch ist.Erstellen Sie ein inspizierende Eigenschaften Fenster, Button als JDialog

windowDeactivated() wird immer ausgelöst, wenn Sie auf die Schaltfläche klicken oder nicht, solange der Benutzer außerhalb des Dialogfelds klickt. Das Problem, das ich habe, ist, wenn der Benutzer auf die Schaltfläche klickt, um den Dialog zu schließen. Der Dialog wird durch die WindowListener geschlossen und dann versucht die ActionListener, es anzuzeigen.

Wenn windowDeactivated() nicht setVisible(false) ist, dann ist das Dialogfeld noch geöffnet, aber hinter dem übergeordneten Fenster. Was ich verlange, ist, wie man auf den Ort des Klicks innerhalb windowDeactivated() zugreifen kann. Wenn ich weiß, dass der Benutzer auf die Schaltfläche geklickt hat und windowDeactivated() kann das Ausblenden des Dialogfelds überspringen, so dass die Schaltfläche ActionListener wird sehen, dass es immer noch sichtbar ist und es ausblenden.

 
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

Nicht sicher, was die Frage ist. Es sieht so aus, als hättest du es herausgefunden. Das sieht gut aus. (Auf den ersten Blick) – jjnguy

+0

Was ich oben habe, wird alles tun, was ich wollte, außer wenn das Klicken außerhalb des Dialogs. Ein Klick nach außen schließt den Dialog, was gut ist, aber wenn ich auf die Schaltfläche klicke, öffnet sich der Dialog nicht beim ersten Mal.Von dem, was ich verstehe, wird der WindowListener vor dem ActionListener ausgelöst und obwohl der Dialog NICHT wirklich sichtbar ist, wenn der ActionListener auslöst, gibt der .isVisible() - Aufruf ein true zurück. Also wird die Schaltfläche .setVisible (false), obwohl es nicht sichtbar ist. – Brian

+2

Brian, Sie können einen 'WindowAdapter' anstelle eines Fenster-Listeners verwenden. Dann müssen Sie nur die gewünschten Methoden implementieren. – jjnguy

Antwort

0

Sie könnten versuchen, einen JPanel anstelle eines JDialogs für die Dropdown-Eigenschaftenliste zu verwenden. Etwas wie folgt aus:

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

} 

Verwendung von Leichtbau-Komponenten statt Schwergewichte diejenigen wie die JDialog ist immer besser in Swing, und hat weniger unerwünschte Wirkungen wie die, die Sie berichten. Das einzige Problem bei diesem Ansatz besteht darin, dass die Position und Größe des Bereichs durch den Layout-Manager beeinflusst werden kann, der im übergeordneten Element aktiv ist.

0

Ein einfacher, wenn auch etwas hackischer Weg, der dies lösen könnte, ist, dass der PropertiesButton ein boolesches Flag hat, das anzeigt, ob wir uns mit der nächsten Tastenaktion beschäftigen sollten. Wir spiegeln dieses Flag, wenn das Dialogfeld aufgrund eines windowDeactivated-Ereignisses ausgeblendet ist.

public PropertiesButton extends JButton { 

    private JDialog theWindow; 
    private boolean ignoreNextAction; 

(Schnipsel)

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

Bitte beachte, dass ich mich wohl mit diesem Trick nicht zu 100% bin: es kann etwas subtiler Fall sein, die ich verpasst, wo der Ansatz versagt.

0

Erweiternd auf den Rat von awheel, habe ich das folgende Beispiel geschrieben, das die Glasfensterfunktion von Swing verwendet. Der Ansatz ist ein bisschen chaotisch, aber das ist nicht ungewöhnlich, wenn Sie etwas mäßig fortgeschritten in Swing versuchen.

Die Idee besteht darin, ein transparentes Overlay-Panel (eine Glasscheibe, die den gesamten Fensterinhalt abdeckt) anzuzeigen, wenn man auf die Schaltfläche klickt und es entsendet, wenn der Benutzer entweder irgendwo in das Fenster klickt oder eine Taste drückt.

Oben auf dieser Glasscheibe, zeige ich ein anderes JPanel ("Popup") und versuche es direkt über der Schaltfläche zu positionieren, die seine Sichtbarkeit auslöst.

Dieser Ansatz hat eine Einschränkung der ursprünglichen dialogbasierten Lösung: Was auch immer auf der Glasfläche gezeichnet wird, muss in den Inhaltsbereich des Rahmens passen (schließlich ist es kein Fenster). Aus diesem Grund nehme ich im folgenden Code einige Anpassungen vor, um sicherzustellen, dass die Koordinaten des Popups < innerhalb der Grenzen des Inhaltsfensters liegen (andernfalls würde das JLabel einfach an den Kanten des Rahmens abgeschnitten werden).

Es hat auch die Einschränkung, dass die von der Glasscheibe gefangenen Mauspressen nicht an darunter liegende Komponenten delegiert werden. Wenn Sie also auf eine Schaltfläche klicken, während die Glasscheibe sichtbar ist, verschwindet die Glasscheibe, nimmt aber auch den Klick auf und die Schaltfläche, auf die Sie geklickt haben, reagiert nicht. Es ist möglich, dies zu umgehen, wenn man möchte, aber dann wird es noch unordentlicher und ich wollte mein Beispiel relativ einfach halten. :-)

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

Darf ich vorschlagen, dass stattdessen einen Window verwenden Sie einen WindowStateListener verwenden, und testen Sie dann die Window bestanden sowohl in WINDOW_DEACTIVATED und WINDOW_LOST_FOCUS. Dies sollte die Möglichkeit abdecken, dass sich der Dialog hinter dem übergeordneten Fenster befindet.

0

Ich war neugierig, also entschied ich mich, dieses Problem zu versuchen. Wie Sie herausgefunden haben, ist es schwieriger, als es aussieht, denn welcher Code Sie auch schreiben, wird immer vor dem Eltern-Fenster ausgelöst und der Button bekommt den Fokus, und deshalb wird der Dialog schon geschlossen.

Ich glaube, die Lösung besteht darin, sicherzustellen, dass die Schaltfläche deaktiviert ist, bis der Dialog für eine Weile geschlossen wurde, und das habe ich getan. Ich deaktiviere die Schaltfläche, während das Dialogfeld geschlossen wird. Die zweite Herausforderung bestand darin, eine Möglichkeit zu finden, die Schaltfläche erneut zu aktivieren, jedoch nur, nachdem das Mouse-Down-Ereignis verarbeitet wurde. Andernfalls wird die Schaltfläche angeklickt und der Dialog wird sofort wieder angezeigt.

Meine erste Lösung verwendet eine javax.swing.Timer, die einmal ausgelöst wurde, nachdem der Dialog Fokus verlieren, mit einer Verzögerung von 100ms, die dann die Schaltfläche wieder aktivieren würde. Dies funktionierte, da die kleine Zeitverzögerung sicherstellte, dass die Schaltfläche erst aktiviert wurde, nachdem das Klickereignis bereits auf die Schaltfläche gegangen war, und da die Schaltfläche weiterhin deaktiviert war, wurde sie nicht angeklickt.

Die zweite Lösung, die ich hier poste, ist besser, weil keine Timer oder Verzögerungen erforderlich sind. Ich wickle einfach den Anruf ein, um die Schaltfläche in SwingUtilities.invokeLater wieder zu aktivieren, die dieses Ereignis an das Ende der Ereigniswarteschlange verschiebt. An diesem Punkt befindet sich das Mouse-Down-Ereignis bereits in der Warteschlange, sodass die Aktion zum Aktivieren der Schaltfläche danach garantiert erfolgt, da Swing Ereignisse ausschließlich in der Reihenfolge verarbeitet. Die Deaktivierung und Aktivierung der Schaltfläche geschieht so plötzlich, dass es unwahrscheinlich ist, dass sie passiert, aber es reicht aus, Sie davon abzuhalten, auf die Schaltfläche zu klicken, bis der Dialog beendet ist.

Der Beispielcode hat eine Hauptmethode, die die Schaltfläche in eine JFrame setzt. Sie können den Dialog öffnen und dann durch Klicken auf die Schaltfläche oder durch Klicken auf die Titelleiste des Fensters den Fokus verlieren. Ich habe Ihren Originalcode so umstrukturiert, dass die Schaltfläche nur dafür zuständig ist, den angegebenen Dialog ein- und auszublenden, damit Sie ihn wieder verwenden können, um einen beliebigen Dialog anzuzeigen.

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

Hier ist eine funktionierende Lösung. Grundsätzlich wollen wir vermeiden, das Fenster zu zeigen, wenn es gerade geschlossen wurde, indem wir auf den Button klicken, der das Fenster auch deaktiviert und verbirgt. Die Funktionen mouseDown und windowDeactivated werden beide für dasselbe Eingabeereignis verarbeitet, obwohl sich die Ereigniszeiten geringfügig unterscheiden. Die Aktionszeit kann viel später sein, da sie auf dem mouseUp generiert wird. Die Verwendung von WindowAdapter ist praktisch für WindowListener, und die Verwendung der @Override-Annotation ist gut, um zu vermeiden, dass aufgrund eines Tippfehlers etwas nicht funktioniert.


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