概要

JInternalFrameModalにして、他のJInternalFrameなどを操作できないようにブロックします。

サンプルコード

//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);
  }
}
view all
//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);
  }
}

解説

  • Alt+1: JOptionPane.showInternalMessageDialogメソッドを使用して、簡単なメッセージを表示するModalDialogJDesktopPane内に表示
    • JButtonのマウスクリックは無効になるが、Mnemonicが無効にならない
      • Alt+Bでボタンを押すことが出来てしまう
      • Mnemonicを設定したコンポーネントはsetEnabled(false)とする必要がある
    • MnemonicJMenuに設定しているとsetEnabled(false)としても、Altキーに反応する
    • このInternalMessageDialogを閉じない限り、アプリケーションをAlt+F4などで閉じることは出来ない
    • InternalMessageDialogのシステムメニュー(左上のアイコンをクリックすると表示される)がマウスで操作不可能
    • JToolTipは正常
      • showInternalMessageDialog(...)メソッド内で、pane.putClientProperty(PopupFactory_FORCE_HEAVYWEIGHT_POPUP, Boolean.TRUE)(JDK 1.6.0の場合のKeyは、PopupFactory.forceHeavyWeightPopupKey) されているため、JComboBoxなどのドロップダウンメニューも正常
  • Alt+2: Alt+1と同様にJOptionPane.showInternalMessageDialogメソッドを使用し、かつ半透明なGlassPaneJLayeredPane.MODAL_LAYERに追加
    • 動作、制限などは、Alt+2InternalMessageDialogと同じ
    • JDesktopPane内にマスクが掛かる
  • Alt+3: JFrameに半透明なGlassPaneを追加し、そこにJInternalFrameを追加することでModalに設定
    • JFrame内全体(JMenuBarなども含む)にマスクが掛かる
    • InternalMessageDialogのシステムメニューが自身のレイヤーより奥に表示されるため、アイコン(JLabel)をクリックしても反応しないようにリスナーを除去
    • JComboBoxInternalMessageDialogに追加すると、そのドロップダウンメニューが裏に表示される
    • このInternalMessageDialogを開いていても、アプリケーションをAlt+F4などで閉じることが出来てしまう

  • Alt+3の方法で、InternalOptionDialogJComboBoxを追加する場合、ドロップダウンメニューを正しく表示させるには、リフレクションを使ってClientPropertyを設定するしかない?
    • JInternalFrame#putClientProperty(PopupFactory_FORCE_HEAVYWEIGHT_POPUP, Boolean.TRUE)とすれば、システムメニューも正常に表示されるが、JOptionPane.showInternalXXXDialogでは、なぜかJOptionPaneに設定するようになっている(JInternalFrameは使い回ししているから?)
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);

Alexander Potochkin's Blog: Disabling Swing Containers, the final solution?を参考に(paintではなく、printが使用されている)して、GlassPaneを以下のように修正すると、上記のサンプルのAlt+3(Alt+2の場合は、描画が乱れる)は、Mnemonicもうまくブロックできるようです。

  • JFrameのメニューバーのMnemonicもブロックできる
    • JRootPaneから取得したLayeredPaneが非表示なので、その子コンポーネント(JMenuBarContentPaneなど)のキーイベントがすべて無効になる
    • JRootPane (Java Platform SE 6)の図にあるように、GlassPaneは、JRootPaneの最上位の子コンポーネントなので、LayeredPaneを画像として表示している
  • JFrameのシステムメニューはブロックできない
  • モーダルにしたJInternalFrameのシステムメニューは表示されない
    • ただし表示されないだけで、クリックしてからカーソル移動やダブルクリックなどが動いてしまう
  • モーダルにしたJInternalFrameの右上の閉じるボタンのJToolTipJDesktopPane内で表示される場合、空のJToolTipが背面に表示される
    • UIManager.put("InternalFrame.titleButtonToolTipsOn", Boolean.FALSE);で非表示にすることが可能
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の場合、Disabled Glass Pane « Java Tips Weblogのようにキー入力を無効にするキーリスナーを追加する方法もあります。

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

参考リンク

コメント

  • JInternalFrameを半透明にすると、同様にGlassPaneUbuntu(GNOME)などで半透明にならない場合があります。 -- aterai
    • Alt+2で開いた場合、JInternalFrameGlassPaneを乗せるのではなく、直接JDesktopPaneJLayeredPane.MODAL_LAYERに追加するように変更しました。 -- aterai
  • メモ: Alexander Potochkin's Blog: Disabling Swing Containers, the final solution?のサンプルでは、Mnemonicもちゃんとブロックできているようなので、「あとで調べる & 参考にする」こと。 -- aterai
  • Mnemonicを数字キー(1, 2, 3)に変更 -- aterai
  • すべてのMnemonicを一時的に無効化したい場合に、UIManager.javaprivate static final String disableMnemonicKey = "swing.disablenavaids";は使えない? 以下のように、KeyboardFocusManager.setCurrentKeyboardFocusManager(...)で、Altキーなどを無視する方法もあるが…、もっと簡単な方法を調査中。 -- aterai
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_DOWN_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);