在WPF中实现平滑滚动
WPF实现滚动条还是比较方便的,只要在控件外围加上ScrollViewer即可,但美中不足的是:滚动的时候没有动画效果。在滚动的时候添加过渡动画能给我们的软件增色不少,例如Office 2013的滚动的时候支持动画看起来就舒服多了。 之前倒是研究过如何实现这个平滑滚动,不过网上的方案大部分大多数如下:
- 通过VisualTree找到ScrollViewer
- 在ScrollChanged事件中添加动画
这种方案效果并不好,以为我们的滚动很多时候都是一口气滚动好几格滚轮的,这个时候上一个动画还没有结束,下一个动画就来了,反而还出现了卡顿的感觉,并且网上的一些算法大部分还都会导致偏移错位。
趁着这两天有点时间,就研究了一下ScorllViewer,从MSDN文档中看到,它是支持两种滚动方式的:
物理滚动:
系统默认的滚动方案,控件本身啥都不用干,完全由ScrollViewer来实现滚动。这种方式的好处是简单,但也正由于简单,控件本身完全感知不到ScorllViewer的存在,也就无法加以控制了。
逻辑滚动:
将这种方式需要设置ScrollViewer的CanContentScroll为"True"才能生效,同时需要控件实现IScrollInfo接口。此时ScrollViewer只是将滚动事件通过IScrollInfo接口传递给控件,由控件本身自己去实现滚动。同时从IScrollInfo接口中读取相关的属性更新滚动条界面。
也就是说,逻辑滚动才是我们所需要的方案。由于它要求控件实现IScrollInfo接口,自行控制滚动。也就是说我们要实现自己的Panel,并且实现IScrollInfo接口。关于这个接口,MSDN上有一系列文章介绍过如何实现它:
这个接口实现也不算麻烦,我倒没有细看这几篇文章,自己照着最后的一个例子尝试着弄了一阵子也弄出来了。实际上麻烦的地方不在于实现这个接口,而是实现Panel,我这里为了简单,直接继承了WrapPanel类,代码如下:
- class MyWrapPanel : WrapPanel, IScrollInfo
- {
- TranslateTransform _transForm;
- public MyWrapPanel()
- {
- _transForm = new TranslateTransform();
- this.RenderTransform = _transForm;
- }
- #region Layout
- Size _screenSize;
- Size _totalSize;
- protected override Size MeasureOverride(Size availableSize)
- {
- _screenSize = availableSize;
- if (Orientation == Orientation.Horizontal)
- availableSize = new Size(availableSize.Width, double.PositiveInfinity);
- else
- availableSize = new Size(double.PositiveInfinity, availableSize.Height);
- _totalSize = base.MeasureOverride(availableSize);
- return _totalSize;
- }
- protected override Size ArrangeOverride(Size finalSize)
- {
- var size = base.ArrangeOverride(finalSize);
- if (ScrollOwner != null)
- {
- _transForm.Y = -VerticalOffset;
- _transForm.X = -HorizontalOffset;
- ScrollOwner.InvalidateScrollInfo();
- }
- return _screenSize;
- }
- #endregion
- #region IScrollInfo
- public ScrollViewer ScrollOwner { get; set; }
- public bool CanHorizontallyScroll { get; set; }
- public bool CanVerticallyScroll { get; set; }
- public double ExtentHeight { get { return _totalSize.Height; } }
- public double ExtentWidth { get { return _totalSize.Width; } }
- public double HorizontalOffset { get; private set; }
- public double VerticalOffset { get; private set; }
- public double ViewportHeight { get { return _screenSize.Height; } }
- public double ViewportWidth { get { return _screenSize.Width; } }
- void appendOffset(double x, double y)
- {
- var offset = new Vector(HorizontalOffset + x, VerticalOffset + y);
- offset.Y = range(offset.Y, , _totalSize.Height - _screenSize.Height);
- offset.X = range(offset.X, , _totalSize.Width - _screenSize.Width);
- HorizontalOffset = offset.X;
- VerticalOffset = offset.Y;
- InvalidateArrange();
- }
- double range(double value, double value1, double value2)
- {
- var min = Math.Min(value1, value2);
- var max = Math.Max(value1, value2);
- value = Math.Max(value, min);
- value = Math.Min(value, max);
- return value;
- }
- const double _lineOffset = ;
- const double _wheelOffset = ;
- public void LineDown()
- {
- appendOffset(, _lineOffset);
- }
- public void LineUp()
- {
- appendOffset(, -_lineOffset);
- }
- public void LineLeft()
- {
- appendOffset(-_lineOffset, );
- }
- public void LineRight()
- {
- appendOffset(_lineOffset, );
- }
- public Rect MakeVisible(Visual visual, Rect rectangle)
- {
- throw new NotSupportedException();
- }
- public void MouseWheelDown()
- {
- appendOffset(, _wheelOffset);
- }
- public void MouseWheelUp()
- {
- appendOffset(, -_wheelOffset);
- }
- public void MouseWheelLeft()
- {
- appendOffset(, _wheelOffset);
- }
- public void MouseWheelRight()
- {
- appendOffset(_wheelOffset, );
- }
- public void PageDown()
- {
- appendOffset(, _screenSize.Height);
- }
- public void PageUp()
- {
- appendOffset(, -_screenSize.Height);
- }
- public void PageLeft()
- {
- appendOffset(-_screenSize.Width, );
- }
- public void PageRight()
- {
- appendOffset(_screenSize.Width, );
- }
- public void SetVerticalOffset(double offset)
- {
- this.appendOffset(HorizontalOffset, offset - VerticalOffset);
- }
- public void SetHorizontalOffset(double offset)
- {
- this.appendOffset(offset - HorizontalOffset, VerticalOffset);
- }
- #endregion
- }
基本上从代码中也能看出IScrollInfo接口的交互流程,这里就不多介绍了。
主界面代码如下:
- <ItemsControl ItemsSource="{Binding}" >
- <ItemsControl.ItemTemplate>
- <DataTemplate>
- <Border BorderThickness="1" BorderBrush="Black" Margin="8" Width="150" Height="50">
- <Rectangle Fill="{Binding}" />
- </Border>
- </DataTemplate>
- </ItemsControl.ItemTemplate>
- <ItemsControl.ItemsPanel>
- <ItemsPanelTemplate>
- <local:MyWrapPanel />
- </ItemsPanelTemplate>
- </ItemsControl.ItemsPanel>
- <ItemsControl.Template>
- <ControlTemplate>
- <ScrollViewer CanContentScroll="True">
- <ItemsPresenter />
- </ScrollViewer>
- </ControlTemplate>
- </ItemsControl.Template>
- </ItemsControl>
需要注意的是,这儿需要设置<ScrollViewer CanContentScroll="True">,否则使用的不是逻辑滚动。
数据源代码如下:
- var brushes = from property in typeof(Brushes).GetProperties()
- let value = property.GetValue(null)
- select value;
- this.DataContext = brushes.Take().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的控件,实现就更简单了,也留着读者朋友自己实现吧。
在WPF中实现平滑滚动的更多相关文章
- 页面中的平滑滚动——smooth-scroll.js的使用
正常的本页面锚链接跳转的时候跟PPT似的,特别生硬,用户体验非常差. 这时候我们就可以借助smooth-scroll.js这个插件,来实现本页面的平滑的跳转. 1首先,导入必须的JS文件 <sc ...
- 如何在pyqt中实现平滑滚动的QScrollArea
平滑滚动的视觉效果 Qt 自带的 QScrollArea 滚动时只能在两个像素节点之间跳变,看起来很突兀.刚开始试着用 QPropertyAnimation 来实现平滑滚动,但是效果不太理想.所以直接 ...
- WPF中ListBox滚动时的缓动效果
原文:WPF中ListBox滚动时的缓动效果 上周工作中遇到的问题: 常规的ListBox在滚动时总是一格格的移动,感觉上很生硬. 所以想要实现类似Flash中的那种缓动的效果,使ListBox滚动时 ...
- VS编程,WPF中两个滚动条 ScrollViewer 同步滚动的一种方法
原文:VS编程,WPF中两个滚动条 ScrollViewer 同步滚动的一种方法 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/ar ...
- WPF中获取TreeView以及ListView获取其本身滚动条的方法,可实现自行调节scoll滚动的位置(可相应获取任何控件中的内部滚动条)
原文:WPF中获取TreeView以及ListView获取其本身滚动条的方法,可实现自行调节scoll滚动的位置(可相应获取任何控件中的内部滚动条) 对于TreeView而言: TreeViewAut ...
- js平滑滚动到顶部,底部,指定地方
[原文链接] 采用锚点进行页面中的跳转的确很方便,但是要想增加网页的效果,可以使用jquery中的animate,实现滚动的一个动作,慢慢的滚动到你想跳转到的位置,从而看起来会非常高大上. [示例演示 ...
- WPF中加载高分辨率图片性能优化
在最近的项目中,遇到一个关于WPF中同时加载多张图片时,内存占用非常高的问题. 问题背景: 在一个ListView中同时加载多张图片,注意:我们需要加载的图片分辨率非常高. 代码: XAML: < ...
- 【转】使用jquery animate创建平滑滚动效果
这篇文章主要介绍了使用jquery animate创建平滑滚动效果,效果可以滚动到顶部.到底部或页面中指定地方,生要的是非常平滑,很舒服,需要的朋友可以参考下 滚动到顶部: $('.scroll_to ...
- WPF中图形表示语法详解(Path之Data属性语法)ZZ
大可山 [MSN:a3news(AT)hotmail.com] http://www.zpxp.com 萝卜鼠在线图形图像处理 ------------------------------------ ...
随机推荐
- [Leet code 2]Two Sum
1 题目 You are given two linked lists representing two non-negative numbers. The digits are stored in ...
- C#如何在List里求某一列的數值的和SUM
var X=Xlist.Sum(key => key.XXX);
- .NET 证书加密 存储保存 IIS授权
最近接到一个任务,加密DotNet项目的配置文件.配置文件里需要加密的地方一共有两块,一个是数据库连接字符串,一个是自定义的所有AppSettings. 一开始接到这个任务我是拒绝的,因为压根不知道怎 ...
- [leetcode.com]算法题目 - Pascal's Triangle
Given numRows, generate the first numRows of Pascal's triangle. For example, given numRows = 5,Retur ...
- Android-----application的学习
一.Application的对象回调函数 1.onCreate : Application对象被创建时候会调用 2.onConfigurationChanged : 屏幕方向变化.系统语言的更改等 3 ...
- cad2020卸载/安装失败/如何彻底卸载清除干净cad2020注册表和文件的方法
cad2020提示安装未完成,某些产品无法安装该怎样解决呢?一些朋友在win7或者win10系统下安装cad2020失败提示cad2020安装未完成,某些产品无法安装,也有时候想重新安装cad2020 ...
- zookeeper单机版安装
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用提供一致性服务的软件,提供的功 ...
- python学习笔记16-装饰器
装饰器(函数) 1.函数作用域 2.高阶函数 把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式. 3.闭包 闭包就是能够读取其他函数内部变量的函数. 在本质上,闭包 ...
- 【tomcat】servlet原理及其生命周期
1.什么是servlet? Servlet(Servlet Applet),全称Java Servlet,是用Java编写的服务器端程序.而这些Servlet都要实现Servlet这个接口.其主要功能 ...
- Numpy.random中shuffle与permutation的区别(转)
huffle与permutation的区别 函数shuffle与permutation都是对原来的数组进行重新洗牌(即随机打乱原来的元素顺序):区别在于shuffle直接在原来的数组上进行操作,改变原 ...