前言

在WPF/Silverlight当中,如果已经存在的Element无法满足你特殊的需求,你可能想自定义Element,那么就有可能会面临重写MeasureOverride和ArrangeOverride两个方法,而这两个方法是WPF/SL的Layout系统提供给用户的自定义接口,因此,理解Layout系统的工作机制,对自定义Element是非常有必要的。那么,究竟WPF/SL的Layout系统是怎么工作的呢?接下来,我简单的描述一下,然后,在后面的章节具体分析。

简单来说,WPF的Layout系统是一个递归系统,他有两个子过程,总是以调用父元素的Measure方法开始,以调用Ararnge方法结束,而进入每个子过程之后,父元素又会调用孩子元素的Measure,完成后,又调用孩子元素的Arrange方法,这样一直递归下去。而对两个子过程的一次调用,可以看作是一次会话,可以理解为下图所示:

这个会话可以用下面一段话描述:

子过程1: 父根据自己的策略给孩子一个availableSize,并发起对话,通过调用孩子的Measure(availableSize)方法,询问孩子:你想要多大的空间显示自己?孩子接到询问后,根据父给的availableSize以及自己的一些限制,比如Margin,Width,等等,孩子回答:我想要XXX大小的空间。父拿到孩子给的期望的空间大小后,根据自己的策略开始真正给孩子分配空间,就进入第二个子过程。

子过程2: 父拿到孩子的期望空间后,再根据自己的情况,决定给孩子分配finalRect大小的矩形区域,然后他发起对话,调用孩子的Arrange(finalRect)给孩子说:我给你了finalRect这么大的空间。孩子拿到这个大小后,会去布置它的内容,并且布置完成后,会告诉父:其实我用了XXX大小的空间来绘制我自己的内容。父知道后,什么也没说,还是按照分配给他的finalRect去安置孩子,如果孩子最终绘制的区域大于这个区域,就被父裁剪了。Layout过程完成。

通过上面两个子过程的理解,或多或少对WPF的Layout系统有个初步的了解,接下来的章节,我具体描述Measure过程和Arrange过程具体做了哪些事情,帮助你跟深入的理解Layout系统。

预设条件 

通过下面的一个预设场景,我们来展开Layout系统的讲解。

假定:我们需要自定义一个Panel,类型为 *MyPanel* ,MyPanel的父为 *MyPanelParent* ,也是一个Panel;MyPanel的孩子为 *MyPanelChild* ,也是一个Panel。

切入点1:重写MyPanelParent的MeasureOverride()和ArrangeOverride(),研究父如何影响孩子MyPanel的Layout;

切入点2:重写MyPanel.MeasureOverride()和ArrangeOverride方法,研究自身有哪些属性影响MyPanel的Layout,以及重写这两个方法时应该注意的点;

注意:后面的研究,我只基于Element的Width,也就是水平方向的维度,所有的数据都是只设置水平方向的,垂直方向设置的跟水平方向一致,但不做描述。

Measure过程概述 

1. 普通基类属性对Measure过程的影响

请看下面的一些设置:

<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="522" Width="594" Loaded="Window_Loaded" xmlns:my="clr-namespace:WpfApplication1">
<Canvas>
<my:MyPanelParent x:Name="myPanelParent1" Height="400" Width="400" Background="Green" Canvas.Left="10" Canvas.Top="10">
<my:MyPanel Margin="10" x:Name="myPanel1" Background="Red" MinWidth="150" Width="200" MaxWidth="250"/>
<my:MyPanel Margin="10" x:Name="myPanel2" Background="Red" MinWidth="150" Width="200" MaxWidth="250"/>
</my:MyPanelParent>
</Canvas>
</Window>
public class MyPanelParent:Panel
{
protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
{
foreach (UIElement item in this.InternalChildren)
{
item.Measure(new Size(120, 120));//这里是入口
} return availableSize;
} protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
{
double x = 0;
foreach (UIElement item in this.InternalChildren)
{
item.Arrange(new Rect(x, 0, item.DesiredSize.Width, item.DesiredSize.Height));
x += item.DesiredSize.Width;
} return finalSize;
}
} public class MyPanel : Panel
{
protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
{
foreach (UIElement item in this.InternalChildren)
{
item.Measure(availableSize);
}
return new Size(50, 50);//MyPanel 返回它期望的大小
} protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
{
double xCordinate = 0;
foreach (UIElement item in this.InternalChildren)
{
item.Arrange(new Rect(new Point(xCordinate, 0), item.DesiredSize));
xCordinate += item.DesiredSize.Width;
}
return finalSize;
}
}

在上面的设置之后,应用程序运行起来之后,Window的表现为:

分析一下设置:

MyPanel1.Width = 200, MyPanel1.MinWidth = 150, MyPanel1.MaxWidth = 250, MyPanel1.Margin = Thickness(10)

MyPanel1.Measure()传入的参数为120*120,MyPanel1.MeasureOverride返回的参数为50*50

分析一下结果:

MyPanel1实际的画出来的大小(红色部分)是100*50

从结果可以看出,红色的部分受多个因素的影响,有人要问,我已经设置了MyPanel.Width=200,可是怎么画出来的Width却是100;MyPanel.Height没设置,可是画出来的却是50,为什么不是其他值。接下来我通过Measure的流程图说明一下这个结果是怎么来的:

看了上图,有些人可能会看出一些端倪,也可能还不是很清晰,我按照自己的理解总结一下Measure过程究竟想干什么?

1. 第一点很清晰,MyPanelParent调用MyPanel.Measure的过程是想得到MyPanel.DesiredSize,MyPanelParent需要在Arrange孩子MyPanel时,参考孩子的DesiredSize,决定将孩子MyPanel安置多大的空间。

2. MyPanel.DesiredSize是包含Margin以及内容的大小空间

3. MyPanel.MeasureOverride传入的参数constrainedSize,是基类的实现刨去Margin的大小,然后按照MyPanel对MinWidth,MaxWidth,Width的设置计算的一个MyPanel想要的值,我们自定义时在MeasureOverride当中不需要关心自己的Margin,以及其他基类上影响Layout的属性,只要考虑在给定参数的范围类安排自己的内容区域;MyPanel.MinWidth,Width, MaxWidth的设定都是针对内容区域的,不含Margin部分

4. 如果不设定Width,那么可以在MeasureOverride返回的时候返回一个期望的内容区域大小,它会被MinWidth和MaxWidth再调整一下,调整后,还有待于MyPanelParent的衡量(旁白:别瞎折腾,也别玩Layout系统,都设置MinWidth,MaxWidth,就乖乖的呆在这个范围内。)

5. 不论MyPanel怎么设置自己的Width,MinWidth,MaxWidth,以及在MeasureOverride返回一个大小,来表明自己期望多大的空间显示自己的内容,但这些都仅仅是期望的,期望是美好的,现实是残酷的,这一切还必须限定在MyPanel.Measure开始时传入的参数availableSize刨去MyPanel.Margin后的范围内,小于这个范围就满足,大于这个范围就被裁断。(可怜呀,总是受制于父)

6. 影响Measure过程的参数和属性存在一个优先级的,大概如下所示:

Measure方法参数availableSize>MinWidth,Width,MaxWidth > MeasureOverride返回值

2. Transform对Measure过程的影响

通过上面的过程,我们已经大概了解了Measure过程的工作方式,以及各个属性是如何影响的。但是还有一个属性我们没有提及,但它对Measure的过程也影响甚大,这就是LayoutTransform。通过下面的两段分析,你会看到这个属性的具体表现。

设置1:

<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="522" Width="594" Loaded="Window_Loaded" xmlns:my="clr-namespace:WpfApplication1">
<Canvas>
<my:MyPanelParent x:Name="myPanelParent1" Height="400" Width="400" Background="Lime" Canvas.Left="10" Canvas.Top="10">
<my:MyPanel Margin="10" x:Name="myPanel1" Background="Red" Width="200">
<my:MyPanel.LayoutTransform>
<RotateTransform Angle="90"/>
</my:MyPanel.LayoutTransform>
</my:MyPanel>
<my:MyPanel Margin="10" x:Name="myPanel2" Background="Red" MinWidth="150" MaxWidth="250"/>
</my:MyPanelParent>
</Canvas>
</Window>
    public class MyPanelParent:Panel
{
protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
{
foreach (UIElement item in this.InternalChildren)
{
item.Measure(new Size(1000, 800));
} return availableSize;
} protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
{
double x = 0;
foreach (UIElement item in this.InternalChildren)
{
item.Arrange(new Rect(x, 0, item.DesiredSize.Width, item.DesiredSize.Height));
x += item.DesiredSize.Width;
} return finalSize;
}
 
    public class MyPanel : Panel
{
protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
{
foreach (UIElement item in this.InternalChildren)
{
item.Measure(availableSize);
}
return new Size(80, 50);
} protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
{
double xCordinate = 0;
foreach (UIElement item in this.InternalChildren)
{
item.Arrange(new Rect(new Point(xCordinate, 0), item.DesiredSize));
xCordinate += item.DesiredSize.Width;
}
return finalSize;
}
}

运行的表现为:

分析一下设置:

MyPanel1.LayoutTransform = new RotateTransform(90)//旋转了90度

MyPanel1.Width = 200

MyPanel1.Margin = Thickness(10)

MyPanel1.Measure()传入的参数为1000*800,MyPanel1.MeasureOverride返回的参数为80*50.

分析一下结果:

MyPanel1实际的画出来的大小是50×200,明显是被旋转了90度。

运行起来,你会发现最终的MyPanel1.DesiredSize在Measure过程之后为70×220,也就是说,它是被Transform之后的大小,明显是被旋转过的。另外,观察MyPanel.MeasureOverride传入的参数,为200×980,根据上一节对Measure过程的分析,MeasureOverride传入的参数宽为200是可预知的,因为我们设置了MyPanel1.Width为200,但Height为980,明显是MyPanel.Measure传入的宽1000减去2*10等于980,看来在进入MeasureOverride之前,Layout系统也处理了LayoutTransform对Measure过程的影响,它希望MeasureOverride不要关心自身LayoutTransform的影响。MeasureOverride结束后,返回值为80×50,根据上一节对Measure过程的分析,宽为80被调节为符合自己的设置,为200,由于高没有设置,这个50肯定会保留,因此最后在没有Transform之前的DesiredSize应该是220×70,然而基类会将MeasureOverride返回的大小再进行一次Transform,达到最终的DesiredSize的大小,以便Arrange的时候分配合适的空间来容纳MyPanel的大小。

如果你将上面例子的MyPanel1.LayoutTransform设置成ScaleTransform:

<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="522" Width="594" Loaded="Window_Loaded" xmlns:my="clr-namespace:WpfApplication1">
<Canvas>
<my:MyPanelParent x:Name="myPanelParent1" Height="400" Width="400" Background="Lime" Canvas.Left="10" Canvas.Top="10">
<my:MyPanel Margin="10" x:Name="myPanel1" Background="Red" Width="200">
<my:MyPanel.LayoutTransform>
<ScaleTransform ScaleX="2" ScaleY="2"/>
</my:MyPanel.LayoutTransform>
</my:MyPanel>
<my:MyPanel Margin="10" x:Name="myPanel2" Background="Red" MinWidth="150" MaxWidth="250"/>
</my:MyPanelParent>
</Canvas>
</Window>

然后再观察myPanel.MeasureOverride传入的参数,为200×390,首先200是可预知的,因为设置了Width属性,而390是怎么回事呢,其实为Measure传入的1000×800的高800减去Margin为20后得到780,然后根据LayoutTransform将高缩小2倍之后得到的390,因此传入的参数就是200×390,可见,Layout系统,在进入MeasureOverride之前,他希望,MeasureOverride只关心内容怎么布置,而不需要关心基类属性的设置对MeasureOverride的影响。由于MeasureOverride的返回值依然是80×50,可推理,80被调节为200,50被保留,没有Transform之前的值应该是200×50。因为基类还要进行Transform,因此,内容区域的真实的大小应该是400×100,再加上Margin之后,最终的DesiredSize肯定为420*120,你可以尝试调试给出的代码。

 

3. Measure过程的总结

Measure过程的总结

通过上面的过程分析,我相信你或多或少对WPF的Layout系统的Measure过程有了更进一步的了解,其实还有一些因素影响Measure的过程,比如UseLayoutRounding属性,在进入MeasureOverride之前和之后,基类都被将参数根据DPI进行Rounding,这个过程知道就行了,不需要在自己的MeasureOverride里面关心。我们总结一下哪些属性和参数会影响Measure的过程:MyPanel.Measure传入的参数availableSize,MyPanel的MinWidth, Width, MaxWidth,Margin,UseLayoutRounding,LayoutTransform,MeasureOverride的返回值。

Measure过程相关问题解答 

Q1:什么是Layout Slot? 什么时候能获取到?在哪里获取? 

Layout Slot就是调用Arrange方法的时候,传入的参数finalRect,这是父分配给子的容纳Margin以及内容区域的矩形空间;

当Arrange过程结束后,你可以拿到;

通过调用静态类LayoutInformation.GetLayoutSlot(FrameworkElement element)方法可以拿到。

Q2:什么是Layout Clip?什么时候能获取到?在哪里获取? 

Layout Clip 只的是当内容区域要绘制的大小,大于LayoutSlot刨去Margin区域后的大小,这时候,内容区域就会被Clip,超出的部分会被Clip掉,而剩下的可显示的部分就是Layout Clip,他是一个Geometry。

Arrange过程结束后,可以拿到;

通过调用静态类LayoutInformation.GetLayoutClip(FrameworkElement element)方法可以拿到。如果内容区域可以完全显示

在Layout Slot刨去Margin的区域内,LayoutClip为Null。

Q3:在父的MeasureOverride当中调用孩子的Measure方法时,传入的参数有没有什么限制? 

有,确保availableSize.Width和Height不是NaN;但可以是Infinity

Q4:在进入自己的MeasureOverride方法后,面对参数我该咋办? 

首先,心里应该明白,传入的参数已经是基类刨去自己的Margin,并且考虑了基类影响Measure过程的属性之后的值。

其次,看自身有没有自定义的,并且影响Layout的属性,根据自己的内容要求,或者孩子的情况,调用孩子的Measure方法,并传入希望孩子限定在多大范围内空间。

最后,返回一个自己期望的Size。

这里应该注意的点:

1. 调用孩子的Measure方法时,传入的参数,是你限定孩子的最大空间,用来显示孩子的Margin以及内容区域的,而孩子不管最终期望的大小有多少,都会被你给他的availableSize裁剪。

2. 根据自身的策略返回一个期望的值,这个期望的值应该是在自己的MinWidth,Width,MaxWidth限定的范围呢,如果没有,基类还会强行调整。

3. 基类调整后的值还会被父传入的availableSize再次调整,返回值不能大于父传入的参数减去Margin之后的值

Q5: MeasureOverride的返回值有没有什么限制? 

有,除了如Q5所说,返回值会被重新调节之外,必须保证自己定义的MeasureOverride的返回值是一个确定的值,不是NaN,也不是Infinity。如果小于0时,基类会强制调节为0.

Q6:DesiredSize究竟是什么? 

DesiredSize是Measure过程结束后确定的一个大小,他是孩子期望父在Arrange的时候给他分配的大小,包含孩子的Margin区域以及内容区域。如果父在ArrangeOverride的时候,需要调用孩子的Arrange方法时,如果根据策略他希望满足孩子的期望大小,那么,调用孩子的Arrange方法应该传入孩子DesiredSize大小的Rect。

Q7:孩子的DesiredSize确定后,是不是最终就可以得到这么大的空间? 

不一定。就像Q7答案所讲,根据父的策略而定,如果父期望分配给孩子期望的大小,就在调用孩子的Arrange方法时,传入DesiredSize大小的Rect,比如Canvas,Canvas的孩子的大小就是孩子的DesiredSize那么大;而如果父是根据自身的设置决定,就不会参考孩子的DesiredSize,传入的当然是自己只能分配给孩子的空间,比如UniformGrid,他根据自身的可用大小,根据行数列数均分空间,然后,均分后的空间分配给每个孩子,而不考虑孩子的DesiredSize。给孩子分配空间,这个过程是在Arrange阶段的。

原文地址:http://www.cnblogs.com/powertoolsteam/archive/2011/01/10/1932036.html

WPF/Silverlight Layout 系统概述——Measure(转)的更多相关文章

  1. WPF/Silverlight Layout 系统概述——Arrange(转)

    Arrange过程概述 普通基类属性对Arrange过程的影响 我们知道Measure过程是在确定DesiredSize的大小,以便Arrange过程参考这个DesiredSize,确定给MyPane ...

  2. WPF Layout 系统概述——Measure

    原文:WPF Layout 系统概述--Measure 前言 在WPF/Silverlight当中,如果已经存在的Element无法满足你特殊的需求,你可能想自定义Element,那么就有可能会面临重 ...

  3. WPF Layout 系统概述——Arrange

    原文:WPF Layout 系统概述--Arrange Arrange过程概述 普通基类属性对Arrange过程的影响 我们知道Measure过程是在确定DesiredSize的大小,以便Arrang ...

  4. XData -–无需开发、基于配置的数据库RESTful服务,可作为移动App和ExtJS、WPF/Silverlight、Ajax等应用的服务端

    XData -–无需开发.基于配置的数据库RESTful服务,可作为移动App和ExtJS.WPF/Silverlight.Ajax等应用的服务端   源起一个App项目,Web服务器就一台,已经装了 ...

  5. WPF/Silverlight HierarchicalDataTemplate 模版的使用(转)

    上一篇 对Wpf/Silverlight Template 进行了总结,本篇继续上一篇,主要是介绍 HierarchicalDataTemplate 的使用方法.HierarchicalDataTem ...

  6. WPF/Silverlight Template使用及总结(转)

    WPF/Silverlight 中的控件都有Style和Template两种属性.前者解释为样式,是用来改变控件原有属性的,比如 Button 控件的(Width,Height,Background ...

  7. Mvvm Light Toolkit for WPF/Silverlight系列之搭建mvvmlight开发框架

    Mvvm Light Toolkit for WPF/Silverlight系列之搭建mvvmlight开发框架   本章节,我将通过示例介绍如何搭建mvvmlight开发环境.示例中的我会针对wpf ...

  8. WPF/Silverlight深度解决方案:(一)解锁被Storyboard束缚的关联属性

    原文 WPF/Silverlight深度解决方案:(一)解锁被Storyboard束缚的关联属性 如果您在使用WPF/Silverlight进行相关动画开发中使用了Storyboard,并对关联属性进 ...

  9. WPF/Silverlight中图形的平移,缩放,旋转,倾斜变换演示

    原文:WPF/Silverlight中图形的平移,缩放,旋转,倾斜变换演示 为方便描述, 这里仅以正方形来做演示, 其他图形从略. 运行时效果图:XAML代码:// Transform.XAML< ...

随机推荐

  1. axis1调用方式

    axis http://10.15.22.28/itfmgr/services/ITaxManagement?wsdl package com.isoftstone.core.service.impl ...

  2. mysql kill操作

    KILL语法 KILL [CONNECTION | QUERY] thread_id 每个与mysqld的连接都在一个独立的线程里运行,您可以使用SHOW PROCESSLIST语句查看哪些线程正在运 ...

  3. IE=EmulateIE8和IE=IE8的区别

    IE=8<meta http-equiv="X-UA-Compatible" content="IE=8" />This forces IE 8 t ...

  4. fastreport for .net 数据邦定

    C# Code: private void button4_Click(object sender, EventArgs e){   //打印主从表数据    string file = Applic ...

  5. java/android线程池详解

    一,简述线程池: 线程池是如何工作的:一系列任务出现后,根据自己的线程池安排任务进行. 如图: 线程池的好处: 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销. 能有效控制线程池的最大并 ...

  6. 使用MySQL中的EXPLAIN解释命令来检查SQL

    我们看到许多客户的系统因为SQL及数据库设计的很差所以导致许多性能上的问题,这些问题不好解决,但是可以采用一套简单的策略来检查生产系统,发现并纠正一些共性问题. 很显然,您应该尽最大努力设计出最好的数 ...

  7. redis-在乌班图下设置自动启动

    一.修改redis.conf 1.打开后台运行选项,默认情况下,Redis不在后台运行: daemonize yes 2.配置log文件地址,默认使用标准输入,即打印在命令行终端 的窗口上 logfi ...

  8. cocos2d制作动态光晕效果基础——blendFunc

    转自:http://www.2cto.com/kf/201207/144191.html 最近的项目要求动态光晕的效果. 何谓动态光晕?之前不知道别人怎么称呼这个效果, 不过在我看来,“动态光晕”这个 ...

  9. ClassRequestHandler or VendorRequestHandler wIndex must be less than NumIFs

    P1_ro:20000EEA ClassRequestHandler ; CODE XREF: USB__HandleSetup+38j P1_ro:20000EEA LDRB R0, [R4,#4] ...

  10. jcmd命令使用

    概述 在JDK 1.7之后,新增了一个命令行工具jcmd. 它是一个多功能工具,能够用来导出堆,查看java进程,导出线程信息.运行GC等. 使用演示样例 以下这个命令能够列出当前运行的全部虚拟机: ...