Swing/ScrollBackToTopButton のバックアップ(No.2)
- バックアップ一覧
- 差分 を表示
- 現在との差分 を表示
- 現在との差分 - Visual を表示
- ソース を表示
- Swing/ScrollBackToTopButton へ行く。
- 1 (2021-07-12 (月) 00:25:58)
- 2 (2021-08-31 (火) 16:47:22)
- category: swing folder: ScrollBackToTopButton title: JScrollPaneに先頭領域までのスクロールバックを実行するJButtonを追加する tags: [JScrollPane, JViewport, JLayer, JButton, Timer] author: aterai pubdate: 2021-07-12T00:25:15+09:00 description: JScrollPaneにJButtonを描画するJLayerを設定し、ここでクリックイベントを取得したら先頭領域までのスクロールバックを実行します。 image: https://drive.google.com/uc?id=1SQP-yapYstaY4KpdrUqkyhpcA00ZUVpM
概要
JScrollPane
にJButton
を描画するJLayer
を設定し、ここでクリックイベントを取得したら先頭領域までのスクロールバックを実行します。
Screenshot
Advertisement
サンプルコード
class ScrollBackToTopLayerUI extends LayerUI<JScrollPane> {
private static final int GAP = 5;
private final Container rubberStamp = new JPanel();
private final Point mousePt = new Point();
private final JButton button = new JButton(new ScrollBackToTopIcon()) {
@Override public void updateUI() {
super.updateUI();
setBorder(BorderFactory.createEmptyBorder());
setFocusPainted(false);
setBorderPainted(false);
setContentAreaFilled(false);
setRolloverEnabled(false);
}
};
private final Rectangle buttonRect = new Rectangle(button.getPreferredSize());
private void updateButtonRect(JScrollPane scroll) {
JViewport viewport = scroll.getViewport();
int x = viewport.getX() + viewport.getWidth() - buttonRect.width - GAP;
int y = viewport.getY() + viewport.getHeight() - buttonRect.height - GAP;
buttonRect.setLocation(x, y);
}
@Override public void paint(Graphics g, JComponent c) {
super.paint(g, c);
if (c instanceof JLayer) {
JScrollPane scroll = (JScrollPane) ((JLayer<?>) c).getView();
updateButtonRect(scroll);
if (scroll.getViewport().getViewRect().y > 0) {
button.getModel().setRollover(buttonRect.contains(mousePt));
SwingUtilities.paintComponent(g, button, rubberStamp, buttonRect);
}
}
}
@Override public void installUI(JComponent c) {
super.installUI(c);
if (c instanceof JLayer) {
((JLayer<?>) c).setLayerEventMask(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
}
}
@Override public void uninstallUI(JComponent c) {
if (c instanceof JLayer) {
((JLayer<?>) c).setLayerEventMask(0);
}
super.uninstallUI(c);
}
@Override protected void processMouseEvent(MouseEvent e, JLayer<? extends JScrollPane> l) {
JScrollPane scroll = l.getView();
Rectangle r = scroll.getViewport().getViewRect();
Point p = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), scroll);
mousePt.setLocation(p);
int id = e.getID();
if (id == MouseEvent.MOUSE_CLICKED) {
if (buttonRect.contains(mousePt)) {
scrollBackToTop(l.getView());
}
} else if (id == MouseEvent.MOUSE_PRESSED && r.y > 0 && buttonRect.contains(mousePt)) {
e.consume();
}
}
@Override protected void processMouseMotionEvent(MouseEvent e, JLayer<? extends JScrollPane> l) {
Point p = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), l.getView());
mousePt.setLocation(p);
l.repaint(buttonRect);
}
private void scrollBackToTop(JScrollPane scroll) {
JComponent c = (JComponent) scroll.getViewport().getView();
Rectangle current = scroll.getViewport().getViewRect();
new Timer(20, e -> {
Timer animator = (Timer) e.getSource();
if (0 < current.y && animator.isRunning()) {
current.y -= Math.max(1, current.y / 2);
c.scrollRectToVisible(current);
} else {
animator.stop();
}
}).start();
}
}
View in GitHub: Java, Kotlin解説
ScrollBackToTopLayerUI#paint(...)
JScrollPane
相対でその子JViewport
の右下隅領域に半透明アイコンを設定したJButton
をSwingUtilities.paintComponent(...)
メソッドで描画- すでに先頭領域が表示中(
JViewport#getViewRect()
で取得される領域のy
座標が0
)の場合はJButton
を描画しない
- すでに先頭領域が表示中(
- 上記の右下隅領域内にマウスカーソルが存在する場合は
JButton#getModel()#setRollover(true)
でJButton
をロールオーバー状態に変更
ScrollBackToTopLayerUI#processMouseEvent(...)
- SwingUtilities.convertPoint(...)メソッドでマウスイベントを
JScrollPane
基準に変換 - 変換したマウスイベントが
JButton
の描画領域内(JViewport
の右下隅領域)のクリックイベントの場合、JComponent#scrollRectToVisible(...)
を繰り返し実行するTimer
を起動して先頭領域までスクロールバック JButton
が非表示(すでに先頭領域が表示中)かつ、変換したマウスイベントがJButton
の描画領域内のプレスイベントの場合、子コンポーネントにイベントが伝達しないようにMouseEvent#consume()
で消費- ここでプレスイベントを消費しないと、
JButton
のプレスだけではなくたとえばJScrollPane
内のJTable
のセル選択状態まで変化してしまう
- ここでプレスイベントを消費しないと、
- SwingUtilities.convertPoint(...)メソッドでマウスイベントを
参考リンク
- JScrollBarが最後までスクロールしたことを確認する
- JTextAreaでSmoothScrollによる行移動
- JScrollPane内にあるJTableなどで追加した行が可視化されるようにスクロールする