[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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章