WPF 中的樣式及範本

Windows Presentation Foundation (WPF) 樣式設定和範本化係指一套功能,可讓開發人員和設計人員創造引人注目的效果和其產品一致的外觀。 自訂應用程式的外觀時,您需要一個強大的樣式設定和範本化模型,以便能夠在應用程式內部和之間維護及共用外觀。 WPF 便有提供該模型。

WPF 樣式設定模型的另一項功能是將呈現與邏輯區分開來。 設計人員可以在開發人員使用 C# 或 Visual Basic 來處理程式設計邏輯的同時,僅使用 XAML 來處理應用程式的外觀。

本概觀將焦點放在應用程式的樣式設定和範本化方面,而不討論任何資料繫結概念。 如需有關資料繫結的資訊,請參閱資料繫結概觀

了解資源相當重要,資源可讓您重複使用樣式和範本。 如需有關資源的詳細資訊,請參閱 XAML 資源

範例

本概觀中使用的範例程式碼是根據下圖中所示的簡單相片瀏覽應用程式

已設定樣式的 ListView

這個簡單的相片範例使用樣式設定和範本化,來創造引人注目的使用者體驗。 此範例有兩個 TextBlock 元素,以及繫結至影像清單的 ListBox 控制項。

如需完整範例,請參閱樣式設定和範本化範例簡介 (英文)

樣式

您可以將 Style 想成是一種可將一組屬性值套用到多個元素的便利方式。 您可以在任何衍生自 FrameworkElementFrameworkContentElement 的元素上使用樣式,例如 WindowButton

宣告樣式的最常見方式是在 XAML 檔案的 Resources 區段中宣告成資源。 由於樣式是資源,因此會遵守適用於所有資源的範圍規則。 簡單地說,宣告樣式的位置會影響可套用該樣式的位置。 例如,如果您是在應用程式定義 XAML 檔案的根元素中宣告樣式,就可在應用程式中的任何地方使用該樣式。

例如,下列 XAML 程式碼會宣告兩個 TextBlock樣式,一個會自動套用至所有 TextBlock 元素,另一個必須明確參考。

<Window.Resources>
    <!-- .... other resources .... -->

    <!--A Style that affects all TextBlocks-->
    <Style TargetType="TextBlock">
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="FontFamily" Value="Comic Sans MS"/>
        <Setter Property="FontSize" Value="14"/>
    </Style>
    
    <!--A Style that extends the previous TextBlock Style with an x:Key of TitleText-->
    <Style BasedOn="{StaticResource {x:Type TextBlock}}"
           TargetType="TextBlock"
           x:Key="TitleText">
        <Setter Property="FontSize" Value="26"/>
        <Setter Property="Foreground">
            <Setter.Value>
                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
                    <LinearGradientBrush.GradientStops>
                        <GradientStop Offset="0.0" Color="#90DDDD" />
                        <GradientStop Offset="1.0" Color="#5BFFFF" />
                    </LinearGradientBrush.GradientStops>
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

以下是上述所宣告的樣式範例。

<StackPanel>
    <TextBlock Style="{StaticResource TitleText}" Name="textblock1">My Pictures</TextBlock>
    <TextBlock>Check out my new pictures!</TextBlock>
</StackPanel>

已設定樣式的 textblocks

ControlTemplates

在 WPF 中,控制項的 ControlTemplate 會定義控制項的外觀。 定義新的 ControlTemplate 並將其指派給控制項,便可變更控制項的結構和外觀。 在許多情況下,範本提供足夠的彈性,讓您不需要撰寫自己的自訂控制項。

每個控制項都有指派給 Control.Template 屬性的預設範本。 範本會將控制項的視覺呈現與控制項的功能連接起來。 由於您在 XAML 中定義範本,因此您可以變更控制項的外觀,而不需撰寫任何程式碼。 每個樣本都是針對特定控制項所設計,例如 Button

您通常會將範本宣告為 XAML 檔案 Resources 區段上的資源。 與所有資源一樣,範圍規則也適用。

控制項範本比樣式更複雜。 這是因為控制項範本會重寫整個控制項的視覺外觀,而樣式只會將屬性變更套用至現有的控制項。 不過,由於控制項的範本是藉由設定 Control.Template 屬性來套用,因此您可以使用樣式來定義或設定範本。

設計工具通常可讓您建立現有範本的複本並加以修改。 例如,在 Visual Studio WPF 設計工具中,選取 CheckBox 控制項,按一下滑鼠右鍵並選取 [編輯範本]>[建立複本]。 此命令會產生定義範本的樣式

<Style x:Key="CheckBoxStyle1" TargetType="{x:Type CheckBox}">
    <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual1}"/>
    <Setter Property="Background" Value="{StaticResource OptionMark.Static.Background1}"/>
    <Setter Property="BorderBrush" Value="{StaticResource OptionMark.Static.Border1}"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type CheckBox}">
                <Grid x:Name="templateRoot" Background="Transparent" SnapsToDevicePixels="True">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Border x:Name="checkBoxBorder" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="1" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
                        <Grid x:Name="markGrid">
                            <Path x:Name="optionMark" Data="F1 M 9.97498,1.22334L 4.6983,9.09834L 4.52164,9.09834L 0,5.19331L 1.27664,3.52165L 4.255,6.08833L 8.33331,1.52588e-005L 9.97498,1.22334 Z " Fill="{StaticResource OptionMark.Static.Glyph1}" Margin="1" Opacity="0" Stretch="None"/>
                            <Rectangle x:Name="indeterminateMark" Fill="{StaticResource OptionMark.Static.Glyph1}" Margin="2" Opacity="0"/>
                        </Grid>
                    </Border>
                    <ContentPresenter x:Name="contentPresenter" Grid.Column="1" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="HasContent" Value="true">
                        <Setter Property="FocusVisualStyle" Value="{StaticResource OptionMarkFocusVisual1}"/>
                        <Setter Property="Padding" Value="4,-1,0,0"/>

... content removed to save space ...

編輯範本複本是了解範本運作方式的絕佳方式。 編輯複本並變更一些視覺呈現方面比建立新的空白範本更容易。

如需範例,請參閱建立控制項的範本

TemplateBinding

您可能已經注意到上一節中定義的範本資源會使用 TemplateBinding 標記延伸TemplateBinding 是範本案例的最佳繫結形式,類似於使用 {Binding RelativeSource={RelativeSource TemplatedParent}} 建構的繫結。 TemplateBinding 有助於將範本的元件繫結至 控制項的屬性。 例如,每個控制項都有 BorderThickness 屬性。 使用 TemplateBinding 來管理此控制項設定會影響範本中的元素。

ContentControl 和 ItemsControl

如果在 ContentControlControlTemplate 中宣告 ContentPresenterContentPresenter 會自動繫結至 ContentTemplateContent 屬性。 同樣地,位於 ItemsControlControlTemplate 中的 ItemsPresenter 會自動繫結至 ItemTemplateItems 屬性。

DataTemplates

在此範例應用程式中,有一個 ListBox 控制項繫結至相片清單。

<ListBox ItemsSource="{Binding Source={StaticResource MyPhotos}}"
         Background="Silver" Width="600" Margin="10" SelectedIndex="0"/>

ListBox 目前看起來如下。

套用範本之前的 ListBox

大多數控制項都有某個型別的內容,而該內容通常來自您要繫結的資料。 在此範例中,該資料是相片清單。 在 WPF 中,您可以使用 DataTemplate 來定義資料的視覺表示。 基本上,您在 DataTemplate 中放入的內容,會決定資料在轉譯後的應用程式中看起來的樣子。

在我們的範例應用程式中,每個自訂的 Photo 物件都有一個字串類型的 Source 屬性,用來指定影像的檔案路徑。 目前,相片物件是顯示成檔案路徑。

public class Photo
{
    public Photo(string path)
    {
        Source = path;
    }

    public string Source { get; }

    public override string ToString() => Source;
}
Public Class Photo
    Sub New(ByVal path As String)
        Source = path
    End Sub

    Public ReadOnly Property Source As String

    Public Overrides Function ToString() As String
        Return Source
    End Function
End Class

若要讓相片顯示成影像,您需建立 DataTemplate 來作為資源。

<Window.Resources>
    <!-- .... other resources .... -->

    <!--DataTemplate to display Photos as images
    instead of text strings of Paths-->
    <DataTemplate DataType="{x:Type local:Photo}">
        <Border Margin="3">
            <Image Source="{Binding Source}"/>
        </Border>
    </DataTemplate>
</Window.Resources>

請注意,DataType 屬性類似於 StyleTargetType 屬性。 如果您的 DataTemplate 位於資源區段中,當您將 DataType 屬性指定為類型並省略 x:Key 時,只要該類型出現,就會套用 DataTemplate。 您一律可以選擇指派含有 x:KeyDataTemplate,然後針對接受 DataTemplate 類型的屬性將其設定為 StaticResource,例如 ItemTemplate 屬性或 ContentTemplate 屬性。

基本上,上述範例中的 DataTemplate 定義每當有 Photo 物件時,都會在 Border 內顯示為 Image。 使用此 DataTemplate,我們的應用程式現在看起來像這樣。

圖片影像

資料範本化模型還提供其他功能。 例如,如果您要使用 MenuTreeViewHeaderedItemsControl 類型來顯示包含其他集合的集合資料,則會有 HierarchicalDataTemplate。 另一個資料範本化功能是 DataTemplateSelector,可讓您根據自訂邏輯選擇要使用的 DataTemplate。 如需詳細資訊,請參閱資料範本化概觀,其中提供不同資料範本化功能的更深入探討。

觸發程序

觸發程序會在屬性值發生變更或在某個事件被引發時,設定屬性或啟動動作 (例如動畫)。 StyleControlTemplateDataTemplate 都有一個可包含一組觸發程式的 Triggers 屬性。 觸發程序有幾種類型。

PropertyTriggers

根據某個屬性的值來設定屬性值或啟動動作的 Trigger 即稱為屬性觸發程序。

為了示範如何使用屬性觸發程序,您可以讓每個 ListBoxItem 在處於未選取狀態時呈現部分透明。 下列樣式會將 ListBoxItemOpacity 值設定為 0.5。 不過,當 IsSelected 屬性為 true 時,Opacity 會設定為 1.0

<Window.Resources>
    <!-- .... other resources .... -->

    <Style TargetType="ListBoxItem">
        <Setter Property="Opacity" Value="0.5" />
        <Setter Property="MaxHeight" Value="75" />
        <Style.Triggers>
            <Trigger Property="IsSelected" Value="True">
                <Trigger.Setters>
                    <Setter Property="Opacity" Value="1.0" />
                </Trigger.Setters>
            </Trigger>
        </Style.Triggers>
    </Style>
</Window.Resources>

此範例使用 Trigger 來設定屬性值,但請注意,Trigger 類別也具有 EnterActionsExitActions 屬性,可讓觸發程式執行動作。

請注意,ListBoxItemMaxHeight 屬性設定為 75。 在下圖中,第三個項目是已選取的項目。

已設定樣式的 ListView

EventTrigger 和分鏡腳本

另一種類型的觸發程序是 EventTrigger,這會根據某個事件的發生來啟動一組動作。 例如,下列 EventTrigger 物件會指定當滑鼠指標進入 ListBoxItem 時,MaxHeight 屬性會在 0.2 秒期間內以動畫方式變為 90 的值。 當滑鼠指標從項目移開時,該屬性會在 1 秒的期間內恢復成原始值。 請注意,不需要為 MouseLeave 動畫指定 To 值。 這是因為動畫能夠記錄原始值。

<Style.Triggers>
    <Trigger Property="IsSelected" Value="True">
        <Trigger.Setters>
            <Setter Property="Opacity" Value="1.0" />
        </Trigger.Setters>
    </Trigger>
    <EventTrigger RoutedEvent="Mouse.MouseEnter">
        <EventTrigger.Actions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation
                        Duration="0:0:0.2"
                        Storyboard.TargetProperty="MaxHeight"
                        To="90"  />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger.Actions>
    </EventTrigger>
    <EventTrigger RoutedEvent="Mouse.MouseLeave">
        <EventTrigger.Actions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation
                        Duration="0:0:1"
                        Storyboard.TargetProperty="MaxHeight"  />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger.Actions>
    </EventTrigger>
</Style.Triggers>

如需詳細資訊,請參閱分鏡腳本概觀

在下圖中,滑鼠指標正指向第三個項目。

樣式設定範例螢幕擷取畫面

MultiTrigger、DataTrigger 及 MultiDataTrigger

除了 TriggerEventTrigger 之外,還有其他類型的觸發程式。 MultiTrigger 可讓您根據多個條件來設定屬性值。 當條件的屬性已繫結資料時,您可以使用 DataTriggerMultiDataTrigger

視覺狀態

控制項一律處於特定狀態。 例如,當滑鼠移至控制項表面上方時,控制項會被視為處於 MouseOver 的一般狀態中。 沒有特定狀態的控制項會被視為處於一般 Normal 狀態。 狀態分成多個群組,先前提到的狀態隸屬於狀態群組 CommonStates。 大部分的控制項都有兩個狀態群組:CommonStatesFocusStates。 在套用至控制項的每個狀態群組中,控制項一律處於每個群組的一個狀態,例如 CommonStates.MouseOverFocusStates.Unfocused。 不過,控制項不能同時處於相同群組內的兩個不同狀態,例如 CommonStates.NormalCommonStates.Disabled。 以下是大部分控制項可辨識和使用的狀態資料表。

VisualState 名稱 VisualStateGroup 名稱 描述
Normal CommonStates 預設狀態。
MouseOver CommonStates 滑鼠指標移到控制項上。
Pressed CommonStates 已按下控制項。
Disabled CommonStates 已停用控制項。
Focused FocusStates 控制項已取得焦點。
Unfocused FocusStates 控制項未取得焦點。

藉由在控制項範本的根項目上定義 System.Windows.VisualStateManager,您可以在控制項進入特定狀態時觸發動畫。 VisualStateManager 會宣告要監看的 VisualStateGroupVisualState 組合。 當控制項進入監看狀態時,便會啟動 VisualStateManager 所定義的動畫。

例如,下列 XAML 程式碼會監看 CommonStates.MouseOver 狀態,以動畫顯示名為 backgroundElement 的元素填滿色彩。 當控制項回到 CommonStates.Normal 狀態時,會還原名為 backgroundElement 的元素填滿色彩。

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup Name="CommonStates">
                <VisualState Name="Normal">
                    <ColorAnimation Storyboard.TargetName="backgroundElement"
                                    Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                    To="{TemplateBinding Background}"
                                    Duration="0:0:0.3"/>
                </VisualState>
                <VisualState Name="MouseOver">
                    <ColorAnimation Storyboard.TargetName="backgroundElement"
                                    Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                    To="Yellow"
                                    Duration="0:0:0.3"/>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        ...

如需分鏡腳本的詳細資訊,請參閱分鏡腳本概觀

共用的資源和佈景主題

典型的 WPF 應用程式可能會有多個透過應用程式套用的使用者介面資源。 這組資源集體可視為應用程式的佈景主題。 WPF 提供支援,可讓您使用封裝成 ResourceDictionary 類別的資源字典,將使用者介面資源封裝成佈景主題。

定義 WPF 佈景主題時,是透過使用 WPF 所公開來自訂任何元素之視覺效果的樣式設定和範本化機制來定義。

WPF 佈景主題資源儲存在內嵌資源字典中。 這些資源字典必須內嵌在已簽署的組件內,並且可內嵌在與程式碼本身相同的組件中,也可內嵌在並存的組件中。 對於 PresentationFramework.dll (包含 WPF 控制項的組件),佈景主題資源位於一系列並存的組件中。

當搜尋元素的樣式時,佈景主題會成為最後一個要查看的地方。 一般而言,搜尋會從元素樹狀目錄往上開始、搜尋適當的資源,然後查看應用程式資源集合,最後查詢系統。 這會為應用程式開發人員提供一個機會,可在到達佈景主題之前,先為樹狀結構或應用程式層級的任何物件,重新定義樣式。

您可以將資源字典定義成個別的檔案,這可讓您跨多個應用程式重複使用佈景主題。 您也可以透過定義多個資源字典來提供類型相同但值不同的資源,以建立可切換的佈景主題。 重新定義這些樣式或其他應用程式層級的資源,是簡化應用程式的建議做法。

若要跨應用程式共用一組資源 (包括樣式和範本),您可以建立一個 XAML 檔案並定義包含 shared.xaml 檔案參照的 ResourceDictionary

<ResourceDictionary.MergedDictionaries>
  <ResourceDictionary Source="Shared.xaml" />
</ResourceDictionary.MergedDictionaries>

這是 shared.xaml 的共用,其本身定義的 ResourceDictionary 包含一組樣式和筆刷資源,可讓應用程式中的控制項具有一致的外觀。

如需詳細資訊,請參閱合併的資源字典

如果您要為自訂控制項建立佈景主題,請參閱控制項製作概觀定義佈景主題層級的資源一節。

另請參閱