JListに固定ヘッダを実装する
Total: 87
, Today: 2
, Yesterday: 4
Posted by aterai at
Last-modified:
概要
JList
の表示領域に存在する行を検索して特定のデータを保持するセルをヘッダとしてJLayer
上に固定して描画します。
Screenshot
Advertisement
サンプルコード
class StickyLayerUI extends LayerUI<JScrollPane> {
private final JPanel renderer = new JPanel();
private int currentHeaderIdx = -1;
private int nextHeaderIdx = -1;
@Override public void installUI(JComponent c) {
super.installUI(c);
if (c instanceof JLayer) {
((JLayer<?>) c).setLayerEventMask(
AWTEvent.MOUSE_WHEEL_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 processMouseMotionEvent(
MouseEvent e, JLayer<? extends JScrollPane> l) {
super.processMouseMotionEvent(e, l);
Component c = l.getView().getViewport().getView();
if (e.getID() == MouseEvent.MOUSE_DRAGGED && c instanceof JList) {
update((JList<?>) c);
}
}
@Override protected void processMouseWheelEvent(
MouseWheelEvent e, JLayer<? extends JScrollPane> l) {
super.processMouseWheelEvent(e, l);
Component c = l.getView().getViewport().getView();
if (c instanceof JList) {
update((JList<?>) c);
}
}
private void update(JList<?> list) {
int idx = list.getFirstVisibleIndex();
if (idx >= 0) {
currentHeaderIdx = getHeaderIndex1(list, idx);
nextHeaderIdx = getNextHeaderIndex1(list, idx);
} else {
currentHeaderIdx = -1;
nextHeaderIdx = -1;
}
}
@Override public void paint(Graphics g, JComponent c) {
super.paint(g, c);
JList<?> list = getList(c);
if (list != null && currentHeaderIdx >= 0) {
JScrollPane scroll = (JScrollPane) ((JLayer<?>) c).getView();
Rectangle headerRect = scroll.getViewport().getBounds();
headerRect.height = list.getFixedCellHeight();
Graphics2D g2 = (Graphics2D) g.create();
int firstVisibleIdx = list.getFirstVisibleIndex();
if (firstVisibleIdx + 1 == nextHeaderIdx) {
Dimension d = headerRect.getSize();
Component c1 = getComponent(list, currentHeaderIdx);
Rectangle r1 = getHeaderRect(list, firstVisibleIdx, c, d);
SwingUtilities.paintComponent(g2, c1, renderer, r1);
Component c2 = getComponent(list, nextHeaderIdx);
Rectangle r2 = getHeaderRect(list, nextHeaderIdx, c, d);
SwingUtilities.paintComponent(g2, c2, renderer, r2);
} else {
Component c1 = getComponent(list, currentHeaderIdx);
SwingUtilities.paintComponent(g2, c1, renderer, headerRect);
}
g2.dispose();
}
}
private static JList<?> getList(JComponent layer) {
JList<?> list = null;
if (layer instanceof JLayer) {
JScrollPane scroll = (JScrollPane) ((JLayer<?>) layer).getView();
Component view = scroll.getViewport().getView();
if (view instanceof JList) {
list = (JList<?>) view;
}
}
return list;
}
private static int getHeaderIndex1(JList<?> list, int start) {
return list.getNextMatch("0", start, Position.Bias.Backward);
}
private static int getNextHeaderIndex1(JList<?> 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> r = list.getCellRenderer();
Component c = r.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
原点直下に存在するセルのインデックス(JList#getFirstVisibleIndex()で取得可能)を取得し、前方検索で固定ヘッダのインデックスを取得- 取得した固定ヘッダのインデックスから
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のスクロールをセルユニット単位にするかを変更する