1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-06-12 08:29:29 +00:00

Improve restoration of top position

The ListView control provides a ScrollIntoView() method that ensures
the specified item is on screen, scrolling the ListView if needed.
Unfortunately this method is very slow (50-100 ms) and sometimes
fails entirely on larger lists.  (Yay WPF.)

Because the ListView is scrolling by fixed-height item, it's possible
to use the underlying ScrollViewer to move the list instantaneously
and reliably.  So now we do that.

Also, fixed a bug with select-all, where we weren't clearing the
previous selection before calling SelectAll(), leading to a mismatch
with the secondary data structure that we maintain because WPF
ListViews can't deal with large selections efficiently.  (Yay WPF.)

There's still some weird behavior, e.g. sometimes hitting F5 clears
the current selection and sometimes it doesn't.  I think it's related
to which item has focus and the fact you're hitting a key; using the
debug menu item doesn't cause the behavior.

Also, increased MAX_SEL_COUNT from 2000 to 5000.  That takes about
200ms to restore to a ListView on my 5-year-old system.
This commit is contained in:
Andy McFadden 2019-07-17 17:42:57 -07:00
parent a0dca6a5be
commit 1044b9d479
4 changed files with 47 additions and 8 deletions

View File

@ -71,6 +71,9 @@ namespace CommonWPF {
/// <remarks>
/// See https://stackoverflow.com/q/2926722/294248 for an alternative approach that
/// uses hit-testing, as well as a copy of this approach.
///
/// Looks like we get the same values from ScrollViewer.VerticalOffset. I don't know
/// if there's a reason to favor one over the other.
/// </remarks>
/// <returns>The item index, or -1 if the list is empty.</returns>
public static int GetTopItemIndex(this ListView lv) {
@ -92,7 +95,9 @@ namespace CommonWPF {
/// specific placement.
/// </summary>
/// <remarks>
/// Equivalent to setting myListView.TopItem in WinForms.
/// Equivalent to setting myListView.TopItem in WinForms. Unfortunately, the
/// ScrollIntoView call takes 60-100ms on a list with fewer than 1,000 items. And
/// sometimes it just silently fails. Prefer ScrollToIndex() to this.
/// </remarks>
public static void ScrollToTopItem(this ListView lv, object item) {
ScrollViewer sv = lv.GetVisualChild<ScrollViewer>();
@ -100,6 +105,15 @@ namespace CommonWPF {
lv.ScrollIntoView(item);
}
/// <summary>
/// Scrolls the ListView to the specified vertical index. The ScrollViewer should
/// be operating in "logical" units (lines) rather than "physical" units (pixels).
/// </summary>
public static void ScrollToIndex(this ListView lv, int index) {
ScrollViewer sv = lv.GetVisualChild<ScrollViewer>();
sv.ScrollToVerticalOffset(index);
}
/// <summary>
/// Returns the ListViewItem that was clicked on, or null if an LVI wasn't the target
/// of a click (e.g. off the bottom of the list).

View File

@ -106,6 +106,8 @@ namespace SourceGenWPF {
/// </summary>
/// <param name="e">Argument from SelectionChanged event.</param>
public void SelectionChanged(SelectionChangedEventArgs e) {
Debug.WriteLine("SelectionChanged event: Add=" + e.AddedItems.Count +
" Rem=" + e.RemovedItems.Count);
foreach (DisplayList.FormattedParts parts in e.AddedItems) {
Debug.Assert(parts.ListIndex >= 0 && parts.ListIndex < mSelection.Length);
this[parts.ListIndex] = true;

View File

@ -695,6 +695,7 @@ namespace SourceGenWPF {
mReanalysisTimer.StartTask("ProjectView.ApplyChanges()");
mReanalysisTimer.StartTask("Save selection");
mMainWin.CodeListView_DebugValidateSelectionCount();
int topItemIndex = mMainWin.CodeListView_GetTopIndex();
LineListGen.SavedSelection savedSel = LineListGen.SavedSelection.Generate(
CodeLineList, mMainWin.CodeDisplayList.SelectedIndices, topItemIndex);
@ -1493,7 +1494,6 @@ namespace SourceGenWPF {
if (SelectionAnalysis.mNumItemsSelected != 1) {
return false;
}
Debug.WriteLine("LINE TYPE " + SelectionAnalysis.mLineType);
return (SelectionAnalysis.mLineType == LineListGen.Line.Type.LongComment ||
SelectionAnalysis.mLineType == LineListGen.Line.Type.Note);
}

View File

@ -697,14 +697,21 @@ namespace SourceGenWPF.WpfGui {
/// </summary>
/// <param name="sel">Selection bitmap.</param>
public void CodeListView_SetSelection(DisplayListSelection sel) {
const int MAX_SEL_COUNT = 2000;
// Time required increases non-linearly. Quick test:
// 50K: 10 seconds, 20K: 1.6 sec, 10K: 0.6 sec, 5K: 0.2 sec
const int MAX_SEL_COUNT = 5000;
// The caller will clear the DisplayListSelection before calling here, so we
// need to clear the ListView selection to match, even if we're about to call
// SelectAll. If we don't, the SelectAll() call won't generate the necessary
// events, and our DisplayListSelection will get out of sync.
codeListView.SelectedItems.Clear();
if (sel.IsAllSelected()) {
Debug.WriteLine("SetSelection: re-selecting all items");
codeListView.SelectAll();
return;
}
codeListView.SelectedItems.Clear();
if (sel.Count > MAX_SEL_COUNT) {
// Too much for WPF ListView -- only restore the first item.
@ -713,6 +720,9 @@ namespace SourceGenWPF.WpfGui {
return;
}
Debug.WriteLine("SetSelection: selecting " + sel.Count + " of " +
CodeDisplayList.Count);
//DateTime startWhen = DateTime.Now;
DisplayList.FormattedParts[] tmpArray = new DisplayList.FormattedParts[sel.Count];
@ -771,20 +781,33 @@ namespace SourceGenWPF.WpfGui {
}
}
/// <summary>
/// Returns the index of the line that's currently at the top of the control.
/// </summary>
public int CodeListView_GetTopIndex() {
int index = codeListView.GetTopItemIndex();
Debug.Assert(index >= 0);
return index;
}
/// <summary>
/// Scrolls the code list so that the specified index is at the top of the control.
/// </summary>
/// <param name="index">Line index.</param>
public void CodeListView_SetTopIndex(int index) {
//Debug.WriteLine("CodeListView_SetTopIndex(" + index + "): " + CodeDisplayList[index]);
// ScrollIntoView does the least amount of scrolling required. This extension
// method scrolls to the bottom, then scrolls back up to the top item.
//
// NOTE: it looks like scroll-to-bottom (which is done directly on the
// ScrollViewer) happens immediately, whiel scroll-to-item (which is done via the
// ListView) kicks in later. So don't try to check the topmost item immediately.
codeListView.ScrollToTopItem(CodeDisplayList[index]);
// It looks like scroll-to-bottom (which is done directly on the ScrollViewer)
// happens immediately, while scroll-to-item (which is done via the ListView)
// kicks in later. So you can't immediately query the top item to see where
// we were moved to.
//codeListView.ScrollToTopItem(CodeDisplayList[index]);
// This works much better.
codeListView.ScrollToIndex(index);
}
/// <summary>