Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to scroll to the bottom of a ScrollViewer automatically with Xaml and binding?

Tags:

c#

wpf

xaml

I've got a TextBlock whose content is data bound to a string property of the ViewModel. This TextBlock has a ScrollViewer wrapped around it.

What I want to do is every time the logs change, the ScrollViewer will scroll to the bottom. Ideally I want something like this:

    <ScrollViewer ScrollViewer.HorizontalScrollBarVisibility="Auto"
                  ScrollPosition="{Binding Path=ScrollPosition}">
        <TextBlock Text="{Binding Path=Logs}"/>
    </ScrollViewer>

I don't want to use Code Behind! The solution I'm looking for should be using only binding and/or Xaml.

like image 439
JiBéDoublevé Avatar asked Dec 03 '11 19:12

JiBéDoublevé


2 Answers

You can either create an attached property or a behavior to achieve what you want without using code behind. Either way you will still need to write some code.

Here is an example of using attached property.

Attached Property

public static class Helper
{
    public static bool GetAutoScroll(DependencyObject obj)
    {
        return (bool)obj.GetValue(AutoScrollProperty);
    }

    public static void SetAutoScroll(DependencyObject obj, bool value)
    {
        obj.SetValue(AutoScrollProperty, value);
    }

    public static readonly DependencyProperty AutoScrollProperty =
        DependencyProperty.RegisterAttached("AutoScroll", typeof(bool), typeof(Helper), new PropertyMetadata(false, AutoScrollPropertyChanged));

    private static void AutoScrollPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var scrollViewer = d as ScrollViewer;

        if (scrollViewer != null && (bool)e.NewValue)
        {
            scrollViewer.ScrollToBottom();
        }
    }
}

Xaml Binding

<ScrollViewer local:Helper.AutoScroll="{Binding IsLogsChangedPropertyInViewModel}" .../>

You will need to create a boolean property IsLogsChangedPropertyInViewModel and set it to true when the string property is changed.

Hope this helps! :)

like image 145
Justin XL Avatar answered Nov 16 '22 05:11

Justin XL


Answer updated 2017-12-13, now uses the ScrollChanged event and checks if the size of extent changes. More reliable and doesn't interfere with manual scrolling

I know this question is old, but I've got an improved implementation:

  • No external dependencies
  • You only need to set the property once

The code is heavily influenced by Both Justin XL's and Contango's solutions

public static class AutoScrollBehavior
{
    public static readonly DependencyProperty AutoScrollProperty =
        DependencyProperty.RegisterAttached("AutoScroll", typeof(bool), typeof(AutoScrollBehavior), new PropertyMetadata(false, AutoScrollPropertyChanged));


    public static void AutoScrollPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        var scrollViewer = obj as ScrollViewer;
        if(scrollViewer != null && (bool)args.NewValue)
        {
            scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged;
            scrollViewer.ScrollToEnd();
        }
        else
        {
            scrollViewer.ScrollChanged-= ScrollViewer_ScrollChanged;
        }
    }

    private static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        // Only scroll to bottom when the extent changed. Otherwise you can't scroll up
        if (e.ExtentHeightChange != 0)
        {
            var scrollViewer = sender as ScrollViewer;
            scrollViewer?.ScrollToBottom();
        }
    }

    public static bool GetAutoScroll(DependencyObject obj)
    {
        return (bool)obj.GetValue(AutoScrollProperty);
    }

    public static void SetAutoScroll(DependencyObject obj, bool value)
    {
        obj.SetValue(AutoScrollProperty, value);
    }
}

Usage:

<ScrollViewer n:AutoScrollBehavior.AutoScroll="True" > // Where n is the XML namespace 
like image 39
Roy T. Avatar answered Nov 16 '22 06:11

Roy T.