2012-01-11 22 views
29

我有一個列表框XAML視圖中的SelectedItem:滾動列表框WPF在代碼中設置一個視圖模型

<control:ListBoxScroll ItemSource="{Binding Path=FooCollection}" 
         SelectedItem="{Binding SelectedFoo, Mode=TwoWay}" 
         ScrollSelectedItem="{Binding SelectedFoo}"> 
    <!-- data templates, etc. --> 
</control:ListBoxScroll> 

選定的項目綁定到我的視圖屬性。當用戶在列表框中選擇一個項目時,視圖模型中的SelectedFoo屬性被更新。當我在視圖模型中設置SelectedFoo屬性時,在列表框中選擇正確的項目。

問題是,如果代碼中設置的SelectedFoo當前不可見,則需要在列表框上另外調用ScrollIntoView。由於我的ListBox在一個視圖內,而我的邏輯在我的視圖模型中......我找不到一個方便的方法來完成它。所以我延長ListBoxScroll:

class ListBoxScroll : ListBox 
{ 
    public static readonly DependencyProperty ScrollSelectedItemProperty = DependencyProperty.Register(
     "ScrollSelectedItem", 
     typeof(object), 
     typeof(ListBoxScroll), 
     new FrameworkPropertyMetadata(
      null, 
      FrameworkPropertyMetadataOptions.AffectsRender, 
      new PropertyChangedCallback(onScrollSelectedChanged))); 
    public object ScrollSelectedItem 
    { 
     get { return (object)GetValue(ScrollSelectedItemProperty); } 
     set { SetValue(ScrollSelectedItemProperty, value); } 
    } 

    private static void onScrollSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var listbox = d as ListBoxScroll; 
     listbox.ScrollIntoView(e.NewValue); 
    } 
} 

它基本上暴露我綁定到我的視圖模型的SelectedFoo物業新依賴屬性ScrollSelectedItem。然後我鉤入屬性改變了依賴屬性的回調,並將新選擇的項目滾動到視圖中。

是否有其他人知道更簡單的方法來調用視圖模型支持的XAML視圖上的用戶控件上的函數?這是一個有點跑的身邊:

  1. 創建一個依賴屬性
  2. 回調添加到屬性更改回調靜態回調內
  3. 處理函數調用

這將是很好將邏輯權放在ScrollSelectedItem { set {方法中,但依賴框架似乎潛入並且設法工作而不實際調用它。

+0

設置'SelectedIndex'會容易得多。 – 2012-01-11 22:24:53

+1

這聽起來像是一個View「關注點」,而不是ViewModel。我不得不做類似的事情,但我把代碼留在了視圖中。請參閱http://matthamilton.net/focus-a-virtualized-listboxitem – 2012-01-11 22:31:44

+0

@MattHamilton - 此代碼在技術上位於View中(在控件中)。你將在一個視圖(任何地方)中編寫什麼代碼來完成調用ScrollIntoView?請記住,我不能覆蓋SelectedItem上的集合,因爲它不是虛擬的。 – 2012-01-11 22:49:51

回答

30

回顧了答案後,出現了一個常見的主題:外部類監聽ListBox的SelectionChanged事件。這讓我意識到,依賴屬性的方法是矯枉過正,我可能只是有子類聽自己:

class ListBoxScroll : ListBox 
{ 
    public ListBoxScroll() : base() 
    { 
     SelectionChanged += new SelectionChangedEventHandler(ListBoxScroll_SelectionChanged); 
    } 

    void ListBoxScroll_SelectionChanged(object sender, SelectionChangedEventArgs e) 
    { 
     ScrollIntoView(SelectedItem); 
    } 
} 

我覺得這是最簡單的解決方案,我想要做什麼。

榮譽提及adcool2007提出行爲。這裏有幾篇文章對於那些有興趣:

http://blogs.msdn.com/b/johngossman/archive/2008/05/07/the-attached-behavior-pattern.aspx
http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx

我認爲這將被添加到幾個不同的用戶控件的通用行爲(例如點擊行爲,拖動行爲,動畫行爲等)那麼附加的行爲就很有意義。我不想在這種情況下使用它們的原因是,行爲的實現(調用ScrollIntoView)不是可以發生在ListBox以外的任何其他控件的通用操作。

+0

簡單而有效,可能會因爲不深入技術而得到較少的關注,但得到了我的投票:) – 2013-04-13 07:16:18

+0

+1簡單而不依賴混合sdk,謝謝! – kbo4sho88 2013-12-17 15:55:25

+2

不要忘記取消訂閱活動:P – adminSoftDK 2016-08-06 10:06:08

12

因爲這完全是一個View問題,所以沒有理由在此視圖的代碼背後沒有事件處理函數。聽取ListBox.SelectionChanged並使用它將新選擇的項目滾動到視圖中。

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) 
{ 
    ((ListBox)sender).ScrollIntoView(e.AddedItems[0]); 
} 

你也不需要派生的ListBox來做到這一點。只需使用標準控件,當值更改(如原始問題中所述)時,將執行上述處理程序並將項目滾動到視圖中。

<ListBox 
     ItemsSource="{Binding Path=FooCollection}" 
     SelectedItem="{Binding Path=SelectedFoo}" 
     SelectionChanged="ListBox_SelectionChanged" 
     /> 

另一種方法是編寫一個附加屬性,用於偵聽ICollectionView.CurrentChanged,然後調用ListBox.ScrollIntoView新的當前項目。如果您需要多個列表框的功能,這是一種更「可重用」的方法。你可以在這裏找到一個很好的例子,讓你開始:http://michlg.wordpress.com/2010/01/16/listbox-automatically-scroll-currentitem-into-view/

+0

我不介意聽一個事件,但「滾動到選定的項目」的活動真的覺得它是控制的一部分,而不是周圍的視圖。然而,我可以在我的ListBoxScroll子類中編寫事件監聽器 - 這會讓我遠離依賴屬性回調和清潔事件代理。 – 2012-01-12 00:16:34

+0

我喜歡這種方法,但是如果你有很多ListBox的話,這會有點麻煩,我可以利用這個功能並派生我自己的ListBox。我同意這只是一個視圖問題,代碼可以在視圖中... – 2013-04-13 07:14:58

+1

@AdriaanDavel只是將所有ListBox SelectionChanged綁定到同一個處理程序,或者您已經這樣做了,並且只是在談論所有的事件處理程序訂閱語句都很麻煩? – Zack 2015-09-08 19:39:14

43

你試過用行爲...這是一個ScrollInViewBehavior。我已經使用它的ListView和DataGrid .....我認爲它應該適用於ListBox ......

你必須添加引用System.Windows.Interactivity使用Behavior<T> class

行爲

public class ScrollIntoViewForListBox : Behavior<ListBox> 
{ 
    /// <summary> 
    /// When Beahvior is attached 
    /// </summary> 
    protected override void OnAttached() 
    { 
     base.OnAttached(); 
     this.AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged; 
    } 

    /// <summary> 
    /// On Selection Changed 
    /// </summary> 
    /// <param name="sender"></param> 
    /// <param name="e"></param> 
    void AssociatedObject_SelectionChanged(object sender, 
              SelectionChangedEventArgs e) 
    { 
     if (sender is ListBox) 
     { 
      ListBox listBox = (sender as ListBox); 
      if (listBox .SelectedItem != null) 
      { 
       listBox.Dispatcher.BeginInvoke(
        (Action) (() => 
            { 
             listBox.UpdateLayout(); 
             if (listBox.SelectedItem != 
              null) 
              listBox.ScrollIntoView(
               listBox.SelectedItem); 
            })); 
      } 
     } 
    } 
    /// <summary> 
    /// When behavior is detached 
    /// </summary> 
    protected override void OnDetaching() 
    { 
     base.OnDetaching(); 
     this.AssociatedObject.SelectionChanged -= 
      AssociatedObject_SelectionChanged; 

    } 
} 

使用

添加別名XAMLxmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

然後在Control

 <ListBox ItemsSource="{Binding Path=MyList}" 
        SelectedItem="{Binding Path=MyItem, 
             Mode=TwoWay}" 
        SelectionMode="Single"> 
      <i:Interaction.Behaviors> 
       <Behaviors:ScrollIntoViewForListBox /> 
      </i:Interaction.Behaviors> 
     </ListBox> 

現在如果在ViewModel中設置了「MyItem」屬性,則在更改被改變時將滾動列表。

+0

這實際上很酷。它與crazyarabian描述的附屬財產方法相似,但感覺更清潔。藉助他的附屬財產解決方案和行爲解決方案,我將不得不編寫一個新課程。它似乎更有意義的擴展ListBox控件並在子類中進行事件處理。我今天會嘗試一下,看看它是否像你的行爲解決方案一樣乾淨。 – 2012-01-12 18:02:08

+0

@JamesFassett Behavoir通常用來給你控制它目前沒有的功能.... IE ....避免擴展控件....但我相信擴展是一樣好.. – Ankesh 2012-01-13 06:29:38

+0

只是使用Express版本。這個解決方案是否需要Blend? – paul 2012-07-04 09:47:38

10

我知道這是一個古老的問題,但我最近對同樣的問題的搜索已經帶給我了。我想用行爲方式,但不希望在混合SDK的依賴只是給我Behavior<T>所以這裏是我的解決方案,而它:

public static class ListBoxBehavior 
{ 
    public static bool GetScrollSelectedIntoView(ListBox listBox) 
    { 
     return (bool)listBox.GetValue(ScrollSelectedIntoViewProperty); 
    } 

    public static void SetScrollSelectedIntoView(ListBox listBox, bool value) 
    { 
     listBox.SetValue(ScrollSelectedIntoViewProperty, value); 
    } 

    public static readonly DependencyProperty ScrollSelectedIntoViewProperty = 
     DependencyProperty.RegisterAttached("ScrollSelectedIntoView", typeof (bool), typeof (ListBoxBehavior), 
              new UIPropertyMetadata(false, OnScrollSelectedIntoViewChanged)); 

    private static void OnScrollSelectedIntoViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var selector = d as Selector; 
     if (selector == null) return; 

     if (e.NewValue is bool == false) 
      return; 

     if ((bool) e.NewValue) 
     { 
      selector.AddHandler(Selector.SelectionChangedEvent, new RoutedEventHandler(ListBoxSelectionChangedHandler)); 
     } 
     else 
     { 
      selector.RemoveHandler(Selector.SelectionChangedEvent, new RoutedEventHandler(ListBoxSelectionChangedHandler)); 
     } 
    } 

    private static void ListBoxSelectionChangedHandler(object sender, RoutedEventArgs e) 
    { 
     if (!(sender is ListBox)) return; 

     var listBox = (sender as ListBox); 
     if (listBox.SelectedItem != null) 
     { 
      listBox.Dispatcher.BeginInvoke(
       (Action)(() => 
        { 
         listBox.UpdateLayout(); 
         if (listBox.SelectedItem !=null) 
          listBox.ScrollIntoView(listBox.SelectedItem); 
        })); 
     } 
    } 
} 

,然後使用只是

<ListBox ItemsSource="{Binding Path=MyList}" 
     SelectedItem="{Binding Path=MyItem, Mode=TwoWay}" 
     SelectionMode="Single" 
     behaviors:ListBoxBehavior.ScrollSelectedIntoView="True"> 
+0

工作得很好,我更喜歡不要拖延依賴關係。 – angularsen 2015-08-10 11:05:45

+0

不應該刪除'if(e.NewValue is bool == false)'代碼嗎?如果'e.NewValue'變爲false,那麼處理程序應該被刪除嗎? – Ziriax 2017-03-14 15:37:35

+0

@Ziriax這是一個測試,e.NewValue的類型是布爾值,而不是它是假的。 – Dutts 2017-03-14 17:19:58

7

試試這個:

private void lstBox_SelectionChanged(object sender, SelectionChangedEventArgs e) 
{ 
    lstBox.ScrollIntoView(lstBox.SelectedItem); 
} 
+0

這是一個解決方案,但我認爲這個問題需要與MVVM模式兼容的解決方案。 – user672951 2014-09-06 22:39:59

+2

@ user672951與此問題中的其他任何解決方案一樣,這適合MVVM模式。 – Zack 2015-09-08 19:40:43

2

搭售各種方法後,我發現下面是最簡單和最

lstbox.Items.MoveCurrentToLast(); 
lstbox.ScrollIntoView(lstbox.Items.CurrentItem); 
0

我拿了Ankesh的回答,並且使它不依賴於混合sdk。我的解決方案的缺點是,它將適用於您的應用程序中的所有列表框。但好處是沒有需要的習慣課程。

當您的應用程序初始化...

internal static void RegisterFrameworkExtensionEvents() 
    { 
     EventManager.RegisterClassHandler(typeof(ListBox), ListBox.SelectionChangedEvent, new RoutedEventHandler(ScrollToSelectedItem)); 
    } 

    //avoid "async void" unless used in event handlers (or logical equivalent) 
    private static async void ScrollToSelectedItem(object sender, RoutedEventArgs e) 
    { 
     if (sender is ListBox) 
     { 
      var lb = sender as ListBox; 
      if (lb.SelectedItem != null) 
      { 
       await lb.Dispatcher.BeginInvoke((Action)delegate 
       { 
        lb.UpdateLayout(); 
        if (lb.SelectedItem != null) 
         lb.ScrollIntoView(lb.SelectedItem); 
       }); 
      } 
     } 
    } 

這使得所有的列表框的滾動選擇(我喜歡的默認行爲)。

5

我使用這個(在我看來)明確和簡單的解決方案

listView.SelectionChanged += (s, e) => 
    listView.ScrollIntoView(listView.SelectedItem); 

其中listView在XAML ListView控制的名字,SelectedItem是從我的MVVM和代碼的影響是插在構造函數中 xaml.cs文件。

+0

好的謝謝:)先生 – Peter 2017-06-14 12:44:26

+0

@彼得你的歡迎。 – honzakuzel1989 2017-06-14 15:58:44

相關問題