カスタム添付プロパティ

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

前提条件

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

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

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

警告

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

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

他の種類で使う添付プロパティを厳密に定義する場合、プロパティが登録されているクラスが DependencyObject から派生する必要はありません。 ただし、添付プロパティが依存関係プロパティでもある標準モデルに従う場合は、バッキング プロパティ ストアを使うことができるように、アクセサーのターゲット パラメーターで DependencyObject を使う必要があります。

DependencyProperty 型の publicstaticreadonly プロパティを宣言することで、添付プロパティを依存関係プロパティとして定義します。 このプロパティは、RegisterAttached メソッドの戻り値を使って定義します。 プロパティ名は、RegisterAttachedname パラメーターとして指定した添付プロパティ名の終わりに "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)

target オブジェクトは実装でより具体的な型にすることができますが、DependencyObject から派生する必要があります。 valueType 戻り値も、実装でより具体的な型にすることができます。 基本的な Object 型が受け入れられますが、多くの場合、添付プロパティにタイプ セーフを適用します。 タイプ セーフ手法として、getter シグネチャと setter シグネチャで型指定を使うことをお勧めします。

SetPropertyName アクセサーのシグネチャは次のようにする必要があります。

public static void SetPropertyName(DependencyObject target ,valueType value)

Visual Basic の場合は、次のようになります。

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

target オブジェクトは実装でより具体的な型にすることができますが、DependencyObject から派生する必要があります。 value オブジェクトとその valueType は、実装でより具体的な型にすることができます。 添付プロパティがマークアップに検出された場合、このメソッドの値は XAML プロセッサからの入力であることに注意してください。 属性値 (最終的には単なる文字列) から適切な型を作成できるように、使う型の型変換または既存のマークアップ拡張サポートが必要です。 基本的な Object 型が受け入れられますが、多くの場合、さらにタイプ セーフにします。 これを実現するには、アクセサーに型を適用します。

注意

プロパティ要素構文で使うことを意図した添付プロパティを定義することもできます。 その場合、値の型変換は必要ではありませんが、意図した値を 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 マークアップからのカスタム添付プロパティの設定

注意

C++/WinRT を使用している場合は、次のセクション「C++/WinRT を使用してカスタム添付プロパティを強制的に設定する」にスキップします。

添付プロパティを定義し、そのサポート メンバーをカスタム型の一部として含めたら、定義を 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 名前空間にあることは想定できません。 たとえば、GameService.IsMovableImageWithLabelControl のカスタム型 (定義は示しません) に設定する場合、その両方が同じプレフィックスにマップされる同じコード名前空間に定義されていても、XAML は依然として次のようになります。

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

注意

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

C++/WinRT を使用してカスタム添付プロパティを強制的に設定する

C++/WinRT を使用している場合は、命令型コードからカスタム添付プロパティにアクセスできますが、XAML マークアップからはできません。 次のコードは、その方法を示しています。

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

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

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

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

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

添付プロパティ (カスタムまたはそれ以外) で最も一般的な値型は、単純な文字列です。 これは、添付プロパティは一般に XAML 属性で使われることが意図されており、文字列を値型として使うとプロパティが軽量になるためです。 整数、倍精度浮動小数点、列挙値など、文字列メソッドへのネイティブ変換を持つその他のプリミティブも、添付プロパティの値型として一般的です。 ネイティブ文字列変換をサポートしない他の値型を添付プロパティ値として使うこともできます。 ただし、その場合は、使用方法または実装について選択が必要です。

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

Canvas.Left の例に関する詳細

添付プロパティの使用法として前に挙げた例では、Canvas.Left 添付プロパティを設定するさまざまな方法を説明しました。 しかし、それによって Canvas がオブジェクトとやり取りする方法やタイミングがどのように変わるのでしょうか。 ここでは、この例をさらに詳しく検討します。添付プロパティを実装しており、他のオブジェクトで添付プロパティの値が検出された場合に、典型的な添付プロパティの所有者クラスが添付プロパティの値に対して実行する処理を理解するのは意味のあることだからです。

Canvas の主な機能は、UI で絶対位置の決まったレイアウト コンテナーとなることです。 Canvas の子は、基底クラスの定義済みプロパティ Children に格納されます。 パネルのうち、Canvas だけが絶対配置を使います。 Canvas や特定の UIElementUIElement の子要素になっている場合にのみ関係するプロパティを追加した場合には、共通の 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 カスタム パネルの概要」をご覧ください。