控制項撰寫概觀

Windows Presentation Foundation (WPF) 控制項模型的擴充性可大幅減少建立新控制項的需求。 不過,在某些情況下,您可能還是需要建立自訂控制項。 本主題討論在 Windows Presentation Foundation 中建立自訂控制項和不同控制項撰寫模型所需的功能。 本主題也將示範如何建立新的控制項。

撰寫新控制項的替代方案

在過去,若要從現有控制項自訂控制項,只能變更控制項的標準屬性,例如背景色彩、框線寬度和字型大小。 除了這些預先定義的參數外,若還想擴充控制項的外觀或行為,就需要建立新的控制項,建立的方式通常是繼承現有控制項並覆寫負責繪製控制項。 雖然這仍然是一個選項,但 WPF 可讓您使用其豐富的 con帳篷模式l、樣式、範本和觸發程式來自訂現有的控制項。 下列清單提供的範例說明如何在不建立新控制項的情況下,使用這些功能來建立自訂且一致的控制項。

  • 豐富內容。 許多標準 WPF 控制項都支援豐富的內容。 例如,的 content 屬性 Button 的類型為 Object ,因此理論上任何專案都可以顯示在 上 Button 。 若要讓按鈕顯示影像和文字,您可以將影像和 TextBlock 新增至 ,並將 指派 StackPanelStackPanelContent 屬性。 因為控制項可以顯示 WPF 視覺效果元素和任意資料,因此不需要建立新的控制項或修改現有的控制項以支援複雜的視覺效果。 如需有關 wpf 中 con帳篷模式l Button 和其他 con帳篷模式ls 的詳細資訊,請參閱 WPF 內容模型

  • 樣式。 Style是值集合,表示控制項的屬性。 藉由使用樣式,您可以針對所要的控制項外觀和行為,建立可重複使用的表示方式,而不需要撰寫新的控制項。 例如,假設您想要讓所有 TextBlock 控制項都有紅色的 Arial 字型,字型大小為 14。 您可以建立樣式作為資源,並對應地設定適當的屬性。 然後,您新增至應用程式的每個 TextBlock 專案都會有相同的外觀。

  • 資料範本。 DataTemplate可讓您自訂在 控制項上顯示資料的方式。 例如, DataTemplate 可用來指定 如何在 中 ListBox 顯示資料。 如需此作業的範例,請參閱資料範本化概觀。 除了自訂資料的外觀之外,還可以 DataTemplate 包含 UI 元素,讓您在自訂 UI 中擁有許多彈性。 例如,藉由使用 DataTemplate ,您可以建立 , ComboBox 其中每個專案都包含核取方塊。

  • 控制項範本。 WPF 中的許多控制項都會使用 ControlTemplate 來定義控制項的結構和外觀,這會分隔控制項的外觀與控制項的功能。 您可以藉由重新定義 ControlTemplate 控制項 來大幅變更控制項的外觀。 例如,假設您希望控制項看起來像號誌燈。 這個控制項具有簡單的使用者介面和功能。 控制項是三個圓圈,每次只會亮其中一個。 經過一些反射之後,您可能會發現 , RadioButton 提供一次只選取一個的功能,但的預設面板 RadioButton 看起來與停燈上的燈光一樣。 RadioButton因為 會使用控制項範本來定義其外觀,因此很容易重新 ControlTemplate 定義 以符合控制項的需求,並使用選項按鈕進行停燈。

    注意

    RadioButton雖然 可以使用 DataTemplateDataTemplate 但此範例中沒有足夠的 。 DataTemplate定義控制項內容的外觀。 在 的案例中 RadioButton ,內容會是圓形右邊所顯示的任何內容,指出是否已 RadioButton 選取 。 在停電燈的範例中,選項按鈕只需要一個可以「亮起」的圓形。由於停燈的外觀需求與 的預設面板 RadioButton 不同,因此必須重新定義 ControlTemplate 。 一般而言, DataTemplate 用於定義控制項的內容(或資料),而 ControlTemplate 用於定義控制項的結構方式。

  • 觸發程序。 Trigger可讓您動態變更控制項的外觀和行為,而不需建立新的控制項。 例如,假設您在應用程式中有多個 ListBox 控制項,並且希望每個 ListBox 控制項中的專案在選取時為粗體和紅色。 您的第一個本能可能是建立繼承自 ListBox 的類別,並覆寫 OnSelectionChanged 方法來變更選取專案的外觀,但更好的方法是將觸發程式新增至 ListBoxItem 樣式,以變更選取專案的外觀。 觸發程序可讓您變更屬性值,或是依據屬性值採取動作。 EventTrigger可讓您在事件發生時採取動作。

如需樣式、範本和觸發程序的詳細資訊,請參閱設定樣式和範本

一般而言,若您的控制項可以對應到現有控制項的功能,但您希望控制項看起來不太一樣,則應該先考慮是否能使用本節中所討論的任何方法來變更現有控制項的外觀。

控制項撰寫模型

豐富的內容模型、樣式、範本和觸發程序,可以讓您將建立新控制項的需求降到最低。 不過,如果您需要建立新的控制項,請務必瞭解 WPF 中的不同控制項撰寫模型。 WPF 提供三個一般模型來建立控制項,每個模型都提供一組不同的功能和彈性層級。 三個模型的基類為 UserControlControlFrameworkElement

衍生自 UserControl

在 WPF 中建立控制項最簡單的方法是衍生自 UserControl 。 當您建置繼承自 UserControl 的控制項時,您會將現有的元件新增至 UserControl 、將元件命名為 ,並在 XAML 中參考事件處理常式。 接著,您可以在程式碼中參考該具名項目,並定義事件處理常式。 此開發模型與 WPF 中用於應用程式開發的模型非常類似。

如果正確建置, UserControl 則可以利用豐富內容、樣式和觸發程式的優點。 不過,如果您的控制項繼承自 UserControl ,則使用控制項的人員將無法使用 DataTemplateControlTemplate 來自訂其外觀。 必須衍生自 Control 類別或其其中一個衍生類別(非 UserControl ) 來建立支援範本的自訂控制項。

從 UserControl 衍生的優點

如果適用下列所有專案,請考慮衍生自 UserControl

  • 您想要以類似建置應用程式的方式來建置控制項。

  • 您的控制項只包含現有的元件。

  • 您不需要支援複雜的自訂作業。

衍生自 Control

衍生自 Control 類別是大部分現有 WPF 控制項所使用的模型。 當您建立繼承自 類別的 Control 控制項時,您可以使用範本來定義其外觀。 這樣做您便可以將作業邏輯與視覺表示方式分開處理。 您也可以使用命令和系結,而不是事件,避免盡可能參考 中的 ControlTemplate 元素,以確保 UI 和邏輯的分離。 如果控制項的 UI 和邏輯已適當分離,控制項的使用者就可以重新定義控制項的 ControlTemplate 來自訂其外觀。 雖然建置自訂 Control 並不像建 UserControl 置 那麼簡單,但自訂 Control 提供最大的彈性。

從 Control 衍生的優點

如果適用下列任一項,請考慮衍生自 Control 而不是使用 UserControl 類別:

  • 您希望控制項的外觀可透過 ControlTemplate 自訂。

  • 您想要控制項支援不同的佈景主題。

衍生自 FrameworkElement

衍生自 UserControlControl 依賴撰寫現有元素的控制項。 在許多情況下,這是可接受的解決方案,因為繼承自 FrameworkElement 的任何物件都可以在 中 ControlTemplate 。 然而,有時候控制項外觀需要的不僅止於簡單項目組合的功能。 在這些案例中,以 元件 FrameworkElement 為基礎是正確的選擇。

建置型元件的標準方法 FrameworkElement 有兩種:直接轉譯和自訂專案組合。 直接轉譯牽涉到 OnRender 覆寫 的 方法 FrameworkElement ,並提供 DrawingContext 明確定義元件視覺效果的作業。 這是 和 Border 所使用的 Image 方法。 自訂專案組合牽涉到使用 類型的 Visual 物件來撰寫元件的外觀。 如需範例,請參閱使用 DrawingVisual 物件Track 是 WPF 中使用自訂專案組合的控制項範例。 您也可以在同一個控制項中混合使用直接轉譯和自訂項目組合。

從 FrameworkElement 衍生的優點

如果適用下列任一項,請考慮衍生自 FrameworkElement

  • 您想要精確控制控制項的外觀,而這超出了簡單項目組合所能提供的控制。

  • 您想要藉由定義自己的轉譯邏輯,來定義控制項的外觀。

  • 您想要以新穎的方式撰寫現有的元素,超越 和 Control 的可能 UserControl

控制項撰寫基本概念

如先前所述,WPF 最強大的功能之一是超越設定控制項的基本屬性以變更其外觀和行為的能力,但仍不需要建立自訂控制項。 WPF 屬性系統和 WPF 事件系統可以設定樣式、資料系結和觸發程式功能。 下列各節說明您應該遵循的一些做法,不論您用來建立自訂控制項的模型為何,自訂控制項的使用者都可以像 WPF 隨附的控制項一樣使用這些功能。

使用相依性屬性

當屬性是相依性屬性時,有可能會進行下列作業:

  • 設定樣式中的屬性。

  • 將屬性繫結至資料來源。

  • 使用動態資源作為屬性值。

  • 顯示屬性的動畫。

若希望控制項屬性可以支援這些任一功能,就應該將控制項實作為相依性屬性。 下列範例會藉由執行下列動作,定義名為 Value 的相依性屬性:

  • DependencyProperty將名為 ValuePropertypublicstaticreadonly 識別碼定義為欄位。

  • 藉由呼叫 DependencyProperty.Register ,向屬性系統註冊屬性名稱,以指定下列專案:

  • 藉由實作 屬性的 getset 存取子,定義名為 Value 的 CLR 包裝函式屬性,這是用來註冊相依性屬性的相同名稱。 請注意, getset 存取子只會分別呼叫 GetValueSetValue 。 建議相依性屬性的存取子不包含其他邏輯,因為用戶端和 WPF 可以略過存取子並直接呼叫 GetValueSetValue 。 例如,在屬性繫結到資料來源時,就不會呼叫屬性的 set 存取子。 不使用將其他邏輯新增至 get 和 set 存取子,而是使用 ValidateValueCallbackCoerceValueCallbackPropertyChangedCallback 委派來回應或檢查其變更時的值。 如需這些回呼的詳細資訊,請參閱相依性屬性回呼和驗證

  • 定義具名 CoerceValue 的方法 CoerceValueCallbackCoerceValue 可以確保 Value 大於或等於 MinValue,且小於或等於 MaxValue

  • 定義 名為 的方法 PropertyChangedCallbackOnValueChangedOnValueChanged 會建立 RoutedPropertyChangedEventArgs<T> 物件,並準備引發 ValueChanged 路由事件。 下一節中將討論路由事件。

/// <summary>
/// Identifies the Value dependency property.
/// </summary>
public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register(
        "Value", typeof(decimal), typeof(NumericUpDown),
        new FrameworkPropertyMetadata(MinValue, new PropertyChangedCallback(OnValueChanged),
                                      new CoerceValueCallback(CoerceValue)));

/// <summary>
/// Gets or sets the value assigned to the control.
/// </summary>
public decimal Value
{
    get { return (decimal)GetValue(ValueProperty); }
    set { SetValue(ValueProperty, value); }
}

private static object CoerceValue(DependencyObject element, object value)
{
    decimal newValue = (decimal)value;
    NumericUpDown control = (NumericUpDown)element;

    newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue));

    return newValue;
}

private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
    NumericUpDown control = (NumericUpDown)obj;			

    RoutedPropertyChangedEventArgs<decimal> e = new RoutedPropertyChangedEventArgs<decimal>(
        (decimal)args.OldValue, (decimal)args.NewValue, ValueChangedEvent);
    control.OnValueChanged(e);
}
''' <summary>
''' Identifies the Value dependency property.
''' </summary>
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Decimal), GetType(NumericUpDown), New FrameworkPropertyMetadata(MinValue, New PropertyChangedCallback(AddressOf OnValueChanged), New CoerceValueCallback(AddressOf CoerceValue)))

''' <summary>
''' Gets or sets the value assigned to the control.
''' </summary>
Public Property Value() As Decimal
    Get
        Return CDec(GetValue(ValueProperty))
    End Get
    Set(ByVal value As Decimal)
        SetValue(ValueProperty, value)
    End Set
End Property

Private Shared Overloads Function CoerceValue(ByVal element As DependencyObject, ByVal value As Object) As Object
    Dim newValue As Decimal = CDec(value)
    Dim control As NumericUpDown = CType(element, NumericUpDown)

    newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue))

    Return newValue
End Function

Private Shared Sub OnValueChanged(ByVal obj As DependencyObject, ByVal args As DependencyPropertyChangedEventArgs)
    Dim control As NumericUpDown = CType(obj, NumericUpDown)

    Dim e As New RoutedPropertyChangedEventArgs(Of Decimal)(CDec(args.OldValue), CDec(args.NewValue), ValueChangedEvent)
    control.OnValueChanged(e)
End Sub

如需詳細資訊,請參閱自訂相依性屬性

使用路由事件

如同相依性屬性擴充具有額外功能的 CLR 屬性概念,路由事件擴充標準 CLR 事件的概念。 當您建立新的 WPF 控制項時,因為路由事件支援下列行為,所以將事件實作為路由事件也是很好的作法:

  • 事件可以對多個控制項的父項進行處理。 若事件是反昇事件,項目樹狀結構中的單一父項可以訂閱事件。 然後應用程式作者可以使用一個處理常式,以回應多個控制項的事件。 例如,如果您的控制項是 中每個專案的 ListBox 一部分(因為它包含在 中 DataTemplate ),應用程式開發人員可以在 上 ListBox 定義控制項事件的事件處理常式。 每當任一控制項上發生事件時,就會呼叫事件處理常式。

  • 路由事件可用於 EventSetter 中,這可讓應用程式開發人員在樣式中指定事件的處理常式。

  • 路由事件可用於 EventTrigger 中,這適用于使用 XAML 建立屬性的動畫效果。 如需詳細資訊,請參閱 動畫概觀

下列範例會藉由執行下列動作,定義路由事件:

  • RoutedEvent將名為 ValueChangedEventpublicstaticreadonly 識別碼定義為欄位。

  • 呼叫 EventManager.RegisterRoutedEvent 方法以註冊路由事件。 此範例會在呼叫 RegisterRoutedEvent 時指定下列資訊:

    • 事件名稱是 ValueChanged

    • 路由策略為 Bubble ,這表示會先呼叫來源上的事件處理常式(引發事件的 物件),然後接連呼叫來源父元素上的事件處理常式,從最接近父元素上的事件處理常式開始。

    • 事件處理常式的類型是 RoutedPropertyChangedEventHandler<T> ,是使用 Decimal 型別建構的。

    • 事件的擁有者類型是 NumericUpDown

  • 宣告名為 ValueChanged 的公用事件,並包含事件存取子宣告。 此範例會在 add 存取子宣告和 RemoveHandler 存取子宣告中 remove 呼叫 AddHandler ,以使用 WPF 事件服務。

  • 建立名為 OnValueChanged 的受保護虛擬方法,該方法會引發 ValueChanged 事件。

/// <summary>
/// Identifies the ValueChanged routed event.
/// </summary>
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
    "ValueChanged", RoutingStrategy.Bubble,
    typeof(RoutedPropertyChangedEventHandler<decimal>), typeof(NumericUpDown));

/// <summary>
/// Occurs when the Value property changes.
/// </summary>
public event RoutedPropertyChangedEventHandler<decimal> ValueChanged
{
    add { AddHandler(ValueChangedEvent, value); }
    remove { RemoveHandler(ValueChangedEvent, value); }
}

/// <summary>
/// Raises the ValueChanged event.
/// </summary>
/// <param name="args">Arguments associated with the ValueChanged event.</param>
protected virtual void OnValueChanged(RoutedPropertyChangedEventArgs<decimal> args)
{
    RaiseEvent(args);
}
''' <summary>
''' Identifies the ValueChanged routed event.
''' </summary>
Public Shared ReadOnly ValueChangedEvent As RoutedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, GetType(RoutedPropertyChangedEventHandler(Of Decimal)), GetType(NumericUpDown))

''' <summary>
''' Occurs when the Value property changes.
''' </summary>
Public Custom Event ValueChanged As RoutedPropertyChangedEventHandler(Of Decimal)
    AddHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
        MyBase.AddHandler(ValueChangedEvent, value)
    End AddHandler
    RemoveHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
        MyBase.RemoveHandler(ValueChangedEvent, value)
    End RemoveHandler
    RaiseEvent(ByVal sender As System.Object, ByVal e As RoutedPropertyChangedEventArgs(Of Decimal))
    End RaiseEvent
End Event

''' <summary>
''' Raises the ValueChanged event.
''' </summary>
''' <param name="args">Arguments associated with the ValueChanged event.</param>
Protected Overridable Sub OnValueChanged(ByVal args As RoutedPropertyChangedEventArgs(Of Decimal))
    MyBase.RaiseEvent(args)
End Sub

如需詳細資訊,請參閱路由事件概觀建立自訂路由事件

使用繫結

若要降低控制項 UI 和邏輯間的耦合,請考慮使用資料繫結。 如果您使用 來定義控制項 ControlTemplate 的外觀,這特別重要。 在您使用資料繫結時,也許可以排除從程式碼參考特定部分之 UI 的需要。 最好避免參考 中的 ControlTemplate 專案,因為當程式碼參考 和 中的專案 ControlTemplateControlTemplate 已變更時,參考的專案必須包含在新的 ControlTemplate 中。

下列範例會更新 TextBlock 控制項的 NumericUpDown ,並將名稱指派給控制項,並在程式碼中依名稱參考文字方塊。

<Border BorderThickness="1" BorderBrush="Gray" Margin="2" 
        Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">
  <TextBlock Name="valueText" Width="60" TextAlignment="Right" Padding="5"/>
</Border>
private void UpdateTextBlock()
{
    valueText.Text = Value.ToString();
}
Private Sub UpdateTextBlock()
    valueText.Text = Value.ToString()
End Sub

下列範例使用繫結達成相同目的。

<Border BorderThickness="1" BorderBrush="Gray" Margin="2" 
        Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">

    <!--Bind the TextBlock to the Value property-->
    <TextBlock 
        Width="60" TextAlignment="Right" Padding="5"
        Text="{Binding RelativeSource={RelativeSource FindAncestor, 
                       AncestorType={x:Type local:NumericUpDown}}, 
                       Path=Value}"/>

</Border>

如需有關資料繫結的詳細資訊,請參閱 資料繫結概觀

設計工具的設計

若要在 Visual Studio 的 WPF 設計工具中接收自訂 WPF 控制項的支援(例如,使用 屬性視窗 進行屬性編輯),請遵循這些指導方針。 如需 WPF 設計工具開發的詳細資訊,請參閱 在 Visual Studio 中設計 XAML。

相依性屬性

請務必如先前的中所述實作 CLR getset 存取子。設計工具可以使用包裝函式來偵測相依性屬性的存在,但是它們,例如 WPF 和 控制項的用戶端,在取得或設定 屬性時不需要呼叫存取子。

附加屬性

請使用下列方針,實作自訂控制項上的附加屬性:

  • 具有使用 方法建立之 PropertyName Property 格式 RegisterAttachedpublicstaticreadonlyDependencyProperty 傳遞至 RegisterAttached 的屬性名稱必須符合 PropertyName

  • 實作一組 publicstatic CLR 方法,分別名為 Set<屬性名稱>Get<屬性名稱>。 這兩種方法都應該接受衍生自 DependencyProperty 的類別作為其第一個引數。 SetPropertyName 方法也接受其型別與屬性之已註冊資料型別相符的引數。 <屬性名稱>Get 方法應傳回相同類型的值。 若遺漏 <屬性名稱>Set 方法,屬性就會標示為唯讀。

  • SetPropertyName Get PropertyName 必須分別路由傳送至 GetValue 目標相依性物件上的 和 SetValue 方法。 藉由呼叫方法包裝函式,或直接呼叫目標相依性物件,設計工具可以存取附加屬性。

如需附加屬性的詳細資訊,請參閱附加屬性概觀

定義和使用共用資源

您可以將控制項納入與應用程式相同的組件,或者將控制項封裝在不同的組件中,以用於多個應用程式。 在大多數情況下,不論使用的方法為何,本主題所討論的資訊都適用。 不過,有一項差異值得注意。 當您將控制項放入與應用程式相同的組件中時,可以任意將全域資源新增至 App.xaml 檔案。 但是,只包含控制項的元件沒有 Application 與其相關聯的物件,因此無法使用 App.xaml 檔案。

當應用程式尋找資源時,會以下列順序在三個層級中尋找:

  1. 項目層級。

    系統會從參考資源的項目開始,然後搜尋邏輯父項的資源,以此類推,直到達到根項目為止。

  2. 應用程式層級。

    物件所 Application 定義的資源。

  3. 佈景主題層級。

    佈景主題層級字典會儲存在名為 Themes 的子資料夾。 Themes 資料夾中的檔案會與佈景主題對應。 例如,您可能有 Aero.NormalColor.xaml、Luna.NormalColor.xaml、Royale.NormalColor.xaml 等等。 您也可以有名為 generic.xaml 的檔案。 當系統在佈景主題層級尋找資源時,會先在佈景主題特定檔案中尋找資源,再到 generic.xaml 中尋找資源。

當您的控制項位於與應用程式不同的組件中時,必須將全域資源置於項目層級或佈景主題層級。 這兩種方法各有其優點。

在項目層級定義資源

您可以在專案層級定義共用資源,方法是建立自訂資源字典,並將其與控制項的資源字典合併。 當您使用這個方法時,可以隨意命名資源檔,而且資源檔可以與控制項位於相同的資料夾中。 項目層級的資源也可以使用簡單的字串作為索引鍵。 下列範例會 LinearGradientBrush 建立名為 Dictionary1.xaml 的資源檔。

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <LinearGradientBrush 
    x:Key="myBrush"  
    StartPoint="0,0" EndPoint="1,1">
    <GradientStop Color="Red" Offset="0.25" />
    <GradientStop Color="Blue" Offset="0.75" />
  </LinearGradientBrush>
  
</ResourceDictionary>

定義字典之後,您需要將它與控制項的資源字典合併。 您可以使用 XAML 或程式碼來執行此動作。

下列範例會使用 XAML 合併資源字典。

<UserControl.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Dictionary1.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</UserControl.Resources>

此方法的缺點是 ResourceDictionary 每次參考物件時都會建立物件。 例如,如果您的程式庫中有 10 個自訂控制項,並使用 XAML 合併每個控制項的共用資源字典,您就會建立 10 個相同的 ResourceDictionary 物件。 您可以藉由建立靜態類別來合併程式碼中的資源,並傳回產生的 ResourceDictionary 來避免這種情況。

下列範例會建立會傳回共用 的 ResourceDictionary 類別。

internal static class SharedDictionaryManager
{
    internal static ResourceDictionary SharedDictionary
    {
        get
        {
            if (_sharedDictionary == null)
            {
                System.Uri resourceLocater =
                    new System.Uri("/ElementResourcesCustomControlLibrary;component/Dictionary1.xaml",
                                    System.UriKind.Relative);

                _sharedDictionary =
                    (ResourceDictionary)Application.LoadComponent(resourceLocater);
            }

            return _sharedDictionary;
        }
    }

    private static ResourceDictionary _sharedDictionary;
}

下列範例會在呼叫 InitializeComponent 之前,在自訂控制項的建構函式中將共用資源與該控制項的資源合併。 SharedDictionaryManager.SharedDictionary因為 是靜態屬性, ResourceDictionary 因此只會建立一次。 由於資源字典在呼叫之前 InitializeComponent 已合併,因此資源可供其 XAML 檔案中的控制項使用。

public NumericUpDown()
{
    this.Resources.MergedDictionaries.Add(SharedDictionaryManager.SharedDictionary);
    InitializeComponent();
}

在佈景主題層級定義資源

WPF 可讓您為不同的 Windows 主題建立資源。 身為控制項作者,您可以為特定佈景主題定義資源,以根據使用的佈景主題變更控制項的外觀。 例如,Windows 傳統主題中 的外觀 Button (Windows 2000 的預設主題)與 Button Windows Luna 主題中的 不同,因為 Button 會針對每個主題使用不同的 ControlTemplate 主題。

某個佈景主題專屬的資源會存放在具有特定檔名的資源字典中。 這些檔案必須位於名為 Themes 的資料夾中,這是包含控制項之資料夾的子資料夾。 下表列出資源字典檔以及與每個檔案相關聯的佈景主題:

資源字典檔名稱 Windows 佈景主題
Classic.xaml Windows XP 上的傳統 Windows 9x/2000 外觀
Luna.NormalColor.xaml Windows XP 上的預設藍色佈景主題
Luna.Homestead.xaml Windows XP 上的橄欖色佈景主題
Luna.Metallic.xaml Windows XP 上的銀色佈景主題
Royale.NormalColor.xaml Windows XP Media Center Edition 上的預設佈景主題
Aero.NormalColor.xaml Windows Vista 上的預設佈景主題

您不需要為每個佈景主題定義資源。 若未定義特定佈景主題的資源,則控制項會在 Classic.xaml 中檢查資源。 若在對應於目前佈景主題的檔案中或在 Classic.xaml 中都未定義資源,控制項會使用名為 generic.xaml 的資源字典檔中的一般資源。 generic.xaml 檔案位於與佈景主題特有資源字典檔相同的資料夾中。 雖然 generic.xaml 並未對應到特定 Windows 佈景主題,但它仍是佈景主題層級字典。

具有主題和 UI 自動化支援範例的 C# Visual Basic NumericUpDown 自訂控制項包含控制項的兩個資源字典 NumericUpDown :一個是在 generic.xaml 中,另一個是在 Luna.NormalColor.xaml 中。

當您將 放在 ControlTemplate 任何主題特定資源字典檔案中時,您必須為 控制項建立靜態建構函式,並在 上 DefaultStyleKey 呼叫 OverrideMetadata(Type, PropertyMetadata) 方法,如下列範例所示。

static NumericUpDown()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown),
               new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}
Shared Sub New()
    DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
End Sub
定義和參考佈景主題資源的索引鍵

當您在項目層級定義資源時,可以指派字串作為它的索引鍵,然後透過字串存取資源。 當您在主題層級定義資源時,必須使用 ComponentResourceKey 做為索引鍵。 下列範例會在 generic.xaml 中定義資源。

<LinearGradientBrush 
     x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:Painter}, 
                                  ResourceId=MyEllipseBrush}"  
                                  StartPoint="0,0" EndPoint="1,0">
    <GradientStop Color="Blue" Offset="0" />
    <GradientStop Color="Red" Offset="0.5" />
    <GradientStop Color="Green" Offset="1"/>
</LinearGradientBrush>

下列範例會藉由指定 ComponentResourceKey 作為索引鍵來參考資源。

<RepeatButton 
    Grid.Column="1" Grid.Row="0"
    Background="{StaticResource {ComponentResourceKey 
                        TypeInTargetAssembly={x:Type local:NumericUpDown}, 
                        ResourceId=ButtonBrush}}">
    Up
</RepeatButton>
<RepeatButton 
    Grid.Column="1" Grid.Row="1"
    Background="{StaticResource {ComponentResourceKey 
                    TypeInTargetAssembly={x:Type local:NumericUpDown}, 
                    ResourceId=ButtonBrush}}">
    Down
 </RepeatButton>
指定佈景主題資源的位置

若要尋找控制項的資源,裝載的應用程式必須知道組件是否包含控制項特定的資源。 您可以將 新增 ThemeInfoAttribute 至包含 控制項的元件,來完成此作業。 ThemeInfoAttribute具有 GenericDictionaryLocation 屬性,指定泛型資源的位置,以及 ThemeDictionaryLocation 指定主題特定資源位置的屬性。

下列範例會將 GenericDictionaryLocationThemeDictionaryLocation 屬性設定為 SourceAssembly ,以指定泛型和主題特定資源位於與 控制項相同的元件中。

[assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly,
           ResourceDictionaryLocation.SourceAssembly)]
<Assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly, ResourceDictionaryLocation.SourceAssembly)>

另請參閱