zoukankan      html  css  js  c++  java
  • [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

  • 相关阅读:
    进度条
    html5 表单新增事件
    html5 表单的新增type属性
    html5 表单的新增元素
    html5 语义化标签
    jq 手风琴案例
    codeforces 702D D. Road to Post Office(数学)
    codeforces 702C C. Cellular Network(水题)
    codeforces 702B B. Powers of Two(水题)
    codeforces 702A A. Maximum Increase(水题)
  • 原文地址:https://www.cnblogs.com/dino623/p/TemplatedControlPrinciple.html
Copyright © 2011-2022 走看看