WPF自定义控件 —— 布局

原文: WPF自定义控件 —— 布局

本篇是上一篇自绘的补充,但需要一定的WPF相关知识,感谢Clingingboy 通宵达旦的帮助。

一.ScrollViewer

在前一篇我们做了一个可拖动的矩形,但你是否发现当矩形拖出背景后就不见了,一般来说对于不可见区域需要有ScrollBar来呈现,如图:

clip_image002

对于这一应用在WPF中最常用的应该在控件外面包个ScrollViewer,那么如何使得我们的控件支持ScrollViewer呢?

首先我们来了解一下ScrollViewer基本原理

clip_image004

扫描二维码关注公众号,回复: 11170693 查看本文章

通过上图我们可以看到ScrollViewer是以Grid为容器组成的控件,其中主要包括ScrollContentPresenter,和两个ScrollBar,其中ScrollBar就是我们第一张图中看到那两条,它也是一个由多个控件组成的复合控件,在这里先略过ScrollBar;来看红色边框内的ScrollContentPresenter,可以看到我们的控件CustomerRender在ScrollContentPresenter内,那么我们控件呈现的位置必定是由它来控制的。

这个神秘的ScrollContentPresenter到底做了什么能让我们看到一部分内容呢?看下这张图就清楚了

clip_image006

我们可以知道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有什么用?(欢迎大家补充)

  1. 在onRender里可以用,比如画背景。
  2. 在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的具体关系如下图:

 image

四.补充

     如果想要自定义的ScrollBar你可以能要根绝ScrollBar的值实时进行重绘图,这个好处是数据量大,你只需呈现当前画面中的图形,缺点是动一动就要重绘。通过例如ScrollViewer裁减的方式,遮罩得时候不会重绘,不过刚开始呈现的时候数据量大会慢。

    另外在复合控件中配合Transform中的各种类来进行布局,使用CompositionTarget和动画类可以产生很多效果。

本例下载

转载请注明

猜你喜欢

转载自www.cnblogs.com/lonelyxmas/p/12819986.html