• WPF自定义控件 —— 布局


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

    一.ScrollViewer

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

    clip_image002

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

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

    clip_image004

    通过上图我们可以看到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和动画类可以产生很多效果。

    本例下载

    转载请注明

  • 相关阅读:
    编写一个函数,接受三个string参数,s,oldVal和newVal。使用迭代器及insert和erase函数将s中所有oldVal替换为newVal。测试你的程序,用他替换通用的简写形式,如,将“tho”,将“”“”
    输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
    oracle中 connect by prior 递归算法
    Java实现3DES加密--及ANSI X9.8 Format标准 PIN PAN获取PIN BlOCK
    XML预览
    Javascript跳转页面和打开新窗口等方法
    Eclipse读取含有extjs的项目文件时卡死或者编写ExtJS时卡
    Oracle-更新字段-一张表的字段更新另一张的表的字段
    Oracle-表被锁住
    03_Ext_Viewport_Window_Dialog
  • 原文地址:https://www.cnblogs.com/Curry/p/1437092.html
Copyright © 2020-2023  润新知