Swing/TableOfContentsTree のバックアップ差分(No.8)
- バックアップ一覧
- 現在との差分 を表示
- 現在との差分 - Visual を表示
- ソース を表示
- バックアップ を表示
- Swing/TableOfContentsTree へ行く。
- 1 (2013-12-30 (月) 00:12:31)
- 2 (2014-01-01 (水) 04:45:01)
- 3 (2014-11-21 (金) 02:21:41)
- 4 (2014-11-21 (金) 18:11:05)
- 5 (2015-04-07 (火) 19:54:30)
- 6 (2016-02-09 (火) 02:54:11)
- 7 (2017-03-06 (月) 15:13:29)
- 8 (2018-01-07 (日) 18:24:15)
- 9 (2018-02-24 (土) 19:51:30)
- 10 (2019-11-14 (木) 18:56:51)
- 11 (2021-05-28 (金) 08:06:57)
- 追加された行はこの色です。
- 削除された行はこの色です。
---
category: swing
folder: TableOfContentsTree
title: JTreeで目次を作成する
tags: [JTree, DefaultTreeCellRenderer, BasicStroke]
author: aterai
pubdate: 2013-12-30T00:12:31+09:00
description: JTreeのノードにリーダーとページ番号を追加表示して目次を作成します。
image: https://lh4.googleusercontent.com/-uecZSLw75K4/UsAxPx9ol2I/AAAAAAAAB9M/TcD_QI2Ex_Y/s800/TableOfContentsTree.png
hreflang:
href: http://java-swing-tips.blogspot.com/2014/01/use-jtree-as-table-of-contents.html
lang: en
---
* 概要 [#summary]
`JTree`のノードにリーダーとページ番号を追加表示して目次を作成します。
#download(https://lh4.googleusercontent.com/-uecZSLw75K4/UsAxPx9ol2I/AAAAAAAAB9M/TcD_QI2Ex_Y/s800/TableOfContentsTree.png)
* サンプルコード [#sourcecode]
#code(link){{
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;
}
}
}}
* 解説 [#explanation]
- 左: `TreeCellRenderer`
-- `DefaultTreeCellRenderer`をオーバーライドし、デフォルトのレンダリングで使用する`JLabel`を`JTree`を超える十分な幅をもつ`JPanel`でラップ
--- `JTree#getScrollableTracksViewportWidth()`が常に`true`を返すようオーバーライドして、スクロールバーが表示されないよう設定(参考: [https://community.oracle.com/thread/1357473 JTree cell width question | Oracle Forums])
--- `JTree`のノードの幅には、最初に表示された時にキャッシュされたものが使用されるため、`JTable`や`JList`のレンダラーのように`LayoutManager`を使ったセル内でのレイアウトが使用できない(`JTree`をリサイズした場合、右寄せが維持できない)
--- 代わりに、ラップした`JPanel`の`paintComponent(...)`メソッドをオーバーライドして、ノードに続けてリーダー、`JTree`の右端付近にページ番号を表示
-- ページ番号などをクリックしてノードを展開可能
-- `JTree`に十分な幅がなく、ノードとページ番号が重なる場合などは考慮していない
- 右: `JTree#paintComponent(...)`
-- `JTree#paintComponent(...)`をオーバーライドし、ノードとは別にリーダーとページ番号を直接`JTree`上に描画
-- `JTree#getRowBounds(int)`で取得したノードのセル領域が表示中の場合だけ処理を行う
-- ページ番号などをクリックしてもノードを展開しない
-- `JTree`に十分なサイズがなく、ノードとページ番号が重なる場合などは考慮していない
#code{{
JTree tree2 = new JTree(makeModel()) {
@Override public void updateUI() {
super.updateUI();
setBorder(BorderFactory.createTitledBorder("JTree#paintComponent(...)"));
isSynth = getUI().getClass().getName().contains("Synth");
}
private boolean isSynth = false;
private final BasicStroke reader = new BasicStroke(
1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER,
1f, new float[] { 1f }, 0f);
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`等の場合、ノード選択時にリーダーとページ番号の文字色を変更するよう設定しています。
* 参考リンク [#reference]
- [https://community.oracle.com/thread/1357473 JTree cell width question | Oracle Forums]
- [[Swing/AutoChapterNumberingTreeNode>Swing/AutoChapterNumberingTreeNode]]
* コメント [#comment]
#comment
#comment