JTreeで目次を作成する
Total: 5117
, Today: 1
, Yesterday: 1
Posted by aterai at
Last-modified:
概要
JTree
のノードにリーダーとページ番号を追加表示して目次を作成します。
Screenshot
Advertisement
サンプルコード
class TableOfContentsTreeCellRenderer extends DefaultTreeCellRenderer {
private static BasicStroke READER = new BasicStroke(
1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER,
1f, new float[] { 1f }, 0f);
private String pn;
private Point pnPt = new Point();
private int rxs, rxe, ry;
private boolean isSynth = false;
private final JPanel p = new JPanel(new BorderLayout()) {
@Override protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (pn != null) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setColor(isSynth ? getForeground() : getTextNonSelectionColor());
g2.drawString(pn, pnPt.x - getX(), pnPt.y);
g2.setStroke(READER);
g2.drawLine(rxs, pnPt.y, rxe - getX(), pnPt.y);
g2.dispose();
}
}
@Override public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
d.width = Short.MAX_VALUE;
return d;
}
};
public TableOfContentsTreeCellRenderer() {
super();
p.setOpaque(false);
}
@Override public void updateUI() {
super.updateUI();
isSynth = getUI().getClass().getName().contains("Synth");
if (isSynth) {
//System.out.println("XXX: FocusBorder bug?, JDK 1.7.0, Nimbus start LnF");
setBackgroundSelectionColor(new Color(0x0, true));
}
}
@Override public Component getTreeCellRendererComponent(
JTree tree, Object value, boolean selected, boolean expanded,
boolean leaf, int row, boolean hasFocus) {
JLabel l = (JLabel) super.getTreeCellRendererComponent(
tree, value, selected, expanded, leaf, row, hasFocus);
if (value instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode n = (DefaultMutableTreeNode) value;
Object o = n.getUserObject();
if (o instanceof TableOfContents) {
TableOfContents toc = (TableOfContents) o;
FontMetrics metrics = l.getFontMetrics(l.getFont());
int gap = l.getIconTextGap();
int h = l.getPreferredSize().height;
Insets ins = tree.getInsets();
p.removeAll();
p.add(l, BorderLayout.WEST);
if (isSynth) p.setForeground(l.getForeground());
pn = String.format("%3d", toc.page);
pnPt.x = tree.getWidth() - metrics.stringWidth(pn) - gap;
pnPt.y = (h + metrics.getAscent()) / 2;
rxs = l.getPreferredSize().width + gap;
rxe = tree.getWidth() - ins.right - metrics.stringWidth("000") - gap;
ry = h / 2;
return p;
}
}
pn = null;
return l;
}
}
View in GitHub: Java, Kotlin解説
- 左:
TreeCellRenderer
DefaultTreeCellRenderer
をオーバーライドし、デフォルトのレンダリングで使用するJLabel
をJTree
を超える十分な幅をもつJPanel
でラッピングJTree#getScrollableTracksViewportWidth()
が常にtrue
を返すようオーバーライドして、スクロールバーが表示されないよう設定- 参考: JTree cell width question | Oracle Forums
JTree
のノードの幅には、最初に表示された時にキャッシュされたものが使用されるため、JTable
やJList
のレンダラーのようにLayoutManager
を使ったセル内でのレイアウトが使用できないJTree
をリサイズした場合、右寄せが維持できない- 代わりに、ラップした
JPanel
のpaintComponent(...)
メソッドをオーバーライドして、ノードに続けてリーダー、JTree
の右端付近にページ番号を表示
- ページ番号などをクリックしてノードを展開可能
JTree
に十分な幅がなく、ノードとページ番号が重なる場合などは考慮していない
- 右:
JTree#paintComponent(...)
JTree#paintComponent(...)
をオーバーライドし、ノードとは別にリーダーとページ番号を直接JTree
上に描画JTree#getRowBounds(int)
で取得したノードのセル領域が表示中の場合だけ処理を行う- ページ番号などをクリックしてもノードを展開しない
JTree
に十分なサイズがなく、ノードとページ番号が重なる場合などは考慮していない
JTree tree2 = new JTree(makeModel()) {
private boolean isSynth = false;
private final BasicStroke reader = new BasicStroke(
1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER,
1f, new float[] { 1f }, 0f);
@Override public void updateUI() {
super.updateUI();
setBorder(BorderFactory.createTitledBorder("JTree#paintComponent(...)"));
isSynth = getUI().getClass().getName().contains("Synth");
}
private Rectangle getVisibleRowsRect() {
Insets i = getInsets();
Rectangle visRect = getVisibleRect();
if (visRect.x == 0 && visRect.y == 0 && visRect.width == 0 &&
visRect.height == 0 && getVisibleRowCount() > 0) {
// The tree doesn't have a valid bounds yet. Calculate
// based on visible row count.
visRect.width = 1;
visRect.height = getRowHeight() * getVisibleRowCount();
} else {
visRect.x -= i.left;
visRect.y -= i.top;
}
// we should consider a non-visible area above
Component component = SwingUtilities.getUnwrappedParent(this);
if (component instanceof JViewport) {
component = component.getParent();
if (component instanceof JScrollPane) {
JScrollPane pane = (JScrollPane) component;
JScrollBar bar = pane.getHorizontalScrollBar();
if (bar != null && bar.isVisible()) {
int height = bar.getHeight();
visRect.y -= height;
visRect.height += height;
}
}
}
return visRect;
}
@Override protected void paintComponent(Graphics g) {
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
FontMetrics fm = g.getFontMetrics();
int pnmaxWidth = fm.stringWidth("000");
Insets ins = getInsets();
Rectangle rect = getVisibleRowsRect();
for (int i = 0; i < getRowCount(); i++) {
Rectangle r = getRowBounds(i);
if (rect.intersects(r)) {
TreePath path = getPathForRow(i);
if (isSynth && isRowSelected(i)) {
TreeCellRenderer tcr = getCellRenderer();
if (tcr instanceof DefaultTreeCellRenderer) {
DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) tcr;
g2.setColor(renderer.getTextSelectionColor());
}
} else {
g2.setColor(getForeground());
}
DefaultMutableTreeNode node =
(DefaultMutableTreeNode) path.getLastPathComponent();
Object o = node.getUserObject();
if (o instanceof TableOfContents) {
TableOfContents toc = (TableOfContents) o;
String pn = "" + toc.page;
int x = getWidth() - 1 - fm.stringWidth(pn) - ins.right;
int y = r.y + (r.height + fm.getAscent()) / 2;
g2.drawString(pn, x, y);
int gap = 5;
int x2 = getWidth() - 1 - pnmaxWidth - ins.right;
Stroke s = g2.getStroke();
g2.setStroke(reader);
g2.drawLine(r.x + r.width + gap, y, x2 - gap, y);
g2.setStroke(s);
}
}
}
g2.dispose();
}
};
NimbusLookAndFeel
等の場合、ノード選択時にリーダーとページ番号の文字色を変更するよう設定