• 追加された行はこの色です。
  • 削除された行はこの色です。
TITLE:JScrollPaneのViewPortをマウスで掴んでスクロール
#navi(../)
*JScrollPaneのViewPortをマウスで掴んでスクロール [#ef47828b]
>編集者:[[Terai Atsuhiro>terai]]~
作成日:2006-01-02~
更新日:&lastmod;
---
category: swing
folder: HandScroll
title: JScrollPaneのViewportをマウスで掴んでスクロール
tags: [JScrollPane, JViewport, MouseListener, MouseMotionListener, JLabel]
author: aterai
pubdate: 2006-01-02T06:45:45+09:00
description: JScrollPaneの窓の中をマウスで掴んで画像をスクロールします。
image: https://lh6.googleusercontent.com/_9Z4BYR88imo/TQTNqjajfcI/AAAAAAAAAbI/Km-h7tWdYOo/s800/HandScroll.png
hreflang:
    href: https://java-swing-tips.blogspot.com/2009/03/mouse-dragging-viewport-scroll.html
    lang: en
---
* 概要 [#summary]
`JScrollPane`の窓の中をマウスで掴んで画像をスクロールします。

#contents
#download(https://lh6.googleusercontent.com/_9Z4BYR88imo/TQTNqjajfcI/AAAAAAAAAbI/Km-h7tWdYOo/s800/HandScroll.png)

**概要 [#id739e63]
JScrollPaneの窓の中をマウスで掴んで画像をスクロールします。
* サンプルコード [#sourcecode]
#code(link){{
class HandScrollListener extends MouseAdapter {
  private final Cursor defCursor = Cursor.getDefaultCursor();
  private final Cursor hndCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
  private final Point pp = new Point();

#screenshot
  @Override public void mouseDragged(MouseEvent e) {
    JViewport vport = (JViewport) e.getComponent();
    Point cp = e.getPoint();
    Point vp = vport.getViewPosition();
          // = SwingUtilities.convertPoint(vport, 0, 0, label);
    vp.translate(pp.x - cp.x, pp.y - cp.y);
    // if (r1.isSelected()) {
    label.scrollRectToVisible(new Rectangle(vp, vport.getSize()));
    // } else {
    //   vport.setViewPosition(vp);
    // }
    pp.setLocation(cp);
  }

**サンプルコード [#e92a8d9e]
#code{{
 final Cursor cc = label.getCursor();
 final Cursor hc = new Cursor(Cursor.HAND_CURSOR);
 final JViewport vport = scroll.getViewport();
 vport.addMouseMotionListener(new MouseMotionAdapter() {
   public void mouseDragged(final MouseEvent e) {
     Rectangle vr = vport.getViewRect();
     int w = vr.width;
     int h = vr.height;
     int x = e.getX();
     int y = e.getY();
     Point pt = SwingUtilities.convertPoint(vport,0,0,label);
     rect.setRect(pt.x-x+startX,pt.y-y+startY,w,h);
     label.scrollRectToVisible(rect);
     startX = x;
     startY = y;
   }
 });
 vport.addMouseListener(new MouseAdapter() {
   public void mousePressed(MouseEvent e) {
     startX = e.getX();
     startY = e.getY();
     label.setCursor(hc);
   }
   public void mouseReleased(MouseEvent e) {
     label.setCursor(cc);
     label.repaint();
   }
 });
  @Override public void mousePressed(MouseEvent e) {
    e.getComponent().setCursor(hndCursor);
    pp.setLocation(e.getPoint());
  }

  @Override public void mouseReleased(MouseEvent e) {
    e.getComponent().setCursor(defCursor);
  }
}
}}
-&jnlp;
-&jar;
-&zip;

**解説 [#bf42cf21]
JViewportの原点(左上)をSwingUtilities.convertPointメソッドを使って中のJLabel(画像)の座標に変換して基準のPoint座標としています。このPointをマウスの移動量に応じて変更し、JComponent#scrollRectToVisibleメソッドの引数として使用しています。
* 解説 [#explanation]
`JViewport`の原点(左上)をマウスの移動に応じて変更し、`JComponent#scrollRectToVisible`メソッドの引数として使用して覗き窓のスクロールを行っています。

**参考リンク [#b337b5bb]
-[[2000ピクセル以上のフリー写真素材集>http://sozai-free.com/]]
--大きな''猫''写真があったので拝借しています。
- `JComponent#scrollRectToVisible(...)`ではなく`JViewport#setViewPosition(Point)`を使用すると内部コンポーネントの外側に移動可能

**コメント [#ubd1d4e0]
- つかんで移動ということですが、移動方向が逆の気がします。 -- [[名無し]] &new{2006-02-25 (土) 01:24:46};
- 確かに逆ですね。画像を掴んでというより、スクロールバーを掴んでみたいな動きになってました。修正しておきます。 -- [[terai]] &new{2006-02-25 (土) 03:33:50};
----
- [https://bugs.openjdk.org/browse/JDK-6333318 JDK-6333318 JViewPort.scrollRectToVisible( Rectangle cr ) doesn't scroll if cr left or above - Java Bug System]
-- `JDK 1.7.0`から、`JViewport#setViewPosition(Point)`などで左上外部などの枠外に移動不可になっている
-- 左上外部は`Java 11`で修正された?、右下外部は移動不可のまま
--- [https://bugs.openjdk.org/browse/JDK-8195738 JDK-8195738 scroll position in ScrollPane is reset after calling validate() - Java Bug System]
-- 以下のように`JViewport#revalidate()`をオーバーライドして枠外スクロールは可能に戻せるが、親`JFrame`をリサイズすると表示位置はリセットされる

#code{{
// // JDK 1.6.0
// JScrollPane scroll = new JScrollPane(label);
// JViewport vport = scroll.getViewport();

// JDK 1.7.0 or later
JViewport vport = new JViewport() {
  private static final boolean WEIGHT_MIXING = false;
  private boolean isAdjusting;
  @Override public void revalidate() {
    if (!WEIGHT_MIXING && isAdjusting) {
      return;
    }
    super.revalidate();
  }

  @Override public void setViewPosition(Point p) {
    if (WEIGHT_MIXING) {
      super.setViewPosition(p);
    } else {
      isAdjusting = true;
      super.setViewPosition(p);
      isAdjusting = false;
    }
  }
};
vport.add(label);
JScrollPane scroll = new JScrollPane();
scroll.setViewport(vport);
}}

* 参考リンク [#reference]
- [[JScrollPaneのオートスクロール>Swing/AutoScroll]]
- [http://sozai-free.com/ 2000ピクセル以上のフリー写真素材集]
-- 猫の写真を引用
- [https://bugs.openjdk.org/browse/JDK-6333318 JDK-6333318 JViewPort.scrollRectToVisible( Rectangle cr ) doesn't scroll if cr left or above - Java Bug System]
-- [https://bugs.openjdk.org/browse/JDK-8195738 JDK-8195738 scroll position in ScrollPane is reset after calling validate() - Java Bug System]
- [[JScrollPaneでキネティックスクロール>Swing/KineticScrolling]]
- [[JTreeの余白をドラッグしてスクロール>Swing/TreeDragScroll]]

* コメント [#comment]
#comment
- つかんで移動ということですが、移動方向が逆の気がします。 -- &user(名無し); &new{2006-02-25 (土) 01:24:46};
-- ご指摘ありがとうございます。確かに逆ですね。画像を掴んでというより、スクロールバーを掴んでみたいな動きになってました。修正しておきます。 -- &user(aterai); &new{2006-02-25 (土) 03:33:50};
- `SwingUtilities.convertPoint`の代わりに、`vport.getViewPosition()`を使用するように変更。スクリーンショットの更新。 -- &user(aterai); &new{2009-01-19 (Mon) 16:58:27};
- `JDK 1.7.0`では、`JViewport#setViewPosition(Point)`を使って%%右下%%左上外部に移動できなくなっている。`Heavyweight`と`Lightweight`コンポーネントが混在しても問題ないようにするために、内部で`revalidate()`しているのが原因? このサンプルでは`Lightweight`コンポーネントしか使用しないので、`revalidate()`しないように対応。 -- &user(aterai); &new{2011-10-03 (月) 18:03:55};
- サンプルほしい --  &new{2013-05-11 (土) 03:30:34};
-- ページ上部にソースコードなどへのリンクがあるので、使ってみてください。上げ忘れてるとかメンテ中などでなければダウンロードできると思います。 -- &user(aterai); &new{2013-05-13 (月) 20:08:18};

#comment