I'm working with the UltraTree and we have a table with self-referencing data that forms a hierarchy.
According to the following KB, one step in loading the data is that many "root-level nodes" need to be hidden (they are positioned/visible in the root of tree by default, even though they have a reference to a valid parent that lives elsewhere in the tree).
http://devcenter.infragistics.com/Support/KnowledgeBaseArticle.aspx?ArticleID=10084
"If you were to run your application at this point, you will notice that not only does the UltraTree display all of the nodes in the correct hierarchy, but it also displays all of the nodes on the root level as well. This is because the UltraTree does not intuitively know not to show the unnecessary data. To work around this problem, you need to handle the UltraTree’s InitializeDataNode event and hide the unnecessary root-level nodes."
The main problem I'm encountering when loading lots of data is the amount of CPU involved in setting UltraTreeNode.Visible to false. This takes a long time for a large tree. The profiler shows most of the time spent in these areas (internal UltraTree code). This happens during the InitializeDataNode event.
- Infragistics.Win.UltraWinTree.UltraTreeNode::set_Visible
- Infragistics.Win.UltraWinTree.VisibleNodesManager::Reinitialize
Given that I'm waiting so long just to "hide the unnecessary root-level nodes" , I suspect I've reached the limit of the number of nodes that can be supported by the UltraTree. We are loading 10's of thousands.
Can someone verify that this is the practical limit of what the UltraTree supports? Has anyone tried to load trees with more than 10,000 items? Is there any way to improve the performance while changing of the visibility of these nodes? Would it be possible to do this on background threads? (I'm assuming that would involve U/I errors or concurrency errors...).
Any help would be appreciated.
One way around this would be to use two different tables.
It's simple to create a DataSet with a single table that references itself, but this means that every row exists at the root level and so you have to somehow hide those nodes at the root level.
But you don't have to do it that way. You could create a root-level table (using a query) that only contains the root-level rows you want. Then create a relationship from that root-level table to the child table (which contains ALL the rows). And a second relationship from the child table back to itself.
I can play with that idea. Is there an example? It seems like the ultratree would have to auto-magically know to switch from one relationship to the other at the second level of the hierarchy.
Otherwise is there any way to pre-emptively manage the visibility of the nodes before initialization (ie. outside of the "InitializeDataNode " event)? ... or is it possible to manage that in an async way that is happening outside of the u/i thread?
Can you estimate for me how many nodes is reasonable for an ultratree? I assumed I had already hit the limit, but even if get over the current hump (with UltraTreeNode::set_Visible), I can't imagine that the ultratree will allow me to add nodes indefinitely.
Also, is it possible to upload attachments to this forum, in case I modify my example and it still isn't working. Are attachments disabled for me?
Here's the same I used to test this out. The first tree just uses the easy method. The second tree uses the more complicated, but more efficient method of using two tables.
ParentChildDataInUltraTree.zip
Regarding uploading files, these forums don't have an option to restrict file uploading for an individual user. So I suspect it might be some security on your end - maybe on your company's network. Either that or it was just a network hiccup. Or maybe you were choosing the wrong option? Make sure you zip up the project into one file. Then select Insert-->Insert Image/Video/File--> then choose File Upload from the combo at the top. Then you can pick a filename from your local machine.
It's also a good idea to delete the ".vs", "bin", and "obj" folders from the project before you zip it up. These are just unnecessary files that bloat the zip.
UltraTree binds to the DataSource you give it. So it doesn't have to switch anything. The DataSource would alraedy have the relationships defined. It's just a question of building your DataSet properly.
It would be hard to provide a meaningful example of this, since you are presumably getting your data from a SQl Server or something that we don't have access to. I could build a DataSet with the appropriate relationships in code, but that probably wouldn't help you very much since your real application is different and gets data from a server.
The number of reasonable nodes is arbitrary. There's no hard and fast rule. It depends on the speed of the hardward and the amount of memory and such. You might want to think about how many nodes is reasonable for your UI, anyway. No human user could possibly deal with 10,000 nodes in a tree in any meaningful way. Maybe you need some sort of search or query interface so the user can pull down the nodes they need to work with instead of the entire set.
Anyway, the approach is not that complicated though.
Right now, you have a DataSet with a single table and a relationship to itself. Presumably every row in your data has an Id that uniquely identifies the row and also a ParentId which indicates the parent row for that row. And some of your rows have Null or 0 as the ParentId, indicating that they are root-level rows with no parent.
Instead of doing that, you need to create a DataSet with 2 tables.
You get the root table using a query that only returns root-level rows. So you use a Select or something and put in a condition that the ParentId is null. Let's call this table "Root Table"
Then you get a second table which has the opposite condition and only get the rows where the ParentId is NOT null. Let's call this table "Child Table"
Once you have those two tables in your DataSet, you create two relationships.
private DataSet GetRecursiveDataSetWithTwoTables() { DataSet ds = new DataSet(); var parentTable = CreateRootTableWithData(); parentTable.TableName = "Root table"; ds.Tables.Add(parentTable); var childTable = CreateChildTableWithData(); childTable.TableName = "Child table"; ds.Tables.Add(childTable); // We have to specify false here for CreateConstraints, because some of the children have // parent that are in a different table. ds.Relations.Add("Parent To Child", parentTable.Columns["Id"], childTable.Columns["ParentID"], false); ds.Relations.Add("Child To Child", childTable.Columns["Id"], childTable.Columns["ParentID"], false); return ds; }