WPF:为什么需要Measure和Arrange两步

温习measure和arrange过程

首先uielement.measure的参数是可用的空间(size对象),这个空间通常代表着父控件留给你显示的可用空间,接着uielement内部的measurecore会被调用,该方法会去决定调用measureoverride的大小参数。具体过程是先判断大小属性值是否被显示设置,如果是的话,直接使用设置的值,这就是为什么如果你强行设置一个控件的width和height后,它总会保持设置的大小(当然如果超出了父控件的规划大小,wpf会根据iscliptobounds属性来决定是否裁剪它)。当然如果没有被显示设置,那么measurecore会根据可用大小和当前控件的属性(比如margin)来决定最终传入measureoverride的可用大小参数。

接着measureoverride被调用,这个可以被子控件改写,返回的只会影响uielement.desiredsize属性。

这个desiredsize是计算后的包括margin的结果,而且被三个条件所制约(优先级由上到下):

  • measure中传入的可用大小参数
  • 显示设置的属性大小
  • measureoverride返回的大小

让我们做一些示例:

<canvas width="30">
    <button width="300" height="50"/>
</canvas>

显然layoutslot大小为200*200。如果我们把textblock的width和height都设置成300,textblock的desiredsize还会是200*200,显然他被measure中的可用大小最先制约。

如果第一条限制通过的话,那么后续限制

 

此文来自: 马开东博客 转载请注明出处 网址:

会被应用,比如我们把textblock的大小设置成10*10,那么此时textblock的desiredsize会成为:110*100。此时desiredsize是控件显示设置的大小加上margin大小。10+50*2=110。

最后如果textblock的大小不被设置,那么desiredsize就会使measureoverride的结果加上margin,在我这里结果是:107.74*115.96。

arrange的过程则是决定uielement.rendersize属性,整个过程和measure类似但有不一样的地方。首先,传入arrange的参数是layoutslot的位置和大小,是rect对象。接着arrangecore做类似measurecore的工作去决定传入arrangeoverride大小参数,这个参数是size,这个size代表着最终需要显示的可用大小。

arrangeoverride返回的大小会影响rendersize,但是注意不同于measureoverride那样,arrangeoverride影响的rendersize不会受到arrange方法的参数的限制,也就是说rendersize只会受到显示设置的属性大小和arrangeoverride返回值这两个限制的制约。至于measureoverride和arrangeoverride的返回结果都要受到显示设置的属性大小影响,这个也是情理之中的。想象一下,如果你设置了元素的width和height但是最终他有可能不会按照规定大小去显示,那将是多么疯狂的一件事啊。

返回目录

现有控件对measure和arrange的使用

为什么要用measure和arrange两个过程?我们从现有控件的执行就已找到诸多答案。

canvas是基于坐标的控件容器,因此他不会因为自身的大小而限制子容器的大小。看这样

 

一个代码:

<canvas width="30">

    <button width="300" height="50"/>

</canvas>

vs设计器很给力的显示出这个复杂情况:

image

canvas只有30宽度,但是button则需要300,同时canvas不应该限制button大小。那么此时在measure阶段,canvas并没有把measureoverride中的父控件可用大小传给button,而是用double.positiveinfinity(代表无穷大)作为参数调用button的measure方法。然后button设置好了自己的desiredsize后,canvas在arrange过程中同样没有传入自己arrangeoverride的参数,而是直接把button的desiredsize作为button的arrange方法参数,这样满足button的任何大小要求。

类似的控件还有scrollviewer。

还有canvas的measureoverride总是返回0*0。也就是说如果你没有直接设置canvas的大小的话,他的desiredsize总会是0*0。因此如果你放在stackpanel中,他不会被显示,因为stackpanel会根据方向的不同只显示控件desiredsize(另一个方向无限制),显然0*0的desiredsize会使canvas不可见。如果你把canvas放在grid中,那么canvas的大小就是grid所给的大小,因为grid会向已有大小都交给子成员,如果你把canvas放在scrollviewer中,那么canvas也会无限大,因为scrollviewer本身就是无限大。

也可以通过改写measureoverride来创建一个desiredsize返回最小值的canvas,可以参考这篇文章:wpf:一个估量最小大小的canvas

还有textblock总会已单行的形式延伸,只有设置了textwrapping属性后,textblock才会根据父控件的可用空间限制来执行换行规划。

这些成果都是measure和arrange配合的结果。

当然绝大多数情况,如果你不是写自定义的panel控件的话,是不需要太多关心measure和arrange(这也使许多人都不是很了解measure和arrange)。同时wpf的诸多内置类型都有measureoverride和arrangeoverride的执行,可以参考这篇文章:

wpf中内置类型的measureoverride和arrangeoverride表现

比如control类型的执行都是对第一个visual child进行测量。那么我们可以直接创建一个类型继承自control,然后把一个visual加入到visualchildren中,不用改写measureoverride和arrangeoverride,一个显示另一个控件的自定义control就实现了:

class contentbutton : control
{
    button btn;
 
    public contentbutton()
    {
        btn = new button() { content = "mgen" };
        base.addvisualchild(btn);
    }
 
    protected override visual getvisualchild(int index)
    {
        return btn;
    }
 
    protected override int visualchildrencount
    {
        get
        {
            return 1;
        }
    }
}

把这个自定义控件放在stackpanel中,背后的button会直接显示出来:

image

返回目录

自定义控件演示measure和arrange

下面自定义一个控件演示有趣的measure和arrange。

我们创建一个在measureoverride中总返回50*50。然后在arrange中总返回100*100的控件:

class mycontrol : control
{
    protected override void onrender(drawingcontext drawingcontext)
    {
        base.onrender(drawingcontext);
        drawingcontext.drawrectangle(brushes.red, null, new rect(new point(), rendersize));
    }
 
    protected override size arrangeoverride(size arrangebounds)
    {
        base.arrangeoverride(arrangebounds);
        return new size(100, 100);
    }
 
    protected override size measureoverride(size constraint)
    {
        base.measureoverride(constraint);
        return new size(50, 50);
    }
}

在stackpanel中,控件会被显示成100*50的。因为横向stackpanel不做大小限制,所以desiredsize被应用,而纵向对大小进行限制,rendersize被应用。

在grid或者其他不限制大小的容器中,大小会是100*100的,同时控件不会根据可用控件的改变而放大或者缩小,因为arrangeoverride没有使用arrangebounds参数,而总是返回100*100的size。

还可以创建一个纵向的stackpanel,但是不尝试拉伸子成员的横向宽度,效果如下:

image

代码:

class mypanel : panel
{
    protected override size measureoverride(size availablesize)
    {
        var retsize = new size();
        foreach (uielement ui in internalchildren)
        {
            ui.measure(new size(availablesize.width, availablesize.height));
            retsize.height += ui.desiredsize.height;
            retsize.width = math.max(retsize.width, ui.desiredsize.width);
        }
        return retsize;
    }
 
    protected override size arrangeoverride(size finalsize)
    {
        var next = new point();
        foreach (uielement ui in internalchildren)
        {
            ui.arrange(new rect(next, ui.desiredsize));
            next.y += ui.rendersize.height;
 
        }
        return finalsize;
    }
}

此文来自: 马开东博客  网址:http://www.makaidong.com/博客园知识库/2698.shtml

猜你喜欢

转载自blog.csdn.net/qq_28368039/article/details/88572819