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

简介: 原文:[UWP]了解模板化控件(10):原则与技巧1. 原则 推荐以符合以下原则的方式编写模板化控件: 选择合适的父类:选择合适的父类可以节省大量的工作,从UWP自带的控件中选择父类是最安全的做法,通常的选择是Control、ContentControl、ItemsControl,也可以选择从RangeBase、Selector中。
原文: [UWP]了解模板化控件(10):原则与技巧

1. 原则

推荐以符合以下原则的方式编写模板化控件:

  • 选择合适的父类:选择合适的父类可以节省大量的工作,从UWP自带的控件中选择父类是最安全的做法,通常的选择是Control、ContentControl、ItemsControl,也可以选择从RangeBase、Selector中。
  • 代码和UI分离:通常控件的开发者不能控制最终用户怎么重写ControlTemplate,尽量做到代码和UI分离可以避免更多的异常。而且先写完所有代码,再用Blend实现UI,会比在代码和UI间交错地工作更高效。
  • 使用依赖属性:控件的使用者会认为所有控件的属性都是可以绑定的,除非有特殊理由不要破坏这个约定俗成的规则。
  • 不要实施严格的模版约定:模版约定指TemplatePart和TemplateVisualState,应该尽可能减少约定,在没有遵循模版约定的任何一项时也不应该引发异常,要允许ControlTemplate的开发者可以通过删除某项TemplatePart或VisualState来屏蔽某项功能。

2. 命名模式

一个控件是否好用,很大一部分取决于名称。好的命名能让使用者用起来更得心应手,坏的命名只会让代码更混淆。下面总结了UWP控件命名的一般模式:

  • 根据控件实际功能命名,譬如Button。
  • 以父类型的名字作为后缀,如RepeatButton。
  • 使用常用的后缀,如-Control、-Box、-Item、-View、-Viewer、-Bar。
  • 如果控件如现有控件功能相同,可以考虑使用Extend-、Advanced-、Simple-做前缀;也可以使用公司名做前缀,譬如ComponentOne公司的C1DataGrid。
  • 可以使用-ex做后缀,但容易和扩展方法类混淆。
  • ItemsControl派生类的子元素控件要使用父元素名称做前缀、-Item做后缀,譬如ComboBox的子元素ComboBoxItem。
  • 如果控件通过鼠标选取内容(通常会打开一个Popup),可以使用-Picker做后缀。
  • 尽量不要用-Panel做后缀,通常只有继承Panel的才会用这种方式命名,如StackPanel。但也有ControlPanel这种例外。

3. 小技巧

对于复杂的控件或控件库项目,以下技巧可能对你有帮助。

3.1 partial class

在编写模板化控件时,依赖属性最大的缺点会暴露无遗:它太复杂了。一个完整的依赖属性定义可以有20行(属性标识符、属性包装器、PropertyChangedCallback等),而且其中一部分是静态的,另外一部分不是,在类中将一个依赖属性的所有部分放在一起,还是按静态、非静态的顺序存放,这也可能引起争论。

一个好的做法是使用单独的partial 类存放所有依赖属性,具体可参考UWPCommunityToolkit的AdaptiveGridView.Properties.cs

3.2 合并资源字典

如果一个项目的模板化控件太多,Generic.xaml会异常的复杂,可以将各个控件的资源文件分开存放,再在Generic.xaml中合并它们。具体可参考UWPCommunityToolkit的做法:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/HamburgerMenu/HamburgerMenu.xaml" />
        <ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/RangeSelector/RangeSelector.xaml" />
        <ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/SlidableListItem/SlidableListItem.xaml" />
        <ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/ImageEx/ImageEx.xaml" />
        <ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.xaml" />
        <ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/RadialGauge/RadialGauge.xaml" />
        <ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/PullToRefreshListView/PullToRefreshListView.xaml" />
        <ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/RotatorTile/RotatorTile.xaml" />
        <ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/BladeView/BladeView.xaml" />
        <ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/GridSplitter/GridSplitter.xaml" />
        <ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.xaml" />
        <ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/ScrollHeader/ScrollHeader.xaml" />
        <ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/MosaicControl/MosaicControl.xaml" />
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

3.3 统一管理VisualState

在控件库中,很多VisualState都是通用的,譬如Normal、Disabled、Selected,把它们全都写进一个VisualStates的类中可以方便调用。SilverlightToolkit即是使用这种方式处理:VisualStates.cs

/// <summary>
/// Names and helpers for visual states in the controls.
/// </summary>
internal static class VisualStates
{
    #region GroupCommon
    /// <summary>
    /// Common state group.
    /// </summary>
    public const string GroupCommon = "CommonStates";

    /// <summary>
    /// Normal state of the Common state group.
    /// </summary>
    public const string StateNormal = "Normal";

    /// <summary>
    /// Normal state of the Common state group.
    /// </summary>
    public const string StateReadOnly = "ReadOnly";

    /// <summary>
    /// MouseOver state of the Common state group.
    /// </summary>
    public const string StateMouseOver = "MouseOver";

    /// <summary>
    /// Pressed state of the Common state group.
    /// </summary>
    public const string StatePressed = "Pressed";

    /// <summary>
    /// Disabled state of the Common state group.
    /// </summary>
    public const string StateDisabled = "Disabled";
    #endregion GroupCommon
}

4. 结语

这个系列的主旨是讲解常见的模板化控件技术,希望了解这些技术后能更轻松地构造自己的控件,对理解开源控件库的代码也有一定的帮助。
职业生涯中看过很多程序员都不会写模板化控件(毕竟大部分场景使用UserControl或修改ControlTemplate就能解决),希望这个系列可以帮到想要学习模板化控件的开发者。
虽然写得很长,其实已经尽量精简文字和内容了。平时我看到很长的文章,都会“保存到Pocket”,然后就再也没读过。汲取了这个教训,这次的文章分成多篇,尽量每篇都控制在可以三五分钟内看完。
这个系列的内容有很多来自于WPF/Silverlight的经验,虽然有一些小出入,基本上可以用在WPF的自定义控件。
创建模板化控件通常意味着会被其它开发者使用,那么就应该遵守Framework Design Guidelines
如有错漏请指出。

5. 参考

控件模板
Silverlight 控件自定义
UWPCommunityToolkit

目录
相关文章
|
程序员 C# C++
2000条你应知的WPF小姿势 基础篇<22-27 WPF生命周期, 基础类等>
2000条你应知的WPF小姿势 基础篇<22-27 WPF生命周期, 基础类等>
48 0
|
存储 开发框架 .NET
2000条你应知的WPF小姿势 基础篇<51-56 依赖属性>
2000条你应知的WPF小姿势 基础篇<51-56 依赖属性>
45 0
|
XML 设计模式 Android开发
Android自定义控件 | 运用策略模式扩展单选按钮和产品经理成为好朋友
变化是永恒的,产品需求稳定不变是不可能的,和产品经理互怼是没有用的,但有一个方向是可以努力的:让代码更有弹性,以不变应万变。 继上一次发版前突然变更单选按钮样式之后,又新增了两个和选项按钮有关的需求。
124 0
|
C#
艾伟_转载:WPF/Silverlight陷阱:XAML自定义控件的嵌套内容无法通过名称访问
为了说明这个问题,假定我们需要实现一个具有特殊功能的按钮控件。编写Xaml文件如下: Button> 对 Code Behind类,唯一的改动是把向导生成的基类从UserControl改成Button: public partial class XamlButton : Button{    ...
1079 0
|
C#
[WPF 容易忽视的细节] —— x:Name与Name属性
原文:[WPF 容易忽视的细节] —— x:Name与Name属性 一、前言 WPF使用XAML来对界面进行编写,界面与后台逻辑分离。我们也可以写Style、Trigger来实现一些界面效果, 这些都是通过Name来定位控件的,例如Setter.TargetName、Trigger.SourceName和Binding的ElementName等。
1121 0
|
C# 开发者
[UWP]了解模板化控件(5):VisualState
原文:[UWP]了解模板化控件(5):VisualState 1. 功能需求 使用TemplatePart实现上篇文章的两个需求(Header为空时隐藏HeaderContentPresenter,鼠标没有放在控件上时HeaderContentPresent半透明),虽然功能已经实现,但这样实现的话基本上也就别想扩展了。
1208 0
|
UED 容器
[UWP]了解模板化控件(2.1):理解ContentControl
原文:[UWP]了解模板化控件(2.1):理解ContentControl UWP的UI主要由布局容器和内容控件(ContentControl)组成。布局容器是指Grid、StackPanel等继承自Panel,可以拥有多个子元素的类。
1245 0
|
C# Windows
[UWP]了解模板化控件(3):实现HeaderedContentControl
原文:[UWP]了解模板化控件(3):实现HeaderedContentControl 1. 概述 来看看这段XMAL: 是不是觉得它们中出了一个叛徒?这个示例中除了ListBox控件其它都自带Header,但是ListBox没有Header属性,只好用一个TextBlock模仿它的Header。
1190 0
[UWP]了解模板化控件(1):基础知识
原文:[UWP]了解模板化控件(1):基础知识 1.概述 UWP允许开发者通过两种方式创建自定义的控件:UserControl和TemplatedControl(模板化控件)。这个主题主要讲述如何创建和理解模板化控件,目标是能理解模板化控件常见的知识点,并且可以创建扩展性良好的模板化控件。
1140 0
|
开发者
[UWP]了解模板化控件(2):模仿ContentControl
原文:[UWP]了解模板化控件(2):模仿ContentControl ContentControl是最简单的TemplatedControl,而且它在UWP出场频率很高。ContentControl和Panel是VisualTree的基础,可以说几乎所有VisualTree上的UI元素的父节点中总有一个ContentControl或Panel。
1101 0