Set TreeListView.SelectetItem is too slow when subtree init dynamically using GetChildren override

Grids for WPF Forum

Posted 4 years ago by Vadim Tsedrik
Version: 19.1.0683
Platform: .NET 4.8
Environment: Windows 10 (64-bit)
Avatar

Hello, currently we are using in our product TreeListView for WPF and we have big problem when navigating through huge tree.
Our test case contains about 5000 nodes with maximum 6 nesting depth.

Tree looks like:

groupA
   subgroupA1
      subgroupA11
        variableA111
           index0
             index0.0
             index0.1
           index1
        variableA112
   subgroupA2
      variableA21
groupB
   subgroupB1
       variableB1
       variableB2
groupC
   subgroupC1
       variableC1
       variableC2
groupD
    subgroupD1
        variableD1
 
group B,C,D can contains 1000 nodes in each subgroup. Indexes created dynamically when navigating by tree, and depends on metadata of each variable.

Also class VariableTreeListBoxItemAdapter : TreeListBoxItemAdapter is using for better performance, with override

public override IEnumerable GetChildren(TreeListBox ownerControl, object item)
    var model = item as VarsTreeItem;
    return model != null ? model.Children : null
}
VarsTreeItem:
public ObservableCollection<VarsTreeItemBase> Children
{
  get
  {
   if (_children == null)
      if (VarMetadata != null)
          MakeChildren();
     return _children;
   }
}
The  actual problem that binding to SelectedItem in TreeListView from AutoCompleteBox calls GetChildren() from VariableTreeListBoxItemAdapter for all 5000 nodes that created before selected if try to select last node in tree from AutoCompleteBox collection. And this call takes more than 50 seconds in UI thread. If node selected from first group the delay is not so big, and not visible by user.

VarsTreeItemBase.Children property used lazy loading and created elements only for groups and subgroups. Subtree for any variable created in Children property. So when user try to expand elements subtree creates dynamically and fast.
But when user select last item or in the middle of tree from AutoCompleteBox creating of 5000 previous elements in tree is too slow and hangs UI thread using TreeListBoxItemAdapter.
Could you suggest how i can fix the issue?

[Modified 4 years ago]

Comments (6)

Posted 4 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hello,

One optimization you can do in your item adapter is override the CanHaveChildren method and return false for any nodes you quickly know for a fact will always be leaf nodes.  When that method returns false, it won't call GetChildren on for that node.

Pair that with setting the item adapter's ChildrenQueryModeDefault property to TreeItemChildrenQueryMode.OnExpansion instead of OnDisplay.  This tells the control to always show an expander and not call GetChildren until the expander is actually clicked.  When this property is its default of OnDisplay, the GetChildren method will be called for each node so that it knows whether to show an expander or not, which is causing the slowdown for you since all the visible nodes above the selection are being examined.  Changing it to OnExpand prevents that slowdown.

If you want more precise control over the TreeItemChildrenQueryMode result for each node, you can override the item adapter's GetChildrenQueryMode method.  It returns the ChildrenQueryModeDefault property value by default.


Actipro Software Support

Posted 4 years ago by Vadim Tsedrik
Avatar

Hello, thank you for quick feedback.

I tried both suggestions from you: in override CanHaveChildren and return false for definitely leaf node, also set TreeItemChildrenQueryMode.OnExpansion but it still doesn't help me if try to select variable in last group in case of previous groups contains nodes with a lot of Children.
You can see in image that 2nd and 3rd groups contains nodes that can have up to 20000 leafs. And when i try to search variable in 3rd or 4th group it takes me 40seconds to create all nodes for 1st,2nd,3rd groups that not needed for me now.

2nd group example:

https://cl.ly/f5755cb6222b

search result from AutoCompleteBox

https://cl.ly/c43503dda642

Maybe you can suggest how to change TreeListBoxItemAdapter to call GetChildren only inside search subtree? e.g. GroupD->SubgroupD->Variables and do not call for all other subgroups (GroupA, GroupB, GroupC)

Thank you

[Modified 4 years ago]

Posted 4 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hello,

Can you please put together a new simple sample project that shows this delay so that we can work with that and debug your situation to get you a solution?

When you have that ready, send it to our support address (or reply to the other ticket you originally created in the support system with it).  Make sure exclude the bin/obj folders from the ZIP you send so it doesn't get spam blocked.  Thanks!


Actipro Software Support

Posted 4 years ago by Vadim Tsedrik
Avatar

Hello,

I replied to the original ticket, please take a look the test project.

Note, that in real solution the result in 10 times slower in case of up to 1000000 leafs are created when only last node selected from AutoCompleteBox in last group.

Thanks.

Posted 4 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Vadim,

Thank you for the sample.  What happens when you set the TreeListView.SelectedItem property is that it has to traverse the tree to look for the matching item to select.  Since you are selecting the very last item programmatically here in an enormous tree, it takes quite a bit of time to locate that item.

A better way to select it would be by "path" via the TreeListView.SelectItemByFullPath method.  That allows the control to just walk the path and instantly select the item.  That being said, each item must have a unique string path (at least unique with its siblings) for this to work.

To add this to your sample, I made several changes.  In the item adapter, I added this, which finds the path segment for a particular node:

public override string GetPath(TreeListBox ownerControl, object item) {
    var node = item as Node;
    return (node != null ? node.Caption : base.GetPath(ownerControl, item));
}

I added this to your Node class, which finds the full path (excludes the root item) for the node:

public string FullPath {
    get {
        var fullPath = new StringBuilder();
        var node = this;
        fullPath.Append(node.Caption);
        node = node._parent;
        while (node._parent != null) {
            fullPath.Insert(0, $"{node.Caption}\\");
            node = node._parent;
		}
        return fullPath.ToString();
	}
}

Then in your button event handler where you do the selection, I took out your SelectedItem setter statement and replaced it with:

var fullPath = lastNode.FullPath;
var success = Tlv1.SelectItemByFullPath(fullPath);

The node is instantly selected with those changes.

[Modified 4 years ago]


Actipro Software Support

Posted 4 years ago by Vadim Tsedrik
Avatar

Thank you, provided solution works fine!

The latest build of this product (v24.1.3) was released 28 days ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.