如何使用Xaml和绑定自动滚动到ScrollViewer的底部?

时间:2011-12-03 19:10:08

标签: c# wpf xaml

我有一个TextBlock,其内容是绑定到ViewModel的字符串属性的数据。这个TextBlock包含ScrollViewer

我想要做的是每次日志更改时,ScrollViewer都会滚动到底部。理想情况下,我想要这样的事情:

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

想要使用Code Behind!我正在寻找的解决方案应该是使用绑定和/或Xaml。

6 个答案:

答案 0 :(得分:44)

您可以创建附加属性或行为以实现您想要的而不使用后面的代码。无论哪种方式,您仍然需要编写一些代码。

以下是使用附加属性的示例。

附加财产

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}" .../>

您需要创建一个布尔属性IsLogsChangedPropertyInViewModel,并在更改字符串属性时将其设置为true。

希望这有帮助! :)

答案 1 :(得分:23)

答案更新2017-12-13,现在使用ScrollChanged事件并检查范围大小是否发生变化。更可靠,不会干扰手动滚动

我知道这个问题已经过时了,但我的实施得到了改进:

  • 没有外部依赖
  • 您只需要设置一次属性

该代码深受Justin XL和Contango的解决方案的影响

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

用法:

<ScrollViewer n:AutoScrollBehavior.AutoScroll="True" > // Where n is the XML namespace 

答案 2 :(得分:11)

来自Geoff's Blog on ScrollViewer AutoScroll Behavior

添加此课程:

namespace MyAttachedBehaviors
{
    /// <summary>
    ///     Intent: Behavior which means a scrollviewer will always scroll down to the bottom.
    /// </summary>
    public class AutoScrollBehavior : Behavior<ScrollViewer>
    {
        private double _height = 0.0d;
        private ScrollViewer _scrollViewer = null;

        protected override void OnAttached()
        {
            base.OnAttached();

            this._scrollViewer = base.AssociatedObject;
            this._scrollViewer.LayoutUpdated += new EventHandler(_scrollViewer_LayoutUpdated);
        }

        private void _scrollViewer_LayoutUpdated(object sender, EventArgs e)
        {
            if (Math.Abs(this._scrollViewer.ExtentHeight - _height) > 1)
            {
                this._scrollViewer.ScrollToVerticalOffset(this._scrollViewer.ExtentHeight);
                this._height = this._scrollViewer.ExtentHeight;
            }
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();

            if (this._scrollViewer != null)
            {
                this._scrollViewer.LayoutUpdated -= new EventHandler(_scrollViewer_LayoutUpdated);
            }
        }
    }
}

此代码依赖于Blend Behaviors,它需要引用System.Windows.Interactivity。请参阅help on adding System.Windows.Interactivity

如果安装MVVM Light NuGet包,可以在此处添加引用:

packages\MvvmLightLibs.4.2.30.0\lib\net45\System.Windows.Interactivity.dll

确保标题中包含此属性,该属性指向System.Windows.Interactivity.dll

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

ScrollViewer

中添加混合行为
<i:Interaction.Behaviors>
    <implementation:AutoScrollBehavior />
</i:Interaction.Behaviors>

示例:

<GroupBox Grid.Row="2" Header ="Log">
    <ScrollViewer>
        <i:Interaction.Behaviors>
            <implementation:AutoScrollBehavior />
        </i:Interaction.Behaviors>
        <TextBlock Margin="10" Text="{Binding Path=LogText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" TextWrapping="Wrap"/>
    </ScrollViewer>
</GroupBox> 

我们必须为命名空间添加一个定义,否则它将不知道在哪里找到我们刚刚添加的C#类。将此属性添加到<Window>标记中。如果您使用的是ReSharper,它会自动为您建议。

xmlns:implementation="clr-namespace:MyAttachedBehaviors"

现在,如果一切顺利,框中的文字将始终向下滚动到底部。

给出的示例XAML会将绑定属性LogText的内容打印到屏幕上,这非常适合记录。

答案 3 :(得分:5)

很简单,例如:

yourContronInside.ScrollOwner.ScrollToEnd (); 
yourContronInside.ScrollOwner.ScrollToBottom ();

答案 4 :(得分:0)

这是一个小小的变化。

当滚动查看器高度(视口)及其滚动呈现器内容(范围)的高度发生变化时,这将滚动到底部。

这是基于Roy T的答案,但我无法发表评论,所以我发布了答案。

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


        public static void AutoScrollPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            var scrollViewer = obj as ScrollViewer;
            if (scrollViewer == null) return;

            if ((bool) args.NewValue)
            {
                scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged;
                scrollViewer.ScrollToEnd();
            }
            else
            {
                scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged;
            }
        }

        static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            // Remove "|| e.ViewportHeightChange < 0 || e.ExtentHeightChange < 0" if you want it to only scroll to the bottom when it increases in size
            if (e.ViewportHeightChange > 0 || e.ExtentHeightChange > 0 || e.ViewportHeightChange < 0 || e.ExtentHeightChange < 0)
            {
                var scrollViewer = sender as ScrollViewer;
                scrollViewer?.ScrollToEnd();
            }
        }

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

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

答案 5 :(得分:-1)

我正在使用@Roy T.的答案,但是我想要更多的规定,如果你及时滚动,但随后添加了文字,滚动视图应该自动滚动到底部。

我用过这个:

private static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
    var scrollViewer = sender as ScrollViewer;

    if (e.ExtentHeightChange > 0)
    {
        scrollViewer.ScrollToEnd();
    }    
}

代替SizeChanged事件。

相关问题