c# - 使用 ScrollViewer.ScrollToEnd() 执行 AutoScroll 仅在调试时有效,事件处理程序太简单

标签 c# wpf debugging scrollview

查看this solution为了更好的自动滚动,我认为自己很聪明,找到了一个更简单的解决方案,但它只在调试 session 中有效:

    private void scrollviewer_Messages_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        ScrollViewer sv = sender as ScrollViewer;
        if (sv.VerticalOffset == sv.ScrollableHeight)
        {
            sv.ScrollToEnd();//debug breakpoint
        }
        return;
    }

在此 ScrollViewer 中向文本 block 添加内容时,自动滚动有效,文本底部保持在 View 中。当用户向上滚动并添加更多内容时,底部消失,自动滚动关闭,这很好。当用户滚动回到底部时,ScrollToEnd() 应该重新打开自动滚动,但是当添加更多内容时,底部仍然滚动到 View 之外。

当我设置断点时,我可以验证确实调用了 ScrollToEnd()。然后,在删除断点并添加更多内容后,自动滚动再次起作用。

我通过按下按钮、ViewModel 中的代码和绑定(bind)来添加内容。所以我确定没有并发问题。添加内容和手动滚动之间有很多时间。

这真的让我感到困惑,虽然我对我的简单自动滚动解决方案非常满意。这怎么行不通?


编辑:

我发现滚动回到底部后自动滚动又起作用了,但不知何故真正触底并不是那么容易。我需要向下移动 slider ,然后单击滚动条的向下箭头。我现在将尝试替换 ==登录我的代码以允许几个像素的差异。


编辑:

这个问题会不会是因为内容是一个包含多行文本字符串和TextWrap的TextBlock?

    <ScrollViewer Name="scrollviewer_Messages" DockPanel.Dock="Top" 
                  Height="100" Width="200"           
                  ScrollChanged="scrollviewer_Messages_ScrollChanged">
        <TextBlock Name="tb_Message"
               Margin="10" TextWrapping="Wrap"
               Text="{Binding Path=Messages}">
        </TextBlock>
    </ScrollViewer>

编辑:

将事件处理程序中的公式更改为:

 sv.ScrollableHeight - sv.VerticalOffset < 20

我已经尝试过 < 10但是 pushpraj(见下面的答案)让我尝试了更大的数字。仍然不清楚为什么会这样,因为问题不在于 ScrollToEnd()没有被调用。


关于解决方案:

<20不需要,因为它是关于小数的。一般来说,两个实数永远不会相等,但在这里情况并非如此。 double当 slider 位于末尾时,偏移量和高度的数字实际上是相等的。

问题是,显然,ScrollToEnd/Bottom()使用 slider 滚动时不起作用。就是这样。我会称之为错误,但它也可能是一个“功能”:当用户滑动 slider 并期望控制时,不应更改 slider 的行为。

解决方法是,首先我们将 slider 滑动到末端,使 Offset == Height。第二步是添加内容会增加高度,由于上面的错误, slider 会向上移动一点点,在我的例子中大约是 15 点。这会引发 ScrollChanged 事件和 <20 的阈值足以接到第二次电话 ScrollToBottom .每次添加内容时都会执行第二步。

我之前的编辑提到单击向下按钮的工作方式类似。显然,ScrollToEnd 适用于向下按钮。

问题当然是错误就是错误。一次添加更多内容时,阈值可能不起作用,并且自动滚动可能会停止。

最终的解决方案,不像我希望的那么简单,但也不会太复杂,应该是我下面的答案。

最佳答案

问题的原因是 ScrollToEnd() 与自动滚动无关。这个调用只是滚动到最后,就是这样。通过将调用放在事件处理程序中,它会经常滚动到末尾,但对于真正的自动滚动,有必要确定是谁触发了事件:用户通过移动 slider ,或者由于内容大小的变化而移动 slider 。不再通过查看 ExtentHeight 来忽略“无用”事件,而是使用此属性来确定触发事件的人或事件。

此解决方案将自动滚动位的状态保存在控件的标记中。将新的用户控件 AutoScrollViewer 子类化会更好。

毕竟,这个解决方案并不比问题中上面提到的以前的解决方案“简单”多少,它只是一个变体,但(希望)更准确。

    /// <summary>
    /// If the scrollviewer is at the bottom, keep the bottom in view.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void scrollviewer_Messages_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        ScrollViewer sv = sender as ScrollViewer;
        bool AutoScrollToEnd = true;
        if (sv.Tag != null)
        {
            AutoScrollToEnd = (bool)sv.Tag;
        }
        if (e.ExtentHeightChange == 0)// user scroll
        {                
            AutoScrollToEnd = sv.ScrollableHeight == sv.VerticalOffset;
        }
        else// content change
        {                
            if (AutoScrollToEnd)
            {
                sv.ScrollToEnd();
            }
        }
        sv.Tag = AutoScrollToEnd;
        return;
    }

关于c# - 使用 ScrollViewer.ScrollToEnd() 执行 AutoScroll 仅在调试时有效,事件处理程序太简单,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25761795/

相关文章:

c - QDevelop问题调试位置指针消失

c# - 在 Xamarin 中从 JavaScript 调用 C#

c# - 在 asp.net 中使用 smtp 和 gmail 提交表单后发送电子邮件

c# - .NET Core DI,为包注册默认实现

wpf - 为什么 ICollectionView<T> 没有 .Net 接口(interface)?

php - 在 PhpStorm 中排除其他脚本调试

java - 无法找出逻辑错误

c# - 表单发布时将下拉列表中的选定值从 View 传递到 Controller

wpf - x :Key and x:Name之间的区别

c# - 将 Button.CommandParameter 绑定(bind)到 MVVM 对象以使用相同的 ICommand 到不同的按钮