樣式和範本 (WPF .NET)

Windows Presentation Foundation (WPF) 樣式和範本化是指一套功能,可讓開發人員和設計工具為其產品建立視覺上吸引人的效果和一致的外觀。 自訂應用程式的外觀時,您想要強式樣式和範本化模型,以在應用程式內和之間維護及共用外觀。 WPF 會提供該模型。

WPF 樣式模型的另一個功能是簡報和邏輯的分離。 設計工具只能使用 XAML 來處理應用程式的外觀,同時開發人員可以使用 C# 或 Visual Basic 來處理常式設計邏輯。

本概觀著重于應用程式的樣式和範本化層面,且不會討論任何資料系結概念。 如需有關資料繫結的資訊,請參閱資料繫結概觀

請務必瞭解資源,這是可重複使用樣式和範本的功能。 如需資源的詳細資訊,請參閱 XAML 資源 概觀。

重要

.NET 7 和 .NET 6 的桌面指南檔正在建置中。

範例

本概觀中提供的範例程式碼是以下圖所示的簡單相片流覽應用程式 為基礎

Styled ListView

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

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

樣式

您可以將 視為 Style 將一組屬性值套用至多個元素的便利方式。 您可以在任何衍生自 FrameworkElement 或 例如 WindowFrameworkContentElementButton 的專案上使用樣式。

宣告樣式的最常見方式是作為 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>

Styled 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 適用于將範本的元件系結至 控制項的屬性。 例如,每個控制項都有 屬性 BorderThicknessTemplateBinding使用 來管理此控制項設定會影響範本中的專案。

ContentControl 和 ItemsControl

ContentPresenter如果在 的 ContentControlControlTemplate 宣告 ,則 ContentPresenter 會自動系結至 ContentTemplateContent 屬性。 同樣地, ItemsPresenter 中的 ControlTemplateItemsControl ,會自動系結至 ItemTemplateItems 屬性。

DataTemplates

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

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

ListBox 目前看起來如下。

ListBox before applying template

大多數控制項都有某個型別的內容,而該內容通常來自您要繫結的資料。 在此範例中,該資料是相片清單。 在 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 屬性類似于 TargetTypeStyle 屬性。 DataTemplate如果您的 位於 resources 區段中,當您將 屬性指定 DataType 為類型並省略 x:KeyDataTemplate ,就會在出現該類型時套用 。 您一律可以選擇使用 來指派 DataTemplate ,然後將它設定為 StaticResource 接受 DataTemplate 型別的屬性,例如 ItemTemplate 屬性或 ContentTemplate 屬性。 x:Key

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

Photo image

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

觸發程序

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

PropertyTriggers

設定 Trigger 屬性值或根據屬性值啟動動作的 ,稱為屬性觸發程式。

若要示範如何使用屬性觸發程式,除非選取屬性觸發程式,否則您可以讓每個 ListBoxItem 部分透明。 下列樣式會將 Opacity 的值 ListBoxItem 設定為 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 屬性,可讓觸發程式執行動作。

請注意, MaxHeightListBoxItem 屬性設定為 75 。 在下圖中,第三個專案是選取的專案。

Styled ListView

EventTrigger 和分鏡腳本

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

<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>

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

在下圖中,滑鼠指向第三個專案。

Styling sample screenshot

MultiTrigger、DataTrigger 及 MultiDataTrigger

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

視覺狀態

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

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

藉由在控制項範本的根項目上定義 System.Windows.VisualStateManager ,您可以在控制項進入特定狀態時觸發動畫。 會 VisualStateManager 宣告 和 VisualStateVisualStateGroup 監看的組合。 當控制項進入監看狀態時,會啟動 所 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 應用程式可能會有多個在應用程式內套用的 UI 資源。 整體而言,這組資源可以視為應用程式的主題。 WPF 支援使用封裝為 類別的資源字典,將 UI 資源封裝為 ResourceDictionary 主題。

WPF 主題是使用 WPF 針對自訂任何元素視覺效果所公開的樣式和範本化機制來定義。

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

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

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

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

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

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

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

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

另請參閱