共用方式為


控制項撰寫概觀

因為 Windows Presentation Foundation (WPF) 控制項模型具有擴充性,所以大幅減少了建立新控制項的需求。 不過,在某些情況下,您可能還是需要建立自訂控制項。 本主題將討論可讓您將建立自訂控制項之需求降到最低的一些功能,以及 Windows Presentation Foundation (WPF) 中的不同控制項撰寫模型。 本主題也將示範如何建立新的控制項。

撰寫新控制項的替代方案

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

  • 豐富內容。 許多標準的 WPF 控制項支援的內容都十分豐富。 例如,Button 的內容屬性的類型為 Object,因此理論上,任何項目都可以顯示在 Button 上。 若要讓按鈕顯示影像和文字,您可以將影像和 TextBlock 新增至 StackPanel,並將 StackPanel 指派給 Content 屬性。 因為控制項可以顯示 WPF 視覺化元素和任意資料,所以就比較不需要建立新控制項或修改現有控制項來支援複雜的視覺效果。 如需 Button 內容模型和 WPF 中其他內容模型的詳細資訊,請參閱 WPF 內容模型

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

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

  • 控制項範本。 WPF 中的許多控制項使用 ControlTemplate 來定義控制項的結構和外觀,這樣會將控制項外觀和控制項功能分隔開來。 藉由重新定義控制項的 ControlTemplate,即可以大幅變更控制項的外觀。 例如,假設您希望控制項看起來像號誌燈。 這個控制項具有簡單的使用者介面和功能。 控制項是三個圓圈,每次只會亮其中一個。 在一番對照之後,您會發現 RadioButton 提供的功能每次只能選取一個選項按鈕,但 RadioButton 的預設外觀看起來就像號誌燈的亮燈。 因為 RadioButton 使用控制項範本定義其外觀,所以很容易重新定義 ControlTemplate 以滿足控制項需求,並使用選項按鈕製作號誌燈。

    注意

    雖然 RadioButton 可以使用 DataTemplate,但在此範例中,只有 DataTemplate 是不夠的。 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 類別的控制項時,請藉由使用範本來定義其外觀。 這樣做您便可以將作業邏輯與視覺表示方式分開處理。 另一種確保 UI 和邏輯會分開處理的方式是,使用命令和繫結而不是事件,並且盡量避免參考 ControlTemplate 中的項目。 若能夠將 UI 和邏輯適當分開處理,則控制項的使用者只要重新定義控制項的 ControlTemplate,就能自訂控制項的外觀。 雖然建置自訂 Control 並不像建置 UserControl 那麼簡單,但自訂 Control 可提供最大的彈性。

從 Control 衍生的優點

如果符合下列任一項條件,請考慮 Control 的衍生項目,而不是使用 UserControl 類別:

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

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

衍生自 FrameworkElement

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

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

從 FrameworkElement 衍生的優點

如果符合下列任意條件,請考慮 FrameworkElement 的衍生項目:

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

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

  • 您希望以新穎的方式撰寫現有的元素,以超越 UserControlControl 可能實現的功能。

控制項撰寫基本概念

如稍早所討論,WPF 其中一個最強大的功能,在於除了設定控制項的基本屬性之外,尚有能力變更其外觀和行為,並且不需要建立自訂控制項。 樣式、資料繫結和觸發程序功能是透過 WPF 屬性系統和 WPF 事件系統來達成的。 下列各節說明您應該遵循的一些做法,而不論您是使用哪種模型來建立自訂控制項,讓自訂控制項的使用者能夠使用這些功能,就如同使用 WPF 隨附的控制項一樣。

使用相依性屬性

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

  • 設定樣式中的屬性。

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

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

  • 顯示屬性的動畫。

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

  • 將名為 ValuePropertyDependencyProperty 識別碼定義為 public static readonly 欄位。

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

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

  • 定義名為 CoerceValueCoerceValueCallback 方法。 CoerceValue 可以確保 Value 大於或等於 MinValue,且小於或等於 MaxValue

  • 定義名為 OnValueChangedPropertyChangedCallback 方法。 OnValueChanged 會建立 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 顯示屬性的動畫非常有用。 如需詳細資訊,請參閱 動畫概觀

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

  • 將名為 ValueChangedEventRoutedEvent 識別碼定義為 public static readonly 欄位。

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

    • 事件名稱是 ValueChanged

    • 路由策略是 Bubble,這代表會先呼叫來源 (引發事件的物件) 上的事件處理常式,然後再接續呼叫來源父項目上的事件處理常式,從最接近的父項目上的事件處理常式開始。

    • 事件處理程式的類型是 RoutedPropertyChangedEventHandler<T>,以 Decimal 類型建構。

    • 事件的擁有者類型是 NumericUpDown

  • 宣告名為 ValueChanged 的公用事件,並包含事件存取子宣告。 此範例會在 add 存取子宣告中呼叫 AddHandler,並在 remove 存取子宣告中呼叫 RemoveHandler,以使用 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 中的元素且 ControlTemplate 受到變更時,參考的元素必須包含在新的 ControlTemplate 中,所以最好避免參考 ControlTemplate 中的元素。

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

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

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

設計工具的設計

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

相依性屬性

請務必依照稍早「使用相依性屬性」中敘述的內容來實作 CLR getset 存取子。設計工具可以使用包裝函式來偵測相依性屬性的存在,但就跟 WPF 和控制項用戶端一樣,設計工具在取得或設定屬性時不一定要呼叫存取子。

附加屬性

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

  • 具有使用 RegisterAttached 方法建立之 PropertyNameProperty 表單 public static readonly DependencyProperty。 傳遞至 RegisterAttached 的屬性名稱必須與 PropertyName 相符。

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

  • Set PropertyNameGetPropertyName 必須分別直接路由至目標相依性物件上的 GetValueSetValue 方法。 藉由呼叫方法包裝函式,或直接呼叫目標相依性物件,設計工具可以存取附加屬性。

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

定義和使用共用資源

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

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

  1. 項目層級。

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

  2. 應用程式層級。

    Application 物件所定義的資源。

  3. 佈景主題層級。

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

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

在項目層級定義資源

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

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

某個佈景主題專屬的資源會存放在具有特定檔名的資源字典中。 這些檔案必須位於名為 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)>

另請參閱