Partilhar via


Propriedades de dependência: retornos de chamada e validação

Este tópico descreve como criar propriedades de dependência usando implementações personalizadas alternativas para recursos relacionados à propriedade, como determinação de validação, callbacks que são invocados sempre que o valor efetivo da propriedade é alterado e anulando possíveis influências externas na determinação de valor. Este tópico também discute cenários em que a expansão dos comportamentos padrão do sistema de propriedades usando essas técnicas é apropriada.

Pré-requisitos

Este tópico pressupõe que você entenda os cenários básicos de implementação de uma propriedade de dependência e como os metadados são aplicados a uma propriedade de dependência personalizada. Consulte Propriedades de Dependência Personalizadas e Metadados de Propriedade de Dependência para contexto.

Retornos de chamada de validação

Os callbacks de validação podem ser atribuídos a uma propriedade de dependência quando se a regista pela primeira vez. O callback de validação não faz parte dos metadados da propriedade; é uma entrada direta do método Register. Portanto, uma vez que um retorno de chamada de validação é definido para uma propriedade de dependência, ele não poderá ser substituído por uma nova implementação.

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

Os retornos de chamada são implementados de tal forma que lhes é fornecido um valor de objeto. Eles devolvem true se o valor fornecido for válido para o imóvel; caso contrário, eles retornam false. Supõe-se que a propriedade seja do tipo correto conforme registrado no sistema de propriedades, portanto, normalmente não se verifica o tipo dentro dos retornos de chamada. Os callbacks são utilizados pelo sistema de propriedades numa variedade de operações. Isso inclui a inicialização inicial do tipo por valor padrão, a alteração programática invocando SetValueou tentativas de substituir metadados pelo novo valor padrão fornecido. Se o retorno de chamada de validação for invocado por qualquer uma dessas operações e retornar false, uma exceção será gerada. Os criadores de aplicativos devem estar preparados para lidar com essas exceções. Um uso comum de retornos de chamada de validação é validar valores de enumeração ou restringir valores de inteiros ou duplos quando a propriedade define medidas que devem ser zero ou maiores.

As funções de retorno de validação destinam-se especificamente a serem validadores de classe, não validadores de instância. Os parâmetros do retorno de chamada não comunicam um DependencyObject específico sobre o qual estão definidas as propriedades a serem validadas. Portanto, os callbacks de validação não são úteis para impor as possíveis "dependências" que podem influenciar um valor de propriedade, onde o valor específico da instância de uma propriedade depende de fatores como valores específicos da instância de outras propriedades ou estado durante a execução.

A seguir está um código de exemplo para um cenário de callback de validação muito simples: validar que uma propriedade tipada como a primitiva Double não é PositiveInfinity ou 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

Retornos de Coerção de Valor e Eventos de Alteração de Propriedade

Os callbacks de coerção de valor passam a instância específica de DependencyObject para propriedades, assim como as implementações de PropertyChangedCallback, invocadas pelo sistema de propriedades sempre que houver uma alteração no valor de uma propriedade de dependência. Usando estes dois callbacks em combinação, podes criar uma série de propriedades em elementos onde as alterações numa propriedade forçarão a coerção ou a reavaliação de outra propriedade.

Um cenário típico para usar uma ligação de propriedades de dependência é quando você tem uma propriedade orientada pela interface do usuário onde o elemento mantém uma propriedade cada para o valor mínimo e máximo e uma terceira propriedade para o valor real ou atual. Aqui, se o máximo foi ajustado de tal forma que o valor atual excedesse o novo máximo, você desejaria coagir o valor atual a não ser maior do que o novo máximo, e uma relação semelhante entre mínimo e atual.

A seguir está um código de exemplo muito breve para apenas uma das três propriedades de dependência que ilustram essa relação. O exemplo mostra como a propriedade CurrentReading de um conjunto Min/Max/Current de propriedades *Reading relacionadas é registrada. Ele usa a validação como mostrado na seção anterior.

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

O retorno de chamada alterado de propriedade para Current é usado para encaminhar a alteração para outras propriedades dependentes, invocando explicitamente os retornos de chamada de valor coercitivo registrados para essas outras propriedades:

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

O retorno de chamada do valor coerce verifica os valores das propriedades das quais a propriedade atual é potencialmente dependente e coage o valor atual, se necessário:

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

Observação

Os valores padrão das propriedades não são coagidos. Um valor de propriedade igual ao valor padrão pode ocorrer se um valor de propriedade ainda tiver seu padrão inicial ou por meio da compensação de outros valores com ClearValue.

O valor coativo e os callbacks de alteração de propriedade fazem parte dos metadados da propriedade. Pode, portanto, alterar os callbacks para uma propriedade de dependência específica, tal como ela existe num tipo derivado do tipo que possui essa propriedade de dependência, ao substituir os metadados dessa propriedade no seu tipo.

Cenários avançados de coerção e retorno de chamada

Restrições e valores desejados

Os retornos de chamada CoerceValueCallback serão usados pelo sistema de propriedades para coagir um valor de acordo com a lógica declarada, mas um valor coagido de uma propriedade definida localmente ainda manterá um "valor desejado" internamente. Se as restrições forem baseadas em outros valores de propriedade que podem mudar dinamicamente durante o tempo de vida do aplicativo, as restrições de coerção também serão alteradas dinamicamente, e a propriedade restrita poderá alterar seu valor para chegar o mais próximo possível do valor desejado dadas as novas restrições. O valor se tornará o valor desejado se todas as restrições forem removidas. Você pode potencialmente introduzir alguns cenários de dependência bastante complicados se tiver várias propriedades que dependem umas das outras de maneira circular. Por exemplo, no cenário Min/Max/Current, você pode optar por ter Minimum e Maximum como configuráveis pelo usuário. Em caso afirmativo, poderá ter de coagir que o Máximo é sempre superior ao Mínimo e vice-versa. Mas se essa coerção estiver ativa, e Máximo coagir ao Mínimo, ela deixará Atual em um estado inconfigurável, porque é dependente de ambos e está restrita ao intervalo entre os valores, que é zero. Então, se Máximo ou Mínimo forem ajustados, Corrente parecerá "seguir" um dos valores, porque o valor desejado de Corrente ainda está armazenado e está tentando alcançar o valor desejado à medida que as restrições são afrouxadas.

Não há nada tecnicamente errado com dependências complexas, mas elas podem ser um pequeno prejuízo de desempenho se exigirem um grande número de reavaliações e também podem ser confusas para os usuários se afetarem diretamente a interface do usuário. Tenha cuidado com a propriedade alterada e coaja retornos de valor e certifique-se de que a coerção que está sendo tentada possa ser tratada da forma mais inequívoca possível, e não "constranga demais".

Usando CoerceValue para cancelar alterações de valor

O sistema de propriedades tratará qualquer CoerceValueCallback que devolva o valor UnsetValue como um caso especial. Este caso especial significa que a mudança de propriedade que resultou na CoerceValueCallback ser chamada deve ser rejeitada pelo sistema de propriedade, e que o sistema de propriedade deve, em vez disso, relatar qualquer valor anterior que a propriedade tinha. Esse mecanismo pode ser útil para verificar se as alterações em uma propriedade que foram iniciadas de forma assíncrona ainda são válidas para o estado atual do objeto e suprimir as alterações, se não. Outro cenário possível é que você pode suprimir seletivamente um valor dependendo de qual componente da determinação do valor da propriedade é responsável pelo valor que está sendo relatado. Para fazer isso, pode-se usar o DependencyProperty passado na função de callback e o identificador de propriedade como entrada para GetValueSource, e depois processar o ValueSource.

Ver também