列表框滚动到带有 MVVM 的视图中

本文关键字:MVVM 视图 滚动 列表 | 更新日期: 2023-09-27 18:16:41

我有一个非常简单的问题,但我无法弄清楚如何使用MVVM破解它。

我有一个绑定到ObservableCollection<string>ListBox.

我运行一个过程,将一大堆项目添加到集合中,因此它们显示在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());
    }
}

}

列表框滚动到带有 MVVM 的视图中

您可以创建一个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}" />

您还可以添加行为:

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