Link to home
Start Free TrialLog in
Avatar of bustany
bustanyFlag for United States of America

asked on

JTree: Dynamic 'add' to the rootNode doesn't show up in the tree

Environment: Jbuilder 3.0 using Swing 2.0

I'm dynamically adding DefaultMutableTreeNode
objects to the root node of a JTree.  However,
they don't show up on the JTree scrollpane display,
but they ARE IN THE CHILDREN VECTOR of the root
node.

I can solve the problem by using TreeModel.  The
Sun code at:

http://java.sun.com/docs/books/tutorial/uiswing/components/example-swing/DynamicTree.java

shows how to do it.  However, (1) I don't want to
use TreeModel and (2) I'm puzzled why the add
method of DefaultMutableTreeNode doesn't work
after the root node has been put into a JTree.
I must be misunderstanding the usage of JTree
and that is very concerning.

Here is the sample code.  It's a little long
but a simple copy and paste should
be very quick.  And it compiles fine.

Some restrictions:
a) I don't want to use TreeModel
b) I must maintain the current hierarchy of
   classes
c) I'd prefer to add the new tree nodes (i.e.,
   the dynamically tree nodes that are not
   showing) as a result of ComponentListener
   event rather than TreeListener.  However,
   if the CL is not viable, then I guess I'll
   have to use TL

Thank you




// ============== TestApplet.java BEGIN =====================

package Children;

import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import javax.swing.*;

public class TestApplet extends JApplet
{
    boolean isStandalone = false;
    JTabbedPane jTabbedPane1 = new JTabbedPane();
    JPanel     panel1 = new JPanel();
    SecondPage panel2 = new SecondPage();

    //Get a parameter value
    public String getParameter(String key, String def)
    {
        return isStandalone ? System.getProperty(key, def) :
        (getParameter(key) != null ? getParameter(key) : def);
    }

    //Construct the applet
    public TestApplet()
    {
    }

    //Initialize the applet
    public void init()
    {
        try
        {
            jbInit();
        }
        catch ( Exception e )
        {
            e.printStackTrace();
        }
    }

    //Component initialization
    private void jbInit() throws Exception
    {
        this.setSize(new Dimension(400,300));
        this.getContentPane().add(jTabbedPane1, BorderLayout.CENTER);
        jTabbedPane1.add(panel1, "Page 1");
        jTabbedPane1.add(panel2, "Page 2");
    }

    //Start the applet
    public void start()
    {
    }

    //Stop the applet
    public void stop()
    {
    }

    //Destroy the applet
    public void destroy()
    {
    }

    //Get Applet information
    public String getAppletInfo()
    {
        return "Applet Information";
    }

    //Get parameter info
    public String[][] getParameterInfo()
    {
        return null;
    }

    // static initializer for setting look & feel
    static {
        try
        {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            //UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
        }
        catch ( Exception e )
        {
        }
    }
        // Main method
    public static void main(String[] args)
    {
        TestApplet applet = new TestApplet();
        applet.isStandalone = true;
        JFrame frame = new JFrame();
        frame.setTitle("TestApplet");
        frame.getContentPane().add(applet, BorderLayout.CENTER);
        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

        frame.addWindowListener(new WindowAdapter()
                                {
                                    public void windowClosed(WindowEvent e)
                                    {
                                        System.exit(0);
                                    }
                                });
        applet.init();
        applet.start();
        frame.setSize(600,600);
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        frame.setLocation((d.width - frame.getSize().width) / 2, (d.height - frame.getSize().height) / 2);
        frame.setVisible(true);
    }
}

// =============== TestApplet.java END ======================


// ============== SecondPage.java BEGIN =====================

package Children;

import java.awt.*;
import javax.swing.*;
import javax.swing.tree.*;
import java.awt.event.*;

public class SecondPage extends JPanel
{
    private JTree myTree;

    private MyTreeNode rootNode;

    public SecondPage()
    {
        super();

        try
        {
            jbInit();
        }
        catch ( Exception ex )
        {
            ex.printStackTrace();
        }
    }

    private void jbInit() throws Exception
    {
        this.setLayout(new BorderLayout());

        rootNode = new MyTreeNode();
        rootNode.add(new DefaultMutableTreeNode("Initial 1"));
        rootNode.add(new DefaultMutableTreeNode("Initial 2"));
        rootNode.add(new DefaultMutableTreeNode("Initial 3"));

        myTree   = new JTree(rootNode);

        JScrollPane treePane  = new JScrollPane(myTree);

        this.add(treePane);
        this.addComponentListener(new CL());
    }

    private class CL implements ComponentListener
    {
        public void componentResized(ComponentEvent e)
        {
            //System.out.println("StatusSplitPane: componentResized " );
        }

        public void componentMoved(ComponentEvent e)
        {
            //System.out.println("StatusSplitPane: componentMoved " );
        }

        public void componentShown(ComponentEvent e)
        {
            rootNode.updateFields();
            System.out.println("# of Children: " + rootNode.getChildCount());
            myTree.revalidate();
            myTree.repaint();
        }

        public void componentHidden(ComponentEvent e)
        {
            //System.out.println("StatusSplitPane: componentHidden " );
        }
    }
}

// =============== SecondPage.java END ======================


// ============== MyTreeNode.java BEGIN =====================

package Children;

import javax.swing.tree.*;

public class MyTreeNode extends DefaultMutableTreeNode
{
    protected DefaultMutableTreeNode[] innerNode = new DefaultMutableTreeNode[40];

    protected int idx;

    public MyTreeNode()
    {
        super("Root");
    }

    public void updateFields()
    {
        innerNode[idx] = new DefaultMutableTreeNode("Node " + idx);
        this.add(innerNode[idx++]);
    }
}

// =============== MyTreeNode.java END ======================

Avatar of mwibbels
mwibbels

the add and remove methods of DefaultMutableTreeNode do not fire TreeModelEvent, so the JTree never receives any notifications of the fact that its underlying model has changed. Normally you'd use a DefaultTreeModel for that: insertNodeInto and removeTreeFromParent.

If you don't want to use a TreeModel (although in you code you of course ARE using a TreeModel, you just don't see it) you could maybe solve the problem by letting the root node have a referemce to either the JTree or (as shown in the next parts of the code) a DefaultTreeModel:

changed in jbInit:

        rootNode = new MyTreeNode();
        // the next line is also done inside the constructor of JTree if you specify a node instead of
        // TreeModel
        DefaultTreeModel model = new DefaultTreeModel(rootNode);
        rootNode.setModel(model);

       // now add the children to the root



And changed the TreeNode:

class MyTreeNode extends DefaultMutableTreeNode
{

    protected DefaultMutableTreeNode[] innerNode = new DefaultMutableTreeNode[40];

    protected int idx;

    protected DefaultTreeModel model;

    public MyTreeNode()
    {
        super("Root");
        this.model = null;
    }

    public void setModel(DefaultTreeModel model) {
        this.model = model;
    }

    public void updateFields()
    {
        innerNode[idx] = new DefaultMutableTreeNode("Node " + idx);
        this.add(innerNode[idx++]);
    }

    public void add(MutableTreeNode node) {
        super.add(node);
        nodeWasAdded(this, getChildCount() - 1);
    }

    protected void nodeWasAdded(TreeNode node, int index) {
        if (model == null) {
            ((MyTreeNode)node.getParent()).nodeWasAdded(node, index);
        }
        else {
            int[] childIndices = new int[1];
            childIndices[0] = index;
            model.nodesWereInserted(node, childIndices);
        }
    }
}
If you are reluctant to use tree model, you need to implement the same functionality the DefaultTreeModel implements in order to be able to dynamically add nodes to the tree.

However, I would like ot start with some weak places I think I spotted in your code.

1. I wouldn't put the code for adding child nodes in the root node. This is a bad design decision, but won't hurt the program itself. Generally, it is enough that you keep a reference to a normal (not your custom) mutable root tree node and add/remove children whenever you wish.

2. Component listener on resize does help to make the JTree update, however this is far from doing the correct thing. You cannot keep on resizing the frame in order to update your tree.

----
When you add dynamically nodes, they are added to the structure of nodes. What is then necessary to be done is to notify the JTree about that. As you know, the whole Swing relies on the Model-View pattern, which in this case plays a dirty trick to you: your tree UI does not get updated.

Normally, the DefaultTreeModel has methods for that - insertNodeInto(parent, child) is merely the same as adding the child directly to the parent node, BUT the difference is that the model takes care of the notifications for you.

I cannot understand why do you reject to use a model. It increases your code with 2-3 lines. One to create the defaultthreemodel object with root your root node, one to pass it to the jtree constructor, and one to insertNodes in the parent using the model (see above). This will update you jtree UI.

On the other hand, if you still does not want to use tree model, look at the source code of the DefaultTreeModel.java and see how they notify the JTree to update itself. Again I want ot emphasize - Why do again yourself something other people already did (in the DefaultTreeModel).

Tell me whether you need more assistance.

Hope this helps,
  Nik
Avatar of bustany

ASKER

thank you, both of you.  you both will get 100 (
I'll file a dummy question for one of you to get
the other 100 points).

Followup for both, but especially for Nik's response:

The only reason why I don't want to use model because
I don't have access to the model from the node classes
(inheriting from DefaultMutableTreeNode, DMTN).  
So I can't insert nodes/leafs into the DMTN.

Let me elaborate more on the particular task.
I'm writing a system inventory tree. Consists of various
components. Each component has subcomponents,
and that has sub-subcomponents.  The tree has
a depth of at least 7. I've defined many distinct
classes (each subclassed from DMTN) that represent each
unique component.  This unique component "knows"
what type of subcomponents (children) it must
instantiate (dynamically).  This DMTN-like
class doesn't have access to the JTree's Model
since it wasn't informed of it.  I would rather
not pass a handle to the model to every node.
The inventory has about 400 nodes and leafs.

Questions:
1. It seems from your response that I MUST pass
in a model handle to each node.  Please confirm.
If that's what I MUST do, then that is what I will do.

2. Your first point says it is not a good
practice to add nodes from a DMTN root node.  Where else
can I add them.  I then will have to subclass
DefaultTreeModel, add the nodes, then create the JTree?
This approach may work for the rootNode, but
very difficult to keep track for, say a 4th
level component.  The subclassed model will
get pretty complicated if I embed the knowledge
of which component should have what children.  At
the moment, each one of the 60 or so node
classes is very, very small.  Putting all that
logic in a big case statement inside a subclassed
model would be difficult to maintain.  

So, in effect, how can I delegate the
instantiation of children components to each
DMTN-class and use the TreeModel cleanly.

-------------------------------

After reading both of your responses, I'm relaxing
the no model approach.  It seems that I have
to.  That's why I'm at this moment concentrating
on Nik's design suggestion rather then mwibbels's.
mwibbels' answer is great since it did do exactly
what I requested, but again, you two made it clear
that my request was invalid and not recommended.







First let set the terms. It is not very convenient to speak about components and subcomponents when refering to nodes of a tree as this calls for ambiguity that says you use different visual components in the JTree, which is a separate issue than the dynamic modifications of the structure. Better speak about parent node, child node, node. The default component for the visual representation of a leaf of a JTree is a JLabel component. You can change that, however it is a separate issue.

Let me verify what I understood about your task:

- You want to maintain a JTree, that can dynamically (after initialization) change structure - delete/add nodes.


1. At the moment of creation of the JTree, you can provide a DefaultTreeModel to the JTree and an initial tree structure by passing the root node, that have children, that have their childrent, etc.

2. When you change tree structure later, you have to get access to a particular node, and then depending on what you want to do, remove it from its parent, or add a child to it.
When you do that however, it is necessary to notify the JTree about the change so the GUI will update. Thus, you better use the methods of DTM (you can keep a reference to it from initialisation time when you passeed it to the JTree constructor) insertNodeInto and deletNodeFrom. NOTE THAT the DTM does not keep any information about structure of the tree! It is kept in the nodes as each node (DMTN) maintains an array of its children by default. DTM plays only a management role. The different between, for example, adding a child directly to the parent node and adding it through the DTM is that with the DTM all updates will be done automatically and model listeners will be notified.

3. How do you keep access to the tree nodes? The JTree does not care about that. When your tree modifications are based on the selection or a mouse click within the tree (event related to the tree itself), there are standard methods to determine which is the selected node. Thus, you can obtain a reference to the particular DMTN and then look for a particular child, or its parent. When the need (event or other) for tree modification comes from a source outside the tree, you need to find the appropriate place in the tree where to do the modification, which you can do by walking the tree for example, or in case you have categories (in a system inventory) you can keep references to particular nodes and start the walking of the tree from a particular place.

Question:
How do you change the tree structure with respect to the event that triggers the change : is it a mouse click, is it the current selection, is it an external to the tree event, like a socket communication or a method call from other GUI (like button or etc)?

Hope this helps,
  Nik
Avatar of bustany

ASKER

Hi Nik,

I'll start by answering your last question and then elaborate more on the three points you raised.  Finally, I'll ask you about this notion of data containers vs the GUI managers.

The update to the tree (or the tree nodes) is taking place as a result of a thread that periodically retrieve the current system status and updates the tree.  The tree does not get updated based on any mouse clicks on the tree itself.

1. Does that mean that when I create the DMTNs, I'll need to pass in the DTM handle to every single DMTN?  Why can't the DMTN say "to the tree I am part of, can you please give me your model?"  (I guess java doesn't offer the above ability.)

2. It is clear to me now that I must use the DTM.  The question I have left is how to pass it around to the various DMTNs.  Passing it during the contructor phase of the DMTNs sounds most logical so far.

3. I will do that.


Finally, I'm having a problem grasping the separation between the data and the GUI. So, if I add data to the Tree, the GUI doesn't display it until it gets notified.  It seems I'm having this notion problem with almost all the other data containers.  For example, if I create a JScrollPane:

scrollPane = new JScrollPane(new JLabel("first"));

Then, later due to a some event, I change the content (or
data) of this scrollpane:

scrollPane.getViewport().add(new JLabel("Second"));

I don't see the scrollpane updated on the screen (i.e.,
I keep seeing the word 'first' instead of 'second'). And I've tried to find a model for the scrollpane but it doesn't have one.  

There are other data containers I'm having problems updating the GUI when the data is updated.  I've search javasoft.com for a detail tutorial on this separation but to
no avail.  Do you recommend anything for me to read to get a better grasp of this notion?

Thank you


Avatar of bustany

ASKER

Hi Nik,

I'll start by answering your last question and then elaborate more on the three points you raised.  Finally, I'll ask you about this notion of data containers vs the GUI managers.

The update to the tree (or the tree nodes) is taking place as a result of a thread that periodically retrieve the current system status and updates the tree.  The tree does not get updated based on any mouse clicks on the tree itself.

1. Does that mean that when I create the DMTNs, I'll need to pass in the DTM handle to every single DMTN?  Why can't the DMTN say "to the tree I am part of, can you please give me your model?"  (I guess java doesn't offer the above ability.)

2. It is clear to me now that I must use the DTM.  The question I have left is how to pass it around to the various DMTNs.  Passing it during the contructor phase of the DMTNs sounds most logical so far.

3. I will do that.


Finally, I'm having a problem grasping the separation between the data and the GUI. So, if I add data to the Tree, the GUI doesn't display it until it gets notified.  It seems I'm having this notion problem with almost all the other data containers.  For example, if I create a JScrollPane:

scrollPane = new JScrollPane(new JLabel("first"));

Then, later due to a some event, I change the content (or
data) of this scrollpane:

scrollPane.getViewport().add(new JLabel("Second"));

I don't see the scrollpane updated on the screen (i.e.,
I keep seeing the word 'first' instead of 'second'). And I've tried to find a model for the scrollpane but it doesn't have one.  

There are other data containers I'm having problems updating the GUI when the data is updated.  I've search javasoft.com for a detail tutorial on this separation but to
no avail.  Do you recommend anything for me to read to get a better grasp of this notion?

Thank you


Avatar of bustany

ASKER

Hi Nik,

I'll start by answering your last question and then elaborate more on the three points you raised.  Finally, I'll ask you about this notion of data containers vs the GUI managers.

The update to the tree (or the tree nodes) is taking place as a result of a thread that periodically retrieve the current system status and updates the tree.  The tree does not get updated based on any mouse clicks on the tree itself.

1. Does that mean that when I create the DMTNs, I'll need to pass in the DTM handle to every single DMTN?  Why can't the DMTN say "to the tree I am part of, can you please give me your model?"  (I guess java doesn't offer the above ability.)

2. It is clear to me now that I must use the DTM.  The question I have left is how to pass it around to the various DMTNs.  Passing it during the contructor phase of the DMTNs sounds most logical so far.

3. I will do that.


Finally, I'm having a problem grasping the separation between the data and the GUI. So, if I add data to the Tree, the GUI doesn't display it until it gets notified.  It seems I'm having this notion problem with almost all the other data containers.  For example, if I create a JScrollPane:

scrollPane = new JScrollPane(new JLabel("first"));

Then, later due to a some event, I change the content (or
data) of this scrollpane:

scrollPane.getViewport().add(new JLabel("Second"));

I don't see the scrollpane updated on the screen (i.e.,
I keep seeing the word 'first' instead of 'second'). And I've tried to find a model for the scrollpane but it doesn't have one.  

There are other data containers I'm having problems updating the GUI when the data is updated.  I've search javasoft.com for a detail tutorial on this separation but to
no avail.  Do you recommend anything for me to read to get a better grasp of this notion?

Thank you


Avatar of bustany

ASKER

Hi Nik,

I'll start by answering your last question and then elaborate more on the three points you raised.  Finally, I'll ask you about this notion of data containers vs the GUI managers.

The update to the tree (or the tree nodes) is taking place as a result of a thread that periodically retrieve the current system status and updates the tree.  The tree does not get updated based on any mouse clicks on the tree itself.

1. Does that mean that when I create the DMTNs, I'll need to pass in the DTM handle to every single DMTN?  Why can't the DMTN say "to the tree I am part of, can you please give me your model?"  (I guess java doesn't offer the above ability.)

2. It is clear to me now that I must use the DTM.  The question I have left is how to pass it around to the various DMTNs.  Passing it during the contructor phase of the DMTNs sounds most logical so far.

3. I will do that.


Finally, I'm having a problem grasping the separation between the data and the GUI. So, if I add data to the Tree, the GUI doesn't display it until it gets notified.  It seems I'm having this notion problem with almost all the other data containers.  For example, if I create a JScrollPane:

scrollPane = new JScrollPane(new JLabel("first"));

Then, later due to a some event, I change the content (or
data) of this scrollpane:

scrollPane.getViewport().add(new JLabel("Second"));

I don't see the scrollpane updated on the screen (i.e.,
I keep seeing the word 'first' instead of 'second'). And I've tried to find a model for the scrollpane but it doesn't have one.  

There are other data containers I'm having problems updating the GUI when the data is updated.  I've search javasoft.com for a detail tutorial on this separation but to
no avail.  Do you recommend anything for me to read to get a better grasp of this notion?

Thank you


ASKER CERTIFIED SOLUTION
Avatar of diakov
diakov

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial