Swing/TableOfContentsTree のバックアップソース(No.2)
- バックアップ一覧
- 差分 を表示
- 現在との差分 を表示
- 現在との差分 - 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)
TITLE:JTreeで目次を作成する #navi(../) #tags(JTree, DefaultTreeCellRenderer, BasicStroke) RIGHT:Posted by &author(aterai); at 2013-12-30 * JTreeで目次を作成する [#b49290b2] `JTree`のノードにリーダーとページ番号を追加表示して目次を作成します。 - &jnlp; - &jar; - &zip; #ref(https://lh4.googleusercontent.com/-uecZSLw75K4/UsAxPx9ol2I/AAAAAAAAB9M/TcD_QI2Ex_Y/s800/TableOfContentsTree.png) ** サンプルコード [#ta946b33] #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 public 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(0, 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(); 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 = (l.getIcon().getIconHeight() + metrics.getAscent()) / 2; rxs = l.getPreferredSize().width + gap; rxe = tree.getWidth() - ins.right - metrics.stringWidth("000") - gap; ry = l.getIcon().getIconHeight() / 2; return p; } } pn = null; return l; } } }} ** 解説 [#t1ea06a1] - 左: `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 public 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`等の場合、ノード選択時にリーダーとページ番号の文字色を変更するよう設定しています。 ** 参考リンク [#x841c125] - [https://community.oracle.com/thread/1357473 JTree cell width question | Oracle Forums] ** コメント [#m21c402f] #comment