GlassPaneとブロッカ

GlassPaneとはSwingのフレーム上を構成するいくつかのペイン(レイヤ)のうち、一番上部にあるペインを指す。ここは名前通り「ガラス区画」と呼ばれており、デフォルトの実装では不可視のパネルとして実装されている。

How to Use Root Panes - The Java Tutorials

今回のネタはこのGlassPaneが常時一番上のレイヤとして構成されるのを利用して、処理の実行中に一切のユーザ入力をブロックするために使う"ブロッカ"をを作ることだ。

業務アプリケーションを開発する際に、処理の実行中のマウスイベントやキーボードイベントをブロックするのは誤動作を防止したり、意図的に重複した処理の起動を防ぐのに非常に重要な非業務機能である。WebアプリケーションであればJavascriptで割と簡易に実装しているのを良く見ることができるだろう。.NET WindowForms用のフレームワークではForm上の全てのコントロールのEnabledプロパティをfalseにすることを実現していたが、Java - Swingではこの機能はガラス区画を利用するだけで出来るらしいので、試しに簡単なガラス区画によるブロッカを実装した。

public class Blocker extends JComponent implements MouseInputListener {
    private Cursor oldCursor;
    private KeyEventDispatcher dispatcher;

    public Blocker() {
        super();
        this.addMouseListener(this);
        this.addMouseMotionListener(this);
        this.dispatcher = new KeyEventDispatcher() {
            public boolean dispatchKeyEvent(KeyEvent ke) {
                return true;
        }};
    }
    public void block() {
        KeyboardFocusManager.getCurrentKeyboardFocusManager()
             .addKeyEventDispatcher(this.dispatcher);
        this.oldCursor = this.getCursor();
        this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        this.setVisible(true);
    }
    public void unBlock() {
        this.setCursor(this.oldCursor);
        KeyboardFocusManager.getCurrentKeyboardFocusManager()
            .removeKeyEventDispatcher(this.dispatcher);
        this.setVisible(false);
    }
    @Override
    protected void paintComponent(final Graphics g) {
        Graphics2D g2 = (Graphics2D) g; 
        g2.setColor(Color.white); 
        g2.setComposite(
                AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f)); 
        g2.fillRect(0, 0, this.getWidth(), this.getHeight());
    }
    @Override
    public void mouseClicked(MouseEvent e) { e.consume(); }
    @Override
    public void mouseEntered(MouseEvent e) { e.consume(); }
    @Override
    public void mouseExited(MouseEvent e) { e.consume(); }
    @Override
    public void mousePressed(MouseEvent e) { e.consume(); }
    @Override
    public void mouseReleased(MouseEvent e) { e.consume(); }
    @Override
    public void mouseDragged(MouseEvent e) { e.consume(); }
    @Override
    public void mouseMoved(MouseEvent e) { e.consume(); }
}

同じ目的の似たような実装はいろいろな所にあるので詳しい解説はしないが、重要なのはマウスリスナの空の実装を用意してマウスイベントを無効にしていることと、KeyboardFocusManagerのKeyEventDispatcherの実装を差し替えてキーイベントを無効にしていることである。(これをしないとGlassPane表示時もマウスによるクリックやキーボード入力が出来てしまう)
GlassPane自体はブロック中はカーソルを待機用カーソルに変更し、半透明で矩形を塗りつぶすことで現在入力操作がブロックされていることを表す(このとき、何かアイコンを表示したり、文字を描画しても良いだろう)。

使い方は簡単であり、対象のフレームのGlassPaneを入れ替えて、ブロッカのメソッドを使うだけだ。

JFrame1 jframe = new JFrame1();
Blocker blocker = new Blocker();
jframe.setGlassPane(blocker);
〜

try {
    blocker.block();
    //何か時間がかかる処理//
} finally {
    blocker.unBlock();
}

前回のエントリでも書いたが、ブロック中の処理がEDT(Event Dispatch Thread)で実行されているとGlassPaneが描画されないことが考えられるので、ゆめゆめ注意が必要である。