带有惯性的ScrollViewer

图片 5

  调整ScrollViewer的垂直滚动能够选取 ScrollViewer.ScrollToVerticalOffset ,横向也相仿。为啥不可能用 VerticalOffset ?因为 VerticalOffset 在登记的时候就注解了是只读的:

图片 1

二、原理

  标题1:鼠标中轮不可能使ScrollViewer上下滚动

1 protected override void OnMouseWheel(MouseWheelEventArgs e)
2 {
3      e.Handled = true;
4 }

  为啥把那做为单独的大器晚成环来谈谈吗?因为前面的代码已经能够完毕宗旨的职业了,并且现身的标题提到也并不是比一点都不小。不过总会不爽,因为它就不那么完美,所以,Fix
It!

  因为周边的惯性滚动以垂直方向居多,所以作者平昔不写水平方向的逻辑,但也十分轻巧增添,有意思味的博友能够下载源代码自身研讨。

  在协会前台分界面时,首先,定义了一个Grid做为容器,并把它分为了四份,分别是内容、竖向滚动条、横向滚动条、空白。此中,内容位于0行、0列,使用ScrollContentPresenter来表示就要展现的剧情;竖向滚动条位于0行1列,使用ScrollBar来代表;横向滚动条位于1行0列,使用横向(Orientation=”Horizontal”)的ScrollBar来代表。

三、源码

 

生机勃勃、先看看效果

 1         /// <summary>
 2         /// 滚动框背景
 3         /// </summary>
 4         public Brush ScrollViewerBackground
 5         {
 6             get { return (Brush)GetValue(ScrollViewerBackgroundProperty); }
 7             set { SetValue(ScrollViewerBackgroundProperty, value); }
 8         }
 9         public static readonly DependencyProperty ScrollViewerBackgroundProperty =
10             DependencyProperty.Register("ScrollViewerBackground", typeof(Brush), typeof(MessageTextBox), new PropertyMetadata(Brushes.LightBlue));
11 
12         /// <summary>
13         /// 滚动条前景
14         /// </summary>
15         public Brush ScrollBarForeground
16         {
17             get { return (Brush)GetValue(ScrollBarForegroundProperty); }
18             set { SetValue(ScrollBarForegroundProperty, value); }
19         }
20         public static readonly DependencyProperty ScrollBarForegroundProperty =
21             DependencyProperty.Register("ScrollBarForeground", typeof(Brush), typeof(MessageTextBox), new PropertyMetadata(Brushes.RoyalBlue));
22 
23         /// <summary>
24         /// 滚动条背景
25         /// </summary>
26         public Brush ScrollBarBackground
27         {
28             get { return (Brush)GetValue(ScrollBarBackgroundProperty); }
29             set { SetValue(ScrollBarBackgroundProperty, value); }
30         }
31         public static readonly DependencyProperty ScrollBarBackgroundProperty =
32             DependencyProperty.Register("ScrollBarBackground", typeof(Brush), typeof(MessageTextBox), new PropertyMetadata(Brushes.WhiteSmoke));

图片 2

  首先,给前台的最上层成分TextBox增多SelectionChanged=”TextBox_SelectionChanged”事件,以跟踪选中时鼠标所在地方:

  本质上大家只要接管ScrollViewer的轮转逻辑,并且把这几个逻辑替换到带有惯性的就能够,那么怎样去接管呢?这里的主要性是先屏蔽ScrollViewer的鼠标滚轮事件:

  便能够周详肃清鼠标中轮滚动难点。

 

   日常的话,大家在任何文字相关软件上,举例记事本、网页等,只要鼠标左键按下拖动选汉语本,假若鼠标超过文本框可突显范围,便会自动向鼠标所在方向滚动文本内容,以实现跨页选中的职能。不过与主题素材1等同,由于改换了ScrollViewer的Template,导致这一个通用成效也亟需团结完成了。

 

  然后,本身达成中轮滚动方法,为ScrollViewer增添MouseWheel=”PART_ContentHost_MouseWheel”事件,增添后台响应代码:

 

 1     /// <summary>
 2         /// 消息体滚动框
 3         /// </summary>
 4         public ScrollViewer ScrollViewer { get; set; }
 5     
 6     // 初始化滚动条
 7         private void PART_ContentHost_Initialized(object sender, EventArgs e)
 8         {
 9             this.ScrollViewer = sender as ScrollViewer;
10         }

  好了,接下去正是怎么在滚轮响应措施中落到实处惯性运动了,也正是风度翩翩种减速运动。想到此时,熟谙动画的博友异常快就知晓要用WPF的卡通片来贯彻了,暗许的动画片都以三回线性的,要有惯性功能就得用缓动函数,WPF的缓动函数有大多,而 CubicEase 特别符合用来做惯性,它的描述图如下:

  对应的后台依赖属性:

  尽管效果很简短,然则英特网的片段材质涉及的代码量非常可观,并且意义亦不是特不错,滚动的时候未有三个顺滑感。笔者那边提供的源码大器晚成共120多行,就会促成上海教室的信守。

图片 3图片 4音信框基础C#

  图中,横轴表示时间,纵轴表示运动间距。很刚烈,中间的 EaseOut 格局便是大家想要的。到了此处思路就清楚了,我们得以定义叁性情能 CurrentVerticalOffset ,大家会在它上边达成动画,在它的值回调函数中调用 ScrollViewer.ScrollToVerticalOffset 来更新ScrollViewer的轮转地方。当然大家还索要二个民用字段 _totalVerticalOffset ,这么些是用来寄存ScrollViewer滚动指标地点的,滚轮向下滚动一个单位我们就给它减去一遍 e.Delta ,这里的e是滚轮响应措施传进来的参数,每回给它赋值之后,就能够在 CurrentVerticalOffset 上实践动画了: BeginAnimation(CurrentVerticalOffsetProperty,
animation) ,须求特别注意的是,当二个借助属性用了动画片改造后,再对其赋值则不会卓有功用,原因是在七个动画片达到活动期的极限后,时间线暗中认可会保持其速度,直到其父级的活动期和保持期甘休停止。要是想在动画结束后还足以手动改善正视属性的值,则供给把 FillBehavior 设置为Stop。也就这样又会并发三个标题,生龙活虎旦动画结束,那么些借助属性又会恢复生机初步值,所以还要给那几个动画订阅贰个 Completed 事件,在事变响应措施中为 CurrentVerticalOffset 给定目的值,也正是 _totalVerticalOffset 。

 1     // 竖向
 2     private void ScrollYMethod()
 3         {
 4             double endOffset = 0;
 5             if (_ScrollY < 0)       // 向上滚动
 6                 endOffset = 0;
 7             else                    // 向下滚动
 8                 ScrollViewer.Dispatcher.Invoke((Action)(() => endOffset = ScrollViewer.ScrollableHeight), null);
 9             // 初始位置
10             double offset = 0;
11             ScrollViewer.Dispatcher.Invoke((Action)(() => offset = ScrollViewer.VerticalOffset), null);
12             // 开始滚动
13             while (offset != endOffset && _ScrollY != 0)
14             {
15                 ScrollViewer.Dispatcher.Invoke((Action)(() =>
16                 {
17                     offset = ScrollViewer.VerticalOffset;
18                     ScrollViewer.ScrollToVerticalOffset(ScrollViewer.VerticalOffset + _ScrollY);
19                 }), null);
20                 Thread.Sleep(100);
21             }
22         }
23 
24     // 横向
25     private void ScrollXMethod()
26         {
27             double endOffset = 0;
28             if (_ScrollX < 0)       // 向左滚动
29                 endOffset = 0;
30             else                    // 向右滚动
31                 ScrollViewer.Dispatcher.Invoke((Action)(() => endOffset = ScrollViewer.ScrollableWidth), null);
32             // 初始位置
33             double offset = 0;
34             ScrollViewer.Dispatcher.Invoke((Action)(() => offset = ScrollViewer.HorizontalOffset), null);
35             // 开始滚动
36             while (offset != endOffset && _ScrollX != 0)
37             {
38                 ScrollViewer.Dispatcher.Invoke((Action)(() =>
39                 {
40                     offset = ScrollViewer.HorizontalOffset;
41                     ScrollViewer.ScrollToHorizontalOffset(ScrollViewer.HorizontalOffset + _ScrollX);
42                 }), null);
43                 Thread.Sleep(100);
44             }
45         }

  最终还或许有一个矛盾难题,当手动拖动滑块也许当用上下文菜单退换滚动条地点时是不能够用动画的,因为此时没有触发 OnMouseWheel ,没提到,那正是大家想要的,然而假使重新触发 OnMouseWheel 就不通常了,因为手动触发滚动的时候大家从未给 CurrentVerticalOffset 和 _totalVerticalOffset 赋值( CurrentVerticalOffset 和 _totalVerticalOffset 只在 OnMouseWheel 中赋值),所以在用动画实践滚动操作前要先推断一下是还是不是需求先更新一下它们俩,怎么样判别?大家能够用多少个私有字段 _isRunning 来维护状态,每当动画开头就给它赋值true,甘休则赋值false。那样一来,当 _isRunning = false 时,表达在调用 OnMouseWheel 前,动画已经终止,客户恐怕早已手动改变了滚动条地点(也可能未有,但这并不影响),所以将要给前面俩弟兄更新一下值了。

 1     // 坚向位移
 2         private double _ScrollY
 3         {
 4             get { return _scrollY; }
 5             set
 6             {
 7                 _scrollY = value;
 8                 // 开启滚动
 9                 if (_scrollY != 0 && (_ScrollYResult == null || _ScrollYResult.IsCompleted))
10                     _ScrollYResult = _ScrollYAction.BeginInvoke(null, null);
11             }
12         }
13         private double _scrollY;
14 
15         // 横向位移
16         private double _ScrollX
17         {
18             get { return _scrollX; }
19             set
20             {
21                 _scrollX = value;
22                 // 开启滚动
23                 if (_scrollX != 0 && (_ScrollXResult == null || _ScrollXResult.IsCompleted))
24                     _ScrollXResult = _ScrollXAction.BeginInvoke(null, null);
25             }
26         }
27         private double _scrollX;
28 
29     // 竖向滚动
30         private Action _ScrollYAction;
31         private IAsyncResult _ScrollYResult;
32     
33         // 横向滚动
34         private Action _ScrollXAction;
35         private IAsyncResult _ScrollXResult;
1 protected override void OnMouseWheel(MouseWheelEventArgs e)
2 {
3     if (!IsEnableInertia)
4     {
5         base.OnMouseWheel(e);
6         return;
7     }
8     e.Handled = true;
9 }    

  滚动条是利用的Track控件,它又带有多个区域,分别是空间白、滑块、下空白,大家来看个示例图:

  那样一来,ScrollViewer就不会响应滚轮事件了,我们就在这间做小说。首先我们给那些ScrollViewer加多贰性子能 IsEnableInertia ,用来决定是还是不是使用惯性,因为萝卜不结球大白菜各有所好,不要想着强制全数人使用惯性,所以滚轮响应措施成为:

  此时的意义如图所示:图片 5  看起来还不易啊,右上角的关闭开关由于截图原因不是很清晰,稍后大家得以看出完好版的要好一些。

 

  后台代码此时也特轻易,只是简短地继续了TextBox控件:

图片 6

  寒假过完,在家真心什么都做不了,只怕老了,再想早前那样能一心坐下来已经极度了。回来第风流倜傥件事正是改了品种的贰个bug,近日又新添了二个新的作用,为顺序增加了二个音讯栏。音信栏有过多款式,供给是一个没有必要历史记录,可以用鼠标选中国国投息内容的音讯栏。我先是想到的正是TextBox,笔者个人比较欣赏赏心悦指标,有一点性冷淡,所以必得把TextBox中的ScrollViewer给改写了,行吗,最早。

  本文所切磋的控件源码已经在github开源:

图片 7图片 8消息框基础XAML

1 private static readonly DependencyPropertyKey VerticalOffsetPropertyKey = DependencyProperty.RegisterReadOnly(nameof (VerticalOffset), typeof (double), typeof (ScrollViewer), (PropertyMetadata) new FrameworkPropertyMetadata((object) 0.0));
2 
3 public static readonly DependencyProperty VerticalOffsetProperty = ScrollViewer.VerticalOffsetPropertyKey.DependencyProperty;

图片 9图片 10滚动函数体

  也便是说,在_ScrollX和_ScrollY更新的时候,程序会举行二次决断,假设滚动量不为0,而且信托调用未有早前照旧已经停止的时候,就调用委托,开首次展览开滚动。

  解决措施:

   爆发那些主题材料的因由特别稀奇,如若不是更改ScrollViewer的Template来完全改善它,而是使用ScrollViewer.Resources来定义ScrollBar的Style则完全不会产生这种难题,可是那不能够使的改观各控件的抑扬顿挫和布局。

  Track的DecreaseRepeatButton便是空间白、Thumb则是滑块、IncreaseRepeatButton是下空白,分别对这两个控件进行体制自定义就能够改动其外观。须求表达的是竖向滚动条需求把Track的IsDirectionReversed属性设置为True,横向则设置为False,不然会现出大惊失色的现象(原因嘛,我们看属性名的意味就知道了)。

  private void PART_ContentHost_MouseWheel(object sender,
System.Windows.Input.MouseWheelEventArgs e)
  {
    ScrollViewer.ScrollToVerticalOffset(ScrollViewer.VerticalOffset

  二、改造ScrollViewer控件

发表评论

电子邮件地址不会被公开。 必填项已用*标注