0

I have a Java app that displays the users file system in a tree. I display the folder with a checkbox, icon and text. To do this I had to created a custom component for the tree cell. The component subclasses JLabel and contains JCheckBox and JLabel. I wrote my own renderer and editor to display and edit the component. When you click on the checkbox the first time it goes into edit mode but the code to turn off editing does not happen. All subsequent clicks on the checkbox work properly. I have done a lot of searching and cannot resolve this problem.

Here is the code for my renderer and editor. The tree has been set to allow editing and the renderer and editor are set.

class MyRenderer implements TreeCellRenderer
{
    private CheckBoxPanel m_panel;

    public MyRenderer()
    {
        m_panel = new CheckBoxPanel();
    }

    @Override
    public Component getTreeCellRendererComponent(JTree tree,
                                                  Object value,
                                                  boolean selected,
                                                  boolean expanded,
                                                  boolean leaf,
                                                  int row,
                                                  boolean hasFocus) {

        DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
        MyState state = (MyState)node.getUserObject();

        m_panel.setState(state);

        return m_panel;
    }
}

class MyEditor extends AbstractCellEditor implements TreeCellEditor
{
    private CheckBoxPanel m_panel;
    private JCheckBox m_checkbox;
    private MyState m_state;

    public MyEditor()
    {
        m_panel = new CheckBoxPanel();
        // m_panel.setColor(Color.red);
    }

    @Override
    public Component getTreeCellEditorComponent(JTree tree,
                                                Object value,
                                                boolean isSelected,
                                                boolean expanded,
                                                boolean leaf,
                                                int row) {
        DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)value;
        m_state = (MyState)treeNode.getUserObject();

        m_panel.setState(m_state);

        m_checkbox = m_panel.getCheckBox();

        m_checkbox.addItemListener(new ItemListener()
        {
            @Override
            public void itemStateChanged(ItemEvent e)
            {
                fireEditingStopped();
                m_checkbox.removeItemListener(this);
            }
        });

        return m_panel;
    }

    @Override
    public Object getCellEditorValue()
    {
        m_state.setSelected(m_checkbox.isSelected());
        return m_state;
    }

    @Override
    public boolean isCellEditable(EventObject anEvent)
    {
        if (anEvent instanceof MouseEvent)
        {
            return true;
        }

        return false;
    }        
}

class CheckBoxPanel extends JPanel
{
    private JCheckBox m_checkBox;
    private JLabel m_label;

    public CheckBoxPanel()
    {
        m_checkBox = new JCheckBox();
        m_checkBox.setBackground(UIManager.getColor("Tree.background"));
        m_checkBox.setBorder(null);
        m_checkBox.setFocusable(true);

        m_label = new JLabel();
        m_label.setFont(UIManager.getFont("Tree.font"));
        m_label.setFocusable(false);

        setOpaque(false);

        setLayout(new BorderLayout());
        add(m_checkBox, BorderLayout.WEST);
        add(m_label, BorderLayout.CENTER);
    }

    public JCheckBox getCheckBox()
    {
        return m_checkBox;
    }

    public void setState(MyState _state)
    {
        m_label.setText(_state.getText());

        m_checkBox.setSelected(_state.isSelected());
    }

    public void setColor(Color _color)
    {
        m_label.setForeground(_color);
    }
}

class MyState
{
    private String m_text;
    private boolean m_selected;

    public MyState(String _text, boolean _selected)
    {
        m_text = _text;
        m_selected = _selected;
    }

    public String getText()
    {
        return m_text;
    }

    public void setText(String _text)
    {
        m_text = _text;
    }

    public boolean isSelected()
    {
        return m_selected;
    }

    public void setSelected(boolean _selected)
    {
        m_selected = _selected;
    }
}
aterai
  • 9,658
  • 4
  • 35
  • 44
user3375401
  • 481
  • 1
  • 9
  • 19

1 Answers1

1
  • My envirment: Windows 7 x64
    • Java 1.6.0_41: work OK
    • Java 1.7.0_51: not work to first click for me too
    • Java 1.8.0: work OK

Here is one possible implementation to avoid this issue on Java 1.7.0_51 (override TreeCellEditor#isCellEditable(...)):

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;

public final class TreeCellEditorFirstClickTest {
  public JComponent makeUI() {
    JTree tree = new JTree();
    TreeModel model = tree.getModel();
    DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
    Enumeration e = root.breadthFirstEnumeration();
    while (e.hasMoreElements()) {
      DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.nextElement();
      Object o = node.getUserObject();
      if (o instanceof String) {
        node.setUserObject(new CheckBoxNode((String) o, false));
      }
    }
    tree.setEditable(true);
    tree.setCellRenderer(new CheckBoxNodeRenderer());
    tree.setCellEditor(new CheckBoxNodeEditor());
    tree.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
    tree.expandRow(0);
    return new JScrollPane(tree);
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        createAndShowGUI();
      }
    });
  }
  public static void createAndShowGUI() {
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    frame.getContentPane().add(new TreeCellEditorFirstClickTest().makeUI());
    frame.setSize(320, 240);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }
}

class CheckBoxNode {
  public final String text;
  public final boolean selected;
  public CheckBoxNode(String text, boolean selected) {
    this.text = text;
    this.selected = selected;
  }
  @Override public String toString() {
    return text;
  }
}

class CheckBoxNodeRenderer implements TreeCellRenderer {
  private DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer();
  private final JCheckBox check = new JCheckBox();
  private final JPanel p = new JPanel(new BorderLayout());
  public CheckBoxNodeRenderer() {
    p.setFocusable(false);
    p.setRequestFocusEnabled(false);
    p.setOpaque(false);
    p.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);
    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;
        l.setText(node.text);
        check.setSelected(node.selected);
      }
      p.add(l);
      return p;
    }
    return l;
  }
}

class CheckBoxNodeEditor extends AbstractCellEditor implements TreeCellEditor {
  private DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer();
  private final JCheckBox check = new JCheckBox();
  private final JPanel p = new JPanel(new BorderLayout());
  private String str = null;
  public CheckBoxNodeEditor() {
    super();
    check.addActionListener(new ActionListener() {
      @Override public void actionPerformed(ActionEvent e) {
        stopCellEditing();
      }
    });
    p.setFocusable(false);
    p.setRequestFocusEnabled(false);
    p.setOpaque(false);
    p.add(check, BorderLayout.WEST);
    check.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);
    if (value instanceof DefaultMutableTreeNode) {
      Object userObject = ((DefaultMutableTreeNode) value).getUserObject();
      if (userObject instanceof CheckBoxNode) {
        CheckBoxNode node = (CheckBoxNode) userObject;
        l.setText(node.text);
        check.setSelected(node.selected);
        str = node.text;
      }
      p.add(l);
      return p;
    }
    return l;
  }
  @Override public Object getCellEditorValue() {
    return new CheckBoxNode(str, check.isSelected());
  }
  ////1.6.0_41`: work OK
  ////1.7.0_51`: not work to first click
  ////1.8.0`: work OK
  //@Override public boolean isCellEditable(EventObject e) {
  //  if (e instanceof MouseEvent && e.getSource() instanceof JTree) {
  //    return true;
  //  }
  //  return false;
  //}
  @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 = check.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" + p.getBounds());
          check.setBounds(new Rectangle(0, 0, d.width, r.height));
        }
        return true;
      }
    }
    return false;
  }
}
aterai
  • 9,658
  • 4
  • 35
  • 44
  • Why does this work? What are you doing in the isCellEditable method that makes this work? – user3375401 Mar 04 '14 at 01:02
  • You could see what the code is doing and see also [Bug ID: JDK-8023474 First mousepress doesn't start editing in JTree](http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8023474) comment. – aterai Mar 04 '14 at 02:04
  • +1..... [but AFAIK 1st. mouse click is about setFocus](http://www.coderanch.com/t/584508/GUI/java/change-DefaultCellEditor-setClickCountToStart-int-DefaultTreeCellEditor) – mKorbel Mar 06 '14 at 13:52