diff --git a/CommonWPF/WPFExtensions.cs b/CommonWPF/WPFExtensions.cs
index 311a60f..4d33c9f 100644
--- a/CommonWPF/WPFExtensions.cs
+++ b/CommonWPF/WPFExtensions.cs
@@ -71,6 +71,9 @@ namespace CommonWPF {
///
/// 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.
///
/// The item index, or -1 if the list is empty.
public static int GetTopItemIndex(this ListView lv) {
@@ -92,7 +95,9 @@ namespace CommonWPF {
/// specific placement.
///
///
- /// 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.
///
public static void ScrollToTopItem(this ListView lv, object item) {
ScrollViewer sv = lv.GetVisualChild();
@@ -100,6 +105,15 @@ namespace CommonWPF {
lv.ScrollIntoView(item);
}
+ ///
+ /// Scrolls the ListView to the specified vertical index. The ScrollViewer should
+ /// be operating in "logical" units (lines) rather than "physical" units (pixels).
+ ///
+ public static void ScrollToIndex(this ListView lv, int index) {
+ ScrollViewer sv = lv.GetVisualChild();
+ sv.ScrollToVerticalOffset(index);
+ }
+
///
/// 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).
diff --git a/SourceGenWPF/DisplayListSelection.cs b/SourceGenWPF/DisplayListSelection.cs
index faf4778..25dd4bf 100644
--- a/SourceGenWPF/DisplayListSelection.cs
+++ b/SourceGenWPF/DisplayListSelection.cs
@@ -106,6 +106,8 @@ namespace SourceGenWPF {
///
/// Argument from SelectionChanged event.
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;
diff --git a/SourceGenWPF/MainController.cs b/SourceGenWPF/MainController.cs
index 2f4fbb7..cbb5a00 100644
--- a/SourceGenWPF/MainController.cs
+++ b/SourceGenWPF/MainController.cs
@@ -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);
}
diff --git a/SourceGenWPF/WpfGui/MainWindow.xaml.cs b/SourceGenWPF/WpfGui/MainWindow.xaml.cs
index a9ae75d..a1f3925 100644
--- a/SourceGenWPF/WpfGui/MainWindow.xaml.cs
+++ b/SourceGenWPF/WpfGui/MainWindow.xaml.cs
@@ -697,14 +697,21 @@ namespace SourceGenWPF.WpfGui {
///
/// Selection bitmap.
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 {
}
}
+ ///
+ /// Returns the index of the line that's currently at the top of the control.
+ ///
public int CodeListView_GetTopIndex() {
int index = codeListView.GetTopItemIndex();
Debug.Assert(index >= 0);
return index;
}
+ ///
+ /// Scrolls the code list so that the specified index is at the top of the control.
+ ///
+ /// Line index.
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);
}
///