当前位置: 移动技术网 > IT编程>开发语言>.net > [WPF自定义控件库]以Button为例谈谈如何模仿Aero2主题

[WPF自定义控件库]以Button为例谈谈如何模仿Aero2主题

2019年05月28日  | 移动技术网IT编程  | 我要评论

李阳疯狂英语学习方法,甘肃新闻,国庆天气情况

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统一风格作借口我也可以不做动画啦。

最近我发现这样介绍我:

其实我也并不是那么喜欢亲自写动画,只是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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网