1. 前言

我常常看到同一个应用程序中的表单的按钮————也就是“确定”、“取消”那两个按钮————实现得千奇百怪,其实只要使用统一的style起码就可以统一按钮的大小,而我喜欢更进一步将”确定“、”取消“或其它按钮封装进一个自定义控件里。

这篇文章介绍了另一种itemscontrol的实现方式,并使用它为表单及自定义window添加常用的按钮及其它功能。

2. 为form添加functionbar

本来打算派生自toolbar,或者参考uwp的commandbar,但最后决定参考mahapps.metro的windowcommands创建了formfunctionbar,它继承自headereditemscontrol,代码里没有任何功能,defaultstyle如下:

<style targettype="button"
       x:key="formfunctionbarbuttonbase">
    <setter property="minwidth"
            value="48" />
    <setter property="margin"
            value="4,0,0,0" />
    <style.triggers>
        <trigger property="isdefault"
                 value="true">
            <setter property="minwidth"
                    value="96" />
        </trigger>
    </style.triggers>
</style>

<style x:key="formfunctionbarextendedbutton"
       targettype="local:extendedbutton"
       basedon="{staticresource formfunctionbarbuttonbase}" />


<style x:key="formfunctionbarbutton"
       targettype="button"
       basedon="{staticresource formfunctionbarbuttonbase}" />



<style targettype="{x:type local:formfunctionbar}">
    <setter property="focusvisualstyle"
            value="{x:null}" />
    <setter property="focusable"
            value="false" />
    <setter property="istabstop"
            value="false" />
    <setter property="margin"
            value="0" />
    <setter property="padding"
            value="12,0,12,12" />
    <setter property="horizontalcontentalignment"
            value="right" />
    <setter property="template">
        <setter.value>
            <controltemplate targettype="local:formfunctionbar">
                <controltemplate.resources>
                    <style basedon="{staticresource formfunctionbarbutton}"
                           targettype="{x:type button}" />
                    <style basedon="{staticresource formfunctionbarextendedbutton}"
                           targettype="{x:type local:extendedbutton}" />
                </controltemplate.resources>
                <grid margin="{templatebinding padding}">
                    <contentpresenter content="{templatebinding header}"
                                      contenttemplate="{templatebinding headertemplate}"
                                      horizontalalignment="left" />
                    <itemspresenter horizontalalignment="{templatebinding horizontalcontentalignment}"
                                    grid.column="1" />
                </grid>
            </controltemplate>
        </setter.value>
    </setter>
    <setter property="itemspanel">
        <setter.value>
            <itemspaneltemplate>
                <stackpanel orientation="horizontal" />
            </itemspaneltemplate>
        </setter.value>
    </setter>
</style>

这是itemscontrol的另一种实现方式,放进formfunctionbar的button及kinobutton都会自动应用defaultstyle预设的样式。然后在form中添加functionbar属性,并在控件底部放一个placeholder:

<grid>
    <grid.rowdefinitions>
        <rowdefinition height="auto" />
        <rowdefinition />
        <rowdefinition height="auto" />
    </grid.rowdefinitions>
    <local:pagetitle content="{templatebinding header}"
                         contenttemplate="{templatebinding headertemplate}" />
    <local:extendedscrollviewer grid.row="1"
                            padding="{templatebinding padding}">
        <itemspresenter snapstodevicepixels="true"
                        uselayoutrounding="true" />
    </local:extendedscrollviewer>
    <contentpresenter content="{templatebinding functionbar}"
                      grid.row="2" />
</grid>

最终formfunctionbar的使用方式如下:

<kino:form>
    <kino:form.functionbar>
        <kino:formfunctionbar>
            <button content="ok"
                    click="onok"
                    isdefault="true" />
            <button content="cancel"
                    iscancel="true"
                    click="oncancel" />
        </kino:formfunctionbar>
    </kino:form.functionbar>
</kino:form>

这样做可以统一所有form的按钮。由于做得很简单,后期可以再按需要添加其他控件的样式。其实这种方式很像toolbar,我本来也考虑从toolbar派生functionbar,但考虑到toolbar本身的功能不少,而我只想要实现最简单的功能,所以直接从headereditemscontrol派生。(我将这个控件库定位为入门教材,所以越简单越好。)

有必要的话可以设置isdefault和iscancel属性,前者表示按钮会在表单点击enter时触发,后者表示按钮会在表单点击esc时触发。在formfunctionbar我通过trigger设置了isdefault=true的按钮比其它按钮更长。

3. 为自定义window添加按钮

为自定义window在标题栏添加一些按钮也是个常见的需求,原理和formfunctionbar一样,只需要在自定义的window的适当位置放置一个placeholder,然后把windowfunctionbar放进去,使用方式如下:

<kino:extendedwindow.functionbar>
    <kino:windowfunctionbar>
        <button content="dino.c" />
        <separator />
        <menu>
            <menuitem header="发送反馈">
                <menuitem header="报告问题"
                          ischeckable="true" />
                <menuitem header="提供反馈"
                          ischeckable="true" />
                <separator />
                <menuitem header="设置..." />
            </menuitem>
        </menu>
        <button tooltip="help">
            <grid uselayoutrounding="true">
                <path  data="some data"
                       width="12"
                       height="12"
                       uselayoutrounding="true"
                       verticalalignment="center"
                       horizontalalignment="center"
                       fill="white" />
            </grid>
        </button>
    </kino:windowfunctionbar>
</kino:extendedwindow.functionbar>

windowfunctionbar的defaultstyle和formfunctionbar大同小异,只是多了一些常用控件(如menu、separator)的样式,这里不一一展示。

4. 结语

functionbar展示了另一种自定义控件的方式:它本身基本上没有功能,只是方便添加items并为为items套用style。如果派生自toolbar的话可以使用overflowitems功能,这很有趣,但现在还用不到所以没做。将来把functionbar添加到listboxitem之类的地方可能会需要。

有必要的话还可以添加多个functionbar,如window上可以添加leftwindowcommands、rightwindowcommands等各个功能区域,我工作上没遇到这种需求为求简单就只添加了一个功能区。

其实实现functionbar最大的难题是命名,我在commandbar、actionbar、toolbar、buttonsbar等名称之间由于了很久,根据反馈也许还是会修改。

5. 参考

mahapps.metro_windowcommands.cs at master

button.isdefault property (system.windows.controls) microsoft docs

button.iscancel property (system.windows.controls) microsoft docs

6. 源码

kino.toolkit.wpf_functionbar at master