【问题标题】:How to scroll to the bottom of a ScrollViewer automatically with Xaml and binding?如何使用 Xaml 和绑定自动滚动到 ScrollViewer 的底部?
【发布时间】:2011-12-03 19:10:08
【问题描述】:

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

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

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

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

【问题讨论】:

  • 没有代码背后的任何具体原因?
  • 你是对的,但在我看来,MVVM 只是建议你的业务逻辑(视图模型)不应该与你的 UI(视图)混合。 Scroll Viewer 是 UI/View 如果我们在代码中添加一些代码来将 ScrollViewer 移动到底部,它不会违反 MVVM,因为我们只是在玩 UI
  • @Haris:我理解并同意你的观点,但我不确定 OP 是否这样做。
  • @Kent Boogaart 我想要一个 MVVM 答案有三个原因: 1- 我使用的是 MVVM 模式,因此我想找到的第一种答案是 MVVM。 2- 在请求 MVVM 之前,我在 Google 或 StackOverflow 中找到了答案背后的代码。所以我不会要求答案,因为我知道我几乎会有解决方案背后的代码 3-只有当我知道所有不同类型的可能性时,我才能做出正确的选择,赢了'我?别担心,我不是狂热者;)
  • MVVM 没有t deny code behind. I think the point of comments of @Harris and @Kent was that theres 没有任何重要的理由在 XAML 或帮助程序类中编写巨大的构造,只是为了避免在代码中包含一行特定于视图的代码。

标签: c# wpf xaml


【解决方案1】:

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

这是使用附加属性的示例。

附加属性

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 绑定

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

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

希望这会有所帮助! :)

【讨论】:

  • 不幸的是,我无法在我的机器上使用 VS 2012 + MVVM Light。我的猜测是它可能取决于未记录的参考。我已经从 Geoff 的博客中发布了一个对我有用的答案。
  • Julien XL 的回答非常适合我。我有 VS 2012 + Mahapps。我不使用 MVVM Light
  • @Zougi 它也适用于我,VS2015 + MVVM Light。很好的解决方案!
  • 效果很好,我确实必须添加一个自定义 OnPropertyChanged 事件,以便在字符串绑定更改时将 bool 值更改为 true
  • 我在项目控件中使用滚动查看器,此解决方案不起作用。 Roy T. 的解决方案对我有用。
【解决方案2】:

答案于 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 

【讨论】:

  • 如果将此属性放在任何不是 ScrollViewer 的东西上,我会看到等待发生的空引用异常。
  • 具体在哪里?在我期望 ScrollViewer 的情况下,我使用 Elvis 运算符和“as”转换。
  • @RoyT 在AutoScrollPropertyChanged 方法中,else 可以在scrollViewer 为空时到达,不仅当scrollViewer 不是nullNewValuefalse
  • 我首先实现了接受的答案。然后我看到这个答案在实现中看起来要简单得多,因为我不必有一个会改变的布尔值。效果很好,从现在开始使用它
  • 经过测试,可以确认这在 .NET Core 3.1 上运行良好
【解决方案3】:

来自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# 类。将此属性添加到 &lt;Window&gt; 标记中。如果您使用的是 ReSharper,它会自动为您提供建议。

xmlns:implementation="clr-namespace:MyAttachedBehaviors"

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

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

【讨论】:

  • 如果它对使用您的示例的任何人有所帮助,您可以从 Visual Studio 2012 或 2013 安装文件夹获取适用于 .NET 4.5 的 System.Windows.Interactivity.dll,如果您自 Blend 以来安装了其中任何一个版本的 Blend随他们一起来。
  • @Alex Marshall。您是绝对正确的,感谢您添加此注释。当我使用 MVVM Light 时,我无法让它工作,直到我使用了 MVVM Light 提供的确切 System.Windows.Interactivity.dll(如答案中所述)。如果使用了其他 MVVM 框架,或者甚至是背后的代码,那么这可能会工作得很好。换句话说,如果你的 MVVM 框架已经包含,你不能将这个.dll 的多个版本添加到你的项目中。
  • 能否添加 XAML 中设置资源的部分?
  • @Mark W 我一直在使用它,它工作得非常好。刚刚更新了答案,现在就试试吧。
  • 这在 2021 年不再完全正确。行为的实现是正确的,但要使用当前正确的命名空间和 dll,请参阅stackoverflow.com/questions/20743961/…
【解决方案4】:

很简单,例子:

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

【讨论】:

  • 考虑扩展您的答案以向提问者解释为什么这样可以达到预期的结果,可能链接到文档。照原样,这只是微不足道的用处。
  • 这对我来说似乎不是 MVVM 解决方案。诀窍是在不使用代码的情况下做到这一点..
  • 为什么...为什么这不仅仅是答案,它实际上如此简单,甚至这里的人都吓坏了.. XAML 中的@ecth 为 ScrollChanged 和字面意思 Put在该事件中 scrollViewName.ScrollToBottom();完美运行。
  • 这正是我所说的非 MVVM 的意思。这是普通的WPF,真的。但是如果你想分离视图和视图模型,你不需要在 xaml 中创建事件并在代码隐藏中处理它们。您的代码隐藏只是一个带有 InitializeComponent() 的构造函数。您将内容绑定到视图模型中的命令。
【解决方案5】:

这里有一个细微的变化。

当滚动查看器高度(视口)和滚动演示者内容的高度(范围)发生变化时,这将滚动到底部。

这是基于 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);
        }
    }

【讨论】:

    【解决方案6】:

    我正在使用@Roy T. 的答案,但是我想要添加的规定,如果您及时回滚,然后添加文本,则滚动视图应自动滚动到底部。

    我用过这个:

    private static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        var scrollViewer = sender as ScrollViewer;
    
        if (e.ExtentHeightChange > 0)
        {
            scrollViewer.ScrollToEnd();
        }    
    }
    

    代替 SizeChanged 事件。

    【讨论】:

    • 我将其绑定为我的 XAML 中的一种行为。像这样:&lt;ScrollViewer Name="Scroller" Margin="0" behaviors:AutoScrollBehavior.AutoScroll="True"&gt;
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-24
    • 2014-02-26
    • 2016-11-13
    • 1970-01-01
    相关资源
    最近更新 更多