MVVM style attached behavior
This pinned behavior automatically scrolls the list down when a new item is added.
<ListBox ItemsSource="{Binding LoggingStream}"> <i:Interaction.Behaviors> <behaviors:ScrollOnNewItemBehavior IsActiveScrollOnNewItem="{Binding IfFollowTail, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> </i:Interaction.Behaviors> </ListBox>
In your ViewModel you can bind to boolean IfFollowTail { get; set; } IfFollowTail { get; set; } IfFollowTail { get; set; } to control the auto scroll activity.
Behavior does everything right:
- If
IfFollowTail=false set in the ViewModel, the ListBox no longer scrolls to the bottom of the new item. - Once
IfFollowTail=true set to ViewModel, the ListBox instantly scrolls down and continues to do so. - It is fast. It scrolls only after a couple of hundred milliseconds of inactivity. The naive implementation will be very slow, as it will scroll with each added addition.
- It works with duplicate ListBox elements (many other implementations do not work with duplicates - they scroll to the first element and then stop).
- Ideal for a logging console that deals with continuous inbound items.
Behavior C # Code
public class ScrollOnNewItemBehavior : Behavior<ListBox> { public static readonly DependencyProperty IsActiveScrollOnNewItemProperty = DependencyProperty.Register( name: "IsActiveScrollOnNewItem", propertyType: typeof(bool), ownerType: typeof(ScrollOnNewItemBehavior), typeMetadata: new PropertyMetadata(defaultValue: true, propertyChangedCallback:PropertyChangedCallback)); private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) { // Intent: immediately scroll to the bottom if our dependency property changes. ScrollOnNewItemBehavior behavior = dependencyObject as ScrollOnNewItemBehavior; if (behavior == null) { return; } behavior.IsActiveScrollOnNewItemMirror = (bool)dependencyPropertyChangedEventArgs.NewValue; if (behavior.IsActiveScrollOnNewItemMirror == false) { return; } ListboxScrollToBottom(behavior.ListBox); } public bool IsActiveScrollOnNewItem { get { return (bool)this.GetValue(IsActiveScrollOnNewItemProperty); } set { this.SetValue(IsActiveScrollOnNewItemProperty, value); } } public bool IsActiveScrollOnNewItemMirror { get; set; } = true; protected override void OnAttached() { this.AssociatedObject.Loaded += this.OnLoaded; this.AssociatedObject.Unloaded += this.OnUnLoaded; } protected override void OnDetaching() { this.AssociatedObject.Loaded -= this.OnLoaded; this.AssociatedObject.Unloaded -= this.OnUnLoaded; } private IDisposable rxScrollIntoView; private void OnLoaded(object sender, RoutedEventArgs e) { var changed = this.AssociatedObject.ItemsSource as INotifyCollectionChanged; if (changed == null) { return; } // Intent: If we scroll into view on every single item added, it slows down to a crawl. this.rxScrollIntoView = changed .ToObservable() .ObserveOn(new EventLoopScheduler(ts => new Thread(ts) { IsBackground = true})) .Where(o => this.IsActiveScrollOnNewItemMirror == true) .Where(o => o.NewItems?.Count > 0) .Sample(TimeSpan.FromMilliseconds(180)) .Subscribe(o => { this.Dispatcher.BeginInvoke((Action)(() => { ListboxScrollToBottom(this.ListBox); })); }); } ListBox ListBox => this.AssociatedObject; private void OnUnLoaded(object sender, RoutedEventArgs e) { this.rxScrollIntoView?.Dispose(); } /// <summary> /// Scrolls to the bottom. Unlike other methods, this works even if there are duplicate items in the listbox. /// </summary> private static void ListboxScrollToBottom(ListBox listBox) { if (VisualTreeHelper.GetChildrenCount(listBox) > 0) { Border border = (Border)VisualTreeHelper.GetChild(listBox, 0); ScrollViewer scrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(border, 0); scrollViewer.ScrollToBottom(); } } }
Bridge from events to jet extensions
Finally, add this extension method so that we can use all the features of RX:
public static class ListBoxEventToObservableExtensions {
Add reactive extensions
You need to add Reactive Extensions to your project. I recommend NuGet .
Contango Mar 14 '17 at 17:45 2017-03-14 17:45
source share