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

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派生。(我将这个控件库定位为入门教材,所以越简单越好。)

有必要的话可以设置IsDefaultIsCancel属性,前者表示按钮会在表单点击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

posted @ 2019-06-05 09:04 dino.c 阅读(...) 评论(...) 编辑 收藏