JListに固定ヘッダを実装する
Total: 21
, Today: 21
, Yesterday: 0
Posted by aterai at
Last-modified:
概要
JList
の表示領域に存在する行を検索して特定のデータを保持するセルをヘッダとしてJLayer
上に固定して描画します。
Screenshot
Advertisement
サンプルコード
class StickyLayerUI extends LayerUI<JScrollPane> {
private final JPanel panel = new JPanel();
@SuppressWarnings("unchecked")
@Override public void paint(Graphics g, JComponent c) {
super.paint(g, c);
if (c instanceof JLayer) {
JScrollPane scroll = (JScrollPane) ((JLayer<?>) c).getView();
JViewport viewport = scroll.getViewport();
JList<String> list = (JList<String>) viewport.getView();
int cellHeight = list.getFixedCellHeight();
Rectangle viewRect = viewport.getViewRect();
Point vp = SwingUtilities.convertPoint(viewport, 0, 0, c);
Point pt1 = SwingUtilities.convertPoint(c, vp, list);
int idx1 = list.locationToIndex(pt1);
Rectangle header1 = new Rectangle(
vp.x, vp.y, viewRect.width, cellHeight);
if (idx1 >= 0) {
Graphics2D g2 = (Graphics2D) g.create();
int headerIndex1 = getHeaderIndex1(list, idx1);
Component c1 = getComponent(list, headerIndex1);
int nhi = getNextHeaderIndex1(list, idx1);
Point nextPt = list.getCellBounds(nhi, nhi).getLocation();
if (header1.contains(
SwingUtilities.convertPoint(list, nextPt, c))) {
Dimension d = header1.getSize();
SwingUtilities.paintComponent(
g2, c1, panel, getHeaderRect(list, idx1, c, d));
Component cn = getComponent(list, nhi);
SwingUtilities.paintComponent(
g2, cn, panel, getHeaderRect(list, nhi, c, d));
} else {
SwingUtilities.paintComponent(g2, c1, panel, header1);
}
g2.dispose();
}
}
}
private static int getHeaderIndex1(JList<String> list, int start) {
return list.getNextMatch("0", start, Position.Bias.Backward);
}
private static int getNextHeaderIndex1(JList<String> list, int start) {
return list.getNextMatch("0", start, Position.Bias.Forward);
}
private static Rectangle getHeaderRect(
JList<?> list, int i, Component dst, Dimension d) {
Rectangle r = SwingUtilities.convertRectangle(
list, list.getCellBounds(i, i), dst);
r.setSize(d);
return r;
}
private static <E> Component getComponent(JList<E> list, int idx) {
E value = list.getModel().getElementAt(idx);
ListCellRenderer<? super E> renderer = list.getCellRenderer();
Component c = renderer.getListCellRendererComponent(
list, value, idx, false, false);
c.setBackground(Color.GRAY);
c.setForeground(Color.WHITE);
return c;
}
// ...
}
View in GitHub: Java, Kotlin解説
JList<String>
を配置したJScrollPane
にJLayer
を設定し、JList<String>
のスクロールとは関係なく固定ヘッダを描画- このサンプルではセル文字列が空白文字ではなく
0
で開始する行を固定ヘッダとして使用するため、JList#getNextMatch("0", startIndex, Position.Bias.Backward)
で前方検索している - このサンプルでは
1
段の固定ヘッダにのみ対応し、多段階の固定ヘッダには未対応
- このサンプルではセル文字列が空白文字ではなく
- 固定ヘッダは
LayerUI#paint(...)
をオーバーライドして以下のように描画するJViewport
原点直下に存在するセルのインデックスを取得し、前方検索で固定ヘッダのインデックスを取得- 取得した固定ヘッダのインデックスから
ListCellRenderer#getListCellRendererComponent(...)
で固定ヘッダセル描画用のJLabel
を取得 - 取得した
JLabel
の背景色などを変更 - 取得した
JLabel
の描画領域を縦スクロールバーの幅などを除くJViewport#getViewRect()
の幅に変更してSwingUtilities.paintComponent(...)
で描画 - 次の固定ヘッダとなる文字列を所有する行のインデックスを後方検索で取得し、その表示領域が
JLayer
上に描画している固定ヘッダ内に含まれる位置までスクロールしている場合は現在の固定ヘッダと次の固定ヘッダの両方をSwingUtilities.paintComponent(...)
で描画
参考リンク
- Ideas how to make a "StickyHeaderList" with a
JList
and orJTable
· aterai/java-swing-tips · Discussion #28 - JListのスクロールをセルユニット単位にするかを変更する