次の方法で共有


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

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

前提 条件

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

検証コールバック

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

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); }
}
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

コールバックは、オブジェクト値が提供されるように実装されます。 指定された値がプロパティに対して有効な場合は、true を返します。それ以外の場合は、false返されます。 プロパティがプロパティ システムに登録されている型に従って正しい型であると見なされるため、通常、コールバック内の型のチェックは行われません。 コールバックは、さまざまな操作でプロパティ システムによって使用されます。 これには、既定値による初期型の初期化、SetValueの呼び出しによるプログラムによる変更、または指定された新しい既定値でメタデータのオーバーライドが試みられた場合が含まれます。 検証コールバックがこれらの操作のいずれかによって呼び出され、falseを返す場合は、例外が発生します。 これらの例外を処理するには、アプリケーション ライターを準備する必要があります。 検証コールバックの一般的な用途は、列挙値の検証や、プロパティが 0 以上である必要がある測定値を設定する際に、整数やdouble型の値を制約することです。

検証コールバックは、特に、インスタンス検証コントロールではなくクラス検証コントロールを目的としています。 コールバックのパラメーターは、検証するプロパティが設定されている特定の DependencyObject を伝えるものではありません。 したがって、検証コールバックは、プロパティ値に影響を与える可能性のある "依存関係" を適用する場合には役立ちません。プロパティのインスタンス固有の値は、他のプロパティのインスタンス固有の値や実行時の状態などの要因に依存します。

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

public static bool IsValidReading(object value)
{
    Double v = (Double)value;
    return (!v.Equals(Double.NegativeInfinity) && !v.Equals(Double.PositiveInfinity));
}
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

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

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

依存関係プロパティのリンケージを使用する一般的なシナリオは、要素が最小値と最大値にそれぞれ 1 つのプロパティを保持し、実際または現在の値の 3 番目のプロパティを保持するユーザー インターフェイス ドリブン プロパティがある場合です。 ここで、現在の値が新しい最大値を超えるような方法で最大値が調整された場合は、現在の値を新しい最大値より大きくせず、最小値と現在の値に対する同様の関係を強制する必要があります。

次に示すのは、このリレーションシップを示す 3 つの依存関係プロパティの 1 つだけの簡単なコード例です。 この例では、関連する *Reading プロパティの最小/最大/現在のセットの CurrentReading プロパティを登録する方法を示します。 前のセクションに示すように、検証が使用されます。

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); }
}
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

Current のプロパティ変更コールバックは、他のプロパティに登録されている強制値コールバックを明示的に呼び出すことによって、変更を他の依存プロパティに転送するために使用されます。

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

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

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;
}
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

手記

プロパティの既定値は強制されません。 既定値と等しいプロパティ値は、プロパティ値の初期既定値が残っている場合や、ClearValueを使用して他の値をクリアする場合に発生する可能性があります。

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

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

制約と必要な値

CoerceValueCallback コールバックは、宣言したロジックに従って値を強制するためにプロパティ システムによって使用されますが、ローカルに設定されたプロパティの強制値は内部的に "必要な値" を保持します。 制約が、アプリケーションの有効期間中に動的に変更される可能性がある他のプロパティ値に基づいている場合は、強制制約も動的に変更され、制約付きプロパティは、新しい制約を指定すると、可能な限り目的の値に近づくように値を変更できます。 すべての制約が解除されると、値が目的の値になります。 相互に依存する複数のプロパティが循環的に存在する場合は、かなり複雑な依存関係シナリオを導入する可能性があります。 たとえば、最小/最大/現在のシナリオでは、最小値と最大値をユーザー設定可能にすることができます。 その場合は、Maximum が常に Minimum より大きくなるように強制する必要があります。 ただし、その強制がアクティブで、[最大強制] が [最小] に設定されている場合、Current は両方に依存し、値間の範囲 (ゼロ) に制約されるため、設定できない状態になります。 次に、[最大] または [最小] を調整すると、Current の目的の値がまだ格納され、制約が緩められた時点で目的の値に到達しようとしているため、Current はいずれかの値に "従う" ように見えます。

複雑な依存関係に技術的に問題はありませんが、大量の再評価が必要な場合はパフォーマンスが若干低下する可能性があり、UI に直接影響を与える場合はユーザーに混乱を招く可能性があります。 プロパティの変更通知と値の強制コールバックに注意し、試みられている強制が可能な限り明確かつ誤解のないように扱われ、「過剰な制約」を避けるようにしてください。

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

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

関連項目