依存関係プロパティのコールバックと検証

このトピックでは、検証による判定、プロパティの有効値が変更されたときに呼び出されるコールバック、値の決定への外部的影響のオーバーライドなど、プロパティ関連機能の代替カスタム実装を使用して依存関係プロパティを作成する方法について説明します。 また、これらの手法を用いてプロパティ システムの既定の動作を拡張することが適切であるシナリオについても説明します。

このトピックは、次のセクションで構成されています。

  • 必要条件
  • 検証コールバック
  • 強制値コールバックとプロパティ変更イベント
  • 高度な強制とコールバック シナリオ
  • 関連トピック

必要条件

このトピックでは、依存プロパティの実装の基本シナリオとカスタム依存プロパティへのメタデータの適用方法を理解していることを前提とします。 詳細については、「カスタム依存関係プロパティ」および「依存関係プロパティのメタデータ」を参照してください。

検証コールバック

検証コールバックは、最初の登録時に依存関係プロパティに割り当てることができます。 検証コールバックは、プロパティ メタデータの一部ではなく、Register メソッドの直接入力です。 したがって、依存関係プロパティの検証コールバックを作成した後で、その検証コールバックを新しい実装でオーバーライドすることはできません。

Public Shared ReadOnly CurrentReadingProperty As DependencyProperty =
    DependencyProperty.Register("CurrentReading",
        GetType(Double), GetType(Gauge),
        New FrameworkPropertyMetadata(Double.NaN,
            FrameworkPropertyMetadataOptions.AffectsMeasure,
            New PropertyChangedCallback(AddressOf OnCurrentReadingChanged),
            New CoerceValueCallback(AddressOf CoerceCurrentReading)),
        New ValidateValueCallback(AddressOf IsValidReading))

Public Property CurrentReading() As Double
    Get
        Return CDbl(GetValue(CurrentReadingProperty))
    End Get
    Set(ByVal value As Double)
        SetValue(CurrentReadingProperty, value)
    End Set
End Property
public static readonly DependencyProperty CurrentReadingProperty = DependencyProperty.Register(
    "CurrentReading",
    typeof(double),
    typeof(Gauge),
    new FrameworkPropertyMetadata(
        Double.NaN,
        FrameworkPropertyMetadataOptions.AffectsMeasure,
        new PropertyChangedCallback(OnCurrentReadingChanged),
        new CoerceValueCallback(CoerceCurrentReading)
    ),
    new ValidateValueCallback(IsValidReading)
);
public double CurrentReading
{
  get { return (double)GetValue(CurrentReadingProperty); }
  set { SetValue(CurrentReadingProperty, value); }
}

検証コールバックは、オブジェクト値を受け取るように実装されます。 指定された値がプロパティに対して有効である場合は true を、有効でない場合は false を返します。 プロパティの型はプロパティ システムに登録済みの適切な型であると想定されます。したがって、通常の場合、コールバック内での型チェックは行われません。 検証コールバックは、プロパティ システムが実行するさまざまな操作で使用されます。 たとえば、最初に型を既定値で初期化したり、SetValue を呼び出してプログラムで値を変更したり、指定された新しい既定値でメタデータをオーバーライドする場合などに使用されます。 これらの操作で呼び出された検証コールバックが false を返す場合、例外が発生します。 アプリケーションの作成者はこれらの例外を処理する必要があります。 検証コールバックは、列挙値の検証や、プロパティの測定値がゼロ以上に設定されている場合に integer 型または double 型の値を制限するときによく使用されます。

検証コールバックは、インスタンスではなくクラスを検証するためのものです。 検証コールバックのパラメーターは、検証対象のプロパティが設定されている特定の DependencyObject を通知しません。 したがって、検証コールバックを使用して、プロパティ値に影響を及ぼす可能性がある "依存関係" (インスタンス固有のプロパティ値が他のインスタンス固有のプロパティ値や実行時の状態などの要因に依存する関係) を強制することはできません。

非常に簡単な検証コールバック シナリオのコード例を次に示します。この例では、Double プリミティブ型として型指定されているプロパティが PositiveInfinity または NegativeInfinity でないことを検証します。

Public Shared Function IsValidReading(ByVal value As Object) As Boolean
    Dim v As Double = CType(value, Double)
    Return ((Not v.Equals(Double.NegativeInfinity)) AndAlso
            (Not v.Equals(Double.PositiveInfinity)))
End Function
public static bool IsValidReading(object value)
{
    Double v = (Double)value;
    return (!v.Equals(Double.NegativeInfinity) && !v.Equals(Double.PositiveInfinity));
}

強制値コールバックとプロパティ変更イベント

強制値コールバックは、依存関係プロパティの値が変更されるたびにプロパティ システムによって呼び出される PropertyChangedCallback 実装と同様に、プロパティの特定の DependencyObject インスタンスを渡します。 これら 2 つのコールバックを組み合わせて使用すると、あるプロパティが変更されたときに他のプロパティに自動的に強制または再評価が適用される一連のプロパティを要素に作成することができます。

依存関係プロパティ間の関連は、最小値、最大値、および実際の値 (または現在の値) を表す 3 つのプロパティが要素に含まれる場合において、ユーザー インターフェイス ドリブン プロパティがあるときによく使用されます。 ここで、最大値が現在の値より小さい値に調整された場合、現在の値を新しい最大値以下の値に強制する必要があります。現在の値と最小値についても同様の関係が成り立ちます。

次の、3 つの依存関係プロパティの 1 つを示す非常に簡単なコード例は、こうした関係を表しています。 この例は、3 つの関連する Reading プロパティ (Min、Max、Current) のうちの 1 つである CurrentReading プロパティの登録方法を示しています。 ここでは、前のセクションで説明した検証を使用しています。

Public Shared ReadOnly CurrentReadingProperty As DependencyProperty =
    DependencyProperty.Register("CurrentReading",
        GetType(Double), GetType(Gauge),
        New FrameworkPropertyMetadata(Double.NaN,
            FrameworkPropertyMetadataOptions.AffectsMeasure,
            New PropertyChangedCallback(AddressOf OnCurrentReadingChanged),
            New CoerceValueCallback(AddressOf CoerceCurrentReading)),
        New ValidateValueCallback(AddressOf IsValidReading))

Public Property CurrentReading() As Double
    Get
        Return CDbl(GetValue(CurrentReadingProperty))
    End Get
    Set(ByVal value As Double)
        SetValue(CurrentReadingProperty, value)
    End Set
End Property
public static readonly DependencyProperty CurrentReadingProperty = DependencyProperty.Register(
    "CurrentReading",
    typeof(double),
    typeof(Gauge),
    new FrameworkPropertyMetadata(
        Double.NaN,
        FrameworkPropertyMetadataOptions.AffectsMeasure,
        new PropertyChangedCallback(OnCurrentReadingChanged),
        new CoerceValueCallback(CoerceCurrentReading)
    ),
    new ValidateValueCallback(IsValidReading)
);
public double CurrentReading
{
  get { return (double)GetValue(CurrentReadingProperty); }
  set { SetValue(CurrentReadingProperty, value); }
}

Current のプロパティ変更コールバックを使用して、他の依存関係プロパティに対して登録されている強制値コールバックを明示的に呼び出すことにより、変更を他のプロパティに伝播します。

Private Shared Sub OnCurrentReadingChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
    d.CoerceValue(MinReadingProperty)
    d.CoerceValue(MaxReadingProperty)
End Sub
private static void OnCurrentReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  d.CoerceValue(MinReadingProperty);
  d.CoerceValue(MaxReadingProperty);
}

強制値コールバックは、Current プロパティが依存している可能性があるプロパティの値を検証し、必要に応じて現在の値に強制を機能させます。

Private Shared Function CoerceCurrentReading(ByVal d As DependencyObject, ByVal value As Object) As Object
    Dim g As Gauge = CType(d, Gauge)
    Dim current As Double = CDbl(value)
    If current < g.MinReading Then
        current = g.MinReading
    End If
    If current > g.MaxReading Then
        current = g.MaxReading
    End If
    Return current
End Function
private static object CoerceCurrentReading(DependencyObject d, object value)
{
  Gauge g = (Gauge)d;
  double current = (double)value;
  if (current < g.MinReading) current = g.MinReading;
  if (current > g.MaxReading) current = g.MaxReading;
  return current;
}
メモメモ

プロパティの既定値は強制されません。プロパティ値が初期既定値を保持している場合、または ClearValue を使用して他の値を消去した場合、プロパティ値が既定値に等しくなる可能性があります。

強制値コールバックとプロパティ変更コールバックはプロパティ メタデータの一部です。 したがって、特定の依存関係プロパティを所有する型から派生させた型に存在するその特定の依存関係プロパティのコールバックは、派生させた型でプロパティのメタデータをオーバーライドすることで変更できます。

高度な強制とコールバック シナリオ

制約と目的の値

プロパティ システムは、CoerceValueCallback コールバックを使用して、開発者が宣言したロジックに従って値に強制を機能させます。ただし、ローカルに設定されたプロパティの、強制が機能している値は、"目的の値" を内部で保持します。 制約がアプリケーションの有効期間内に動的に変更される可能性があるプロパティ値に基づいている場合、強制の制約も動的に変更されます。そして、制約されているプロパティは、指定された新しい制約の下で、その値を目的の値にできる限り近づけることができます。 制約がすべて解除された場合、値は目的の値になります。 相互に循環的に依存する複数のプロパティを使用している場合、非常に複雑な依存関係シナリオを導入することができます。 たとえば、Min/Max/Current シナリオでは、Minimum と Maximum をユーザー設定可能なプロパティにすることができます。 その場合、Maximum が常に Minimum より大きくなり、Minimum が常に Maximum より小さくなるように強制しなければならないことがあります。 ただし、この強制を有効にし、Maximum が Minimum に強制された場合、Current が未設定の状態になります。なぜなら、Currenct は Maximum と Minimum の両方に依存し、両者の値の間の範囲内 (この場合はゼロ) に制約されるからです。 その後、Maximum または Minimum が調整された場合、Current はいずれかの値を "追跡" すると考えられます。なぜなら、Current の目的の値が依然として保持されており、制約が緩和されると Current が目的の値に到達しようとするからです。

複雑な依存関係に関する技術的な問題はありません。ただし、多数の再評価が必要になる場合、パフォーマンスが若干低下することがあります。また、UI に直接影響する場合は、ユーザーの混乱につながることもあります。 プロパティ変更コールバックおよび強制値コールバックについては注意が必要です。試行中の強制ができる限り明確に処理されること、およびその制約が "過剰制約" ではないことを確認してください。

CoerceValue を使用した値変更の取り消し

プロパティ システムは、UnsetValue 値を返すすべての CoerceValueCallback を特殊なケースとして処理します。 この特殊なケースは、CoerceValueCallback 呼び出しの原因となったプロパティの変更がプロパティ システムによって拒否され、代わりにプロパティの直前の値がプロパティ システムによって報告されることを意味します。 このメカニズムは、非同期に開始されたプロパティの変更が現在のオブジェクトの状態に対して依然として有効であるかどうかをチェックし、有効でない場合はその変更を抑制する場合に役立ちます。 考えられるもう 1 つのシナリオとして、プロパティ値の決定におけるどの構成要素が報告されるプロパティ値を決定するかに応じて値を選択的に抑制することもできます。 これを行うには、コールバックで渡された DependencyProperty とプロパティ識別子を GetValueSource への入力として使用し、次に ValueSource を処理します。

参照

概念

依存関係プロパティの概要

依存関係プロパティのメタデータ

カスタム依存関係プロパティ