JTree : Update tree after deleting nodes

2.6k views Asked by At

I am using my own custom model for JXTree which extends from AbstractTreeTableModel. So the option of reload / removeNodeFromParent is not there.

I have tried using TreeModelListener and treeModelListener.treeNodesRemoved(event) call with every input option possible. Never is my tree's GUI getting updated. Unless i call tree.updateUI() the changed structure is not getting reflected.(But that call is updating the entire tree and i just want the deleted node to be refreshed). I am using my custom tree editor and tree renderer. I have not written any custom tree listener.

So the question i ask is : Does listener.treeNodesRemoved() implicitly call some code that would have the same result as tree.updateUI(). Or do i need to write some code myself to refresh that particular parent node from which the child was deleted. Is the listener call not working because i am using custom tree editor and renderer.

EDIT :

I am posting an SSCCE. In this case i am not using any tree editor or rendrer but the issue can be seen using this code also.

    public class TestListener  extends JFrame{

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {        
                new TestListener();
            }
        });
    }

    public TestListener(){
        Departement dept1 = new Departement("1ST DEPARTMENT");
        Departement dept2 = new Departement("2ND DEPARTMENT");
        Employee emp1 = new Employee("1ST Employee");
        Employee emp2 =  new Employee("2ND Employee");
        Employee emp3 = new Employee("3rd Employee");
        final Employee emp4 =  new Employee("4th Employee");
        ArrayList<Employee> empList1 = new ArrayList<Employee>();
        empList1.add(emp1);
        empList1.add(emp2);
        final ArrayList<Employee> empList2 = new ArrayList<Employee>();
        empList2.add(emp3);
        empList2.add(emp4);
        dept1.setEmpList(empList1);
        dept2.setEmpList(empList2);
        ArrayList<Departement> deptList = new ArrayList<Departement>();
        deptList.add(dept1);
        deptList.add(dept2);
        TestModel model = new TestModel(deptList);
            final JXTree rootTree = new JXTree(model);
        rootTree.setShowsRootHandles(true); // to show collapse and expand icons
        rootTree.setEditable(true);
        rootTree.setRootVisible(false); //not to show the top root
        rootTree.setVisible(true);
        JButton button = new JButton("Delete Node");
            button.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent arg0) {

                    JXTree rootNew = rootTree;
                    empList2.remove(emp4);
                    TestModel model = (TestModel) rootNew.getModel();
                    TreeModelEvent event = new TreeModelEvent(this, 
                            new Object[] {rootNew.getPathForRow(3)}, // harcoding because i know i am deleting from dept2
                            new int[]{1}, //hardcoding as i am removing emp4
                            new Object[] {emp4});
                    TreeModelListener[] listeners = model.getTreeModelListeners();
                    for (TreeModelListener listener : listeners) {

                        listener.treeNodesRemoved(event);
                    }

                }
            });   

        this.setLayout(new GridLayout(0, 1));
        this.getContentPane().add(new JScrollPane(rootTree));
        this.getContentPane().add(button);
        this.setSize(new java.awt.Dimension(400, 400));
        this.setLocation(280, 50);
        this.setVisible(true);
        setVisible(true);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        pack();


    }
}

TestModel Class

public class TestModel extends AbstractTreeTableModel {

    private final static String[] COLUMN_NAMES = { "LABEL" };
    public TestModel(ArrayList<Departement> depList) {
        super(depList);
        this.depList = depList;
    }

    private ArrayList<Departement> depList;
    /* (non-Javadoc)
     * @see org.jdesktop.swingx.treetable.TreeTableModel#getColumnCount()
     */
    @Override
    public int getColumnCount() {

        return COLUMN_NAMES.length;
    }

    /* (non-Javadoc)
     * @see org.jdesktop.swingx.treetable.TreeTableModel#getValueAt(java.lang.Object, int)
     */
    @Override
    public Object getValueAt(Object arg0, int arg1) {
        if (arg0 instanceof Employee) {
            Employee emp = (Employee) arg0;

            JLabel newLabel =  new JLabel();
            newLabel.setText(emp.getName());
            return (JLabel)newLabel;

        } else if (arg0 instanceof Departement) {
            Departement dept = (Departement) arg0;

            JLabel newLabel =  new JLabel();
            newLabel.setText(dept.getName());
            return (JLabel)newLabel;

        }

        return null;
    }

    /* (non-Javadoc)
     * @see javax.swing.tree.TreeModel#getChild(java.lang.Object, int)
     */
    @Override
    public Object getChild(Object arg0, int arg1) {
        if (arg0 instanceof Departement) {
            Departement dept = (Departement) arg0;
            return dept.getEmpList().get(arg1);
        }

        return depList.get(arg1);
    }

    /* (non-Javadoc)
     * @see javax.swing.tree.TreeModel#getChildCount(java.lang.Object)
     */
    @Override
    public int getChildCount(Object arg0) {
        if (arg0 instanceof Departement) {
            Departement dept = (Departement) arg0;
            return dept.getEmpList().size();
        }

        if (arg0 instanceof Employee) {
            return 0;
        }

        return this.depList.size();
    }

    /* (non-Javadoc)
     * @see javax.swing.tree.TreeModel#getIndexOfChild(java.lang.Object, java.lang.Object)
     */
    @Override
    public int getIndexOfChild(Object arg0, Object arg1) {
        Departement dept = (Departement) arg0;
        Employee emp = (Employee) arg1;
        return dept.getEmpList().indexOf(emp);
    }

}

The Department and Employee class is fairly simple with a constructor and some getter/setters .

So when i click on the delete node button no action is happening on the tree. The UI is not getting updated at all. I am using swingx 1.6.4 version.

Department

public class Departement {

    private String name;
    public String getName() {
        return name;
    }

    private ArrayList<Employee> empList;
    public ArrayList<Employee> getEmpList() {
        return empList;
    }
    public void setEmpList(ArrayList<Employee> empList) {
        this.empList = empList;
    }
    public Departement(String name){

        this.name = name;
    }
}

Employee

public class Employee {

    private String name;
    public String getName() {
        return name;
    }
    public Employee(String name){
        this.name =name;
    }
}
2

There are 2 answers

2
kleopatra On BEST ANSWER

Technically, the reason boils down to an incorrect event (that you manually created in your action). Apart from the (minor) incorrect source, it's the path argument that's wrong: There are two constructors for a TreeEvent which you mixed up

// one taking a TreePath
public TreeModelEvent(Object source, TreePath path, int[] childIndices,
          Object[] children)

// the other taking an array of nodes to the root
public TreeModelEvent(Object source, Object[] path, int[] childIndices,
          Object[] children)

// mixture (**WRONG**)
TreeModelEvent event = new TreeModelEvent(this, 
    // this is an array with the path as single element
    new Object[] {rootNew.getPathForRow(3)}, // harcoding because i know i am deleting from dept2
    new int[]{1}, //hardcoding as i am removing emp4
    new Object[] {emp4});

// technically correct (but **don't** - TreeModelSupport does it for you :-) 
TreeModelEvent event = new TreeModelEvent(this, 
    // this is an array with the path as single element
    rootNew.getPathForRow(3), // harcoding because i know i am deleting from dept2
    new int[]{1}, //hardcoding as i am removing emp4
    new Object[] {emp4});

The real issue is the manual firing under the feet of the model: it's model's inherent responsibility to notify its listeners, so best provide it with api to remove an employee. Even then don't create the event manually which can be done incorrectly easily, that's why SwingX has TreeModelSupport to ease the pain.

// public api in your custom model
public void removeEmployee(Departement dept, Employee emp) {
    TreePath path = new TreePath(new Object[] {depList, dept});
    int index = dept.empList.indexOf(emp);
    dept.empList.remove(emp);
    modelSupport.fireChildRemoved(path, index, emp);
}

// its usage in application code
TestModel model = (TestModel) rootTree.getModel();
model.removeEmployee(dept2, emp4);

Unrelated to the notification issue, don't ever return view from model methods, the correct getValueAt would be something like

@Override
public Object getValueAt(Object arg0, int arg1) {
    if (arg0 instanceof Employee) {

        Employee emp = (Employee) arg0;
        return emp.getName();
    } else if (arg0 instanceof Departement) {
        Departement dept = (Departement) arg0;
        return dept.getName();
    }

    return null;
}
0
Catalina Island On

You can add a reload() method to your custom model using the DefaultTreeTableModel version as a guide. There's an example here that calls fireTreeStructureChanged().