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

1. 使用TemplateSettings统一外观

TemplateSettings提供一组只读属性,用于在新建ControlTemplate时使用这些约定的属性。

譬如,修改HeaderedContentControl的ControlTemplate以呈现不同的外观,但各个ControlTemplate之间的HeaderedContentControl中的Margin和FontWeight想要保持统一。为了实现这个目的可以创建一个提供默认Margin和FontWeight值的HeaderedContentControlTemplateSettings类。实现如下:

HeaderedContentControlTemplateSettings.cs

public class HeaderedContentControlTemplateSettings: DependencyObject
{public Thickness HeaderMargin{get{return new Thickness(0, 0, 0, 8);}}public FontWeight HeaderFontWeight{get{return FontWeights.Normal;}}
}

HeaderedContentControl.cs

public HeaderedContentControl()
{this.DefaultStyleKey = typeof(HeaderedContentControl);TemplateSettings = new HeaderedContentControlTemplateSettings();
}public HeaderedContentControlTemplateSettings TemplateSettings { get; }

Generic.xaml

<ContentPresenter x:Name="HeaderContentPresenter"
                  Visibility="Collapsed"
                  Foreground="{ThemeResource TextControlHeaderForeground}"
                  Margin="{Binding RelativeSource={RelativeSource TemplatedParent},Path=TemplateSettings.HeaderMargin}"
                  FontWeight="{Binding RelativeSource={RelativeSource TemplatedParent},Path=TemplateSettings.HeaderFontWeight}"
                  Content="{TemplateBinding Header}"
                  ContentTemplate="{TemplateBinding HeaderTemplate}"/>

TemplateSettings类有约定的命名规则,默认以使用它的控件的名称作为前缀,以“-TemplateSettings”作为后缀。

UWP中有多个 TemplateSettings 类。 它们全部都在 Windows.UI.Xaml.Controls.Primitives 命名空间中,如ComboBox.TemplateSettings和ProgressBar.TemplateSettings。

2. 借用附加属性

以TextBox为例,TextBox中包含一个ScrollViewer部件,想要通过属性控制这个ScrollViewer,其中一种做法是在TextBox中添加各项属性,然后在ControlTemplate中通过TemplateBinding设置到ScrollViewer的对应属性。使用方式如下:

<TextBox HorizontalScrollMode="Auto"
         HorizontalScrollBarVisibility="Auto"
         VerticalScrollMode="Auto"
         VerticalScrollBarVisibility="Auto"
         IsHorizontalRailEnabled="True"
         IsVerticalRailEnabled="True"
         IsDeferredScrollingEnabled="True" />

假设真的这么做,TextBox就会多了很多个属性,而其它包含ScrollViewer的控件也很可能参考TextBox添加这一大批属性。

幸运的是ScrollViewer将这些属性做成了附加属性,其它控件可以借这些属性来用。实际的使用方式如下:

<TextBox ScrollViewer.HorizontalScrollMode="Auto"
         ScrollViewer.HorizontalScrollBarVisibility="Auto"
         ScrollViewer.VerticalScrollMode="Auto"
         ScrollViewer.VerticalScrollBarVisibility="Auto"
         ScrollViewer.IsHorizontalRailEnabled="True"
         ScrollViewer.IsVerticalRailEnabled="True"
         ScrollViewer.IsDeferredScrollingEnabled="True" />

在TextBox的ControlTemplate中,ScrollViewer是这样绑定到附加属性的:

<ScrollViewer x:Name="ContentElement"
    Grid.Row="1"
    HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}"
    HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
    VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}"
    VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
    IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}"
    IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}"
    IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
    Margin="{TemplateBinding BorderThickness}"
    Padding="{TemplateBinding Padding}"
    IsTabStop="False"
    AutomationProperties.AccessibilityView="Raw"
    ZoomMode="Disabled" />

如果控件像ScrollViewer那样被频繁地使用,可以考虑定义这样的附加属性,这样既方便通过属性定制外观,又可以少定义很多属性。唯一的坏处,就是用户根本不知道原来有这些属性可用。

以下是ScrollViewer定义的全部附加属性:

  • ScrollViewer.BringIntoViewOnFocusChange
  • ScrollViewer.HorizontalScrollBarVisibility
  • ScrollViewer.HorizontalScrollMode
  • ScrollViewer.IsDeferredScrollingEnabled
  • ScrollViewer.IsHorizontalRailEnabled
  • ScrollViewer.IsHorizontalScrollChainingEnabled
  • ScrollViewer.IsScrollInertiaEnabled
  • ScrollViewer.IsVerticalRailEnabled
  • ScrollViewer.IsVerticalScrollChainingEnabled
  • ScrollViewer.IsZoomChainingEnabled
  • ScrollViewer.IsZoomInertiaEnabled
  • ScrollViewer.VerticalScrollBarVisibility
  • ScrollViewer.VerticalScrollMode
  • ScrollViewer.ZoomMode

3. StyleTypedPropertyAttribute

想进一步开放对部件外观的控制,可以考虑添加一个Style属性。例如,前述例子中的DateTimeSelector中包含一个TimePicker部件,可以公开一个TimePickerStyle属性让TimePicker绑定到这个属性。

/// <summary>
/// 获取或设置TimePickerStyle的值
/// </summary>
public Style TimePickerStyle
{get { return (Style)GetValue(TimePickerStyleProperty); }set { SetValue(TimePickerStyleProperty, value); }
}

<TimePicker x:Name="TimeElement" Style="{TemplateBinding TimePickerStyle}"/>

为了让其他人清楚这个Style的TargetType,可以在DateTimeSelector类上添加StyleTypedPropertyAttribute:

[StyleTypedProperty(Property = "TimePickerStyle", StyleTargetType = typeof(TimePicker))]

4. IsTabStop

要在UI上使用“Tab”键导航到某个控件,需要将这个控件的IsTabStop设置为True(默认值就是True)。如果设置成False,不止不能导航到,而且还不能获得焦点。

IsTabStop是Control的属性,FrameworkElement并没有这个属性。

对于复合型控件(即ControlTemplate中包含其它控件的控件,譬如DateTimeSelector,它本身是一个控件,又包含CalendarDatePicker和TimePicker),很多时候需要将IsTabStop默认设置成False。

<StackPanel><TextBox Width="300"
             HorizontalAlignment="Left" /><local:DateTimeSelector  HorizontalAlignment="Left"
                             Margin="0,10" /><ComboBox  Width="300"
               HorizontalAlignment="Left" />
</StackPanel>

在上面这段XAML中,如果DateTimeSelector.IsTabStop=True,在TextBox上需要输入两次“Tab”DateTimeSelector内的CalendarDatePicker才能获得焦点,但用户通常期望的是按一次Tab就能导航到CalendarDatePicker。这是因为Tab的导航顺序是用深度优先算法搜索VisualTree上的Control。DateTimeSelector和CalendarDatePicker都是Control,Tab会让DateTimeSelector先获得焦点,然后才让CalendarDatePicker获得焦点。解决办法是将DateTimeSelector的IsTabStop设置为False,这样Tab会忽略DateTimeSelector,由于Tab的导航顺序是深度优先,所以先是CalendarDatePicker获得焦点,然后是TimePicker,然后才是ComboBox。

再重申一次,模板化控件的属性默认值要在DefaultStyle中设置,尽量不要在构造函数中设置。

5. 处理焦点外观

5.1 FocusVisual

FocusVisual指控件获得焦点时的视觉指示器,默认是一个围绕控件边界的矩形边框。通常只用Tab键导航并获得焦点FocusVisual才会显示。UWP提供了一组FucosVisual属性用于控制这个矩形边框的外观。

<RadioButton FocusVisualMargin="-10"
             FocusVisualPrimaryBrush="Red"
             FocusVisualPrimaryThickness="2"
             FocusVisualSecondaryBrush="Green"
             FocusVisualSecondaryThickness="3"
             Content="RadioButton"/>

其中 FocusVisualPrimary指外边框,FocusVisualSecondary指内边框。

使用UseSystemFocusVisuals="False"可以禁用默认的FocusVisual。

FocusVisual属性属于FrameworkElement,这意味着派生自FrameworkElement的元素理论上都可以由FocusVisual。

5.2 IsTemplateFocusTarget

IsTemplateFocusTarget附加属性是Control类提供的唯一一个附加属性。控件在获得焦点时会尝试从已加载的ControlTemplate中查找Control.IsTemplateFocusTarget="True"的UI元素,如果找到,就将FocusVisual绘制到这个元素的边界。

<ControlTemplate TargetType="RadioButton"><Grid x:Name="RootGrid"
          BorderBrush="{TemplateBinding BorderBrush}"
          BorderThickness="{TemplateBinding BorderThickness}"
          Background="{TemplateBinding Background}"><Grid.ColumnDefinitions><ColumnDefinition Width="20" /><ColumnDefinition Width="*" /></Grid.ColumnDefinitions>...<Grid Height="32" Control.IsTemplateFocusTarget="True"
              VerticalAlignment="Top">...</Grid><ContentPresenter x:Name="ContentPresenter"
                          AutomationProperties.AccessibilityView="Raw"
                          ContentTemplate="{TemplateBinding ContentTemplate}"
                          ContentTransitions="{TemplateBinding ContentTransitions}"
                          Content="{TemplateBinding Content}"
                          Grid.Column="1"
                          Foreground="{TemplateBinding Foreground}"
                          HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                          Margin="{TemplateBinding Padding}"
                          TextWrapping="Wrap"
                          VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /></Grid>
</ControlTemplate>

5.3 自定义FocusVisual

如果确实需要完全自定义FocusVisual的外观,可以重写ControlTemplate,在VisualStateManager.VisualStateGroups中加入名称为FocusStates的VisualSateGroup,其中包含三个VisualState:

  • Focused: 使用Tab导航并获得焦点的状态;
  • Unfocused: 没获得任何焦点的状态;
  • PointerFocused: 点击控件并获得焦点的状态;

Control自身已处理好在这三个状态中转换的逻辑,不需要额外写代码来转换状态。在ControlTemplate使用如下:

<Grid x:Name="RootGrid"
      Background="{TemplateBinding Background}"><VisualStateManager.VisualStateGroups><!--other visual state groups here--><VisualStateGroup x:Name="FocusStates"><VisualState x:Name="Focused"><Storyboard><DoubleAnimation Storyboard.TargetName="FocusVisual"
                                     Storyboard.TargetProperty="Opacity"
                                     To="1"
                                     Duration="0" /></Storyboard></VisualState><VisualState x:Name="Unfocused" /><VisualState x:Name="PointerFocused" /></VisualStateGroup></VisualStateManager.VisualStateGroups><ContentPresenter x:Name="ContentPresenter"
                      AutomationProperties.AccessibilityView="Raw"
                      BorderBrush="{TemplateBinding BorderBrush}"
                      BorderThickness="{TemplateBinding BorderThickness}"
                      ContentTemplate="{TemplateBinding ContentTemplate}"
                      ContentTransitions="{TemplateBinding ContentTransitions}"
                      Content="{TemplateBinding Content}"
                      HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                      Padding="{TemplateBinding Padding}"
                      VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" /><Rectangle x:Name="FocusVisual" StrokeThickness="1" Stroke="BlueViolet"  StrokeDashArray="4 2" Opacity="0"/>
</Grid>

6. 简化ControlTemplate

通过简化ControlTemplate可以有效提交UI的性能。先看一个反例:

<Border x:Name="Background"
        BorderBrush="{TemplateBinding BorderBrush}"
        BorderThickness="{TemplateBinding BorderThickness}"
        Background="White"
        CornerRadius="3"><Grid Background="{TemplateBinding Background}"
          Margin="1"><Border x:Name="BackgroundAnimation"
                Background="#FF448DCA"
                Opacity="0" /><Rectangle x:Name="BackgroundGradient"><Rectangle.Fill><LinearGradientBrush EndPoint=".7,1"
                                     StartPoint=".7,0"><GradientStop Color="#FFFFFFFF"
                                  Offset="0" /><GradientStop Color="#F9FFFFFF"
                                  Offset="0.375" /><GradientStop Color="#E5FFFFFF"
                                  Offset="0.625" /><GradientStop Color="#C6FFFFFF"
                                  Offset="1" /></LinearGradientBrush></Rectangle.Fill></Rectangle></Grid>
</Border>
<ContentPresenter x:Name="contentPresenter"
                  ContentTemplate="{TemplateBinding ContentTemplate}"
                  Content="{TemplateBinding Content}"
                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                  Margin="{TemplateBinding Padding}"
                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
<Rectangle x:Name="DisabledVisualElement"
           Fill="#FFFFFFFF"
           IsHitTestVisible="false"
           Opacity="0"
           RadiusY="3"
           RadiusX="3" />
<Rectangle x:Name="FocusVisualElement"
           IsHitTestVisible="false"
           Margin="1"
           Opacity="0"
           RadiusY="2"
           RadiusX="2"
           Stroke="#FF6DBDD1"
           StrokeThickness="1" />

这是Silverlight中Button的ControlTemplate(不包含VisualState)。复杂的XAML结构不止影响了性能,还做了错误的示范。

简化XAML结构对CPU使用率及性能开销都有好处。幸好现在的主流是扁平化的简单的设计,在UWP中按钮的模板被大大简化:

<ContentPresenter x:Name="ContentPresenter"
    BorderBrush="{TemplateBinding BorderBrush}"
    BorderThickness="{TemplateBinding BorderThickness}"
    Content="{TemplateBinding Content}"
    ContentTransitions="{TemplateBinding ContentTransitions}"
    ContentTemplate="{TemplateBinding ContentTemplate}"
    Padding="{TemplateBinding Padding}"
    HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
    VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
    AutomationProperties.AccessibilityView="Raw" />

以我的经验来说,控件层级UI尽量保持简洁,或者与系统保持一致,后期维护起来也更简单,出错几率更少,性能也会更好(通常自己设计的ControlTemplate性能都不会比系统自带的好)。

7. 缩短过渡动画时间

为了给人系统流畅的感觉,过渡动画通常限制在1秒以内。曾经看过一个说法:把设计动画时觉得合理的时间,再缩短一半才是合适的。

另外,操作后0.5秒内要给出反应,否则用户会以为系统没有反应,甚至有可能重复操作。

8. 符合操作系统的操作习惯

以Windows平台来说,典型的错误是将约定俗成的“OK、Cancel”顺序改成“Cancel、OK”,甚至同一个程序中同时存在两种状况。

例如这个对话框,一不小心就点击左边的“取消”按钮了。

9. 符合典型的GUI设计原则

在控件层级就应该将UI设计成符合设计原则,例如对齐,使用字体和颜色突出主要内容,易于操作等。

[UWP]了解模板化控件(9):UI指南相关推荐

  1. [UWP]了解模板化控件(7):支持Command

    原文:[UWP]了解模板化控件(7):支持Command 以我的经验来说,要让TemplatedControl支持Command的需求不会很多,大部分情况用附加属性解决这个需求会更便利些,譬如UWPC ...

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

    原文:[UWP]了解模板化控件(4):TemplatePart 1. TemplatePart TemplatePart(部件)是指ControlTemplate中的命名元素.控件逻辑预期这些部分存在 ...

  3. [UWP]了解模板化控件(6):使用附加属性

    原文:[UWP]了解模板化控件(6):使用附加属性 1. 基本需求 之前的ContentView2添加了PointerOver等效果,和TextBox等本来就有Header的控件放在一起反而变得鹤立鸡 ...

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

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

  5. [UWP]做个调皮的BusyIndicator

    1. 前言 最近突然想要个BusyIndicator.做过WPF开发的程序员对BusyIndicator应该不陌生,Extended WPF Toolkit 提供了BusyIndicator的开源实现 ...

  6. 微软把UWP定位成业务线应用程序开发平台

    微软把UWP定位成传统业务线(LOB)应用程序开发平台,以使用Windows Template Studio实现快速应用程序开发为重点.但是,为了把LOB开发人员吸引到UWP平台,他们在做的事情不止这 ...

  7. 固定ui大小_Material Design 响应式UI

    材料设计中的响应式布局适应任何可能的屏幕尺寸.此UI指南包括一个灵活的网格,可确保布局之间的一致性,关于内容如何在不同屏幕上回流的断点详细信息,以及应用程序如何从小屏幕扩展到超大屏幕的说明. 断点 为 ...

  8. 14 个最佳 Vue UI 组件库推荐

    Vue.js 是无数前端开发人员首选的 JavaScript 框架,因为它易于学习和使用. 组件是 Vue 框架的核心--它们可以帮助你更快地从想法转变为功能性 UI. 在这个集合中,您可以找到最好的 ...

  9. Unity文档——优化UI控件

    优化UI控件 版本检查:2017.3 - 难度:高级 Optimizing Unity UI指南的这一部分重点介绍特定于某些类型的UI控件的问题.虽然大多数UI控件在性能方面都相对类似,但两个突出显示 ...

最新文章

  1. linux as 4 内核版本,Linux Redhat AS 4中编译内核与以往版本的区别(转载)
  2. 平衡二叉树平衡因子怎么计算_平衡二叉树(AVL Tree)旋转机制分析
  3. Hadoop大数据--Mapreduce程序运行并发度
  4. DCMTK:OFDate, OFTime and OFDateTime类的测试程序
  5. 【maven】改造已有项目
  6. 使用Apache Flume抓取数据(1)
  7. foreach 和 map_每个开发人员都应该知道的forEach()和map()之间的差异
  8. wrodpress登录mysql_登陆wordpress后台MySQL错误
  9. OCR引擎-Tesseract
  10. 闻乐天授江州司马 [唐] 元稹
  11. hibernate oracle 读写分离_ASP.NET CORE 国产最火前后端完全分离框架BCVP
  12. 下载UBUNTU的可用链接
  13. Kinect 数据录制与回放
  14. 木材材积表快速计算器_原木材积计算器
  15. 2021年中国食糖产销量及重点企业对比分析[图]
  16. 用python画篮球场_如何使用 Python 创建一个 NBA 得分图?
  17. 企业WiFi安全管家有何好处?数据安全最重要
  18. 互联网大厂面试笔试题大全
  19. Error: EPERM: operation not permitted, mkdir......
  20. 音乐是我们日常生活中不可缺少的

热门文章

  1. c语言勤工助学信息管理系统,勤工助学
  2. 翼次元学院的的少儿有艺翼行动 ICAE@Kid.ArtWing+国际少儿艺术展
  3. 网站建设常用英文翻译对照
  4. 小麦盒子cdn_CDN加速,提高网站访问速度的最佳方法
  5. 朱江明不断提高产品硬实力
  6. ZZULIOJ:1006求等差数列的和
  7. 【深耕冷链物流】HEGERLS移动冷库生产厂家 移动冷库箱成市场追捧的焦点
  8. Android Framework——zygote 启动 SystemServer
  9. 关于clash退出后,华硕电脑连不上网了
  10. 2021-2022 ICPC, NERC, Southern and Volga Russian Regional Contest Smash the Trash(二分)