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