java - 自定义组件的 JTree 自定义单元格编辑器不响应首次单击

标签 java swing jtree treetablecelleditor

我有一个 Java 应用程序,可以在树中显示用户文件系统。我用复选框、图标和文本显示该文件夹。为此,我必须为树单元创建一个自定义组件。该组件是 JLabel 的子类并包含 JCheckBox 和 JLabel。我编写了自己的渲染器和编辑器来显示和编辑组件。当您第一次单击该复选框时,它会进入编辑模式,但关闭编辑的代码不会发生。对该复选框的所有后续单击都可以正常工作。我进行了很多搜索,但无法解决这个问题。

这是我的渲染器和编辑器的代码。树已设置为允许编辑,并且渲染器和编辑器已设置。

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;
    }
}

最佳答案

  • 我的环境:Windows 7 x64
    • Java 1.6.0_41:工作正常
    • Java 1.7.0_51:我也无法首次点击
    • Java 1.8.0:工作正常

以下是在 Java 1.7.0_51 上避免此问题的一种可能实现(覆盖 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;
  }
}

关于java - 自定义组件的 JTree 自定义单元格编辑器不响应首次单击,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22150567/

相关文章:

java - Java 中日期的年差

java - Gradle/Jenkins JAR发布到GitHub

java - JCheckBoxTree初始状态问题

java - 是否可以自定义 JTree 节点?

java - 如何修改分配给JTree的对象?

java - 获取 XSD 验证错误的父元素

java - Eclipse:来自其他项目的引用源

Java swing BoxLayout 中心不起作用

Java - 使用单独的 ActionListener 更改 JTextField

java - 使用 iText 将 Swing 组件导出为 PDF