當我們改變ListBox的ItemsSource時,會發現這樣一個問題:數據源變化時,雖然控件中的內容會跟着變化,但滾動條卻不會重置。
舉個例子:
-
將ListBox綁定到一百個字符串: listbox.ItemsSource = Enumerable.Range(0, 100).Select(i => "## " + i);。
-
將ListBox的滾動條拖到最后,使之能看到最后的"## 99",看不到最開始的"## 0"。
-
將ListBox綁定到另外一百個字符串: listbox.ItemsSource = Enumerable.Range(0, 100).Select(i => ">> " + i);。 這時我們會發現:雖然數據內容會變更,但滾動條仍然在最后,能看到最后的">> 99",看不到最開始的">> 0"。
大多數情況下,這個並不是我們所期望的結果。如何解決這個問題,stackoverflow文章Reset scrollbar on ItemsSource change給了一個解決方案:找到ListBox的ScrollViewer,響應ListBox的SourceUpdated事件,滾動滾動條到頂端。
listbox.SourceUpdated += (_1, _2) => scrollView.ScrollToTop();
這種方法本身沒有什么問題,但是由於ScrollViewer是視覺樹的一部分,從ListBox上獲取並不容易(可能會修改模板)。我后來又從Wordpress文章ListBox – Automatically scroll CurrentItem into View上找到了一個方案:響應ListBox的Items.CurrentChanged事件,通過函數ScrollIntoView實現滾動到頂端。
listbox.Items.CurrentChanged += (_1, _2) => listbox.ScrollIntoView(listbox.Items[0]);
原文本來的目的是為了實現將ListBox自動滾動到CurrentItem,也可用來解決這個問題。原文更是實現了一個附加屬性,使得可以在XAML中直接使用,來非常方便。
<ListBox local:ListBoxExtenders.AutoScrollToCurrentItem="True"/>
由於眾所周知的原因,Wordpress這個並不存在的網站只能從火星上訪問,沒有火星專線的朋友可以找方校長借,或者直接參考我下面的代碼(稍微修改了點,貌似也沒有什么bug)。

2 /// This class contains a few useful extenders for the ListBox
3 /// </summary>
4 public class ListBoxExtenders : DependencyObject
5 {
6 #region Properties
7
8 public static readonly DependencyProperty AutoScrollToCurrentItemProperty = DependencyProperty.RegisterAttached( " AutoScrollToCurrentItem ",
9 typeof( bool), typeof(ListBoxExtenders), new UIPropertyMetadata( default( bool), OnAutoScrollToCurrentItemChanged));
10
11 /// <summary>
12 /// Returns the value of the AutoScrollToCurrentItemProperty
13 /// </summary>
14 /// <param name="obj"> The dependency-object whichs value should be returned </param>
15 /// <returns> The value of the given property </returns>
16 public static bool GetAutoScrollToCurrentItem(DependencyObject obj)
17 {
18 return ( bool)obj.GetValue(AutoScrollToCurrentItemProperty);
19 }
20
21 /// <summary>
22 /// Sets the value of the AutoScrollToCurrentItemProperty
23 /// </summary>
24 /// <param name="obj"> The dependency-object whichs value should be set </param>
25 /// <param name="value"> The value which should be assigned to the AutoScrollToCurrentItemProperty </param>
26 public static void SetAutoScrollToCurrentItem(DependencyObject obj, bool value)
27 {
28 obj.SetValue(AutoScrollToCurrentItemProperty, value);
29 }
30
31 #endregion
32
33 #region Events
34
35 /// <summary>
36 /// This method will be called when the AutoScrollToCurrentItem
37 /// property was changed
38 /// </summary>
39 /// <param name="sender"> The sender (the ListBox) </param>
40 /// <param name="e"> Some additional information </param>
41 public static void OnAutoScrollToCurrentItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
42 {
43 var listBox = sender as ListBox;
44 if ((listBox == null) || (listBox.Items == null))
45 return;
46
47 var enable = ( bool)e.NewValue;
48 var autoScrollToCurrentItemWorker = new EventHandler((_1, _2) => OnAutoScrollToCurrentItem(listBox, listBox.Items.CurrentPosition));
49
50 if (enable)
51 listBox.Items.CurrentChanged += autoScrollToCurrentItemWorker;
52 else
53 listBox.Items.CurrentChanged -= autoScrollToCurrentItemWorker;
54 }
55
56 /// <summary>
57 /// This method will be called when the ListBox should
58 /// be scrolled to the given index
59 /// </summary>
60 /// <param name="listBox"> The ListBox which should be scrolled </param>
61 /// <param name="index"> The index of the item to which it should be scrolled </param>
62 public static void OnAutoScrollToCurrentItem(ListBox listBox, int index)
63 {
64 if (listBox != null && listBox.Items != null && listBox.Items.Count > index && index >= 0)
65 listBox.ScrollIntoView(listBox.Items[index]);
66 }
67
68 #endregion
69