UWP下控件开发01 - 布局面板的布局

啊~啊~麦克风测试…啊~啊~这次不会再掉档了吧

本来都码了一大段了的,突然 WinEcos 他刷新了,东西都没了,OTZ…于是只好转战 OneNote

这系列主要讲一些 HLib 开发途中遇到的问题,与对应的解决方案,为之后自己或者其他 UAP 开发者做个提示什么的(哈~反正没人看我写的东西啦,权当给自己练习打字啦

首先介绍一下 HLib (这命名还真是随便啊…是代表着 HIGAN's Library 吗?HLib 是我正在开发中的 UAP 框架下的一个类库与控件库…之后加入NuGet,开源的话也有考虑,但是如果最后代码不堪入目了的话呢,还是算了(我就写不出好看的代码…

HLib 的东西很杂,我大致分了几个部分:

01.杂项与各种 Helper,这些都在主命名空间 HLib 里面,有什么 HSB 颜色,HttpWebRequest,可视化树,什么的

02.控件库,预计是加上瓷贴,水印编辑框,密码可视框,还有目前这个VirtualizingPanel ,之后如果还有什么打算的话也会加上其他控件,都在 HLib.Controls 命名控件里面,和 HLib 是两个程序集

03.各种 API 的封装,主要是目前我用到的 Booru 和 ExHentai 的API

04.社会化组件

这是我第一次造轮子啊,以前都是一个项目的代码写了之后基本不能复用,都是一次性的代码…OTZOK,废话就到这里,下面我们开始正题.(我也会讲一点排版的…嗯,尽量不用三个点…

首先呢,在UAP里面,原生的 Windows.UI.Xaml.Controls.VirtualizingStackPanel 是个密封类,并不能继承与重写.

而他的父类 Windows.UI.Xaml.Controls.Primitives.OrientedVirtualizingPanel 虽说不是密封类但是并没有提供公共的构造函数,也是无法继承的.

而再往上一层 Windows.UI.Xaml.Controls.VirtualizingPanel 也是没有公共的构造函数,也是无法继承

那么只能再往上一层了 Windows.UI.Xaml.Controls.Panel 是所有排版面板的基类,这个是可以继承的

所以我们的自定义布局面板只能从 Windows.UI.Xaml.Controls.Panel 继承与派生,OTZ,明明在WP8或者WP8.1SL里面都是可以继承 System.Windows.Controls.VirtualizingPanel 的,微软到底怎么在搞,我有点怀疑我的 UI 虚拟化到底能不能搞定

public class VirtualizingPanel : Windows.UI.Xaml.Controls.Panel  

这就是我们的面板了,接下来来简单说一下 UAP 框架下,控件们是如何完成布局的吧

(注意:本节内容可能有错误或者不确定元素,请带着怀疑的态度来看)

控件布局是一个路由事件,由父控件一步步向子控件询问空间要求.

之后再由父控件一步一步的定位与提供空间给子控件.

也就是说布局的过程是从底层到顶层的,一共两次,这两次过程就分别代表了控件的Measure与Arrange过程.

Measure过程中,由父控件询问他的子控件,"你,说的就是你,你要多大的地皮?",然后子控件也不知道自己要多大的空间,之后他就问自己的子控件,要多少空间,然后汇一个总,告诉他的父控件,再由父控件计算一下自己要多少空间,向底层提交,这样一个过程就完成了空间的测量,弄懂了每个UI元素需要多大的空间

Arrange 过程中,也是由父控件到子控件的递归,一开始,父控件在Measure过程中计算了自己需要的地盘,之后最大的控件告诉他和其他的平行控件,"这块地是你的,你自己看着办吧",他就得到一个记录着坐标与大小的 Rect ,这些空间再由他分给他的子控件,之后就是一层一层的分配,每个控件都得到了属于自己的地盘,开始进行造房子(渲染内容)

那么我们布局面板,做好布局就只需要,完成好这两个步骤就可以完成自己想要的布局了想要定义Measure的测量过程,只需要重写MeasureOverride函数即可,而Arrange 过程则是由ArrangeOverride函数控制,重写这两个函数就可以完成布局了

接下来我们来看一下这两个函数

/// <summary>
/// Measure 测量过程
/// </summary>
/// <param name="availableSize">由父控件给出的可用空间</param>
/// <returns>返回自己需要多少控件</returns>
protected override Size MeasureOverride(Size availableSize)  
{
    return base.MeasureOverride(availableSize);  
}
```CSharp
MeasureOverride函数有一个 Size 参数,这个参数是由父控件给出的最大可用空间,而返回值则是这个控件需要多少控件,这里我们可以自己定义测量方法,例如,需要 Stack 布局的话,就只要遍历 Children ,调用UIElement.Measure() 方法,询问子控件需要多少空间,同时给出自己最大提供的空间,调用完后只需获取 UIElement.DesiredSize属性即可知道,该子控件需要多大空间了.之后再把每个DesiredSize.Height 的相加,就可以知道这个 Stack 有多长了,这是纵向排版.

用代码写就是:
```CSharp
protected override Size MeasureOverride(Size availableSize)  
{
    Size request = new Size(availableSize.Width, 0);
    foreach (var item in Children)
    {
        item.Measure(availableSize);
        request.Height += item.DesiredSize.Height;
    }
    return request;
}

这就完成了 Stack 布局,如果每个元素之间还有间隔的话,还可以加上一个间隔.

/// <summary>
/// Arrange布局过程
/// </summary>
/// <param name="finalSize">最终分配到的空间大小</param>
/// <returns>并没有什么特殊含义,一半是原封不动直接返回参数</returns>
protected override Size ArrangeOverride(Size finalSize)  
{
    return base.ArrangeOverride(finalSize);
}

ArrangeOverride函数,提供一个 Size 参数,这个参数代表了,父控件最后是分配了多少空间给你,返回值并没有什么特殊的用法,可能是我不知道吧,一般就是直接返回参数就好了,参数也不需要改动.具体的空间分配过程呢,就是遍历 Children 调用Arrange函数,提供一个 Rect 参数,给子控件定位,子控件会将 Rect 的 Width 和 Height 封装成 Size 提供给子控件的ArrangeOverride布局.同样我们用 Stack 布局当示范,只要给定指定的坐标和大小就好了.

用代码写就是:

protected override Size ArrangeOverride(Size finalSize)  
{
    double offsetY = 0;
    foreach (var item in Children)
    {
        item.Arrange(new Rect(0, offsetY, item.DesiredSize.Width, item.DesiredSize.Height));
        offsetY += item.DesiredSize.Height;
    }
    return finalSize;
}

对于一些特殊的控件,例如 Grid , Canvas 他们都会提供无限大的可用布局空间,也就是他们在Measure过程给子控件的availableSize参数是无限大,而 ScrollView 则会看是否能横向或者纵向滚动,横向或者纵向滚动时给的对应的宽和高是无限大.

之后留个思考题,根据 ViewBox 的性质,你们能够想象他的Measure和Arrange是如何计算与分配的呢?