WPF实现滚动条还是比较方便的,只要在控件外围加上ScrollViewer即可,但美中不足的是:滚动的时候没有动画效果。在滚动的时候添加过渡动画能给我们的软件增色不少,例如Office 2013的滚动的时候支持动画看起来就舒服多了。 之前倒是研究过如何实现这个平滑滚动,不过网上的方案大部分大多数如下:

  1. 通过VisualTree找到ScrollViewer
  2. 在ScrollChanged事件中添加动画

这种方案效果并不好,以为我们的滚动很多时候都是一口气滚动好几格滚轮的,这个时候上一个动画还没有结束,下一个动画就来了,反而还出现了卡顿的感觉,并且网上的一些算法大部分还都会导致偏移错位。

趁着这两天有点时间,就研究了一下ScorllViewer,从MSDN文档中看到,它是支持两种滚动方式的:

物理滚动:

系统默认的滚动方案,控件本身啥都不用干,完全由ScrollViewer来实现滚动。这种方式的好处是简单,但也正由于简单,控件本身完全感知不到ScorllViewer的存在,也就无法加以控制了。

逻辑滚动:

将这种方式需要设置ScrollViewer的CanContentScroll为"True"才能生效,同时需要控件实现IScrollInfo接口。此时ScrollViewer只是将滚动事件通过IScrollInfo接口传递给控件,由控件本身自己去实现滚动。同时从IScrollInfo接口中读取相关的属性更新滚动条界面。

也就是说,逻辑滚动才是我们所需要的方案。由于它要求控件实现IScrollInfo接口,自行控制滚动。也就是说我们要实现自己的Panel,并且实现IScrollInfo接口。关于这个接口,MSDN上有一系列文章介绍过如何实现它:

这个接口实现也不算麻烦,我倒没有细看这几篇文章,自己照着最后的一个例子尝试着弄了一阵子也弄出来了。实际上麻烦的地方不在于实现这个接口,而是实现Panel,我这里为了简单,直接继承了WrapPanel类,代码如下:

  1. class MyWrapPanel : WrapPanel, IScrollInfo
  2. {
  3. TranslateTransform _transForm;
  4. public MyWrapPanel()
  5. {
  6. _transForm = new TranslateTransform();
  7. this.RenderTransform = _transForm;
  8. }
  9.  
  10. #region Layout
  11.  
  12. Size _screenSize;
  13. Size _totalSize;
  14.  
  15. protected override Size MeasureOverride(Size availableSize)
  16. {
  17. _screenSize = availableSize;
  18.  
  19. if (Orientation == Orientation.Horizontal)
  20. availableSize = new Size(availableSize.Width, double.PositiveInfinity);
  21. else
  22. availableSize = new Size(double.PositiveInfinity, availableSize.Height);
  23.  
  24. _totalSize = base.MeasureOverride(availableSize);
  25. return _totalSize;
  26. }
  27.  
  28. protected override Size ArrangeOverride(Size finalSize)
  29. {
  30. var size = base.ArrangeOverride(finalSize);
  31. if (ScrollOwner != null)
  32. {
  33. _transForm.Y = -VerticalOffset;
  34. _transForm.X = -HorizontalOffset;
  35.  
  36. ScrollOwner.InvalidateScrollInfo();
  37. }
  38. return _screenSize;
  39. }
  40. #endregion
  41.  
  42. #region IScrollInfo
  43.  
  44. public ScrollViewer ScrollOwner { get; set; }
  45. public bool CanHorizontallyScroll { get; set; }
  46. public bool CanVerticallyScroll { get; set; }
  47.  
  48. public double ExtentHeight { get { return _totalSize.Height; } }
  49. public double ExtentWidth { get { return _totalSize.Width; } }
  50.  
  51. public double HorizontalOffset { get; private set; }
  52. public double VerticalOffset { get; private set; }
  53.  
  54. public double ViewportHeight { get { return _screenSize.Height; } }
  55. public double ViewportWidth { get { return _screenSize.Width; } }
  56.  
  57. void appendOffset(double x, double y)
  58. {
  59. var offset = new Vector(HorizontalOffset + x, VerticalOffset + y);
  60.  
  61. offset.Y = range(offset.Y, , _totalSize.Height - _screenSize.Height);
  62. offset.X = range(offset.X, , _totalSize.Width - _screenSize.Width);
  63.  
  64. HorizontalOffset = offset.X;
  65. VerticalOffset = offset.Y;
  66.  
  67. InvalidateArrange();
  68. }
  69.  
  70. double range(double value, double value1, double value2)
  71. {
  72. var min = Math.Min(value1, value2);
  73. var max = Math.Max(value1, value2);
  74.  
  75. value = Math.Max(value, min);
  76. value = Math.Min(value, max);
  77.  
  78. return value;
  79. }
  80.  
  81. const double _lineOffset = ;
  82. const double _wheelOffset = ;
  83.  
  84. public void LineDown()
  85. {
  86. appendOffset(, _lineOffset);
  87. }
  88.  
  89. public void LineUp()
  90. {
  91. appendOffset(, -_lineOffset);
  92. }
  93.  
  94. public void LineLeft()
  95. {
  96. appendOffset(-_lineOffset, );
  97. }
  98.  
  99. public void LineRight()
  100. {
  101. appendOffset(_lineOffset, );
  102. }
  103.  
  104. public Rect MakeVisible(Visual visual, Rect rectangle)
  105. {
  106. throw new NotSupportedException();
  107. }
  108.  
  109. public void MouseWheelDown()
  110. {
  111. appendOffset(, _wheelOffset);
  112. }
  113.  
  114. public void MouseWheelUp()
  115. {
  116. appendOffset(, -_wheelOffset);
  117. }
  118.  
  119. public void MouseWheelLeft()
  120. {
  121. appendOffset(, _wheelOffset);
  122. }
  123.  
  124. public void MouseWheelRight()
  125. {
  126. appendOffset(_wheelOffset, );
  127. }
  128.  
  129. public void PageDown()
  130. {
  131. appendOffset(, _screenSize.Height);
  132. }
  133.  
  134. public void PageUp()
  135. {
  136. appendOffset(, -_screenSize.Height);
  137. }
  138.  
  139. public void PageLeft()
  140. {
  141. appendOffset(-_screenSize.Width, );
  142. }
  143.  
  144. public void PageRight()
  145. {
  146. appendOffset(_screenSize.Width, );
  147. }
  148.  
  149. public void SetVerticalOffset(double offset)
  150. {
  151. this.appendOffset(HorizontalOffset, offset - VerticalOffset);
  152. }
  153.  
  154. public void SetHorizontalOffset(double offset)
  155. {
  156. this.appendOffset(offset - HorizontalOffset, VerticalOffset);
  157. }
  158. #endregion
  159. }

基本上从代码中也能看出IScrollInfo接口的交互流程,这里就不多介绍了。

主界面代码如下:

  1. <ItemsControl ItemsSource="{Binding}" >
  2. <ItemsControl.ItemTemplate>
  3. <DataTemplate>
  4. <Border BorderThickness="1" BorderBrush="Black" Margin="8" Width="150" Height="50">
  5. <Rectangle Fill="{Binding}" />
  6. </Border>
  7. </DataTemplate>
  8. </ItemsControl.ItemTemplate>
  9. <ItemsControl.ItemsPanel>
  10. <ItemsPanelTemplate>
  11. <local:MyWrapPanel />
  12. </ItemsPanelTemplate>
  13. </ItemsControl.ItemsPanel>
  14. <ItemsControl.Template>
  15. <ControlTemplate>
  16. <ScrollViewer CanContentScroll="True">
  17. <ItemsPresenter />
  18. </ScrollViewer>
  19. </ControlTemplate>
  20. </ItemsControl.Template>
  21. </ItemsControl>

需要注意的是,这儿需要设置<ScrollViewer CanContentScroll="True">,否则使用的不是逻辑滚动。

数据源代码如下:

  1. var brushes = from property in typeof(Brushes).GetProperties()
  2. let value = property.GetValue(null)
  3. select value;
  4. this.DataContext = brushes.Take().ToArray();

由于使用了IscrollInfo接口,所有的滚动操作是自己实现的,这里我是通过设置Panel的RenderTransFrom的X,Y偏移来实现滚动操作的。运行后看上去上和WrapPanel没有什么区别,但是由于是自己控制的滚动,加上动画效果也只是分分钟的事情了,把上面代码的RenderTransFrom的X,Y硬切换改成动画切换即可:

  1. protected override Size ArrangeOverride(Size finalSize)
  2. {
  3. var size = base.ArrangeOverride(finalSize);
  4. if (ScrollOwner != null)
  5. {
  6. var yOffsetAnimation = new DoubleAnimation() { To = -VerticalOffset, Duration = TimeSpan.FromSeconds(0.3) };
  7. _transForm.BeginAnimation(TranslateTransform.YProperty, yOffsetAnimation);
  8.  
  9. var xOffsetAnimation = new DoubleAnimation() { To = -HorizontalOffset, Duration = TimeSpan.FromSeconds(0.3) };
  10. _transForm.BeginAnimation(TranslateTransform.XProperty, xOffsetAnimation);
  11. ScrollOwner.InvalidateScrollInfo();
  12. }
  13. return _screenSize;
  14. }

对于其它的Panel,如Grid,DockPanel等,基本上也可以按照这种方式实现,IScrollInfo接口处基本上可以保持不变,只需要重写MeasureOverride和ArrangeOverride两个函数即可。一个特殊的控件是StackPanel,由于它本身已经实现了IScrollInfo接口,也就是说它本身就有自身的自绘制滚动的方案,并且没有提供接口在覆盖自身的自绘制滚动,因此我们需要自己写一个StackPanel,好在实现StackPanel并不难,由于篇幅有限,这里我懒得继续写了,读者朋友自己实现吧。至于那些非Panel的控件,实现就更简单了,也留着读者朋友自己实现吧。

在WPF中实现平滑滚动的更多相关文章

  1. 页面中的平滑滚动——smooth-scroll.js的使用

    正常的本页面锚链接跳转的时候跟PPT似的,特别生硬,用户体验非常差. 这时候我们就可以借助smooth-scroll.js这个插件,来实现本页面的平滑的跳转. 1首先,导入必须的JS文件 <sc ...

  2. 如何在pyqt中实现平滑滚动的QScrollArea

    平滑滚动的视觉效果 Qt 自带的 QScrollArea 滚动时只能在两个像素节点之间跳变,看起来很突兀.刚开始试着用 QPropertyAnimation 来实现平滑滚动,但是效果不太理想.所以直接 ...

  3. WPF中ListBox滚动时的缓动效果

    原文:WPF中ListBox滚动时的缓动效果 上周工作中遇到的问题: 常规的ListBox在滚动时总是一格格的移动,感觉上很生硬. 所以想要实现类似Flash中的那种缓动的效果,使ListBox滚动时 ...

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

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

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

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

  6. js平滑滚动到顶部,底部,指定地方

    [原文链接] 采用锚点进行页面中的跳转的确很方便,但是要想增加网页的效果,可以使用jquery中的animate,实现滚动的一个动作,慢慢的滚动到你想跳转到的位置,从而看起来会非常高大上. [示例演示 ...

  7. WPF中加载高分辨率图片性能优化

    在最近的项目中,遇到一个关于WPF中同时加载多张图片时,内存占用非常高的问题. 问题背景: 在一个ListView中同时加载多张图片,注意:我们需要加载的图片分辨率非常高. 代码: XAML: < ...

  8. 【转】使用jquery animate创建平滑滚动效果

    这篇文章主要介绍了使用jquery animate创建平滑滚动效果,效果可以滚动到顶部.到底部或页面中指定地方,生要的是非常平滑,很舒服,需要的朋友可以参考下 滚动到顶部: $('.scroll_to ...

  9. WPF中图形表示语法详解(Path之Data属性语法)ZZ

    大可山 [MSN:a3news(AT)hotmail.com] http://www.zpxp.com 萝卜鼠在线图形图像处理 ------------------------------------ ...

随机推荐

  1. [Leet code 2]Two Sum

    1 题目 You are given two linked lists representing two non-negative numbers. The digits are stored in ...

  2. C#如何在List里求某一列的數值的和SUM

    var X=Xlist.Sum(key => key.XXX);

  3. .NET 证书加密 存储保存 IIS授权

    最近接到一个任务,加密DotNet项目的配置文件.配置文件里需要加密的地方一共有两块,一个是数据库连接字符串,一个是自定义的所有AppSettings. 一开始接到这个任务我是拒绝的,因为压根不知道怎 ...

  4. [leetcode.com]算法题目 - Pascal's Triangle

    Given numRows, generate the first numRows of Pascal's triangle. For example, given numRows = 5,Retur ...

  5. Android-----application的学习

    一.Application的对象回调函数 1.onCreate : Application对象被创建时候会调用 2.onConfigurationChanged : 屏幕方向变化.系统语言的更改等 3 ...

  6. cad2020卸载/安装失败/如何彻底卸载清除干净cad2020注册表和文件的方法

    cad2020提示安装未完成,某些产品无法安装该怎样解决呢?一些朋友在win7或者win10系统下安装cad2020失败提示cad2020安装未完成,某些产品无法安装,也有时候想重新安装cad2020 ...

  7. zookeeper单机版安装

    ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用提供一致性服务的软件,提供的功 ...

  8. python学习笔记16-装饰器

    装饰器(函数) 1.函数作用域 2.高阶函数 把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式. 3.闭包  闭包就是能够读取其他函数内部变量的函数. 在本质上,闭包 ...

  9. 【tomcat】servlet原理及其生命周期

    1.什么是servlet? Servlet(Servlet Applet),全称Java Servlet,是用Java编写的服务器端程序.而这些Servlet都要实现Servlet这个接口.其主要功能 ...

  10. Numpy.random中shuffle与permutation的区别(转)

    huffle与permutation的区别 函数shuffle与permutation都是对原来的数组进行重新洗牌(即随机打乱原来的元素顺序):区别在于shuffle直接在原来的数组上进行操作,改变原 ...