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:
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);
}