2010-11-16 1 views
6

Вначале я спросил, не изложил ли я свой вопрос/проблему, поэтому я объясню это лучше. У меня есть JButton, который устанавливает вид JDialog. JDialog имеет WindowListener, который устанавливает его на НЕ видное событие windowDeactivated(), которое запускается в любое время, когда пользователь щелкает за пределами диалогового окна. Кнопка ActionListener проверяет, является ли диалоговое окноVisible, скрывает его, если true, показывает его, если false.Создайте окно проверяющих свойств, кнопка, управляемая как JDialog

windowDeactivated() всегда будет срабатывать, если нажать на кнопку или нет, если пользователь нажимает за диалоговое окно. Проблема, с которой я сталкиваюсь, - это когда пользователь нажимает кнопку, чтобы закрыть диалог. Диалог закрывается WindowListener, а затем ActionListener пытается его отобразить.

Если windowDeactivated() не setVisible(false), то диалог по-прежнему открыт, но за родительским окном. Я спрашиваю, как получить доступ к местоположению щелчка внутри windowDeactivated(). Если я знаю, что пользователь нажал на кнопку, а windowDeactivated() может пропустить скрытие диалога, так что кнопка ActionListener будет видеть, что она все еще видна и скрывает ее.

 
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

Не знаете, в чем вопрос. Похоже, вы поняли это. Это выглядит правильно. (На первый взгляд) – jjnguy

+0

То, что у меня выше, сделает все, что я хочу, за исключением случаев, когда вы нажимаете за пределами диалогового окна. Щелчок снаружи закрывает диалоговое окно, и это нормально, но когда я нажимаю кнопку, чтобы открыть диалог, он не открывается в первый раз.Из того, что я понимаю, WindowListener запускается перед ActionListener, и хотя диалог НЕ отображается на самом деле, когда срабатывает ActionListener, вызов .isVisible() возвращает значение true. Таким образом, кнопка будет .setVisible (false), даже если она не видна. – Brian

+2

Брайан, вы можете использовать «WindowAdapter» вместо прослушивателя окна. Тогда вам нужно только реализовать методы, которые вы хотите. – jjnguy

ответ

0

Вы можете попробовать использовать JPanel вместо в JDialog для списка свойств. Что-то вроде этого:

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

} 

Используя легкие компоненты вместо тяжеловесов те, как JDialog всегда предпочтительнее в свинге, и имеет меньше нежелательные эффекты, как один вы сообщаете. Единственная проблема такого подхода заключается в том, что на позицию и размер панели может влиять менеджер компоновки, активный в родительском.

0

Простой, если несколько хакерский способ, который может решить эту проблему, заключается в том, чтобы позволить PropertiesButton иметь логический флаг, который указывает, следует ли нам беспокоиться об обработке следующего действия кнопки. Мы переворачиваем этот флаг, если диалог скрыт из-за события windowDeactivated.

public PropertiesButton extends JButton { 

    private JDialog theWindow; 
    private boolean ignoreNextAction; 

(надрез)

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

Обратите внимание, что я не на 100% комфортно с этим трюком: может быть какой-то тонкий случай я пропустил, когда подход терпит неудачу.

0

Развернувшись на совете awheel, я написал следующий пример, который использует функциональные возможности стекла Swing. Этот подход немного запутан, но это не редкость, когда вы пытаетесь сделать что-то умеренно продвинутое в Swing.

Идея состоит в том, чтобы отображать прозрачную панель наложения (стеклянную панель, покрывающую все содержимое окна), когда вы нажимаете на кнопку и удаляете ее, когда пользователь либо щелкает в любом месте окна, либо нажимает клавишу.

В верхней части этого стекла я показываю еще один JPanel («всплывающее окно») и пытаюсь установить его над кнопкой, которая вызывает его видимость.

Этот подход имеет одно ограничение, которое вы не используете в оригинальном диалоговом решении: все, что нарисовано поверх стеклянной панели, должно входить в область содержимого фрейма (в конце концов, это не окно). Вот почему я в приведенном ниже коде внес некоторые корректировки, чтобы обеспечить, чтобы всплывающие координаты < находились в пределах области содержимого (иначе JLabel просто обрезается по краям фрейма).

Он также имеет ограничение на то, что мышиные пресса, пойманные стеклянным стеклом, не делегируются никаким базовым компонентам. Поэтому, если вы нажмете кнопку, когда стекло будет видимым, стеклянное стекло исчезнет, ​​а также уничтожит щелчок, и кнопка, которую вы считали нажатой, не будет реагировать. Можно обойти это, если хотите, но потом он становится еще более грязным, и я хотел бы, чтобы мой пример был относительно простым. :-)

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

Могу ли я предположить, что вместо того, чтобы использовать WindowListener, вы используете WindowStateListener, а затем протестировать WindowEvent прошли в течение обоих WINDOW_DEACTIVATED и WINDOW_LOST_FOCUS. Это должно охватывать возможность того, что диалог находится за родительским окном.

0

Мне было любопытно, поэтому я решил попробовать эту проблему. Как вы узнали, это сложнее, чем выглядит, потому что, независимо от кода, который вы пишете в WindowAdapter, это всегда срабатывает перед родительским окном, и кнопка становится фокусом, и поэтому диалог будет закрыт.

Я считаю, что решение состоит в том, чтобы убедиться, что кнопка отключена, пока диалог не был закрыт какое-то время, и это то, что я сделал. Я выключаю кнопку во время закрытия диалогового окна. Вторая задача заключалась в том, чтобы найти способ включения кнопки снова, но только после того, как событие мыши было обработано, в противном случае кнопка будет нажата, и диалоговое окно снова отобразится.

Мое первое решение использовало javax.swing.Timer, которое было настроено на срабатывание один раз после того, как диалог потерял фокус, с задержкой в ​​100 мс, которая затем снова включит кнопку. Это работало, потому что небольшая временная задержка гарантировала, что кнопка не была включена, пока после того, как событие клика уже перешло к кнопке, и поскольку кнопка все еще была отключена, она не была нажата.

Второе решение, которое я публикую здесь, лучше, потому что не требуется никаких таймеров или задержек. Я просто завершаю вызов, чтобы снова включить кнопку в SwingUtilities.invokeLater, что подталкивает это событие к END очереди событий. На этом этапе событие «мыши вниз» уже находится в очереди, поэтому после этого действие для включения кнопки гарантировано произойдет, поскольку Swing обрабатывает события строго по порядку. Отключение и включение кнопки происходит так внезапно, что вы вряд ли увидите, что это произойдет, но этого достаточно, чтобы вы не нажали кнопку до тех пор, пока диалог не исчезнет.

Код примера имеет основной метод, который ставит кнопку в JFrame. Вы можете открыть диалог, а затем потерять фокус, нажав кнопку или щелкнув строку заголовка окна. Я реорганизовал ваш исходный код, чтобы кнопка отвечала только за отображение и скрытие указанного диалога, поэтому вы можете повторно использовать его, чтобы показать любой диалог, который вы хотите.

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

Это рабочее решение. В основном мы хотим не показывать окно, если оно было просто закрыто, нажав кнопку, которая также деактивирует и скрывает окно. MouseDown и windowDeactivated обрабатываются на одном и том же входном событии, хотя время события немного отличается. Время действия может быть намного позже, так как оно генерируется на mouseUp. Использование WindowAdapter удобно для WindowListener, и использование аннотации @Override является хорошим, чтобы избежать ненужной работы из-за опечатки.


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