Windows Phone 7 - 淺談Custom Control

Windows Phone 7 - 淺談Custom Control

最近在開發一些應用,想要對現有的控件進行客製,不想透過程式化硬寫出一個Control出來,

而且好奇對於控件的Style與ContentTemplate怎麼透過XAML就可以定義一個新控件的樣式,

希望透過該篇可以加以了解怎麼自訂一個控件。

 

要自訂控件,要先了解XAML提供何種方式讓設計人員可以調整現有的控件為新型的控件?

 

〉Using Styles to Change the Appearance of Multiple Controls

    修改控件的外貌有二種方式:

    ‧Styles:定義控件屬性設定的集合。方便簡單的地修改控件的既有屬性

    ‧Templates:一份XAML定義控件的Visual appearance與Visual behavior,取代控件的外貌,呈現完全不同的控件

 

(A) Styles

     Styles基本上是目標屬性設定的集合,透過指定的控件目標(TargetType)做為適用的類型,讓控件可透過Style屬性設定,

轉換成定義好的Styles。(需注意控件指定Style時,在XAML模式下需符合StaticResource Markup Extension的格式),如下:

   1:  
   2: <StackPanel.Resources>
   3:     <Style TargetType="Button" x:Key="myButtonStyle">
   4:     <Setter Property="Background" Value="Purple" />
   5:     <Setter Property="Foreground" Value="#9900FF" />
   6:     <Setter Property="Height" Value="50" />
   7:     <Setter Property="Width" Value="100" />
   8:     <Setter Property="Margin" Value="5" />
   9:     <Setter Property="HorizontalContentAlignment" Value="Center" />
  10:     <Setter Property="VerticalContentAlignment" Value="Center" />
  11:     <Setter Property="Cursor" Value="Hand" />
  12:     <Setter Property="FontSize" Value="14" />
  13:     </Style>
  14: </StackPanel.Resources>
  15:  
  16:  
  17: ...
  18:  
  19:  
  20: <Button x:Name="button3" Width="130" Content="Click Me Instead!" Style="{StaticResource myButtonStyle}"/>

 

(B). ContentTemplate

     ContentTemplate可用於定義控件的Visual structure與Visual behavior,所以透過它可自訂Control呈現的樣式,

但需注意只支援定義外貌,無法修改它的功能。透過<ControlTemplate Class>定義Button例子加以說明:

   1:  
   2: <ControlTemplate TargetType="Button">
   3:   <Grid >
   4:     <VisualStateManager.VisualStateGroups>
   5:       <VisualStateGroup x:Name="CommonStates">
   6:  
   7:         <VisualStateGroup.Transitions>
   8:  
   9:           <!--Take one half second to trasition to the MouseOver state.-->
  10:           <VisualTransition To="MouseOver" 
  11:                               GeneratedDuration="0:0:0.5"/>
  12:         </VisualStateGroup.Transitions>
  13:  
  14:         <VisualState x:Name="Normal" />
  15:  
  16:         <!--Change the SolidColorBrush, ButtonBrush, to red when the
  17:             mouse is over the button.-->
  18:         <VisualState x:Name="MouseOver">
  19:           <Storyboard>
  20:             <ColorAnimation Storyboard.TargetName="ButtonBrush" 
  21:                             Storyboard.TargetProperty="Color" To="Red" />
  22:           </Storyboard>
  23:         </VisualState>
  24:       </VisualStateGroup>
  25:     </VisualStateManager.VisualStateGroups>
  26:     <Grid.Background>
  27:       <SolidColorBrush x:Name="ButtonBrush" Color="Green"/>
  28:     </Grid.Background>
  29:   </Grid>
  30: </ControlTemplate>

Button使用Grid加以包裝,並且VisualStateManager定義在各種VisualState下要執行的任務,更搭配VisualStateGroup.Transactions

定義要執行的轉換項目,最後配合Storyboard定義顏色轉換的效果。

 

那麼ContentTemplate在使用上到底有那些重點呢?

 

B-1. Control Contract

         Control Contract強調客製Control必須嚴格的分隔:logic與visuals。這樣的好處在於讓二者不會互相牽扯,降低它們的耦合,

Designer可專心設計需要控件與具有的子項目,開發人員專心寫控件與子項目的功能,共同完成控件的功能。

然而,Control Contract內容包括

     ‧Visual properties exposed on the control class.

     ‧Expected parts in the template.

     ‧Expected logic associated with the parts in the template.

 

B-2. Parts and States Model

         Silverlight中經由Parts and States Model實作Control Contract,該Model保持分割logic與visual的理念,主要由:

Parts、States、State Groups與Visual Transitions所構成。要怎麼知道Control具有的Parts and States Model為何呢?

可參考<Control Styles and Templates>從中得知客製控件時,要怎麼使用其中的內容。

 

以下擷錄<Control Customization>說明Parts、States、States Groups與Visual Transitions:

1. Parts

     在ContentTemplate中Parts被稱為Element,因此,logic處理部分主要是針對這些Element進行操作。以下是介紹ComboBox控件

的組合結構,主要分成五個部分:ContentPresenter、ContentPresenterBorder、DropDownToggle、ScrollViewer、Popup。

     ComboBox parts

 

2. States與State Groups

     Visual State用於識別目前控件出現的狀態類型,舉例來說:下圖即說明Button的三種狀態中Background Color的改變。

    Button Normal, MouseOver, and Pressed states

 

     State Groups本身是一組互斥狀態的集合,不同的狀態組也有可能是正相交的。這也代表,控件可能同一時間具有二種

不同的States,並且它們來自不同的State Groups。以下舉Combox為例,它具有四種State Groups:ComonStates、CheckStates、

FocusSates、ValidationStates。

Combox可以同一時間出現Normal與Checked二種States,因為它各來自不同的State Groups。但不可以Normal與Pressed同時,

因為它們屬於同一個State Groups。

     CheckBox state groups

 

3. Visual Transitions

    用於呈現控件視覺化由一個狀態轉換成另一個狀態的結果,透過下圖的例子更容易明白,Button的狀態改成Background也跟著

改變,這一連串的視覺變化就是:Visual Transitions。

    Button transitioning between states

 

以上是針對ContentTemplate的定義加以說明,如果想知道更詳細怎麼使用ContentTemplate建立一個新控件,可以參考:

<Creating a New Control by Creating a ControlTemplate>有更多的範例說明。

 

以上是針對Styles與ContentTemplate的概要說明,接下來便實際來做看看怎麼客製一個新的Control吧。

 

(C) 實作範例

1. How to Define the ContentTemplate in XAML

    透過Template屬性來定義控件的ContentTemplate,以下為MSDN上指出可定義Template的方式,如下:

    ‧Locally set Template to a ControlTemplate that is defined inline;

   1: <!-- Locally set Template to a ControlTemplate that is defined inline. -->
   2: <Button Content="Button1">
   3:   <Button.Template>
   4:     <ControlTemplate TargetType="Button">
   5:  
   6:       <!--Define the ControlTemplate here.-->
   7:  
   8:     </ControlTemplate>
   9:   </Button.Template>
  10: </Button> 

    ‧Locally set Template to a reference to a ControlTemplate that is defined as a resource;

   1: <!-- Locally set Template to a reference to a ControlTemplate that is defined as a resource. -->
   2: <StackPanel>
   3:   <StackPanel.Resources>
   4:     <ControlTemplate TargetType="Button" x:Key="newTemplate">
   5:  
   6:       <!--Define the ControlTemplate here.-->
   7:  
   8:     </ControlTemplate>
   9:   </StackPanel.Resources>
  10:  
  11:   <Button Template="{StaticResource newTemplate}" Content="Button1"/>
  12: </StackPanel>

    ‧Set Template and define a ControlTemplate in a Style

   1: <!-- Set Template and define a ControlTemplate in a Style. -->
   2: <StackPanel>
   3:   <StackPanel.Resources>
   4:     <Style TargetType="Button" x:Key="newTemplate"> 
   5:       <Setter Property="Template">
   6:         <Setter.Value>
   7:           <ControlTemplate TargetType="Button">
   8:  
   9:             <!--Define the ControlTemplate here.-->
  10:  
  11:           </ControlTemplate>
  12:         </Setter.Value>
  13:       </Setter>
  14:     </Style>
  15:   </StackPanel.Resources>
  16:   <Button Style="{StaticResource newTemplate}" Content="Button1"/>
  17: </StackPanel>

以上三種均是可以定義ContentTemplate的方式,看自行的需要來使用,但使用的範圍可以根據上層包裝的父標籤有所不同。

 

 

2. Changing the Visual Structure of a Control

     在Silverlight中每一個控件通常是FrameworkElement objects組合而成的,當使用ContentTemplate定義控件的新樣式時,

ContentTemplate只能有一個FrameworkElement為「Root Element」,也就代表ContentTemplate需要一個控件來包裝其他控件。

組合的結果即為Visual Structure。以下舉例Button控件在ContentTemplate被修改的樣式:

   1: <Button Width="100" Height="100"
   2:     x:Name="btnData" Content="測試" Background="Red">
   3:     <Button.Template>
   4:         <ControlTemplate TargetType="Button" x:Name="newTemplate">
   5:             <Border x:Name="RootElement" >
   6:                 <!--為Border建立SolidColorBrush物件,並且給予一個名稱,
   7:                       它可在這Template中被使用-->
   8:                 <Border.Background>
   9:                     <SolidColorBrush x:Name="BorderBrush" Color="White"/>
  10:                 </Border.Background>
  11:  
  12:                 <!--為Grid建立不同的Background,分開與Border背景不同的顏色,Grid的Background,
  13:                     代表Button的Background顏色-->
  14:                 <Grid Margin="4" x:Name="GridBrush" Background="{TemplateBinding Background}">
  15:  
  16:                     <!--使用ContentPresenter呈現Button的Content-->
  17:                     <ContentPresenter
  18:                     HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
  19:                     VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
  20:                     Margin="0,0,0,0" />
  21:                    
  22:                 </Grid>
  23:             </Border>
  24:         </ControlTemplate>
  25:     </Button.Template>
  26: </Button>

在上述的範例裡出現了一個重要關鍵字「TemplateBinding」,它的功能為何?

TemplateBinding

     如果使用ContentTemplate時想要延用既有控件的公開屬性,TemplateBinding是標準的作法協助取得這些屬性。

以上述範例來看,ContentTemplate在Background使用了TemplateBinding,如此一來,在原有Button上設定Background,

相同的Grid也會被影響,BorderBrush也是相同的道理。

 

     Control class 定義了一些屬性可被用於ContentTemplated影響Visual Structures裡的控件。至於ContentTemplate怎麼

使用相依的屬性呢?主要有二個方式:

     (1) 在ContentTemplate中的Element使用Binding到該屬性;

     (2) 在ContentTemplate中的Element繼承來從FrameworkElement的屬性;

下列擷取MSDN中提供的可視屬性表:

Property Usage method
Background Template binding
BorderThickness Template binding
BorderBrush Template binding
FontFamily Property inheritance or Template binding
FontSizd Property inheritance or Template binding
ForStretch Property inheritance or Template binding
FontWeight Property inheritance or Template binding
Foreground Property inheritance or Template binding
HorizontalContentAlignment Template binding
Padding Template binding
VerticalContentAlignment Template binding

 

上表只是可視屬性其Control類別,更包括:DataContext、Language、TextDecordation等來自FrameworkElement屬性,

比較特別的是:ContentPresenter與ItemPresenter分成在ContentControl與ItemsControl裡的ContentTemplate自動binding。

 

那麼定義好的ContentTemplate要怎麼套用於新的Button呢?如下:

   1: <StackPanel>
   2:   <!-- 透過StatisResource指定剛定義好的ContentTemplate -->
   3:   <Button Style="{StaticResource newTemplate}" 
   4:           Background="Navy" Foreground="White" FontSize="14"
   5:           Content="Button1"/>
   6:  
   7:   <Button Style="{StaticResource newTemplate}" 
   8:           Background="Purple" Foreground="White" FontSize="14"
   9:           Content="Button2" Click="Button_Click"/>
  10: </StackPanel>

 

 

3. Changing the Appearance of a Control Depending on Its State

     了解如何定義ContentTemplate、調整Visual Structures後,接下來即是了解如何透過State與State Groups的改變,影響控件本身。

ContentTemplate不能改變Control的功能,卻可改變Visual Behavior(視覺行為)。Visual Behavior描述控件在一定狀態下的顯示外觀

舉Button的例子來說,Click事件是功能,但Button在Click有多種Visual Behavior(滑入、按下、放開)它們均影響著Button的外觀。

 

在ContentTemplate中利用VisualState指定控件在一定狀態下出現的樣式,並且它更可結合Storyboard讓外觀更有效果。

另外,在控件State的邏輯可直接使用VisualStateManager控件Control本身具有的VisualState。以下舉例指定VisualState.Name啟動

Storyboard,結束State時一併關閉Storyboard。然而,VisualState.Name需要對應於TemplateVisualStateAttribute中的值。

   1: <!-- 定義在MouseOver的VisualState啟動時,更改Button的BorderBrush的顏色,
   2:       並且使用ColorAnimation.-->
   3: <VisualState x:Name="MouseOver">
   4:   <Storyboard>
   5:     <ColorAnimation Storyboard.TargetName="BorderBrush" 
   6:         Storyboard.TargetProperty="Color" To="Red" />
   7:   </Storyboard>
   8: </VisualState>

 

TemplateVisualStateAttribute

     為何VisualState.Name需要對應TemplateVisualStateAttribute?根據<Customizing Other Controls by Understanding the Control Contract>

內容提及一個Control要有這些VisualState的控件,在實作Control Contract時即需要透過TemplateVisualState去定義要支援的State與Group,

如下範例:

   1: [TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
   2: [TemplateVisualState(Name = "MouseOver", GroupName = "CommonStates")]
   3: [TemplateVisualState(Name = "Pressed", GroupName = "CommonStates")]
   4: [TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]
   5: [TemplateVisualState(Name = "Unfocused", GroupName = "FocusStates")]
   6: [TemplateVisualState(Name = "Focused", GroupName = "FocusStates")]
   7: public class Button : ButtonBase
   8: {
   9:     //在上方訂了支援Normal, MouseOver, Pressed, Disable, Unfocused, Focused
  10:     //並分配至CommonStates與FocusStates二種Groups
  11: }
  12:  

TemplateVisualStateAttribute.GroupName收納有多少的States屬相同的Groups,States在相同的Group內是互相排斥可讓狀態獨立性。

另外,它們的階層是:Visual State < Visual State Group < VisualStateManager.VisualStateGroups,因此如果今天要新增一個新的State,

則需要一層層往上註冊,讓VisualStateManager可管理到Visual State與觸發定義好的樣式。如下範例:

   1: <ControlTemplate TargetType="Button">
   2:   <Border x:Name="RootElement">
   3:     <!-- 修改Visaul State裡內容 -->
   4:     <VisualStateManager.VisualStateGroups>
   5:  
   6:       <!-- CommonStates屬於常見的States集合,定義相關的States。裡面States彼此之間是互斥的-->
   7:       <VisualStateGroup x:Name="CommonStates">
   8:  
   9:         <!-- Normal State代表Button的在正常狀態下。 目前被觸發的是其他人的VisualStateGroups-->
  10:         <VisualState x:Name="Normal" />
  11:  
  12:         <!-- 修改在MouseOver的State被啟動時,透過ColorAnimation修改BorderBrush的顏色-->
  13:         <VisualState x:Name="MouseOver">
  14:           <Storyboard>
  15:             <ColorAnimation Storyboard.TargetName="BorderBrush" 
  16:                               Storyboard.TargetProperty="Color" To="Red" />
  17:           </Storyboard>
  18:         </VisualState>
  19:  
  20:         <!-- 修改在Pressed的State被啟動時,轉換BorderBrush的顏色為透明-->
  21:         <VisualState x:Name="Pressed">
  22:           <Storyboard >
  23:             <ColorAnimation Storyboard.TargetName="BorderBrush" 
  24:                               Storyboard.TargetProperty="Color" To="Transparent"/>
  25:           </Storyboard>
  26:         </VisualState>
  27:         </VisualStateGroup>
  28:     </VisualStateManager.VisualStateGroups>
  29:  
  30:     <Border.Background>
  31:       <SolidColorBrush x:Name="BorderBrush" Color="Black"/>
  32:     </Border.Background>
  33:  
  34:     <Grid Background="{TemplateBinding Background}" Margin="4">
  35:       <ContentPresenter
  36:         HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
  37:         VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
  38:         Margin="4,5,4,4" />
  39:     </Grid>
  40:   </Border>
  41: </ControlTemplate>

 

 

4. Specifying the Behavior of a Control When It Transitions Between States

     接下來談論的是在指定States交換時的行為。舉上述例子而言,當進入Button時,按鈕的邊框變成了紅色,長按超過1秒,

邊框變成了透明。由於Animation預設需要1秒鐘才會觸發,加上Click是很快速的切換Visual State造成影響是很難感覺到的。

因此,為了讓用戶設定每個Visual State觸發狀態的時間長度進一步順暢地連接起其他Visual State,提供了VisualTransition

定義,使用VisualTransition需要注意以下幾點:

    ‧二個Visual State發生交換的過渡時間;

    ‧指定在交換期間控件顯示的額外變化;

    ‧指定那一個VisualState適用何種VisualTransition;

 

     4-1. Specifying the Duration of a Transition

              使用GeneratedDuration屬性指定Visual State交換的過渡時間長度。舉例Button按一秒才能有效果為例,可透過定義

              VisualTransition與GeneratedDuration項目,加速Button按下引發事件的速度;

 

    4-2. Specifying Changes to the Control's Appearance During a Transition

             了解如何調整過渡時間後,接下來是為過渡時間進行時改變控件外貌。在VisualTransition內一樣可以使用Storyboard,

             因此,可以在VisualTransition指定一個Storyboard的開始,等VisualState轉換完畢後再關閉;

 

   結合4.1與4.2的說明,根據<Customizing the Appearance of an Existing Control by Using a ControlTemplate>的範例設定Button在VisualState

   由MouseOver回到Normal時,Button的Border顏色由blue->yellow->black,並且持續1.5秒,如下:

   1: <Button Grid.Row="0" Width="100" Height="100"
   2:            x:Name="btnData" Content="測試" Background="Red">
   3:        <Button.Template>
   4:            <ControlTemplate TargetType="Button" x:Name="newTemplate">
   5:                <Border x:Name="RootElement">
   6:                    <VisualStateManager.VisualStateGroups>
   7:                        <VisualStateGroup x:Name="CommonStates">
   8:                            <VisualState x:Name="Normal" />
   9:                            <!-- 修改在MouseOver的State被啟動時,透過ColorAnimation修改BorderBrush的顏色-->
  10:                            <VisualState x:Name="MouseOver" />
  11:  
  12:                            <!-- 修改在Pressed的State被啟動時,轉換BorderBrush的顏色為透明-->
  13:                            <VisualState x:Name="Pressed">
  14:                                <Storyboard >
  15:                                    <ColorAnimation Storyboard.TargetName="BorderBrush" 
  16:                                        Storyboard.TargetProperty="Color" To="Transparent"/>
  17:                                </Storyboard>
  18:                            </VisualState>
  19:                            
  20:                            <VisualStateGroup.Transitions>
  21:                                <VisualTransition From="MouseOver" To="Normal" 
  22:                                                  GeneratedDuration="0:0:1.5">
  23:                                    <!-- 設定一個Storyboard,分別在這1.5秒中變換3種顏色 -->
  24:                                    <Storyboard>
  25:                                        <ColorAnimationUsingKeyFrames
  26:                                              Storyboard.TargetProperty="Color"
  27:                                              Storyboard.TargetName="BorderBrush"
  28:                                              FillBehavior="HoldEnd" >
  29:                                            <ColorAnimationUsingKeyFrames.KeyFrames>
  30:                                                <!--0.5秒轉成藍色,1秒轉成黃色,1.5秒轉成黑色-->
  31:                                                <LinearColorKeyFrame Value="Blue" KeyTime="0:0:0.5" />
  32:                                                <LinearColorKeyFrame Value="Yellow" KeyTime="0:0:1" />
  33:                                                <LinearColorKeyFrame Value="Black" KeyTime="0:0:1.5" /> 
  34:                                            </ColorAnimationUsingKeyFrames.KeyFrames>
  35:                                        </ColorAnimationUsingKeyFrames>
  36:                                    </Storyboard>
  37:                                </VisualTransition>
  38:                            </VisualStateGroup.Transitions>
  39:                        </VisualStateGroup>
  40:                    </VisualStateManager.VisualStateGroups>
  41:  
  42:                    <!--為Border建立SolidColorBrush物件,並且給予一個名稱,它可在這Template中被使用-->
  43:                    <Border.Background>
  44:                        <SolidColorBrush x:Name="BorderBrush" Color="White"/>
  45:                    </Border.Background>
  46:  
  47:  
  48:                    <!--為Grid建立不同的Background,分開與Border背景不同的顏色,Grid的Background,
  49:                        代表Button的Background顏色-->
  50:                    <Grid Margin="4" x:Name="GridBrush" Background="{TemplateBinding Background}">
  51:  
  52:                        <!--使用ContentPresenter呈現Button的Content-->
  53:                        <ContentPresenter
  54:                        HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
  55:                        VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
  56:                        Margin="0,0,0,0" />
  57:  
  58:                    </Grid>
  59:                </Border>
  60:            </ControlTemplate>
  61:        </Button.Template>                
  62:    </Button>

 

    4-3. Specifying When a VisualTransition Is Applied

            在VisualTransition使用上可支援任何時間控件State之間的轉換,但有一些限制,透過下表來加以說明:

Type of restriction Value of From Value of To
From a specified state to another specified state The name of a VisualState The name of a VisualState
From any state to a specified state Not set The name of a VisualState
From a specified state to any state The name of a VisualState Not set
From any state to any other state Not set Not set

          可配合上表檢視自己要使用的VisualTransition是否需指定From與To來加以使用,如果有指定From與To代表該VisualTransition被

          啟動時機需要滿足二個設定值,如果只設定From或To,則會按照指定的VisualState來啟動。例如:

   1: <!-- 在MouseOver時保持0.5秒的轉換 -->
   2: <VisualTransition To="MouseOver" 
   3:                       GeneratedDuration="0:0:0.5" />
   4:  
   5: <!-- 在Pressed轉至MouseOver時保持0.01秒的轉換 -->
   6: <VisualTransition From="Pressed" To="MouseOver" 
   7:                       GeneratedDuration="0:0:0.01" />

 

 

(4) 讓Custom Control實作Parts and States Model,支援Expression Blend

       在上述說明透過XAML調整既有控件的Visual behavior、Visual structure後,最後再討論客製控件如何實作Control Contract,

以支援在Blend工具上可以直接操作客製好的控件。

Control Contract有三個元素:

   ‧可供控件邏輯使用的Visual Element。(例如:Button的Border、Grid…等)

   ‧控件具有的State與State Groups。

   ‧影響控件視覺的公開屬性。

往下分別三元素加以說明。

 

(4-1). Visual Elements in the Control Contract

            大部分Control的邏輯是繼承FrameworkElement,而具有ContentTemplate功能。客製控件時,如果希望在ContentTemplate

中找到特定的FrameworkElement,必須傳達訊息給ContentTemplate。傳達的方式透過TemplatePartAttribute指定type類型與name,

讓ContentTemplate可以得知那些FrameworkElement在客製中將會有所互動。

           舉例來說:Combox中指定ContentPresenter與Popup,告訴ContentTemplate期望找到這二個控件。

   1: [TemplatePartAttribute(Name = "ContentPresenter", Type = typeof(ContentPresenter))]
   2: [TemplatePartAttribute(Name = "Popup", Type = typeof(Popup))]
   3: public class ComboBox : ItemsControl
   4: {
   5: }

          在程式裡指定好這二個期望物件後,即可以在XAML內容中取得相關內容,如下:

   1:  
   2: <ControlTemplate TargetType="ComboBox">
   3:   <Grid>
   4:     <Border x:Name="ContentPresenterBorder">
   5:       <Grid>
   6:         <ToggleButton x:Name="DropDownToggle"
   7:                       HorizontalAlignment="Stretch" VerticalAlignment="Stretch"  
   8:                       Margin="-1" HorizontalContentAlignment="Right">
   9:           <Path x:Name="BtnArrow" Height="4" Width="8" 
  10:                 Stretch="Uniform" Margin="0,0,6,0"  Fill="Black"
  11:                 Data="F1 M 300,-190L 310,-190L 305,-183L 301,-190 Z " />
  12:         </ToggleButton>
  13:         <ContentPresenter x:Name="ContentPresenter" Margin="6,2,25,2">
  14:           <TextBlock Text=" " />
  15:         </ContentPresenter>
  16:       </Grid>
  17:     </Border>
  18:     <Popup x:Name="Popup">
  19:       <Border x:Name="PopupBorder" 
  20:               HorizontalAlignment="Stretch" Height="Auto" 
  21:               BorderThickness="{TemplateBinding BorderThickness}" 
  22:               BorderBrush="Black" Background="White" CornerRadius="3">
  23:         <ScrollViewer x:Name="ScrollViewer" BorderThickness="0" Padding="1">
  24:           <ItemsPresenter/>
  25:         </ScrollViewer>
  26:       </Border>
  27:     </Popup>
  28:  
  29:   </Grid>
  30: </ControlTemplate>

 

(4-2). States in the Control Contract

          每一個Control均有自身的Visual State可以應用,如何修改狀態呢,可參考<Changing the Appearance of a Control Depending on Its State>

的說明,有比較完整的範例與說明,包括如何建立自立的VisualSate與定義Group等。

 

(4-3). Properties in the Control Contract

        修改控件視覺的公開屬性也是Control Contract之一,它可以讓我們不用定義ContentTemplate可以達到視覺的調整,更可以使用

TemplateBinding的方式擴充屬性的設定。

   1:  
   2: [TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
   3: [TemplateVisualState(Name = "MouseOver", GroupName = "CommonStates")]
   4: [TemplateVisualState(Name = "Pressed", GroupName = "CommonStates")]
   5: [TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]
   6: [TemplateVisualState(Name = "Unfocused", GroupName = "FocusStates")]
   7: [TemplateVisualState(Name = "Focused", GroupName = "FocusStates")]
   8: public class Button : ButtonBase
   9: {
  10:     public static readonly DependencyProperty BackgroundProperty;
  11:     public static readonly DependencyProperty BorderBrushProperty;
  12:     public static readonly DependencyProperty BorderThicknessProperty;
  13:     public static readonly DependencyProperty ContentProperty;
  14:     public static readonly DependencyProperty ContentTemplateProperty;
  15:     public static readonly DependencyProperty FontFamilyProperty;
  16:     public static readonly DependencyProperty FontSizeProperty;
  17:     public static readonly DependencyProperty FontStretchProperty;
  18:     public static readonly DependencyProperty FontStyleProperty;
  19:     public static readonly DependencyProperty FontWeightProperty;
  20:     public static readonly DependencyProperty ForegroundProperty;
  21:     public static readonly DependencyProperty HorizontalContentAlignmentProperty;
  22:     public static readonly DependencyProperty PaddingProperty;
  23:     public static readonly DependencyProperty TextAlignmentProperty;
  24:     public static readonly DependencyProperty TextDecorationsProperty;
  25:     public static readonly DependencyProperty TextWrappingProperty;
  26:     public static readonly DependencyProperty VerticalContentAlignmentProperty;
  27:  
  28:     public Brush Background { get; set; }
  29:     public Brush BorderBrush { get; set; }
  30:     public Thickness BorderThickness { get; set; }
  31:     public object Content { get; set; }
  32:     public DataTemplate ContentTemplate { get; set; }
  33:     public FontFamily FontFamily { get; set; }
  34:     public double FontSize { get; set; }
  35:     public FontStretch FontStretch { get; set; }
  36:     public FontStyle FontStyle { get; set; }
  37:     public FontWeight FontWeight { get; set; }
  38:     public Brush Foreground { get; set; }
  39:     public HorizontalAlignment HorizontalContentAlignment { get; set; }
  40:     public Thickness Padding { get; set; }
  41:     public TextAlignment TextAlignment { get; set; }
  42:     public TextDecorationCollection TextDecorations { get; set; }
  43:     public TextWrapping TextWrapping { get; set; }
  44:     public VerticalAlignment VerticalContentAlignment { get; set; }
  45: }

 

更多詳細介紹怎麼客製Control與定義Control Contract可參考<Customizing the Appearance of an Existing Control by Using a ControlTemplate>。

 

[補充]

‧上述說明的Parts and States Model只是建議客製控件的一種做法,它並非由Silverlight Runtime所需的,並非一定要按此實作,

     但是如想在Expression Blend上使用,即需要實作Parts and States Model Pattern。

 

ContentPresenter、ContentPresenterBorder、DropDownToggle

    透過下圖加以說明:

Ee341409.69fada84-8d77-4b89-a14c-18fcbd2da5a9(zh-tw,Expression.40).png

組名名稱 說明
(1) ContentPresenter 強制項目,用途是顯示目前的項目。如果將一些內容放入範本中的 ContentPresenter組件,只要沒有選取項目,就會顯示此內容。
(2) ContentPresenterBorder 如果 IsHitTestVisible為 True,則可以按一下 ContentPresenterBorder組件來開啟和關閉快顯視窗,而且快顯視窗將位於 ContentPresenterBorder (2) 左下角。版面配置面板對 ContentPresenterBorder組件來說是不錯的選擇。
(3) DropDownToggle 選用項目,但這可當作開啟和關閉快顯視窗的另一種方式。
(4) Popup 強制項目。這是在其中顯示項目的組件。 ItemsPresenter可用來指定顯示項目的位置。 ItemsPresenter應置於 ScrollViewer組件內。 Popup的開啟位置相對於範本根物件的界限。

 

======

以上是整理<Customizing the Appearance of an Existing Control by Using a ControlTemplate>一文中描述如何了解

ContentTemplate的使用方式。由於客製Control還有相關Control Contract的部分是開發人員在實作新的XAML控

件時需要特別注意的地方。希望對大家有所幫助。

 

References

建立系統控制項可重複使用的範本 (Blend教學檔)

Customizing the Appearance of an Existing Control by Using a ControlTemplate (重要)

Creating a New Control by Creating a ControlTemplate

Walkthrough: Customizing the Appearance of a Button by Using a ControlTemplate

Customizing Other Controls by Understanding the Control Contract

Applying a ContentTemplate via C# Trigger

Control Customization

Getting Started with Controls

Control Styles and Templates (重要的參考,列出控件可使用的Styles與Templates)

 

Dotblogs Tags: