• 追加された行はこの色です。
  • 削除された行はこの色です。
TITLE:Cursorを砂時計に変更
#navi(../)
RIGHT:Posted by [[aterai]] at 2004-06-07
#tags()
RIGHT:Posted by &author(aterai); at 2004-06-07
*Cursorを砂時計に変更 [#w760af9f]
処理中、マウスカーソルを砂時計に変更します。

-&jnlp;
-&jar;
-&zip;

//#screenshot
#ref(http://lh5.ggpht.com/_9Z4BYR88imo/TQTWfYWDbsI/AAAAAAAAApU/rldJwQuVm-8/s800/WaitCursor.png)

**サンプルコード [#r111147b]
#code{{
#code(link){{
frame.setGlassPane(new LockingGlassPane());
frame.getGlassPane().setVisible(false);
button.addActionListener(new ActionListener() {
  @Override public void actionPerformed(ActionEvent e) {
    frame.getGlassPane().setVisible(true);
    button.setEnabled(false);
    new SwingWorker() {
      @Override public Object doInBackground() {
        dummyLongTask();
        return "Done";
      }
      @Override public void done() {
        frame.getGlassPane().setVisible(false);
        button.setEnabled(true);
      }
  }.execute();
}
});
}}
//frame.setGlassPane(new LockingGlassPane());
//frame.getGlassPane().setVisible(false);
//button.addActionListener(new ActionListener() {
//  @Override public void actionPerformed(ActionEvent e) {
//    //System.out.println("actionPerformed: " + EventQueue.isDispatchThread());
//    frame.getGlassPane().setVisible(true);
//    button.setEnabled(false);
//    Thread t = new Thread() {
//      @Override public void run() {
//        //System.out.println("Thread: " + EventQueue.isDispatchThread());
//        dummyLongTask();
//        EventQueue.invokeLater(new Runnable() {
//          @Override public void run() {
//            //System.out.println("invokeLater: " + EventQueue.isDispatchThread());
//            frame.getGlassPane().setVisible(false);
//            button.setEnabled(true);
//          }
//        });
//      }
//    };
//    //t.setPriority(Thread.MIN_PRIORITY);
//    t.start();
//  }
//});

#code{{
class LockingGlassPane extends JComponent {
  public LockingGlassPane() {
    setOpaque(false);
    setFocusTraversalPolicy(new DefaultFocusTraversalPolicy() {
      @Override public boolean accept(Component c) {return false;}
    });
    addKeyListener(new KeyAdapter() {});
    addMouseListener(new MouseAdapter() {});
    requestFocusInWindow();
    setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
  }
  @Override public void setVisible(boolean flag) {
    super.setVisible(flag);
    setFocusTraversalPolicyProvider(flag);
  }
}
}}

**解説 [#a497267c]
上記のサンプルでは、カーソルを砂時計に変更し、なにもしないマウスリスナーなどを設定したGlassPaneをJFrame#setGlassPane()メソッドでフレームに追加しています。

スタートボタンがクリックされて処理が継続している間は、このGlassPaneが有効になり、マウス、キー、フォーカス移動などのイベントが、すべてGlassPaneに奪われるため、フレーム内のコンポーネントをアクセス不可にすることが出来ます。

このため、サンプルにあるsetEnabled(true)なJTextFieldの上にマウスポインタを移動しても、処理中はカーソルアイコンは砂時計のまま変化しません。

----
タブキーなどによるフォーカス移動を禁止する場合は、GlassPaneに以下のような設定を行います。

-どのコンポーネントにもフォーカス移動できないFocusTraversalPolicyを設定する

#code{{
setFocusTraversalPolicy(new DefaultFocusTraversalPolicy() {
  @Override public boolean accept(Component c) {return false;}
});
}}

-または、TraversalKeysを空にする
--参考:[http://forums.sun.com/thread.jspa?threadID=791415 Swing - How to display "Loading data..." to the user]

#code{{
Set<AWTKeyStroke> s = Collections.emptySet();
setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, s);
setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, s);
}}

----
Mnemonic なども禁止したい場合は、以下のようなGlassPaneを使用する方法があります(参考:[http://weblogs.java.net/blog/alexfromsun/archive/2008/01/ Disabling Swing Containers, the final solution?])。
#code{{
class LockingGlassPane extends JComponent {
  public LockingGlassPane() {
    setOpaque(false);
    super.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
  }
  @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);
    }
    super.paintComponent(g);
  }
}
}}

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

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

----
JDK 7 の場合、JLayerなどを使用することで、特定のコンポーネントだけ入力不可にしてカーソルを砂時計にするといった設定が簡単に出来るようになっています。
#code{{
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import javax.swing.*;
import javax.swing.plaf.LayerUI;
 
public class DisableInputLayerUITest {
  public JComponent makeUI() {
    final JPanel p = new JPanel();
    final DisableInputLayerUI layerUI = new DisableInputLayerUI();
    final JLayer<JPanel> jlayer = new JLayer<JPanel>(p, layerUI);
    final Timer stopper = new Timer(4000, new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        layerUI.stop();
      }
    });
    p.add(new JCheckBox());
    p.add(new JTextField(10));
    p.add(new JButton(new AbstractAction("button") {
      public void actionPerformed(ActionEvent e) {
        layerUI.start();
        if (!stopper.isRunning()) {
          stopper.start();
        }
      }
    }));
    stopper.setRepeats(false);
    return jlayer;
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() { createAndShowGUI(); }
    });
  }
  public static void createAndShowGUI() {
    JComponent c = new DisableInputLayerUITest().makeUI();
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.getContentPane().add(c, BorderLayout.NORTH);
    f.getContentPane().add(new JScrollPane(new JTextArea()));
    f.setSize(320, 240);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
  }
}
-[[JLayerで指定したコンポーネントへの入力を禁止>Swing/DisableInputLayer]] に移動

class DisableInputLayerUI extends LayerUI<JPanel> {
  private boolean isRunning = false;
  @Override public void paint(Graphics g, JComponent c) {
    super.paint(g, c);
    if(!isRunning) return;
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .5f));
    g2.setPaint(Color.GRAY);
    g2.fillRect(0, 0, c.getWidth(), c.getHeight());
    g2.dispose();
  }
  @Override public void installUI(JComponent c) {
    super.installUI(c);
    JLayer jlayer = (JLayer)c;
    jlayer.getGlassPane().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
    jlayer.setLayerEventMask(
      AWTEvent.MOUSE_EVENT_MASK |
      AWTEvent.MOUSE_MOTION_EVENT_MASK |
      AWTEvent.KEY_EVENT_MASK);
  }
  @Override public void uninstallUI(JComponent c) {
    JLayer jlayer = (JLayer)c;
    jlayer.setLayerEventMask(0);
    super.uninstallUI(c);
  }
  @Override public void eventDispatched(AWTEvent e, JLayer l) {
    if(isRunning && e instanceof InputEvent) {
        ((InputEvent)e).consume();
    }
  }
  private static final String CMD_REPAINT = "repaint";
  public void start() {
    if (isRunning) return;
    isRunning = true;
    firePropertyChange(CMD_REPAINT,false,true);
  }
  public void stop() {
    isRunning = false;
    firePropertyChange(CMD_REPAINT,true,false);
  }
  @Override public void applyPropertyChange(PropertyChangeEvent pce, JLayer l) {
    String cmd = pce.getPropertyName();
    if(CMD_REPAINT.equals(cmd)) {
      l.getGlassPane().setVisible((Boolean)pce.getNewValue());
      l.repaint();
    }
  }
}
}}


**参考リンク [#xaea8879]
-[http://forums.sun.com/thread.jspa?threadID=791415 Swing - How to display "Loading data..." to the user]
-[http://weblogs.java.net/blog/alexfromsun/archive/2008/01/ Disabling Swing Containers, the final solution?]
--[[JInternalFrameをModalにする>Swing/ModalInternalFrame]]
-[http://tips4java.wordpress.com/2008/11/07/disabled-glass-pane/ Disabled Glass Pane « Java Tips Weblog]

**コメント [#h83774c4]
-Tabキーで状態遷移しないようにするため、なにもしないFocusTraversalPolicyを追加しました。 -- [[aterai]] &new{2005-04-18 (月) 10:51:25};
- 相当悩みました。JDialog だと同じことができないのは何でなんでしょうねぇ。。。 -- [[おれ]] &new{2006-05-17 (水) 16:33:12};
-- カーソルが変わらないのでしょうか? それともコンパイルエラーが出るとかでしょうか? -- [[aterai]] &new{2006-05-17 (水) 17:59:14};
- 申し訳ない。カーソルが変わらないのだけれど、1.5系でコンパイルするとだめみたい。同じソースでも1.4系でコンパイルするとちゃんと変わる。1.5でのバグかな。。。 -- [[おれ]] &new{2006-05-18 (木) 12:58:11};
-- 追記。JDialog のコンストラクタに null を指定しているとこうなるようです。オーナフレームを指定してあげたら、1.5でもきちんと出ました。お騒がせしました。 -- [[おれ]]
-- なるほど、new JDialog((Frame)null);で試してみるとカーソルが変わらないですね。情報どうもでした。 -- [[aterai]] &new{2006-05-18 (木) 21:45:15};
- DefaultFocusTraversalPolicy を使うように変更しました。 -- [[aterai]] &new{2007-07-03 (火) 16:39:12};
- GlassPaneで、FocusTraversalPolicyを使わず、print を使ってMnemonicなどをブロックするように変更しました。 -- [[aterai]] &new{2008-04-15 (火) 17:14:09};
- SwingWorkerを使うように変更。 -- [[aterai]] &new{2011-03-26 (土) 23:21:11};

#comment