2013-05-31 23 views
9

我有什麼是一個非常簡單的問題,但我無法弄清楚如何使用MVVM來破解它。ListBox滾動到MVVM的視圖

我有一個ListBox綁定到ObservableCollection<string>

我運行了一個進程,它會將大量項目添加到集合中,因此它們顯示在ListBox中。

問題是,隨着項目被添加到列表框...滾動條只是增長,但我似乎無法弄清楚如何使它爲每個項目添加到集合的ScrollIntoView

此示例代碼完美地說明了這個問題。

XAML

<Window x:Class="Stack.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:vm="clr-namespace:Stack" 
    Title="MainWindow" 
    Height="350" 
    Width="525"> 
<Window.DataContext> 
    <vm:MainWindowViewModel /> 
</Window.DataContext> 
<StackPanel> 
    <ListBox Margin="10" Height="150" 
      ItemsSource="{Binding Path=MyValue}" /> 
    <Button Margin="10" 
      Height="25" 
      Content="Generate" 
      Command="{Binding Path=CommandName}" /> 
</StackPanel> 
</Window> 

視圖模型

namespace Stack 
{ 
using System; 
using System.Collections.ObjectModel; 
using System.ComponentModel; 
using System.Linq; 
using System.Windows.Input; 
using GalaSoft.MvvmLight.Command; 

/// <summary> 
/// TODO: Update summary. 
/// </summary> 
public class MainWindowViewModel : INotifyPropertyChanged 
{ 
    private readonly BackgroundWorker _worker; 

    private ICommand _commandName; 

    private ObservableCollection<string> _myValue = new ObservableCollection<string>(); 

    /// <summary> 
    /// Initializes a new instance of the <see cref="MainWindowViewModel" /> class. 
    /// </summary> 
    public MainWindowViewModel() 
    { 
     this._worker = new BackgroundWorker(); 
     this._worker.DoWork += new DoWorkEventHandler(DoWork); 
     this._worker.ProgressChanged += new ProgressChangedEventHandler(ProgressChanged); 
     this._worker.RunWorkerCompleted += delegate(object sender, RunWorkerCompletedEventArgs e) 
     { 
      CommandManager.InvalidateRequerySuggested(); 
     }; 
    } 

    /// <summary> 
    /// Occurs when a property value changes. 
    /// </summary> 
    public event PropertyChangedEventHandler PropertyChanged; 

    public ICommand CommandName 
    { 
     get 
     { 
      if (this._commandName == null) 
      { 
       this._commandName = new RelayCommand(() => this.CommandMethod()); 
      } 
      return this._commandName; 
     } 
    } 

    /// <summary> 
    /// Gets or sets my value. 
    /// </summary> 
    /// <value>My value.</value> 
    public ObservableCollection<string> MyValue 
    { 
     get 
     { 
      return this._myValue; 
     } 
     set 
     { 
      this._myValue = value; 
      this.NotifyPropertyChange("MyValue"); 
     } 
    } 

    /// <summary> 
    /// Notifies the property change. 
    /// </summary> 
    /// <param name="propName">Name of the prop.</param> 
    internal void NotifyPropertyChange(string propName) 
    { 
     if (this.PropertyChanged != null) 
     { 
      this.PropertyChanged(this, new PropertyChangedEventArgs(propName)); 
     } 
    } 

    /// <summary> 
    /// Commands the method. 
    /// </summary> 
    private void CommandMethod() 
    { 
     this.MyValue.Clear(); 
     this._worker.RunWorkerAsync(); 
     this._worker.WorkerReportsProgress = true; 
    } 

    /// <summary> 
    /// Does the work. 
    /// </summary> 
    /// <param name="sender">The sender.</param> 
    /// <param name="e">The <see cref="System.ComponentModel.DoWorkEventArgs" /> instance containing the event data.</param> 
    private void DoWork(object sender, DoWorkEventArgs e) 
    { 
     this.Populate(); 
    } 

    /// <summary> 
    /// Populates this instance. 
    /// </summary> 
    private void Populate() 
    { 
     for (int index = 0; index < 100; index++) 
     { 
      System.Threading.Thread.Sleep(10); 
      this._worker.ReportProgress(index); 
     } 
    } 

    /// <summary> 
    /// Progresses the changed. 
    /// </summary> 
    /// <param name="sender">The sender.</param> 
    /// <param name="e">The <see cref="System.ComponentModel.ProgressChangedEventArgs" /> instance containing the event data.</param> 
    private void ProgressChanged(object sender, ProgressChangedEventArgs e) 
    { 
     this.MyValue.Add(e.ProgressPercentage.ToString()); 
    } 
} 

}

回答

17

您可以創建一個DependencyProperty或者乾脆延長ListBox控制和使用新的控制來代替。

public class ScrollingListBox : ListBox 
{ 
    protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) 
    { 
     int newItemCount = e.NewItems.Count; 

     if(newItemCount > 0) 
      this.ScrollIntoView(e.NewItems[newItemCount - 1]); 

     base.OnItemsChanged(e); 
    } 
} 

在您的XAML,添加類的命名空間:

xmlns:custom="clr-namespace:ScrollingListBoxNamespace" 

和換出你的標準ListBox與自定義一個:

<custom:ScrollingListBox Margin="10" Height="150" 
         ItemsSource="{Binding Path=MyValue}" /> 
+2

非常感謝...! –

+0

@BOBINJOSEPH不客氣:) – keyboardP

+0

@keyboard我知道你已經回答了這個問題已經3年了,但是你有什麼機會擴展你的答案並解釋依賴屬性選項如何工作?謝謝。 – Thierry

9

您還可以添加行爲:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
. 
. 
. 
<ListBox Margin="10" Height="150" ItemsSource="{Binding Path=MyValue}" > 
<i:Interaction.Behaviors> 
    <bhv:ScrollIntoViewBehavior/> 
</i:Interaction.Behaviors> 
</ListBox> 

並執行該行爲:

using System.Windows.Interactivity; 

public class ScrollIntoViewBehavior : Behavior<ListBox> 
{ 
    protected override void OnAttached() 
    { 
     ListBox listBox = AssociatedObject; 
     ((INotifyCollectionChanged)listBox.Items).CollectionChanged += OnListBox_CollectionChanged; 
    } 

    protected override void OnDetaching() 
    { 
     ListBox listBox = AssociatedObject; 
     ((INotifyCollectionChanged)listBox.Items).CollectionChanged -= OnListBox_CollectionChanged; 
    } 

    private void OnListBox_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
    { 
     ListBox listBox = AssociatedObject; 
     if (e.Action == NotifyCollectionChangedAction.Add) 
     { 
      // scroll the new item into view 
      listBox.ScrollIntoView(e.NewItems[0]); 
     } 
    } 
}