Hi, I'm emailing about the feature request I raised[1] about adding a data-grid-viewer-like widget to the shoes API (presumably using the GTK binding to a TreeView). I'd like to help (I've never used GTK directly before, but I've been reading throught this[2]), so I've sketched out the API for discussion. I'll use this table[3] as an example to illustrate usage. 1) Model-View separation GTK's TreeView has a clear difference between the data in the tree and the view of that tree - and this should be preserved in the Shoes API. To make the widget simple to use there should only be one type: >> a = Gridview.new This will contain the model and a default view; if you want to create new views onto the same data you could do: >> a_view = a.new_view Any DML calls - see (2) - will affect the shared model (i.e. a *and* a_view), but any style calls - (5) below - will only affect the instance that it was called on (i.e. a *or* a_view). 2) DML DML (I stole the term from SQL - basically insertion & deletion into the model) should be simple. In GTK's TreeView, rows can are indexed in a tree (a 2D grid is a tree with only a single level of row index) using (I think) a series of colon-separated numbers. In the example[3], the third row from the top has index 0:1 (base-zero indices). I think the shoes API should have the same hierarchy, but use symbols to make up the index (something like symlinks to rows; GTK's TreeRowReferences) as working with absolute indices (that change as the rows above it are delete or new ones are inserted) has caused me a lot of trouble in the past (although getting the absolute index of a row as a list of integers and vice-versa should be possible). Each row should be a Hash, and the column names should be symbols (that don't necessarily correspond to the heading name in the UI). to create the example: >> a.upsert :1, :11, id => 11, name => "Billy Bob Junior" >> a.upsert :2, id => 2, name => "Joey Jojo" >> a.index_of :1, :11 # not relly a good example as the symbols are numbers... [0,0] >> a.indexed_by 0, 0 [:1,:11] >> a.delete :1, :11 # deletes the first row we inserted >> a.clear # empties the table (like the GTK method) I think upsert (insert or update) is better than separate operations as it reduces the "surface area" of the API, and means you don't have to write boilerplate that checks for an existing row before updating. You can always use the returned object for this. Columns should be determined dynamically (in the Shoes API; you can't do this with the underlying GTK TreeView), and (5) specifies default values to use for columns if an insertion doesn't provide all of them (just like passing a block to a new Hash is used for default values). 3) Access The [] operator should give you row access (i.e. return a Hash), and any mutation methods (e.g. update, delete_if, store, &c.) on the returned Hash should result in upsert calls on the TreeView object to keep the table in sync (deleting from the Hash should replace the value in the table with the relevant column's default value. >> a[:1, :11][name] = "Billy Bob Senior" Iteration is fairly important, and you should be able to iterate over rows and columns. When iterating over rows, you should be able to specify part of a rowkey to iterate over sub-branches of the tree: >> a.each_row{|*rowkey,row| ... } >> a.each_row[:3]{|*rowkey,row| ... } # only iterate over rows including and below the :3 branch; [:3] and [:3,:31] in the example >> a.each_col{|col,rows| ... } # here rows is a Hash of rowkeys (e.g. [:3,:31]) to values (where the value is for the relevant column for that row 4) Callbacks Callbacks (GTK's Signals) are one of the most important features of the table (imo). They should in general behave like the change() callbacks in the rest of the Shoes API (and so should always be given a reference to the top-level TreeView). Unlike SQL triggers, they can't modify the change that's happening (i.e. change the values being inserted or updated), and so the Hashes in the examples below should be immutable (frozen?). >> a.on_upsert {|self,*rowkey,changes,previous| ... } Here /previous/ is nil for an insert and an immutable Hash of the previous rows' values for an update. The /changes/ Hash can be empty, and contains only the values that have changed (i.e. it's not a snapshot of the row in its new state). Similarly, there should be a callback for deletions: >> a.on_delete{|self,*rowkey| ... } 5) Styles This is somewhat more difficult, as there are some attributes at a cell level (e.g. background colours in alternate rows) and others at a column level (e.g. the function to sort the rows by). There are also some callbacks that should be per-view, not per model - see (1) - like what has been selected: >> a.on_select{|self,*rowkey,col| ... } Maybe the column-level attributes should be set on the TreeView object: >> a.column_is :name, :editable => true, :hidden => false, :default => {|*rowkey| "" }, :sortable => true And there should be a way of setting column-level attributes for all columns (to prevent iteration or boilerplate - and because column names can be added dynamically): >> a.all_columns_are :width => :autofit # can GTK automatically size columns to fit the largest element? The :sortable attribute should sort by the default order (i.e. <=> in ruby) if a boolean is given, or by the given block to allow custom sort orders. Note that sorting is per-view, not per-model. The :editable attribute has to be set at the column level as (afaik) GTK doesn't let you set per-cell editability. This is unfortunate, as it would be nice to let a cell contain one of Shoes' EditLine elements to indicate a cell was editable. As for per-cell attributes, maybe it should be possible to model each cell as a Shoes slot, so you can add any of the existing Shoes elements to it: >> a.column_is :name, :cell => {|self,*rowkey| background black } There are examples of GTK TreeViews with checkboxes in cells[4], so the below should be possible: >> a.column_is :has_name, :cell => { check } What do you think? Altough I'm not able to write the implementation, I could write unit tests if that would help. I think the data-grid-viewer is second most important widget after the button (at least in the applications I usually write :), so I hope this eventually ends up in Green Shoes. Thanks, Nick [1] https://github.com/ashbb/green_shoes/issues/33 [2] http://scentric.net/tutorial/treeview-tutorial.html [3] http://developer.gnome.org/gtkmm-tutorial/2.22/figures/treeview_tree.png.en [4] http://mail.gnome.org/archives/gtk-list/2003-September/msg00202.html
Hi Nicholas, Wow, awesome! Thank you for the great suggestion. But sorry, I have no time this week to read in detail. :( So, please give me more time. I'll reply next week. Thanks, ashbb
Hi Nicholas,
> I've sketched out the API for discussion.
Great!
I didn't know GTK's TreeView well. So, the API you sketched and the
references you found were very helpful for me to study. Thanks!
You've sketched many ideas, e.g. "a series of colon-separated numbers",
"using [] operator to access", etc. They are really nice. But looks a little
bit too close to GTK APIs, IMHO.
Shoes has a method list_box():
http://ashbb.github.com/green_shoes/ListBox.html
So, how about following the list_box style?
For example:
require 'green_shoes'
Row = Struct.new :id, :name
tables = [[[1, 'Billy Bob'], [11, 'Billy Bob Junior'], [12, 'Sue Bob']],
[[2, 'Joey Jojo']],
[[3, 'Rob McRoberts'], [31, 'Xavier McRoberts']]]
tree = tables.map do |table|
table.map do |id, name|
Row.new id, name
end
end
Shoes.app treeview: true do
tree_view tree do |tv|
para tv.id, tv.name # tv is a selected row
end
end
I'm not sure it's possible to implement, though. :-P
ashbb
I like the list_box API - it's nice and simple. Is the "item" list mutable (i.e. if you do it.items << "s" will it add "s" to the list of options? I'm on a mac at the minute so can't really test it. I can definitely see a TreeView as a generalisation of a (mutable) list_box - but with the "items" as an array of Hashes (column names to values). The many-views-of-one-model (para (1) of my email) maps fairly well - if you have two list_boxes that share the same items array, then they (should?) share the same model: >> a = list_box items => [ "a", "b", "c" ] >> b = list_box items => a.items >> a.items << "d" >> puts b.items [ "a", "b", "c", "d" ] Again, I don't know if green_shoes' list_box actually does this, but model-view separation is a fairly powerful tool. As for square-bracket indexing, you can reference list_box elements with absolute indices (e.g. a.items[2]) so that would work for free with a TreeView list box. I agree, it's harder to see how multi-dimensional row indices (and indeed named rows all together) fit into this - I need to think a bit more about that. As for callbacks, the list_box's "change" one is a callback on the view - so I think the "upsert" callback is still needed for hooking events into changes of the model. To illustrate - the list_box is missing a callback that gets invoked when someone calls the "items=" method with a new array. You're right that my solution is over-engineered; if the upsert callback got given two Hashes (the new row and the old row) we wouldn't need a separate "delete" callback: >> v = tree_view upsert => {|new,old| puts new.inspect, " ", old.inspect} >> v.items << :id => 7, :name => "Me" [name=Me,id=7] nil >> v.items[0][:name] = "You" [name=You,id=7] [name=Me,id=7] >> v.items.clear nil [name=You,id=7] I see the "items" property of the list_box / tree_view as a way of accessing the model, and all the other methods on the list_box (including the ones common to all green_shoes components[1]) as ways of accessing the view. We'd get a lot of customisation for free (i.e. no new methods added to the API), but would still need new methods for sorting. This may be useful for existing list_boxes though; currently if you want to add a new element you'd have to sort it each time, which is verbose. Compare: >> y = list_box :items => [ "c" ] >> y.items = (y.items << "b").sort >> y.items = (y.items << "a").sort With: >> y = list_box :items => [ "c" ], :sorted >> y.items = "b" >> y.items = "a" You're more familiar with gtk bindings than I am, so I hope your last line is not true. Version 1.0 could just map to the list_box API (but with callbacks on the model too :) - everything else is just a nice-to-have. Thanks, Nick [1] http://ashbb.github.com/green_shoes/Common.html On 24 July 2011 03:06, ashbb <ashbbb@gmail.com> wrote: > Hi Nicholas, > >> I've sketched out the API for discussion. > Great! > I didn't know GTK's TreeView well. So, the API you sketched and the > references you found were very helpful for me to study. Thanks! > > You've sketched many ideas, e.g. "a series of colon-separated numbers", > "using [] operator to access", etc. They are really nice. But looks a little > bit too close to GTK APIs, IMHO. > > Shoes has a method list_box(): > http://ashbb.github.com/green_shoes/ListBox.html > > So, how about following the list_box style? > > For example: > > require 'green_shoes' > > Row = Struct.new :id, :name > tables = [[[1, 'Billy Bob'], [11, 'Billy Bob Junior'], [12, 'Sue Bob']], > [[2, 'Joey Jojo']], > [[3, 'Rob McRoberts'], [31, 'Xavier McRoberts']]] > > tree = tables.map do |table| > table.map do |id, name| > Row.new id, name > end > end > > Shoes.app treeview: true do > tree_view tree do |tv| > para tv.id, tv.name # tv is a selected row > end > end > > I'm not sure it's possible to implement, though. :-P > > ashbb > >
Hi Nicholas, > Is the "item" list mutable? Yes. Try out the following. But you have to use the latest Green Shoes. Because I've fixed a bug you found. ;-) # Thanks to you, I noticed this bug. https://github.com/ashbb/green_shoes/commit/b979fb41610faef2f0f3c8f67766004fce1f3df0 require 'green_shoes' Shoes.app do lb = list_box items: [1,2,3,4,5] list_box items: lb.items button('add 6'){lb.items += [6]} end > As for callbacks, the list_box's "change" one is a callback on > the view - so I think the "upsert" callback is still needed for > hooking events into changes of the model. Umm,... What is "change"? In the case of EditBox and EditLine, the "change" means the time user edits the text. But in ListBox, it means "select", not change the items. Try out the following. Shoes.app do lb = list_box items: [1,2,3,4,5] lb.change{para lb.text} end I think your upsert and sort functions are useful. But they are the functions that manipulate items. And the items is an Array object, not ListBox object. So, it's better not to add your upsert method into ListBox class itself, but to add Array class. IMHO, though. :) > I hope your last line is not true. hahaha. Yeah, I hope, too. Let's continue to discuss about a simple TreeView API and try to implement. ;-) ashbb
Hi Nicholas and folks,
> try to implement. ;-)
Done!
https://github.com/ashbb/green_shoes/commit/8878a7cfc9bc696c3c5c8c5413f3134c69ede458
Try out the latest Green Shoes. :-D
http://rubygems.org/gems/green_shoes
ashbb
Excellent work! I've been playing about in a different direction -
possibly related to the other thread on observers. I keep going on
about the importance of separating the model and the view; consider
this example -
require 'green_shoes'
Shoes.app do
para "Choose a number:"
@num = 1
@box = list_box items: [@num.to_s]
button "Add Next" do @box.items << (@num += 1).to_s end
button "Remove Odd" do @box.items.select!{|num|num.to_i%2==0} end
button "Sysout Items" do puts @box.items.inspect end
end
Here the model is an array of strings, initially ["1"]. I've added
some buttons that play about with the model (i.e. add and remove
elements from it, and to print the contents of the model out to the
console. If you try this app you'll see that the model as dumped to
the console is updated correctly, but the view (i.e. the list_box) is
never updated (it only has the single choice "1"). I've had to do
quite a bit of hackery to get this to work as expected (and had to use
the "diff" gem as I don't have time to re-invent the wheel :)...
require 'observer'
require 'diff'
class Shoes::ListBox < Shoes::Native
def initialize args
super args
@observer = lambda{|diff|
offset = 0
diff.diffs.map{|x|x[0]}.each{|sign,index,value|
if sign == '+'
real.insert_text index + offset, value; offset += 1
else
real.remove_text index + offset; offset -= 1
end
}
} # fired when the model changes
@remove_observer = lambda{} # to unwire the current model from the view
self.items = @items || [] # wire up the initial model
end
def items= items
# current implementation
@items.length.times{real.remove_text 0}
@items = items
items.each{|item| real.append_text item.to_s}
# wire view to model (note we do all this mangling of the
# items object in case anyone has a reference to it.
actual_array = items.dup
(items.methods - Object.class.instance_methods +
[:to_s,:inspect]).each{|m|items.instance_eval "undef :#{m}"}
items.extend Observable
items.instance_variable_set :@actual_array, actual_array
def items.method_missing m, *args, &blk
copy = @actual_array.dup
ret = @actual_array.send m, *args, &blk
puts @actual_array.inspect
if copy != @actual_array; changed; notify_observers
copy.diff(@actual_array) end
ret
end
# good old observer pattern
@remove_observer.call # remove old model
@remove_observer = lambda{ # remove this model (for next time)
items.delete_observer @observer
}
items.add_observer @observer, :call
end
end
If you paste this between the require 'green_shoes' and the app then
it works as expected (i.e. the view - list_box - correctly shows the
model after the buttons are pressed). The code's a bit unreadable, but
the general idea is to hook all (interesting) method calls to the
model and fire an observer if the model has changed. It gives the
observer a list of diffs that have been applied to the model, and the
observer makes calls on the GTK widget binding to update it to match
the model. What are your thoughts on this?
Also, I've noticed you duplicate the data in the model - there's one
copy in the GTK widget and another copy in the "items" instance
variable of the list_box. This is not a big issue in this example, but
may cause memory-related problems in a treeview with loads of data. I
haven't finished looking through your treeview commit, so I don't know
if your implementation suffers from this, though.
Thanks -
Nick
On 2 August 2011 13:55, ashbb <ashbbb@gmail.com> wrote:
> Hi Nicholas and folks,
>
>> try to implement. ;-)
> Done!
>
>
https://github.com/ashbb/green_shoes/commit/8878a7cfc9bc696c3c5c8c5413f3134c69ede458
>
> Try out the latest Green Shoes. :-D
>
> http://rubygems.org/gems/green_shoes
>
> ashbb
>
Hi Nick, > I've had to do quite a bit of hackery to get this to work as expected Wow, awesome code using observer and diff. I've not used the gem. :-P But wait a moment. We have the `ListBox#items=` method. So, let me revise your first code a little bit. ;-) require 'green_shoes' Shoes.app do para "Choose a number:" @num = 1 @box = list_box items: [@num] button "Add Next" do @box.items += [@num += 1] end button "Remove Odd" do @box.items = @box.items.select{|num|num%2==0} end button "Sysout Items" do puts @box.items.inspect end end It seems to work as expected. ;-) > Also, I've noticed you duplicate the data in the model - there's > one copy in the GTK widget and another copy in the "items" > instance variable of the list_box. Yes, you are right. Hence, you can write without `to_s` like the above code. ;-) Cheers, ashbb
I take your point that it's a little complicated :) However, while
using item= fixes the app I wrote it means you have to abide by
non-obvious rules or the view will get out of sync with the model.
Also, this doesn't address the fact that the are two copies of the
mode (one in the Ruby code & one in the GTK widget) - and using item=
means you have to clear and repopulate the entire GTK model every time
you want to do an update, no matter how trivial. This is not an issue
with the list_box as there's so few Strings in the model that the
performance & memory overhead is minimal, but in a TreeView with many
columns and tens of rows this would be noticeable. As an aside, the
GTK ComboBox behind the list_box stores its model in a TreeModel - the
same object behind the GTK TreeView[1]. This is why I think paying the
price of a complicated list_box implementation is necessary - the code
will generalise to an efficient TreeView implementation.
I've simplified my solution - what we really want for our model is
something that responds_to? exactly like an Array, but in fact is a
thing layer over (i.e. delegates the actual storage to) the GTK model.
This is a fairly useful idea - I've spent a while googling for
something like it previously when I was looking at object-relational
mapping (think Hiberate in Java) - I wanted something that looked like
an Array, but every time you accessed an element it would read it from
a database, and every time you set an element in the array it would
insert or update a row in the database. I've generalised this idea to
the below (implementation is incomplete - bear with me...):
# so you basically need at(i), set(i,val), size & delete_at(i)
# in your actual class to be an array
module ArrayLike
include Enumerable
def clear
size.times{|i|delete_at 0}; self
end
# returns copy, so ok to be inefficient
[:+, :-, :&, :*, :|].each{|m| instance_eval "def #{m} other;
to_ary.send :#{m}, other end"}
def to_ary; ret = []; each{|x|ret<<x}; ret end
def concat other; other.each{|x|push x}; self end
def empty?; size == 0 end
def push *args; args.each{|arg|set size, arg} end
def join sep=$,
each_with_object(""){|i,s|s.empty? ? (s<<i) : (s<<sep<<i) }
end
def inspect; "[#{join(", ")}]" end
def select! &blk
off=0
size.times{|i|
if not blk.call at(i+off); delete_at (i+off); off-=1 end
}
self
end
def each; size.times{|i|yield at(i)} end
def slice! *args; ret=self[*args]; self[*args]=nil; ret end
def values_at *args
args.map{|a|a.class.included_modules.include?(Enumerable) ? a.to_a
: [a]}.flatten.map{|i|at(i)}
end
alias_method :<<, :push
alias_method :index, :find_index
end
# todo
# [ "<=>", "assoc", "choice", "collect!", "combination", "compact",
# "compact!", "delete", "delete_if", "each_index", "fetch", "fill",
# "flatten", "flatten!", "indexes", "indices", "insert", "last", "length"
# "map!", "nitems", "pack", "permutation", "pop", "product", "rassoc",
# "reject!", "replace", "reverse", "reverse!", "rindex", "shift",
# "shuffle", "shuffle!", "transpose", "slice", "sort!", "uniq", "uniq!",
# "unshift" ]
I've left off a quite a few methods, but you get the idea; you
implement set, at, delete_at & size methods in your class that
delegate to your backing store, include this module and you act like
an array. All the implementations of the array methods are done so
that they only references these four delegating methods. You don't
even have to write unit tests for this - you can use the ones in
Rubyspec[2] to ensure this model is exactly interchangable with a
native Ruby Array. If you do implement the rest of this you should
consider releasing it as a separate gem - I unfortunately don't have
the time or I'd do this myself (above code BSD-licensed if you want it
:).
To use this in my example shoes app you still need some (although
less) monkey-patching:
class Shoes::ListBox < Shoes::Native
def initialize args
super args
@remove_observer = lambda{} # to unwire the current model from the view
self.items = @items || [] # wire up the initial model
end
def items= items
# current implementation
@items.length.times{real.remove_text 0}
@items = items
items.each{|item| real.append_text item.to_s}
# wire view to model (note we do all this mangling of the
# items object in case anyone has a reference to it).
items.extend ArrayLike
items.clear # might as well save some memory
items.instance_variable_set :@real, real
# and now the implementation of the ArrayLike interface
def items.size; i=0; @real.model.each{i+=1}; i end
def items.at i
count=0
@real.model.each{|model, path, iter|
return model.get_value iter, 0 if i == count;
count+=1
}
end
def items.delete_at i; @real.remove_text (i<0 ? (i+size) : i) end
def items.set i, val
i+=size if i<0
if i == size
@real.append_text val.to_s
else
@real.remove_text i; real.insert_text i, val
end
end
@remove_observer.call # remove old model
@remove_observer = lambda{ # remove this model (for next time)
new_items = items.to_a
# re-implement the ArrayLike interface, pointing to the new_items Array
def items.size; new_items.size end
def items.at i; new_items.at(i) end
def items.delete_at i; new_items.delete_at(i) end
def set i, val; new_items[i] = val end
}
end
end
Shoes.app do
para "Choose a number:"
@num = 1
@box = list_box items: [@num.to_s]
button "Add Next" do @box.items << (@num += 1).to_s end
button "Remove Odd" do @box.items.select!{|num|num.to_i%2==0} end
button "Sysout Items" do puts @box.items.inspect end
end
The code that interacts with the GTK ComboBox is grossly inefficient
(lots of looping) but I couldn't find a "size" method in the API. I'm
also not sure that this would work as-is with multiple list_boxes (or
tree_views) sharing the same model - you'd have to have another layer
between the items Array (now ArrayLike) and the ComboBoxes / TreeViews
that implemented the Observer pattern (and so all the views could be
observers of a single model).
What do you think?
Nick
[1] http://ruby-gnome2.sourceforge.jp/hiki.cgi?Gtk%3A%3AComboBox#model
[2] https://github.com/rubyspec/rubyspec/tree/master/core/array
On 4 August 2011 13:26, ashbb <ashbbb@gmail.com> wrote:
> Hi Nick,
>
>> I've had to do quite a bit of hackery to get this to work as expected
> Wow, awesome code using observer and diff. I've not used the gem. :-P
>
> But wait a moment. We have the `ListBox#items=` method.
> So, let me revise your first code a little bit. ;-)
>
> require 'green_shoes'
> Shoes.app do
> para "Choose a number:"
> @num = 1
> @box = list_box items: [@num]
> button "Add Next" do
> @box.items += [@num += 1]
> end
> button "Remove Odd" do
> @box.items = @box.items.select{|num|num%2==0}
> end
> button "Sysout Items" do puts @box.items.inspect end
> end
>
> It seems to work as expected. ;-)
>
>> Also, I've noticed you duplicate the data in the model - there's
>> one copy in the GTK widget and another copy in the "items"
>> instance variable of the list_box.
> Yes, you are right.
> Hence, you can write without `to_s` like the above code. ;-)
>
> Cheers,
> ashbb
>
>
Hi Nicholas, > using item= means you have to clear and repopulate the entire GTK model > every time you want to do an update Yes, you are right. > but in a TreeView with many columns and tens of rows this would be > noticeable. In general, that's true. But there are very few Shoes apps which need so much high performance, IMHO. :-P As you know, the `:item` attribute is just an array. So, if you accept using `item=` method when you update each time, you can use whole methods of Array. ashbb
> But there are very few Shoes apps which need so much high performance, IMHO. I do! But seriously, that's why I started this thread - I'm writing GUIs that display and aggregate large amounts of data to make it "useful" (think H2's database HTML viewer[1] or excel's pivot tables[2] that let you drill into aggregate rows). No ruby GUI toolkits really support this yet (I could use the GTK2 bindings directly, but I want concise GUI code, and WxRuby's wxGrid only supports 2D data) - so I thought I'd help shoes out as it's by far the easiest toolkit to work with. I'm not trying to be difficult by thinking of theoretical edge cases - the functionality I've been sketching out is pretty much a requirement for my use case. However, I appreciate that this isn't shoes' usual use-case, and so will understand if you don't want to over-complicate the shoes implementation by supporting it... [1] http://img.skitch.com/20101127-fbacj94arqxfbkqnph7g733r5u.png [2] http://www.mssqltips.com/tipimages2/1716_pivot_table.jpg On 7 August 2011 08:54, ashbb <ashbbb@gmail.com> wrote: > Hi Nicholas, > >> using item= means you have to clear and repopulate the entire GTK model >> every time you want to do an update > Yes, you are right. > >> but in a TreeView with many columns and tens of rows this would be >> noticeable. > In general, that's true. > But there are very few Shoes apps which need so much high performance, IMHO. > :-P > > As you know, the `:item` attribute is just an array. > So, if you accept using `item=` method when you update each time, > you can use whole methods of Array. > > ashbb >
Hi Nicholas, >> But there are very few Shoes apps which need so much high performance, IMHO. > I do! Oh, yeah. :) Okay, let me try to check treeview performance with Green Shoes. Look at this: https://gist.github.com/1131773 - Line 23-33: Set up a table with about 10000 rows. - Line 36-45: Open TreeView Window button launches a treeview window. - Line 47-52: Modify Data button replaces two values to 9999 and executes `tree_view_text=` method. - Line 5-18: The tree_view_text= method does just clear all treeview rows and append all new rows again. Look at this flush video: http://www.rin-shun.com/shoes/green_shoes_treeview_performance_check.swf.html The performance (response) is not so quick, but not so bad, IMHO. Can't you bear it? ashbb