• 追加された行はこの色です。
  • 削除された行はこの色です。
---
category: swing
folder: FileSystemTreeWithCheckBox
title: JCheckBox付きJTreeでディレクトリ構造を表示
tags: [JTree, JCheckBox, TreeCellRenderer, TreeCellEditor, File, TreeModelListener, SwingWorker]
author: aterai
pubdate: 2011-08-15T15:43:03+09:00
description: 編集可能なJCheckBoxをノードに追加したJTreeでディレクトリ構造を表示します。
image: https://lh6.googleusercontent.com/-5ihZ2R-e4Ug/Tki-blUTxaI/AAAAAAAABA0/5KCjlm9CkSY/s800/FileSystemTreeWithCheckBox.png
hreflang:
    href: https://java-swing-tips.blogspot.com/2011/08/filesystemtree-with-jcheckbox.html
    lang: en
---
* 概要 [#summary]
編集可能な`JCheckBox`をノードに追加した`JTree`でディレクトリ構造を表示します。

#download(https://lh6.googleusercontent.com/-5ihZ2R-e4Ug/Tki-blUTxaI/AAAAAAAABA0/5KCjlm9CkSY/s800/FileSystemTreeWithCheckBox.png)

* サンプルコード [#sourcecode]
#code(link){{
class CheckBoxNodeEditor extends TriStateCheckBox implements TreeCellEditor {
  private final FileSystemView fileSystemView;
  private final JPanel panel = new JPanel(new BorderLayout());
  private DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer();
  private File file;

  public CheckBoxNodeEditor(FileSystemView fileSystemView) {
    super();
    this.fileSystemView = fileSystemView;
    this.addActionListener(new ActionListener() {
      @Override public void actionPerformed(ActionEvent e) {
        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());
    setOpaque(false);
    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);
        }
        file = node.file;
        l.setIcon(fileSystemView.getSystemIcon(file));
        l.setText(fileSystemView.getSystemDisplayName(file));
        setSelected(node.status == Status.SELECTED);
      }
      //panel.add(this, BorderLayout.WEST);
      // panel.add(this, BorderLayout.WEST);
      panel.add(l);
      return panel;
    }
    return l;
  }

  @Override public Object getCellEditorValue() {
    return new CheckBoxNode(file, 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.getX(), me.getY())) {
        //if (file == 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));
        //}
        // if (file == 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));
        // }
        return true;
      }
    }
    return false;
  }

  @Override public void updateUI() {
    super.updateUI();
    if (panel != null) {
      panel.updateUI();
    }
    //1.6.0_24 bug??? @see 1.7.0 DefaultTreeCellRenderer#updateUI()
    // 1.6.0_24 bug??? @see 1.7.0 DefaultTreeCellRenderer#updateUI()
    renderer = new DefaultTreeCellRenderer();
  }
//......
// ...
}}

* 解説 [#explanation]
このサンプルは、[[FileSystemViewを使ってディレクトリ構造をJTreeに表示する>Swing/DirectoryTree]]と、[[JTreeの葉ノードをJCheckBoxにする>Swing/CheckBoxNodeTree]]を組み合わせて作成しています。

----
`TreeCellEditor#isCellEditable(...)`をオーバーライドして、`JCheckBox`付近をクリックした場合のみ編集可能(チェックの有無を切り替えることができる)にし、ラベルやアイコンなどをクリックした場合は、編集状態にせずノードの展開や折り畳みができるように設定しています。
- `TreeCellEditor#isCellEditable(...)`をオーバーライドして`JCheckBox`の領域内をクリックした場合のみ編集可能(チェックの有無を切り替えることができる)に設定
- ラベルやアイコンなどをクリックした場合は編集状態にせずノードの展開や折り畳みができるように設定

* 参考リンク [#reference]
- [[FileSystemViewを使ってディレクトリ構造をJTreeに表示する>Swing/DirectoryTree]]
- [[JTreeの葉ノードをJCheckBoxにする>Swing/CheckBoxNodeTree]]

* コメント [#comment]
#comment
- [[JTreeのすべてのノードにJCheckBoxを追加する>Swing/CheckBoxNodeEditor]] で使用している`TreeModelListener`を追加して`JCheckBox`の状態を変更するように修正。 -- &user(aterai); &new{2012-04-13 (金) 20:12:08};
- チェックされたノード(最上位となる)の一覧をコンソールに表示する`JButton`を追加(スクリーンショットなどは面倒なので更新しない)。 -- &user(aterai); &new{2012-04-19 (木) 19:50:07};
- ノードをチェックしてから、そのディレクトリを開いても子ディレクトリにチェックが反映されない。 -- &user(aterai); &new{2012-07-31 (火) 18:15:44};
- いつも勉強させていただいております。サンプルでは`root`はデスクトップとなっていますが、もし例えば`Z:\`または`Z:\aaa`と`TOP`にしたい場合、どこを修正すれば宜しいでしょうか?ご教示をお願いいたします。 -- &user(Tiger); &new{2013-12-25 (水) 14:11:08};
-- こんばんは。このサンプルでは、`fileSystemView.getRoots()`で`Desktop`フォルダ(`Windows`の場合)を取得しているので、この箇所を、例えば`File fileSystemRoot = new File("Z:/"); /* for(File fileSystemRoot: fileSystemView.getRoots()) */ {`のように変更するのはどうでしょうか。 -- &user(aterai); &new{2013-12-25 (水) 16:34:38};
- ご教示、ありがとうございました。ご指摘のところを見落としました。やり方は理解できました。ついでに、もし`root`はデスクトップにしておいて、`C:\`を表示させないで(または展開させないで)、`X:\`,`Y:\`のみ操作させるには、どこを弄れば宜しいでしょうか?ありがとうございました。来年もよろしくお願いします。 -- &user(Tiger); &new{2013-12-26 (木) 13:36:28};
-- `fileSystemView.getRoots()`で`Desktop`フォルダを取得すると、 マイコンピュータとか、`Desktop`フォルダが`C:\`にある場合はマイドキュメントなどを選択不可にするのが、面倒な気がします。以下のように`new File(System.getProperty("user.home")+"/Desktop")`とデスクトップを決め打ちにしてノードを作ってしまうのが簡単かもしれません。 -- &user(aterai); &new{2013-12-26 (木) 21:55:24};

#code{{
final FileSystemView fileSystemView = FileSystemView.getFileSystemView();
DefaultMutableTreeNode root = new DefaultMutableTreeNode();
final DefaultTreeModel treeModel = new DefaultTreeModel(root);
File desktopFile = new File(System.getProperty("user.home")+"/Desktop");
DefaultMutableTreeNode desktop = new DefaultMutableTreeNode(new CheckBoxNode(desktopFile, Status.DESELECTED));
root.add(desktop);
for (File file: fileSystemView.getFiles(desktopFile, true)) {
  if (file.isDirectory()) {
    desktop.add(new DefaultMutableTreeNode(new CheckBoxNode(file, Status.DESELECTED)));
  }
}
for (File fileSystemRoot: Arrays.asList(new File("X:/"), new File("Y:/"))) {
  DefaultMutableTreeNode node = new DefaultMutableTreeNode(new CheckBoxNode(fileSystemRoot, Status.DESELECTED));
  desktop.add(node);
  for (File file: fileSystemView.getFiles(fileSystemRoot, true)) {
    System.out.println(file.getAbsolutePath());
    if (file.isDirectory()) {
      node.add(new DefaultMutableTreeNode(new CheckBoxNode(file, Status.DESELECTED)));
    }
  }
}
treeModel.addTreeModelListener(new CheckBoxStatusUpdateListener());
}}
- いつも勉強させていただいております。チェックしたファイルまたはフォルダーのチェックマークの外し方を教えていただけませんか? -- &user(Tiger); &new{2014-03-04 (火) 13:55:30};
-- こんばんは。マウスを使わずにチェックを外したいということですよね。このサンプルの場合、`MutableTreeNode#setUserObject(...)`でチェックを外した`new CheckBoxNode(node.file, Status.DESELECTED)`を設定し、そのあと[https://docs.oracle.com/javase/jp/8/docs/api/javax/swing/tree/DefaultTreeModel.html#nodeChanged-javax.swing.tree.TreeNode- DefaultTreeModel#nodeChanged(...)]メソッドを呼べばいいと思います。 -- &user(aterai); &new{2014-03-05 (水) 18:23:39};

#code{{
//例えば、すべてのチェックを外す場合...
private static void deselectedAll(DefaultTreeModel model, TreePath path) {
  Object o = path.getLastPathComponent();
  if (!(o instanceof DefaultMutableTreeNode)) {
    return;
  }
  DefaultMutableTreeNode node = (DefaultMutableTreeNode) o;
  o = node.getUserObject();
  if (!(o instanceof CheckBoxNode)) {
    return;
  }
  CheckBoxNode check = (CheckBoxNode) o;
  if (check.status == Status.SELECTED) {
    node.setUserObject(new CheckBoxNode(check.file, Status.DESELECTED));
    model.nodeChanged(node);
    //or: model.valueForPathChanged(path, new CheckBoxNode(check.file, Status.DESELECTED));
  } else if (!node.isLeaf() && node.getChildCount() >= 0) {
    Enumeration e = node.children();
  } else if (!node.isLeaf()) {
    // Java 9: Enumeration<TreeNode> e = node.children();
    Enumeration<?> e = node.children();
    while (e.hasMoreElements()) {
      deselectedAll(model, path.pathByAddingChild(e.nextElement()));
    }
  }
}
}}

#comment