共用方式為


如何建立控制項的範本 (WPF.NET)

使用 Windows Presentation Foundation (WPF),您可以使用自己的可重複使用範本來自訂現有控制項的視覺結構和行為。 範本可以全域套用至您的應用程式、視窗和頁面,或直接套用至控制項。 大部分需要您建立新控制項的案例,都可以改成為現有的控制項建立新範本。

在本文中,您將探索為 Button 控制項建立新的 ControlTemplate

建立 ControlTemplate 的時機

控制項有許多屬性,例如 BackgroundForegroundFontFamily。 這些屬性控制著不同層面的控制項外觀,但透過設定這些屬性可以進行的變更有限。 例如,您可以在 CheckBox 上,將 Foreground 屬性設定為藍色,並將 FontStyle 設定為斜體。 當您想要自訂控制項的外觀,而在控制項上設定其他屬性並無法滿足此需求時,您可以建立 ControlTemplate

在大部分的使用者介面中,按鈕都有相同的一般外觀:內含某些文字的矩形。 如果您想要建立圓形按鈕,您可以建立繼承該按鈕的新控制項,或重新建立該按鈕的功能。 此外,新的使用者控制項會提供圓形視覺效果。

您可以透過自訂現有控制件的視覺配置,來避免建立新的控制件。 使用圖形按鈕,您可以建立內含所需視覺配置的 ControlTemplate

此外,如果您需要具有新功能、不同屬性和新設定的控制項,您可以建立新的 UserControl

必要條件

建立新的 WPF 應用程式,並在 MainWindow.xaml (或您選擇的另一個視窗) 中,在 <Window> 元素上設定下列屬性:

屬性
Title Template Intro Sample
SizeToContent WidthAndHeight
MinWidth 250

<Window> 元素的內容設定為下列 XAML:

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button>Button 2</Button>
</StackPanel>

最後,MainWindow.xaml 檔案看起來應該類似下列所示:

<Window x:Class="IntroToStylingAndTemplating.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:IntroToStylingAndTemplating"
        mc:Ignorable="d"
        Title="Template Intro Sample" SizeToContent="WidthAndHeight" MinWidth="250">
    <StackPanel Margin="10">
        <Label>Unstyled Button</Label>
        <Button>Button 1</Button>
        <Label>Rounded Button</Label>
        <Button>Button 2</Button>
    </StackPanel>
</Window>

如果您執行應用程式,其看起來如下所示:

WPF 視窗,內含兩個未設定樣式的按鈕

建立 ControlTemplate

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

若要開始,請將 Window.Resources 元素新增至 MainWindow.xaml 檔案:

<Window x:Class="IntroToStylingAndTemplating.Window2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:IntroToStylingAndTemplating"
        mc:Ignorable="d"
        Title="Template Intro Sample" SizeToContent="WidthAndHeight" MinWidth="250">
    <Window.Resources>
        
    </Window.Resources>
    <StackPanel Margin="10">
        <Label>Unstyled Button</Label>
        <Button>Button 1</Button>
        <Label>Rounded Button</Label>
        <Button>Button 2</Button>
    </StackPanel>
</Window>

使用下列屬性集建立新的 <ControlTemplate>

屬性
x:Key roundbutton
TargetType Button

此控制項範本很簡單:

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <Ellipse Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

TemplateBinding

當您建立新的 ControlTemplate 時,可能仍然會想要使用公用屬性來變更控制項的外觀。 TemplateBinding 標記延伸會將 ControlTemplate 中元素的屬性繫結至控制項所定義的公用屬性。 使用 TemplateBinding 時,可讓控制項上的屬性作為範本的參數。 也就是說,已設定控制項上的屬性時,該值會傳遞給具有 TemplateBinding 的元素。

橢圓形

請注意,<Ellipse> 元素的 FillStroke 屬性會繫結至控制項的 ForegroundBackground 屬性。

ContentPresenter

<ContentPresenter> 元素也會新增至範本。 由於此範本是針對按鈕所設計,因此請考慮該按鈕繼承自 ContentControl。 按鈕會顯示元素的內容。 您可以在按鈕內設定任何內容,例如純文字或甚至是另一個控制項。 下列兩者都是有效的按鈕:

<Button>My Text</Button>

<!-- and -->

<Button>
    <CheckBox>Checkbox in a button</CheckBox>
</Button>

在上述兩個範例中,文字和核取方塊都設定為 Button.Content 屬性。 只要設定為內容,都可以透過 <ContentPresenter> 來呈現,這就是範本的功能。

如果 ControlTemplate 套用至 ContentControl 類型,例如 Button,則會在元素樹狀結構中搜尋 ContentPresenter。 如果找到 ContentPresenter,範本會自動將控制項的 Content 屬性繫結至 ContentPresenter

使用範本

尋找本文開頭所宣告的按鈕。

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button>Button 2</Button>
</StackPanel>

將第二個按鈕的 Template 屬性設定為 roundbutton 資源:

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button Template="{StaticResource roundbutton}">Button 2</Button>
</StackPanel>

如果您執行專案並查看結果,您會看到按鈕有圓形的背景。

具有一個範本橢圓按鈕的 WPF 視窗

您可能已經注意到該按鈕不是圓形,而是傾斜的。 由於 <Ellipse> 元素的運作方式,它一律會展開以填滿可用空間。 將按鈕的 widthheight 屬性變更為相同的值,使圓形統一:

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button Template="{StaticResource roundbutton}" Width="65" Height="65">Button 2</Button>
</StackPanel>

WPF 視窗,內含一個範本循環按鈕

新增觸發程序

即使套用範本的按鈕看起來不同,但它的行為與任何其他按鈕相同。 如果您按下按鈕,就會引發 Click 事件。 不過,您可能已經注意到,當您將滑鼠移至按鈕上方時,按鈕的視覺效果沒有變更。 這些視覺互動都是由範本所定義。

使用 WPF 提供的動態事件和屬性系統,您可以監看某個值的特定屬性,然後在適當時重設範本的樣式。 在此範例中,您將監看按鈕的 IsMouseOver 屬性。 當滑鼠移至控制項上方時,請使用新的色彩來設定 <Ellipse> 的樣式。 這種類型的觸發程式稱為 PropertyTrigger

若要讓其運作,您必須將名稱新增至您可以參考的 <Ellipse>。 將其命名為 backgroundElement

<Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />

接下來,將新的 Trigger 新增至 ControlTemplate.Triggers 集合。 觸發程式會監看 IsMouseOver 事件的值 true

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
    <ControlTemplate.Triggers>
        <Trigger Property="IsMouseOver" Value="true">

        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

接下來,將 <Setter> 新增至 [觸發程式]<>,其會將 <Ellipse> 的 [填滿] 屬性變更為新的色彩。

<Trigger Property="IsMouseOver" Value="true">
    <Setter Property="Fill" TargetName="backgroundElement" Value="AliceBlue"/>
</Trigger>

執行專案。 請注意,當您將滑鼠移至按鈕上方時,<Ellipse> 的顏色會變更。

將滑鼠移至 WPF 按鈕上方以變更填滿色彩

使用 VisualState

視覺狀態由控制項所定義和觸發。 例如,當滑鼠移至控制項頂端時,就會觸發 CommonStates.MouseOver 狀態。 您可以根據控制項的目前狀態,對屬性變更進行動畫處理。 在上一節中,<PropertyTrigger> 用來在 IsMouseOver 屬性為 true 時,將按鈕的背景變更為 AliceBlue。 取而代之的是,請建立對此色彩變更進行動畫處理的視覺狀態,以提供平滑的轉換。 如需 VisualStates 的詳細資訊,請參閱 WPF 中的樣式和範本

若要將 <PropertyTrigger> 轉換成動畫視覺狀態,首先,請從範本中移除 <ControlTemplate.Triggers> 元素。

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

接下來,在控制項範本的 <Grid> 根中,針對 CommonStates,新增 <VisualStateManager.VisualStateGroups> 元素,內含 <VisualStateGroup>。 定義兩個狀態,NormalMouseOver

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup Name="CommonStates">
                <VisualState Name="Normal">
                </VisualState>
                <VisualState Name="MouseOver">
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

觸發該狀態時,會套用 <VisualState >中所定義的任何動畫。 為每個狀態建立動畫。 動畫會放在 <Storyboard> 元素內。 如需分鏡腳本的詳細資訊,請參閱分鏡腳本概觀

  • Normal

    此狀態會以動畫顯示橢圓形填滿,並將其還原至控制項的 Background 色彩。

    <Storyboard>
        <ColorAnimation Storyboard.TargetName="backgroundElement" 
            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
            To="{TemplateBinding Background}"
            Duration="0:0:0.3"/>
    </Storyboard>
    
  • MouseOver

    此狀態會將橢圓形 Background 色彩以動畫顯示成新的色彩:Yellow

    <Storyboard>
        <ColorAnimation Storyboard.TargetName="backgroundElement" 
            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" 
            To="Yellow" 
            Duration="0:0:0.3"/>
    </Storyboard>
    

<ControlTemplate> 現在看起來應該如下所示。

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

執行專案。 請注意,當您將滑鼠移至按鈕上方時,<Ellipse> 的色彩會以動畫處理。

將滑鼠移至 WPF 按鈕上方,以使用視覺狀態變更填滿色彩

下一步