Archivo

Posts Tagged 'propiedad de dependencia'

DependencyProperty: Manejo de validación, la coerción y el Cambio (Parte III: La propiedad de dependencia de fragmentos de código)

15 de noviembre 2008 comentarios No

Introducción

Esta es la última parte de la serie de tres partes sobre cómo implementar la propiedad de dependencia con la validación, la coerción y la prueba completa en WPF y Silverlight. En la Parte I , he implementado una propiedad de dependencia simple y depurado a través de demostrar cómo la propiedad de dependencia trabaja en WPF y el patrón común para la aplicación propiedad de dependencia en WPF. En parte , yo hice lo mismo en Silverlight. Debido a que sólo es compatible con Silverlight PropertyChangedCallback, pero no CoerceValueCallback ValidateValueCallback y, por lo que la validación, la coacción y la manipulación de todos los cambios tienen que ser aplicadas con PropertyChangedCallback. Esto hace que PropertyChangedCallback se llama de forma recursiva cuando la validación o la coacción cambia el valor efectivo de la propiedad de dependencia. Además de otras limitaciones del sistema de propiedad de Silverlight, que puede ser muy difícil de implementar correctamente la propiedad de dependencia. Este post voy a mostrar algunos comportamientos oscuros de controles de Silverlight RangeBase y sus causas para demostrar aún más lo difícil que puede ser. Y por fin, le daré un fragmento de código que implementa el patrón completo de la aplicación de propiedades de dependencia que discutí en parte , como una recompensa a aquellos que leen a través de la serie :-)

Silverlight Controles RangeBase

Información general

ProgressBar ScrollBar y Slider hereda RangeBase, que implementa mínimo, máximo y valor las propiedades de dependencia, con la restricción de la coacción que el mínimo <= Valor <= máximo.

RangeBase

Esto suena simple :-) , Pero el comportamiento de los controles RangeBase "puede ser muy extraño. Tome algunos ejemplos:

  1. <Slider X:Name="sl"/> le da un control deslizante con el mínimo = 0, valor = 0, y Máximo = 10, mientras que <Slider x:Name="sl2" Minimum="-1" /> produce un control deslizante con mínimo = -1, valor = 0, y Máximo = 0.
  2. Haga clic en el pulgar de arrastre deslizante sl2 para hacer SL2 en el enfoque, presione la derecha o la flecha hacia arriba, a continuación, cambiar sl2.Maximum a un número positivo 10, sl2.Value mágicamente cambia de 0 a 0,1.
  3. Conjunto de propiedades sl2 como valor a Double.NaN, un valor no válido, una excepción a lanzar, pero sl2.Value se cambia a Double.NaN todos modos, y ningún caso ValueChanged es despedido.
Código Fuente

A continuación se muestra el código que vamos a depurar y probar:

  • Page.xaml:
  • Page.xaml.cs:
  •  Slider2 : Slider { protected override void OnMinimumChanged( double oldMinimum, double newMinimum) { base .OnMinimumChanged(oldMinimum, newMinimum); } protected override void OnMaximumChanged( double oldMaximum, double newMaximum) { base .OnMaximumChanged(oldMaximum, newMaximum); } protected override void OnValueChanged( double oldValue, double newValue) { base .OnValueChanged(oldValue, newValue); } protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e) { base .OnKeyDown(e); } } public partial class Page : UserControl { public Page() { InitializeComponent(); } // the button click event handler provides a chance to break into debugger // and experiment with RangeBase validation, coercion and change handling. private void btn_Click( object sender, RoutedEventArgs e) { } } } {/ / Subclase Slider para romperse con facilidad en el cambio de propiedad y los principales eventos de la clase pública por Slider2:.. Deslizante {protected override void OnMinimumChanged (doble oldMinimum, doble newMinimum) {base OnMinimumChanged (oldMinimum, newMinimum);} protected void anular OnMaximumChanged (doble oldMaximum, doble newMaximum) {base OnMaximumChanged (oldMaximum, newMaximum).} protected override void OnValueChanged (doble oldValue, doble nuevoValor) {base OnValueChanged (oldValue, nuevoValor).} OnKeyDown protected override void (System.Windows.Input.KeyEventArgs e) {. OnKeyDown de base (e);}} Page public partial class: UserControl {página pública () {InitializeComponent ();} / / el clic en el botón controlador de eventos proporciona la oportunidad de entrar en depurador / / y experimentar con RangeBase validación , la coerción y la gestión del cambio. private void btn_Click (object sender, RoutedEventArgs e) {}}} 

Depuración y Experimental

Construir y correr por encima de sencilla aplicación Silverlight, verá a continuación captura de pantalla. Por favor, note la diferencia ubicación pulgar entre los dos cursores:

SilverlightApplication1

Haga clic en la Semana Santa! botón para entrar en su controlador de eventos en Visual Studio, compruebe el valor del control deslizante SL y SL2:

  • <Slider X:Name="sl"/>: sl tiene el valor correcto de Minimo = Valor = 0 y máximo = 10, mientras que <Slider x:Name="sl2" Minimum="-1"/> sl2 ha = mínimo -1, pero Máximo = Valor = 0;

imagen

Para los que leen la segunda parte y ahora está familiarizado con el modelo de implementación, esta es la causa del extraño comportamiento de los SL2:

  • RangeBase hojas _initialMax, _initialVal, _requestedMax, _requestedVal ser inicializado por CLR al valor por defecto 0. Esto está bien para la propiedad del valor, ya que por defecto a cero de todos modos, pero es un error de la propiedad máxima, que por defecto a 1 en su llamamiento DependencyProperty.Register y utiliza su valor por defecto su valor efectivo:

RangeBase no inicializar sus campos

  • Establecer sl2.Minimum a -1 provoca OnMinimumPropertyChanged -> CoerceMaximum, que utiliza el CLR inicia _requestedMax para coaccionar a Máximo. Desde _requestedMax es 0 en lugar del actual valor eficaz máximo de 1 y 0 pasa a ser mayor que el valor mínimo actual es de -1, por lo que es el cambio máximo en 0:

RangeBase.CoerceMaximum

  • La razón <Slider x:Name="sl"> tiene un valor máximo de 10 en lugar de 1 por defecto es que su plantilla por defecto tiene un <Setter Property="Maximum" Value="10"/>. Esta línea también se corrige el problema que _requestedMax no se inicializa. Esta es la razón por sl._requestedMax es de 10 en captura de pantalla anterior.

Deslizador de plantilla por defecto

A continuación, establezca un punto de quiebre en cada función, que la aplicación se ejecute. Seleccione el pulgar sl2 a hacerlo en el enfoque, presione tecla de flecha hacia la derecha o arriba, se rompe en manejador de evento OnKeyDown. Compruebe el valor de SL2: _requestedVal es 0; paso a través de base.OnKeyDown (e); comprobar sl2 nuevo: _requestedVal se convierte en 0,1. Esto se debe a base.OnKeyDown maneja la carrera de flecha derecha / arriba clave y trata de aumentar el valor de SmallIncrement de 0,1, lo que provoca OnValuePropertyChanged -> CoerceValue, que luego se coacciona a valor de nuevo a 0, ya que Máximo es 0, pero recuerda la petición de incremento por ajuste de _requestedVal a 0,1.

imagen

Si bien todavía dentro de la ventana inmediata, establezca sl2.Maximum a 10. Esto desencadena OnMaxiumPropertyChanged:

imagen

que a su vez desencadena CoerceValue, que comprueba _requestedVal, cuyo valor es ahora 0,1. Desde 0,1 en el rango de [-1, 10], por lo que de valor se cambia a 0,1:

imagen

Por último, vamos a poner valor a Double.NaN. Esto desencadena una ArgumentException, como se esperaba:

imagen

Pero el valor se cambia a Double.NaN todos modos, y ningún caso ValueChanged se dispara debido a la ArgumentException:

imagen

Propiedad de dependencia de fragmento de código

A estas alturas probablemente tiene la idea de que es muy dificil de implementar la propiedad de dependencia con la validación, la coerción y el cambio de manejo correctamente en Silverlight. Para hacer esto más fácil, a continuación es un fragmento de código que proporciona la mayor parte del código para el patrón de la aplicación antes mencionados. Este fragmento de código fue escrito originalmente por Ted Glaza, y he modificado para ofrecer el modelo completo. Usted puede quitar cosas que no necesita de las propiedades de dependencia más simple, como los que no necesitan la validación, la coacción o la gestión del cambio. Que sigue siendo necesario para llenar su propia validación, la coerción, la manipulación y la lógica de cambio de funciones, como la modificación de la propiedad IsValid $ $, obligar a la propiedad $ $, en la propiedad $ $ PropertyChanged propiedad, o $ en $ etc cambiado, y añadir una definición única de _nestLevel private int. Pero al menos usted no tiene que preocuparse por la mayor parte de las diferencias entre el PMA y los sistemas de propiedad de Silverlight, y centrarse sólo en la validación, la coerción y la lógica de cambio de manejo de sí mismos.

 > < Shortcut > sdp </ Shortcut > < Description > Code snippet for a dependency property with validation, coercion and changed event. </ Description > < Author > Ning Propiedad de dependencia </ title> <> Acceso directo SDP </ Shortcut> <> Descripción fragmento de código para una propiedad de dependencia con la validación, la coerción y el evento cambiado. </ Description> <Autor> Ning  > < Default > MyProperty </ Default > </ Literal > < Literal > < ID > type </ ID > < ToolTip > Property type </ ToolTip > < Default > object </ Default > </ Literal > < Literal > < ID > defaultValue </ ID > < ToolTip > Default name </ ToolTip> <> Default MyProperty </ Default> </ literal> <Literal> <ID> tipo </ ID> <ToolTip> Tipo de propiedad </ ToolTip> <Default object> </ Default> </ literal> < Literal> <ID> defaultValue </ ID> ToolTip <> Default  > /// Gets or sets the value of $property$ dependency property. propiedad pública $ tipo $ $ $ / / / <summary> / / / Obtiene o establece el valor de la propiedad $ $ propiedad de dependencia.  > public $type$ $property$ { get { return ($type$)GetValue($property$Property); } set { SetValue($property$Property, value); } } /// < summary > /// Identifies the $property$ dependency property. / / / </ Summary> propiedad pública $ tipo $ $ $ {get {return ($ type $) GetValue ($ $ de propiedad de propiedad);} conjunto {SetValue ($ $ de propiedad de propiedad, valor);}} / / / < summary> / / / Identifica la propiedad $ $ propiedad de dependencia.  > public static readonly $SystemWindowsDependencyProperty$ $property$Property = $SystemWindowsDependencyProperty$.Register( "$property$", typeof($type$), typeof($classname$), new $SystemWindowsPropertyMetadata$($defaultValue$, On$property$PropertyChanged)); /// < summary > /// $property$Property property changed handler. / / / </ Summary> public static readonly SystemWindowsDependencyProperty $ $ $ $ de propiedad de propiedad = $ $ SystemWindowsDependencyProperty. Registro ("$ $ la propiedad", typeof ($ type $), typeof ($ classname $), nuevo $ SystemWindowsPropertyMetadata $ ($ defaultValue $, $ A $ PropertyChanged propiedad)) / / / <summary> / / / $ $ de propiedad de propiedad de propiedad cambió de controlador.  > /// < param name ="d" > $classname$ that changed its $property$. </ param > /// < param name ="e" > Event arguments. </ param > private static void On$property$PropertyChanged($SystemWindowsDependencyObject$ d, $SystemWindowsDependencyPropertyChangedEventArgs$ e) { $classname$ source = ($classname$)d; $type$ newValue = ($type$)e.NewValue; $type$ oldValue = ($type$)e.OldValue; // validate newValue if (!IsValid$property$(newValue)) { // revert back to e.OldValue source._nestLevel++; source.SetValue(e.Property, e.OldValue); source._nestLevel--; // throw ArgumentException throw new ArgumentException("Invalid $property$Property value", "e"); } if (source._nestLevel == 0) { // remember initial state source._initial$property$ = oldValue; source._requested$property$ = newValue; } source._nestLevel++; // coerce newValue $type$ coercedValue = ($type$)Coerce$property$(d, e.NewValue); if (newValue != coercedValue) { // always set $property$Property to coerced value source.$property$ = coercedValue; } source._nestLevel--; if (source._nestLevel == 0 && source.$property$ != source._initial$property$) { // fire changed event only at root level and when there is indeed a change source.On$property$Changed(oldValue, source.$property$); } } /// < summary > /// $property$Property validation handler. / / / </ Summary> / / / <param name = "d"> $ $ classname que cambió su propiedad de $ $. </ Param> / / / <param name = "e"> argumentos del evento. </ Param> private void estática en la fuente $ $ PropertyChanged la propiedad ($ SystemWindowsDependencyObject $ d, $ SystemWindowsDependencyPropertyChangedEventArgs $ e) {$ classname $ = ($ classname $) d, $ tipo $ nuevoValor = ($ type $) e.NewValue, $ type $ oldValue = ($ type $) e.OldValue; / / validar si nuevoValor {/ / volver a e.OldValue source._nestLevel + + ($ IsValid propiedad $ (nuevoValor)!); source.SetValue (e.Property, e.OldValue) ; source._nestLevel--; / / lanzar ArgumentException ArgumentException lanzar nuevos ("no válido de propiedad $ $ Valor de la propiedad", "e");} if (source._nestLevel == 0) {/ / recordar el estado inicial de $ source._initial propiedad $ = oldValue; source._requested propiedad $ $ = nuevoValor;} source._nestLevel + +; / / nuevoValor coaccionar a $ type $ coercedValue = ($ type $) obligar a la propiedad $ $ (d, e.NewValue); if (= nuevoValor coercedValue ) {/ / siempre se establece la propiedad $ $ la propiedad a la fuente de valor de la propiedad forzadas $ $ = coercedValue;.} source._nestLevel--;. if (source._nestLevel == 0 & & $ fuente de la propiedad $ = $ source._initial propiedad $ ) {/ / el fuego cambió único evento a nivel de la raíz y cuando de hecho existe un cambio de propiedad source.On $ $ ha cambiado la propiedad (oldValue, la fuente de $ $).}} / / / <summary> / / / $ $ de propiedad de propiedad la validación del controlador.  > /// < param name ="value" > New value of $property$Property. </ param > /// < returns > /// Returns true if value is valid for $property$Property, false otherwise. / / / </ Summary> / / / <param name = "valor"> Nuevo valor de los bienes propiedad de $ $. </ Param> / / / <vuelve> / / / Devuelve true si el valor es válido para la propiedad $ $ la propiedad , en caso contrario.  > private static bool IsValid$property$($type$ value) { return true; } /// < summary > /// $property$Property coercion handler. / / / </ Returns> private static bool IsValid propiedad $ $ ($ type $ valor) {return true;} / / / <summary> / / / $ $ manejador de propiedad de propiedad coerción.  > /// < param name ="d" > $classname$ that changed its $property$. </ param > /// < param name ="value" > Event arguments. </ param > /// < returns > /// Coerced effective value of $property$Property from input parameter value. / / / </ Summary> / / / <param name = "d"> $ $ classname que cambió su propiedad de $ $. </ Param> / / / <param name = "valor"> argumentos del evento. </ Param> / / / <vuelve> / / / Obligado valor efectivo de los bienes propiedad de $ $ de valor de parámetro de entrada.  > private static object Coerce$property$($SystemWindowsDependencyObject$ d, object value) { $classname$ source = ($classname$)d; $type$ newValue = ($type$)value; return newValue; } /// < summary > /// $property$Property changed event. / / / </ Returns> coaccionar objeto estático privado propiedad $ $ ($ SystemWindowsDependencyObject $ d, valor del objeto) {$ classname $ source = ($ classname $) d, $ tipo $ nuevoValor = ($ type $) valor; nuevoValor retorno ;} / / / <summary> / / / $ $ de propiedad Propiedad evento de cambio.  > public event RoutedPropertyChangedEventHandler < $type$ > $property$Changed; /// < summary > /// Called by On$property$PropertyChanged static method to fire $property$Changed event. / / / </ Summary> RoutedPropertyChangedEventHandler acto público <$ type $> $ $ la propiedad ha cambiado, / / / <summary> / / / Llamado por el método $ $ PropertyChanged propiedad estática a desencadenar el evento $ $ la propiedad ha cambiado.  > /// < param name ="oldValue" > The old value of $property$. </ param > /// < param name ="newValue" > The new value of $property$. </ param > protected virtual void On$property$Changed($type$ oldValue, $type$ newValue) { RoutedPropertyChangedEventArgs < $type$ > e = new RoutedPropertyChangedEventArgs < $type$ > (oldValue, newValue); if ($property$Changed != null) { $property$Changed(this, e); } } /// < summary > /// Cached previous value of $property$Property. / / / </ Summary> / / / <param name = "oldValue"> El antiguo valor de la propiedad $ $. </ Param> / / / <param name = "nuevoValor"> El nuevo valor de la propiedad $ $. < / param> protected void virtual En la propiedad $ $ Cambio ($ type $ oldValue, $ type $ nuevoValor) {RoutedPropertyChangedEventArgs <$ type $> e = new RoutedPropertyChangedEventArgs <$ type $> (oldValue, nuevoValor); if ($ $ ha cambiado la propiedad ! = null) {$ $ ha cambiado de propiedad (esto, e);}} / / / <summary> / / / caché valor anterior de la Propiedad $ $ la propiedad.  > private $type$ _initial$property$ = $defaultValue$; /// < summary > /// Cached originally requested value of $property$Property by user. / / / </ Summary> private $ tipo $ $ _initial propiedad $ = $ defaultValue $ / / / <summary> / / / en caché el valor solicitado inicialmente de la Propiedad $ $ la propiedad por el usuario.  > private $type$ _requested$property$; #endregion public $type$ $property$ ]] > </ Code > </ Snippet > </ CodeSnippet > </ CodeSnippets > / / / </ Summary> private $ tipo $ $ _requested propiedad $, # endregion pública $ tipo de propiedad $ $ $]]> </ CODE> </ Snippet> </ SeccionDeCodigo> </ CodeSnippets> 

Conclusión

Espero que la serie le ayudó a entender e implementar las propiedades de dependencia en WPF y Silverlight. WPF y Silverlight son grandes plataformas, y será fundamental para el desarrollo de software, posiblemente más de lo que hizo antes de Win32.

Como siempre, comentarios, sugerencias, correcciones son bienvenidas. Gracias!