5.2 布局原理

很多时候在编写程序界面的时候都会忽略了应用布局的重要性,仅仅只是把布局看作是对UI元素的排列,只要能实现布局的效果就可以了,但是在实际的产品开发中这是远远不够的,你可能面临要实现的布局效果要比常规布局更加复杂,这就需要对布局的技术知识有深入的理解和掌握才能够实现。要实现一个布局的效果,可能会有很多总布局方案,我们该怎么去选择实现的方法?如果要实现的一个布局效果是比较复杂的,我们该怎么去对这种布局规律进行封装?要解决这些问题,首要的问题就是需要我们对程序的布局原理有着深入的理解。

5.2.1 布局的意义

布局是页面编程的第一步,是从总体的方向去把握页面上UI元素的显示。布局其实一个一个应用程序开发里面非常重要的一部分,这块的知识也是往往会被开发者所忽视。随着Windows 10支持这非常多的设备和屏幕分辨率,布局显得原来越重要,也将会变得更加复杂,我们非常有必要去了解做程序的界面布局有什么样的意义,这不仅仅是界面显示可以就完事了。下面来看一下应用程序的布局有什么样的意义:

(1)代码逻辑

良好的布局会使得代码逻辑非常清晰,差的布局方案会让页面代码逻辑很混乱。如果你只是靠拖拉控件来做Windows 10的布局,这个程序的界面布局肯定会变成很糟糕,所以好的布局方案,一定要基于对各种布局控件的理解,然后充分地它们的特性去实现布局的效果。

(2)效率性能

布局不仅仅是界面UI的事情,它甚至会影响到程序的运行效率。当然简单的几个控件的页面布局,对程序效率性能的影响是微乎其微的,但是如果你的界面要展示大量的控件的时候,这时候布局的好坏就会直接影响到程序的效率。好的布局实现逻辑会让程序即使在有大量控件的页面也能流程的运行。

(3)动态适配

动态适配包括两个方面,一个是Windows 10的多种分辨率的界面的适配,另外一个是页面的控件是不确定的也会产生动态适配的问题。好的布局方案,可以使应用程序可以兼容各种分辨率的设别,受到不同分辨率所影响的页面,也是可以通过布局的技巧来解决的,保证不同的分辨率下面都是符合产品的显示效果。还有一个就是动态产生的控件,这个是指你的页面上会根据不同的情况显示不同的内容,这时候在做布局的时候就要思考如何对付这些会变化的页面。

(4)实现复杂的布局

有时候页面需要实现一些复杂的布局效果,比如像圆圈一定排列控件,Windows 10里面是没有这样的布局控件支持这种复杂的布局效果的,这时候就需要去自定义布局的规律来解决这样的问题。能否自定义布局控件去实现复杂的布局效果,这就要看你对Windows 10的布局技术的掌握程度了。

5.2.2 布局系统

布局系统是指对Windows 10的布局面板所进行的布局过程的运作原理的统称。布局其实是一个在 Windows 10应用中调整对象大小和定位对象的过程。要定位可视化对象,必须将它们放置于 Panel 或其他布局面板中。Panel类是所有布局面板的父类,系统的布局面板Canvas、StackPanel、Grid和RelativePanel都是Panel类的子类,继承了所有Panel类的特性。Panel类定义了在屏幕上绘制所有的面板里面的成员(Children属性)的布局行为。这是一个计算密集型过程,即 Children 集合越大,执行的计算次数就越多,也就是面板里面的元素越多,布局系统布局的整个过程的时间就会越长。所有的布局面板类都是在Panel的基础上添加了相应的布局规律的,在Panel类的基础上继续封装的布局面板是为了更好地解决一些布局规律的问题,实际上这样的封装是增加复杂性,对性能造成一定的损失的。所以如果不需要较为复杂的布局面板(如 Grid),则可以使用构造相对简单的布局(如 Canvas),这种布局可产生更佳的性能。

简单地说,布局是一个递归系统,实现在屏幕上对元素进行大小调整、定位和绘制。在整个布局的过程中,布局系统对布局面板成员的处理分为两个过程:第一个是测量处理过程,第二个是排列处理过程。测量处理过程是确定每个子元素所需大小的过程。排列处理过程是最终确定每个子元素的大小和位置的过程。每当面板里面的成员改变其位置时,布局系统就可能触发一个新的处理过程,重新处理上面所说的两个过程。不论何时调用布局系统,都会发生以下一系列的操作:

1、第一次递归遍历测量每个布局面板子元素(UIElement类的子类控件)的大小。

2、计算在 FrameworkElement类的子类控件元素上定义的大小调整属性,例如 Width、Height 和 Margin。

3、应用布局面板特定的逻辑,例如StackPanel面板的水平布局。

4、第二次递归遍历负责把子元素排到对于自己的相对的位置。

5、把所有的子元素绘制到屏幕上。

如果其他子元素添加到了集合中、子元素的布局属性(如 Width 和 Height)发生了改变或调用了 UpdateLayout 方法,均会再次调用该过程。因此,了解布局系统的特性就很重要,因为不必要的调用可能导致应用性能变差。下面的内容将会详细地演示这个布局的过程。

5.2.3 布局系统的重要方法和属性

在Windows 10中,布局不仅仅是布局面板的要做的事情,布局面板是在负责把这个布局的过程组织起来,而在整个布局的过程中对于布局面板里面的元素都要经过一个从最外面到最里面的一个递归的测量和排列的过程。在研究这个递归的排列和测试的过程,先来了解一下,基本的控件上关于布局的一些重要的方法和属性。

Windows 10的UI元素有两个非常重要的基类UIElement类和FrameworkElement类,他们的继承层次结构如下:

Windows.UI.Xaml.DependencyObject

Windows.UI.Xaml.UIElement

Windows.UI.Xaml.FrameworkElement

(1)UIElement

UIElement类是具有可视外观并可以处理基本输入的大多数对象的基类。关于布局,UIElement类有两个非常重要的属性——DesiredSize和RenderSize属性和两个非常重要的方法——Measure 方法和Arrange方法。

DesiredSize属性:这是一个只读的属性,类型是Size类,表示在布局过程的测量处理过程中计算的大小。

RenderSize属性:这是一个只读的属性,类型是Size类,表示UI元素最终呈现大小,RenderSize和DesiredSize并不一定是相等的。RenderSize就是其ArrangeOverride方法的返回值。

public void Measure(Size availableSize)方法:Measure方法所做的事情是更新 UIElement 的 DesiredSize属性,测量出UI元素的大小。如果在该UI元素上实现了FrameworkElement.MeasureOverride(System.Windows.Size)方法,将会用此方法以形成递归布局更新。参数availableSize表示:父对象可以为子对象分配的可用空间。子对象可以请求大于可用空间的空间,如果该特定面板中允许滚动或其他调整大小行为,则提供的大小可以适应此空间。

public void Arrange(Rect finalRect)方法:Arrange方法所做的事情是定位子对象并确定 UIElement 的大小,也就是DesiredSize属性的值。如果在该UI元素上实现了FrameworkElement. ArrangeOverride(System.Windows.Size)方法,将会用此方法以形成递归布局更新。参数finalRect表示:布局中父对象为子对象计算的最终大小,作为 System.Windows.Rect 值提供。

(2)FrameworkElement

FrameworkElement类是UIElement类的子类,为 Windows 10布局中涉及的对象提供公共 API 的框架。FrameworkElement类有两个和布局相关的虚方法MeasureOverride 方法和ArrangeOverride方法。如果已经存在的布局面板无法满足特殊的布局需求,你可能需要自定义布局面板,就需要重写MeasureOverride和ArrangeOverride两个方法,而这两个方法是Windows 10的布局系统提供给用户的自定义接口,下面来看下这两个方法的含义。

protected virtual Size MeasureOverride(Size availableSize)提供 Windows 10布局的度量处理过程的行为,可以重写该方法来定义其自己的度量处理过程行为。参数availableSize表示对象可以赋给子对象的可用大小,可以指定无穷大值 (System.Double.PositiveInfinity),以指示对象的大小将调整为可用内容的大小,如果子对象所计算出来的大小比availableSize大,那么将会被截取出availableSize大小的部分。返回结果表示此对象在布局过程中基于其对子对象分配大小的计算或者基于固定面板大小等其他因素而确定的它所需的大小。

protected virtual Size ArrangeOverride(Size finalSize)提供 Windows 10布局的排列处理过程的行为,可以重写该方法来定义其自己的排列处理过程行为。参数finalSize表示父级中此对象应用来排列自身及其子元素的最终区域。返回结果表示元素在布局中排列后使用的实际大小。

5.2.4 测量和排列的过程

Windows 10的布局系统是一个递归系统,它总是以Measure方法开始,最后以Ararnge方法结束。假设在整个布局系统里面只有一个对象,这个对象在加载到界面之前会先调用Measure方法来测量对象的大小,最后再调用Ararnge方法来安排对象的位置完成了整个过程的布局。但是现实中往往是一个对象里面包含了多个子对象,子对象里面也包含着子对象,如此递归下去,直到最底下的对象。布局的过程就是从最顶层的对象开始测量,最顶层的对象的测量过程又会调用它的子对象的测量方法,如此递归直到最底下的对象。测量的过程完成之后,则开始排列的过程,排列的过程也是和测量的过程的原理一样,一直递归下去直到最底下的对象。下面通过一个示例来模拟这个过程。

示例里面创建了两个类,TestPanel类用来模拟最外面的布局面板,作为父对象的角色;TestUIElement类用来模拟作为布局面板的元素,作为最底下子对象的角色。TestUIElement类和TestPanel类都继承了Panel类,实现了MeasureOverride和ArrangeOverride方法,并打印出相关的日志用于跟踪布局的详细情况。

代码清单5-8模拟测量和排列的过程(源代码:第5章\Examples_5_8)

TestUIElement.cs文件主要代码
------------------------------------------------------------------------------------------------------------------
public class TestUIElement : Panel
{
protected override Size MeasureOverride(Size availableSize)
{
Debug.WriteLine("进入子对象" + this.Name + "的MeasureOverride方法测量大小");
return availableSize;
}
protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
{
Debug.WriteLine("进入子对象" + this.Name + "的ArrangeOverride方法进行排列");
return finalSize;
}
}
TestPanel.cs文件主要代码
-----------------------------------------------------------------------------------------------------------------
public class TestPanel : Panel
{
protected override Size MeasureOverride(Size availableSize)
{
Debug.WriteLine("进入父对象" + this.Name + "的MeasureOverride方法测量大小");
foreach (UIElement item in this.Children)
{
item.Measure(new Size(, ));//这里是入口
Debug.WriteLine("子对象的DesiredSize值 Width:" + item.DesiredSize.Width + " Height:" + item.DesiredSize.Height);
Debug.WriteLine("子对象的RenderSize值 Width:" + item.RenderSize.Width + " Height:" + item.RenderSize.Height);
}
Debug.WriteLine("父对象的DesiredSize值 Width:" + this.DesiredSize.Width + " Height:" + this.DesiredSize.Height);
Debug.WriteLine("父对象的RenderSize值 Width:" + this.RenderSize.Width + " Height:" + this.RenderSize.Height);
return availableSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
Debug.WriteLine("进入父对象" + this.Name + "的ArrangeOverride方法进行排列");
double x = ;
foreach (UIElement item in this.Children)
{
//排列子对象
item.Arrange(new Rect(x, , item.DesiredSize.Width, item.DesiredSize.Height));
x += item.DesiredSize.Width;
Debug.WriteLine("子对象的DesiredSize值 Width:" + item.DesiredSize.Width + " Height:" + item.DesiredSize.Height);
Debug.WriteLine("子对象的RenderSize值 Width:" + item.RenderSize.Width + " Height:" + item.RenderSize.Height);
}
Debug.WriteLine("父对象的DesiredSize值 Width:" + this.DesiredSize.Width + " Height:" + this.DesiredSize.Height);
Debug.WriteLine("父对象的RenderSize值 Width:" + this.RenderSize.Width + " Height:" + this.RenderSize.Height);
return finalSize;
}
}

创建了TestUIElement类和TestPanel类之后,接下来要在UI上使用这两个类,把TestPanel看作是布局面板控件来使用,把TestPanel看作是普通控件来使用,然后观察打印出来的运行日志。要添加这两个控件需要现在xaml页面上把这两个空间所在的空间引入进去再进行调用,如下面的代码所示:

MainPage.xaml文件主要代码
-----------------------------------------------------------------------------------------------------------------
……省略若干代码 下面引入控件空间
xmlns:local="using:MeasureArrangeDemo"
……省略若干代码 下面调用控件布局
<StackPanel>
<Button Content="改变高度" Click="Button_Click_1"></Button>
<local:TestPanel x:Name="panel" Height="400" Width="400" Background="White" >
<local:TestUIElement x:Name="element1" Width="60" Height="60" Background="Red" Margin="10"/>
<local:TestUIElement x:Name="element2" Width="60" Height="60" Background="Red" />
</local:TestPanel>
</StackPanel>

程序在Debug的状态下运行之后,在Visual Studio的输出窗口可以看到打印出来的日志。日志的详细情况如下:

/*日志开始*/

进入父对象panel的MeasureOverride方法测量大小

进入子对象element1的MeasureOverride方法测量大小

子对象的DesiredSize值  Width:80 Height:80

子对象的RenderSize值  Width:0 Height:0

进入子对象element2的MeasureOverride方法测量大小

子对象的DesiredSize值  Width:60 Height:60

子对象的RenderSize值  Width:0 Height:0

父对象的DesiredSize值  Width:0 Height:0

父对象的RenderSize值  Width:0 Height:0

进入父对象panel的ArrangeOverride方法进行排列

进入子对象element1的ArrangeOverride方法进行排列

子对象的DesiredSize值  Width:80 Height:80

子对象的RenderSize值  Width:60 Height:60

进入子对象element2的ArrangeOverride方法进行排列

子对象的DesiredSize值  Width:60 Height:60

子对象的RenderSize值  Width:60 Height:60

父对象的DesiredSize值  Width:400 Height:400

父对象的RenderSize值  Width:0 Height:0

/*日志结束*/

从打印出来的日志可以很清楚地看到整个布局过程的步骤(如图5.25所示),以及布局过程中DesiredSize值和RenderSize值的变化情况。

从布局的过程中可以总结出下面的结论:

1.       测量的过程是为了确认DesiredSize的值,最终是要提供给排列的过程去使用。

2.       DesiredSize是根据Margin,,Width,Height等属性来决定。

3.       排列的过程确定RenderSize,以及最终子对象被安置的空间。RenderSize就是ArrangeOverride的返回值,没还有被裁剪过的值。

4.       Margin,,Width,Height等属性只是控件表面上的属性,而实际掌控住这些效果的是布局的测量排列过程。

5.       Margin,,Width,Height等属性的改变会重新触发布局的过程。

本文来源于《深入浅出Windows 10通用应用开发》

源代码下载:http://vdisk.weibo.com/u/2186322691

目录:http://www.cnblogs.com/linzheng/p/5021428.html

欢迎关注我的微博@WP林政   微信公众号:wp开发(号:wpkaifa)

Windows10/WP技术交流群:284783431

[深入浅出Windows 10]布局原理的更多相关文章

  1. 《深入浅出Windows 10通用应用开发》

        <深入浅出Windows 10通用应用开发>采用Windows 10的SDK进行重新改版,整合了<深入浅出Windows Phone 8.1应用开发>和<深入解析 ...

  2. [深入浅出Windows 10]QuickCharts图表控件库解析

    13.4 QuickCharts图表控件库解析     QuickCharts图表控件是Amcharts公司提供的一个开源的图表控件库,这个控件库支持WPF.Silverlight.和Windows等 ...

  3. [深入浅出Windows 10]模拟实现微信的彩蛋动画

    9.7 模拟实现微信的彩蛋动画 大家在玩微信的时候有没有发现节日的时候发一些节日问候语句如“情人节快乐”,这时候会出现很多爱心形状从屏幕上面飘落下来,我们这小节就是要模拟实现这样的一种动画效果.可能微 ...

  4. [深入浅出Windows 10]实现饼图控件

    13.2 实现饼图控件 上一小节讲解了动态生成折线图和区域图,对于简单的图形这样通过C#代码来生成的方式是很方便的,但是当我们的图表要实现更加复杂的逻辑的时候,这种动态生成的方式就显得力不从心了,那就 ...

  5. [深入浅出Windows 10]不同平台设备的适配

    2.3 不同平台设备的适配 Windows 10通用应用程序针对特定的平台还会有一个子API的集合,当我们要使用到某个平台的特定API的时候(比如手机相机硬件按钮触发事件),这时候就需要调用特定平台的 ...

  6. [深入浅出Windows 10]分屏控件(SplitView)

    4.18 分屏控件(SplitView) 分屏控件(SplitView)是Windows 10新增的控件类型,也是Windows 10通用应用程序主推的交互控件,通常和一个汉堡按钮搭配作为一种抽屉式菜 ...

  7. [深入浅出Windows 10]应用实战:Bing在线壁纸

    本章介绍一个使用Bing搜索引擎背景图接口实现的一个应用——Bing在线壁纸,讲解如何使用网络的接口来实现一个壁纸下载,壁纸列表展示和网络请求封装的内容.通过该例子我们可以学习到如何使用网络编程的知识 ...

  8. 【WinHec启示录】透过Windows 10技术布局,谈微软王者归来

    每个时代都有王者,王者的成功,往往是因为恰逢其时地发布了一个成功的产品(具有里程碑意义,划时代的产品).Windows 95的成功标示着微软是PC时代的王者:WinXP的成功标示着微软是互联网时代的王 ...

  9. Windows 10技术布局,谈微软王者归来

    Windows 10技术布局,谈微软王者归来 每个时代都有王者,王者的成功,往往是因为恰逢其时地发布了一个成功的产品(具有里程碑意义,划时代的产品).Windows 95的成功标示着微软是PC时代的王 ...

随机推荐

  1. 重温WCF之群聊天程序(十)

    完成的效果图: 服务器端代码: using System; using System.Collections.Generic; using System.Linq; using System.Serv ...

  2. C# 一些常用的技巧代码

    1.字符串风格成字符数组: 比如将字符串:23$123$45$转换成int[]这样的数组,你该怎么转换?其实你不用写那么的for循环,只需要一句话: int [] Relst =Array.Conve ...

  3. 使用python递归子目录处理日志文件

    重要说明: (1)python使用4个空格进行层次缩进的(不是tab),在eclipse里面可以直接使用tab缩进,是因为eclipse会实时地将tab转成4个空格 (2)在eclipse中安装pyD ...

  4. Win10 兼容性 Visual studio web应用程序 ASP.NET 4.0 尚未在 Web 服务器上注册

    系统升级到windows10 ,Visual studio 创建web应用程序时出现如下提示ASP.NET 4.0尚未在 Web 服务器上注册.为了使网站正确运行,可能需要手动将 Web 服务器配置为 ...

  5. ASMCMD命令

    安装好用的rlwrap工具,在环境变量里添加如下,就能实现显示当前路径(目录),目录补全的方便功能 alias asmcmd='rlwrap -r -i asmcmd –p' asmcmd>he ...

  6. STL Map的使用

    Map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据处理能力.下面就通过示例记录一下map的使用: 一.向map中 ...

  7. 提供给Android和iOS开发人员的UWP移植向导

    (此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:前几天微软发布了一个针对Android和iOS开发人员理解Windows Apps概念 ...

  8. HDU 5876 Sparse Graph BFS 最短路

    Sparse Graph Problem Description   In graph theory, the complement of a graph G is a graph H on the ...

  9. Linux之find命令用于统计信息

    1. 计算当前目录中的文件数: [root@localhost tmp]# find . -type f | wc -l 2. 查找/etc目录中最新的和最旧的文件,以文件时间排序并按年-月-日的格式 ...

  10. JMeter中BeanShell用法总结(一)

    一.什么是Bean Shell BeanShell是一种完全符合Java语法规范的脚本语言,并且又拥有自己的一些语法和方法; BeanShell是一种松散类型的脚本语言(这点和JS类似); BeanS ...