title author date CreateTime categories
win10 uwp 自定义控件入门
lindexi
2018-10-22 09:47:54 +0800
2018-10-21 15:52:11 +0800
Win10 UWP

本文告诉大家如何在 UWP 使用 CustomControl 自定义控件,在 UWP 的自定义控件的中文翻译是模板化控件,通过自定义控件可以完全控制整个控件的布局和渲染。

默认创建的自定义控件是没有带 xaml 的,如果想要让 CustomControl 可以使用 xaml 就需要引入主题的方法

下面就来告诉大家如何使用 xaml 来做界面

在 CustomControl 使用 xaml 写界面

在 UWP 主要的元素就是控件,可以说,整个 UWP 的界面都依靠控件画出来的。使用 xaml 可以快速画出好看的界面,而默认创建的 自定义控件和用户控件不一样,用户控件会带一个 xaml 直接修改就可以在设计器看到界面。

通过创建一个类继承 Control 类,我这里创建的是一个 Board 类

public sealed class Board : Control

然后在相同的文件夹,创建一个资源字典 Board.xaml 这样可以对应资源字典和创建的控件

在资源字典先引用命名控件,我这里创建 Board 是在 lindexi.UWP.Framework 命名空间,就需要在资源字典引用xmlns:local="using:lindexi.UWP.Framework" 这样才可以拿到对应的控件

namespace lindexi.UWP.Framework
{
public sealed class Board : Control
{ }
}

添加一个 Style 指定为刚才创建的 Board 控件

<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:lindexi.UWP.Framework">
<Style TargetType="local:Board"> </Style>
</ResourceDictionary>

在这里不添加 Key 就是默认所有的 Board 控件都使用这个样式,通过修改 Template 的方法添加控件

    <Style TargetType="local:Board">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Board">
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> </Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

通过 ControlTemplate 的方法里面就和用户控件一样可以使用 xaml 写出界面,我这里就放一个 ContentControl 可以来定制

可以使用 ContentControl 的 Content 属性放入任意的 UIElement 都可以加入视觉树

   <Style TargetType="local:Board">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Board">
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<ContentControl x:Name="ContentControl"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

但是现在的代码还没完成,还需要在项目创建一个 Theme 文件夹,然后在这个文件夹里面添加 Generic.xaml 资源字典,从这个字典引用刚才创建的 Board 资源字典,才可以在使用的时候找到

在 Generic.xaml 资源字典只需要添加下面的代码

<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:lindexi.github.io"> <ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///lindexi/Framework/Board.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

需要注意 ResourceDictionary 的路径,修改为自己实际的控件的 xaml 文件的路径,注意这里必须使用 ms-appx:/// 开头,文件使用的是相对于项目的路径,如果使用的是相对于这个文件的路径,就会在运行的时候,在某个类的构造函数告诉

Failed to assign to property 'Windows.UI.Xaml.ResourceDictionary.Source' because the type 'Windows.Foundation.String' cannot be assigned to the type 'Windows.Foundation.Uri'.

虽然现在设置好了控件的 xaml 但是现在的 xaml 没有内容,需要在 Board 类添加一些代码,让大家可以看到自己的 xaml 是否可以在 Board 使用

首先是添加 TemplatePart 在 Board 类,这样是在约定在 xaml 界面需要添加一个对应的控件,指定了控件的 Name 和这是一个什么控件

    [TemplatePart(Name = "ContentControl", Type = typeof(ContentControl))]
public sealed class Board : Control

是否记得在 Board 的资源字典就写了一个 ContentControl 类,虽然添加了约定但是还是需要将这个控件拿出来,通过重写 OnApplyTemplate 方法就可以使用 GetTemplateChild 方法拿到 xaml 里写的控件

        protected override void OnApplyTemplate()
{
Content = (ContentControl) GetTemplateChild("ContentControl");
}

这样就可以拿到对应的 xaml 的控件,虽然界面都在不断变化,但是这里拿到的控件是需要使用强转的方式,一旦找不到控件就给一个异常。

如果在 xaml 忘记写了一个控件,通过 GetTemplateChild 方法会返回 null 而不是抛异常,但是建议在这个方法下面判断拿到的如果是空,就抛出异常

        protected override void OnApplyTemplate()
{
var foo = GetTemplateChild("不存在的控件");
if (foo == null)
{
throw new ArgumentException("使用的模板不包含");
}
}

我通过去拿一个不存在的控件,拿到的是空判断是空就抛出异常

如果此时运行了代码,在 OnApplyTemplate 添加断点,会发现这个函数无法进来,原因是 Board 控件的构造函数还忘记写下面的代码

        public Board()
{
this.DefaultStyleKey = typeof(Board);
}

通过这个方法就可以拿到在 xaml 定义的控件,拿到了之后就可以在代码修改,如何修改请看下面

布局

如果已经写了 xaml 在代码拿到了 xaml 的控件,自定义控件还可以修改布局的方式

先在界面添加一些元素

        public ContentControl Content { get; set; }

        private Grid _grid;

        protected override void OnApplyTemplate()
{
Content = (ContentControl) GetTemplateChild("ContentControl");
_grid = new Grid()
{
Children =
{
new TextBlock()
{
Text = "欢迎访问我博客 lindexi.github.io 里面有很多 UWP WPF 博客",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
}
};
Content.Content = _grid; base.OnApplyTemplate();
}

通过重写 MeasureOverride 方法可以拿到测量的值,在 UWP 的布局和 WPF 的一样,都是先进过测量再进行控制每个控件的坐标和大小。

测量是什么?在 UWP 的布局过程,这里提高了布局过程,还需要继续解释一下什么是布局过程。在 UWP 会将所有的控件按照控件所在的容器,作为视觉树,视觉树的意思很简单,我有一个 Grid 在里面放在两个 Grid 同时又在第一个 Grid 里面添加一个文本,这时的控件可以使用树这个数据结构表示。第一个节点是最上面的,也是最外层的 Grid 这个 Grid 有两个子节点,分别就是放在 Grid 里面的两个 Grid 而这里的两个 Grid 的第一个 Grid 里面也有一个节点就是文本。

在 UWP 通过 xaml 界面就可以知道控件的树结构,如果熟悉树这个结构就知道,可以使用递归的方式处理。也就是一个节点只处理这个节点的子节点,而不处理子节点的子节点,所以 UWP 的布局就依赖这个视觉树,通过布局子节点的方式,然子节点自己递归这个布局方法,布局子节点的子节点。

那么布局是什么?布局就是让子节点控件放在该放的地方,虽然定义了视觉树,知道了一个控件的里面包含了哪些控件,但是这个控件还没准备好里面的控件的坐标和大小。例如我有一个容器是 StackPanel 这个容器需要让里面的控件按照垂直或水平的方式布局,也就是在 StackPanel 垂直布局里面的控件,第二个控件的坐标的 Y 点是第一个控件的坐标的 Y 点加上控件的高度。假如第一个控件也是一个容器,那么如何知道这个容器的的高度是多少?因为容器的大小可以是容器里面的元素决定的,需要让这个容器先知道他里面的控件的大小才可以知道容器的大小。

这就是测量的过程,测量的过程就是让每个控件知道子节点的大小,从而计算出控件的大小,然后将控件的大小返回给上一层,让上一层可以知道子节点的大小。有了测量的过程,在进行 StackPenel 布局的时候,就可以在测量的过程知道了控件大小,从而在可以安排每个控件坐标。

这里自定义的控件也是这样,通过重写 MeasureOverride 可以修改计算自定义控件的大小的方法,从而报告给上一层一个特殊的值。

如我这里的控件是想要上一层给我多大的空间,我就要多大的空间,我可以通过重写 MeasureOverride 方法,返回参数

        protected override Size MeasureOverride(Size availableSize)
{
base.MeasureOverride(availableSize);
return availableSize;
}

因为我这个控件里面有一些控件是需要在测量的过程重新给他一个值,我就可以这样写

        protected override Size MeasureOverride(Size availableSize)
{
_grid.Height = availableSize.Height;
_grid.Width = availableSize.Width; base.MeasureOverride(availableSize);
return availableSize;
}

处理测量的方法可以重写,布局的方法也可以重写

通过重写 ArrangeOverride 的方法可以做到实际的布局,从测量的方法传入的参数也许不是最外层控件在布局的时候传入的大小,假如我有一个 StackPanel 他的高度 100 宽度也是 100 在测量的过程就会传入大小是 100x100 但是在布局的过程就依赖当前的控件是在 StackPanel 的第几个控件,减去前面控件用的地方在是这个控件可以用的。

本文的控件是不需要重新布局的方法,现在看起来的控件的代码请看下面

    [TemplatePart(Name = "ContentControl", Type = typeof(ContentControl))]
public sealed class Board : Control
{
public Board()
{
this.DefaultStyleKey = typeof(Board);
} protected override Size MeasureOverride(Size availableSize)
{
_grid.Height = availableSize.Height;
_grid.Width = availableSize.Width; base.MeasureOverride(availableSize);
return availableSize;
} public ContentControl Content { get; set; } private Grid _grid; protected override void OnApplyTemplate()
{
Content = (ContentControl) GetTemplateChild("ContentControl");
_grid = new Grid()
{
Children =
{
new TextBlock()
{
Text = "欢迎访问我博客 lindexi.github.io 里面有很多 UWP WPF 博客",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
}
};
Content.Content = _grid; base.OnApplyTemplate();
}
}

在界面添加这个控件然后运行一下,可以看到界面居中显示了这个控件

2018-10-22-win10-uwp-自定义控件入门的更多相关文章

  1. win10 uwp MVVM入门

    MVVM 是一个强大的架构,基本从 WPF 开始,wr(我说的就是微软)就提倡使用 MVVM.它可以将界面和后台分离,让开发人员可以不关心界面是怎样,全心投入到后台代码编写中. 然后在编写完后台代码后 ...

  2. win10 uwp 自定义控件 SplitViewItem

    本文主要是因为汉堡菜单里面列出的菜单很多重复的图标和文字,我把它作为控件,因为是随便写,可能存在错误,如果发现了,请和我说或关掉浏览器,请不要发不良言论. 我们使用汉堡菜单,经常需要一个 需要一个图标 ...

  3. win10 uwp 自定义控件初始化

    我遇到一个问题,我在 xaml 用了我的自定义控件,但是我给他设置了一个值,但是什么时候我才可以获得这个值? 本文告诉大家,从构造函数.loaded.Initialized 的调用过程. 用最简单的方 ...

  4. NOIP模拟赛-2018.10.22

    模拟赛 今天第一节课是历史,当然是不可能上的,一来到机房发现今天高二考试... 老师说以后可能还要给高一考...那还不如现在跟着做好了,毕竟在学长学姐中垫底显得没那么丢人 这套题风格挺奇怪的...为什 ...

  5. 2018.10.22 bzoj1009: [HNOI2008]GT考试(kmp+矩阵快速幂优化dp)

    传送门 f[i][j]f[i][j]f[i][j]表示从状态"匹配了前i位"转移到"匹配了前j位"的方案数. 这个东西单次是可以通过跳kmp的fail数组得到的 ...

  6. 2018.10.22 bzoj1742: Grazing on the Run 边跑边吃草(区间dp)

    传送门 区间dp入门题. 可以想到当前吃掉的草一定是一个区间(因为经过的草一定会吃掉). 然后最后一定会停在左端点或者右端点. f[i][j][0/1]f[i][j][0/1]f[i][j][0/1] ...

  7. 2018.10.22 cogs2471. [EZOI 2016]源氏的数学课(线段树)

    传送门 线段树入门操作. 直接把题目给的(r−i+1)∗a[i](r-i+1)*a[i](r−i+1)∗a[i]拆开变成(r+1)∗1∗a[i]−i∗a[i](r+1)*1*a[i]-i*a[i](r ...

  8. 2018.10.22 bzoj4380: [POI2015]Myjnie(区间dp)

    传送门 区间dp好题. f[i][j][k]f[i][j][k]f[i][j][k]表示区间[i,j][i,j][i,j]最小值为kkk时的最大贡献. 然后可以枚举端点转移. 当时口胡到这儿就不会了. ...

  9. POI 2018.10.22

    [POI2015]ODW 喵锟讲过.分块. N>=blo,那就暴力倍增往上跳.O(N/blo*logN) N<blo,预处理,f[i][j]表示,i往上跳,每次跳j步,到根节点为止,权值和 ...

  10. noip训练 2018.10.22~2018.10.23

    day1 100+100+0=200 T1 稍微比划一下,发现其实就是缩点双,然后区间最小值的和 T2 发现答案为原lis|+1|-1 对每个点做从前最长上升序列以及从后最长下降序列, 想了半个小时怎 ...

随机推荐

  1. IO-02. 整数四则运算

    本题要求编写程序,计算2个正整数的和.差.积.商并输出.题目保证输入和输出全部在整型范围内. 输入格式: 输入在一行中给出2个正整数A和B. 输出格式: 在4行中按照格式“A 运算符 B = 结果”顺 ...

  2. 24种编程语言的Hello World程序

    24种编程语言的Hello World程序 这篇文章主要介绍了 24 种编程语言的 Hello World 程序,包括熟知的 Java.C 语言.C++.C#.Ruby.Python.PHP 等编程语 ...

  3. 【JZOJ3886】【长郡NOIP2014模拟10.22】道路维护

    CCC 最近徆多人投诉说C国的道路破损程度太大,以至亍无法通行 C国的政府徆重视这件事,但是最近财政有点紧,丌可能将所有的道路都进行维护,所以他们决定按照下述方案进行维护 将C国抽象成一个无向图,定义 ...

  4. 2019.10.22 用TCP实现服务端并发接收

    client import socket client = socket.socket() client.connect( ('127.0.0.1',8888) ) while 1: msg = in ...

  5. No PostCSS Config found解决办法

    npm install报错 Module build failed: Error: No PostCSS Config found 解决办法是同级package.json文件新建postcss.con ...

  6. 【Leetcode链表】分隔链表(86)

    题目 给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前. 你应当保留两个分区中每个节点的初始相对位置. 示例: 输入: head = 1->4 ...

  7. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第五章:渲染流水线

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第五章:渲染流水线 学习目标 了解几个用以表达真实场景的标志和2D图像 ...

  8. Ubuntu matplotlib显示中文乱码的解决方法

    https://blog.csdn.net/huuinn/article/details/78968966

  9. @NOIP2018 - D2T2@ 填数游戏

    目录 @题目描述@ @题解@ @代码@ @题目描述@ 小 D 特别喜欢玩游戏.这一天,他在玩一款填数游戏. 这个填数游戏的棋盘是一个 n×m 的矩形表格.玩家需要在表格的每个格子中填入一个数字(数字 ...

  10. 请注意更新TensorFlow 2.0的旧代码

    TensorFlow 2.0 将包含许多 API 变更,例如,对参数进行重新排序.重新命名符号和更改参数的默认值.手动执行所有这些变更不仅枯燥乏味,而且容易出错.为简化变更过程并让您尽可能顺畅地过渡到 ...