BringPathIntoView performance issue - Need alternative solution

3 posts, 0 answers
  1. Kathleen
    Kathleen avatar
    55 posts
    Member since:
    Jun 2009

    Posted 25 Apr 2013 Link to this post

    Hello

    I have a production related issue that requires me to find a good solution without re-writing our UI.

    I have found a potential solution but there is 1 showstopper.

    Some background:
    I have two treeviews.  The left side acts like a breadcrumb navigation tool, the right side displays the UI for data entry.

    The left side is bound to a heirarchical template with this outline:
    RootTreeViewItem
         SectionTreeViewItem
               SubsectionTreeViewItem
               SubsectionTreeViewItem
               SubsectionTreeViewItem
         SectionTreeViewItem
    etc...

    The right pane looks like this:
         SectionTreeViewItem
               SubsectionTreeViewItem
                     DataEntryView (from template selector, height varies)
                     DataEntryView (from template selector, height varies)
                     DataEntryView (from template selector, height varies)
               SubsectionTreeViewItem
                     DataEntryView (from template selector, height varies)
    etc....


    When I select an item on the left side, it needs to select the corresponding first DataEntryView on the right.

    I am selecting the item then using BringPathIntoView for this.

    If I turn off virtualization this works, however performance suffers on load.

    If I turn on virutalization this works, if the total number of overall items if or or less than about 50.

    Once the total number of items is about 300+ (need support for about 600) with virtualization on, the perfomance suffers greatly especially if I am selecting an item very low on the left pane.  The UI hangs and eventually my selected item comes into the viewport Maybe.  And never with the way it needs to.

    If I scroll down the right pane, essentially causing all the views to load.  Then perform the same task, I get instant response.

    I used DotTrace to understand why and it seems that since my data entry items are of varying height, in order to decide how far to scroll down, the treeview tries to measure the true height of all the future item, or even, renders them.

    So, as a work around, I am trying to set the datacontext of the right pane to be just the Section.
    This works wonderful.  I get instant response and I get a view that looks like this:

    -----top of viewport
         SectionTreeViewItem
             SubsectionTreeViewItem
                 DataEntryView (from template selector, height varies)
                 DataEntryView (from template selector, height varies)

    If I add all the "future" sections and subsections back to my bound observable collection using AddRange I also get good performance and the UI renders just the way I want.

    Where I am stuck is I want to be able to insert some items "The prior" items into the observable collection, that would be the items I could scroll up to.  However when I do this, the UI renders and the new items are event scrolled to.

    Ideally what I want is for the currently selected item to remain where it is at the top and the inserted items that are not in the viewport to just cause the scrollbar to grow.

    This is the behavior when you add items to the collection "below" but not "above".

    Is there anyway at all to tell the treeview to just accept the new items inserted before the current items without trying to render them?

    I've attached a demonstration video of what I mean.   The code is written in a way that inserting the items is delayed intentionally to allow you to see what is happening.  So you will see the UI render exactly as I need it to, then jump up as the items are inserted, with the last item I inserted becoming selected.  What I want to occur is that the 'prior' items inserted out of the viewport just change the ability to scrollup without rendering them.

    http://screencast.com/t/DEsm09VTAmI
    The code in questoin looks like this:
    if (sectionOrSubsectionViewModel is SubsectionViewModel)
                {
                     
                    var b = (sectionOrSubsectionViewModel as SubsectionViewModel);
                    var p = b.ParentViewModel;
                    sectionOrSubsectionViewModel = b.ParentViewModel;
                    p.CurrentSubsectionViewModels.Clear();
                    p.CurrentSubsectionViewModels.Add(b);
     
                        this.AssessmentViewModel.CurrentSectionViewModel.Clear();
                        this.AssessmentViewModel.CurrentSectionViewModel.Add(sectionOrSubsectionViewModel as SectionViewModel);
                        if (qvm == null)
                        {
                            SelectFirstQuestionInSection(p);
                        }
                        else
                            this.SelectedSessionItem = qvm;
     
                    if (!navigateBySection)
                        {
                            Delay.Action(() =>
                                             {                                            
                                                 var nextSubSections =
                                                     p.SubsectionViewModels.Where(
                                                         d =>
                                                         p.SubsectionViewModels.IndexOf(d) >
                                                         p.SubsectionViewModels.IndexOf(b)).ToList();
     
                                                 var priorSubsections =
                                                     p.SubsectionViewModels.Where(
                                                         d =>
                                                         p.SubsectionViewModels.IndexOf(d) <
                                                         p.SubsectionViewModels.IndexOf(b)).ToList();
     
                                                 p.CurrentSubsectionViewModels.AddRange(nextSubSections);
     
                                                 priorSubsections.Reverse();
                                                 foreach (var subsectionViewModel in priorSubsections)
                                                 {
                                                     p.CurrentSubsectionViewModels.Insert(0, subsectionViewModel);
                                                 }
     
                                                 var nextSections =
                                                     this.AssessmentViewModel.SectionsViewModel.Where(
                                                         e =>
                                                         this.AssessmentViewModel.SectionsViewModel.IndexOf(e) >
                                                         this.AssessmentViewModel.SectionsViewModel.IndexOf(p)).ToList();
     
                                                 this.AssessmentViewModel.CurrentSectionViewModel.AddRange(nextSections);
     
                                                 var priorSections =
                                                     this.AssessmentViewModel.SectionsViewModel.Where(
                                                         e =>
                                                         this.AssessmentViewModel.SectionsViewModel.IndexOf(e) <
                                                         this.AssessmentViewModel.SectionsViewModel.IndexOf(p)).ToList();
                                                 priorSections.Reverse();
                                                 foreach (var sectionViewModel in priorSections)
                                                 {
                                                     this.AssessmentViewModel.CurrentSectionViewModel.Insert(0,
                                                                                                             sectionViewModel);
                                                 }
                                                
                                              
                                             }, 1000);
     
                         
     
                        }



  2. Kathleen
    Kathleen avatar
    55 posts
    Member since:
    Jun 2009

    Posted 25 Apr 2013 Link to this post

    Added another screencast: http://screencast.com/t/NXAuqZR8

    That is what the production product peforms like when I tried to bring the selected item into view when its virutalized.
    Hit or miss if it works.

    What I need is for the section and subsection I select to be the top of the ViewPort and the right item selected.
  3. DevCraft banner
  4. Hristo
    Admin
    Hristo avatar
    352 posts

    Posted 30 Apr 2013 Link to this post

    Hello Kathleen,

    Thank you for the screen casts, they were very helpful in understanding the issue. Having a virtualized TreeView with variable item height is quite tricky and brings additional complexity to a solution, like it does in your case. If virtualization is turned on, the RadTreeView does not know where its items are. In addition, RadTreeView creates and renders items when requested. When the bring-into-view (BringPathIntoView) is called, the RadTreeView tries to guess where the requested item should be. It performs a scroll operation, then creates and renders the element. However, due to different item height, there is no guarantee that the TreeView will make a correct guess. There is e retry count of 10 and the last attempt is visualized. If you would like to have an absolutely correct predictability in that scenario, you could turn off the virtualization or use a virtualized RadTreeView but with items with same size (but from your description I gather that the second option is not applicable to your case).

    The possible solution would be to disable the virtualization of second/right TreeView and load just the child items (the children of selected item in the left TreeView).

    After the load of all subitems is finished you could call BringPathIntoView in order to scroll the requested item. This is a typical Master-Detail approach where selection from the left side displays the sub-content in the right side (no need to load the full hierarchy in the right, only the scroll will be smaller). This approach would result in a flat collection in the right and the TreeView would have a greater chance to find the item (if virtualized) and fewer items to load (when loading without virtualization).

    Hope this helps. Please let us know if you need further assistance.

    All the best,
    Hristo
    the Telerik team

    Explore the entire Telerik portfolio by downloading Telerik DevCraft Ultimate.

Back to Top