使用 ScrollViewer.ScrollToEnd() 进行自动滚动仅在调试时有效,事件处理程序太简单

2024-02-01

看着这个解决方案 https://stackoverflow.com/questions/2984803/how-to-automatically-scroll-scrollviewer-only-if-the-user-did-not-change-scrol为了更好的自动滚动,我认为自己很聪明,找到了一个更简单的解决方案,但它仅在调试会话中有效:

    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 中向文本块添加内容时,自动滚动起作用,文本底部保持在视图中。当用户向上滚动并添加更多内容时,底部将消失,自动滚动关闭,这很好。当用户滚动回到底部时,ScrollToEnd() 应该重新打开自动滚动,但是当添加更多内容时,底部仍然滚动到视图之外。

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

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

这确实让我感到困惑,尽管我对简单的自动滚动解决方案非常满意。这怎么行不通呢?


edit:

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


edit:

这个问题是否是由于内容是带有多行文本字符串和 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>

edit:

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

 sv.ScrollableHeight - sv.VerticalOffset < 20

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


关于解决方案:

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

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

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

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

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

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


问题的原因在于ScrollToEnd()与自动滚动无关。这个调用只是滚动到最后,仅此而已。通过将调用放入事件处理程序中,它会经常滚动到末尾,但对于真正的自动滚动,有必要确定谁触发了事件:用户移动滑块,或者由于内容大小变化而移动滑块。而不是通过查看来忽略“无用”事件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;
    }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 ScrollViewer.ScrollToEnd() 进行自动滚动仅在调试时有效,事件处理程序太简单 的相关文章

  • 在 C++ 中分割大文件

    我正在尝试编写一个程序 该程序接受一个大文件 任何类型 并将其分成许多较小的 块 我想我已经有了基本的想法 但由于某种原因我无法创建超过 12 kb 的块大小 我知道谷歌等上有一些解决方案 但我更感兴趣的是了解这个限制的根源是什么 然后实际
  • 在 OpenCL 中将函数作为参数传递

    是否可以在 OpenCL 1 2 中将函数指针传递给内核 我知道可以用C实现 但不知道如何在OpenCL的C中实现 编辑 我想做这篇文章中描述的同样的事情 在 C 中如何将函数作为参数传递 https stackoverflow com q
  • 处理 fanart.tv Web 服务响应 JSON 和 C#

    我正在尝试使用 fanart tv Webservice API 但有几个问题 我正在使用 Json Net Newtonsoft Json 并通过其他 Web 服务将 JSON 响应直接反序列化为 C 对象 这里的问题是元素名称正在更改
  • 在 Xcode4 中使用 Boost

    有人设置 C Xcode4 项目来使用 Boost 吗 对于一个简单的 C 控制台应用程序 我需要在 Xcode 中设置哪些设置 Thanks 用这个来管理它 和这个
  • 如何用 kevent() 替换 select() 以获得更高的性能?

    来自Kqueue 维基百科页面 http en wikipedia org wiki Kqueue Kqueue 在内核和用户空间之间提供高效的输入和输出事件管道 因此 可以修改事件过滤器以及接收待处理事件 同时每次主事件循环迭代仅使用对
  • 是否有与 C++11 emplace/emplace_back 函数类似的 C# 函数?

    从 C 11 开始 可以写类似的东西 include
  • std::bind 重载解析

    下面的代码工作正常 include
  • C# using 语句、SQL 和 SqlConnection

    使用 using 语句 C SQL 可以吗 private static void CreateCommand string queryString string connectionString using SqlConnection c
  • 如何排列表格中的项目 - MVC3 视图 (Index.cshtml)

    我想使用 ASP NET MVC3 显示特定类型食品样本中存在的不同类型维生素的含量 如何在我的视图 Index cshtml 中显示它 an example 这些是我的代码 table tr th th foreach var m in
  • 在 C 中复制两个相邻字节的最快方法是什么?

    好吧 让我们从最明显的解决方案开始 memcpy Ptr const char a b 2 调用库函数的开销相当大 编译器有时不会优化它 我不会依赖编译器优化 但即使 GCC 很聪明 如果我将程序移植到带有垃圾编译器的更奇特的平台上 我也不
  • 通过等待任务或访问其 Exception 属性都没有观察到任务的异常

    这些是我的任务 我应该如何修改它们以防止出现此错误 我检查了其他类似的线程 但我正在使用等待并继续 那么这个错误是怎么发生的呢 通过等待任务或访问其 Exception 属性都没有观察到任务的异常 结果 未观察到的异常被终结器线程重新抛出
  • 过期时自动重新填充缓存

    我当前缓存方法调用的结果 缓存代码遵循标准模式 如果存在 则使用缓存中的项目 否则计算结果 在返回之前将其缓存以供将来调用 我想保护客户端代码免受缓存未命中的影响 例如 当项目过期时 我正在考虑生成一个线程来等待缓存对象的生命周期 然后运行
  • gdb查找行号的内存地址

    假设我已将 gdb 附加到一个进程 并且在其内存布局中有一个文件和行号 我想要其内存地址 如何获取文件x中第n行的内存地址 这是在 Linux x86 上 gdb info line test c 56 Line 56 of test c
  • 内核开发和 C++ [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 从我know https stackoverflow com questions 580292 what languages are windo
  • 为什么我使用google'smtp'无法发送电子邮件?

    我有以下程序使用 smtp gmail com 587 发送电子邮件 namespace TestMailServer class Program static void Main string args MailMessage mail
  • Fluent NHibernate 日期时间 UTC

    我想创建一个流畅的 nhibernate 映射来通过以下方式映射 DateTime 字段 保存时 保存 UTC 值 读取时 调整为本地时区值 实现此映射的最佳方法是什么 就我个人而言 我会将日期存储在 UTC 格式的对象中 然后在读 写时在
  • 我应该在应用程序退出之前运行 Dispose 吗?

    我应该在应用程序退出之前运行 Dispose 吗 例如 我创建了许多对象 其中一些对象具有事件订阅 var myObject new MyClass myObject OnEvent OnEventHandle 例如 在我的工作中 我应该使
  • 过度使用委托对性能来说是一个坏主意吗? [复制]

    这个问题在这里已经有答案了 考虑以下代码 if IsDebuggingEnabled instance Log GetDetailedDebugInfo GetDetailedDebugInfo 可能是一个昂贵的方法 因此我们只想在调试模式
  • boost::program_options:带有固定和可变标记的参数?

    是否可以在 boost program options 中使用此类参数 program p1 123 p2 234 p3 345 p12 678 即 是否可以使用第一个标记指定参数名称 例如 p 后跟一个数字 是动态的吗 我想避免这种情况
  • 如何确定母版页中正在显示哪个子页?

    我正在母版页上编写代码 我需要知道正在显示哪个子 内容 页面 我怎样才能以编程方式做到这一点 我用这个 string pageName this ContentPlaceHolder1 Page GetType FullName 它以 AS

随机推荐