JTree - TreeCellEditor 实现更改节点对象 Class

JTree - TreeCellEditor Implemetation changing the Node Object Class

我正在尝试构建一个带有 TestObjectCheckBoxNode 叶子的 JTree,其中每个叶子都包含一个 TestObject,一切似乎都正常工作,除非我选中叶子中的框,它正在将节点从 TestObjectCheckBoxNode 更改为 CheckBoxNode 超类。我知道这发生在 TreeCellEditor 实现中,特别是 CheckBoxNodeEditor:getCellEditorValue(),因为它正在从该节点的 UI 创建更新的 TreeCellRenderer。

CheckBoxNode checkBoxNode = 
  new CheckBoxNode(checkBoxPanel.label.getText(),
                   checkBoxPanel.checkBox.isSelected());
return checkBoxNode;

我完全不知道如何以我可以访问 CheckBoxNodeEditor 中选定节点的 TestObject 的方式执行此操作,所以我可以这样做:

TestObjectCheckBoxNode testObjectCheckBoxNode = 
  new TestObjectCheckBoxNode(testObject,
                             checkBoxPanel.checkBox.isSelected());
return testObjectCheckBoxNode;

下面是完整的代码:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.font.TextAttribute;
import java.util.Collections;
import java.util.Enumeration;
import java.util.EventObject;
import java.util.Vector;

import javax.swing.AbstractCellEditor;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

public class JCheckBoxTreeTest 
{

  private TestObjectCheckBoxTree tree;

  public static void main(String... s)
  {
    new JCheckBoxTreeTest();
  }

  public JCheckBoxTreeTest() 
  {
    JFrame frame = new JFrame("JCheckBoxTreeTest Tree");


    Vector rootVector = new Category("Root", new Object[]
        {              
            new Category("POI", 
                new TestObjectCheckBoxNode[] {
                    new TestObjectCheckBoxNode(new TestObject("TestObject 1"),true),
                    new TestObjectCheckBoxNode(new TestObject("TestObject 2"),true),
            }),
        });




    tree = new TestObjectCheckBoxTree(rootVector);

    tree.addTreeSelectionListener(new TreeSelectionListener() 
    {
      public void valueChanged(TreeSelectionEvent e) 
      {
        DefaultMutableTreeNode node = (DefaultMutableTreeNode)tree.getLastSelectedPathComponent();

        if (node == null) return;

        Object userObject = ((DefaultMutableTreeNode) node).getUserObject();

        System.err.println("node: " + node.toString());
        System.err.println("userObject: " + userObject.toString());

      }
    });

    tree.expandAll();

    JScrollPane scrollPane = new JScrollPane(tree);
    frame.getContentPane().add(scrollPane, BorderLayout.NORTH);
    frame.setSize(360, 600);
    frame.setVisible(true);
  }



  class TestObject
  {
    String name;  
    public TestObject(String inStr)
    {
      name = inStr;
    }
  }

  public class CheckBoxNode 
  {
    String text;

    boolean selected;

    public CheckBoxNode(String text, boolean selected) 
    {
      this.text = text;
      this.selected = selected;
    }

    public boolean isSelected() { return selected; }

    public void setSelected(boolean newValue) { selected = newValue; }

    public String getText() { return text; }

    public void setText(String newValue) { text = newValue; }

    public String toString() { return getClass().getName() + "[" + text + "/" + selected + "]"; }
  }

  public class TestObjectCheckBoxNode extends CheckBoxNode
  {
    TestObject testObject;

    public TestObjectCheckBoxNode(TestObject testObject, boolean selected) 
    {
      super(testObject.name, selected);
      this.testObject = testObject;
    }  
  }




  public class CheckBoxTree extends JTree
  {    
    public CheckBoxTree(Vector rootVector)
    {     
      super(rootVector);      

      CheckBoxNodeRenderer renderer = new CheckBoxNodeRenderer();
      setCellRenderer(renderer);
      setCellEditor(new CheckBoxNodeEditor());
      setEditable(true);
    }   

    public void expandAll()
    {      
      expandAll(this, new TreePath(((DefaultMutableTreeNode)this.treeModel.getRoot()).getPath()), true);
    }


    private void expandAll(JTree tree, TreePath path, boolean expand) {
      TreeNode node = (TreeNode) path.getLastPathComponent();

      if (node.getChildCount() >= 0) {
        Enumeration<? extends TreeNode> enumeration = node.children();
        while (enumeration.hasMoreElements()) {
          TreeNode treeNode = enumeration.nextElement();
          TreePath treePath = path.pathByAddingChild(treeNode);

          expandAll(tree, treePath, expand);
        }
      }

      if (expand) {
        tree.expandPath(path);
      } else {
        tree.collapsePath(path);
      }
    }
  }

  class CheckBoxPanel extends JPanel
  {
    public JCheckBox checkBox;
    public JLabel label;

    public CheckBoxPanel() 
    { 
      super();

      checkBox = new JCheckBox();
      label = new JLabel();

      checkBox.setBorder(new EmptyBorder(0, 0, 0, 0));

      add(checkBox);
      add(label);
    }
  }

  class CheckBoxNodeRenderer implements TreeCellRenderer 
  {
    private CheckBoxPanel leafRenderer = new CheckBoxPanel();

    private DefaultTreeCellRenderer nonLeafRenderer = new DefaultTreeCellRenderer();

    Color selectionBorderColor, selectionForeground, selectionBackground,
    textForeground, textBackground;

    protected CheckBoxPanel getLeafRenderer() {
      return leafRenderer;
    }

    public CheckBoxNodeRenderer() {
      Font fontValue;
      fontValue = UIManager.getFont("Tree.font");
      if (fontValue != null)
      {
        leafRenderer.checkBox.setFont(fontValue);
        leafRenderer.label.setFont(fontValue);

        //set the nonLeaf text to bold
        nonLeafRenderer.setFont(fontValue.deriveFont(Collections.singletonMap(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD)));
      }


      Boolean booleanValue = (Boolean) UIManager.get("Tree.drawsFocusBorderAroundIcon");
      leafRenderer.checkBox.setFocusPainted((booleanValue != null) && (booleanValue.booleanValue()));

      selectionBorderColor = UIManager.getColor("Tree.selectionBorderColor");
      selectionForeground = UIManager.getColor("Tree.selectionForeground");
      selectionBackground = UIManager.getColor("Tree.selectionBackground");
      textForeground = UIManager.getColor("Tree.textForeground");
      textBackground = UIManager.getColor("Tree.textBackground");
    }

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

      Component returnValue;
      if (leaf) 
      {

        String stringValue = tree.convertValueToText(value, selected,
            expanded, leaf, row, false);

        leafRenderer.checkBox.setSelected(false);
        leafRenderer.label.setText(stringValue);

        leafRenderer.setEnabled(tree.isEnabled());

        if (selected) {
          leafRenderer.setForeground(selectionForeground);
          leafRenderer.setBackground(selectionBackground);
        } else {
          leafRenderer.setForeground(textForeground);
          leafRenderer.setBackground(textBackground);
        }

        if ((value != null) && (value instanceof DefaultMutableTreeNode)) {
          Object userObject = ((DefaultMutableTreeNode) value)
              .getUserObject();
          if (userObject instanceof CheckBoxNode) {
            CheckBoxNode node = (CheckBoxNode) userObject;                                        
            leafRenderer.checkBox.setSelected(node.isSelected());
            leafRenderer.label.setText(node.getText());
          }
        }
        returnValue = leafRenderer;
      } 
      else 
      {
        returnValue = nonLeafRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
      }
      return returnValue;
    }
  }

  class CheckBoxNodeEditor extends AbstractCellEditor implements TreeCellEditor {

    CheckBoxNodeRenderer renderer = new CheckBoxNodeRenderer();
      TestObject testObject;
      
    ChangeEvent changeEvent = null;

    public Object getCellEditorValue() 
    {
      CheckBoxPanel checkBoxPanel = renderer.getLeafRenderer();
      
          if (testObject != null) 
          { 
            return new TestObjectCheckBoxNode(testObject, checkBoxPanel.checkBox.isSelected());
          }
          else 
          { 
            return new CheckBoxNode(checkBoxPanel.label.getText(), checkBoxPanel.checkBox.isSelected()); 
          }
    }

    public boolean isCellEditable(EventObject event) {
      boolean returnValue = false;

      if (event instanceof MouseEvent) {
        MouseEvent mouseEvent = (MouseEvent) event;

        JTree tree = (JTree)event.getSource();

        TreePath path = tree.getPathForLocation(mouseEvent.getX(), mouseEvent.getY());
        if (path != null) {
          Object node = path.getLastPathComponent();
          if ((node != null) && (node instanceof DefaultMutableTreeNode)) {
            DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) node;
            Object userObject = treeNode.getUserObject();
            returnValue = ((treeNode.isLeaf()) && (userObject instanceof CheckBoxNode));
          }
        }
      }
      return returnValue;
    }

    public Component getTreeCellEditorComponent(JTree tree, Object value,
        boolean selected, boolean expanded, boolean leaf, int row) {
      
      Object userObject = ((DefaultMutableTreeNode)value).getUserObject();
                 
          if (userObject instanceof TestObjectCheckBoxNode) 
          {            
              testObject = ((TestObjectCheckBoxNode)userObject).testObject;             
          }
          else 
          {
              testObject = null;
          }
          
      Component editor = renderer.getTreeCellRendererComponent(tree, value,
          true, expanded, leaf, row, true);

      // editor always selected / focused
      ItemListener itemListener = new ItemListener() {
        public void itemStateChanged(ItemEvent itemEvent) {
          if (stopCellEditing()) {
            fireEditingStopped();
          }
        }
      };

      if (editor instanceof CheckBoxPanel) 
      {
        ((CheckBoxPanel) editor).checkBox.addItemListener(itemListener);
      }

      return editor;
    }
  }



  class Category extends Vector 
  {
    String name;

    public Category(String name) 
    {
      this.name = name;
    }

    public Category(String name, Object elements[]) 
    {
      this.name = name;
      for (int i = 0, n = elements.length; i < n; i++) 
      {
        add(elements[i]);
      }
    }

    public String toString() 
    {
      return "[" + name + "]";
    }
  }



  class TestObjectCheckBoxTree extends CheckBoxTree
  {  
    public TestObjectCheckBoxTree(Vector rootVector)
    {     
      super(rootVector);
    }    
  }  
}

编辑树节点时Swing调用CheckBoxNodeEditor.getTreeCellEditorComponent(),当前节点作为value参数。稍后当您停止编辑它调用的节点时 CheckBoxNodeEditor.getCellEditorValue().

你需要做什么:如果 getTreeCellEditorComponent() 中的 value 是一个 TestObjectCheckBoxNode 然后将其 testObject 字段存储到 [=19= 的实例字段中].

稍后调用 getCellEditorValue() 时,如果存储了 testObject,您可以 return a TestObjectCheckBoxNode,否则 return a ChechBoxNode .

代码可能如下所示(缩写)示例:

class CheckBoxNodeEditor extends AbstractCellEditor implements TreeCellEditor {
    CheckBoxNodeRenderer renderer = new CheckBoxNodeRenderer();
    TestObject testObject;

    public Object getCellEditorValue() {
        CheckBoxPanel checkBoxPanel = renderer.getLeafRenderer();
        if (testObject != null) {
            return new TestObjectCheckBoxNode(testObject, checkBoxPanel.checkBox.isSelected());
        } else {
            return new CheckBoxNode(checkBoxPanel.label.getText(), checkBoxPanel.checkBox.isSelected());
        }
    }

    public Component getTreeCellEditorComponent(
        JTree tree, Object value,
        boolean selected, boolean expanded, boolean leaf, int row
    ) {
        Object realValue = ((DefaultMutableTreeNode) value).getUserObject();
        if (realValue instanceof TestObjectCheckBoxNode n) {
            testObject = n.testObject;
        } else {
            testObject = null;
        }
        // remaining code from your getTreeCellEditorComponent method
    }
    // other code removed for brevity
}

如果你不能使用新的pattern matching instanceof operator(因为你没有使用Java16或更高版本)getTreeCellEditorComponent开头的if语句需要写成

    public Component getTreeCellEditorComponent(
        JTree tree, Object value,
        boolean selected, boolean expanded, boolean leaf, int row
    ) {
        Object realValue = ((DefaultMutableTreeNode) value).getUserObject();
        if (realValue instanceof TestObjectCheckBoxNode) {
            TestObjectCheckBoxNode n = (TestObjectCheckBoxNode) value;
            testObject = n.testObject;
        } else {
            testObject = null;
        }
        // remaining code from your getTreeCellEditorComponent method
    }

为什么getTreeCellEditorComponent中的valueDefaultMutableTreeNode

解释是 JTree 只适用于 TreeNode 个实例。如果您使用 VectorHashtableObject[] 创建 JTree,它会将这些包含在 JTree.DynamicUtilTreeNode 实例中的值包装起来,并将您的对象设置为 userObject 属性 在他们身上。

JTree.DynamicUtilTreeNode 扩展自 DefaultMutableTreeNode