I have an application which uses an UltraTree in Outlook mode which has worked well over the years however i am hitting some performance issues which I am trying to get to the bottom of.
It Outlook mode it appears that I have to add node using the parentNode.Add() method to get the column set created correctly for the new node which I do and then set a number fof attributes for the node (cell values etc). I have traced the code and added timers to find what seems to be taking the time and have narrowed it down to a single line which sets
node.Override.ShowExpansionIndicator = ShowExpansionIndicator.CheckOnExpand;
For about 16K nodes this one line increases the time to display the tree from 6 seconds to over 20 seconds yet to me it shouldn't actually cause anything to happen? I have the source code for the UltraTree but cannot see what is happening when this flag is set - does it trigger other events? I have begin / End Update around the entire operation but that doesn't help.
I was hooping I could set the flag for the parent node and have it filter down but that doesn't work either. Any suggestions most welcome.
I also find it odd that there does not appear to be a way to add a range of nodes in one go when working in Outlook mode - well you can but there doesn't appear to be a way to create all of the nodes, set their cell values and tags and then add to the parent node as the column set is not set for the new node until it is added to the parent (as far as I can tell).
May I ask why you are using ShowExpansionIndicator.CheckOnExpand? The purpose of this setting is to optimize the loading of the tree when using DataBinding. But it sounds like you are manually adding the nodes to your tree using node.Nodes.Add(). The point of CheckOnExpand is that it allows all nodes in the tree to show an expansion indication and then the tree doesn't actually check to see if there are child nodes until the user or something else expands the node. That way, the tree doesn't have to access the data data source to determine if that node has any child nodes, which can be an expensive process. As opposed to CheckOnDisplay, which would have to force all nodes to load their child nodes in order to determine whether the expansion indicator is needed whenever that node is displayed on-screen, even before the user expands it. So ShowExpansionIndicator.CheckOnExpand doesn't really make a lot of sense for an unbound tree, since there's no data source to access and the node already knows if it has child nodes instantly, since that information is right in memory. If CheckOnExand is somehow slowing things down, then I agree that sounds like a problem, and maybe a bug. But using that setting for an unbound tree doesn't make sense, anyway, so I'm not sure it's worth pursuing. Unless I am missing something. :) EDIT: Oh... there is one other reason to use ShowExpansionIndicator.CheckOnExpand. If you are not populating all of the tree nodes up-front and you are handling the BeforeExpand event to populate the child nodes as-needed. Are you doing that?
Hi Mike, exactly your last point,. The total quantity of nodes in the tree could be very large so I took the decision early on that it was not worth while populating a branch until such time as the user attempted to expand it. I am therefore handling the BeforeExpand and populating any children if there are any, updating the Expansion Indicator based on this. There are however items being added to the tree which can never have children and as such I am setting those to 'Never' as part of the load of that node. The SQL query to recover the data is pretty quick but I have found that it can take quite some time to populate / display the tree if you add a lot of nodes.
Is it possible to create and add a range of nodes in Outlook mode? I can't see a way of doing this but I guess there may be a function I have not yet found to associate a column set with a new node (or range of nodes) before adding the nodes to their parent.
So... if you are using ViewStyle.OutlookExpress, or really any style with columns, and you create a node outside of the tree, then that node has no way of knowing what it's columns will be until you add that node into a Nodes collection where it can resolve the ColumnSet. So I don't see any way to do what you are describing using AddRange.
That is to say, you can't create the node outside the tree and give it the cell data before adding it to the tree. You can, of course, create an array of empty Nodes and use AddRange to add all of those nodes to the Nodes collection using AddRange, and then go back and fill in the cell data into each node once they are in the collection. This might be a good candidate for a feature request. It probably wouldn't be that tough for us to add some way to set the cell values on a node with no ColumnSet and then hook up the data once that node gets added to the Nodes collection. But right now, I don't see any way to do this.
When you say "Change the ShowExpansionIndicator flag", do you mean you are changing it more than once? You should really only be setting that property once on the creation of the node or it's parent. It's not intended to be changed at run-time after the initial setting. Anyway, if you could whip up a sample project that demonstrates the performance hit, we could check it out.
Good suggestion but you are probably correct in that it will cause problems later down the road. I hadn't considered that the columnset just defaults to that of the parent node and doesn't actually 'inherit' it as such.
I still think the issue is more down to what happens when you change the status of the ShowExpansionIndicator flag. My logic would say that setting the flag should do nothing but it must be firing an event somewhere. I have the source code but for the life of me I can't see where the flag is being set as that would allow me to walk through the code and find out what it actually does.
Chris Drew said:it would seem sensible to be able to create a new node and then set the columnset for it, then set the data based on that columnset, and finally add the node to its parent as a range.
Well... you technically CAN do that. But I don't think it's a good idea. There may be ramifications that cause behaviors you don't want.
this.ultraTree1.ViewStyle = ViewStyle.OutlookExpress; var columnSet = new UltraTreeColumnSet(); var keyColumn = columnSet.Columns.Add("Key"); keyColumn.DataType = typeof(int); var nameColumn = columnSet.Columns.Add("Name"); nameColumn.DataType = typeof(string); var descriptionColumn = columnSet.Columns.Add("Description"); descriptionColumn.DataType = typeof(string); this.ultraTree1.ColumnSettings.RootColumnSet = columnSet; List<UltraTreeNode> rootNodes = new List<UltraTreeNode>(); for (int i = 0; i < 10; i++) { var rootNode = new UltraTreeNode(); rootNode.Override.ColumnSet = columnSet; rootNode.SetCellValue(keyColumn, i); rootNode.SetCellValue(nameColumn, string.Format("Name {0}", i.ToString())); rootNode.SetCellValue(descriptionColumn, string.Format("Description {0}", i.ToString())); rootNodes.Add(rootNode); } this.ultraTree1.Nodes.AddRange(rootNodes.ToArray());
So this code creates the node and then set the ColumnSet on the individual node so that it can set the cell values.
The problem with this is that every node now has a direct reference to the ColumnSet, rather than just picking it up from the collection. To be honest, I'm not entirely sure what the ramifications of that are. In my example, they are all pointing to the same ColumnSet instance, so that's not a big deal - it's not like I am creating more than one ColumnSet. And since they all use the same instance, Sorting and AutoSizing the columns still works.
Something tells me there's going to be a price to pay for this approach, but I can't think of exactly what it is right off the top of my head. But I suppose you could try it out.
I doubt it will have any effect on the performance issue you reported initially for this thread, though.
Thanks Mike - it would seem sensible to be able to create a new node and then set the columnset for it, then set the data based on that columnset, and finally add the node to its parent as a range. I would doubt if adding the nodes as a range and then filling in the details would be any different to adding them one at a time after setting their details assuming that the Begin / EndUpdate flags do what they are supposed to!