博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
在WPF中实现平滑滚动
阅读量:5982 次
发布时间:2019-06-20

本文共 7421 字,大约阅读时间需要 24 分钟。

原文:

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

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

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

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

物理滚动:

系统默认的滚动方案,控件本身啥都不用干,完全由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, 0, _totalSize.Height - _screenSize.Height); 62             offset.X = range(offset.X, 0, _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  82         const double _lineOffset = 30; 83         const double _wheelOffset = 90; 84  85         public void LineDown() 86         { 87             appendOffset(0, _lineOffset); 88         } 89  90         public void LineUp() 91         { 92             appendOffset(0, -_lineOffset); 93         } 94  95         public void LineLeft() 96         { 97             appendOffset(-_lineOffset, 0); 98         } 99 100         public void LineRight()101         {102             appendOffset(_lineOffset, 0);103         }104 105         public Rect MakeVisible(Visual visual, Rect rectangle)106         {107             throw new NotSupportedException();108         }109 110         public void MouseWheelDown()111         {112             appendOffset(0, _wheelOffset);113         }114 115         public void MouseWheelUp()116         {117             appendOffset(0, -_wheelOffset);118         }119 120         public void MouseWheelLeft()121         {122             appendOffset(0, _wheelOffset);123         }124 125         public void MouseWheelRight()126         {127             appendOffset(_wheelOffset, 0);128         }129 130         public void PageDown()131         {132             appendOffset(0, _screenSize.Height);133         }134 135         public void PageUp()136         {137             appendOffset(0, -_screenSize.Height);138         }139 140         public void PageLeft()141         {142             appendOffset(-_screenSize.Width, 0);143         }144 145         public void PageRight()146         {147             appendOffset(_screenSize.Width, 0);148         }149 150         public void SetVerticalOffset(double offset)151         {152             this.appendOffset(HorizontalOffset, offset - VerticalOffset);153         }154 155         public void SetHorizontalOffset(double offset)156         {157             this.appendOffset(offset - HorizontalOffset, VerticalOffset);158         }159         #endregion160     }
View Code

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

主界面代码如下: 

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

数据源代码如下:

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

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

protected override Size ArrangeOverride(Size finalSize)    {        var size = base.ArrangeOverride(finalSize);        if (ScrollOwner != null)        {            var yOffsetAnimation = new DoubleAnimation() { To = -VerticalOffset, Duration = TimeSpan.FromSeconds(0.3) };            _transForm.BeginAnimation(TranslateTransform.YProperty, yOffsetAnimation);            var xOffsetAnimation = new DoubleAnimation() { To = -HorizontalOffset, Duration = TimeSpan.FromSeconds(0.3) };            _transForm.BeginAnimation(TranslateTransform.XProperty, xOffsetAnimation);             ScrollOwner.InvalidateScrollInfo();        }        return _screenSize;    }

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

转载地址:http://xgrox.baihongyu.com/

你可能感兴趣的文章
我的友情链接
查看>>
sweetAlert弹窗组件
查看>>
Centos7_Minimal 静默安装 Oracle 12c版本
查看>>
Azure Stack技术深入浅出系列5:在Azure Stack上使用Web App PaaS服务及其背后原理窥探(开发案例)...
查看>>
struts 1.2 原理
查看>>
WINDOWS下双网卡做负载均衡 多网口做负载
查看>>
深入浅出Zabbix 3.0 -- 第十七章 扩展告警方式
查看>>
voting disk ocr
查看>>
菜鸟学Linux 第037篇笔记 脚本知识、实现任务计划
查看>>
java线程池
查看>>
Spring与SpringMVC整合时产生的两个上下文容器之间的关系
查看>>
阿里云ECS的一次简单试用
查看>>
软件加密授权工具比较
查看>>
基于CentOS 7的owncloud部署
查看>>
mysql limit 分页简单优化
查看>>
一、android常用adb命令
查看>>
获取Android应用程序出错信息并将信息发送到指定邮件
查看>>
备份目录的脚本
查看>>
Redhat安装系统后开机一直停在进度条处
查看>>
基于Cadence Virtuoso 设计平台的单片射频收发集成电路的设计过程
查看>>