I have what is a pretty simple problem, but I can't figure out how to crack it using MVVM.
I have a ListBox
that is bound to an ObservableCollection<string>
.
I run a process that will add a whole bunch of items to the collection and they are therefore shown in the ListBox
.
The problem is that as the items are added to the list box... the scroll bar just grows, but I can't seem to figure out how to make it ScrollIntoView
for each item added to the collection.
This sample code illustrates the problem perfectly.
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>
View Model
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());
}
}
}
You could create a DependencyProperty
or simply extend the ListBox
control and use your new control instead.
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);
}
}
In your XAML, add the class's namespace:
xmlns:custom="clr-namespace:ScrollingListBoxNamespace"
and swap out your standard ListBox
with your custom one:
<custom:ScrollingListBox Margin="10" Height="150"
ItemsSource="{Binding Path=MyValue}" />
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With