Devoluciones de llamada y validación de las propiedades de dependencia

En este tema se describe cómo crear propiedades de dependencia utilizando implementaciones personalizadas alternativas para las características relacionadas con propiedades, tales como la determinación de validación, las devoluciones de llamada que se invocan cada vez que cambia el valor efectivo de la propiedad, y la invalidación de posibles influencias externas para la determinación del valor. En este tema también se explican los escenarios donde es apropiado expandir los comportamientos del sistema de propiedades predeterminado mediante estas técnicas.

Este tema contiene las secciones siguientes.

  • Requisitos previos
  • Devoluciones de llamada de validación
  • Devoluciones de llamada de forzado de valores y eventos de cambio de propiedad
  • Escenarios avanzados de forzado y devolución de llamada
  • Temas relacionados

Requisitos previos

En este tema se supone que entiende los escenarios básicos de la implementación de una propiedad de dependencia, así como la aplicación de una propiedad a una propiedad de dependencia personalizada. Vea Propiedades de dependencia personalizadas y Metadatos de las propiedades de dependencia para obtener contexto.

Devoluciones de llamada de validación

Las devoluciones de llamada de validación se pueden asignar a una propiedad de dependencia cuando se registra por primera vez. La devolución de llamada de validación no forma parte de los metadatos de la propiedad; es una entrada directa del método Register. Por consiguiente, una vez creada una devolución de llamada de validación para una propiedad de dependencia, no puede invalidarse mediante una nueva implementación.

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

Las devoluciones de llamada se implementan de modo que se les proporcione un valor de objeto. Devuelven true si el valor proporcionado es válido para la propiedad; de lo contrario, devuelven false. Se supone que la propiedad es del tipo correcto con respecto al tipo registrado en el sistema de propiedades, por lo que normalmente no se comprueba su tipo dentro de las devoluciones de llamada. El sistema de propiedades utiliza las devoluciones de llamada en varias operaciones diferentes. Entre ellas, se incluyen la inicialización de tipos por valores predeterminados, el cambio mediante programación invocando el método SetValue, o los intentos de invalidación de metadatos con el nuevo valor predeterminado proporcionado. Si cualquiera de estas operaciones invoca la devolución de llamada de validación, y devuelve false, entonces se inicia una excepción. El autor de la aplicación debe estar preparado para administrar estas excepciones. Un uso común de las devoluciones de llamada de validación es la validación de valores de enumeración, o la restricción de valores de tipo Integer o Double cuando la propiedad establece medidas que deben ser mayores o iguales que cero.

Las devoluciones de llamada de validación están diseñadas específicamente como validadores de clase, no de instancias. Los parámetros de la devolución de llamada no comunican un objeto DependencyObject concreto para el que se establecen las propiedades que se van a validar. Por consiguiente, las devoluciones de llamada de validación no resultan útiles para aplicar las posibles "dependencias" que podrían influir en el valor de una propiedad, donde el valor específico de la instancia de una propiedad depende de factores tales como los valores específicos de la instancia de otras propiedades, o el estado en tiempo de ejecución.

A continuación, se muestra un ejemplo de código para un escenario de devolución de llamada de validación muy simple: se valida que el valor de una propiedad cuyo tipo primitivo es Double no es PositiveInfinity ni 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));
}

Devoluciones de llamada de forzado de valores y eventos de cambio de propiedad

Las devoluciones de llamada de forzado de valores pasan la instancia de DependencyObject específica para las propiedades, al igual que las implementaciones de PropertyChangedCallback que invoca el sistema de propiedades cada vez que cambia el valor de una propiedad de dependencia. Mediante el uso combinado de estas dos devoluciones de llamada, puede crear una serie de propiedades de elementos de tal forma que los cambios de una propiedad fuercen una conversión o reevaluación de otra propiedad.

Un escenario típico en que se suele usar la vinculación de propiedades de dependencia es aquél en que existe una propiedad controlada por la interfaz de usuario donde el elemento contiene una propiedad para cada valor mínimo y máximo, y una tercera propiedad para el valor real o actual. Aquí, si el máximo se ajustara de tal modo que el valor actual superase el nuevo máximo, sería deseable forzar el valor actual de manera que no fuese mayor que el nuevo máximo, con una relación similar entre los valores mínimo y actual.

A continuación, se muestra un ejemplo de código muy breve con una sola de las tres propiedades de dependencia que ilustran esta relación. En el ejemplo se muestra cómo se registra la propiedad CurrentReading de un conjunto Min/Max/Current de propiedades *Reading. Se utiliza la validación como se indica en la sección anterior.

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

La devolución de llamada de cambio de propiedad para el valor Current se utiliza para reenviar el cambio a otras propiedades dependientes, invocando explícitamente las devoluciones de llamada de forzado de valores registradas para esas otras propiedades:

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

La devolución de llamada de forzado de valores comprueba los valores de las propiedades de las que depende potencialmente la propiedad actual y fuerza el valor actual si es necesario:

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

Los valores predeterminados de las propiedades no se fuerzan.Puede ocurrir que se produzca un valor de propiedad igual al predeterminado si un valor de propiedad sigue teniendo su valor predeterminado inicial, o si se borran otros valores con el método ClearValue.

Las devoluciones de llamada de forzado de valores y de cambio de propiedad forman parte de los metadatos de una propiedad. Por consiguiente, puede cambiar las devoluciones de llamada para una propiedad de dependencia determinada cuando existe en un tipo que se deriva del tipo que posee la propiedad de dependencia, invalidando los metadatos para esa propiedad en el tipo.

Escenarios avanzados de forzado y devolución de llamada

Restricciones y valores deseados

El sistema de propiedades utiliza las devoluciones de llamada de CoerceValueCallback para forzar un valor de conformidad con la lógica que se declara, pero un valor forzado de una propiedad establecida localmente conservará internamente un "valor deseado". Si las restricciones se basan en otros valores de propiedad que pueden cambiar dinámicamente en el transcurso de la duración de la aplicación, también se cambian dinámicamente las restricciones de forzado, con lo que puede suceder que cambie el valor de la propiedad restringida a fin de acercarse lo más posible al valor deseado en virtud de esas nuevas restricciones. El valor se convertirá en el valor deseado si se retiran todas las restricciones. Potencialmente, es posible incluir algunos escenarios de dependencia bastante complicados si tiene varias propiedades dependientes entre sí de manera circular. Por ejemplo, en el escenario Min/Max/Current (con tres valores: mínimo, máximo y actual), podría permitir al usuario establecer el mínimo y el máximo. En este caso, podría ser necesario forzar el valor máximo para que siempre sea mayor que el mínimo, y viceversa. Pero si ese forzado está activo, y el máximo se fuerza al mínimo, dejará el valor actual en un estado que no permite establecerlo, porque depende de ambos valores y está restringido al intervalo comprendido entre ellos, que es cero. A continuación, si se ajustan el máximo y el mínimo, el valor actual parecerá "seguir" a uno de los valores, porque el valor deseado del valor actual sigue estando almacenado e intenta alcanzar el valor deseado cuando se retiran las restricciones.

Técnicamente, no hay nada de malo en establecer dependencias complejas, pero pueden provocar un ligero deterioro del rendimiento si requieren grandes cantidades de reevaluaciones, además de resultar confusas para los usuarios si afectan directamente a la interfaz de usuario. Extreme las precauciones con las devoluciones de llamada de cambio de propiedad y de forzado de valores: asegúrese de que el forzado previsto se pueda tratar del modo menos ambiguo posible y no cree un exceso de restricción.

Utilizar valores de forzado para cancelar cambios de valor

El sistema de propiedades tratará cualquier CoerceValueCallback que devuelva el valor UnsetValue como un caso especial. Este caso especial significa que el sistema de propiedades debe rechazar el cambio de propiedad que ha dado lugar a la llamada a CoerceValueCallback, y que el sistema de propiedades debe comunicar, en su lugar, el valor anterior que tenía la propiedad. Este mecanismo puede ser útil para comprobar si los cambios de una propiedad iniciados de manera asincrónica siguen siendo válidos para el estado actual del objeto y suprimirlos en caso negativo. Otro escenario posible es la supresión selectiva de un valor dependiendo de qué componente de la determinación del valor de la propiedad sea responsable del valor comunicado. Para ello, puede utilizar el objeto DependencyProperty pasado en la devolución de llamada y el identificador de propiedad como entrada para el método GetValueSource y, a continuación, procesar ValueSource.

Vea también

Conceptos

Información general sobre las propiedades de dependencia

Metadatos de las propiedades de dependencia

Propiedades de dependencia personalizadas