Dire TreeView performance in .Net 2.0 vs 1.1

K

kiwidude

All,

Microsoft appear to have changed the internal implementation of the
TreeView control and the changes appear to be not for the better as far
as my application is concerned. Has anyone else had performance issues
with it?

The issue I have is that I need to change the .Text and .ImageIndex of
the nodes AFTER the tree has been populated. This is because the tree
contains percentages etc based on the children of each node.

In .Net 1.0/1.1 I had this finely tuned to do a "load on demand". I
override OnBeforeExpand() so as you expand a node it updates the
text/imageindex of the children which works really well. The number of
node children is usually around the 10-20 mark but often only a few, so
very little processing involved this way. Performance is great under
1.1.

However running exactly the same code under .Net 2.0 gives what ranges
from "appalling" to just plain "dire" usability.

The "improved" performance is gained by adding BeginUpdate/EndUpdate
around the code which iterates through the child nodes updating the
..Text. However the performance is still completely awful compared to
..Net 1.1, and it comes down to the .EndUpdate method which is chewing
up the time in it's calls to Control.SendMessage.

The alternative without using BeginUpdate/EndUpdate?

I found that when setting the Node.Text property the TreeView goes into
meltdown with literally millions of windows messages and handle
requests initiated through TreeNode.set_Text -> TreeNode.UpdateNode ->
TreeView.ForceScrollbarUpdate.

As an experiment I set "Scrollable=false" on the TreeView. Sure enough,
I now get the equivalent of .Net 1.1 performance when expanding... but
of course now the treeview is not scrollable. You ask "why not just
turn that off and on afterwards then"? Because the TreeView appears to
completely reset and redraw itself when the Scrollable is set back to
true which is completely unusable.

So - I'm knackered if I used EndUpdate, and I'm knackered if I don't.

Does anyone have any suggestions? At the moment it is "don't use .Net
2.0" which clearly ain't gonna fly long term.

Many thanks.
 
R

Robbe Morris [C# MVP]

I have not seen these types of performance issues. I know
the tree loads a few thousand nodes rather quickly for me.

I do a fair amount of work with hierarchies in research
applications. So, I'm somewhat familiar with apps that
do this sort of thing.

Out of curiousity, are the percentages on each TreeNode
a percentage within its siblings or are the percentages
of themselves in conjunction with either their parents
or their own sibling branches?

Typically you'd expect it to be the percentages of
siblings. In that case, there would only be 10 or 20
siblings to update.

I realize your existing code worked in .net 1.1 but
could you shed some light on why you need to adjust
so many nodes in the OnBeforeExpand event?

--
Robbe Morris - 2004-2006 Microsoft MVP C#
Earn money publishing .NET articles at
http://www.eggheadcafe.com
 
K

kiwidude

Hi Robbe,

The application is a code coverage GUI tool (NCoverExplorer). Depending
on the "view" a user chooses (% visited, # seq pts unvisited etc) and
the node type the text is calculated appropriately.

As for why so many nodes are being updated - that's exactly my point in
that I do not actually update all that many nodes (just the immediate
children of the node being expanded which is usually in the 10-20 node
region). When I profile this under .Net 1.1 I see several orders of
magnitude less in the way of messages etc. However in .Net 2.0 it just
explodes in the amount of work going on underneath.

In fact when I comment out the code actually doing the updating of
nodes and do nothing but BeginUpdate/EndUpdate in the OnBeforeExpand
method I still see the same poor performance.

Regards,
Grant.
 
K

kiwidude

Here's the results of a very simple test harness I knocked up (chuck me
an e-mail if you want the full code). It loads a tree with 1 x 25 x 25
x 25 nodes (15,626 for those without a calculator). In the
OnBeforeExpand method I do this (the if statements just wrap checkboxes
in the test harness for the permutations):

protected override void OnBeforeExpand(TreeViewCancelEventArgs e)
{
if (UseBeginUpdate) {
BeginUpdate();
}
if (UpdateText) {
for (int i = 0; i < e.Node.Nodes.Count; i++) {
e.Node.Nodes.Text = "ChildUpdated" + i.ToString();
}
}
if (UseBeginUpdate) {
EndUpdate();
}
base.OnBeforeExpand();
}


The timing results are as follows for expanding a node (using
QueryPerformance API for timings):

Using .Net 1.1:
0.2 secs (with BeginUpdate and updating .Text)
0.2 secs (just updating .Text)
0.007 secs (with BeginUpdate)

Using .Net 2.0
1.2 secs (with BeginUpdate and updating .Text)
30.6 secs (just updating .Text)
1.2 secs (with BeginUpdate)

Setting Scrollable to false under .Net 2.0 (unusable but out of
curiosity)
0.001 secs (just updating .Text)


So in summary - you can see that .Net 2.0 for this size of tree is
approximately six times slower than .Net 1.1 when using Begin/EndUpdate
(and about 150 times slower just updating .Text without it which
obviously is a bad thing to be doing). You can also see that just the
call to BeginUpdate/EndUpdate alone still incurs a massive penalty even
if you don't actually update anything.

Waiting over a second while you expand a node that only has a couple of
children is completely rubbish.

Is anyone using a third party tree control that doesn't suck as badly
under .Net 2.0, or has found some way of making this tree usable when
your .Text contents need to be updated dynamically?

Grant.
 
R

Robbe Morris [C# MVP]

Hmm. I downloaded my .net 1.1 sample and converted it to 2.0.

http://www.eggheadcafe.com/articles/treeview_databinding.asp

It only runs 1 to 2 thousand nodes. Quite a bit smaller than
your sample. However, the speed is pretty darn quick
running your sample code.

I see that you are overriding the standard event. Is
this your own overriden tree control implementation?

--
Robbe Morris - 2004-2006 Microsoft MVP C#
Earn money publishing .NET articles at
http://www.eggheadcafe.com




kiwidude said:
Here's the results of a very simple test harness I knocked up (chuck me
an e-mail if you want the full code). It loads a tree with 1 x 25 x 25
x 25 nodes (15,626 for those without a calculator). In the
OnBeforeExpand method I do this (the if statements just wrap checkboxes
in the test harness for the permutations):

protected override void OnBeforeExpand(TreeViewCancelEventArgs e)
{
if (UseBeginUpdate) {
BeginUpdate();
}
if (UpdateText) {
for (int i = 0; i < e.Node.Nodes.Count; i++) {
e.Node.Nodes.Text = "ChildUpdated" + i.ToString();
}
}
if (UseBeginUpdate) {
EndUpdate();
}
base.OnBeforeExpand();
}


The timing results are as follows for expanding a node (using
QueryPerformance API for timings):

Using .Net 1.1:
0.2 secs (with BeginUpdate and updating .Text)
0.2 secs (just updating .Text)
0.007 secs (with BeginUpdate)

Using .Net 2.0
1.2 secs (with BeginUpdate and updating .Text)
30.6 secs (just updating .Text)
1.2 secs (with BeginUpdate)

Setting Scrollable to false under .Net 2.0 (unusable but out of
curiosity)
0.001 secs (just updating .Text)


So in summary - you can see that .Net 2.0 for this size of tree is
approximately six times slower than .Net 1.1 when using Begin/EndUpdate
(and about 150 times slower just updating .Text without it which
obviously is a bad thing to be doing). You can also see that just the
call to BeginUpdate/EndUpdate alone still incurs a massive penalty even
if you don't actually update anything.

Waiting over a second while you expand a node that only has a couple of
children is completely rubbish.

Is anyone using a third party tree control that doesn't suck as badly
under .Net 2.0, or has found some way of making this tree usable when
your .Text contents need to be updated dynamically?

Grant.
Hi Robbe,

The application is a code coverage GUI tool (NCoverExplorer). Depending
on the "view" a user chooses (% visited, # seq pts unvisited etc) and
the node type the text is calculated appropriately.

As for why so many nodes are being updated - that's exactly my point in
that I do not actually update all that many nodes (just the immediate
children of the node being expanded which is usually in the 10-20 node
region). When I profile this under .Net 1.1 I see several orders of
magnitude less in the way of messages etc. However in .Net 2.0 it just
explodes in the amount of work going on underneath.

In fact when I comment out the code actually doing the updating of
nodes and do nothing but BeginUpdate/EndUpdate in the OnBeforeExpand
method I still see the same poor performance.

Regards,
Grant.
 
K

kiwidude

Hi Robbe,

It is indeed my own implementation, but I'm not doing anything funky.
All those performance figures above were from a completely clean test
harness that just had that one override, no other changes from the
TreeView defaults.

I've written a blog entry about the problem:
http://www.kiwidude.com/blog/2006/09/net-20-treeview-performance-problem.html

and put my sample code available for download from here:
http://www.kiwidude.com/dotnet/TreeViewTest.zip

Regards,
Grant.
Hmm. I downloaded my .net 1.1 sample and converted it to 2.0.

http://www.eggheadcafe.com/articles/treeview_databinding.asp

It only runs 1 to 2 thousand nodes. Quite a bit smaller than
your sample. However, the speed is pretty darn quick
running your sample code.

I see that you are overriding the standard event. Is
this your own overriden tree control implementation?

--
Robbe Morris - 2004-2006 Microsoft MVP C#
Earn money publishing .NET articles at
http://www.eggheadcafe.com




kiwidude said:
Here's the results of a very simple test harness I knocked up (chuck me
an e-mail if you want the full code). It loads a tree with 1 x 25 x 25
x 25 nodes (15,626 for those without a calculator). In the
OnBeforeExpand method I do this (the if statements just wrap checkboxes
in the test harness for the permutations):

protected override void OnBeforeExpand(TreeViewCancelEventArgs e)
{
if (UseBeginUpdate) {
BeginUpdate();
}
if (UpdateText) {
for (int i = 0; i < e.Node.Nodes.Count; i++) {
e.Node.Nodes.Text = "ChildUpdated" + i.ToString();
}
}
if (UseBeginUpdate) {
EndUpdate();
}
base.OnBeforeExpand();
}


The timing results are as follows for expanding a node (using
QueryPerformance API for timings):

Using .Net 1.1:
0.2 secs (with BeginUpdate and updating .Text)
0.2 secs (just updating .Text)
0.007 secs (with BeginUpdate)

Using .Net 2.0
1.2 secs (with BeginUpdate and updating .Text)
30.6 secs (just updating .Text)
1.2 secs (with BeginUpdate)

Setting Scrollable to false under .Net 2.0 (unusable but out of
curiosity)
0.001 secs (just updating .Text)


So in summary - you can see that .Net 2.0 for this size of tree is
approximately six times slower than .Net 1.1 when using Begin/EndUpdate
(and about 150 times slower just updating .Text without it which
obviously is a bad thing to be doing). You can also see that just the
call to BeginUpdate/EndUpdate alone still incurs a massive penalty even
if you don't actually update anything.

Waiting over a second while you expand a node that only has a couple of
children is completely rubbish.

Is anyone using a third party tree control that doesn't suck as badly
under .Net 2.0, or has found some way of making this tree usable when
your .Text contents need to be updated dynamically?

Grant.
Hi Robbe,

The application is a code coverage GUI tool (NCoverExplorer). Depending
on the "view" a user chooses (% visited, # seq pts unvisited etc) and
the node type the text is calculated appropriately.

As for why so many nodes are being updated - that's exactly my point in
that I do not actually update all that many nodes (just the immediate
children of the node being expanded which is usually in the 10-20 node
region). When I profile this under .Net 1.1 I see several orders of
magnitude less in the way of messages etc. However in .Net 2.0 it just
explodes in the amount of work going on underneath.

In fact when I comment out the code actually doing the updating of
nodes and do nothing but BeginUpdate/EndUpdate in the OnBeforeExpand
method I still see the same poor performance.

Regards,
Grant.

Robbe Morris [C# MVP] wrote:
I have not seen these types of performance issues. I know
the tree loads a few thousand nodes rather quickly for me.

I do a fair amount of work with hierarchies in research
applications. So, I'm somewhat familiar with apps that
do this sort of thing.

Out of curiousity, are the percentages on each TreeNode
a percentage within its siblings or are the percentages
of themselves in conjunction with either their parents
or their own sibling branches?

Typically you'd expect it to be the percentages of
siblings. In that case, there would only be 10 or 20
siblings to update.

I realize your existing code worked in .net 1.1 but
could you shed some light on why you need to adjust
so many nodes in the OnBeforeExpand event?

--
Robbe Morris - 2004-2006 Microsoft MVP C#
Earn money publishing .NET articles at
http://www.eggheadcafe.com




All,

Microsoft appear to have changed the internal implementation of the
TreeView control and the changes appear to be not for the better as
far
as my application is concerned. Has anyone else had performance
issues
with it?

The issue I have is that I need to change the .Text and .ImageIndex
of
the nodes AFTER the tree has been populated. This is because the tree
contains percentages etc based on the children of each node.

In .Net 1.0/1.1 I had this finely tuned to do a "load on demand". I
override OnBeforeExpand() so as you expand a node it updates the
text/imageindex of the children which works really well. The number
of
node children is usually around the 10-20 mark but often only a few,
so
very little processing involved this way. Performance is great under
1.1.

However running exactly the same code under .Net 2.0 gives what
ranges
from "appalling" to just plain "dire" usability.

The "improved" performance is gained by adding BeginUpdate/EndUpdate
around the code which iterates through the child nodes updating the
.Text. However the performance is still completely awful compared to
.Net 1.1, and it comes down to the .EndUpdate method which is chewing
up the time in it's calls to Control.SendMessage.

The alternative without using BeginUpdate/EndUpdate?

I found that when setting the Node.Text property the TreeView goes
into
meltdown with literally millions of windows messages and handle
requests initiated through TreeNode.set_Text ->
TreeNode.UpdateNode ->
TreeView.ForceScrollbarUpdate.

As an experiment I set "Scrollable=false" on the TreeView. Sure
enough,
I now get the equivalent of .Net 1.1 performance when expanding...
but
of course now the treeview is not scrollable. You ask "why not just
turn that off and on afterwards then"? Because the TreeView appears
to
completely reset and redraw itself when the Scrollable is set back to
true which is completely unusable.

So - I'm knackered if I used EndUpdate, and I'm knackered if I don't.

Does anyone have any suggestions? At the moment it is "don't use .Net
2.0" which clearly ain't gonna fly long term.

Many thanks.
 
G

Grant Frisken

Check out Infralution's Virtual Tree. Because it only loads the data
actually required for the currently displayed nodes it is much quicker
and leaner than the standard TreeView for large amounts of data.

You can get more information and download a fully functional evaluation
version from:

www.infralution.com/virtualtree.html

Grant Frisken
Infralution


Here's the results of a very simple test harness I knocked up (chuck me
an e-mail if you want the full code). It loads a tree with 1 x 25 x 25
x 25 nodes (15,626 for those without a calculator). In the
OnBeforeExpand method I do this (the if statements just wrap checkboxes
in the test harness for the permutations):

protected override void OnBeforeExpand(TreeViewCancelEventArgs e)
{
if (UseBeginUpdate) {
BeginUpdate();
}
if (UpdateText) {
for (int i = 0; i < e.Node.Nodes.Count; i++) {
e.Node.Nodes.Text = "ChildUpdated" + i.ToString();
}
}
if (UseBeginUpdate) {
EndUpdate();
}
base.OnBeforeExpand();
}


The timing results are as follows for expanding a node (using
QueryPerformance API for timings):

Using .Net 1.1:
0.2 secs (with BeginUpdate and updating .Text)
0.2 secs (just updating .Text)
0.007 secs (with BeginUpdate)

Using .Net 2.0
1.2 secs (with BeginUpdate and updating .Text)
30.6 secs (just updating .Text)
1.2 secs (with BeginUpdate)

Setting Scrollable to false under .Net 2.0 (unusable but out of
curiosity)
0.001 secs (just updating .Text)


So in summary - you can see that .Net 2.0 for this size of tree is
approximately six times slower than .Net 1.1 when using Begin/EndUpdate
(and about 150 times slower just updating .Text without it which
obviously is a bad thing to be doing). You can also see that just the
call to BeginUpdate/EndUpdate alone still incurs a massive penalty even
if you don't actually update anything.

Waiting over a second while you expand a node that only has a couple of
children is completely rubbish.

Is anyone using a third party tree control that doesn't suck as badly
under .Net 2.0, or has found some way of making this tree usable when
your .Text contents need to be updated dynamically?

Grant.
Hi Robbe,

The application is a code coverage GUI tool (NCoverExplorer). Depending
on the "view" a user chooses (% visited, # seq pts unvisited etc) and
the node type the text is calculated appropriately.

As for why so many nodes are being updated - that's exactly my point in
that I do not actually update all that many nodes (just the immediate
children of the node being expanded which is usually in the 10-20 node
region). When I profile this under .Net 1.1 I see several orders of
magnitude less in the way of messages etc. However in .Net 2.0 it just
explodes in the amount of work going on underneath.

In fact when I comment out the code actually doing the updating of
nodes and do nothing but BeginUpdate/EndUpdate in the OnBeforeExpand
method I still see the same poor performance.

Regards,
Grant.
 
R

Robbe Morris [C# MVP]

Downloaded your sample from your blog. Ran it under
..NET 1.1 and .NET 2.0. You are correct, there is a
considerable difference in performance. I did adjust
your code to only use the .NET version rather than
your inherited class "just to be 100%" it wasn't you.

It wasn't.

I did get some improvement whenever I disabled
the tree prior to loading it or making a lot of node
changes and then enabling it again when done. I did
this before the beginupdate and after the endupdate stuff.

I worked with sets of 25 as well as lower volumes
around 18 - 20. Dropping down to around 10k nodes
seemed to provide acceptable performance for a typical
UI.

Still underperforms. You may want to get a hold
of Lutz's Reflector and compare the two System.Windows.Forms
assemblies and the TreeView class. You might be able to
identify the poor performing code and override it. Perhaps...



--
Robbe Morris - 2004-2006 Microsoft MVP C#
I've mapped the database to .NET class properties and methods to
implement an multi-layered object oriented environment for your
data access layer. Thus, you should rarely ever have to type the words
SqlCommand, SqlDataAdapter, or SqlConnection again.
http://www.eggheadcafe.com/articles/adonet_source_code_generator.asp




kiwidude said:
Hi Robbe,

It is indeed my own implementation, but I'm not doing anything funky.
All those performance figures above were from a completely clean test
harness that just had that one override, no other changes from the
TreeView defaults.

I've written a blog entry about the problem:
http://www.kiwidude.com/blog/2006/09/net-20-treeview-performance-problem.html

and put my sample code available for download from here:
http://www.kiwidude.com/dotnet/TreeViewTest.zip

Regards,
Grant.
Hmm. I downloaded my .net 1.1 sample and converted it to 2.0.

http://www.eggheadcafe.com/articles/treeview_databinding.asp

It only runs 1 to 2 thousand nodes. Quite a bit smaller than
your sample. However, the speed is pretty darn quick
running your sample code.

I see that you are overriding the standard event. Is
this your own overriden tree control implementation?

--
Robbe Morris - 2004-2006 Microsoft MVP C#
Earn money publishing .NET articles at
http://www.eggheadcafe.com




kiwidude said:
Here's the results of a very simple test harness I knocked up (chuck me
an e-mail if you want the full code). It loads a tree with 1 x 25 x 25
x 25 nodes (15,626 for those without a calculator). In the
OnBeforeExpand method I do this (the if statements just wrap checkboxes
in the test harness for the permutations):

protected override void OnBeforeExpand(TreeViewCancelEventArgs e)
{
if (UseBeginUpdate) {
BeginUpdate();
}
if (UpdateText) {
for (int i = 0; i < e.Node.Nodes.Count; i++) {
e.Node.Nodes.Text = "ChildUpdated" + i.ToString();
}
}
if (UseBeginUpdate) {
EndUpdate();
}
base.OnBeforeExpand();
}


The timing results are as follows for expanding a node (using
QueryPerformance API for timings):

Using .Net 1.1:
0.2 secs (with BeginUpdate and updating .Text)
0.2 secs (just updating .Text)
0.007 secs (with BeginUpdate)

Using .Net 2.0
1.2 secs (with BeginUpdate and updating .Text)
30.6 secs (just updating .Text)
1.2 secs (with BeginUpdate)

Setting Scrollable to false under .Net 2.0 (unusable but out of
curiosity)
0.001 secs (just updating .Text)


So in summary - you can see that .Net 2.0 for this size of tree is
approximately six times slower than .Net 1.1 when using Begin/EndUpdate
(and about 150 times slower just updating .Text without it which
obviously is a bad thing to be doing). You can also see that just the
call to BeginUpdate/EndUpdate alone still incurs a massive penalty even
if you don't actually update anything.

Waiting over a second while you expand a node that only has a couple of
children is completely rubbish.

Is anyone using a third party tree control that doesn't suck as badly
under .Net 2.0, or has found some way of making this tree usable when
your .Text contents need to be updated dynamically?

Grant.

kiwidude wrote:
Hi Robbe,

The application is a code coverage GUI tool (NCoverExplorer).
Depending
on the "view" a user chooses (% visited, # seq pts unvisited etc) and
the node type the text is calculated appropriately.

As for why so many nodes are being updated - that's exactly my point
in
that I do not actually update all that many nodes (just the immediate
children of the node being expanded which is usually in the 10-20 node
region). When I profile this under .Net 1.1 I see several orders of
magnitude less in the way of messages etc. However in .Net 2.0 it just
explodes in the amount of work going on underneath.

In fact when I comment out the code actually doing the updating of
nodes and do nothing but BeginUpdate/EndUpdate in the OnBeforeExpand
method I still see the same poor performance.

Regards,
Grant.

Robbe Morris [C# MVP] wrote:
I have not seen these types of performance issues. I know
the tree loads a few thousand nodes rather quickly for me.

I do a fair amount of work with hierarchies in research
applications. So, I'm somewhat familiar with apps that
do this sort of thing.

Out of curiousity, are the percentages on each TreeNode
a percentage within its siblings or are the percentages
of themselves in conjunction with either their parents
or their own sibling branches?

Typically you'd expect it to be the percentages of
siblings. In that case, there would only be 10 or 20
siblings to update.

I realize your existing code worked in .net 1.1 but
could you shed some light on why you need to adjust
so many nodes in the OnBeforeExpand event?

--
Robbe Morris - 2004-2006 Microsoft MVP C#
Earn money publishing .NET articles at
http://www.eggheadcafe.com




All,

Microsoft appear to have changed the internal implementation of
the
TreeView control and the changes appear to be not for the better
as
far
as my application is concerned. Has anyone else had performance
issues
with it?

The issue I have is that I need to change the .Text and
.ImageIndex
of
the nodes AFTER the tree has been populated. This is because the
tree
contains percentages etc based on the children of each node.

In .Net 1.0/1.1 I had this finely tuned to do a "load on demand".
I
override OnBeforeExpand() so as you expand a node it updates the
text/imageindex of the children which works really well. The
number
of
node children is usually around the 10-20 mark but often only a
few,
so
very little processing involved this way. Performance is great
under
1.1.

However running exactly the same code under .Net 2.0 gives what
ranges
from "appalling" to just plain "dire" usability.

The "improved" performance is gained by adding
BeginUpdate/EndUpdate
around the code which iterates through the child nodes updating
the
.Text. However the performance is still completely awful compared
to
.Net 1.1, and it comes down to the .EndUpdate method which is
chewing
up the time in it's calls to Control.SendMessage.

The alternative without using BeginUpdate/EndUpdate?

I found that when setting the Node.Text property the TreeView goes
into
meltdown with literally millions of windows messages and handle
requests initiated through TreeNode.set_Text ->
TreeNode.UpdateNode ->
TreeView.ForceScrollbarUpdate.

As an experiment I set "Scrollable=false" on the TreeView. Sure
enough,
I now get the equivalent of .Net 1.1 performance when expanding...
but
of course now the treeview is not scrollable. You ask "why not
just
turn that off and on afterwards then"? Because the TreeView
appears
to
completely reset and redraw itself when the Scrollable is set back
to
true which is completely unusable.

So - I'm knackered if I used EndUpdate, and I'm knackered if I
don't.

Does anyone have any suggestions? At the moment it is "don't use
.Net
2.0" which clearly ain't gonna fly long term.

Many thanks.

 
K

kiwidude

Hi Grant,

I did give this control a quick look. Let me qualify my comments with
"it was very quick" so if I missed something then feel free to correct
me. The Infralution control seemed very focused on the data binding
perspective rather than the particular way I use the tree. I couldn't
see any events related to the expanding of nodes to modify the text
which is the basic problem I have.

Switching to a "data binding" style approach to make best use of your
control is not a trivial exercise. For better or worse I need to have
the entire tree populated to calculate statistics and report on the
nodes. Users can "prune", filter etc the tree so it can be constantly
changing. However I do not rebuild the entire tree from scratch each
time as you might in a data binding scenario as that would be way too
painful.

There are also certain practicalities over my application being under
GPL which don't make commercial controls an option.

Thanks for the suggestion though and certainly if in my "day job" I
need a data binding based solution it's nice to know someone out there
has another option available.

Regards,
Grant.

Grant said:
Check out Infralution's Virtual Tree. Because it only loads the data
actually required for the currently displayed nodes it is much quicker
and leaner than the standard TreeView for large amounts of data.

You can get more information and download a fully functional evaluation
version from:

www.infralution.com/virtualtree.html

Grant Frisken
Infralution


Here's the results of a very simple test harness I knocked up (chuck me
an e-mail if you want the full code). It loads a tree with 1 x 25 x 25
x 25 nodes (15,626 for those without a calculator). In the
OnBeforeExpand method I do this (the if statements just wrap checkboxes
in the test harness for the permutations):

protected override void OnBeforeExpand(TreeViewCancelEventArgs e)
{
if (UseBeginUpdate) {
BeginUpdate();
}
if (UpdateText) {
for (int i = 0; i < e.Node.Nodes.Count; i++) {
e.Node.Nodes.Text = "ChildUpdated" + i.ToString();
}
}
if (UseBeginUpdate) {
EndUpdate();
}
base.OnBeforeExpand();
}


The timing results are as follows for expanding a node (using
QueryPerformance API for timings):

Using .Net 1.1:
0.2 secs (with BeginUpdate and updating .Text)
0.2 secs (just updating .Text)
0.007 secs (with BeginUpdate)

Using .Net 2.0
1.2 secs (with BeginUpdate and updating .Text)
30.6 secs (just updating .Text)
1.2 secs (with BeginUpdate)

Setting Scrollable to false under .Net 2.0 (unusable but out of
curiosity)
0.001 secs (just updating .Text)


So in summary - you can see that .Net 2.0 for this size of tree is
approximately six times slower than .Net 1.1 when using Begin/EndUpdate
(and about 150 times slower just updating .Text without it which
obviously is a bad thing to be doing). You can also see that just the
call to BeginUpdate/EndUpdate alone still incurs a massive penalty even
if you don't actually update anything.

Waiting over a second while you expand a node that only has a couple of
children is completely rubbish.

Is anyone using a third party tree control that doesn't suck as badly
under .Net 2.0, or has found some way of making this tree usable when
your .Text contents need to be updated dynamically?

Grant.
Hi Robbe,

The application is a code coverage GUI tool (NCoverExplorer). Depending
on the "view" a user chooses (% visited, # seq pts unvisited etc) and
the node type the text is calculated appropriately.

As for why so many nodes are being updated - that's exactly my point in
that I do not actually update all that many nodes (just the immediate
children of the node being expanded which is usually in the 10-20 node
region). When I profile this under .Net 1.1 I see several orders of
magnitude less in the way of messages etc. However in .Net 2.0 it just
explodes in the amount of work going on underneath.

In fact when I comment out the code actually doing the updating of
nodes and do nothing but BeginUpdate/EndUpdate in the OnBeforeExpand
method I still see the same poor performance.

Regards,
Grant.

Robbe Morris [C# MVP] wrote:
I have not seen these types of performance issues. I know
the tree loads a few thousand nodes rather quickly for me.

I do a fair amount of work with hierarchies in research
applications. So, I'm somewhat familiar with apps that
do this sort of thing.

Out of curiousity, are the percentages on each TreeNode
a percentage within its siblings or are the percentages
of themselves in conjunction with either their parents
or their own sibling branches?

Typically you'd expect it to be the percentages of
siblings. In that case, there would only be 10 or 20
siblings to update.

I realize your existing code worked in .net 1.1 but
could you shed some light on why you need to adjust
so many nodes in the OnBeforeExpand event?

--
Robbe Morris - 2004-2006 Microsoft MVP C#
Earn money publishing .NET articles at
http://www.eggheadcafe.com




All,

Microsoft appear to have changed the internal implementation of the
TreeView control and the changes appear to be not for the better as far
as my application is concerned. Has anyone else had performance issues
with it?

The issue I have is that I need to change the .Text and .ImageIndex of
the nodes AFTER the tree has been populated. This is because the tree
contains percentages etc based on the children of each node.

In .Net 1.0/1.1 I had this finely tuned to do a "load on demand". I
override OnBeforeExpand() so as you expand a node it updates the
text/imageindex of the children which works really well. The number of
node children is usually around the 10-20 mark but often only a few, so
very little processing involved this way. Performance is great under
1.1.

However running exactly the same code under .Net 2.0 gives what ranges
from "appalling" to just plain "dire" usability.

The "improved" performance is gained by adding BeginUpdate/EndUpdate
around the code which iterates through the child nodes updating the
.Text. However the performance is still completely awful compared to
.Net 1.1, and it comes down to the .EndUpdate method which is chewing
up the time in it's calls to Control.SendMessage.

The alternative without using BeginUpdate/EndUpdate?

I found that when setting the Node.Text property the TreeView goes into
meltdown with literally millions of windows messages and handle
requests initiated through TreeNode.set_Text -> TreeNode.UpdateNode ->
TreeView.ForceScrollbarUpdate.

As an experiment I set "Scrollable=false" on the TreeView. Sure enough,
I now get the equivalent of .Net 1.1 performance when expanding... but
of course now the treeview is not scrollable. You ask "why not just
turn that off and on afterwards then"? Because the TreeView appears to
completely reset and redraw itself when the Scrollable is set back to
true which is completely unusable.

So - I'm knackered if I used EndUpdate, and I'm knackered if I don't.

Does anyone have any suggestions? At the moment it is "don't use .Net
2.0" which clearly ain't gonna fly long term.

Many thanks.
 
K

kiwidude

Hi Robbe,

Yeah I had started down the Reflector route and that was what led me to
the revelation that the "IsScrollable" property is the cause of the
literally millions of messages being pumped out when you avoid the
BeginUpdate/EndUpdate route. Hence that test harness has a "Scrollable"
checkbox on it - if you turn that off you will see how instantly
updated the tree is. Sadly it makes the tree unusable with it turned
off, and turning it back on results in the tree being completely
redrawn and from memory collapses the nodes again.

So that "forces" the BeginUpdate/EndUpdate route - and from what I saw
of what's going on in the problematic EndUpdate call its not an easy
one to workaround and impacts anyone who works with large trees.

I'm rapidly coming to the conclusion it's a case of either live with it
or spend some hours writing my own which simulates the 1.1 behaviour.

That just plain sucks - whoever in Microsoft is responsible for that
garbage TreeView control in .Net 2.0 should be summarily shot. We are
all used to "version 1" of anything usually being rubbish and living
with it - this being the third generation of the framework you don't
expect such a significant regression in the core controls.

Regards,
Grant.
Downloaded your sample from your blog. Ran it under
.NET 1.1 and .NET 2.0. You are correct, there is a
considerable difference in performance. I did adjust
your code to only use the .NET version rather than
your inherited class "just to be 100%" it wasn't you.

It wasn't.

I did get some improvement whenever I disabled
the tree prior to loading it or making a lot of node
changes and then enabling it again when done. I did
this before the beginupdate and after the endupdate stuff.

I worked with sets of 25 as well as lower volumes
around 18 - 20. Dropping down to around 10k nodes
seemed to provide acceptable performance for a typical
UI.

Still underperforms. You may want to get a hold
of Lutz's Reflector and compare the two System.Windows.Forms
assemblies and the TreeView class. You might be able to
identify the poor performing code and override it. Perhaps...



--
Robbe Morris - 2004-2006 Microsoft MVP C#
I've mapped the database to .NET class properties and methods to
implement an multi-layered object oriented environment for your
data access layer. Thus, you should rarely ever have to type the words
SqlCommand, SqlDataAdapter, or SqlConnection again.
http://www.eggheadcafe.com/articles/adonet_source_code_generator.asp




kiwidude said:
Hi Robbe,

It is indeed my own implementation, but I'm not doing anything funky.
All those performance figures above were from a completely clean test
harness that just had that one override, no other changes from the
TreeView defaults.

I've written a blog entry about the problem:
http://www.kiwidude.com/blog/2006/09/net-20-treeview-performance-problem.html

and put my sample code available for download from here:
http://www.kiwidude.com/dotnet/TreeViewTest.zip

Regards,
Grant.
Hmm. I downloaded my .net 1.1 sample and converted it to 2.0.

http://www.eggheadcafe.com/articles/treeview_databinding.asp

It only runs 1 to 2 thousand nodes. Quite a bit smaller than
your sample. However, the speed is pretty darn quick
running your sample code.

I see that you are overriding the standard event. Is
this your own overriden tree control implementation?

--
Robbe Morris - 2004-2006 Microsoft MVP C#
Earn money publishing .NET articles at
http://www.eggheadcafe.com




Here's the results of a very simple test harness I knocked up (chuck me
an e-mail if you want the full code). It loads a tree with 1 x 25 x 25
x 25 nodes (15,626 for those without a calculator). In the
OnBeforeExpand method I do this (the if statements just wrap checkboxes
in the test harness for the permutations):

protected override void OnBeforeExpand(TreeViewCancelEventArgs e)
{
if (UseBeginUpdate) {
BeginUpdate();
}
if (UpdateText) {
for (int i = 0; i < e.Node.Nodes.Count; i++) {
e.Node.Nodes.Text = "ChildUpdated" + i.ToString();
}
}
if (UseBeginUpdate) {
EndUpdate();
}
base.OnBeforeExpand();
}


The timing results are as follows for expanding a node (using
QueryPerformance API for timings):

Using .Net 1.1:
0.2 secs (with BeginUpdate and updating .Text)
0.2 secs (just updating .Text)
0.007 secs (with BeginUpdate)

Using .Net 2.0
1.2 secs (with BeginUpdate and updating .Text)
30.6 secs (just updating .Text)
1.2 secs (with BeginUpdate)

Setting Scrollable to false under .Net 2.0 (unusable but out of
curiosity)
0.001 secs (just updating .Text)


So in summary - you can see that .Net 2.0 for this size of tree is
approximately six times slower than .Net 1.1 when using Begin/EndUpdate
(and about 150 times slower just updating .Text without it which
obviously is a bad thing to be doing). You can also see that just the
call to BeginUpdate/EndUpdate alone still incurs a massive penalty even
if you don't actually update anything.

Waiting over a second while you expand a node that only has a couple of
children is completely rubbish.

Is anyone using a third party tree control that doesn't suck as badly
under .Net 2.0, or has found some way of making this tree usable when
your .Text contents need to be updated dynamically?

Grant.

kiwidude wrote:
Hi Robbe,

The application is a code coverage GUI tool (NCoverExplorer).
Depending
on the "view" a user chooses (% visited, # seq pts unvisited etc) and
the node type the text is calculated appropriately.

As for why so many nodes are being updated - that's exactly my point
in
that I do not actually update all that many nodes (just the immediate
children of the node being expanded which is usually in the 10-20 node
region). When I profile this under .Net 1.1 I see several orders of
magnitude less in the way of messages etc. However in .Net 2.0 it just
explodes in the amount of work going on underneath.

In fact when I comment out the code actually doing the updating of
nodes and do nothing but BeginUpdate/EndUpdate in the OnBeforeExpand
method I still see the same poor performance.

Regards,
Grant.

Robbe Morris [C# MVP] wrote:
I have not seen these types of performance issues. I know
the tree loads a few thousand nodes rather quickly for me.

I do a fair amount of work with hierarchies in research
applications. So, I'm somewhat familiar with apps that
do this sort of thing.

Out of curiousity, are the percentages on each TreeNode
a percentage within its siblings or are the percentages
of themselves in conjunction with either their parents
or their own sibling branches?

Typically you'd expect it to be the percentages of
siblings. In that case, there would only be 10 or 20
siblings to update.

I realize your existing code worked in .net 1.1 but
could you shed some light on why you need to adjust
so many nodes in the OnBeforeExpand event?

--
Robbe Morris - 2004-2006 Microsoft MVP C#
Earn money publishing .NET articles at
http://www.eggheadcafe.com




All,

Microsoft appear to have changed the internal implementation of
the
TreeView control and the changes appear to be not for the better
as
far
as my application is concerned. Has anyone else had performance
issues
with it?

The issue I have is that I need to change the .Text and
.ImageIndex
of
the nodes AFTER the tree has been populated. This is because the
tree
contains percentages etc based on the children of each node.

In .Net 1.0/1.1 I had this finely tuned to do a "load on demand".
I
override OnBeforeExpand() so as you expand a node it updates the
text/imageindex of the children which works really well. The
number
of
node children is usually around the 10-20 mark but often only a
few,
so
very little processing involved this way. Performance is great
under
1.1.

However running exactly the same code under .Net 2.0 gives what
ranges
from "appalling" to just plain "dire" usability.

The "improved" performance is gained by adding
BeginUpdate/EndUpdate
around the code which iterates through the child nodes updating
the
.Text. However the performance is still completely awful compared
to
.Net 1.1, and it comes down to the .EndUpdate method which is
chewing
up the time in it's calls to Control.SendMessage.

The alternative without using BeginUpdate/EndUpdate?

I found that when setting the Node.Text property the TreeView goes
into
meltdown with literally millions of windows messages and handle
requests initiated through TreeNode.set_Text ->
TreeNode.UpdateNode ->
TreeView.ForceScrollbarUpdate.

As an experiment I set "Scrollable=false" on the TreeView. Sure
enough,
I now get the equivalent of .Net 1.1 performance when expanding...
but
of course now the treeview is not scrollable. You ask "why not
just
turn that off and on afterwards then"? Because the TreeView
appears
to
completely reset and redraw itself when the Scrollable is set back
to
true which is completely unusable.

So - I'm knackered if I used EndUpdate, and I'm knackered if I
don't.

Does anyone have any suggestions? At the moment it is "don't use
.Net
2.0" which clearly ain't gonna fly long term.

Many thanks.

 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top