Skip to content


update-tree! for updating JTree #50

wants to merge 2 commits into from

2 participants


Update a tree and keep expanded nodes expanded.


Hey. Thanks. A couple issues and then an idea I'd like your opinion on:

  • Most clojure code I've seen uses "-" instead of "_" in symbols. Not a huge deal, but it looks a little odd to me.
  • I think that the proper way to refresh the tree (or any widget) is to use (seesaw.core/repaint!). updateUI is meant to refresh the tree when the look and feel changes. Also, ideally, if the tree model is being used correctly, the tree should repaint automatically as it's changed and rarely require a manual repaint.

Now for the idea. It seems like this function is doing at least a couple things that could be broken out. The most important capability is the expansion state maintenance. What do you think about splitting that out into it's own function? Something like:

(defn with-expansion-state* [tree f & args]
  (... store expanded paths ...)
  (apply f args)
  (... restore expanded paths ...)
  return tree)

and then a companion macro with which you could do what update-tree! does:

(with-expansion-state tree
    (config! tree :model new-model))


(with-expansion-state tree
    (repaint! tree))

This nicely encapsulates the expansion pattern and makes it reusable in other places as well.



First: (if model model (.getModel tree)) is a relic, and should definitely not be there.

When it comes to the underscores, this is an oddity of my style. I started using underscores because I wanted a clear distinction between global symbols and local. (I'll try to use '-' if I commit again)

I'm sure you're right about the updateUI, it just happened to be the function I know for refreshing a component (I have seen it being recommended when switching the content of a panel with layout). Also, when I test repaint! it doesn't update the tree (but updateUI does).

I noticed now that updateUI has the problem of removing tree 'lines'.

I'll get back to you.


How about something like this (untested, but the 'idea' is used in hafni):

(defprotocol UpdatableTreeModel_p
  (update [this] ))

(defrecord UpdatableTreeModel [listeners branch? children root]
  (getRoot [this] root)
  (getChildCount [this parent] (count (children parent)))
  (getChild [this parent index] (nth (children parent) index))
  (getIndexOfChild [this parent child] 
                   (first (keep-indexed #(when (= %2 child) %1) (children parent))))
  (isLeaf [this node] (not (branch? node)))
  (addTreeModelListener [this listener] (swap! listeners conj listener))
  (removeTreeModelListener [this listener] (swap! listeners remove (partial = listener)))
  (valueForPathChanged [this path newValue] )
  (update [this]
          (let [e (javax.swing.event.TreeModelEvent. root (into-array [root]))]
            (doseq [listener @listeners]
              (.treeStructureChanged listener e)))))

(defn tree-model
  [branch? children root]
  (UpdatableTreeModel. (atom ()) branch? children root))

(defn update-tree!
  {:arglists '([model] [tree] [tree model])}
   (condp #(isa? %1 (class %2)) target
    javax.swing.JTree (recur (.getModel target))
    seesaw.tree.UpdatableTreeModel (.update target)))
  ([tree model]
   (.setModel tree model)))

and then we'll add with-expansion-state on top of it.


I think this is a good extension of what's there already. I'll won't be able to try it out until this evening though.


Haven't forgotten about this, just still thinking about it. It seems like for with-expansion-state, there are a few different approaches, each applicable to different situations. See the comments here:

JTree and JTable never fail to give me a headache.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 28, 2011
  1. @odyssomay


    odyssomay committed
  2. @odyssomay

    tests for update-tree!

    odyssomay committed
Showing with 34 additions and 2 deletions.
  1. +19 −0 src/seesaw/tree.clj
  2. +15 −2 test/seesaw/test/tree.clj
19 src/seesaw/tree.clj
@@ -25,3 +25,22 @@
(removeTreeModelListener [this listener])
(valueForPathChanged [this path newValue])))
+(defn update-tree!
+ "Update a tree.
+ The model is optional, if not supplied this function refreshes
+ the tree (useful for e.g. file trees).
+ Expanded nodes will still be expanded after update, given that
+ the expanded node didn't change.
+ "
+ {:arglists '([tree model?])}
+ [tree & [model]]
+ (if model
+ (let [visible_paths (doall
+ (for [row (range (.getRowCount tree))]
+ (.getPathForRow tree row)))]
+ (.setModel tree (if model model (.getModel tree)))
+ (doseq [path visible_paths]
+ (.makeVisible tree path)))
+ (.updateUI tree))
+ tree)
17 test/seesaw/test/tree.clj
@@ -10,7 +10,7 @@
(ns seesaw.test.tree
(:use seesaw.tree)
- (:use [lazytest.describe :only (describe it testing given)]
+ (:use [lazytest.describe :only (describe do-it it testing given)]
[lazytest.expect :only (expect)]))
(describe simple-tree-model
@@ -32,4 +32,17 @@
(it "should retrieve the index of a child"
(= [0 1 2] (map #(.getIndexOfChild m "dir" %) [1 2 3])))))
+(describe update-tree!
+ (given [tree (javax.swing.JTree. (simple-tree-model (constantly true) #(range (inc %)) 1))
+ path (javax.swing.tree.TreePath. (into-array [1 1 1 1]))]
+ (do-it "expand path"
+ (.makeVisible tree path))
+ (it "should be visible before update"
+ (.isVisible tree path))
+ (do-it "update tree"
+ (update-tree! tree (simple-tree-model (constantly true) #(range (+ % 2)) 1)))
+ (it "should update the tree"
+ (= [1 2]
+ (vec (.getPath (.getPathForRow tree (dec (.getRowCount tree)))))))
+ (it "should retain expanded paths after update"
+ (.isVisible tree path))))
Something went wrong with that request. Please try again.