カスタム添付プロパティ

添付プロパティは、XAML の概念です。 添付プロパティは通常、特殊な形式の依存関係プロパティとして定義されます。 このトピックでは、添付プロパティを依存関係プロパティとして実装する方法と、添付プロパティを XAML で使うために必要なアクセサー変換を定義する方法を説明します。

前提条件

依存関係プロパティを既にある依存関係プロパティのユーザーの観点から理解し、「依存関係プロパティの概要」を読んでいることを前提としています。 「添付プロパティの概要」も読む必要があります。 このトピックの例を参考にするには、XAML について理解し、C++、C#、または Visual Basic を使った基本的な Windows ランタイム アプリを作る方法を理解している必要もあります。

添付プロパティのシナリオ

添付プロパティは、定義クラスではないクラスで使用できるプロパティ設定機構を用意する理由がある場合に作成できます。 これの最も一般的なシナリオは、レイアウトとサービスのサポートです。 既存のレイアウト プロパティの例としては、 Canvas.ZIndexCanvas.Top があります。 レイアウト シナリオでは、レイアウト制御要素の子要素として存在する要素が、親要素に対して個別にレイアウト要件を表現できます。親が添付プロパティとして定義するプロパティ値を、個々の子要素が設定します。 Windows ランタイム API のサービスサポート シナリオの例として、ScrollViewer.IsZoomChainingEnabled などの ScrollViewer の添付プロパティのセットがあります。

警告

ただし、Windows ランタイム XAML 実装の制限があるため、カスタム添付プロパティをアニメーション化することはできません。

カスタム添付プロパティの登録

他の型で使用する目的のみで添付プロパティを定義する場合は、プロパティが登録されているクラスを DependencyObject から派生させる必要はありません。 ただし、バッキング プロパティ ストアを使用可能にするため、添付プロパティを依存関係プロパティにもする一般的なモデルに従う場合には、アクセサーのターゲット パラメーターで DependencyObject を使用する必要があります。

DependencyProperty 型のパブリック静的読み取り専用プロパティを宣言して、添付プロパティを依存関係プロパティとして定義します。 このプロパティは、RegisterAttached メソッドの戻り値を使用して定義します。 プロパティ名は、RegisterAttachedパラメーターとして指定した添付プロパティ名と一致し、末尾に文字列 "Property" が追加されている必要があります。 これは、依存関係プロパティが表すプロパティに関連して、依存関係プロパティの識別子に名前を付けるための確立された規則です。

カスタム添付プロパティの定義がカスタム依存関係プロパティと異なるメイン領域は、アクセサーまたはラッパーを定義する方法です。 カスタム依存関係プロパティで説明されているラッパー手法を使用する代わりに、添付プロパティのアクセサーとして静的な GetPropertyName メソッドと SetPropertyName メソッドも指定する必要があります。 アクセサーは主に XAML パーサーによって使用されますが、他の呼び出し元は、それらを使用して XAML 以外のシナリオで値を設定することもできます。

重要

アクセサーを正しく定義しないと、XAML プロセッサは添付プロパティにアクセスできず、それを使おうとするユーザーにはおそらく XAML パーサー エラーが返されます。 また、デザイン ツールとコーディング ツールは、参照されるアセンブリでカスタム依存関係プロパティを検出した場合に、"*Property" という識別子の命名規則に依存することがよくあります。

アクセサー

GetPropertyName アクセサーのシグネチャはこの形式にする必要があります。

public staticvalueTypeGetPropertyName(DependencyObject target)

Microsoft Visual Basic の場合は、これです。

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

ターゲット オブジェクトは実装内でより具体的な型にすることができますが、DependencyObject から派生する必要があります。 valueType の戻り値は、実装でより具体的な型にすることもできます。 基本的な オブジェクト 型は許容されますが、多くの場合、添付プロパティでタイプ セーフを適用する必要があります。 getter と setter シグネチャでの入力の使用は、推奨されるタイプ セーフ手法です。

SetPropertyName アクセサーのシグネチャはこの形式にする必要があります。

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 プロセッサから生じる入力であることに注意してください。 適切な型を属性値 (最終的には単なる文字列) から作成できるように、使用する型の型変換または既存のマークアップ拡張サポートが必要です。 基本的な オブジェクト 型は許容されますが、多くの場合、さらにタイプ セーフが必要になります。 これを実現するには、アクセサーに型の強制を配置します。

Note

プロパティ要素構文で使うことを意図した添付プロパティを定義することもできます。 その場合、値型変換は必要ありませんが、目的の値を XAML で構築できることを確認する必要があります。 VisualStateManager.VisualStateGroups は、プロパティ要素の使用のみをサポートする既存の添付プロパティの例です。

コードの例

この例では、カスタム添付プロパティの、依存関係プロパティの登録 (RegisterAttached メソッドを使用) および Get アクセサーと Set アクセサーを示します。 この例では、添付プロパティ名は IsMovable です。 したがって、アクセサーの名前は GetIsMovable および SetIsMovable である必要があります。 添付プロパティの所有者は、独自の UI を持たない GameService という名前のサービス クラスです。その目的は、GameService.IsMovable 添付プロパティを使用する場合にのみ添付プロパティ サービスを提供することです。

添付プロパティを C++/CX で定義する場合はもう少し複雑です。 ヘッダー ファイルとコード ファイルの間で因数分解する方法を決定する必要があります。 また、「カスタム依存関係プロパティ」で説明されている理由により、get アクセサーのみを持つプロパティとして識別子を公開する必要があります。 C++/CX では、.NET readonly キーワードと単純なプロパティの暗黙的なバッキングに依存する代わりに、このプロパティとフィールドの関係を明示的に定義する必要があります。 また、アプリの初回起動時、添付プロパティを必要とする XAML ページが読み込まれる前に、1 回だけ実行されるヘルパー関数内で添付プロパティの登録を実行する必要があります。 すべての依存関係または添付プロパティに対してプロパティ登録ヘルパー関数を呼び出す一般的な場所は、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 ページのルート要素に配置されます。 たとえば、前のスニペットに示した添付プロパティ定義を含む名前空間 UserAndCustomControlsGameService という名前が付けられたクラスの場合、マッピングは次のようになります。

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

マッピングを使用すると、ターゲット定義に一致する要素 (Windows ランタイム定義されている既存の型を含む) に GameService.IsMovable 添付プロパティを設定できます。

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

同じくマップされた XML 名前空間内にある要素にプロパティを設定する場合でも、添付プロパティ名にプレフィックスを含める必要があります。 これは、プレフィックスが所有者の型を修飾するためです。 添付プロパティの属性は、属性が含まれる要素と同じ XML 名前空間内にあると見なすことはできません。ただし、通常の XML 規則では、属性は要素から名前空間を継承できます。 たとえば、カスタム型 ImageWithLabelControl (定義は表示されません) で GameService.IsMovable を設定していて、両方が同じプレフィックスにマップされた同じコード名前空間で定義されている場合でも、XAML は引き続きこの値になります。

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

Note

XAML UI を C++/CX で作る場合は、XAML ページが添付プロパティを定義するカスタム型を使うたびに、そのカスタム型のヘッダーを含める必要があります。 各 XAML ページには、コード ビハインド ヘッダー (.xaml.h) が関連付けられています。 ここに、添付プロパティの所有者型の定義のヘッダーを (#include を使って) 含める必要があります。

カスタム添付プロパティを強制的に設定する

命令型コードからカスタム添付プロパティにアクセスすることもできます。 次のコードは、その方法を示しています。

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

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

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

カスタム添付プロパティの値型

カスタム添付プロパティの値型として使用される型は、使用法、定義、または使用法と定義の両方に影響します。 添付プロパティの値型は、Get アクセサー メソッドと Set アクセサー メソッドの両方のシグネチャ、および RegisterAttached 呼び出しの propertyType パラメーターとして、いくつかの場所で宣言されています。

添付プロパティ (カスタムなど) の最も一般的な値型は、単純な文字列です。 これは、添付プロパティは一般に XAML 属性の使用を目的としており、値型として文字列を使用するとプロパティの軽量性が維持されるためです。 整数、double、列挙値など、文字列メソッドへのネイティブ変換を持つ他のプリミティブも、添付プロパティの値型として一般的です。 添付プロパティ値として、他の値型 (ネイティブ文字列変換をサポートしていない型) を使用できます。 ただし、これには、使用法または実装のいずれかを選択する必要があります。

  • 添付プロパティはそのままにできますが、添付プロパティは、添付プロパティがプロパティ要素であり、値がオブジェクト要素として宣言されている場合にのみ使用をサポートできます。 この場合、プロパティ型は、オブジェクト要素としての XAML の使用をサポートする必要があります。 既存のWindows ランタイム リファレンス クラスの場合は、XAML 構文をチェックして、型が XAML オブジェクト要素の使用をサポートしていることを確認します。
  • 添付プロパティはそのままにできますが、文字列として表現できる BindingStaticResource などの XAML 参照手法を使用した属性の使用でのみ使用できます。

Canvas.Left の例の詳細

添付プロパティの使用法の前の例では、Canvas.Left 添付プロパティを設定するさまざまな方法を示しました。 しかし、キャンバスがオブジェクトと対話する方法にどのような変更が行われるのでしょうか? また、いつ変更されるのでしょうか? この特定の例をさらに見ていきます。添付プロパティを実装する際、他のオブジェクトで添付プロパティ値が見つかった場合に、一般的な添付プロパティ所有者クラスが添付プロパティ値を使用して何を行おうとするかを確認するのは興味深いためです。

キャンバス のメイン機能は、UI の絶対配置レイアウト コンテナーです。 キャンバス の子は、基本クラスで定義されたプロパティ Children に格納されます。 すべてのパネルのうち、キャンバス は絶対配置を使用する唯一のパネルです。 キャンバスUIElement の子要素である特定の UIElement ケースにのみ関係する可能性があるプロパティを追加すると、共通の UIElement 型のオブジェクト モデルが肥大化する可能性がありました。 任意の UIElement で使用できる添付プロパティとしてキャンバスのレイアウト コントロール プロパティを定義すると、オブジェクト モデルがより綺麗に保持されます。

実用的なパネルにするために、キャンバス にはフレームワーク レベルの Measure メソッドと Arrange メソッドをオーバーライドするビヘイビアーがあります。 これは、キャンバス が子に添付プロパティ値を実際にチェックする場所です。 Measure パターンと Arrange パターンの両方の一部は、任意のコンテンツを反復処理するループであり、パネルには、パネルの子と見なされる内容を明示的にする Children プロパティがあります。 したがって、キャンバス レイアウト ビヘイビアーはこれらの子を反復処理し、各子に対して Canvas.GetLeftCanvas.GetTop の静的呼び出しを行って、それらの添付プロパティに既定値以外の値が含まれているかどうかを確認します (既定値は 0)。 これらの値を使用して、各子が提供する特定の値に従ってキャンバスの使用可能なレイアウト空間に各子を絶対配置し、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
}

Note

パネルの動作方法について詳しくは、「XAML カスタム パネルの概要」をご覧ください。