I have a legacy application that is written in C# and it displays a very complex treeview with 10 to 20 thousand elements.
In the past I encountered a similar problem (but in C++) that i solved with the OWNERDATA capability offered by the Win32 API.
Is there a similar mechanism in C#?
EDIT: The plan is to optimize the creation time as well as browsing time. The method available through Win32 API is excellent in both of these cases as it reduce initialization time to nothing and the number of requests for elements are limited to only the ones visible at any one time.
Joshl: We are actually doing exactly what you suggest already, but we still need more efficiency.
One technique for improving performance is to load TreeNodes as the user expands the treeview. Normally a user will not require 20,000 nodes to be open on their screen at once. Only load the level that the user needs to see, along with whatever child information you need to properly display affordances to the user (expand icon if children exist, counts, icons, etc). As the user expands nodes, load children just in time.
Helpful hint from Keith: With the winforms TreeView you need to have at least one child node or it won't show the expand [+], but then you handle the TreeNodeExpanded event to remove that dummy node and populate the children.
In our main WinForm app, we have a treeview loaded all in one shot:
BeginUpdate()
Load 20.000 nodes
EndUpdate()
and so far the performance is still nice. It is actually one of the few components we are not replacing with third party ones.
The TreeView performance, in my experience, gets slow when you load nodes (in one shot, or on demand) without calling Begin/EndUpdate(), especially if your nodes are sorted, but if you call Begin/EndUpdate() correctly, you shouldn't really get performance issues related to the component itself.
I don't believe the .NET TreeView supports what you want, although this type of model is supported by .NET's DataGridView (see DataGridView's VirtualMode property). The TreeView will let you draw your own nodes but it won't let you populate them from some virtual store.
If possible, you might want to consider the use of a DataGridView for your application. If not, managing the nodes manually (like joshl mentions above) might work if you can get around some issues with refreshing the screen properly when nodes are expanded. Outside of that, you might want to check out some of the third party vendors, like this one (Divelements SandGrid), that might (emphasis on might) support your desired mode of operation.
NOTE: The SandGrid is not supported by Divelements as of the end of July 2013.
NOTE: This answer is invalidated by an edit by the questioner saying he already does this kind of thing, but I decided to still post it for future reference by others searching on this topic
When I've done similar things in the past, I've tended to opt for the naive lazy-loading style.
Use the TreeNode.Tag property to hold a reference that you can use to look-up the children
Use the TreeView.BeforeExpand event to populate the child nodes
Optionally use the TreeView.AfterCollapse event to remove them.
To get the [+]/[-] boxes to appear, the best way I've found is to create a singleton dummy TreeNode that gets added as a child to all unpopulated Nodes, and you check for its existence before populating with BeforeExpand.
There is one way to make the TreeView perform much better and that is to create all sub-nodes and hook them together and then add the nodes to the TreeView. If it's the graphical performance we are talking about.
TreeView tree = new TreeView();
TreeNode root = new TreeNode("Root");
PopulateRootNode(root); // Get all your data
tree.Nodes.Add(root);
Otherwise, load them node by node using OnTreeNodeExpanded.
For large data in Windows C# programming, whether it be in WPF or WinForms, I have traditionally added nodes dynamically. I load the initial tree root + children + grandchildren deep. When any node is expanded, I load the tree nodes that would represent the grandchildren of the expanding node, if any.
This pattern also works well with data retrieval. If you are truly loading data from a source of thousands or millions of records you probably don't want to load those all up front. No user wants to wait for that to be loaded, and there is not reason to load data that may never be viewed.
I have typically loaded the grandchildren or the great-grandchildren node data as needed on a background thread, then marshal those data back to the UI thread and create and add the nodes. This leaves the UI responsive. You can visually decorate tree nodes to indicate they are still loading for the case where a user gets ahead of your IO to the data store.
This works for me (CSharp):
Visible = false;
...
Visible = true;
In my case(2000 nodes), it takes only 1~2 seconds to load the tree, which is much more quicker than any other ways.
It might work well in C++.
Related
I was looking for a way to search for an item in a treeview in C#. I used the following question/answer TreeView search and it works great. The only issue I'm having now is that I can only look through nodes that have been expanded at least once. So I added a ExpandAll() and Collapse() to the Load event of my form so that I can find any item anywhere in the tree.
But now, I'm dealing with very big hierarchies and it takes 45+ seconds to load my dialog box because of that expandAll() call. Is there a way to do that search without calling expandAll() first?
Thanks a lot.
I have integrated a feature in my tool that allows me to expand all recursively. I realise this would generally be a slow process anyway but currently. I have maybe 100 nested controls which are all expanded at once, so calling Expand All means that each control/datatemplate etc is processed at once.
This causes the tool to lock up for a good 10 seconds before responding. Of course, once the view hierarchy has been constructed, its then fast (I can collapse and expand instantly from then on). But it seems a little odd that there isn't a faster way to generate large forms, I wouldn't consider 100-500~ controls very many.
I have looked into virtualisation, but it doesn't appear to be useful to me in this case because all controls are expanded at once. Its fine if I expand them on a singular basis.
Edit:
Updates we're needed to the question to describe what the layout of my window is:
I have a number of TextBoxes, ComboBoxes, Sliders which are all nested inside of a number of Expanders. Each expander can contain N number of expanders, this is recursive to an extent. Depending on how the data is laid out. Some of these data types (which are represented by datatemplates) may contain StackPanels if needed and numerous grids for layouts.
To expand all it simply means to iterate each expander (which IsExpanded is bound to an IsExpanded property). I set this property on each data type (and its children) to true. And let the binding do the work of expanding everything. This causes every single control to be added at once.
Thanks
So after reading the edited question, I have a guess what's going on:
The UI is busy on rendering controls. And I assume there's not much data kept behind in viewmodels bound, so the data is not the point in your issue.
When the Expander control gets IsExpanded = true the first time, Xaml is parsed (object / resource parsing, not compiling) and visual tree children are created.
100-500 visual tree elements is a high number for rendering, because each of them (if it's belonging to the System.Windows.Controls namespace) consists of many sub-controls (Border, Grids, TextBox,..).
What you can achieve is NOT shortening the expanding time, but to unblock the UI:
advise the dispatcher to handle IsExpanded asynchronously and see how it behaves. This requires that you don't use the expanders default action:
In the arrow button click handler, skip default handling and insert this (IsExpanded is your bound viewmodel property here):
Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(
() => { this.IsExpanded = true; } ));
I know this is a stupid question, I need to let you guys know that I am fully aware that it is useless in 99% of situations to make a listbox with this many elements in c#:
That being said I need this to be done...is there any way to populate a listbox with 40000 elements without it completely destroying performance/freezing up, thanks!
note: I have tried it, this is per the exact requirements of a professor...when adding 40000 elements through a DataSource and DataBind the application freezes up
You tell me.
for(i=0;i<40000;i++)
{
listBox1.Items.Add("click me");
}
Even if is possible (I never tried it), the usability for this form will be 0.
In that cases a more usable implementation is via lookup text-boxes and lists, where the user can enter a text to search record that matches this text and displays them in a any kind of list.
It is of course possible to do it, but not very practicable.
When using a desktop technology like WinForms or WPF, for a large number of items like this you are better off using something like an auto complete textbox, and have it set to filter/search after the user has typed two or three characters. In this case you can also use a control that offers scrolling virtualisation - this means that there is only a limited number of UI elements created in the scrolling portion of the dropdown, and those elements get reused when a scroll occurs. If you don't use virtualisation then a new element gets created for every list item that gets scrolled in to view. (Note that Silverlight controls have this functionality - just in case it's an option).
For ASP.NET though I would suggest that you do not want to do anything that would cause a large transfer of data (large items, or small items but lots of them) as it won't be performant. Instead you should look to do what Google does - retrieve search results in a paged fashion.
I have a TreeView control on my form, and I'm recursively going through the elements of another window starting with the window itself. I'm using this to find the elements:
getRecursiveElements(AutomationElement parent)
{
children = parent.FindAll(TreeScope.Children, Condition.TrueCondition);
foreach (AutomationElement child in children)
{
addToTreeView(child);
getRecursiveElements(child);
}
}
Generally speaking, the code works quite well in most cases. The tree is populated and I have a bit of other supporting code allowing me to double click, for example, an element in the tree-view and it will highlight that element on the target form.
The issue I'm having is that, while it generates an awesome tree, there are still some elements missing for certain target programs.
What possible reason could there be for this, and is there any way to get around it?
If I call EnumChildWindows() from user32.dll will that have the same problem?
Not all programs use separate windowed controls for all their logical children. Mostly this depends on the GUI framework used.
As an extreme example, Qt uses a single window for each top-level window. It then paints all the widgets on the form from the form's WM_PAINT message handler.
Programs that take this approach are typically impossible to automate through generic methods.
It sounds like you have encountered an application that uses some windowed controls but also uses custom controls with a single window for what appears to be multiple widgets. Again this is quite common.
Could you give a better example of what fails? Thinking about the problem, it may be that the 'element' in the other form is being drawn manually, and so doesn't have distinct registered handles for everything.
Here is my scenario and I would like to ask your opinion on which control is best to use. I am using C#, ASP.net 2.0.
I am required to create a structure that resembles a tree. The user will start with something and then will add nodes to it. Every node is a structure by itself, so a user should be presented with an option to create a new node type or use an existing type of a node.
The obvious choice here seems TreeView. However, I have few concerns:
1) I was asked to minimize the number of trips back to the web server, and I am not sure how this would work with TreeView.
2) If I do end up using TreeView, creating a new node type (currently it is a .ascx (user control)) could be tricky to deal with, as I would need to open a new window and return node id and name to the parent page (which contains TreeView ) upon node type creation and seemlessly update it.
Any recommendations?
You may want to look into JQuery's Treeview
http://docs.jquery.com/Plugins/Treeview/treeview#options
this way, you can update the treeview on the client side, and when the user gets everything working as needed, then submit the tree state.
JQuery for .Net
http://jquerydotnet.codeplex.com/