Swing/CheckBoxNodeEditor の変更点
- 追加された行はこの色です。
- 削除された行はこの色です。
- Swing/CheckBoxNodeEditor へ行く。
- Swing/CheckBoxNodeEditor の差分を削除
--- category: swing folder: CheckBoxNodeEditor title: JTreeのすべてのノードにJCheckBoxを追加する tags: [JTree, JCheckBox, TreeCellRenderer, TreeCellEditor, TreeModelListener, Icon] author: aterai pubdate: 2012-02-06T14:38:59+09:00 description: JTreeのすべてのノードに編集可能なJCheckBoxを追加します。 image: https://lh4.googleusercontent.com/-DK6aW3VNikg/TygxL3j8UoI/AAAAAAAABIw/6_9FyPe4v7U/s800/CheckBoxNodeEditor.png hreflang: href: https://java-swing-tips.blogspot.com/2012/02/jcheckbox-node-jtree.html lang: en --- * 概要 [#summary] `JTree`のすべてのノードに編集可能な`JCheckBox`を追加します。 #download(https://lh4.googleusercontent.com/-DK6aW3VNikg/TygxL3j8UoI/AAAAAAAABIw/6_9FyPe4v7U/s800/CheckBoxNodeEditor.png) * サンプルコード [#sourcecode] #code(link){{ class CheckBoxNodeEditor extends TriStateCheckBox implements TreeCellEditor { private DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer(); private final JPanel panel = new JPanel(new BorderLayout()); private String str = null; public CheckBoxNodeEditor() { super(); this.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { // System.out.println("actionPerformed: stopCellEditing"); stopCellEditing(); } }); panel.setFocusable(false); panel.setRequestFocusEnabled(false); panel.setOpaque(false); panel.add(this, BorderLayout.WEST); this.setOpaque(false); } @Override public Component getTreeCellEditorComponent( JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) { JLabel l = (JLabel) renderer.getTreeCellRendererComponent( tree, value, true, expanded, leaf, row, true); l.setFont(tree.getFont()); if (value instanceof DefaultMutableTreeNode) { this.setEnabled(tree.isEnabled()); this.setFont(tree.getFont()); Object userObject = ((DefaultMutableTreeNode) value).getUserObject(); if (userObject instanceof CheckBoxNode) { CheckBoxNode node = (CheckBoxNode) userObject; if (node.status == Status.INDETERMINATE) { setIcon(new IndeterminateIcon()); } else { setIcon(null); } l.setText(node.label); setSelected(node.status == Status.SELECTED); str = node.label; } // panel.add(this, BorderLayout.WEST); panel.add(l); return panel; } return l; } @Override public Object getCellEditorValue() { return new CheckBoxNode(str, isSelected() ? Status.SELECTED : Status.DESELECTED); } @Override public boolean isCellEditable(EventObject e) { if (e instanceof MouseEvent && e.getSource() instanceof JTree) { MouseEvent me = (MouseEvent) e; JTree tree = (JTree) e.getSource(); TreePath path = tree.getPathForLocation(me.getX(), me.getY()); Rectangle r = tree.getPathBounds(path); if (r == null) { return false; } Dimension d = getPreferredSize(); r.setSize(new Dimension(d.width, r.height)); if (r.contains(me.getPoint())) { return true; } } return false; } @Override public void updateUI() { super.updateUI(); setName("Tree.cellEditor"); if (panel != null) { //panel.removeAll(); // ??? Change to Nimbus LnF, JDK 1.6.0 panel.updateUI(); //panel.add(this, BorderLayout.WEST); } // ???#1: JDK 1.6.0 bug??? @see 1.7.0 DefaultTreeCellRenderer#updateUI() // if (System.getProperty("java.version").startsWith("1.6.0")) { // renderer = new DefaultTreeCellRenderer(); // } } // ... }} * 解説 [#explanation] - `JCheckBox`を継承する`TreeCellEditor`、`TreeCellRenderer`を作成 -- `TreeCellEditor#getTreeCellEditorComponent(...)`などはこの`JCheckBox`、`JLabel`(文字列、アイコン)などを含む`JPanel`を生成して返す - マウスクリックなどでノードのチェック状態が変更されたら子ノードと親ノードの選択状態も更新する必要があるので、`TreeModelListener`をモデルに設定して監視 - `JPanel`を継承する`TreeCellEditor`と`TreeCellRenderer`でも、`JDK 1.7.0`、`JDK 1.6.0_30`などでは問題なく動作可能 #code{{ class CheckBoxNodeRenderer extends JPanel implements TreeCellRenderer { private DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer(); private final TriStateCheckBox check = new TriStateCheckBox(); public CheckBoxNodeRenderer() { super(new BorderLayout()); String uiName = getUI().getClass().getName(); if (uiName.contains("Synth") && System.getProperty("java.version").startsWith("1.7.0")) { System.out.println("XXX: FocusBorder bug?, JDK 1.7.0, Nimbus start LnF"); renderer.setBackgroundSelectionColor(new Color(0x0, true)); } setFocusable(false); setRequestFocusEnabled(false); setOpaque(false); add(check, BorderLayout.WEST); check.setOpaque(false); } @Override public Component getTreeCellRendererComponent( JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { JLabel l = (JLabel) renderer.getTreeCellRendererComponent( tree, value, selected, expanded, leaf, row, hasFocus); l.setFont(tree.getFont()); if (value instanceof DefaultMutableTreeNode) { check.setEnabled(tree.isEnabled()); check.setFont(tree.getFont()); Object userObject = ((DefaultMutableTreeNode) value).getUserObject(); if (userObject instanceof CheckBoxNode) { CheckBoxNode node = (CheckBoxNode) userObject; if (node.status == Status.INDETERMINATE) { check.setIcon(new IndeterminateIcon()); } else { check.setIcon(null); } l.setText(node.label); check.setSelected(node.status == Status.SELECTED); } add(l); return this; } return l; } @Override public void updateUI() { super.updateUI(); if (check != null) { // removeAll(); // ??? Change to Nimbus LnF, JDK 1.6.0 check.updateUI(); // add(check, BorderLayout.WEST); } setName("Tree.cellRenderer"); // ???#1: JDK 1.6.0 bug??? @see 1.7.0 DefaultTreeCellRenderer#updateUI() // if (System.getProperty("java.version").startsWith("1.6.0")) { // renderer = new DefaultTreeCellRenderer(); // } } } }} ---- - `JDK 1.6.0`で`LookAndFeel`を`Nimbus`などに変更すると、セルエディタなどが更新されず、表示が不正になる場合があるので、`JTree#updateUI()`を以下のようにオーバーライドして回避 #code{{ JTree tree = new JTree() { @Override public void updateUI() { setCellRenderer(null); setCellEditor(null); super.updateUI(); //???#1: JDK 1.6.0 bug??? Nimbus LnF setCellRenderer(new CheckBoxNodeRenderer()); setCellEditor(new CheckBoxNodeEditor()); } }; }} - `JDK 1.7.0`で、初期`LookAndFeel`を`Nimbus`にすると、ノードにフォーカスがある場合のグラデーション描画がノードの背景色で塗りつぶされてしまう?のを、以下のようにして回避 -- &ref(https://lh3.googleusercontent.com/-DQgyx52YcsQ/T6sfFSWGIpI/AAAAAAAABMc/jAx8XeuMeWI/s800/CheckBoxNodeEditor1.png); #code{{ public CheckBoxNodeRenderer() { super(); String uiName = getUI().getClass().getName(); if (uiName.contains("Synth") && System.getProperty("java.version").startsWith("1.7.0")) { System.out.println("XXX: FocusBorder bug?, JDK 1.7.0, Nimbus start LnF"); renderer.setBackgroundSelectionColor(new Color(0x0, true)); } // ... }} - %%`JDK 1.7.0`で、ノードのチェックボックスをクリックしても、初回だけ反応しない%% `1.7.0_60`で修正されている -- `JDK 1.6.0_30`などは問題なし -- `TreeCellEditor#isCellEditable()`をオーバーライドして、初回のみセルエディタのサイズを以下のように設定 -- `JDK 1.8.0`では、修正されている -- [https://bugs.openjdk.java.net/browse/JDK-8023474 Bug ID: JDK-8023474 First mousepress doesn't start editing in JTree] -- [https://bugs.openjdk.org/browse/JDK-8023474 Bug ID: JDK-8023474 First mousepress doesn't start editing in JTree] #code{{ if (isFirstTime && System.getProperty("java.version").startsWith("1.7.0")) { System.out.println("XXX: Java 7, only on first run\n" + getBounds()); setBounds(new Rectangle(0, 0, d.width, r.height)); } }} * 参考リンク [#reference] - [[JTreeの葉ノードをJCheckBoxにする>Swing/CheckBoxNodeTree]] - [[JCheckBoxに不定状態のアイコンを追加する>Swing/TriStateCheckBox]] - [[JCheckBox付きJTreeでディレクトリ構造を表示>Swing/FileSystemTreeWithCheckBox]] * コメント [#comment] #comment - 親ノードまでではなく、ルートノードまで不定状態の変更を行うように修正。 -- &user(aterai); &new{2012-03-23 (金) 17:43:32}; #comment