Swing/ModalInternalFrame の変更点
- 追加された行はこの色です。
- 削除された行はこの色です。
- Swing/ModalInternalFrame へ行く。
- Swing/ModalInternalFrame の差分を削除
--- category: swing folder: ModalInternalFrame title: JInternalFrameをModalにする tags: [JInternalFrame, GlassPane, Mnemonic, JDesktopPane, JToolTip, JLayeredPane] author: aterai pubdate: 2007-10-15T13:16:07+09:00 description: JInternalFrameをModalにして、他のJInternalFrameなどを操作できないようにブロックします。 image: https://lh6.googleusercontent.com/_9Z4BYR88imo/TQTP9wW-lJI/AAAAAAAAAe0/xQ9vJrX3MuQ/s800/ModalInternalFrame.png hreflang: href: https://java-swing-tips.blogspot.com/2008/10/modal-internal-frame.html lang: en --- * 概要 [#summary] `JInternalFrame`を`Modal`にして、他の`JInternalFrame`などを操作できないようにブロックします。 #download(https://lh6.googleusercontent.com/_9Z4BYR88imo/TQTP9wW-lJI/AAAAAAAAAe0/xQ9vJrX3MuQ/s800/ModalInternalFrame.png) * サンプルコード [#sourcecode] #code(link){{ // menuItem.setMnemonic(KeyEvent.VK_1); class ModalInternalFrameAction1 extends AbstractAction { public ModalInternalFrameAction1(String label) { super(label); } @Override public void actionPerformed(ActionEvent e) { setJMenuEnabled(false); JOptionPane.showInternalMessageDialog( desktop, "information", "modal1", JOptionPane.INFORMATION_MESSAGE); setJMenuEnabled(true); } } // menuItem.setMnemonic(KeyEvent.VK_2); class ModalInternalFrameAction2 extends AbstractAction { private final JPanel glass = new MyGlassPane(); public ModalInternalFrameAction2(String label) { super(label); Rectangle screen = frame.getGraphicsConfiguration().getBounds(); glass.setBorder(BorderFactory.createEmptyBorder()); glass.setLocation(0, 0); glass.setSize(screen.width, screen.height); glass.setOpaque(false); glass.setVisible(false); desktop.add(glass, JLayeredPane.MODAL_LAYER); } @Override public void actionPerformed(ActionEvent e) { setJMenuEnabled(false); glass.setVisible(true); JOptionPane.showInternalMessageDialog( desktop, "information", "modal2", JOptionPane.INFORMATION_MESSAGE); glass.setVisible(false); setJMenuEnabled(true); } } // menuItem.setMnemonic(KeyEvent.VK_3); // Creating Modal Internal Frames -- Approach 1 and Approach 2 // http://web.archive.org/web/20090803142839/http://java.sun.com/developer/JDCTechTips/2001/tt1220.html class ModalInternalFrameAction3 extends AbstractAction { private final JPanel glass = new PrintGlassPane(); public ModalInternalFrameAction3(String label) { super(label); glass.setVisible(false); } @Override public void actionPerformed(ActionEvent e) { JOptionPane optionPane = new JOptionPane(); JInternalFrame modal = optionPane.createInternalFrame(desktop, "modal3"); optionPane.setMessage("Hello, World"); optionPane.setMessageType(JOptionPane.INFORMATION_MESSAGE); removeSystemMenuListener(modal); modal.addInternalFrameListener(new InternalFrameAdapter() { @Override public void internalFrameClosed(InternalFrameEvent e) { glass.removeAll(); glass.setVisible(false); } }); glass.add(modal); modal.pack(); getRootPane().setGlassPane(glass); glass.setVisible(true); modal.setVisible(true); } } }} * 解説 [#explanation] - KBD{Alt+1}: `JOptionPane.showInternalMessageDialog`メソッドを使用して簡単なメッセージを表示する`Modal`な`Dialog`を`JDesktopPane`内に表示 -- `JButton`のマウスクリックは無効になるが`Mnemonic`が無効にならない --- KBD{Alt+B}でボタンを押すことが出来てしまう --- `Mnemonic`を設定したコンポーネントは`setEnabled(false)`とする必要がある -- `Mnemonic`を`JMenu`に設定していると`setEnabled(false)`としてもKBD{Alt}キーに反応する --- 追記: [https://bugs.openjdk.org/browse/JDK-6921687 JDK-6921687 Mnemonic disappears after repeated attempts to open menu items using mnemonics - Java Bug System]で修正されそう? --- この`InternalMessageDialog`を表示している間は`JMenuBar`をダミーと入れ替えて無効化 --- この`InternalMessageDialog`を表示している間は`JMenuBar`を表示専用の別`JMenuBar`と入れ替えて無効化 -- この`InternalMessageDialog`を閉じない限りアプリケーションをKBD{Alt+F4}などで閉じることは出来ない -- `InternalMessageDialog`のシステムメニュー(左上のアイコンをクリックすると表示される)がマウスで操作不可能 -- `JToolTip`は正常 --- `showInternalMessageDialog(...)`メソッド内で、`pane.putClientProperty(PopupFactory_FORCE_HEAVYWEIGHT_POPUP, Boolean.TRUE)`(`JDK 1.6.0`の場合の`Key`は`PopupFactory.forceHeavyWeightPopupKey`) されているため、`JComboBox`などのドロップダウンメニューも正常 - KBD{Alt+2}: KBD{Alt+1}と同様に`JOptionPane.showInternalMessageDialog`メソッドを使用し、かつ半透明な`GlassPane`を`JLayeredPane.MODAL_LAYER`に追加 -- 動作、制限などはKBD{Alt+2}の`InternalMessageDialog`と同じ -- `JDesktopPane`内にマスクが掛かる - KBD{Alt+3}: `JFrame`に半透明な`GlassPane`を追加しそこに`JInternalFrame`を追加することで`Modal`に設定 -- `JFrame`内全体(`JMenuBar`なども含む)にマスクが掛かる -- `InternalMessageDialog`のシステムメニューが自身のレイヤーより奥に表示されるため、アイコン(`JLabel`)をクリックしても反応しないようにリスナーを除去 -- `JComboBox`を`InternalMessageDialog`に追加するとそのドロップダウンメニューが裏に表示される -- この`InternalMessageDialog`を開いていてもアプリケーションをKBD{Alt+F4}などで閉じることが出来てしまう ---- - KBD{Alt+3}の方法で、`InternalOptionDialog`に`JComboBox`を追加する場合、ドロップダウンメニューを正しく表示させるにはリフレクションを使って`ClientProperty`を設定するしかない? -- `JInternalFrame#putClientProperty(PopupFactory_FORCE_HEAVYWEIGHT_POPUP, Boolean.TRUE)`とすればシステムメニューも正常に表示されるが、`JOptionPane.showInternalXXXDialog`ではなぜか`JOptionPane`に設定するようになっている(`JInternalFrame`は使い回ししているから?) #code{{ JInternalFrame modal = optionPane.createInternalFrame(desktop, "modal3"); JComboBox combo = new JComboBox(new String[] {"Banana", "Apple", "Pear", "Grape"}); combo.setEditable(true); try { Field field; if (System.getProperty("java.version").startsWith("1.6.0")) { Class clazz = Class.forName("javax.swing.PopupFactory"); field = clazz.getDeclaredField("forceHeavyWeightPopupKey"); } else { // 1.7.0, 1.8.0 Class clazz = Class.forName("javax.swing.ClientPropertyKey"); field = clazz.getDeclaredField("PopupFactory_FORCE_HEAVYWEIGHT_POPUP"); } field.setAccessible(true); modal.putClientProperty(field.get(null), Boolean.TRUE); } catch (Exception ex) { ex.printStackTrace(); } optionPane.setMessage(combo); optionPane.setMessageType(JOptionPane.QUESTION_MESSAGE); }} ---- - [http://weblogs.java.net/blog/alexfromsun/archive/2008/01/ Alexander Potochkin's Blog: Disabling Swing Containers, the final solution?]を参考に(`paint`ではなく`print`が使用されている)して`GlassPane`を以下のように修正すると、上記のサンプルのKBD{Alt+3}(KBD{Alt+2}の場合描画が乱れる)は`Mnemonic`もうまくブロックできるようになる - `JFrame`のメニューバーの`Mnemonic`もブロックできる -- `JRootPane`から取得した`LayeredPane`が非表示なので、その子コンポーネント(`JMenuBar`や`ContentPane`など)のキーイベントがすべて無効になる -- [https://docs.oracle.com/javase/jp/8/docs/api/javax/swing/JRootPane.html JRootPane (Java Platform SE 8)]の図にあるように、`GlassPane`は`JRootPane`の最上位の子コンポーネントなので`LayeredPane`を画像として表示している - `JFrame`のシステムメニューはブロックできない - モーダルにした`JInternalFrame`のシステムメニューは表示されない -- ただし表示されないだけで、クリックしてからカーソル移動やダブルクリックなどが動いてしまう - モーダルにした`JInternalFrame`の右上の閉じるボタンの`JToolTip`が`JDesktopPane`内で表示される場合、空の`JToolTip`が背面に表示される -- `UIManager.put("InternalFrame.titleButtonToolTipsOn", Boolean.FALSE);`で非表示になる #code{{ class PrintGlassPane extends JPanel { TexturePaint texture = TextureFactory.createCheckerTexture(4); public PrintGlassPane() { super((LayoutManager) null); setOpaque(false); } @Override public void setVisible(boolean isVisible) { boolean oldVisible = isVisible(); super.setVisible(isVisible); JRootPane rootPane = SwingUtilities.getRootPane(this); if (rootPane != null && isVisible() != oldVisible) { rootPane.getLayeredPane().setVisible(!isVisible); } } @Override protected void paintComponent(Graphics g) { JRootPane rootPane = SwingUtilities.getRootPane(this); if (rootPane != null) { // http://weblogs.java.net/blog/alexfromsun/archive/2008/01/ // it is important to call print() instead of paint() here // because print() doesn't affect the frame's double buffer rootPane.getLayeredPane().print(g); } Graphics2D g2 = (Graphics2D) g; g2.setPaint(texture); g2.fillRect(0, 0, getWidth(), getHeight()); } } }} ---- - `JDK 6`の場合、[https://tips4java.wordpress.com/2008/11/07/disabled-glass-pane/ Disabled Glass Pane « Java Tips Weblog]のようにキー入力を無効にするキーリスナーを追加する方法もある -- `JDK 5`などの場合、`WindowsLookAndFeel`でKBD{Alt}キーを押すとメニューバーにフォーカスが移動してしまう * 参考リンク [#reference] - [https://docs.oracle.com/javase/jp/8/docs/api/javax/swing/JOptionPane.html#showInternalMessageDialog-java.awt.Component-java.lang.Object- JOptionPane#showInternalMessageDialog(...) (Java Platform SE 8)] - [http://web.archive.org/web/20090803142839/http://java.sun.com/developer/JDCTechTips/2001/tt1220.html Creating Modal Internal Frames -- Approach 1 and Approach 2] - [https://docs.oracle.com/javase/tutorial/uiswing/components/rootpane.html How to Use Root Panes] - [http://weblogs.java.net/blog/alexfromsun/archive/2008/01/ Disabling Swing Containers, the final solution?] -- [[Cursorを砂時計に変更>Swing/WaitCursor]] * コメント [#comment] #comment - %%[[JInternalFrameを半透明にする>Swing/TransparentFrame]]と、同様に`GlassPane`が`Ubuntu`(`GNOME`)などで半透明にならない場合があります。%% -- &user(aterai); &new{2007-10-15 (月) 13:16:07}; -- KBD{Alt+2}で開いた場合、`JInternalFrame`に`GlassPane`を乗せるのではなく、直接`JDesktopPane`の`JLayeredPane.MODAL_LAYER`に追加するように変更しました。 -- &user(aterai); &new{2007-10-16 (火) 17:31:50}; - メモ: [http://weblogs.java.net/blog/alexfromsun/archive/2008/01/ Alexander Potochkin's Blog: Disabling Swing Containers, the final solution?]のサンプルでは、`Mnemonic`もちゃんとブロックできているようなので、「あとで調べる & 参考にする」こと。 -- &user(aterai); &new{2008-01-25 (金) 17:28:21}; - `Mnemonic`を数字キー(KBD{1}, KBD{2}, KBD{3})に変更 -- &user(aterai); &new{2008-04-25 (金) 20:51:49}; - すべての`Mnemonic`を一時的に無効化したい場合に、`UIManager.java`の`private static final String disableMnemonicKey = "swing.disablenavaids";`は使えない? 以下のように`KeyboardFocusManager.setCurrentKeyboardFocusManager(...)`を使用してKBD{Alt}キーなどを無視することも可能だが、もっと簡単な方法を調査中。 -- &user(aterai); &new{2013-05-09 (木) 11:46:38}; #code{{ KeyboardFocusManager.setCurrentKeyboardFocusManager(new DefaultKeyboardFocusManager() { @Override public boolean dispatchKeyEvent(KeyEvent e) { if ((e.getModifiersEx() & InputEvent.ALT_DOWN_MASK) != 0) { // System.out.println(e); return false; } return super.dispatchKeyEvent(e); } }); }} - メモ: [https://bugs.openjdk.org/browse/JDK-6921687 JDK-6921687 Mnemonic disappears after repeated attempts to open menu items using mnemonics - Java Bug System] -- &user(aterai); &new{2015-03-24 (火) 21:23:15}; #comment