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类,代码如下:

     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中实现平滑滚动的更多相关文章

  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. python36--将数据保存为excel

    #!/usr/bin/env python # -*- coding: utf-8 -*- import xlwt import os class ExcelHelper(object): @stat ...

  2. CAS Ticket票据:TGT、ST、PGT、PT、PGTIOU

    CAS的核心就是其Ticket,及其在Ticket之上的一系列处理操作.CAS的主要票据有TGT.ST.PGT.PGTIOU.PT,其中TGT.ST是CAS1.0协议中就有的票据,PGT.PGTIOU ...

  3. C# Winform模仿百度日历

    想写博客不知道从何处开始,就从回忆开始吧. 第一个就从自定义日历控件开始 产生背景: 大概2015年时候有个项目要用到日历,用默认日历展示给用户看,用户毫不客气都说界面太丑,最好做成像百度日历那样方便 ...

  4. dev gridview自动列宽和单元、行、checkbox选中和多选

    #region 自动列宽 for (int I = 0; I < gridView1.Columns.Count; I++) { this.gridView1.BestFitColumns(); ...

  5. C#复数类的总结

    复数是C#中没有的,不能直接调用的.但是我们可以通过封装,构造自己的复数形式.这里我自己封装了一个Complex类,也不知道写得如何.可能还有一些东西没有考虑. 不过这里包含了复数的基本晕算了了,包括 ...

  6. 【kuangbin专题】计算几何_凸包

    1.poj1113 Wall 题目:http://poj.org/problem?id=1113 题意:用一条线把若干个点包起来,并且线距离任何一个点的距离都不小于r.求这条线的最小距离是多少? 分析 ...

  7. MYSQL基础语法的使用

    喜欢的朋友可以关注下,粉丝也缺. MYSQL介绍 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品.MySQL 是最流行的关系型数据库管理系统之 ...

  8. spring + mybatis 存取clob

    存的时候会比较麻烦,需要使用select for update的方式更新数据,如果原来没有这一条数据,还需要先新增,新增的时候需要将clob字段存为oracle.sql.CLOB.empty_lob( ...

  9. Python中对矩阵的洗牌操作

    [code] import numpy as np # 创建随机交换的索引 permutation = list(np.random.permutation(3)) # 创建矩阵X,Y X = np. ...

  10. C# 多线程八之并行Linq(ParallelEnumerable)

    1.简介 关于并行Linq,Ms官方叫做并行语言集成(PLINQ)查询,其实本质就是Linq的多线程版本,常规的Linq是单线程的,也就是同步的过程处理完所有的查询.如果你的Linq查询足够简单,而且 ...