• 追加された行はこの色です。
  • 削除された行はこの色です。
TITLE:JInternalFrameをModalにする
#navi(../)
#tags(JInternalFrame, GlassPane, Mnemonic, JDesktopPane, JToolTip, JLayeredPane)
RIGHT:Posted by &author(aterai); at 2007-10-15
* JInternalFrameをModalにする [#le7c13f6]
``JInternalFrame``を``Modal``にして、他の``JInternalFrame``などを操作できないようにブロックします。
---
title: JInternalFrameをModalにする
tags: [JInternalFrame, GlassPane, Mnemonic, JDesktopPane, JToolTip, JLayeredPane]
author: aterai
pubdate: 2007-10-15T13:17:37+09:00
description: JInternalFrameをModalにして、他のJInternalFrameなどを操作できないようにブロックします。
---
* 概要 [#le7c13f6]
`JInternalFrame`を`Modal`にして、他の`JInternalFrame`などを操作できないようにブロックします。

- &jnlp;
- &jar;
- &zip;
#download(https://lh6.googleusercontent.com/_9Z4BYR88imo/TQTP9wW-lJI/AAAAAAAAAe0/xQ9vJrX3MuQ/s800/ModalInternalFrame.png)

#ref(https://lh6.googleusercontent.com/_9Z4BYR88imo/TQTP9wW-lJI/AAAAAAAAAe0/xQ9vJrX3MuQ/s800/ModalInternalFrame.png)

** サンプルコード [#d9d01b15]
* サンプルコード [#d9d01b15]
#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);
  }
}
}}
#code{{
//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);
  }
}
}}
#code{{
//menuItem.setMnemonic(KeyEvent.VK_3);
//Creating Modal Internal Frames -- Approach 1 and Approach 2
//http://java.sun.com/developer/JDCTechTips/2001/tt1220.html
//http://web.archive.org/web/20090803142839/http://java.sun.com/developer/JDCTechTips/2001/tt1220.html
class ModalInternalFrameAction3 extends AbstractAction {
  private final Component orgGlassPane;
  private final JPanel glass = new PrintGlassPane();
  public ModalInternalFrameAction3(String label) {
    super(label);
    orgGlassPane = frame.getGlassPane();
    glass.setVisible(false);
  }
  @Override public void actionPerformed(ActionEvent e) {
    JOptionPane optionPane = new JOptionPane();
    optionPane.setMessage("Hello, World");
    optionPane.setMessageType(JOptionPane.INFORMATION_MESSAGE);
    JInternalFrame modal = optionPane.createInternalFrame(desktop, "modal3");
    removeSystemMenuListener(modal);
    modal.addInternalFrameListener(new InternalFrameAdapter() {
      @Override public void internalFrameClosed(InternalFrameEvent e) {
        glass.setVisible(false);
        frame.setGlassPane(orgGlassPane);
      }
    });
    glass.add(modal);
    //Rectangle screen = desktop.getBounds();
    //modal.setLocation(screen.x + screen.width/2  - modal.getSize().width/2,
    //                  screen.y + screen.height/2 - modal.getSize().height/2);
    frame.setGlassPane(glass);
    glass.setVisible(true);
    modal.setVisible(true);
    try{
      modal.setSelected(true);
    }catch(java.beans.PropertyVetoException ex) {}
  }
}
}}

** 解説 [#v497998b]
- KBD{Alt+1}: ``JOptionPane.showInternalMessageDialog``メソッドを使用して、簡単なメッセージを表示する``Modal``な``Dialog``を``JDesktopPane``内に表示
-- ``JButton``のマウスクリックは無効になるが、``Mnemonic``が無効にならない
* 解説 [#v497998b]
- KBD{Alt+1}: `JOptionPane.showInternalMessageDialog`メソッドを使用して、簡単なメッセージを表示する`Modal`な`Dialog`を`JDesktopPane`内に表示
-- `JButton`のマウスクリックは無効になるが、`Mnemonic`が無効にならない
--- KBD{Alt+B}でボタンを押すことが出来てしまう
--- ``Mnemonic``を設定したコンポーネントは``setEnabled(false)``とする必要がある
-- ``Mnemonic``を``JMenu``に設定していると``setEnabled(false)``としても、KBD{Alt}キーに反応する
--- これは``WindowsLookAndFeel``だけ?
--- この``InternalMessageDialog``を表示している間は、``JMenuBar``をダミーと入れ替えて無効化
-- この``InternalMessageDialog``を閉じない限り、アプリケーションをKBD{Alt+F4}などで閉じることは出来ない
-- ``InternalMessageDialog``のシステムメニュー(左上のアイコンをクリックすると表示される)がマウスで操作不可能
-- ``JToolTip``は正常
--- ``showInternalMessageDialog(...)``メソッド内で、``pane.putClientProperty(PopupFactory_FORCE_HEAVYWEIGHT_POPUP, Boolean.TRUE)``(``JDK 1.6.0``の場合の``Key``は、``PopupFactory.forceHeavyWeightPopupKey``) されているため、``JComboBox``などのドロップダウンメニューも正常
--- `Mnemonic`を設定したコンポーネントは`setEnabled(false)`とする必要がある
-- `Mnemonic`を`JMenu`に設定していると`setEnabled(false)`としても、KBD{Alt}キーに反応する
--- これは`WindowsLookAndFeel`だけ?
--- この`InternalMessageDialog`を表示している間は、`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+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}: `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``は使い回ししているから?)
- 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``もうまくブロックできるようです。
[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``など)のキーイベントがすべて無効になる
-- [http://docs.oracle.com/javase/jp/6/api/javax/swing/JRootPane.html JRootPane (Java Platform SE 6)]の図にあるように、``GlassPane``は、``JRootPane``の最上位の子コンポーネントなので、``LayeredPane``を画像として表示している
- ``JFrame``のシステムメニューはブロックできない
- モーダルにした``JInternalFrame``のシステムメニューは表示されない
- `JFrame`のメニューバーの`Mnemonic`もブロックできる
-- `JRootPane`から取得した`LayeredPane`が非表示なので、その子コンポーネント(`JMenuBar`や`ContentPane`など)のキーイベントがすべて無効になる
-- [http://docs.oracle.com/javase/jp/6/api/javax/swing/JRootPane.html JRootPane (Java Platform SE 6)]の図にあるように、`GlassPane`は、`JRootPane`の最上位の子コンポーネントなので、`LayeredPane`を画像として表示している
- `JFrame`のシステムメニューはブロックできない
- モーダルにした`JInternalFrame`のシステムメニューは表示されない
-- ただし表示されないだけで、クリックしてからカーソル移動やダブルクリックなどが動いてしまう
- モーダルにした``JInternalFrame``の右上の閉じるボタンの``JToolTip``が``JDesktopPane``内で表示される場合、空白になる %%``JDK 6``では``JToolTip``は表示されない%%
- モーダルにした`JInternalFrame`の右上の閉じるボタンの`JToolTip`が`JDesktopPane`内で表示される場合、空白になる %%`JDK 6`では`JToolTip`は表示されない%%

#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 public 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``の場合、[http://tips4java.wordpress.com/2008/11/07/disabled-glass-pane/ Disabled Glass Pane « Java Tips Weblog]のようにキー入力を無効にするキーリスナーを追加する方法もあります。
`JDK 6`の場合、[http://tips4java.wordpress.com/2008/11/07/disabled-glass-pane/ Disabled Glass Pane « Java Tips Weblog]のようにキー入力を無効にするキーリスナーを追加する方法もあります。

この方法は、``JDK 5``などの場合、``WindowsLookAndFeel``で、KBD{Alt}キーを押すとメニューバーにフォーカスが移ることがあります。
この方法は、`JDK 5`などの場合、`WindowsLookAndFeel`で、KBD{Alt}キーを押すとメニューバーにフォーカスが移ることがあります。

** 参考リンク [#n6c5a455]
- [http://java.sun.com/developer/JDCTechTips/2001/tt1220.html Creating Modal Internal Frames -- Approach 1 and Approach 2]
* 参考リンク [#n6c5a455]
- [http://web.archive.org/web/20090803142839/http://java.sun.com/developer/JDCTechTips/2001/tt1220.html Creating Modal Internal Frames -- Approach 1 and Approach 2]
- [http://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]]

** コメント [#h8218ae6]
- %%[[JInternalFrameを半透明にする>Swing/TransparentFrame]]と、同様に``GlassPane``が``Ubuntu``(``GNOME``)などで半透明にならない場合があります。%% -- [[aterai]] &new{2007-10-15 (月) 13:16:07};
-- KBD{Alt+2}で開いた場合、``JInternalFrame``に``GlassPane``を乗せるのではなく、直接``JDesktopPane``の``JLayeredPane.MODAL_LAYER``に追加するように変更しました。 -- [[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``もちゃんとブロックできているようなので、「あとで調べる & 参考にする」こと。 -- [[aterai]] &new{2008-01-25 (金) 17:28:21};
- ``Mnemonic``を数字キー(KBD{1}, KBD{2}, KBD{3})に変更 -- [[aterai]] &new{2008-04-25 (金) 20:51:49};
- すべての``Mnemonic``を一時的に無効化したい場合に、``UIManager.java``の``private static final String disableMnemonicKey = "swing.disablenavaids";``は使えない? 以下のように、``KeyboardFocusManager.setCurrentKeyboardFocusManager(...)``で、KBD{Alt}キーなどを無視する方法もあるが…、もっと簡単な方法を調査中。 -- [[aterai]] &new{2013-05-09 (木) 11:46:38};
* コメント [#h8218ae6]
#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 manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
KeyboardFocusManager.setCurrentKeyboardFocusManager(new DefaultKeyboardFocusManager() {
  @Override public boolean dispatchEvent(AWTEvent e) {
    if(e instanceof KeyEvent) {
      KeyEvent ke = (KeyEvent)e;
      if((ke.getModifiers() & InputEvent.ALT_MASK) != 0) {
        System.out.println("----\n"+ke);
        return false;
      }
    }
    return super.dispatchEvent(e);
  }
});
JComboBox<String> combo = new JComboBox<>(new String[] {"Banana", "Apple", "Pear", "Grape"});
combo.setEditable(true);

JOptionPane.showInternalMessageDialog(
  desktop, combo, "modal1", JOptionPane.INFORMATION_MESSAGE);
KeyboardFocusManager.setCurrentKeyboardFocusManager(manager);
}}

#comment