2010-11-16 11 views
6

To, o co pytałem pierwotnie, nie jednoznacznie określiło moje pytanie/problem, więc wyjaśnię to lepiej. Mam JButton, który ustawia JDialog na widoczny. JDialog ma numer WindowListener, który ustawia go na NIEWIDOCZNY w zdarzeniu windowDeactivated(), które jest wyzwalane za każdym razem, gdy użytkownik kliknie poza oknem dialogowym. Przycisk ActionListener sprawdza, czy okno dialogowe jest widoczne, ukrywa je, jeśli jest prawdziwe, pokazuje je, jeśli są fałszywe.Utwórz okno właściwości kontroli, przycisk sterowany jako JDialog

windowDeactivated() zawsze wyzwala kliknięcie przycisku, o ile użytkownik kliknie poza oknem dialogowym. Problem polegający na tym, że użytkownik kliknie przycisk, aby zamknąć okno dialogowe. Okno zostanie zamknięte przez WindowListener, a następnie ActionListener próbuje je wyświetlić.

Jeśli windowDeactivated() nie ma wartości , to okno dialogowe jest nadal otwarte, ale znajduje się za oknem nadrzędnym. Pytam, jak uzyskać dostęp do lokalizacji kliknięcia wewnątrz windowDeactivated(). Jeśli wiem, że użytkownik kliknął przycisk, a windowDeactivated() może pominąć ukrywanie okna dialogowego, tak że przycisk ActionListener zobaczy, że jest nadal widoczny i ukrywa go.

 
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

Nie jestem pewien, jakie jest pytanie. Wygląda na to, że go rozgryzłeś. Wygląda to dobrze. (Na pierwszy rzut oka) – jjnguy

+0

To, co mam powyżej, zrobi wszystko, co chcę, z wyjątkiem kliknięcia poza oknem dialogowym. Kliknięcie na zewnątrz zamyka okno dialogowe, co jest w porządku, ale kiedy kliknę przycisk, aby otworzyć okno dialogowe, nie otwiera się za pierwszym razem.Z tego co rozumiem, WindowListener wyzwala przed ActionListener i nawet jeśli okno dialogowe NIE jest faktycznie widoczne po wywołaniu ActionListener, wywołanie .isVisible() zwraca wartość true. Tak więc przycisk będzie .setVisible (false), mimo że nie jest widoczny. – Brian

+2

Brian, możesz użyć 'WindowAdapter' zamiast detektora okien. Następnie wystarczy wdrożyć odpowiednie metody. – jjnguy

Odpowiedz

0

Możesz spróbować użyć JPanel zamiast JDialog dla rozwijanej listy właściwości. Coś takiego:

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

} 

Użycie lekkich komponentów zamiast tych gigantów, takich jak JDialog jest zawsze korzystne, Swing, i ma mniej działań niepożądanych, takich jak ta, którą raport. Jedynym problemem w tym podejściu jest to, że menedżer układu aktywny w obiekcie nadrzędnym może mieć wpływ na pozycję i rozmiar panelu.

0

Prostym, choć nieco hackowskim sposobem, który może rozwiązać ten problem, jest ustawienie w polu PropertiesButton flagi boolean, która wskazuje, czy powinniśmy sobie poradzić z operacją następnego przycisku. Odwracamy tę flagę, jeśli okno dialogowe jest ukryte z powodu zdarzenia windowDeactivated.

public PropertiesButton extends JButton { 

    private JDialog theWindow; 
    private boolean ignoreNextAction; 

(wycinek)

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

Należy pamiętać, że nie jestem w 100% komfortowo z tej sztuczki: tam może być jakaś subtelna sprawa Tęskniłem gdzie podejście kończy się niepowodzeniem.

0

Po zapoznaniu się z radą Awheela, napisałem następujący przykład, który wykorzystuje funkcjonalność panelu szklanego Swinga. Podejście jest nieco chaotyczne, ale nie jest to rzadkością, gdy próbujesz czegoś umiarkowanie zaawansowanego w Swing.

Ideą jest wyświetlenie przezroczystego panelu nakładkowego (szyba obejmująca całą zawartość okna) po kliknięciu przycisku i pozbycie się go, gdy użytkownik kliknie w dowolnym miejscu okna lub naciśnie klawisz.

Na górze tej szyby wyświetlam kolejny JPanel ("popup") i próbuję ustawić go tuż nad przyciskiem, który uruchamia jego widoczność.

Podejście to ma jedno ograniczenie, którego oryginalne rozwiązanie dialogowe nie ma: cokolwiek jest rysowane na górze szyby, musi zmieścić się w obszarze zawartości ramki (w końcu to nie jest okno). Dlatego w poniższym kodzie dokonuję pewnych korekt, aby zapewnić, że wyskakujące okienka współrzędne < znajdują się w granicach okna zawartości (w przeciwnym razie JLabel zostałby po prostu przycięty na krawędzi ramki).

Ma również ograniczenie, że naciśnięcia myszy przechwycone przez szybę nie są przekazywane do żadnych podstawowych elementów. Jeśli klikniesz przycisk, gdy zobaczysz szklaną szybę, szklana tafla zniknie, a jednocześnie pochłonie kliknięcie, a przycisk, który myślałeś, że klikniesz, nie zareaguje. Można to obejść, jeśli chce, ale potem robi się jeszcze bardziej nieprzyjemnie i chciałem, aby mój przykład był stosunkowo prosty. :-)

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

mogę sugerować, że zamiast używać WindowListener, użyć WindowStateListener, a następnie przetestować WindowEvent przekazany zarówno WINDOW_DEACTIVATED i WINDOW_LOST_FOCUS. Powinno to obejmować możliwość, że okno dialogowe znajduje się za oknem nadrzędnym.

0

Byłem ciekawy, więc zdecydowałem się spróbować w tym problemie. Jak się dowiedziałeś, jest to trudniejsze, niż się wydaje, ponieważ niezależnie od tego, jaki kod napiszesz w WindowAdapter, zawsze będzie on uruchamiany przed oknem nadrzędnym, a przycisk zostanie skupiony, dlatego okno dialogowe zostanie już zamknięte.

Uważam, że rozwiązaniem jest upewnienie się, że przycisk jest wyłączony, dopóki okno dialogowe nie zostanie zamknięte na chwilę, i to właśnie zrobiłem. Wyłączam przycisk podczas zamykania okna dialogowego. Drugim wyzwaniem było znalezienie sposobu na ponowne włączenie przycisku, ale dopiero po przetworzeniu zdarzenia myszy, w przeciwnym razie przycisk zostanie kliknięty, a okno dialogowe pojawi się natychmiast.

Moje pierwsze rozwiązanie zastosowano javax.swing.Timer który został ustawiony do uruchomienia po raz dialogowym utraty ostrości, z opóźnieniem 100ms, które następnie ponownie włączyć przycisk. To zadziałało, ponieważ małe opóźnienie zapewniło, że przycisk nie został włączony, dopóki zdarzenie kliknięcia już nie dotarło do przycisku, a ponieważ przycisk był nadal wyłączony, nie został kliknięty.

Drugie rozwiązanie, które tutaj publikuję, jest lepsze, ponieważ nie są wymagane żadne opóźnienia ani opóźnienia. Po prostu zawijam połączenie, aby ponownie włączyć przycisk w SwingUtilities.invokeLater, który popycha to zdarzenie do KOŃCA kolejki zdarzeń. W tym momencie zdarzenie typu "mouse-down" już znajduje się w kolejce, więc akcja po włączeniu przycisku jest gwarantowana po tym, ponieważ Swing przetwarzał zdarzenia ściśle w kolejności. Wyłączanie i włączanie przycisku dzieje się tak nagle, że nie jest prawdopodobne, aby to się stało, ale wystarczy, że nie klikniesz przycisku, dopóki okno dialogowe nie zniknie.

Przykładowy kod ma główną metodę, która umieszcza przycisk w postaci JFrame. Możesz otworzyć okno dialogowe, a następnie utracić fokus, klikając przycisk lub klikając pasek tytułu okna. Odtworzono oryginalny kod, aby przycisk był odpowiedzialny tylko za wyświetlenie i ukrycie określonego okna dialogowego, dzięki czemu można go ponownie użyć, aby wyświetlić dowolne okno dialogowe.

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

Oto działające rozwiązanie. Zasadniczo chcemy uniknąć wyświetlania okna, jeśli zostało ono zamknięte, klikając przycisk, który również dezaktywuje i ukrywa okno. MouseDown i windowDeactive są przetwarzane na tym samym zdarzeniu wejściowym, chociaż czasy zdarzeń różnią się nieznacznie. Czas działania może być znacznie dłuższy, ponieważ jest generowany w środowisku myszy. Korzystanie z WindowAdapter jest wygodne dla WindowListener i użycie adnotacji @Override jest dobre, aby uniknąć sytuacji, w której rzeczy działają z powodu literówki.


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); 
    } 
} 
Powiązane problemy