c# - style - ListBox使用MVVM滾動到視圖



wpf xaml (2)

我有什麼是一個非常簡單的問題,但我不知道如何使用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());
    }
}

}


Answer #1

你可以創建一個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}" />

Answer #2

您還可以添加一個行為:

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




mvvm