Zpětná volání a ověřování vlastností závislostí (WPF .NET)
Tento článek popisuje, jak definovat vlastnost závislosti a implementovat zpětná volání vlastností závislostí. Zpětná volání podporují ověřování hodnot, převod hodnot a další logiku, která je nutná při změně hodnoty vlastnosti.
Požadavky
Článek předpokládá základní znalost vlastností závislostí a že jste si přečetli přehled vlastností závislostí. Pokud chcete postupovat podle příkladů v tomto článku, pomůže vám to, pokud znáte jazyk XAML (Extensible Application Markup Language) a víte, jak psát aplikace WPF.
Zpětná volání pro ověření hodnoty
Zpětná volání v rámci ověřování hodnoty poskytují způsob, jak ověřit, zda je nová hodnota vlastnosti závislosti platná, než ji aplikuje systém vlastností. Toto zpětné volání vyvolá výjimku, pokud hodnota nesplňuje ověřovací kritéria.
Zpětné volání pro ověření hodnoty lze při registraci vlastnosti závislosti přiřadit pouze jednou. Při registraci vlastnosti závislosti máte možnost předat ValidateValueCallback jako odkaz na metodu Register(String, Type, Type, PropertyMetadata, ValidateValueCallback). Zpětná volání typu Validate-value nejsou součástí metadat vlastností a nelze je přepsat.
Platná hodnota vlastnosti závislosti je její použitá hodnota. Efektivní hodnota je určena prostřednictvím priority hodnoty vlastnosti , pokud existuje více vstupů založených na vlastnosti. Pokud je zpětné volání typu validate-value registrováno pro vlastnost závislosti, systém vlastností vyvolá zpětné volání ověření-hodnota při změně hodnoty a předá novou hodnotu jako objekt. V rámci zpětného volání můžete přetypovat objekt hodnoty zpět na typ zaregistrovaný v systému vlastností a pak na něj spustit ověřovací logiku. Zpětné volání vrátí true
pokud je hodnota platná pro vlastnost, jinak false
.
Pokud zpětné volání typu validate-value vrátí false
, vyvolá se výjimka a nová hodnota se nepoužije. Autoři aplikací musí být připraveni na zpracování těchto výjimek. Běžným použitím zpětných volání ověřovacích hodnot je ověřování hodnot výčtu nebo omezení číselných hodnot, pokud představují hodnoty, které mají omezení. Zpětné volání pro ověření hodnoty je vyvoláno systémem vlastností v různých scénářích, mezi které patří:
- Inicializace objektů, která použije výchozí hodnotu při vytváření.
- Programová volání SetValue.
- Přepisy metadat, které určují novou výchozí hodnotu.
Zpětné volání typu Validate-value nemají parametr, který určuje instanci DependencyObject, na které je nastavena nová hodnota. Všechny instance DependencyObject
sdílejí stejný callback validate-value, takže ho nelze použít k ověření scénářů specifických pro instanci. Další informace najdete v tématu ValidateValueCallback.
Následující příklad ukazuje, jak zabránit, aby vlastnost, typovaná jako Double, byla nastavena na PositiveInfinity nebo NegativeInfinity.
public class Gauge1 : Control
{
public Gauge1() : base() { }
// Register a dependency property with the specified property name,
// property type, owner type, property metadata, and callbacks.
public static readonly DependencyProperty CurrentReadingProperty =
DependencyProperty.Register(
name: "CurrentReading",
propertyType: typeof(double),
ownerType: typeof(Gauge1),
typeMetadata: new FrameworkPropertyMetadata(
defaultValue: double.NaN,
flags: FrameworkPropertyMetadataOptions.AffectsMeasure),
validateValueCallback: new ValidateValueCallback(IsValidReading));
// CLR wrapper with get/set accessors.
public double CurrentReading
{
get => (double)GetValue(CurrentReadingProperty);
set => SetValue(CurrentReadingProperty, value);
}
// Validate-value callback.
public static bool IsValidReading(object value)
{
double val = (double)value;
return !val.Equals(double.NegativeInfinity) &&
!val.Equals(double.PositiveInfinity);
}
}
Public Class Gauge1
Inherits Control
Public Sub New()
MyBase.New()
End Sub
Public Shared ReadOnly CurrentReadingProperty As DependencyProperty =
DependencyProperty.Register(
name:="CurrentReading",
propertyType:=GetType(Double),
ownerType:=GetType(Gauge1),
typeMetadata:=New FrameworkPropertyMetadata(
defaultValue:=Double.NaN,
flags:=FrameworkPropertyMetadataOptions.AffectsMeasure),
validateValueCallback:=New ValidateValueCallback(AddressOf IsValidReading))
Public Property CurrentReading As Double
Get
Return GetValue(CurrentReadingProperty)
End Get
Set(value As Double)
SetValue(CurrentReadingProperty, value)
End Set
End Property
Public Shared Function IsValidReading(value As Object) As Boolean
Dim val As Double = value
Return Not val.Equals(Double.NegativeInfinity) AndAlso
Not val.Equals(Double.PositiveInfinity)
End Function
End Class
public static void TestValidationBehavior()
{
Gauge1 gauge = new();
Debug.WriteLine($"Test value validation scenario:");
// Set allowed value.
gauge.CurrentReading = 5;
Debug.WriteLine($"Current reading: {gauge.CurrentReading}");
try
{
// Set disallowed value.
gauge.CurrentReading = double.PositiveInfinity;
}
catch (ArgumentException e)
{
Debug.WriteLine($"Exception thrown by ValidateValueCallback: {e.Message}");
}
Debug.WriteLine($"Current reading: {gauge.CurrentReading}");
// Current reading: 5
// Exception thrown by ValidateValueCallback: '∞' is not a valid value for property 'CurrentReading'.
// Current reading: 5
}
Public Shared Sub TestValidationBehavior()
Dim gauge As New Gauge1()
Debug.WriteLine($"Test value validation scenario:")
' Set allowed value.
gauge.CurrentReading = 5
Debug.WriteLine($"Current reading: {gauge.CurrentReading}")
Try
' Set disallowed value.
gauge.CurrentReading = Double.PositiveInfinity
Catch e As ArgumentException
Debug.WriteLine($"Exception thrown by ValidateValueCallback: {e.Message}")
End Try
Debug.WriteLine($"Current reading: {gauge.CurrentReading}")
' Current reading: 5
' Exception thrown by ValidateValueCallback: '∞' is not a valid value for property 'CurrentReading'.
' Current reading 5
End Sub
Zpětné volání při změně vlastnosti
Zpětná volání při změně vlastnosti vás upozorní, když se změní účinná hodnota závislé vlastnosti.
Zpětná volání při změně vlastnosti jsou součástí metadat vlastnosti závislosti. Pokud odvozujete z třídy, která definuje vlastnost závislosti, nebo přidáte třídu jako vlastníka vlastnosti závislosti, můžete přepsat metadata. Při přepisování metadat máte možnost zadat nový odkaz PropertyChangedCallback. Zpětné volání změněné vlastností použijte ke spuštění logiky, která je nutná při změně hodnoty vlastnosti.
Na rozdíl od zpětných volání typu validate-value mají zpětné volání změněné vlastností parametr, který určuje DependencyObject instanci, na které je nastavena nová hodnota. Další příklad ukazuje, jak může zpětné volání změněné vlastnosti použít odkaz na instanci DependencyObject
k aktivaci zpětného volání pro úpravu hodnoty.
Zpětná volání pro nucenou hodnotu
Zpětné volání pro úpravu hodnoty poskytuje způsob, jak být upozorněni, když se chystá změnit efektivní hodnota vlastnosti závislosti. Tímto způsobem můžete upravit novou hodnotu před jeho použitím. Kromě toho, že je aktivován systémem vlastností, můžete ze svého kódu vyvolat zpětné volání pro úpravu hodnoty.
Zpětná volání coerce-hodnota jsou součástí metadat vlastností závislostí. Pokud odvozujete z třídy, která definuje vlastnost závislosti, nebo přidáte třídu jako vlastníka vlastnosti závislosti, můžete přepsat metadata. Při úpravě metadat máte možnost poskytnout odkaz na nový CoerceValueCallback. Pomocí zpětného volání coerce-value vyhodnoťte nové hodnoty a v případě potřeby je převeďte. Zpětné volání vrátí převedenou hodnotu, pokud došlo k převodu, jinak vrátí nezměněnou novou hodnotu.
Podobně jako zpětné volání změny vlastnosti mají zpětné volání coerce-value parametr, který určuje instanci DependencyObject, na které je nová hodnota nastavena. Další příklad ukazuje, jak zpětné volání coerce-value může použít odkaz na instanci DependencyObject
k převodu hodnot vlastností.
Poznámka
Výchozí hodnoty vlastností nelze přetěžovat. Vlastnost závislosti má výchozí hodnotu nastavenou při inicializaci objektu nebo při vymazání jiných hodnot pomocí ClearValue.
Volání zpět pro úpravu hodnoty a změnu vlastností v kombinaci
Závislosti mezi vlastnostmi prvku můžete vytvořit pomocí zpětných volání pro úpravu hodnoty a změněné vlastnosti ve vzájemné spolupráci. Například změny v jedné vlastnosti mohou vynutit přetypování nebo opětovné vyhodnocení u jiné závislé vlastnosti. Následující příklad ukazuje běžný scénář: tři vlastnosti závislosti, které ukládají aktuální hodnotu, minimální hodnotu a maximální hodnotu prvku uživatelského rozhraní. Pokud se maximální hodnota změní tak, aby byla menší než aktuální hodnota, nastaví se aktuální hodnota na novou maximální hodnotu. A pokud se minimální hodnota změní tak, aby byla větší než aktuální hodnota, aktuální hodnota se nastaví na novou minimální hodnotu. V příkladu PropertyChangedCallback pro aktuální hodnotu explicitně vyvolá CoerceValueCallback pro minimální a maximální hodnoty.
public class Gauge2 : Control
{
public Gauge2() : base() { }
// Register a dependency property with the specified property name,
// property type, owner type, property metadata, and callbacks.
public static readonly DependencyProperty CurrentReadingProperty =
DependencyProperty.Register(
name: "CurrentReading",
propertyType: typeof(double),
ownerType: typeof(Gauge2),
typeMetadata: new FrameworkPropertyMetadata(
defaultValue: double.NaN,
flags: FrameworkPropertyMetadataOptions.AffectsMeasure,
propertyChangedCallback: new PropertyChangedCallback(OnCurrentReadingChanged),
coerceValueCallback: new CoerceValueCallback(CoerceCurrentReading)
),
validateValueCallback: new ValidateValueCallback(IsValidReading)
);
// CLR wrapper with get/set accessors.
public double CurrentReading
{
get => (double)GetValue(CurrentReadingProperty);
set => SetValue(CurrentReadingProperty, value);
}
// Validate-value callback.
public static bool IsValidReading(object value)
{
double val = (double)value;
return !val.Equals(double.NegativeInfinity) && !val.Equals(double.PositiveInfinity);
}
// Property-changed callback.
private static void OnCurrentReadingChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
depObj.CoerceValue(MinReadingProperty);
depObj.CoerceValue(MaxReadingProperty);
}
// Coerce-value callback.
private static object CoerceCurrentReading(DependencyObject depObj, object value)
{
Gauge2 gauge = (Gauge2)depObj;
double currentVal = (double)value;
currentVal = currentVal < gauge.MinReading ? gauge.MinReading : currentVal;
currentVal = currentVal > gauge.MaxReading ? gauge.MaxReading : currentVal;
return currentVal;
}
// Register a dependency property with the specified property name,
// property type, owner type, property metadata, and callbacks.
public static readonly DependencyProperty MaxReadingProperty = DependencyProperty.Register(
name: "MaxReading",
propertyType: typeof(double),
ownerType: typeof(Gauge2),
typeMetadata: new FrameworkPropertyMetadata(
defaultValue: double.NaN,
flags: FrameworkPropertyMetadataOptions.AffectsMeasure,
propertyChangedCallback: new PropertyChangedCallback(OnMaxReadingChanged),
coerceValueCallback: new CoerceValueCallback(CoerceMaxReading)
),
validateValueCallback: new ValidateValueCallback(IsValidReading)
);
// CLR wrapper with get/set accessors.
public double MaxReading
{
get => (double)GetValue(MaxReadingProperty);
set => SetValue(MaxReadingProperty, value);
}
// Property-changed callback.
private static void OnMaxReadingChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
depObj.CoerceValue(MinReadingProperty);
depObj.CoerceValue(CurrentReadingProperty);
}
// Coerce-value callback.
private static object CoerceMaxReading(DependencyObject depObj, object value)
{
Gauge2 gauge = (Gauge2)depObj;
double maxVal = (double)value;
return maxVal < gauge.MinReading ? gauge.MinReading : maxVal;
}
// Register a dependency property with the specified property name,
// property type, owner type, property metadata, and callbacks.
public static readonly DependencyProperty MinReadingProperty = DependencyProperty.Register(
name: "MinReading",
propertyType: typeof(double),
ownerType: typeof(Gauge2),
typeMetadata: new FrameworkPropertyMetadata(
defaultValue: double.NaN,
flags: FrameworkPropertyMetadataOptions.AffectsMeasure,
propertyChangedCallback: new PropertyChangedCallback(OnMinReadingChanged),
coerceValueCallback: new CoerceValueCallback(CoerceMinReading)
),
validateValueCallback: new ValidateValueCallback(IsValidReading));
// CLR wrapper with get/set accessors.
public double MinReading
{
get => (double)GetValue(MinReadingProperty);
set => SetValue(MinReadingProperty, value);
}
// Property-changed callback.
private static void OnMinReadingChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
depObj.CoerceValue(MaxReadingProperty);
depObj.CoerceValue(CurrentReadingProperty);
}
// Coerce-value callback.
private static object CoerceMinReading(DependencyObject depObj, object value)
{
Gauge2 gauge = (Gauge2)depObj;
double minVal = (double)value;
return minVal > gauge.MaxReading ? gauge.MaxReading : minVal;
}
}
Public Class Gauge2
Inherits Control
Public Sub New()
MyBase.New()
End Sub
' Register a dependency property with the specified property name,
' property type, owner type, property metadata, And callbacks.
Public Shared ReadOnly CurrentReadingProperty As DependencyProperty =
DependencyProperty.Register(
name:="CurrentReading",
propertyType:=GetType(Double),
ownerType:=GetType(Gauge2),
typeMetadata:=New FrameworkPropertyMetadata(
defaultValue:=Double.NaN,
flags:=FrameworkPropertyMetadataOptions.AffectsMeasure,
propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnCurrentReadingChanged),
coerceValueCallback:=New CoerceValueCallback(AddressOf CoerceCurrentReading)),
validateValueCallback:=New ValidateValueCallback(AddressOf IsValidReading))
' CLR wrapper with get/set accessors.
Public Property CurrentReading As Double
Get
Return GetValue(CurrentReadingProperty)
End Get
Set(value As Double)
SetValue(CurrentReadingProperty, value)
End Set
End Property
' Validate-value callback.
Public Shared Function IsValidReading(value As Object) As Boolean
Dim val As Double = value
Return Not val.Equals(Double.NegativeInfinity) AndAlso Not val.Equals(Double.PositiveInfinity)
End Function
' Property-changed callback.
Private Shared Sub OnCurrentReadingChanged(depObj As DependencyObject, e As DependencyPropertyChangedEventArgs)
depObj.CoerceValue(MinReadingProperty)
depObj.CoerceValue(MaxReadingProperty)
End Sub
' Coerce-value callback.
Private Shared Function CoerceCurrentReading(depObj As DependencyObject, value As Object) As Object
Dim gauge As Gauge2 = CType(depObj, Gauge2)
Dim currentVal As Double = value
currentVal = If(currentVal < gauge.MinReading, gauge.MinReading, currentVal)
currentVal = If(currentVal > gauge.MaxReading, gauge.MaxReading, currentVal)
Return currentVal
End Function
Public Shared ReadOnly MaxReadingProperty As DependencyProperty =
DependencyProperty.Register(
name:="MaxReading",
propertyType:=GetType(Double),
ownerType:=GetType(Gauge2),
typeMetadata:=New FrameworkPropertyMetadata(
defaultValue:=Double.NaN,
flags:=FrameworkPropertyMetadataOptions.AffectsMeasure,
propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnMaxReadingChanged),
coerceValueCallback:=New CoerceValueCallback(AddressOf CoerceMaxReading)),
validateValueCallback:=New ValidateValueCallback(AddressOf IsValidReading))
' CLR wrapper with get/set accessors.
Public Property MaxReading As Double
Get
Return GetValue(MaxReadingProperty)
End Get
Set(value As Double)
SetValue(MaxReadingProperty, value)
End Set
End Property
' Property-changed callback.
Private Shared Sub OnMaxReadingChanged(depObj As DependencyObject, e As DependencyPropertyChangedEventArgs)
depObj.CoerceValue(MinReadingProperty)
depObj.CoerceValue(CurrentReadingProperty)
End Sub
' Coerce-value callback.
Private Shared Function CoerceMaxReading(depObj As DependencyObject, value As Object) As Object
Dim gauge As Gauge2 = CType(depObj, Gauge2)
Dim maxVal As Double = value
Return If(maxVal < gauge.MinReading, gauge.MinReading, maxVal)
End Function
' Register a dependency property with the specified property name,
' property type, owner type, property metadata, And callbacks.
Public Shared ReadOnly MinReadingProperty As DependencyProperty =
DependencyProperty.Register(
name:="MinReading",
propertyType:=GetType(Double),
ownerType:=GetType(Gauge2),
typeMetadata:=New FrameworkPropertyMetadata(
defaultValue:=Double.NaN,
flags:=FrameworkPropertyMetadataOptions.AffectsMeasure,
propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnMinReadingChanged),
coerceValueCallback:=New CoerceValueCallback(AddressOf CoerceMinReading)),
validateValueCallback:=New ValidateValueCallback(AddressOf IsValidReading))
' CLR wrapper with get/set accessors.
Public Property MinReading As Double
Get
Return GetValue(MinReadingProperty)
End Get
Set(value As Double)
SetValue(MinReadingProperty, value)
End Set
End Property
' Property-changed callback.
Private Shared Sub OnMinReadingChanged(depObj As DependencyObject, e As DependencyPropertyChangedEventArgs)
depObj.CoerceValue(MaxReadingProperty)
depObj.CoerceValue(CurrentReadingProperty)
End Sub
' Coerce-value callback.
Private Shared Function CoerceMinReading(depObj As DependencyObject, value As Object) As Object
Dim gauge As Gauge2 = CType(depObj, Gauge2)
Dim minVal As Double = value
Return If(minVal > gauge.MaxReading, gauge.MaxReading, minVal)
End Function
End Class
Pokročilé scénáře zpětného volání
Omezení a požadované hodnoty
Pokud se místně nastavená hodnota vlastnosti závislosti změní prostřednictvím převodu, zachová se nezměněná hodnota místně nastavené jako požadovaná hodnota. Pokud je převod založen na jiných hodnotách vlastností, systém vlastností bude dynamicky přehodnocovat převod pokaždé, když se tyto ostatní hodnoty změní. V rámci omezení nátlaku použije systém vlastností hodnotu, která je nejblíže požadované hodnotě. Pokud se podmínka nucení už nepoužije, systém vlastností obnoví požadovanou hodnotu—za předpokladu, že není aktivní žádná hodnota s vyšší prioritou . Následující příklad testuje typové donucení u aktuální hodnoty, minimální hodnoty a maximální hodnoty v tomto scénáři.
public static void TestCoercionBehavior()
{
Gauge2 gauge = new()
{
// Set initial values.
MinReading = 0,
MaxReading = 10,
CurrentReading = 5
};
Debug.WriteLine($"Test current/min/max values scenario:");
// Current reading is not coerced.
Debug.WriteLine($"Current reading: " +
$"{gauge.CurrentReading} (min: {gauge.MinReading}, max: {gauge.MaxReading})");
// Current reading is coerced to max value.
gauge.MaxReading = 3;
Debug.WriteLine($"Current reading: " +
$"{gauge.CurrentReading} (min: {gauge.MinReading}, max: {gauge.MaxReading})");
// Current reading is coerced, but tracking back to the desired value.
gauge.MaxReading = 4;
Debug.WriteLine($"Current reading: " +
$"{gauge.CurrentReading} (min: {gauge.MinReading}, max: {gauge.MaxReading})");
// Current reading reverts to the desired value.
gauge.MaxReading = 10;
Debug.WriteLine($"Current reading: " +
$"{gauge.CurrentReading} (min: {gauge.MinReading}, max: {gauge.MaxReading})");
// Current reading remains at the desired value.
gauge.MinReading = 5;
gauge.MaxReading = 5;
Debug.WriteLine($"Current reading: " +
$"{gauge.CurrentReading} (min: {gauge.MinReading}, max: {gauge.MaxReading})");
// Current reading: 5 (min=0, max=10)
// Current reading: 3 (min=0, max=3)
// Current reading: 4 (min=0, max=4)
// Current reading: 5 (min=0, max=10)
// Current reading: 5 (min=5, max=5)
}
Public Shared Sub TestCoercionBehavior()
' Set initial values.
Dim gauge As New Gauge2 With {
.MinReading = 0,
.MaxReading = 10,
.CurrentReading = 5
}
Debug.WriteLine($"Test current/min/max values scenario:")
' Current reading is not coerced.
Debug.WriteLine($"Current reading: " &
$"{gauge.CurrentReading} (min={gauge.MinReading}, max={gauge.MaxReading})")
' Current reading is coerced to max value.
gauge.MaxReading = 3
Debug.WriteLine($"Current reading: " &
$"{gauge.CurrentReading} (min={gauge.MinReading}, max={gauge.MaxReading})")
' Current reading is coerced, but tracking back to the desired value.
gauge.MaxReading = 4
Debug.WriteLine($"Current reading: " &
$"{gauge.CurrentReading} (min={gauge.MinReading}, max={gauge.MaxReading})")
' Current reading reverts to the desired value.
gauge.MaxReading = 10
Debug.WriteLine($"Current reading: " &
$"{gauge.CurrentReading} (min={gauge.MinReading}, max={gauge.MaxReading})")
' Current reading remains at the desired value.
gauge.MinReading = 5
gauge.MaxReading = 5
Debug.WriteLine($"Current reading: " &
$"{gauge.CurrentReading} (min={gauge.MinReading}, max={gauge.MaxReading})")
' Current reading: 5 (min=0, max=10)
' Current reading: 3 (min=0, max=3)
' Current reading: 4 (min=0, max=4)
' Current reading: 5 (min=0, max=10)
' Current reading: 5 (min=5, max=5)
End Sub
K poměrně složitým scénářům závislostí může dojít v případě, že máte více vlastností závislých na sobě cyklicky. Technicky vzato není nic špatného u složitých závislostí – s tím rozdílem, že velký počet opakovaných vyhodnocení může snížit výkon. Složité závislosti, které jsou vystaveny v uživatelském rozhraní, mohou také zmást uživatele. Zacházejte s PropertyChangedCallback a CoerceValueCallback co nejjednoznačněji a příliš neomezujte.
Zrušení změn hodnot
Vrácením UnsetValue z CoerceValueCallbackmůžete odmítnout změnu hodnoty vlastnosti. Tento mechanismus je užitečný, pokud je změna hodnoty vlastnosti inicializována asynchronně, ale když je použita, již není platná pro aktuální stav objektu. Dalším scénářem může být selektivní potlačení změny hodnoty na základě místa, odkud pochází. V následujícím příkladu CoerceValueCallback
volá metodu GetValueSource, která vrátí ValueSource strukturu s BaseValueSource výčtem, který identifikuje zdroj nové hodnoty.
// Coerce-value callback.
private static object CoerceCurrentReading(DependencyObject depObj, object value)
{
// Get value source.
ValueSource valueSource =
DependencyPropertyHelper.GetValueSource(depObj, CurrentReadingProperty);
// Reject any property value change that's a locally set value.
return valueSource.BaseValueSource == BaseValueSource.Local ?
DependencyProperty.UnsetValue : value;
}
' Coerce-value callback.
Private Shared Function CoerceCurrentReading(depObj As DependencyObject, value As Object) As Object
' Get value source.
Dim valueSource As ValueSource =
DependencyPropertyHelper.GetValueSource(depObj, CurrentReadingProperty)
' Reject any property value that's a locally set value.
Return If(valueSource.BaseValueSource = BaseValueSource.Local, DependencyProperty.UnsetValue, value)
End Function
Viz také
- ValidateValueCallback
- PropertyChangedCallback
- CoerceValueCallback
- Přehled vlastností závislostí
- metadata vlastnosti závislosti
- Vlastní vlastnosti závislosti
.NET Desktop feedback