共用方式為


自訂附加屬性

附加屬性是一個 XAML 觀念。 附加屬性通常定義為相依性屬性的專用形式。 本主題說明如何將附加屬性實作為相依性屬性,以及如何定義附加屬性在 XAML 中可用的存取子慣例。

必要條件

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

附加屬性的案例

非定義類別的類別需要有可用的屬性設定機制時,您可以建立附加屬性。 最常見的案例是配置和服務支援。 現有配置屬性的範例包括 Canvas.ZIndexCanvas.Top。 在配置案例中,做為配置控制元素的下層元素存在的元素,可以單獨向其上層元素表達配置要求,每個元素設定上層元素定義為附加屬性的屬性值。 Windows 執行階段 API 中的服務支援案例的範例是 ScrollViewer 的附加屬性集,例如 ScrollViewer.IsZoomChainingEnabled

警告

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

註冊自訂附加屬性

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

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

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

重要

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

存取子

GetPropertyName 存取子的簽章必須是這樣的。

public staticvalueTypeGetPropertyName(DependencyObject target)

針對 Microsoft Visual Basic,則是這樣。

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

目標物件可以是實作中更具體的類型,但必須從 DependencyObject 衍生。 valueType 傳回值也可以是您的實作中更具體的類型。 基本物件類型是可接受的,但您通常會想要附加屬性強制執行類型安全性。 在 getter 和 setter 簽章中使用輸入是建議的類型安全性技術。

GetPropertyName 存取子的簽章必須是這樣的。

public static void SetPropertyName(DependencyObject target ,valueType value)

針對 Visual Basic,則是這樣。

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

目標物件可以是實作中更具體的類型,但必須從 DependencyObject 衍生。 value 物件及其 valueType 可以是實作中更特定的類型。 請記住,此方法的值是 XAML 處理器在標記中遇到附加屬性時來自 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 頁面的根元素中。 例如,對於命名空間 UserAndCustomControls 中名為 GameService 的類別 (包含前面程式碼片段中顯示的附加屬性定義),對應可能如下所示。

<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 屬性使用方式,而且使用字串做為值類型可讓屬性保持輕量。 具有到字串方法的本機轉換的其他基本類型 (例如整數、雙精確度或列舉值) 也通常用做附加屬性的值類型。 您可以使用其他值類型 (不支援本機字串轉換的值類型) 做為附加屬性值。 不過,這需要選擇使用方式或實作:

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

深入瞭解 Canvas.Left 範例

在前面的附加屬性用法範例中,我們示範了設定 Canvas.Left 附加屬性的不同方法。 但是,這會對 Canvas 與物件的互動方式產生什麼變化,什麼時候會發生呢? 我們會進一步檢查此特定範例,因為如果您實作附加屬性,查看一般附加屬性擁有者類別在其他物件上發現附加屬性值時,想要瞭解其他附加屬性擁有者類別想要執行哪些動作。

Canvas 的主要功能是做為 UI 中的絕對定位配置容器。 Canvas 的下層儲存在基底類別定義的屬性 Children 中。 在所有面板中 ,Canvas 是唯一使用絕對定位的面板。 新增可能僅與 Canvas 以及它們是 UIElement 子元素的特定 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 自訂面板概觀