1. 为什么选择Aero2

除了以外观为卖点的控件库,WPF的控件库都默认使用“素颜”的外观,然后再提供一些主题包。这样做的最大好处是可以和原生控件或其它控件库兼容,而且对于大部分人来说模仿原生的主题也比自己设计一套好看的UI容易得多。

WPF有以下几种原生主题

主题文件 桌面主题
Classic.xaml Windows XP 操作系统上的经典 Windows 外观(Windows 95、Windows 98 和 Windows 2000)。
Luna.NormalColor.xaml Windows XP 上的默认蓝色主题。
Luna.Homestead.xaml Windows XP 上的橄榄色主题。
Luna.Metallic.xaml Windows XP 上的银色主题。
Royale.NormalColor.xaml Windows XP Media Center Edition 操作系统上的默认主题。
Aero.NormalColor.xaml Windows Vista 操作系统上的默认主题。

Win8之后WPF更新了Aero2和AeroLite两种主题,关于Aero、Aero2、AeroLite具体可见这个网页。再之后微软就没有更新WPF主题了

如果不在代码中指定主题,WPF大概就是用这段代码确定主题,也就是说默认是Aero,如果在Win8或以上自动转为Aero2:

_themeName = themeName.ToString();
_themeName = Path.GetFileNameWithoutExtension(_themeName); if(String.Compare(_themeName, "aero", StringComparison.OrdinalIgnoreCase) == 0 && Utilities.IsOSWindows8OrNewer)
{
_themeName = "Aero2";
}

由于我暂时不想兼容Win7,而且我又不讨厌Win8的风格,所以Kino.Toolkit.Wpf直接选择了Aero2作为控件库的主题。

2. Aero2的设计

上面分别是Aero2(左)和Aero(右)的Button在几种状态下的外观,从中可以看出Aero2的设计是扁平化的风格,移除圆角、渐变等装饰性元素,以实用为目的。这样一来控件模板的结构更加简单(如Button只有Border和ContentPresenter 两个元素),移除装饰性元素更节省空间,而且渐变在质量较差或阳光下很影响阅读,圆角则是占用更多空间而且在低分辨率下表现不好。

总的来说就是以实用为目的,尽量简单,减少装饰性元素。

3. 以Button为例,谈谈Aero2中的细节:尺寸、颜色、字体、动画

<Style x:Key="FocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<SolidColorBrush x:Key="Button.Static.Background" Color="#FFDDDDDD"/>
<SolidColorBrush x:Key="Button.Static.Border" Color="#FF707070"/>
<SolidColorBrush x:Key="Button.MouseOver.Background" Color="#FFBEE6FD"/>
<SolidColorBrush x:Key="Button.MouseOver.Border" Color="#FF3C7FB1"/>
<SolidColorBrush x:Key="Button.Pressed.Background" Color="#FFC4E5F6"/>
<SolidColorBrush x:Key="Button.Pressed.Border" Color="#FF2C628B"/>
<SolidColorBrush x:Key="Button.Disabled.Background" Color="#FFF4F4F4"/>
<SolidColorBrush x:Key="Button.Disabled.Border" Color="#FFADB2B5"/>
<SolidColorBrush x:Key="Button.Disabled.Foreground" Color="#FF838383"/>
<Style TargetType="{x:Type Button}">
<Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
<Setter Property="Background" Value="{StaticResource Button.Static.Background}"/>
<Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsDefaulted" Value="true">
<Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.MouseOver.Border}"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.Disabled.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Disabled.Border}"/>
<Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Disabled.Foreground}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

这是Aero2使用Blend获取的Button控件模板。因为Button是最基础最常用最具代表性的控件,所以以它为例谈谈Aero2主题中的各种细节。

3.1 尺寸

首先考虑下控件是否有必要有统一的尺寸。

我记得很久很久以前微软有份文档要求桌面按钮的高度是22像素(有可能是23,已经不记得了)。微软自己有没有遵守?真是太看得起微软了。

就以IE来说,上图从上到下几组按钮的高度分别是21,28,24像素。

这个页面大部分按钮都是28,只有中间那个“将所有区域重置为默认级别”是30像素。

可以看出,微软一直以来开放、包容、拥抱多元化的策略,在IE上可以说是完美体现。作为对比我看了看Chrome的类似按钮,统一为32像素,看来有很好地执行Material Design中"所有距离,尺寸都应该是8dp的整数倍"的要求(到处都是8,可以说深得中国人欢心)。

<Rectangle Height="1" Fill="Gray" />
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center">
<Button Content="Button" VerticalAlignment="Center" />
<TextBox Text="TextBox" VerticalAlignment="Center" />
<PasswordBox Password="password" VerticalAlignment="Center" />
<ComboBox VerticalAlignment="Center">
<ComboBoxItem Content="ComboBox" IsSelected="True"/>
</ComboBox>
<DatePicker VerticalAlignment="Center"/>
</StackPanel>
<Rectangle Height="1" Fill="Gray" />

顺便拿Button与WPF的其它控件、及UWP的相同控件做横向对比,使用相同的XAML产生的UI如上图所示(上为UWP,下为WPF)。可以看出UWP的表单元素基本上完全统一高度,而WPF则根据内容自适应。

总结来说,WPF原生控件通常没有设置具体的尺寸,所以模仿Aero2主题的自定义控件也不应该改变这个行为,只需控件要能够清晰展示数据及容易操作就好(也就是符合基本的UI设计原则)。

我建议在实际项目中根据需要使用样式将按钮的高度统一为24、28、32像素(The sizes, margins, and positions of UI elements should always be in multiples of 4 epx in your UWP apps.,因为Windows系统的缩放比例总是5/4(125%)、6/4(150%)、7/4(175%)、8/4(200%),所以尺寸最好是4的倍数,真不吉利)。

3.2 颜色

从Button的控件模板可以看到Button的字体颜色使用了{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}。WPF为系统环境封装了三个类,用于访问系统环境设置:

  • SystemFonts,包含公开有关字体的系统资源的属性。
  • SystemColors,包含与系统显示元素相对应的系统颜色、系统画笔和系统资源键。
  • SystemParameters,包含可用来查询系统设置的属性。

使用方式可以参考资源帮助主题

这些设置只应用作参考,可以看到Button也只是主要使用了ControlTextBrushKey,Aero2主题有自己的颜色风格,不会跟随系统而改变。

再次横向比较一下,这次试用Disabled状态作比较,可以看到每个控件的边框无论在Enabled或Disabled的状态下边框颜色都不一样(除了TextBox和PasswordBox,他们关系好)。

因为看不到Aero2在颜色上有什么要求,我的建议是,如果自定义的控件长得像TextBox就使用TextBox的颜色设置,长得像Button的就用Button,总之尽量模仿原生控件,颜色也尽量使用蓝色或灰色就可以了。

3.3 字体

只有Menu、StatusBar、Toolbar等有限几个控件会使用SystemFonts的值,其它都可以使用继承值。这样可以方便地通过在根元素设置字体来统一字体的使用。

3.4 动画

几乎、完全、没有。也许是为了兼顾Windows的UI,或者照顾低端配置的电脑,Aero2里真的几乎完全看不到动画效果,一眼看过去所有Storyboard的Duration都是0。也好,以和Aero2统一风格作借口我也可以不做动画啦。

最近我发现lindexi这样介绍我:

其实我也并不是那么喜欢亲自写动画,只是WPF和UWP里连最基本的都没提供所以我才在这方面鼓起干劲努力了一把。

4. 提供VisualState

<ControlTemplate TargetType="local:KinoButton">
<Border x:Name="border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="true">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background)"
Storyboard.TargetName="border">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{StaticResource Button.MouseOver.Background}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush)"
Storyboard.TargetName="border">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{StaticResource Button.MouseOver.Border}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush)"
Storyboard.TargetName="border">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{StaticResource Button.Pressed.Border}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background)"
Storyboard.TargetName="border">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{StaticResource Button.Pressed.Background}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(TextElement.Foreground)"
Storyboard.TargetName="contentPresenter">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{StaticResource Button.Disabled.Foreground}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background)"
Storyboard.TargetName="border">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{StaticResource Button.Disabled.Background}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush)"
Storyboard.TargetName="border">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{StaticResource Button.Disabled.Border}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!--comecode--> <ContentPresenter x:Name="contentPresenter"
Grid.Column="1"
Focusable="False"
RecognizesAccessKey="True"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsDefaulted"
Value="true">
<Setter Property="BorderBrush"
TargetName="border"
Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

出于好玩,我把KinoButton(主要是在Button的基础上添加了Icon的功能)的控件模板从使用Trigger改为尽量使用VisualState,这样做没什么实际意义,真的只是好玩而已,而且XAML的行数还增加了不少。

不过在实现其它自定义控件的时候我也比较倾向提供VisualState,因为这样可以明确指出控件外观有几种状态,避免了混轮,而且提供了VisualState可以更方便扩展。这点WPF原生控件也是一样的,它们很多都没有声明TemplateVisualState,而且ControlTemplate也没有使用VisualState,但使用Blend编辑控件模板还是可以在“状态”面板看到它的TemplateVisualState(其中FocusStates和ValidationStates可以不使用,如果修改了这两组状态也就是让控件外观更个性化而已)。对最终用户来说多一个选择并不是坏事。

5. 结语

通过这篇文章读者应该对Aero2的风格有了一定程度的了解。更多Aero和Aero2的相关信息可以看这个Github项目

很多控件库都会提供额外的主题包,这点可以放到后面再考虑。

6. 参考

Control样式和模板

资源帮助主题

PresentationTheme.Aero

[WPF自定义控件库]以Button为例谈谈如何模仿Aero2主题的更多相关文章

  1. [WPF自定义控件库] 让Form在加载后自动获得焦点

    原文:[WPF自定义控件库] 让Form在加载后自动获得焦点 1. 需求 加载后让第一个输入框或者焦点是个很基本的功能,典型的如"登录"对话框.一般来说"登录" ...

  2. WPF 如何创建自己的WPF自定义控件库

    在我们平时的项目中,我们经常需要一套自己的自定义控件库,这个特别是在Prism这种框架下面进行开发的时候,每个人都使用一套统一的控件,这样才不会每个人由于界面不统一而造成的整个软件系统千差万别,所以我 ...

  3. [WPF自定义控件库] 关于ScrollViewer和滚动轮劫持(scroll-wheel-hijack)

    原文:[WPF自定义控件库] 关于ScrollViewer和滚动轮劫持(scroll-wheel-hijack) 1. 什么是滚动轮劫持# 这篇文章介绍一个很简单的继承自ScrollViewer的控件 ...

  4. [WPF自定义控件库]使用WindowChrome自定义RibbonWindow

    原文:[WPF自定义控件库]使用WindowChrome自定义RibbonWindow 1. 为什么要自定义RibbonWindow 自定义Window有可能是设计或功能上的要求,可以是非必要的,而自 ...

  5. [WPF自定义控件库]自定义Expander

    1. 前言 上一篇文章介绍了使用Resizer实现Expander简单的动画效果,运行效果也还好,不过只有展开/折叠而缺少了淡入/淡出的动画(毕竟Resizer模仿Expander只是附带的功能).这 ...

  6. [WPF自定义控件库] 模仿UWP的ProgressRing

    1. 为什么需要ProgressRing 虽然我认为这个控件库的控件需要模仿Aero2的外观,但总有例外,其中一个就是ProgressRing.ProgressRing是来自UWP的控件,部分代码参考 ...

  7. [WPF自定义控件库] 自定义控件的代码如何与ControlTemplate交互

    1. 前言 WPF有一个灵活的UI框架,用户可以轻松地使用代码控制控件的外观.例设我需要一个控件在鼠标进入的时候背景变成蓝色,我可以用下面这段代码实现: protected override void ...

  8. [WPF自定义控件库]为Form和自定义Window添加FunctionBar

    1. 前言 我常常看到同一个应用程序中的表单的按钮----也就是"确定"."取消"那两个按钮----实现得千奇百怪,其实只要使用统一的Style起码就可以统一按 ...

  9. [WPF自定义控件库]好用的VisualTreeExtensions

    1. 前言 A long time ago in a galaxy far, far away....微软在Silverlight Toolkit里提供了一个好用的VisualTreeExtensio ...

随机推荐

  1. 如何用纯 CSS 创作一副国际象棋

    效果预览 在线演示 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/WyXrjz 可交互视频 ...

  2. redis集群监控之Redis-monitor部

    为了对以后有可能面临的redis集群监控做准备,这两天在准备这方面的事情,现在将其中的过程记录一下. 首先是“Ronney-Hua”的这篇文章对三中开源监控软件做了对比 文章地址:https://bl ...

  3. 汇编语言 Part 2——寄存器

    处理器操作主要涉及处理数据.这些数据可以存储在内存中并从中访问.但是,读取数据并将其存储到内存中会减慢处理器的速度,因为它涉及将数据请求通过控制总线发送到内存存储单元并通过同一通道获取数据的复杂过程. ...

  4. IDEA常用快捷键(不全)

    这里使用的是默认的idea快捷键,如果修改了keymap为其他,那么不适用. 1.格式化代码:Ctrl+Alt+L(可能与QQ的冲突,建议QQ只保留方便的截图,皮) 2.在当前行最后添加分号,或自动补 ...

  5. GPIO程序在PC上的模拟学习

    #include <stdio.h> #include <malloc.h> #include <memory.h> typedef struct gpio { i ...

  6. zookeeper安装、配置、使用

    [安装] wget http://www.apache.org/dist/zookeeper/zookeeper-3.3.6/zookeeper-3.3.6.tar.gz tar zxvf zooke ...

  7. 网络编程-TCP/IP各层介绍(5层模型讲解)

    1.TCP/IP五层协议讲解 物理层--数据链路层--网络层--传输层--应用层 我们将应用层,表示层,会话层并作应用层,从tcp/ip五层协议的角度来阐述每层的由来与功能,搞清楚了每层的主要协议 就 ...

  8. Wordpress入门笔记

    简单介绍一下wordpress个人操作,建议安装中文版. 登入后台管理者页面, 浏览器地址栏输入           (线上) http://XXXX.com/wp-login.php (本地) ht ...

  9. hiho[Offer收割]编程练习赛30

    题目1 : 提取用户名 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 在现在的各种互联网应用中,在一段文字中使用'@'字符来提起一名用户是流行的做法. 例如: &quo ...

  10. 利用Solr的post工具对核心my_core执行删除操作

    <delete> <query>*:*</query> </delete> 将上面代码保存到delete_all.xml文件中,并使用Solr的post ...