TITLE:JTreeのすべてのノードにJCheckBoxを追加する

Posted by aterai at 2012-02-06

JTreeのすべてのノードにJCheckBoxを追加する

JTreeのすべてのノードに編集可能なJCheckBoxを追加します。

  • &jnlp;
  • &jar;
  • &zip;
CheckBoxNodeEditor.png

サンプルコード

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 != null && value instanceof DefaultMutableTreeNode) {
      this.setEnabled(tree.isEnabled());
      this.setFont(tree.getFont());
      Object userObject = ((DefaultMutableTreeNode)value).getUserObject();
      if(userObject!=null && userObject instanceof CheckBoxNode) {
        CheckBoxNode node = (CheckBoxNode)userObject;
        if(!node.selected && node.indeterminate) {
          setIcon(new IndeterminateIcon());
        } else {
          setIcon(null);
        }
        l.setText(node.str);
        setSelected(node.selected);
        str = node.str;
      }
      //panel.add(this, BorderLayout.WEST);
      panel.add(l);
      return panel;
    }
    return l;
  }
  @Override public Object getCellEditorValue() {
    return new CheckBoxNode(str, isSelected());
  }
  @Override public boolean isCellEditable(EventObject e) {
    if(e != null && 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.getX(), me.getY())) {
        if(str==null && 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));
        }
        //System.out.println(getBounds());
        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();
    //}
  }
//...

解説

上記のサンプルでは、JCheckBoxを継承する TreeCellEditor、TreeCellRenderer を作成し、TreeCellEditor#getTreeCellEditorComponent(...)などは、このJCheckBox、JLabel(文字列、アイコン)などを含むJPanel を生成して返しています。

JPanelを継承する TreeCellEditor、TreeCellRenderer でも、JDK 1.7.0, 1.6.0_30 などでは、問題なく動作するようです。

class CheckBoxNodeRenderer extends TriStateCheckBox implements TreeCellRenderer {
  private DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer();
  private final JPanel panel = new JPanel(new BorderLayout());
  private JTree tree = null;
  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(0,0,0,0));
    }
    panel.setFocusable(false);
    panel.setRequestFocusEnabled(false);
    panel.setOpaque(false);
    panel.add(this, BorderLayout.WEST);
    this.setOpaque(false);
  }
  @Override public Component getTreeCellRendererComponent(
      JTree tree, Object value, boolean selected, boolean expanded,
      boolean leaf, int row, boolean hasFocus) {
    this.tree = tree;
    JLabel l = (JLabel)renderer.getTreeCellRendererComponent(
        tree, value, selected, expanded, leaf, row, hasFocus);
    l.setFont(tree.getFont());
    if(value != null && value instanceof DefaultMutableTreeNode) {
      this.setEnabled(tree.isEnabled());
      this.setFont(tree.getFont());
      Object userObject = ((DefaultMutableTreeNode)value).getUserObject();
      if(userObject!=null && userObject instanceof CheckBoxNode) {
        CheckBoxNode node = (CheckBoxNode)userObject;
        if(!node.selected && node.indeterminate) {
          setIcon(new IndeterminateIcon());
        } else {
          setIcon(null);
        }
        l.setText(node.str);
        setSelected(node.selected);
      }
      //panel.add(this, BorderLayout.WEST);
      panel.add(l);
      return panel;
    }
    return l;
  }
  @Override public void updateUI() {
    super.updateUI();
    if(panel!=null) {
      //panel.removeAll(); //??? Change to Nimbus LnF, JDK 1.6.0
      panel.updateUI();
      //panel.add(this, 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();
    //}
  }
}

ノードのチェック変更で、子ノードのチェックをすべて揃えたり、親ノードの状態変更は、TreeModelListenerを追加して行なっています。


  • JDK 1.6.0 で、Look&Feel を Nimbus などに変更すると、セルエディタなどが更新されず?表示がおかしくなる場合があるので、JTree#updateUI()を以下のようにオーバーライドして回避。
    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 で、初期Look&Feel を Nimbus にすると、ノードにフォーカスがある場合のグラデーション描画がノードの背景色で塗りつぶされてしまう?のを、以下のようにして回避。
    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(0,0,0,0));
    }
    
  • JDK 1.7.0 で、ノードのチェックボックスをクリックしても、初回だけ反応しない。
    • JDK 1.6.0_30 などは問題なし
    • TreeCellEditor#isCellEditable()をオーバーライドして、初回のみセルエディタのサイズを以下のように設定。
      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));
      }
      

参考リンク

コメント