Search code examples
javaswinguser-interfacelayoutjtree

Jtrees and combobox layout


So i have a JTree which will has a varying number of leaves and nodes and i need to add JComboBoxes next to the leaves of the tree but no other part of the tree.I have tried using the screen position of the leaves and border layout to achieve this but the boxes always end up off and it gets very bad when i have a lot of leaves and they also seem to just localize in 1 position in the frame and keep squezing themselves thinner with every new combobox added. How could i possibly achieve what im looking for ?


Solution

  • i need to add JComboBoxes next to the leaves of the tree but no other part of the tree

    Consider creating your own TreeCellRenderer that incorporates a JComboBox directly into the JTree. To allow for editing of the JComboBox you will also need to implement a TreeCellEditor that handles the editing component and actions that occur to that component when editing is complete. Below is a very simple example that places a JComboBox next to a JLabel in the leaves of a JTree

    JFrame frame = new JFrame("Test");
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
    DefaultTreeModel model = new DefaultTreeModel(root);
    final JTree tree = new JTree(model);
    
    //flyweight pattern components
    //editor
    final JComboBox editorComboBox = new JComboBox();
    final JComboBox viewComboBox = new JComboBox();
    final Box box = Box.createHorizontalBox();
    final JLabel myLabel = new JLabel();
    myLabel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 10));
    box.add(myLabel);
    box.add(Box.createHorizontalGlue());
    box.add(viewComboBox);
    //Custom Renderer
    DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer (){
        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
            super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
            if ( leaf ){
                if ( value instanceof MyLeafNode ){
                    MyLeafNode node = (MyLeafNode)value;
                    viewComboBox.removeAllItems();
                    myLabel.setText(value.toString());
                    for ( String item : node.items ){
                        viewComboBox.addItem(item);
                    }
                    viewComboBox.setSelectedItem(node.selected);
                    return box;
                }
            }
            return this;
        }
    };
    
    //Custom Editor
    final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree, renderer){
    
        final ActionListener actionListener = new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
                cancelCellEditing();
                tree.repaint();
            }
        };
    
        @Override
        public boolean isCellEditable(EventObject e){
            if ( e.getSource() instanceof JTree ){
                JTree tree = (JTree)e.getSource();
                if ( tree.getLastSelectedPathComponent() == null ){
                    return false;
                }
                DefaultMutableTreeNode o = (DefaultMutableTreeNode)tree.getLastSelectedPathComponent();
                return o.isLeaf();
            }
            return false;
        }
    
        @Override
        public void cancelCellEditing(){
            super.cancelCellEditing();
            DefaultMutableTreeNode node = (DefaultMutableTreeNode)tree.getLastSelectedPathComponent();
            if ( node instanceof MyLeafNode) {
                String sel = editorComboBox.getSelectedItem().toString();
                MyLeafNode mln = (MyLeafNode)node;
                mln.selected = sel;
                editorComboBox.removeActionListener(actionListener);
                tree.repaint();
            }
        }
    
        @Override
        public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
            if ( leaf ){
                if ( tree.getLastSelectedPathComponent() instanceof MyLeafNode ){
                    MyLeafNode o = (MyLeafNode)tree.getLastSelectedPathComponent();
                    editorComboBox.removeAllItems();
                    for ( String item : o.items ){
                        editorComboBox.addItem(item);
                    }
                    editorComboBox.setSelectedItem(o.selected);
                    editorComboBox.addActionListener(actionListener);
                }
                return editorComboBox;
    
            }
            return super.getTreeCellEditorComponent(tree, value, isSelected, expanded, leaf, row);
        }
    
    };
    tree.setCellRenderer(renderer);
    TreePath path = new TreePath(new TreeNode[]{root});
    tree.expandPath(path);
    
    for ( int i = 0; i < 2; i++ ){
        DefaultMutableTreeNode p = new DefaultMutableTreeNode("P" + i);
        model.insertNodeInto(p, root, i);
        for ( int j = 0; j < 2; j++ ){
            String[] items = {"Item 1", "Item 2", "Item 3", "Item 4"};
            MyLeafNode n = new MyLeafNode("N" + j, items);
            model.insertNodeInto(n, p, j);
        }
        path = new TreePath(new TreeNode[]{root, p});
        tree.expandPath(path);
    }
    tree.setCellEditor(editor);
    tree.setEditable(true);
    
    JScrollPane scroller = new JScrollPane(tree);
    frame.add(scroller);
    frame.pack();
    frame.setVisible(true);
    

    Where MyLeafNode is a custom class used to store JComboBox specific data:

    public class MyLeafNode extends DefaultMutableTreeNode{
    
        private String[] items;
        private String selected;
    
        public MyLeafNode(String name, String...items){
            super(name);
            this.items = items;
            this.selected = items[0];
        }
    
    }