1

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

1 回答 1

1

这应该是来自ScrollViewer Source Code的解释:

如果你调用ScrollToBottom一个Double.PositiveInfinity值将被设置为VerticalOffset.

/// <summary>
/// Vertically scroll to the end of the content.
/// </summary>
public void ScrollToBottom()
{
    EnqueueCommand(Commands.SetVerticalOffset, Double.PositiveInfinity, null);
}

添加新项目后ScrollViewer,用最后一个更新视图,VerticalOffset这仍然PositiveInfinity意味着它再次滚动到最后。和

var verticalOffset = MessagesScrollViewer.VerticalOffset;
MessagesScrollViewer.ScrollToVerticalOffset(verticalOffset)‌​;

ScrollViewer设置真实VerticalOffset,一切都会好起来的。

于 2013-10-18T20:01:40.687 回答