Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este artigo descreve como definir uma propriedade de dependência e implementar retornos de chamada de dependência. Os retornos de chamada dão suporte à validação de valor, à imposição de valor e a outra lógica necessária quando um valor de propriedade é alterado.
Pré-requisitos
O artigo pressupõe um conhecimento básico das propriedades de dependência e que você leu visão geral das propriedades de dependência. Para seguir os exemplos neste artigo, ele ajuda se você estiver familiarizado com XAML (Extensible Application Markup Language) e saber como escrever aplicativos WPF.
Retornos de chamada para validação de valor
Retornos de chamada de validação de valor fornecem uma maneira de verificar se um novo valor de propriedade de dependência é válido antes de ser aplicado pelo sistema de propriedades. Esse callback levanta uma exceção se o valor não atender aos critérios de validação.
Os retornos de chamada de validação de valor só podem ser atribuídos a uma propriedade de dependência uma vez, durante o processo de registro da propriedade. Ao registrar uma propriedade de dependência, você tem a opção de passar uma referência ValidateValueCallback para o método Register(String, Type, Type, PropertyMetadata, ValidateValueCallback). Os retornos de chamada de validação de valor não fazem parte dos metadados de propriedade e não podem ser substituídos.
O valor efetivo de uma propriedade de dependência é seu valor aplicado. O valor efetivo é determinado por meio precedência de valor de propriedade quando existem várias entradas baseadas em propriedade. Se um retorno de chamada de validação de valor for registrado para uma propriedade de dependência, o sistema de propriedades invocará seu retorno de chamada de validação de valor quando o valor for alterado, passando o novo valor como um objeto. Dentro do retorno de chamada, você pode converter o objeto de valor de volta para o tipo registrado com o sistema de propriedades e, em seguida, executar sua lógica de validação nele. O retorno de chamada retornará true se o valor for válido para a propriedade; caso contrário, retornará false.
Se um retorno de chamada de valor validado retornar false, uma exceção será gerada e o novo valor não será aplicado. Os desenvolvedores de aplicativos devem estar preparados para lidar com essas exceções. Um uso comum de retornos de chamada de validação de valor é validar valores de enumeração ou restringir valores numéricos quando eles representam medidas que têm limites. Os retornos de chamada de validação de valor são invocados pelo sistema de propriedades em diferentes cenários, incluindo:
- Inicialização de objeto, que aplica um valor padrão no momento da criação.
- Chamadas programáticas para SetValue.
- Substituições de metadados que especificam um novo valor padrão.
Os retornos de chamada de validação de valor não possuem um parâmetro que especifique a instância DependencyObject em que o novo valor é definido. Todas as instâncias de um DependencyObject compartilham o mesmo callback de validação de valor, portanto, ele não pode ser usado para validar cenários específicos da instância. Para obter mais informações, consulte ValidateValueCallback.
O exemplo a seguir mostra como evitar uma propriedade, digitada como Double, sendo definida como PositiveInfinity ou 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
Retornos de chamada de alteração em propriedade
Os retornos de alteração em propriedade notificam você quando o valor efetivo de uma propriedade de dependência é alterado.
Os retornos de chamada de alteração de propriedade fazem parte dos metadados da propriedade de dependência. Se você derivar de uma classe que define uma propriedade de dependência ou adicionar sua classe como proprietário de uma propriedade de dependência, poderá substituir os metadados. Ao substituir os metadados, você tem a opção de fornecer uma nova referência PropertyChangedCallback. Use um retorno de chamada alterado pela propriedade para executar a lógica necessária quando um valor de propriedade é alterado.
Ao contrário dos retornos de chamada de validação de valor, os retornos de chamada de alteração de propriedade têm um parâmetro que especifica a instância DependencyObject na qual o novo valor é definido. O exemplo a seguir mostra como um retorno de chamada de mudança de propriedade pode usar a referência de instância DependencyObject para disparar retornos de chamada de imposição de valor.
Retornos de chamada de imposição de valor
Os retornos de chamada de imposição de valor fornecem uma maneira de você ser notificado quando o valor efetivo de uma propriedade de dependência está prestes a ser alterado, para que você possa ajustar o novo valor antes que ele seja aplicado. Além de ser disparado pelo sistema de propriedades, você pode invocar retornos de chamada de imposição de valor do seu código.
Os retornos de chamada de imposição de valor fazem parte dos metadados da propriedade de dependência. Se você derivar de uma classe que define uma propriedade de dependência ou adicionar sua classe como proprietário de uma propriedade de dependência, poderá substituir os metadados. Ao substituir os metadados, você tem a opção de fornecer uma referência a um novo CoerceValueCallback. Utilize um retorno de chamada de imposição de valor para avaliar novos valores e coagi-los quando necessário. Se a coerção ocorrer, o callback retorna o valor coagido; caso contrário, retorna o novo valor inalterado.
Semelhante aos retornos de chamada de alteração de propriedade, os retornos de chamada de imposição de valor têm um parâmetro que especifica a instância DependencyObject na qual o novo valor é definido. O exemplo a seguir mostra como uma função de retorno de chamada de imposição de valor pode usar uma referência de instância DependencyObject para impor valores de propriedade.
Observação
Valores de propriedade padrão não podem ser impostos. Uma propriedade de dependência tem seu valor padrão definido na inicialização do objeto ou quando você limpa outros valores usando ClearValue.
Retorno de chamada de imposição de valor e mudança de propriedade em combinação
Você pode criar dependências entre propriedades em um elemento, usando retornos de chamada de imposição de valor e retornos de chamada de alteração de propriedade em conjunto. Por exemplo, alterações em uma propriedade forçam a coerção ou a reavaliação em outra propriedade de dependência. O exemplo a seguir mostra um cenário comum: três propriedades de dependência que, respectivamente, armazenam o valor atual, o valor mínimo e o valor máximo de um elemento de interface do usuário. Se o valor máximo for alterado para que seja menor que o valor atual, o valor atual será definido como o novo valor máximo. E, se o valor mínimo for alterado para que seja maior que o valor atual, o valor atual será definido como o novo valor mínimo. No exemplo, o PropertyChangedCallback do valor atual invoca explicitamente o CoerceValueCallback para os valores mínimo e máximo.
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
Cenários avançados de retorno de chamada
Restrições e valores desejados
Se um valor definido localmente de uma propriedade de dependência for alterado por meio da coerção, o valor definido localmente inalterado será mantido como o valor desejado. Se a coerção for baseada em outros valores de propriedade, o sistema de propriedades reavaliará dinamicamente a coerção sempre que esses outros valores forem alterados. Dentro das restrições da coerção, o sistema de propriedades aplicará um valor mais próximo ao valor desejado. Se a condição de coerção não se aplicar mais, o sistema de propriedades restaurará o valor desejado, supondo que não haja um valor de precedência maior em atividade. O exemplo a seguir testa a coerção no valor atual, no valor mínimo e no cenário de valor máximo.
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
Cenários de dependência bastante complexos podem ocorrer quando você tem várias propriedades que dependem umas das outras de maneira circular. Tecnicamente, não há nada de errado com dependências complexas, exceto que um grande número de reavaliações pode reduzir o desempenho. Além disso, dependências complexas expostas na interface do usuário podem confundir os usuários. Trate PropertyChangedCallback e CoerceValueCallback da forma mais inequívoca possível e não restrinja demais.
Cancelar alterações de valor
Ao retornar UnsetValue de um CoerceValueCallback, você pode rejeitar uma alteração no valor da propriedade. Esse mecanismo é útil quando uma alteração de valor de propriedade é iniciada de forma assíncrona, mas quando ela é aplicada não é mais válida para o estado do objeto atual. Outro cenário pode ser suprimir seletivamente uma alteração de valor com base na origem dela. No exemplo a seguir, o CoerceValueCallback chama o método GetValueSource, que retorna uma estrutura ValueSource com uma enumeração BaseValueSource que identifica a origem do novo valor.
// 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
Consulte também
- ValidateValueCallback
- PropertyChangedCallback
- CoerceValueCallback
- Visão geral das propriedades de dependência
- Metadados de propriedade de dependência
- Propriedades de dependência personalizadas
.NET Desktop feedback