Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The Archive Base

The Archive Base Logo The Archive Base Logo

The Archive Base Navigation

  • Home
  • SEARCH
  • About Us
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • Add group
  • Groups page
  • Feed
  • User Profile
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Buy Points
  • Users
  • Help
  • Buy Theme
  • SEARCH
Home/ Questions/Q 302591
In Process

The Archive Base Latest Questions

Editorial Team
  • 0
Editorial Team
Asked: May 12, 20262026-05-12T07:06:41+00:00 2026-05-12T07:06:41+00:00

OK, I couldn’t really find a decent example for a custom TreeModel in Swing

  • 0

OK, I couldn’t really find a decent example for a custom TreeModel in Swing that uses more than the basic features, so I wrote my own (code follows below) so I can ask questions about it, rather than about a more complicated application that’s the one I really want to write when I understand how to write it. Apologies for the fact that there are multiple related questions here, it’s hard to ask these Q’s in a vacuum w/o an example to refer to, and I figure it’s better to put up the example once in one post, than to separate out my questions. My real application is not infinite, just large (state stored in a database) so a custom TreeModel seems appropriate.

package com.example.test.gui;

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.util.EventObject;
import java.util.HashMap;
import java.util.Map;
import javax.swing.AbstractCellEditor;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
/*
 * GUI rendering of the ancestry of hailstone numbers 
 * (see http://mathworld.wolfram.com/CollatzProblem.html)
 * 
 * This is an infinite tree model.
 * 
 * each node in the tree is a Long number
 * each node has 1 or 2 children:
 *   all nodes N have a child 2N
 *   any node N = 3k+1, where k > 0, has a second child k
 *   
 * checkboxes are present just to see custom rendering
 *   - nodes N where N is divisible by 7 are editable, the rest are not
 *   - editable nodes override their default state (stored in a hashmap)
 *   - default state of a node N is checked if N is divisible by 5,
 *     unchecked otherwise  
 */
class HailstoneTreeModel implements TreeModel {
    final private Map<Long,Boolean> modifiedCheckState = new HashMap<Long,Boolean>();

    @Override public Object getChild(Object parent, int index) {
        if (!(parent instanceof Long))
            return null;
        if (index < 0 || index > 1)
            return null;
        final long l = ((Long)parent).longValue();
        if (index == 0)
        {
            return (l*2);
        }
        else if ((l > 1) && (l-1)%3 == 0)
        {
            return (l-1)/3;
        }
        else
            return null;
    }

    @Override public int getChildCount(Object parent) {
        if (!(parent instanceof Long))
            return 0;
        final long l = ((Long)parent).longValue();
        if ((l > 1) && (l-1) % 3 == 0)
            return 2;
        return 1;
    }

    @Override public int getIndexOfChild(Object parent, Object child) {
        if (parent instanceof Long && child instanceof Long)
        {
            final long p = ((Long)parent).longValue();
            final long c = ((Long)child).longValue();
            if (p*2 == c)
                return 0;
            if (p == 3*c+1)
                return 1;
        }
        return -1;
    }

    @Override public Object getRoot() {
        return 1L;
    }

    @Override public boolean isLeaf(Object arg0) {
        return false;
    }

    @Override
    public void addTreeModelListener(TreeModelListener arg0) {
        // TODO Auto-generated method stub      
    }

    @Override
    public void removeTreeModelListener(TreeModelListener arg0) {
        // TODO Auto-generated method stub      
    }

    @Override
    public void valueForPathChanged(TreePath arg0, Object arg1) {
        // !!! what is typically done here and when does this get called?
    }

    public boolean isEditable(TreePath path) {
        if (path != null) {
            Object node = path.getLastPathComponent();
            // only the nodes divisible by 7 are editable
            if (node instanceof Long)
            {
                return ((Long)node) % 7 == 0;
            }
        }
        return false;
    }

    private void _setState(Long value, boolean selected)
    {
        this.modifiedCheckState.put(value, selected);
        System.out.println(value+" -> "+selected);      
    }
    public void setState(Object value, boolean selected) {
        if (value instanceof Long)
        {
            _setState((Long)value, selected);
        }       
    }
    private boolean _getState(Long value)
    {
        Boolean b = this.modifiedCheckState.get(value);
        if (b != null)
        {
            return b.booleanValue();                
        }
        return (value.longValue() % 5 == 0);
    }
    public boolean getState(Object value)
    {
        if (value instanceof Long)
        {
            return _getState((Long) value);
        }           
        return false;       
    }

    public void toggleState(Object value) {
        if (value instanceof Long)
        {
            _setState((Long)value, !_getState((Long)value));
        }       
    }   
}

// adapted from http://www.java2s.com/Code/Java/Swing-JFC/CheckBoxNodeTreeSample.htm
class CheckBoxNodeRenderer implements TreeCellRenderer {
    final private JCheckBox nodeRenderer = new JCheckBox();
    final private HailstoneTreeModel model;
    private Long currentValue = null; // value currently being displayed/edited

    final private Color selectionBorderColor, selectionForeground, selectionBackground,
    textForeground, textBackground;

    protected JCheckBox getNodeRenderer() {
        return this.nodeRenderer;
    }

    public CheckBoxNodeRenderer(HailstoneTreeModel model) {
        this.model=model;

        Font fontValue;
        fontValue = UIManager.getFont("Tree.font");
        if (fontValue != null) {
            this.nodeRenderer.setFont(fontValue);
        }
        Boolean booleanValue = (Boolean) UIManager
        .get("Tree.drawsFocusBorderAroundIcon");
        this.nodeRenderer.setFocusPainted((booleanValue != null)
                && (booleanValue.booleanValue()));

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

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

        Component returnValue = this.nodeRenderer;
        String stringValue = tree.convertValueToText(value, selected,
                expanded, leaf, row, false);
        this.nodeRenderer.setText(stringValue);
        this.nodeRenderer.setSelected(false);       
        this.nodeRenderer.setEnabled(tree.isEnabled());

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

        if (value instanceof Long)
        {
            this.currentValue = (Long) value;
        }
        this.nodeRenderer.setSelected(this.model.getState(value));
        returnValue = this.nodeRenderer;
        return returnValue;
    }
    public Long getCurrentValue() { return this.currentValue; }
}


class CheckBoxNodeEditor extends AbstractCellEditor implements TreeCellEditor {

    final CheckBoxNodeRenderer renderer;
    final HailstoneTreeModel model;

    public CheckBoxNodeEditor(HailstoneTreeModel model) {
        this.model = model;
        this.renderer = new CheckBoxNodeRenderer(model);
        ItemListener itemListener = new ItemListener() {
            public void itemStateChanged(ItemEvent itemEvent) {
                Object cb = itemEvent.getItem();
                if (cb instanceof JCheckBox && itemEvent.getStateChange() == ItemEvent.SELECTED)
                {
                    Long v = CheckBoxNodeEditor.this.renderer.getCurrentValue(); 
                    CheckBoxNodeEditor.this.model.toggleState(v);
                }
                // !!! the following 3 lines are important because... ?
                if (stopCellEditing()) {
                    fireEditingStopped();
                }
            }
        };
        this.renderer.getNodeRenderer().addItemListener(itemListener);
    }

    public Object getCellEditorValue() {
        JCheckBox checkbox = this.renderer.getNodeRenderer();
        return checkbox;
    }

    @Override public boolean isCellEditable(EventObject event) {
        boolean returnValue = false;
        Object source = event.getSource();
        if (event instanceof MouseEvent && source instanceof JTree) {
            MouseEvent mouseEvent = (MouseEvent) event;         
            TreePath path = ((JTree)source).getPathForLocation(mouseEvent.getX(),
                    mouseEvent.getY());
            returnValue = this.model.isEditable(path);
        }
        return returnValue;
    }

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

        Component editor = this.renderer.getTreeCellRendererComponent(tree, value,
                true, expanded, leaf, row, true);
        return editor;
    }
}

public class VirtualTree1 {
    public static void main(String[] args) {
        HailstoneTreeModel model = new HailstoneTreeModel();

        // Create a JTree and tell it to display our model
        JTree tree = new JTree(model);
        tree.setCellRenderer(new CheckBoxNodeRenderer(model));
        tree.setCellEditor(new CheckBoxNodeEditor(model));
        tree.setEditable(true);

        // The JTree can get big, so allow it to scroll
        JScrollPane scrollpane = new JScrollPane(tree);

        // Display it all in a window and make the window appear
        JFrame frame = new JFrame("Hailstone Tree Demo");
        frame.getContentPane().add(scrollpane, "Center");
        frame.setSize(400,600);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }   
}

See the first comment (should be visible above) for what this is showing. It’s a custom TreeModel displaying an infinite tree, something that would not be possible in a “regular” tree where all nodes in the tree need to actually exist in memory, but is possible using custom TreeModels since the only parts that are displayed/instantiated are the ones the user clicks on, which is inherently finite. 🙂

I have a few miscellaneous questions. An accepted answer for this post will be given for the best answer to my question #4.

1) TreeModel listeners — am I correct in assuming that this is for classes that want to receive events for updates from my TreeModel? (whether I write them or someone else does) What are typical use cases?

2) TreeModel.valueForPathChanged() — when does this get called, and what would I typically do about it?

3) (this has to do with TreeCellEditor / TreeCellRenderer) — the lines in the example I adapted had the following call:

if (stopCellEditing()) {
    fireEditingStopped();
}

what’s this for?

4) as far as class organization — is there a better way to structure this sort of thing? I figure I should have a separation between the TreeModel (M = Model in MVC) and the TreeCellEditor / TreeCellRenderer (V = view, or C = controller, I’m not sure), but they vaguely need to know about each other, and I wasn’t sure which should contain a reference to which. Right now I have the TreeModel as an independent object, and the editor/renderer has a reference to the TreeModel so it can query/mutate the model as necessary. Also I’m wondering if maybe the custom TreeCellEditor and TreeCellRenderer should really be one class that implements both interfaces. My itemStateChanged() method in CheckBoxNodeEditor seems kind of weird… I get an item listener event when the checkbox gets clicked, and then I kind of assume this event comes from the renderer, and toggle the appropriate value since I can’t seem to figure out how to determine if the check box is presently checked or unchecked, selection in this checkbox object seems to be whether the mouse has been clicked or let go, rather than the checkbox state.

There’s probably more than one way to restructure this so it seems like a better, more modularized approach, but right now I’m not sure how to do this so any suggestions are appreciated.

5) Directed Acyclic Graphs (DAG) displayed as a tree hierarchy — If you run the application, then expand the nodes 1,2,4,8,16,32,64,128, you’ll see a second “1” which is really the same node as the first “1” since node values in this example application are just Long objects. If you expand this second “1” to 1,2,4,8,16,32,64,128, you will now see two “21” nodes. The “21” node can be checked/unchecked as necessary. But in an ideal world, both “21”s would update their checked state when I click on one of them. Is there any way to do this automatically? Or do I have to keep track of all the multiple paths for a single node that are currently displayed at the same time? (alternatively, all the paths for a single node that exist — possible in a finite DAG, not possible in an infinite DAG) This is only an issue with DAGs where there are multiple paths to reach the same node… I have to deal with this in my application.

  • 1 1 Answer
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

1 Answer

  • Voted
  • Oldest
  • Recent
  • Random
  1. Editorial Team
    Editorial Team
    2026-05-12T07:06:41+00:00Added an answer on May 12, 2026 at 7:06 am

    1

    Yes.
    The JTree (or TreeUI) installs TreeModel listeners that trigger re-layouts of the JTree when data changes. Sometimes just updating single nodes (when a node’s value changed) sometime performing a re-layout of the entire tree.

    2

    see below

    3

    I believe a better name for stopCellEditing is shouldStopCellEditing. For instance If the user hit escape. Why it is not directly bound I don’t know.

    4

    IIRC, TreeCellEditor and TreeCellRenderer do not need to know about the model. They get the data needed to display as the value object in the getTreeCellXXX method.

    Roughly it works like this:

    For each node in the tree:

    1. Get the tree’s TreeCellRenderer and call getTreeCellRendererComponent(this, currNode,…)
    2. Use SwingUtilities paintComponent methods to draw the above component on the JTree
    3. If you are editing the node, instead use the TreeCellEditor.
    4. When editing is finished, get the new value from the editor. This could be ANY object.
    5. The model is updated (getModel().valueForPathChanged( path_to_changed_node, newvalue ))

    That last step is where you would need to (in your treemodel):

    a. update your db
    b. fireTreeNodesChanged on all your tree nodes that are aliases to this data.

    My Solution.

    I would design a TreeNode (or sublcass DefaultMutableTreeNode) that holds a key describing what data it represents. Make sure it has not children initially. Have the model install a TreeWillExpandListener on the tree and when a node WILL expand, load it’s children at this time. This allows for lazy loading of the children and you will only need as many tree nodes in memory as visible nodes on the tree. As a bonus, in a cyclic graph, you have a a-cyclic tree since each repeated node is a unique alias for the same spot in the graph. Since children are loaded (and maybe unloaded) by your TreeWillExpandListener, you can traverse the tree looking for equivalent aliases or register each nodes with some sort of key=>list of nodes map.

    • 0
    • Reply
    • Share
      Share
      • Share on Facebook
      • Share on Twitter
      • Share on LinkedIn
      • Share on WhatsApp
      • Report

Sidebar

Ask A Question

Stats

  • Questions 165k
  • Answers 165k
  • Best Answers 0
  • User 1
  • Popular
  • Answers
  • Editorial Team

    How to approach applying for a job at a company ...

    • 7 Answers
  • Editorial Team

    What is a programmer’s life like?

    • 5 Answers
  • Editorial Team

    How to handle personal stress caused by utterly incompetent and ...

    • 5 Answers
  • Editorial Team
    Editorial Team added an answer Categories extend the original class, but they don't subclass it,… May 12, 2026 at 12:54 pm
  • Editorial Team
    Editorial Team added an answer Haven't tested this, but it's something like: RewriteRule \.php$ -… May 12, 2026 at 12:54 pm
  • Editorial Team
    Editorial Team added an answer "0:0:0:0:0:0:0:1" is the IPv6 loopback address as defined in RFC… May 12, 2026 at 12:54 pm

Related Questions

OK, so I couldn't really think of an apropos title that summarizes this. The
Ok it might sounds dumb, but I couldn't figure out a way to pass
Ok, I know this has been asked before but after searching I couldn't find
I have been googling a lot and I couldn't find if this even exists
OK, I have been working on a random image selector and queue system (so

Trending Tags

analytics british company computer developers django employee employer english facebook french google interview javascript language life php programmer programs salary

Top Members

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help
  • SEARCH

Footer

© 2021 The Archive Base. All Rights Reserved
With Love by The Archive Base

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.