I have a view in my application that contains an ItemsControl
within a ScrollViewer
to display log messages. The collection of strings that are the messages is an ObservableCollection<string>
within my viewmodel.
When a new message is added, the ScrollViewer
needs to scroll to the bottom of the list, depending on whether a checkbox is checked or not. I'd like to handle this completely in the view. I found a decent solution (here). The checkbox is checked by default initially.
I'm seeing strange behavior, though:
- Before any messages are added, if I uncheck the checkbox, and add messages to the list, the list does not scroll (correct behavior).
- If I leave the checkbox checked, and add messages, the list scrolls (correct behavior).
- If I add a few message to the list, then uncheck the box, then add more to the list, the list still scrolls (incorrect behavior).
I've distilled it down to a very simple WPF application that demonstrates the problem, which is below.
What is causing this and how do I fix it?
The XAML:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="229" Width="551">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<CheckBox Grid.Row="0" Content="Autoscroll?" Name="AutoscrollCheckBox" IsChecked="True" />
<Button Grid.Row="1" Content="Add a message" Name="AddMessageButton" Click="AddMessageButton_Click" />
<ScrollViewer Name="MessagesScrollViewer" Grid.Row="2">
<ItemsControl Name="MessagesList" ItemsSource="{Binding Messages}" />
</ScrollViewer>
</Grid>
</Window>
And the code behind:
public partial class MainWindow : Window, INotifyPropertyChanged {
public MainWindow() {
InitializeComponent();
DataContext = this;
((INotifyCollectionChanged)MessagesList.Items).CollectionChanged += Messages_CollectionChanged;
}
private void Messages_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
Console.WriteLine((bool)AutoscrollCheckBox.IsChecked);
if (AutoscrollCheckBox.IsChecked.HasValue && (bool)AutoscrollCheckBox.IsChecked && (e.Action == NotifyCollectionChangedAction.Add))
MessagesScrollViewer.ScrollToBottom();
}
private ObservableCollection<string> m_Messages = new ObservableCollection<string>();
public ObservableCollection<string> Messages {
get { return m_Messages; }
set { m_Messages = value; NotifyPropertyChanged("Messages"); }
}
private int _msgNumber = 0;
private void AddMessageButton_Click(object sender, RoutedEventArgs e) {
Messages.Add(String.Format("Message #{0}", _msgNumber++));
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name) {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}