Swing/ScrollBarSearchHighlighter のバックアップ(No.11)
- バックアップ一覧
 - 差分 を表示
 - 現在との差分 を表示
 - 現在との差分 - Visual を表示
 - ソース を表示
 - Swing/ScrollBarSearchHighlighter へ行く。
  
- 1 (2013-01-28 (月) 02:11:33)
 - 2 (2013-01-28 (月) 10:59:53)
 - 3 (2013-02-01 (金) 20:39:21)
 - 4 (2013-08-01 (木) 17:20:11)
 - 5 (2013-08-18 (日) 23:10:11)
 - 6 (2013-08-19 (月) 11:24:23)
 - 7 (2013-08-19 (月) 14:43:11)
 - 8 (2013-08-19 (月) 17:13:49)
 - 9 (2013-08-20 (火) 19:57:12)
 - 10 (2013-08-23 (金) 16:09:26)
 - 11 (2014-02-14 (金) 00:47:04)
 - 12 (2014-09-28 (日) 01:18:33)
 - 13 (2014-11-01 (土) 00:46:09)
 - 14 (2014-11-23 (日) 16:55:43)
 - 15 (2014-12-31 (水) 01:44:57)
 - 16 (2015-07-07 (火) 16:25:06)
 - 17 (2016-05-25 (水) 13:17:41)
 - 18 (2017-03-28 (火) 15:07:42)
 - 19 (2017-11-02 (木) 15:34:40)
 - 20 (2018-01-30 (火) 19:39:19)
 - 21 (2018-02-24 (土) 19:51:30)
 - 22 (2019-05-22 (水) 19:35:38)
 - 23 (2020-01-28 (火) 16:32:40)
 - 24 (2021-07-24 (土) 23:33:52)
 - 25 (2022-08-20 (土) 22:15:25)
 - 26 (2025-01-03 (金) 08:57:02)
 - 27 (2025-01-03 (金) 09:01:23)
 - 28 (2025-01-03 (金) 09:02:38)
 - 29 (2025-01-03 (金) 09:03:21)
 - 30 (2025-01-03 (金) 09:04:02)
 - 31 (2025-06-19 (木) 12:41:37)
 - 32 (2025-06-19 (木) 12:43:47)
 
 
TITLE:JScrollBarに検索結果をハイライト表示
Posted by aterai at 2013-01-28
JScrollBarに検索結果をハイライト表示
JScrollBarなどにJTextAreaの文字列検索の結果をハイライト表示します。
Screenshot

Advertisement
サンプルコード
scrollbar.setUI(new WindowsScrollBarUI() {
  @Override protected void paintTrack(
      Graphics g, JComponent c, Rectangle trackBounds) {
    super.paintTrack(g, c, trackBounds);
    Rectangle rect = textArea.getBounds();
    double sy = trackBounds.getHeight() / rect.getHeight();
    AffineTransform at = AffineTransform.getScaleInstance(1.0, sy);
    Highlighter highlighter = textArea.getHighlighter();
    g.setColor(Color.YELLOW);
    try{
      for(Highlighter.Highlight hh: highlighter.getHighlights()) {
        Rectangle r = textArea.modelToView(hh.getStartOffset());
        Rectangle s = at.createTransformedShape(r).getBounds();
        int h = 2; //Math.max(2, s.height-2);
        g.fillRect(trackBounds.x, trackBounds.y+s.y, trackBounds.width, h);
      }
    }catch(BadLocationException e) {
      e.printStackTrace();
    }
  }
});
View in GitHub: Java, Kotlin解説
上記のサンプルでは、ScrollBarUI#paintTrack(...)メソッドをオーバーライドして、JTextArea内の文字列の検索結果を縦のJScrollBar内部に描画しています。
- 注:
- 一行分のハイライトの高さは
2pxで固定 - 検索結果の位置は
JTextComponent#modelToView(Matcher#start());を利用しているため、ハイライト対象の文字列が折り返しで二行になっても、ハイライトされるのは開始位置のある一行目のみ 
 - 一行分のハイライトの高さは
 
以下のようなIconを設定したJLabelをJScrollPane#setRowHeaderView(...)で追加する方法もあります。こちらは、縦JScrollBarに直接ハイライトを描画しないので、上下の増減ボタンは考慮せず、またノブの代わりに現在表示位置を示す領域を半透明で描画しています。
JLabel label = new JLabel(new Icon() {
  private final Color THUMB_COLOR = new Color(0,0,255,50);
  @Override public void paintIcon(Component c, Graphics g, int x, int y) {
    Rectangle rect   = textArea.getBounds();
    Dimension sbSize = scrollbar.getSize();
    Insets sbInsets  = scrollbar.getInsets();
    double sy = (sbSize.height - sbInsets.top - sbInsets.bottom) / rect.getHeight();
    AffineTransform at = AffineTransform.getScaleInstance(1.0, sy);
    Highlighter highlighter = textArea.getHighlighter();
    g.setColor(Color.RED);
    try{
      for(Highlighter.Highlight hh: highlighter.getHighlights()) {
        Rectangle r = textArea.modelToView(hh.getStartOffset());
        Rectangle s = at.createTransformedShape(r).getBounds();
        int h = 2; //Math.max(2, s.height-2);
        g.fillRect(x, y+sbInsets.top+s.y, getIconWidth(), h);
      }
    }catch(BadLocationException e) {
      e.printStackTrace();
    }
    //paint Thumb
    JViewport vport = scroll.getViewport();
    Rectangle vrect = c.getBounds();
    vrect.y = vport.getViewPosition().y;
    g.setColor(THUMB_COLOR);
    Rectangle rr = at.createTransformedShape(vrect).getBounds();
    g.fillRect(x, y+sbInsets.top+rr.y, getIconWidth(), rr.height);
  }
  @Override public int getIconWidth() {
    return 4;
  }
  @Override public int getIconHeight() {
    return scrollbar.getHeight();
  }
});
scroll.setVerticalScrollBar(scrollbar);
/*
// Fixed Versions: 7 (b134)
scroll.setRowHeaderView(label);
/*/
// 6826074 JScrollPane does not revalidate the component hierarchy after scrolling
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6826074
// Affected Versions: 6u12,6u16,7
JViewport vp = new JViewport() {
  @Override public void setViewPosition(Point p) {
    super.setViewPosition(p);
    revalidate();
  }
};
vp.setView(label);
scroll.setRowHeader(vp);
縦JScrollBarの中ではなく、左横などにハイライト位置用のIconを表示したい場合は、MatteBorderを利用する方法があります。
JScrollBar scrollBar = new JScrollBar(JScrollBar.VERTICAL) {
  @Override public Dimension getPreferredSize() {
    Dimension d = super.getPreferredSize();
    d.width += getInsets().left;
    return d;
  }
  @Override public void updateUI() {
    super.updateUI();
    setBorder(BorderFactory.createMatteBorder(0, 4, 0, 0, new Icon() {
      @Override public void paintIcon(Component c, Graphics g, int x, int y) {
        //...略...
      }
      @Override public int getIconWidth() {
        return getInsets().left;
      }
      @Override public int getIconHeight() {
        return getHeight();
      }
    }));
  }
};
scroll.setVerticalScrollBar(scrollBar);
コメント
- 行ヘッダーを使用したハイライトはJava7以降でのみ有効に機能するようです。 -- 読者? 
- ご指摘ありがとうございます。仰るとおり、
1.6.0_45で行ヘッダ版が正常に動作しないことを確認しました。回避方法がないか、Bug Databaseあたりを調べてみようと思います。 -- aterai - 修正された時期などから、Bug ID: JDK-6910490 MatteBorder JScrollpane interactionが原因かもと
MatteBorderは使用せずに直接IconをJLabelに追加するよう変更したけど、改善しない…。 -- aterai - Bug ID: JDK-6826074 JScrollPane does not revalidate the component hierarchy after scrollingが原因(
HeavyWeight,LightWeightだけじゃなくレイアウトがうまく更新されていない?)のようです。JViewport#setViewPosition(...)をオーバーライドしてrevalidate()すれば、1.7.0と同様の動作をするようになりました。 -- aterai 
 - ご指摘ありがとうございます。仰るとおり、
 Highlighter.Highlight#getStartOffset()を使用するように変更。 -- aterai
