共用方式為


自訂附加屬性

附加屬性是 XAML 概念。 附加屬性通常定義為相依性屬性的特殊形式。 本主題說明如何將附加屬性實作為相依性屬性,以及如何定義附加屬性在 XAML 中使用所需的存取子慣例。

先決條件

我們假設您從現有相依性屬性取用者的觀點來瞭解相依性屬性,而且您已閱讀相 依性屬性概觀。 您也應該已閱讀 附加屬性概觀。 若要遵循本主題中的範例,您也應該瞭解 XAML,並知道如何使用 C++、C# 或 Visual Basic 撰寫基本的 Windows 執行階段應用程式。

附加屬性的方法情境

當有理由將屬性設定機制用於定義類別以外的類別時,您可以建立附加屬性。 最常見的情境是版面配置和服務支援。 現有版面配置屬性的範例包括 Canvas.ZIndexCanvas.Top。 在版面配置案例中,作為版面配置控制元素的子元素存在的元素可以個別向其父元素表達版面配置需求,每個元素都會設定父元素定義為附加屬性的屬性值。 Windows 執行階段 API 中服務支援案例的範例是 ScrollViewer 附加屬性的設定,例如 ScrollViewer.IsZoomChainingEnabled

警告

Windows 執行階段 XAML 實作的現有限制是您無法以動畫方式設定自訂附加屬性。

註冊自訂附加屬性

如果您嚴格定義附加屬性以用於其他類型,則註冊屬性的類別不必衍生自 DependencyObject。 但是,如果您遵循將附加屬性也設為相依性屬性的一般模型,則需要讓存取子的目標參數使用 DependencyObject,以便您可以使用後援屬性存放區。

將附加屬性定義為相依性屬性,方法是宣告公開靜態唯讀DependencyProperty類型屬性。 您可以使用 RegisterAttached 方法的傳回值來定義此屬性。 屬性名稱必須符合您指定為 RegisterAttached名稱 參數的附加屬性名稱,並將字串 “Property” 新增至結尾。 這是根據相依性屬性所代表的屬性命名相依性屬性識別碼的既定慣例。

定義自訂附加屬性與自訂相依性屬性的主要不同之處在於您定義存取子或包裝函式的方式。 您也必須提供靜態 GetPropertyNameSetPropertyName 方法作為附加屬性的存取子,而不是使用自訂相依性屬性中所述的包裝函式技術。 存取子主要由 XAML 剖析器使用,不過其他任何呼叫者也可以在非 XAML 情境中使用它們來設定值。

這很重要

如果您未正確定義存取子,XAML 處理器將無法存取您的附加屬性,任何嘗試使用該屬性的人可能會收到 XAML 解析器錯誤。 此外,設計和編碼工具在參考元件中遇到自訂相依性屬性時,通常會依賴 「*Property」 慣例來命名識別碼。

Accessors

GetPropertyName 存取子方法的簽章應為 this。

public static valueTypeGetPropertyName(DependencyObject target)

對於 Microsoft Visual Basic,就是這個。

Public Shared Function Get 屬性名稱(ByVal target As DependencyObject) As 值類型)

目標物件可以在實作中屬於更特定的類型,但必須衍生自 DependencyObjectValueType 傳回值也可以是實作中更具體的類型。 基本 Object 類型是可以接受的,但您通常會想要附加屬性來強制執行類型安全。 在 getter 和 setter 簽章中使用類型定義是推薦的類型安全性技術。

SetPropertyName 存取子的簽章必須是 this。

public static void Set 屬性名稱(DependencyObject target ,值類型 value)

對於 Visual Basic,就是這個。

Public Shared Sub Set 屬性名稱(ByVal target As DependencyObject, ByVal value As值類型)

目標物件可以在實作中屬於更特定的類型,但必須衍生自 DependencyObjectvalue 物件及其 valueType 在實作中可以是更具體的類型。 請記住,這個方法的值是 XAML 處理器在標記中遇到附加屬性時所產生的輸入。 您使用的類型必須有類型轉換或現有的標記延伸支援,以便可以從屬性值 (最終只是字串) 建立適當的類型。 基本 物件 類型是可以接受的,但您通常會想要進一步的類型安全性。 若要達成此目的,請在存取方法中實施類型檢查。

備註

您也可以定義附加屬性,其中預期的用途是透過屬性元素語法。 在此情況下,您不需要值的類型轉換,但您必須確保可以在 XAML 中建構您想要的值。 VisualStateManager.VisualStateGroups 是現有附加屬性的範例,僅支援屬性元素使用方式。

程式碼範例

此範例展示如何註冊自訂的附加屬性,使用 RegisterAttached 方法,並且說明 GetSet 方法的存取子。 在此範例中,附加屬性名稱為 IsMovable。 因此,存取子必須命名為 GetIsMovableSetIsMovable。 附加屬性的擁有者是名為 GameService 的服務類別,沒有自己的 UI;其用途只是在使用 GameService.IsMovable 附加屬性時提供附加屬性服務。

在 C++/CX 中定義附加屬性會稍微複雜一些。 您必須決定如何在標頭檔案和程式碼檔案之間進行劃分。 此外,您應該將識別碼公開為僅具有 get 存取子的屬性,詳情如在 自訂相依性屬性 中所討論。 在 C++/CX 中,您必須明確定義此屬性和欄位的關係,而不是依賴 .NET Readonly 關鍵字及簡單屬性自動隱含支援。 您也必須在協助程式函式內執行附加屬性的註冊,該函式只會在應用程式第一次啟動時執行一次,但在載入任何需要附加屬性的 XAML 頁面之前。 在 app.xaml 檔案的程式碼中呼叫任何相依或附加屬性的屬性註冊輔助函式的一般位置是 App / Application 建構函式。

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 標記中設定自訂附加屬性

定義附加屬性並將其支援成員包含在自訂類型中之後,您必須讓定義可供 XAML 使用。 若要達成此目標,您必須映射一個 XAML 命名空間,以參考具有相關類別的程式碼命名空間。 如果您已將附加屬性定義為程式庫的一部分,則必須將該程式庫包含在應用程式的應用程式套件中。

XAML 的 XML 命名空間對應通常會放在 XAML 頁面的根元素中。 例如,對於命名空間GameService中命名UserAndCustomControls的類別,其中包含上述程式碼片段中顯示的附加屬性定義,對應可能如下所示。

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

使用對應,您可以在符合目標定義的任何元素上設定 GameService.IsMovable 附加屬性,包括 Windows 執行階段定義的現有類型。

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

如果您要在同樣對應的 XML 命名空間內的元素上設定屬性,您仍然必須在附加屬性名稱上包含前置詞。 這是因為前置詞限定了擁有者類型。 附加屬性的屬性不能假設與包含屬性的元素位於相同的 XML 命名空間內,即使根據一般 XML 規則,屬性可以從元素繼承命名空間。 例如,如果您要在自訂類型 ImageWithLabelControl 上設定 GameService.IsMovable(定義未顯示),即便兩者都是在對應至相同前置詞的相同程式碼命名空間中定義的,XAML 結果仍會保持不變。

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

備註

如果您要使用 C++/CX 撰寫 XAML UI,則只要 XAML 頁面使用該類型,就必須包含定義附加屬性之自訂類型的標頭。 每個 XAML 頁面都有相關聯的程式碼後置檔案(.xaml.h 作為標頭)。 您應該在這裡引入(使用 #include)附加屬性擁有者類型定義的標頭。

以命令方式設定自訂附加屬性

您也可以從命令式程式碼存取自訂附加屬性。 下面的程式碼顯示了如何操作。

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

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

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

自訂附加屬性的值類型

用作自訂附加屬性值類型的類型會影響使用方式、定義或使用方式和定義。 附加屬性的值類型會在數個位置宣告:在 GetSet 存取子方法的簽章中,以及作為 RegisterAttached 呼叫的 propertyType 參數。

附加屬性 (自訂或其他) 最常見的值類型是簡單字串。 這是因為附加屬性通常用於 XAML 屬性使用,而使用字串作為值類型可讓屬性保持輕量型。 具有字串方法原生轉換的其他基本類型,例如 integer、double 或列舉值,也常見為附加屬性的值類型。 您可以使用其他值類型 (不支援原生字串轉換的值類型) 作為附加屬性值。 不過,這需要對使用或實作做出選擇:

  • 您可以保留附加屬性的設定,但僅當附加屬性用作屬性元素且其值宣告為物件元素時,才能支援其使用。 在此情況下,屬性類型必須支援 XAML 使用方式作為物件元素。 針對現有的 Windows 執行階段參考類別,請檢查 XAML 語法,以確定類型支援 XAML 物件元素使用方式。
  • 您可以保留附加屬性,但只能透過 XAML 參考技術在屬性使用方式中使用它,例如可表示為字串的 BindingStaticResource

有關 Canvas.Left 示例的更多信息

在附加屬性使用方式的早期範例中,我們示範了設定 Canvas.Left 附加屬性的不同方式。 但是,這如何改變 Canvas 與您的物件的交互方式,以及這種情況何時發生呢? 我們將進一步檢查這個特定範例,因為實作附加屬性後,觀察一般附加屬性擁有者類別在其他物件上找到附加屬性值後會對這些值執行哪些其他動作,這會非常有趣。

Canvas 的主要功能是成為 UI 中絕對定位的佈局容器。 Canvas 的子系會儲存在基底類別定義的屬性 Children 中。 在所有面板中, Canvas 是唯一使用絕對定位的面板。 它會使通用 UIElement 類型的物件模型膨脹,以新增可能只與 Canvas 相關的屬性,以及那些特定 UIElement 案例,其中它們是 UIElement 的子元素。 將 Canvas 的版面配置控制項屬性定義為任何 UIElement 都可以使用的附加屬性,可讓物件模型保持更乾淨。

為了成為一個實用的面板,Canvas 會重新定義架構層級 MeasureArrange 方法的行為。 這是 Canvas 實際檢查其子系的附加屬性值的地方。 MeasureArrange 模式都包含一個遍歷所有內容的迴圈,而面板有 Children 屬性,明確指示哪些內容應被視為面板的子項。 因此, Canvas 版面配置行為會逐一查看這些子系,並在每個子系上進行靜態 Canvas.GetLeftCanvas.GetTop 呼叫,以查看這些附加屬性是否包含非預設值 (預設值為 0)。 然後,這些值會用來在 Canvas 可用版面配置空間中,依據每個子系提供的特定值,為每個子系進行絕對定位,並使用 Arrange 進行安排。

程式碼看起來像這個偽程式碼。

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 自訂面板概觀