概要

JTextFieldの表示領域をJScrollBarでスクロール可能にします。

サンプルコード

scroller.setModel(textField.getHorizontalVisibility());
View in GitHub: Java, Kotlin

解説

上記のサンプルでは、JTextField#getHorizontalVisibility()で取得したBoundedRangeModel(可視領域のモデル)をJScrollBarに設定することで、これを使用したスクロールや現在の可視領域の位置、幅の表示などが可能になっています。

  • JScrollPane + VERTICAL_SCROLLBAR_NEVER
    • JTextFieldをスクロールバーを常に非表示に設定したJScrollPaneに追加
  • BoundedRangeModel: textField.getHorizontalVisibility()
    • JTextFieldと水平スクロールバーJScrollBar(Adjustable.HORIZONTAL)を別々に配置
    • JTextField#getHorizontalVisibility()BoundedRangeModelを取得し、これをJScrollBar#setModel(...)で設定して二つを同期する
  • BoundedRangeModel+textField.ArrowButtonlessScrollBarUI
    • 上のJScrollBarに矢印ボタンなどを非表示化とノブの半透明化などを行うScrollBarUIを設定

  • setCaretPosition: 0: JTextField#setCaretPosition(0);JTextFieldにフォーカスが無い場合無効?
    • JScrollBarが同期しない場合がある
  • setScrollOffset: 0: JScrollBarのノブがマウスドラッグに反応しなくなる場合がある?
  • ノブの表示: 文字列をすべて削除するなどしても、ノブが非表示にならない
    • 1pxの余白?、以下のようなリスナーで回避するテストを追加
class EmptyThumbHandler extends ComponentAdapter implements DocumentListener {
  private final BoundedRangeModel emptyThumbModel
    = new DefaultBoundedRangeModel(0, 1, 0, 1);
  private final JTextField textField;
  private final JScrollBar scroller;
  public EmptyThumbHandler(JTextField textField, JScrollBar scroller) {
    super();
    this.textField = textField;
    this.scroller = scroller;
  }

  private void changeThumbModel() {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        BoundedRangeModel m = textField.getHorizontalVisibility();
        int iv = m.getMaximum() - m.getMinimum() - m.getExtent() - 1; //-1:bug?
        if (iv <= 0) {
          scroller.setModel(emptyThumbModel);
        } else {
          scroller.setModel(textField.getHorizontalVisibility());
        }
      }
    });
  }

  @Override public void componentResized(ComponentEvent e) {
    changeThumbModel();
  }

  @Override public void insertUpdate(DocumentEvent e) {
    changeThumbModel();
  }

  @Override public void removeUpdate(DocumentEvent e) {
    changeThumbModel();
  }

  @Override public void changedUpdate(DocumentEvent e) {
    changeThumbModel();
  }
}

  • 以下は、サイズ0ArrowButtonを使用するScrollBarUIを設定する方法
class ArrowButtonlessScrollBarUI extends BasicScrollBarUI {
  private static final Color DEFAULT_COLOR  = new Color(220, 100, 100, 100);
  private static final Color DRAGGING_COLOR = new Color(200, 100, 100, 100);
  private static final Color ROLLOVER_COLOR = new Color(255, 120, 100, 100);
  @Override protected JButton createDecreaseButton(int orientation) {
    return new ZeroSizeButton();
  }

  @Override protected JButton createIncreaseButton(int orientation) {
    return new ZeroSizeButton();
  }

  @Override protected void paintTrack(Graphics g, JComponent c, Rectangle r) {
    // Graphics2D g2 = (Graphics2D) g.create();
    // g2.setPaint(new Color(100, 100, 100, 100));
    // g2.fillRect(r.x, r.y, r.width - 1, r.height - 1);
    // g2.dispose();
  }

  @Override protected void paintThumb(Graphics g, JComponent c, Rectangle r) {
    JScrollBar sb = (JScrollBar) c;
    if (!sb.isEnabled()) {
      return;
    }
    BoundedRangeModel m = sb.getModel();
    int iv = m.getMaximum() - m.getMinimum() - m.getExtent() - 1; //-1:bug?
    if (iv > 0) {
      Graphics2D g2 = (Graphics2D) g.create();
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                          RenderingHints.VALUE_ANTIALIAS_ON);
      Color color;
      if (isDragging) {
        color = DRAGGING_COLOR;
      } else if (isThumbRollover()) {
        color = ROLLOVER_COLOR;
      } else {
        color = DEFAULT_COLOR;
      }
      g2.setPaint(color);
      g2.fillRect(r.x, r.y, r.width - 1, r.height - 1);
      g2.dispose();
    }
  }
}

  • JScrollPane scroll = new JScrollPane(new JTextField(TEXT), ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
    • 縦スクロールバーを非表示にしたJScrollPaneを使用する場合、JTextField内の文字列選択でスクロールしない
    • 文字列を適当な長さまで削除するとノブが非表示になる

参考リンク

コメント