本篇是上一篇自绘的补充,但需要一定的WPF相关知识,感谢Clingingboy 通宵达旦的帮助。
一.ScrollViewer
在前一篇我们做了一个可拖动的矩形,但你是否发现当矩形拖出背景后就不见了,一般来说对于不可见区域需要有ScrollBar来呈现,如图:
对于这一应用在WPF中最常用的应该在控件外面包个ScrollViewer,那么如何使得我们的控件支持ScrollViewer呢?
首先我们来了解一下ScrollViewer基本原理
通过上图我们可以看到ScrollViewer是以Grid为容器组成的控件,其中主要包括ScrollContentPresenter,和两个ScrollBar,其中ScrollBar就是我们第一张图中看到那两条,它也是一个由多个控件组成的复合控件,在这里先略过ScrollBar;来看红色边框内的ScrollContentPresenter,可以看到我们的控件CustomerRender在ScrollContentPresenter内,那么我们控件呈现的位置必定是由它来控制的。
这个神秘的ScrollContentPresenter到底做了什么能让我们看到一部分内容呢?看下这张图就清楚了
我们可以知道ScrollContentPresenter实际对我们玩了一个遮罩效果,把我们的控件当作一个背景图,用ScrollBar来移动背景位置,在ScrollViewer外的控件可视部分统统被裁减掉了。只要继承UIElement的控件就可以重载GetLayoutClip方法来剪切区域。
protected override Geometry GetLayoutClip(Size layoutSlotSize) { return new RectangleGeometry(new Rect(base.RenderSize)); }
二.ArrangeOverride
ScrollContentPresenter又是如何控制子元素的坐标呢?重载ArrangeOverride函数便可,具体看代码注释
protected override Size ArrangeOverride(Size arrangeBounds) { //得到集合中的第一个元素 UIElement visualChild = this.GetVisualChild(0) as UIElement; //把子元素的左上角坐标定义到容器之外 Point point = new Point(-40, -50); if (visualChild != null) { Rect finalRect = new Rect(point, visualChild.DesiredSize); //设置元素坐标和大小 visualChild.Arrange(finalRect); } return arrangeBounds; }
这段代码中参数arrangeBounds是父容器传进的值,一般表示你可以有多大的利用空间,这个函数的返回值一般指的是你控件RenderSize的大小.
RenderSize有什么用?(欢迎大家补充)
- 在onRender里可以用,比如画背景。
- 在MeasureOverride函数中当参数值为无限大时用来得知可用空间的大小。
三.MeasureOverride
visualChild.DesiredSize的值实际就是我们常用的ActualHeight和ActualWidth的源头,也就是控件的实际大小,我们可以重载MeasureOverride产生。下面是我们的自定义控件用的。
protected override Size MeasureOverride(Size constraint) { Size size = new Size ( //判断形参constraint中传的值大,还是我们的Rectangle的值大,以最大的那个作为控件的长宽 Math.Max(double.IsInfinity(constraint.Width) ? this.RenderSize.Width : constraint.Width, _preivewRectangle.Right), Math.Max(double.IsInfinity(constraint.Height) ? this.RenderSize.Height : constraint.Height, _preivewRectangle.Bottom)); return size; }
要说明下的是外容器的大小并不会触发MeasureOverride(如把窗体拖大),只会触发ArrangeOverride,如果你要重新为DesiredSize赋值并通知父容器请使用Measure函数,它会调用父控件的OnChildDesiredSizeChanged方法来通知,同理父控件要监听子控件的大小变化只要重载该方法即可,这个方法可以一直沿着可视树向上引发InvalidateMeasure函数,InvalidateMeasure通过DispatcherPriority为Render来异步调用Measure。
ArrangeOverride中尽量不要调用本身的Measure,Measure函数会再次调用InvalidateArrange方法从而引起循环。控件容器放生变化时可以重载OnRenderSizeChanged实现。
Measure和Arrange的具体关系如下图:
四.补充
如果想要自定义的ScrollBar你可以能要根绝ScrollBar的值实时进行重绘图,这个好处是数据量大,你只需呈现当前画面中的图形,缺点是动一动就要重绘。通过例如ScrollViewer裁减的方式,遮罩得时候不会重绘,不过刚开始呈现的时候数据量大会慢。
另外在复合控件中配合Transform中的各种类来进行布局,使用CompositionTarget和动画类可以产生很多效果。
转载请注明