ListBox使用MVVM滚动到视图中

时间:2013-05-31 21:30:38

标签: c# wpf xaml mvvm

我有一个非常简单的问题,但我无法弄清楚如何使用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());
    }
}

}

2 个答案:

答案 0 :(得分:19)

您可以创建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换成自定义的标准{/ p>

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

答案 1 :(得分:12)

您还可以添加行为:

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]);
        }
    }
}