事情起因

测试报告说存在滚动条不能拖动的情况,我们几个开发人员多次测试都未重现该问题。后面发现是操作系统的问题,在XP和部分Win7上会存在该问题。而在我们开发人员的机器上,包括Win7 SP1,Windows Server2008上都未出现该问题。

该问题的具体表现是拖动ScrollViewer时的滚动条不能滚动里面的内容,但是点击滚动条上下方的RepeatButton(即通常情况下的三角形按钮)却能滚动里面的内容。

本以为找到了问题,解决起来会很快。但是我们几个同事试了好久,都没找到问题。我也简单看了下,开始以为会是ScrollChanged事件响应将滚动条滚回去了,结果不是。后面就忙其他的去了。再后来,另外一个同事发现是外层ScrollViewer的IsDeferredScrollingEnabled设为了True。

下面是这个情况的一个简单示例,感兴趣的朋友可以试试。

<Window x:Class="ScrollViewerTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350">
<Grid>
<ScrollViewer IsDeferredScrollingEnabled="True">
<Canvas Width="1000"
Height="1000"
Background="Red">
<ScrollViewer Canvas.Left="200"
Canvas.Top="200"
Width="100"
Height="100">
<Canvas Width="500" Height="500">
<Canvas.Background>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
<GradientStop Offset="0" Color="Black" />
<GradientStop Offset="1" Color="White" />
</LinearGradientBrush>
</Canvas.Background>
</Canvas>
</ScrollViewer>
</Canvas>
</ScrollViewer>
</Grid>
</Window>

原因探索

MSDN

先MSDN查看IsDeferredScrollingEnabled的含义。

Gets or sets a value that indicates whether the content is stationary when the user drags the Thumb of a ScrollBar.

在下面有备注:

Displaying a large number of items may cause performance issues. In this case, it might be useful to use deferred scrolling. For more information, see Optimizing Performance: Controls.

This property can be used as an instance property and an attached property.

再查看Optimizing Performance: Controls中Deferred Scrolling一节

By default, when the user drags the thumb on a scrollbar, the content view continuously updates. If scrolling is slow in your control, consider using deferred scrolling. In deferred scrolling, the content is updated only when the user releases the thumb.

To implement deferred scrolling, set the IsDeferredScrollingEnabled property to true. IsDeferredScrollingEnabled is an attached property and can be set on ScrollViewer and any control that has a ScrollViewer in its control template.

这些都是我一早就知道的,该属性是用于优化性能的。默认情况下,滚动条滚动时里面的内容会更新,在数据量较大且希望快速滚动时效率会比较低,而将该属性设为true,将会在松开滚动条时才更新内容。

源码

利用ILSpy查看ScrollViewer的源码。

查找IsDeferredScrollingEnabled的引用,发现其只在如下的函数中使用

private static void OnQueryScrollCommand(object target, CanExecuteRoutedEventArgs args)
{
args.CanExecute = true;
if (args.Command == ComponentCommands.ScrollPageUp || args.Command == ComponentCommands.ScrollPageDown)
{
ScrollViewer scrollViewer = target as ScrollViewer;
Control control = (scrollViewer != null) ? (scrollViewer.TemplatedParent as Control) : null;
if (control != null && control.HandlesScrolling)
{
args.CanExecute = false;
args.ContinueRouting = true;
args.Handled = true;
return;
}
}
else
{
if (args.Command == ScrollBar.DeferScrollToHorizontalOffsetCommand || args.Command == ScrollBar.DeferScrollToVerticalOffsetCommand)
{
ScrollViewer scrollViewer2 = target as ScrollViewer;
if (scrollViewer2 != null && !scrollViewer2.IsDeferredScrollingEnabled)
{
args.CanExecute = false;
args.Handled = true;
}
}
}
}

再查找OnQueryScrollCommand的引用,发现其只在如下的函数中使用

private static void InitializeCommands()
{
ExecutedRoutedEventHandler executedRoutedEventHandler = new ExecutedRoutedEventHandler(ScrollViewer.OnScrollCommand);
CanExecuteRoutedEventHandler canExecuteRoutedEventHandler = new CanExecuteRoutedEventHandler(ScrollViewer.OnQueryScrollCommand);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.LineLeftCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.LineRightCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.PageLeftCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.PageRightCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.LineUpCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.LineDownCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.PageUpCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.PageDownCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.ScrollToLeftEndCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.ScrollToRightEndCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.ScrollToEndCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.ScrollToHomeCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.ScrollToTopCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.ScrollToBottomCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.ScrollToHorizontalOffsetCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.ScrollToVerticalOffsetCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.DeferScrollToHorizontalOffsetCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ScrollBar.DeferScrollToVerticalOffsetCommand, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ComponentCommands.ScrollPageUp, executedRoutedEventHandler, canExecuteRoutedEventHandler);
CommandHelpers.RegisterCommandHandler(typeof(ScrollViewer), ComponentCommands.ScrollPageDown, executedRoutedEventHandler, canExecuteRoutedEventHandler);
}

而InitializeCommands仅在ScrollViewer的静态构造函数中调用。

更多关于ScrollViewer与该问题无太大关系,留待以后补充。

猜想

里层的ScrollViewer将滚动的事件传递至上层的ScrollViewer处理,在上层ScrollViewer设置了IsDeferredScrollingEnabled的某些情况下,可能会造成底层ScrollViewer命令不可用。

补充

在这篇博文都快写完的时候,发现了Nested scrollbar and deferred scrolling bug,学习到了一些知识。

将里层的ScrollViewer的IsDeferredScrollingEnabled设为true,可解决这个问题。(由于我电脑为Win7 SP1,未测试)。

在外层ScrollViewer与里层ScrollViewer的逻辑树上的某个控件上更改命令绑定。将相关命令的CanExecute总是设为true,可解决问题(同样未测试)。

更多内容,请移步上述链接。

WPF中ScrollViewer嵌套引发滚动失灵的Bug的更多相关文章

  1. WPF的ScrollViewer鼠标的滚动

    在C# 中,两个ScrollViewer嵌套在一起或者ScrollViewer里面嵌套一个ListBox.Listview(控件本身有scrollviewer)的时候,我们本想要的效果是鼠标滚动整个S ...

  2. WPF中DataGrid垂直滚动条滚动后导致每行CheckBox选择错乱

    问题: WPF的DataGrid中出现选取或者多选以及单选的时候,出现滚动条的时候,如果发生了滚动,默认情况下就会出现已经选择的CheckBox错乱.这样的原因何在? 解决方案: 经过查阅资料,了解到 ...

  3. DEVExpress For WPF 中GridControl如何实现滚动分页(延迟查询)

    在显示大量数据时一般采用分页显示,但是最近用户需要滚动显示,那么问题来了,滚动显示要求将数据全部查询回来,这显然会导致显示速度很慢. 好在想到一种方式,就是当用户滚动鼓动条的时候再查询下面的数据.好吧 ...

  4. 解答WPF中ComboBox SelectedItem Binding不上的Bug

    正在做一个打印机列表,从中选择一个打印机(System.Printing) <ComboBox Width="150" ItemsSource="{Binding ...

  5. VS编程,WPF中两个滚动条 ScrollViewer 同步滚动的一种方法

    原文:VS编程,WPF中两个滚动条 ScrollViewer 同步滚动的一种方法 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/ar ...

  6. 在WPF中实现平滑滚动

    WPF实现滚动条还是比较方便的,只要在控件外围加上ScrollViewer即可,但美中不足的是:滚动的时候没有动画效果.在滚动的时候添加过渡动画能给我们的软件增色不少,例如Office 2013的滚动 ...

  7. 解决ScrollViewer嵌套的DataGrid、ListBox等控件的鼠标滚动事件无效

    C# 中,两个ScrollViewer嵌套在一起或者ScrollViewer里面嵌套一个DataGrid.ListBox.Listview(控件本身有scrollviewer)的时候,我们本想要的效果 ...

  8. WPF中获取TreeView以及ListView获取其本身滚动条的方法,可实现自行调节scoll滚动的位置(可相应获取任何控件中的内部滚动条)

    原文:WPF中获取TreeView以及ListView获取其本身滚动条的方法,可实现自行调节scoll滚动的位置(可相应获取任何控件中的内部滚动条) 对于TreeView而言: TreeViewAut ...

  9. WPF: 实现 ScrollViewer 滚动到指定控件处

    在前端 UI 开发中,有时,我们会遇到这样的需求:在一个 ScrollViewer 中有很多内容,而我们需要实现在执行某个操作后能够定位到其中指定的控件处:这很像在 HTML 页面中点击一个链接后定位 ...

随机推荐

  1. c++11新特性总结(转。加上重点)

    1.类型与变量相关 1.1.nullptr: 取代了NULL,专用于空指针 1.2.constexpr: 近似const, 可以修饰变量,也可以修饰函数, 修饰变量如: const int globa ...

  2. css如何去掉select原始样式

    css代码: select { /*将默认的select选择框样式清除*/ appearance: none; -moz-appearance: none; -webkit-appearance: n ...

  3. Java多线程编程 — 锁优化

      阅读目录 一.尽量不要锁住方法 二.缩小同步代码块,只锁数据 三.锁中尽量不要再包含锁 四.将锁私有化,在内部管理锁 五.进行适当的锁分解 正文 并发环境下进行编程时,需要使用锁机制来同步多线程间 ...

  4. 源码编译安装MySQL-5.6/mysql-5.6.39------踩了无数坑,重装了十几次服务器才会的,不容易啊!

    1.切换到src目录 cd /usr/local/src/ 2. 下载mysql免编译二进制包 免编译的mysql二进制包5.6源码包: wget http://mirrors.163.com/mys ...

  5. RecyclerView的通用适配器

    本来这一个主题应该早就写了,只是项目多,属于自己的时间不多,所以现在才开动!! 前一段时间写了一篇文章,是关于ListView,GriView万能适配器,没有看过的同学,可以先看看那篇文章,然后在来学 ...

  6. luncene 查询字符串的解析-QueryParser类

    搜索流程中的第二步就是构建一个Query.下面就来介绍Query及其构建. 当用户输入一个关键字,搜索引擎接收到后,并不是立刻就将它放入后台开始进行关键字的检索,而应当首先对这个关键字进行一定的分析和 ...

  7. Haproxy搭建Web群集

    一.Haproxy与LVS LVS不支持正则处理,不能实现动静分离,对于大型网站,LVS的实施配置复杂,维护成本相对较高 Harpoxy是一款可提供高可用性,负载均衡.及基于TCP和HTTP应用的代理 ...

  8. google Agent

    一句话:改HOSTS文件 有时候要查看appengine.google.com的后台,但死活上不去,怎么办?还是那个老办法: 1.在cmd下Ping www.google.com.hk,获得IP地址, ...

  9. React 初识

    React We built React to solve one problem: building large applications with data that changes over t ...

  10. 2016级算法第六次上机-E.Bamboo之吃我一拳

    Bamboo之吃我一拳 分析 当两个点的距离<=d时,才可以出拳,想要使得满足出拳条件的点对最少但不为0 寻找最近点对距离,得到的最近距离能够使得可以出拳的组数最少,因为除了最近点对外其他组合均 ...