【问题标题】:Checkbox within a JXTreeTableJXTreeTable 中的复选框
【发布时间】:2025-12-13 13:20:43
【问题描述】:

这里还是一个 Java 新手,试图为我自己的利益做一些太高级的东西。不过,这就是我需要做的:JXTreeTable 中的一个复选框。我的主要课程:

package info.chrismcgee.sky.production;

import info.chrismcgee.sky.production.treetable.NoRootTreeTableModel;

import java.awt.Checkbox;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;

import net.miginfocom.swing.MigLayout;

import org.jdesktop.swingx.JXTreeTable;

public class TestFrame extends JFrame {

    /**
     * 
     */
    private static final long serialVersionUID = -1899673458785493250L;
    private JXTreeTable treeTable;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    TestFrame frame = new TestFrame();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the frame.
     */
    public TestFrame() {
        setMinimumSize(new Dimension(600, 600));
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 546, 600);
        JPanel contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(new MigLayout("", "[grow][grow][grow][100px:n,grow][grow][grow][grow]", "[][251.00,grow][]"));
        
        List<Job> jobList = new ArrayList<Job>();
        
        List<ItemDetail> itemList = new ArrayList<ItemDetail>();
        itemList.add(new ItemDetail("N10", "N10", 2, 1000, PrintType.PAD, true));
        itemList.add(new ItemDetail("N13", "N13", 2, 2000, PrintType.PAD, true));
        
        // create and add the first job with its list of ItemDetail objects
        jobList.add(new Job(new Checkbox("Print Solutions"), "123456", ShipDate.getDate("02/28/14"), itemList));
        
        List<ItemDetail> itemList2 = new ArrayList<ItemDetail>();
        itemList2.add(new ItemDetail("P12", "Green", 1, 250, PrintType.SCREEN, true));
        itemList2.add(new ItemDetail("P12", "Purple", 1, 250, PrintType.SCREEN, true));
        itemList2.add(new ItemDetail("P12", "Gray", 1, 250, PrintType.SCREEN, true));
        
        // create and add a second job with its list of ItemDetail objects
        jobList.add(new Job(new Checkbox("Innovators Inc"), "246801", ShipDate.getDate("03/10/14"), itemList2));
        
        // we use a no root model
        NoRootTreeTableModel noRootTreeTableModel = new NoRootTreeTableModel(jobList);
        treeTable = new JXTreeTable(noRootTreeTableModel);
        treeTable.setEditable(false);
        treeTable.setRootVisible(false);
        treeTable.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));
        treeTable.getTableHeader().setReorderingAllowed(false);
        treeTable.getColumn(0).setPreferredWidth(200);
        treeTable.getColumn(1).setPreferredWidth(75);
        treeTable.getColumn(2).setPreferredWidth(15);
            treeTable.getColumn(2).setMinWidth(15);
            treeTable.getColumn(2).setMaxWidth(15);
        treeTable.getColumn(3).setPreferredWidth(50);
            treeTable.getColumn(3).setMaxWidth(50);
        treeTable.getColumn(4).setPreferredWidth(25);
        JScrollPane scrollPane = new JScrollPane(treeTable);
        scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        contentPane.add(scrollPane, "cell 0 1 7 1,grow");

    }       
}

Job 类(TreeTable 中的分支):

package info.chrismcgee.sky.production;

import java.awt.Checkbox;
import java.util.List;

import org.joda.time.LocalDate;

public class Job {

    private Checkbox cbJob;
    private String idNumber;
    private LocalDate shipDate;
    private List<ItemDetail> itemList;
    
    public Job(Checkbox cbJob, String idNumber, LocalDate shipDate, List<ItemDetail> itemList)
    {
        this.cbJob = cbJob;
        this.idNumber = idNumber;
        this.shipDate = shipDate;
        this.itemList = itemList;
    }

    public List<ItemDetail> getItemList()
    {
        return itemList;
    }
    
    public void setItemList(List<ItemDetail> itemList)
    {
        this.itemList = itemList;
    }
    
    /**
     * @return the cbJob
     */
    public Checkbox getCbJob() {
        return cbJob;
    }

    /**
     * @param cbJob the cbJob to set
     */
    public void setCbJob(Checkbox cbJob) {
        this.cbJob = cbJob;
    }

    /**
     * @return the idNumber
     */
    public String getIdNumber() {
        return idNumber;
    }

    /**
     * @param idNumber the idNumber to set
     */
    public void setIdNumber(String idNumber) {
        this.idNumber = idNumber;
    }

    /**
     * @return the shipDate
     */
    public LocalDate getShipDate() {
        return shipDate;
    }

    /**
     * @param shipDate the shipDate to set
     */
    public void setShipDate(LocalDate shipDate) {
        this.shipDate = shipDate;
    }
    
}

OrderDetail 类(TreeTable 中的叶子):

package info.chrismcgee.sky.production;


public class ItemDetail {

    private String productId;
    private String detail;
    private long numColors;
    private long numQuantity;
    private PrintType printType;
    private boolean active;
    
    public ItemDetail(String productId, String detail, long numColors, long numQuantity, PrintType printType, boolean active)
    {
        this.productId = productId;
        this.detail = detail;
        this.numColors = numColors;
        this.numQuantity = numQuantity;
        this.printType = printType;
        this.active = active;
    }

    /**
     * @return the productId
     */
    public String getProductId() {
        return productId;
    }

    /**
     * @param productId the productId to set
     */
    public void setProductId(String productId) {
        this.productId = productId;
    }

    /**
     * @return the detail
     */
    public String getDetail() {
        return detail;
    }

    /**
     * @param detail the detail to set
     */
    public void setDetail(String detail) {
        this.detail = detail;
    }

    /**
     * @return the numColors
     */
    public long getNumColors() {
        return numColors;
    }

    /**
     * @param numColors the numColors to set
     */
    public void setNumColors(long numColors) {
        this.numColors = numColors;
    }

    /**
     * @return the numQuantity
     */
    public long getNumQuantity() {
        return numQuantity;
    }

    /**
     * @param numQuantity the numQuantity to set
     */
    public void setNumQuantity(long numQuantity) {
        this.numQuantity = numQuantity;
    }

    /**
     * @return the printType
     */
    public PrintType getPrintType() {
        return printType;
    }

    /**
     * @param printType the printType to set
     */
    public void setPrintType(PrintType printType) {
        this.printType = printType;
    }

    /**
     * @return the active
     */
    public boolean isActive() {
        return active;
    }

    /**
     * @param active the active to set
     */
    public void setActive(boolean active) {
        this.active = active;
    }

}

最后是 NoRootTreeTableModel 类,它扩展了 AbstractTreeTableModel 类,因此树的根不显示:

package info.chrismcgee.sky.production.treetable;

import info.chrismcgee.sky.production.ItemDetail;
import info.chrismcgee.sky.production.Job;

import java.util.List;

import org.jdesktop.swingx.treetable.AbstractTreeTableModel;

public class NoRootTreeTableModel extends AbstractTreeTableModel {
    
    private final static String[] COLUMN_NAMES = {"Name/Product", "Job # / Detail", "T",
        "Colors", "Quantity", "Total"};
    
    private List<Job> jobList;
    
    public NoRootTreeTableModel(List<Job> jobList)
    {
        super(new Object());
        this.jobList = jobList;
    }

    @Override
    public int getColumnCount() {
        return COLUMN_NAMES.length;
    }

    @Override
    public String getColumnName(int column)
    {
        return COLUMN_NAMES[column];
    }
    
    @Override
    public boolean isCellEditable(Object node, int column)
    {
        return false;
    }
    
    @Override
    public boolean isLeaf(Object node)
    {
        return node instanceof ItemDetail;
    }

    @Override
    public int getChildCount(Object parent) {
        if (parent instanceof Job) {
            Job job = (Job) parent;
            return job.getItemList().size();
        }
        return jobList.size();
    }
    
    @Override
    public Object getChild(Object parent, int index) {
        if (parent instanceof Job) {
            Job job = (Job) parent;
            return job.getItemList().get(index);
        }
        return jobList.get(index);
    }
    
    @Override
    public int getIndexOfChild(Object parent, Object child) {
        Job job = (Job) parent;
        ItemDetail item = (ItemDetail) child;
        return job.getItemList().indexOf(item);
    }
    
    @Override
    public Object getValueAt(Object node, int column) {
        if (node instanceof Job) {
            Job job = (Job) node;
            switch (column) {
                case 0:
                    return job.getCbJob();
                case 1:
                    return job.getIdNumber();
            }
        } else if (node instanceof ItemDetail) {
            ItemDetail item = (ItemDetail) node;
            switch (column) {
                case 0:
                    return item.getProductId();
                case 1:
                    return item.getDetail();
                case 2:
                    return item.getPrintType();
                case 3:
                    return item.getNumColors();
                case 4:
                    return item.getNumQuantity();
                case 5:
                    return item.getNumColors() * item.getNumQuantity();
            }
        }
        return null;
    }

}

我知道这是很多代码,但我想不出一种快速的方法来减少它并让它按照我需要的方式工作。实际上,它已经减少了很多。 (我遗漏了一些通常也在那个 JFrame 中的东西。)

当代码运行时,我没有得到分支的复选框;相反,我得到了似乎是复选框代码的字符串表示形式:

嗯?这是怎么发生的,更重要的是,我怎样才能让复选框显示出来?

【问题讨论】:

    标签: java swing jcheckbox swingx jxtreetable


    【解决方案1】:

    您不应该将组件添加到模型中,而应该提供TreeCellRenderer 来呈现节点的值。该节点可能正在使用分配给它的用户对象的toString 值...

    查看How to Use TreesCustomizing a Tree's Display 了解更多详情

    直接从Customizing a Tree's Display 撕下的示例

    我将由您来创建编辑器并确定应如何选中复选框

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Component;
    import java.awt.Dimension;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Vector;
    import javax.swing.ButtonGroup;
    import javax.swing.JCheckBox;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JRadioButton;
    import javax.swing.JScrollPane;
    import javax.swing.JTree;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    import javax.swing.event.TreeModelEvent;
    import javax.swing.event.TreeModelListener;
    import javax.swing.tree.TreeCellRenderer;
    import javax.swing.tree.TreeModel;
    import javax.swing.tree.TreePath;
    import javax.swing.tree.TreeSelectionModel;
    
    public class TreeExample {
    
        public static void main(String[] args) {
            //Schedule a job for the event-dispatching thread:
            //creating and showing this application's GUI.
            javax.swing.SwingUtilities.invokeLater(new Runnable() {
                public void run() {
    
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    
                        //Create and set up the window.
                        JFrame frame = new JFrame("GenealogyExample");
                        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
                        //Create and set up the content pane.
                        GenealogyExample newContentPane = new GenealogyExample();
                        newContentPane.setOpaque(true); //content panes must be opaque
                        frame.setContentPane(newContentPane);
    
                        //Display the window.
                        frame.pack();
                        frame.setLocationRelativeTo(null);
                        frame.setVisible(true);
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
                }
            });
        }
    
        public static class GenealogyExample extends JPanel
                implements ActionListener {
    
            private JTree tree;
            private static String SHOW_ANCESTOR_CMD = "showAncestor";
    
            public GenealogyExample() {
                super(new BorderLayout());
    
                //Construct the panel with the toggle buttons.
                JRadioButton showDescendant
                        = new JRadioButton("Show descendants", true);
                final JRadioButton showAncestor
                        = new JRadioButton("Show ancestors");
                ButtonGroup bGroup = new ButtonGroup();
                bGroup.add(showDescendant);
                bGroup.add(showAncestor);
                showDescendant.addActionListener(this);
                showAncestor.addActionListener(this);
                showAncestor.setActionCommand(SHOW_ANCESTOR_CMD);
                JPanel buttonPanel = new JPanel();
                buttonPanel.add(showDescendant);
                buttonPanel.add(showAncestor);
    
                //Construct the tree.
                tree = new JTree(new GenealogyModel(getGenealogyGraph()));
          tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
                tree.setCellRenderer(new CheckBoxTreeCellRenderer());
    
                JScrollPane scrollPane = new JScrollPane(tree);
                scrollPane.setPreferredSize(new Dimension(200, 200));
    
                //Add everything to this panel.
                add(buttonPanel, BorderLayout.PAGE_START);
                add(scrollPane, BorderLayout.CENTER);
            }
    
            /**
             * Required by the ActionListener interface. Handle events on the
             * showDescendant and showAncestore buttons.
             */
            public void actionPerformed(ActionEvent ae) {
                if (ae.getActionCommand() == SHOW_ANCESTOR_CMD) {
                    showAncestor(true);
                } else {
                    showAncestor(false);
                }
            }
    
            public void showAncestor(boolean b) {
            Object newRoot = null;
            TreePath path = tree.getSelectionModel().getSelectionPath();
            if (path != null) {
                newRoot = path.getLastPathComponent();
            }
            ((GenealogyModel)tree.getModel()).showAncestor(b, newRoot);
        }
    
            /**
             * Constructs the genealogy graph used by the model.
             */
            public Person getGenealogyGraph() {
                //the greatgrandparent generation
                Person a1 = new Person("Jack (great-granddaddy)");
                Person a2 = new Person("Jean (great-granny)");
                Person a3 = new Person("Albert (great-granddaddy)");
                Person a4 = new Person("Rae (great-granny)");
                Person a5 = new Person("Paul (great-granddaddy)");
                Person a6 = new Person("Josie (great-granny)");
    
                //the grandparent generation
                Person b1 = new Person("Peter (grandpa)");
                Person b2 = new Person("Zoe (grandma)");
                Person b3 = new Person("Simon (grandpa)");
                Person b4 = new Person("James (grandpa)");
                Person b5 = new Person("Bertha (grandma)");
                Person b6 = new Person("Veronica (grandma)");
                Person b7 = new Person("Anne (grandma)");
                Person b8 = new Person("Renee (grandma)");
                Person b9 = new Person("Joseph (grandpa)");
    
                //the parent generation
                Person c1 = new Person("Isabelle (mom)");
                Person c2 = new Person("Frank (dad)");
                Person c3 = new Person("Louis (dad)");
                Person c4 = new Person("Laurence (dad)");
                Person c5 = new Person("Valerie (mom)");
                Person c6 = new Person("Marie (mom)");
                Person c7 = new Person("Helen (mom)");
                Person c8 = new Person("Mark (dad)");
                Person c9 = new Person("Oliver (dad)");
    
                //the youngest generation
                Person d1 = new Person("Clement (boy)");
                Person d2 = new Person("Colin (boy)");
    
                Person.linkFamily(a1, a2, new Person[]{b1, b2, b3, b4});
                Person.linkFamily(a3, a4, new Person[]{b5, b6, b7});
                Person.linkFamily(a5, a6, new Person[]{b8, b9});
                Person.linkFamily(b3, b6, new Person[]{c1, c2, c3});
                Person.linkFamily(b4, b5, new Person[]{c4, c5, c6});
                Person.linkFamily(b8, b7, new Person[]{c7, c8, c9});
                Person.linkFamily(c4, c7, new Person[]{d1, d2});
    
                return a1;
            }
    
        }
    
        public static class CheckBoxTreeCellRenderer implements TreeCellRenderer {
    
            private JCheckBox cb = new JCheckBox();
    
            @Override
            public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
    
                cb.setText(value.toString());
                cb.setOpaque(selected);
                if (selected) {
                    cb.setBackground(UIManager.getColor("Tree.selectionBackground"));
                    cb.setForeground(UIManager.getColor("Tree.selectionForeground"));
                } else {
                    cb.setBackground(tree.getBackground());
                    cb.setForeground(tree.getForeground());
                }
    
                return cb;
    
            }
    
        } 
    
        public static class GenealogyModel implements TreeModel {
    
            private boolean showAncestors;
            private Vector<TreeModelListener> treeModelListeners
                    = new Vector<TreeModelListener>();
            private Person rootPerson;
    
            public GenealogyModel(Person root) {
                showAncestors = false;
                rootPerson = root;
            }
    
            /**
             * Used to toggle between show ancestors/show descendant and to change the
             * root of the tree.
             */
            public void showAncestor(boolean b, Object newRoot) {
                showAncestors = b;
                Person oldRoot = rootPerson;
                if (newRoot != null) {
                    rootPerson = (Person) newRoot;
                }
                fireTreeStructureChanged(oldRoot);
            }
    
    //////////////// Fire events //////////////////////////////////////////////
            /**
             * The only event raised by this model is TreeStructureChanged with the root
             * as path, i.e. the whole tree has changed.
             */
            protected void fireTreeStructureChanged(Person oldRoot) {
                int len = treeModelListeners.size();
                TreeModelEvent e = new TreeModelEvent(this,
                        new Object[]{oldRoot});
                for (TreeModelListener tml : treeModelListeners) {
                    tml.treeStructureChanged(e);
                }
            }
    
    //////////////// TreeModel interface implementation ///////////////////////
            /**
             * Adds a listener for the TreeModelEvent posted after the tree changes.
             */
            public void addTreeModelListener(TreeModelListener l) {
                treeModelListeners.addElement(l);
            }
    
            /**
             * Returns the child of parent at index index in the parent's child array.
             */
            public Object getChild(Object parent, int index) {
                Person p = (Person) parent;
                if (showAncestors) {
                    if ((index > 0) && (p.getFather() != null)) {
                        return p.getMother();
                    }
                    return p.getFather();
                }
                return p.getChildAt(index);
            }
    
            /**
             * Returns the number of children of parent.
             */
            public int getChildCount(Object parent) {
                Person p = (Person) parent;
                if (showAncestors) {
                    int count = 0;
                    if (p.getFather() != null) {
                        count++;
                    }
                    if (p.getMother() != null) {
                        count++;
                    }
                    return count;
                }
                return p.getChildCount();
            }
    
            /**
             * Returns the index of child in parent.
             */
            public int getIndexOfChild(Object parent, Object child) {
                Person p = (Person) parent;
                if (showAncestors) {
                    int count = 0;
                    Person father = p.getFather();
                    if (father != null) {
                        count++;
                        if (father == child) {
                            return 0;
                        }
                    }
                    if (p.getMother() != child) {
                        return count;
                    }
                    return -1;
                }
                return p.getIndexOfChild((Person) child);
            }
    
            /**
             * Returns the root of the tree.
             */
            public Object getRoot() {
                return rootPerson;
            }
    
            /**
             * Returns true if node is a leaf.
             */
            public boolean isLeaf(Object node) {
                Person p = (Person) node;
                if (showAncestors) {
                    return ((p.getFather() == null)
                            && (p.getMother() == null));
                }
                return p.getChildCount() == 0;
            }
    
            /**
             * Removes a listener previously added with addTreeModelListener().
             */
            public void removeTreeModelListener(TreeModelListener l) {
                treeModelListeners.removeElement(l);
            }
    
            /**
             * Messaged when the user has altered the value for the item identified by
             * path to newValue. Not used by this model.
             */
            public void valueForPathChanged(TreePath path, Object newValue) {
                System.out.println("*** valueForPathChanged : "
                        + path + " --> " + newValue);
            }
        }
    
        public static class Person {
    
            Person father;
            Person mother;
            List<Person> children;
            private String name;
    
            public Person(String name) {
                this.name = name;
                mother = father = null;
                children = new ArrayList<Person>();
            }
    
            /**
             * Link together all members of a family.
             *
             * @param pa the father
             * @param ma the mother
             * @param kids the children
             */
            public static void linkFamily(Person pa,
                    Person ma,
                    Person[] kids) {
                for (Person kid : kids) {
                    pa.children.add(kid);
                    ma.children.add(kid);
                    kid.father = pa;
                    kid.mother = ma;
                }
            }
    
    /// getter methods ///////////////////////////////////
            public String toString() {
                return name;
            }
    
            public String getName() {
                return name;
            }
    
            public Person getFather() {
                return father;
            }
    
            public Person getMother() {
                return mother;
            }
    
            public int getChildCount() {
                return children.size();
            }
    
            public Person getChildAt(int i) {
                return (Person) children.get(i);
            }
    
            public int getIndexOfChild(Person kid) {
                return children.indexOf(kid);
            }
        }
    }
    

    【讨论】:

    • 好的,你能告诉我如何使用TreeCellRenderer 让复选框出现在树表中吗?对不起,我这么无知。
    • 检查链接,尝试示例
    • 是的,我已经阅读了这些链接两次。有很多关于如何自定义树的外观和感觉、更改节点图标和设置工具提示文本的信息。我没有看到任何关于在树中有复选框或其他此类项目的信息。我认为这种事情应该有点普遍。甚至 Windows 也可以在资源管理器中的所有文件夹和文件上显示复选框,包括在文件夹树中。
    • 有一个例子演示了如何使用 DefaultTreeCellRenderer 来改变叶子节点的图标...这是开始的地方
    • 你可能想看看编辑器和渲染器。除了文本组件之外,JTree 和 JTable 是 Swing 中最复杂的组件,而您正在处理两者的组合,就个人而言,这不是一个很好的起点
    【解决方案2】:

    Sturm 提供了大部分解决方案。

    谜题中缺少的部分是您可以像这样从JXTreeTable 获取JTree

    JTree tree = (JTree) treeTable.getCellRenderer(0, treeTable.getHierarchicalColumn())
    

    这有点小技巧,但我不知道他们是否有更好的 API 来解决它。

    【讨论】:

    • 谢谢,我只是在寻找访问JTree 的方法。这可能节省了我几个小时!
    【解决方案3】:

    经过几分钟的谷歌搜索,我找到了这个CheckBox node tree sample、这个blog post about JXTreeTable with CheckBoxes,还有这个very simple solution

    这不是你要找的吗?

    【讨论】:

    • DuncanKinnear,谢谢你的链接,但它仍然不是我想要的。最接近的是您的最后一个链接,即 huionn 的博客文章。当我发布赏金时,我特别要求提供一些个性化的、一对一的、逐步的帮助,以便在我的特定情况下完成这项工作,因为这是我学习的最佳方式。不仅仅是被抛出一组链接并被告知“你应该能够自己找到这些”。我意识到我的要求很高,我快要发疯了,因为我还没有找到一个好人来帮助我解决这个问题。
    最近更新 更多