自訂附加屬性Custom attached properties

「附加屬性」** 是一種 XAML 概念。An attached property is a XAML concept. 附加屬性通常會定義為特殊格式的相依性屬性。Attached properties are typically defined as a specialized form of dependency property. 這個主題說明如何將附加屬性當作相依性屬性來實作,以及如何定義讓附加屬性可以在 XAML 中使用所需的存取子慣例。This topic explains how to implement an attached property as a dependency property and how to define the accessor convention that is necessary for your attached property to be usable in XAML.

先決條件Prerequisites

我們假設您了解現有相依性屬性使用者對相依性屬性的觀點,而且也已經閱讀了相依性屬性概觀We assume that you understand dependency properties from the perspective of a consumer of existing dependency properties, and that you have read the Dependency properties overview. 您必須也要閱讀附加屬性概觀You should also have read Attached properties overview. 為了遵循這個主題中的範例,您也必須了解 XAML 並知道如何使用 C++、C# 或 Visual Basic 撰寫基本的 Windows 執行階段應用程式。To follow the examples in this topic, you should also understand XAML and know how to write a basic Windows Runtime app using C++, C#, or Visual Basic.

附加屬性的案例Scenarios for attached properties

當定義類別以外的類別需要屬性設定機制的時候,就可以建立附加屬性。You might create an attached property when there is a reason to have a property-setting mechanism available for classes other than the defining class. 最常見的案例是配置和服務支援。The most common scenarios for this are layout and services support. 現有配置屬性的範例包括 Canvas.ZIndexCanvas.TopExamples of existing layout properties are Canvas.ZIndex and Canvas.Top. 在配置案例中,做為配置控制元素之子元素的元素,可以個別對它們的父元素表達配置需求,每個都會將父元素定義的屬性值設定為附加屬性。In a layout scenario, elements that exist as child elements to layout-controlling elements can express layout requirements to their parent elements individually, each setting a property value that the parent defines as an attached property. Windows 執行階段 API 中服務支援案例的範例是一組 ScrollViewer 的附加屬性,如 ScrollViewer.IsZoomChainingEnabledAn example of the services-support scenario in the Windows Runtime API is set of the attached properties of ScrollViewer, such as ScrollViewer.IsZoomChainingEnabled.

警告

Windows 執行階段 XAML 執行的現有限制是,您無法建立自訂附加屬性的動畫。An existing limitation of the Windows Runtime XAML implementation is that you cannot animate your custom attached property.

登錄自訂附加屬性Registering a custom attached property

如果定義的附加屬性純粹只是在其他類型上使用,登錄屬性的類別不一定要衍生自 DependencyObjectIf you are defining the attached property strictly for use on other types, the class where the property is registered does not have to derive from DependencyObject. 不過,如果您遵循讓附加屬性也是相依性屬的典型模式,則存取子的 target 參數需要使用 DependencyObject,如此一來,您就可以使用支援屬性儲存區。But you do need to have the target parameter for accessors use DependencyObject if you follow the typical model of having your attached property also be a dependency property, so that you can use the backing property store.

宣告類型 DependencyPropertypublic static readonly 屬性,即可將附加屬性定義為相依性屬性。Define your attached property as a dependency property by declaring a public static readonly property of type DependencyProperty. 使用 RegisterAttached 方法的傳回值來定義這個屬性。You define this property by using the return value of the RegisterAttached method. 屬性名稱必須符合您指定為 RegisterAttached name 參數的附加屬性名稱,最後要加上字串 "Property"。The property name must match the attached property name you specify as the RegisterAttached name parameter, with the string "Property" added to the end. 這是根據代表的屬性命名相依性屬性識別碼的既定慣例。This is the established convention for naming the identifiers of dependency properties in relation to the properties that they represent.

定義自訂附加屬性與自訂相依性屬性的主要差異在於定義存取子或包裝函式的方式。The main area where defining a custom attached property differs from a custom dependency property is in how you define the accessors or wrappers. 您不是使用自訂相依性屬性中所述的包裝函式技術,而是必須也提供靜態 GetPropertyNameSetPropertyName 方法做為附加屬性的存取子。Instead of the using the wrapper technique described in Custom dependency properties, you must also provide static GetPropertyName and SetPropertyName methods as accessors for the attached property. 存取子主要是由 XAML 剖析器使用,不過任何其他呼叫者也可以使用它們在非 XAML 案例中設定值。The accessors are used mostly by the XAML parser, although any other caller can also use them to set values in non-XAML scenarios.

重要

如果您未正確定義存取子,XAML 處理器無法存取您附加的屬性,而且嘗試使用它的任何人可能會收到 XAML 剖析器錯誤。If you don't define the accessors correctly, the XAML processor can't access your attached property and anyone who tries to use it will probably get a XAML parser error. 此外,當識別碼在參考元件中遇到自訂相依性屬性時,設計和程式碼撰寫工具通常會依賴「 * 屬性」慣例來命名識別碼。Also, design and coding tools often rely on the "*Property" conventions for naming identifiers when they encounter a custom dependency property in a referenced assembly.

存取子Accessors

GetPropertyName 存取子的簽章必須是這個。The signature for the GetPropertyName accessor must be this.

public static valueType GetPropertyName (DependencyObject target)public static valueType GetPropertyName (DependencyObject target)

針對 Microsoft Visual Basic,它是這個。For Microsoft Visual Basic, it is this.

Public Shared Function GetPropertyName (ByVal target As DependencyObject) As valueType)Public Shared Function GetPropertyName(ByVal target As DependencyObject) As valueType)

target 物件可以是實作中更具體的類型,但是必須衍生自 DependencyObjectThe target object can be of a more specific type in your implementation, but must derive from DependencyObject. valueType 傳回值也可以是實作中的更具體類型。The valueType return value can also be of a more specific type in your implementation. 基本 Object 類型是可以被接受的,不過通常您會希望您的附加屬性強制執行類型安全技術。The basic Object type is acceptable, but often you'll want your attached property to enforce type safety. 建議的類型安全技術是在 getter 和 setter 簽章中使用類型。The use of typing in the getter and setter signatures is a recommended type-safety technique.

SetPropertyName 存取子的簽章必須是這個。The signature for the SetPropertyName accessor must be this.

public static void SetPropertyName (DependencyObject target ,valueType value)public static void SetPropertyName(DependencyObject target ,valueType value)

針對 Visual Basic,它是這個。For Visual Basic, it is this.

Public Shared Sub SetPropertyName (ByVal target As DependencyObject, ByVal value AsvalueType)Public Shared Sub SetPropertyName(ByVal target As DependencyObject, ByVal value AsvalueType)

target 物件可以是實作中更具體的類型,但是必須衍生自 DependencyObjectThe target object can be of a more specific type in your implementation, but must derive from DependencyObject. value 物件及它的 valueType 可以是實作中的更具體類型。The value object and its valueType can be of a more specific type in your implementation. 請記住,這個方法的值是 XAML 處理器在標記中遇到附加屬性時,來自於 XAML 處理器的輸入。Remember that the value for this method is the input that comes from the XAML processor when it encounters your attached property in markup. 您使用的類型必須取得類型轉換或現有標記延伸的支援,這樣才能從屬性值建立適當的類型 (最終只是字串)。There must be type conversion or existing markup extension support for the type you use, so that the appropriate type can be created from an attribute value (which is ultimately just a string). 基本 Object 類型是可以接受的,但是通常您會想要更進一步的類型安全性。The basic Object type is acceptable, but often you'll want further type safety. 若要這樣做,請將類型強制放到存取子中。To accomplish that, put type enforcement in the accessors.

注意

您也可以定義附加屬性,其中的預期使用方式是透過屬性元素語法。It's also possible to define an attached property where the intended usage is through property element syntax. 在該情況下,您不需要輸入值的轉換,但必須確保您想要的值可以在 XAML 中建構。In that case you don't need type conversion for the values, but you do need to assure that the values you intend can be constructed in XAML. VisualStateManager.VisualStateGroups 是現有附加屬性的一個範例,這個屬性僅支援屬性元素用法。VisualStateManager.VisualStateGroups is an example of an existing attached property that only supports property element usage.

程式碼範例Code example

這個範例示範自訂附加屬性的相依性屬性登錄 (使用 RegisterAttached 方法) 以及 GetSet 存取子。This example shows the dependency property registration (using the RegisterAttached method), as well as the Get and Set accessors, for a custom attached property. 在此範例中,附加屬性名稱為 IsMovableIn the example, the attached property name is IsMovable. 因此,存取子必須命名為 GetIsMovableSetIsMovableTherefore, the accessors must be named GetIsMovable and SetIsMovable. 附加屬性的擁有者是名為 GameService 的服務類別,這個類別沒有自己的 UI;其用途只是在使用 GameService.IsMovable 附加屬性時提供附加屬性服務。The owner of the attached property is a service class named GameService that doesn't have a UI of its own; its purpose is only to provide the attached property services when the GameService.IsMovable attached property is used.

在 c + +/CX 中定義附加屬性有點複雜。Defining the attached property in C++/CX is a bit more complex. 您必須決定如何切分標頭與程式碼檔案。You have to decide how to factor between the header and code file. 另外,您應該將識別碼公開為只包含一個 get 存取子的屬性,原因請參閱自訂相依性屬性中的討論。Also, you should expose the identifier as a property with only a get accessor, for reasons discussed in Custom dependency properties. 在 c + +/CX 中,您必須明確地定義此屬性欄位關聯性,而不是依賴 .NET readonly keywording 和簡單屬性的隱含支援。In C++/CX you must define this property-field relationship explicitly rather than relying on .NET readonly keywording and implicit backing of simple properties. 當應用程式第一次啟動,但在載入任何需要附加屬性的 XAML 頁面之前,您也需要在只執行一次的協助程式函式內執行附加屬性的登錄。You also need to perform the registration of the attached property within a helper function that only gets run once, when the app first starts but before any XAML pages that need the attached property are loaded. 針對任何和所有相依性或附加屬性呼叫屬性註冊協助程式函式的一般位置,是從應用程式的應用 / 程式在應用程式的程式碼中,在您的應用程式 .xaml 檔案內。The typical place to call your property registration helper functions for any and all dependency or attached properties is from within the App / Application constructor in the code for your app.xaml file.

public class GameService : DependencyObject
{
    public static readonly DependencyProperty IsMovableProperty = 
    DependencyProperty.RegisterAttached(
      "IsMovable",
      typeof(Boolean),
      typeof(GameService),
      new PropertyMetadata(false)
    );
    public static void SetIsMovable(UIElement element, Boolean value)
    {
        element.SetValue(IsMovableProperty, value);
    }
    public static Boolean GetIsMovable(UIElement element)
    {
        return (Boolean)element.GetValue(IsMovableProperty);
    }
}
Public Class GameService
    Inherits DependencyObject

    Public Shared ReadOnly IsMovableProperty As DependencyProperty = 
        DependencyProperty.RegisterAttached("IsMovable",  
        GetType(Boolean), 
        GetType(GameService), 
        New PropertyMetadata(False))

    Public Shared Sub SetIsMovable(ByRef element As UIElement, value As Boolean)
        element.SetValue(IsMovableProperty, value)
    End Sub

    Public Shared Function GetIsMovable(ByRef element As UIElement) As Boolean
        GetIsMovable = CBool(element.GetValue(IsMovableProperty))
    End Function
End Class
// GameService.idl
namespace UserAndCustomControls
{
    [default_interface]
    runtimeclass GameService : Windows.UI.Xaml.DependencyObject
    {
        GameService();
        static Windows.UI.Xaml.DependencyProperty IsMovableProperty{ get; };
        static Boolean GetIsMovable(Windows.UI.Xaml.DependencyObject target);
        static void SetIsMovable(Windows.UI.Xaml.DependencyObject target, Boolean value);
    }
}

// GameService.h
...
    static Windows::UI::Xaml::DependencyProperty IsMovableProperty() { return m_IsMovableProperty; }
    static bool GetIsMovable(Windows::UI::Xaml::DependencyObject const& target) { return winrt::unbox_value<bool>(target.GetValue(m_IsMovableProperty)); }
    static void SetIsMovable(Windows::UI::Xaml::DependencyObject const& target, bool value) { target.SetValue(m_IsMovableProperty, winrt::box_value(value)); }

private:
    static Windows::UI::Xaml::DependencyProperty m_IsMovableProperty;
...

// GameService.cpp
...
Windows::UI::Xaml::DependencyProperty GameService::m_IsMovableProperty =
    Windows::UI::Xaml::DependencyProperty::RegisterAttached(
        L"IsMovable",
        winrt::xaml_typename<bool>(),
        winrt::xaml_typename<UserAndCustomControls::GameService>(),
        Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(false) }
);
...
// GameService.h
#pragma once

#include "pch.h"
//namespace WUX = Windows::UI::Xaml;

namespace UserAndCustomControls {
    public ref class GameService sealed : public WUX::DependencyObject {
    private:
        static WUX::DependencyProperty^ _IsMovableProperty;
    public:
        GameService::GameService();
        void GameService::RegisterDependencyProperties();
        static property WUX::DependencyProperty^ IsMovableProperty
        {
            WUX::DependencyProperty^ get() {
                return _IsMovableProperty;
            }
        };
        static bool GameService::GetIsMovable(WUX::UIElement^ element) {
            return (bool)element->GetValue(_IsMovableProperty);
        };
        static void GameService::SetIsMovable(WUX::UIElement^ element, bool value) {
            element->SetValue(_IsMovableProperty,value);
        }
    };
}

// GameService.cpp
#include "pch.h"
#include "GameService.h"

using namespace UserAndCustomControls;

using namespace Platform;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Documents;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Interop;
using namespace Windows::UI::Xaml::Media;

GameService::GameService() {};

GameService::RegisterDependencyProperties() {
    DependencyProperty^ GameService::_IsMovableProperty = DependencyProperty::RegisterAttached(
         "IsMovable", Platform::Boolean::typeid, GameService::typeid, ref new PropertyMetadata(false));
}

從 XAML 標記設定自訂附加屬性Setting your custom attached property from XAML markup

注意

如果您使用 c + +/WinRT,請跳至下一節, (以命令方式使用 c + +/WinRT) 來設定自訂附加屬性If you're using C++/WinRT, then skip to the following section (Setting your custom attached property imperatively with C++/WinRT).

定義您的附加屬性並將它的支援成員包含為自訂類型的一部分之後,您接著必須讓 XAML 可以使用定義。After you have defined your attached property and included its support members as part of a custom type, you must then make the definitions available for XAML usage. 若要這樣做,您必須對應將要參考包含相關類別程式碼命名空間的 XAML 命名空間。To do this, you must map a XAML namespace that will reference the code namespace that contains the relevant class. 在已經將附加屬性定義為程式庫一部分的情況中,您必須包含這個程式庫,讓它成為應用程式之應用程式套件的一部分。In cases where you have defined the attached property as part of a library, you must include that library as part of the app package for the app.

XAML 的 XML 命名空間對應通常會放置在 XAML 頁面的根元素中。An XML namespace mapping for XAML is typically placed in the root element of a XAML page. 例如,針對包含前面程式碼片段中所示之附加屬性定義的命名空間 UserAndCustomControls 中名為 GameService 的類別,其對應看起來就會像這樣。For example, for the class named GameService in the namespace UserAndCustomControls that contains the attached property definitions shown in preceding snippets, the mapping might look like this.

<UserControl
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:uc="using:UserAndCustomControls"
  ... >

使用對應即可在符合您目標定義的任何元素上設定您的 GameService.IsMovable 附加屬性,包括 Windows 執行階段定義的現有類型。Using the mapping, you can set your GameService.IsMovable attached property on any element that matches your target definition, including an existing type that Windows Runtime defines.

<Image uc:GameService.IsMovable="True" .../>

如果您在某個元素上設定屬性且該元素也在同一個對應的 XML 命名空間中,仍然需要在附加屬性名稱上包含前置詞。If you are setting the property on an element that is also within the same mapped XML namespace, you still must include the prefix on the attached property name. 這是因為前置詞會限定擁有者類型。This is because the prefix qualifies the owner type. 您不能假設附加屬性的屬性一定位於與包含屬性的元素相同的 XML 命名空間內;不過,根據一般的 XML 規則,屬性可以從元素繼承命名空間。The attached property's attribute cannot be assumed to be within the same XML namespace as the element where the attribute is included, even though, by normal XML rules, attributes can inherit namespace from elements. 例如,如果您在自訂類型的 ImageWithLabelControl 上設定 GameService.IsMovable (未顯示定義),而且即使這兩者是在對應到相同前置詞的同一個程式碼命名空間中定義的,XAML 仍然會是這個。For example, if you are setting GameService.IsMovable on a custom type of ImageWithLabelControl (definition not shown), and even if both were defined in the same code namespace mapped to same prefix, the XAML would still be this.

<uc:ImageWithLabelControl uc:GameService.IsMovable="True" .../>

注意

如果您要使用 c + +/CX 撰寫 XAML UI,則在 XAML 頁面使用該型別時,您必須包含定義附加屬性之自訂類型的標頭。If you are writing a XAML UI with C++/CX, then you must include the header for the custom type that defines the attached property, any time that a XAML page uses that type. 每個 XAML 頁面都有相關聯的程式碼後端標頭 ( .xaml) 。Each XAML page has an associated code-behind header (.xaml.h). 這是您應該使用** # include**) 附加屬性的擁有者類型定義之標頭中包含 (的位置。This is where you should include (using #include) the header for the definition of the attached property's owner type.

使用 c + +/WinRT 以命令方式設定自訂附加屬性Setting your custom attached property imperatively with C++/WinRT

如果您使用 c + +/WinRT,則可以從命令式程式碼存取自訂附加屬性,而不是從 XAML 標記。If you're using C++/WinRT, then you can access a custom attached property from imperative code, but not from XAML markup. 下列程式碼顯示如何。The code below shows how.

<Image x:Name="gameServiceImage"/>
// MainPage.h
...
#include "GameService.h"
...

// MainPage.cpp
...
MainPage::MainPage()
{
    InitializeComponent();

    GameService::SetIsMovable(gameServiceImage(), true);
}
...

自訂附加屬性的值類型Value type of a custom attached property

做為自訂附加屬性值類型的類型會影響使用方式、定義或同時影響兩者。The type that is used as the value type of a custom attached property affects the usage, the definition, or both the usage and definition. 附加屬性的值類型會在數個位置宣告:在 GetSet 存取子兩個方法的簽章中,以及做為 RegisterAttached 呼叫的 propertyType 參數。The attached property's value type is declared in several places: in the signatures of both the Get and Set accessor methods, and also as the propertyType parameter of the RegisterAttached call.

最常見的附加屬性 (自訂或其他) 值類型是簡單字串。The most common value type for attached properties (custom or otherwise) is a simple string. 這是因為附加屬性通常是用於 XAML 屬性,而將字串當作值類型能讓屬性變得更為精簡。This is because attached properties are generally intended for XAML attribute usage, and using a string as the value type keeps the properties lightweight. 使用原生轉換為字串方法的其他基本類型 (例如整數、雙精度浮點數或列舉值) 也是附加屬性常用的值類型。Other primitives that have native conversion to string methods, such as integer, double, or an enumeration value, are also common as value types for attached properties. 您可以使用其他值類型—不支援原生字串轉換的值類型—當作附加屬性值。You can use other value types—ones that don't support native string conversion—as the attached property value. 不過,這就取決於您要選擇使用或實作它:However, this entails making a choice about either the usage or the implementation:

  • 您可以讓附加屬性保持原狀,但是附加屬性只能支援附加屬性是屬性元素且值被宣告為物件元素的使用方法。You can leave the attached property as it is, but the attached property can support usage only where the attached property is a property element, and the value is declared as an object element. 在這種情況下,屬性類型不一定要支援使用 XAML 作為物件元素。In this case, the property type does have to support XAML usage as an object element. 若為現有的 Windows 執行階段參考類別,請檢查 XAML 語法來確保類型支援 XAML 物件元素使用方法。For existing Windows Runtime reference classes, check the XAML syntax to make sure that the type supports XAML object element usage.
  • 您可以讓附加屬性保持原狀,但是透過 XAML 參考技術 (如 Binding 或可以使用字串表示的 StaticResource) 僅在使用屬性時使用它。You can leave the attached property as it is, but use it only in an attribute usage through a XAML reference technique such as a Binding or StaticResource that can be expressed as a string.

進一步了解 Canvas.Left 範例More about the Canvas.Left example

在前面的附加屬性用法範例中,我們示範了設定 Canvas.Left 附加屬性的不同方法。In earlier examples of attached property usages we showed different ways to set the Canvas.Left attached property. 但是,這對於 Canvas 與您物件的互動方式有何改變,以及在何時發生?But what does that change about how a Canvas interacts with your object, and when does that happen? 我們將進一步探討這個特定的範例,因為如果您實作附加屬性,看看當典型的附加屬性擁有者類別在其他物件上發現它的附加屬性值時,會對這些值做哪些其他處理,將是一件有趣的事。We'll examine this particular example further, because if you implement an attached property, it's interesting to see what else a typical attached property owner class intends to do with its attached property values if it finds them on other objects.

Canvas 的主要功能是成為 UI 中的絕對位置配置容器。The main function of a Canvas is to be an absolute-positioned layout container in UI. Canvas 的子項會儲存在基底類別定義的屬性 Children 中。The children of a Canvas are stored in a base-class defined property Children. 在所有的面板中,Canvas 是唯一使用絕對位置的面板。Of all the panels Canvas is the only one that uses absolute positioning. 當新增屬性而屬性可能只與 Canvas 和它們身為 UIElement 子元素的特定 UIElement 案例相關時,會將常見的 UIElement 類型的物件模型塞得臃腫不堪。It would've bloated the object model of the common UIElement type to add properties that might only be of concern to Canvas and those particular UIElement cases where they are child elements of a UIElement. Canvas 的配置控制項屬性定義成任何 UIElement 都可以使用的附加屬性可以讓物件模型保持簡潔。Defining the layout control properties of a Canvas to be attached properties that any UIElement can use keeps the object model cleaner.

為了能夠成為實際的面板,Canvas 具有會覆寫架構層級的 MeasureArrange 方法的行為。In order to be a practical panel, Canvas has behavior that overrides the framework-level Measure and Arrange methods. 這是 Canvas 實際檢查其子項是否有附加屬性值的位置。This is where Canvas actually checks for attached property values on its children. MeasureArrange 模式都有部分是迴圈,會逐一查看任何內容,而面板具有 Children 屬性,可以明確指出哪個應該視為面板子項。Part of both the Measure and Arrange patterns is a loop that iterates over any content, and a panel has the Children property that makes it explicit what's supposed to be considered the child of a panel. 因此,Canvas 配置行為會逐一查看這些子項,並且在每個子項進行靜態的 Canvas.GetLeftCanvas.GetTop 呼叫,以查看那些附加屬性是否包含非預設值 (預設值為 0)。So the Canvas layout behavior iterates through these children, and makes static Canvas.GetLeft and Canvas.GetTop calls on each child to see whether those attached properties contain a non-default value (default is 0). 然後,會使用這些值並根據每個子項提供的特定值,以賦予每個子項在 Canvas 可用配置空間中的絕對位置,然後使用 Arrange 來進行認可。These values are then used to absolutely position each child in the Canvas available layout space according to the specific values provided by each child, and committed using Arrange.

程式碼看起來像這個虛擬程式碼。The code looks something like this pseudocode.

protected override Size ArrangeOverride(Size finalSize)
{
    foreach (UIElement child in Children)
    {
        double x = (double) Canvas.GetLeft(child);
        double y = (double) Canvas.GetTop(child);
        child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
    }
    return base.ArrangeOverride(finalSize); 
    // real Canvas has more sophisticated sizing
}

注意

如需面板運作方式的詳細資訊,請參閱 XAML 自訂面板總覽For more info on how panels work, see XAML custom panels overview.