1. 概述

来看看这段XMAL:

<StackPanel Width="300">
<TextBox Header="TextBox" />
<ComboBox Header="ComboBox" HorizontalAlignment="Stretch"/>
<AutoSuggestBox Header="AutoSuggestBox" />
<TextBlock Text="ListBox" />
<ListBox>
<ListBoxItem Content="ListBoxItem 1" />
<ListBoxItem Content="ListBoxItem 2" />
<ListBoxItem Content="ListBoxItem 3" />
</ListBox>
</StackPanel>

是不是觉得它们中出了一个叛徒?这个示例中除了ListBox控件其它都自带Header,但是ListBox没有Header属性,只好用一个TextBlock模仿它的Header。这样就带来一个问题:只有ListBox的Header高度和其它控件不一致。

既然现在讨论的是自定义控件,这里就用自定义控件的方式解决这个问题。首先想到最简单的方法,就是自定义一个HeaderedContentControl,如名字所示,这个控件继承自ContentControl并拥有Header属性,用起来大概是这样:

<HeaderedContentControl Header="ListBox">
<ListBox/>
</HeaderedContentControl>

这样,只要在HeaderedContentControl的样式中模仿其它含Header属性的控件,就能统一Header的外观。

WPF中本来就有这个控件,它是Expander、GroupBox、TabItem等诸多拥有Header属性的控件的基类,十分方便好用。UWP中模仿这个控件很简单,而且很适合用来学习自定义控件的进阶知识。

2. 定义HeaderedContentControl结构

比起WPF,借鉴Silverlight的HeaderedContentControl比较好,因为Silverlight的比较简单。HeaderedContentControl只需要在继承ContentControl后添加两个属性:Header和HeaderTemplate。

public class HeaderedContentControl : ContentControl
{
public HeaderedContentControl()
{
this.DefaultStyleKey = typeof(HeaderedContentControl);
} /// <summary>
/// 获取或设置Header的值
/// </summary>
public object Header
{
get { return (object)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
} /// <summary>
/// 标识 Header 依赖属性。
/// </summary>
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(object), typeof(HeaderedContentControl), new PropertyMetadata(null, OnHeaderChanged)); private static void OnHeaderChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
HeaderedContentControl target = obj as HeaderedContentControl;
object oldValue = (object)args.OldValue;
object newValue = (object)args.NewValue;
if (oldValue != newValue)
target.OnHeaderChanged(oldValue, newValue);
} /// <summary>
/// 获取或设置HeaderTemplate的值
/// </summary>
public DataTemplate HeaderTemplate
{
get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
set { SetValue(HeaderTemplateProperty, value); }
} /// <summary>
/// 标识 HeaderTemplate 依赖属性。
/// </summary>
public static readonly DependencyProperty HeaderTemplateProperty =
DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(HeaderedContentControl), new PropertyMetadata(null, OnHeaderTemplateChanged)); private static void OnHeaderTemplateChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
HeaderedContentControl target = obj as HeaderedContentControl;
DataTemplate oldValue = (DataTemplate)args.OldValue;
DataTemplate newValue = (DataTemplate)args.NewValue;
if (oldValue != newValue)
target.OnHeaderTemplateChanged(oldValue, newValue);
} protected virtual void OnHeaderChanged(object oldValue, object newValue)
{
} protected virtual void OnHeaderTemplateChanged(DataTemplate oldValue, DataTemplate newValue)
{
}
}

3. 设计样式

在C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\10.0.14393.0\Generic\generic.xaml中找到ContentControl的样式。

再从TextBox的Style中找到HeaderContentPresenter

提示: 随便找个有ThemeResource的XAML,譬如Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}",在资源名称(ApplicationPageBackgroundThemeBrush)上按"F12",即可导航到存放ThemeResource的generic.xaml。

组合起来,HeaderedContentControl的默认样式就完成了。

<Style TargetType="local:HeaderedContentControl">
<Setter Property="HorizontalContentAlignment"
Value="Left" />
<Setter Property="VerticalContentAlignment"
Value="Top" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:HeaderedContentControl">
<StackPanel>
<ContentPresenter x:Name="HeaderContentPresenter"
Foreground="{ThemeResource TextControlHeaderForeground}"
Margin="0,0,0,8"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
FontWeight="Normal" />
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}"
ContentTransitions="{TemplateBinding ContentTransitions}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

4. 使用

 <StackPanel Visibility="Collapsed">
<TextBox Header="TextBox" />
<ComboBox Header="ComboBox"
HorizontalAlignment="Stretch" />
<AutoSuggestBox Header="AutoSuggestBox" />
<local:HeaderedContentControl Header="ListBox">
<ListBox>
<ListBoxItem Content="ListBoxItem 1" />
<ListBoxItem Content="ListBoxItem 2" />
<ListBoxItem Content="ListBoxItem 3" />
</ListBox>
</local:HeaderedContentControl>
</StackPanel>

调用代码及效果。这样外观就统一了。

注意: 我移除了 x:DeferLoadStrategy="Lazy"这句,这个知识点比较适合放在有关性能的主题里讨论。

[UWP]了解模板化控件(3):实现HeaderedContentControl的更多相关文章

  1. [UWP]了解模板化控件(4):TemplatePart

    1. TemplatePart TemplatePart(部件)是指ControlTemplate中的命名元素.控件逻辑预期这些部分存在于ControlTemplate中,并且使用protected ...

  2. [UWP]了解模板化控件(8):ItemsControl

    1. 模仿ItemsControl 顾名思义,ItemsControl是展示一组数据的控件,它是UWP UI系统中最重要的控件之一,和展示单一数据的ContentControl构成了UWP UI的绝大 ...

  3. [UWP]了解模板化控件(9):UI指南

    1. 使用TemplateSettings统一外观 TemplateSettings提供一组只读属性,用于在新建ControlTemplate时使用这些约定的属性. 譬如,修改HeaderedCont ...

  4. [UWP]合体姿势不对的HeaderedContentControl

    1. 前言 HeaderedContentControl是WPF中就存在的控件,这个控件的功能很简单:提供Header和Content两个属性,在UI上创建两个ContentPresenter并分别绑 ...

  5. [UWP]了解模板化控件(1):基础知识

    1.概述 UWP允许开发者通过两种方式创建自定义的控件:UserControl和TemplatedControl(模板化控件).这个主题主要讲述如何创建和理解模板化控件,目标是能理解模板化控件常见的知 ...

  6. [UWP]了解模板化控件(2):模仿ContentControl

    ContentControl是最简单的TemplatedControl,而且它在UWP出场频率很高.ContentControl和Panel是VisualTree的基础,可以说几乎所有VisualTr ...

  7. [UWP]了解模板化控件(5):VisualState

    1. 功能需求 使用TemplatePart实现上篇文章的两个需求(Header为空时隐藏HeaderContentPresenter,鼠标没有放在控件上时HeaderContentPresent半透 ...

  8. [UWP]了解模板化控件(10):原则与技巧

    1. 原则 推荐以符合以下原则的方式编写模板化控件: 选择合适的父类:选择合适的父类可以节省大量的工作,从UWP自带的控件中选择父类是最安全的做法,通常的选择是Control.ContentContr ...

  9. [UWP]了解模板化控件(2.1):理解ContentControl

    UWP的UI主要由布局容器和内容控件(ContentControl)组成.布局容器是指Grid.StackPanel等继承自Panel,可以拥有多个子元素的类.与此相对,ContentControl则 ...

随机推荐

  1. 修改WebView

    http://jwdev.cn/2015/09/28/use-javascript-to-delete-web-element/ 

  2. keepalived原理

    Keepalived的作用是检测服务器的状态,如果有一台web服务器死机,或工作出现故障,Keepalived将检测到,并将有故障的服务器从系统中剔除,同时使用其他服务器代替该服务器的工作,当服务器工 ...

  3. django源码简析——后台程序入口

    这一年一直在用云笔记,平时记录一些tips或者问题很方便,所以也就不再用博客进行记录,还是想把最近学习到的一些东西和大家作以分享,也能够对自己做一个总结.工作中主要基于django框架,进行项目的开发 ...

  4. .Net程序员学用Oracle系列(17):数据库管理工具(SQL Plus)

    1.数据库管理工具概述 2.SQL Plus 实用命令参考 2.1.连接/断开命令 2.2.执行 SQL 语句 2.3.执行 PL/SQL 语句 2.4.文件操作命令 2.5.修改用户密码 2.6.执 ...

  5. DLL 导出类

    MyMathFun.h #pragma once // #ifdef DLLCLASS_API // #define DLLCLASS_API _declspec(dllimport) // #els ...

  6. linux下常用语言的语法检查插件整理

    linux下常用语言的语法检查插件 可以结合vim语法检查插件syntastic使用,具体请参考syntastic使用说明 如php,sql,json,css,js,html,shell,c等语法插件 ...

  7. 【Java基础】String StringBuffer StringBuilder

    String String是不可变的 我们都知道String不是基本数据类型,而是一个对象,并且是final类型的,不可变的.(public final class String) 查看以下代码: S ...

  8. AR入门系列-03-在unity中将调试好的Vuforia项目导出为APK

    先设置build settings 选中Android后点击Player Settings Product Name设置安装后的Android程序的名字 Bundle Identifier 设置apk ...

  9. String类的indexOf方法的用法和举例

    2017年3月3号博主第一次去郑州互联网公司面试,背景是这样的我先前去了农大龙子湖校园招聘投简历,然后第二天去面试了那经历可以说是很失败的一次面试,当然这跟自己的水平有关了接下来重点讲一下面试的题目: ...

  10. JavaScript的连续赋值的思考

    下面是一个连续赋值的例子: 在这里面,先创建a对象并添加n:1,然后将a对象赋值给b,这时候a和b引用同一个对象.最后我们要着重理解这最后一行代码 a.x = a = {n:2}; 按照我们平常的思维 ...