2010-02-24 12 views
5

En WPF, ¿qué significa realmente ser una "propiedad de dependencia"?WPF: ¿Qué distingue una propiedad de dependencia de una propiedad de CLR normal?

Leí el Dependency Properties Overview de Microsoft, pero en realidad no se está hundiendo para mí. En parte ese artículo dice:

Los estilos y las plantillas son dos de los principales escenarios de motivación para usar propiedades de dependencia. Los estilos son particularmente útiles para establecer propiedades que definen la interfaz de usuario de la aplicación (UI). Los estilos se suelen definir como recursos en XAML. Los estilos interactúan con el sistema de propiedad porque generalmente contienen "setters" para propiedades particulares, así como también "triggers" que cambian el valor de una propiedad en función del valor en tiempo real de otra propiedad.

Y luego el código de ejemplo es la siguiente:

<Style x:Key="GreenButtonStyle"> 
    <Setter Property="Control.Background" Value="Green"/> 
</Style> 
.... 
<Button Style="{StaticResource GreenButtonStyle}">I am green!</Button> 

Pero ahora no recibo lo que es especial acerca de esto. ¿Implica que cuando configuro Style en el botón para el estilo especificado, estoy configurando Background implícitamente? ¿Es eso el quid de la cuestión?

Respuesta

8

En WPF, ¿qué significa realmente ser una "propiedad de dependencia"?

Para ser una propiedad de dependencia, la propiedad debe estar realmente definida como DependencyProperty, estáticamente, en la clase. El sistema de propiedad de dependencia es muy diferente de una propiedad CLR estándar.

Las propiedades de dependencia se manejan de manera muy diferente. Un tipo define una propiedad de dependencia estáticamente y proporciona un valor predeterminado. El tiempo de ejecución en realidad no genera un valor para una instancia hasta que sea necesario. Esto proporciona un beneficio: la propiedad no existe hasta que se solicite un tipo, por lo que puede tener una gran cantidad de propiedades sin gastos generales.

Esto es lo que hace que la propiedad de trabajo de estilo, pero también es importante para permitir propiedades adjuntas, propiedad "herencia" a través del árbol visual y muchas otras cosas en las que WPF confía.

Por ejemplo, tome la propiedad de dependencia DataContext. Normalmente, establece la propiedad de dependencia DataContext para una ventana o un control de usuario. Todos los controles dentro de esa ventana, de forma predeterminada, "heredan" automáticamente el progenitor DataContext de sus padres, lo que le permite especificar enlaces de datos para los controles. Con una propiedad CLR estándar, necesitaría definir ese DataContext para cada control en la ventana, solo para que el enlace funcione correctamente.

+0

Ah, buena información, gracias. Ok, entonces, ¿cómo funciona la magia, que permite que un control secundario tenga acceso al DataContext de la ventana antecesor? ¿Me puede dar un enlace a una reseña de esto? – Cheeso

+1

@ Cheeso: Busque la "herencia de valor de propiedad" aquí: http://msdn.microsoft.com/en-us/library/ms753391.aspx Hay un poco de esto en esa página ... –

27

Aquí está la explicación de cómo funcionan las propiedades de dependencia que siempre deseé que alguien hubiera escrito para mí. Es incompleto y muy posiblemente incorrecto, pero te ayudará a desarrollar una comprensión suficiente de ellos que podrás comprender la documentación que lees.

Las propiedades de dependencia son valores similares a propiedades que se obtienen y configuran a través de los métodos de la clase DependencyObject. Pueden (y generalmente lo hacen) parecerse mucho a las propiedades CLR, pero no lo son. Y esto llega a la primera cosa confusa acerca de ellos. Una propiedad de dependencia en realidad está formada por un par de componentes.

He aquí un ejemplo:

Document es una propiedad del objeto RichTextBox. Es una propiedad CLR real. Es decir, tiene un nombre, un tipo, un captador y un colocador, al igual que cualquier otra propiedad de CLR. Pero a diferencia de las propiedades "normales", la propiedad RichTextBox no solo obtiene y establece un valor privado dentro de la instancia. Internamente, se implementa como esto:

public FlowDocument Document 
{ 
    get { return (FlowDocument)GetValue(DocumentProperty); } 
    set { SetValue(DocumentProperty, value); } 
} 

Al configurar Document, el valor que ha pasado en se pasa a SetValue, junto con DocumentProperty. ¿Y qué es que? ¿Y cómo obtiene su valor GetValue? ¿Y por qué?

Primero, qué. Hay una propiedad estática definida en RichTextBox llamada DocumentProperty. Cuando esta propiedad se declara, se hace así:

public static DependencyProperty DocumentProperty = DependencyProperty.Register(
    "Document", 
    typeof(FlowDocument), 
    typeof(RichTextBox)); 

El método Register, en este caso, le dice al sistema de dependencia propiedad que RichTextBox - el tipo, no es el caso - tiene ahora una propiedad de dependencia llamado Document de tipo FlowDocument. Este método almacena esta información ... en alguna parte. Donde, exactamente, hay un detalle de implementación que está oculto para nosotros.

Cuando el definidor de la propiedad Document llama SetValue, el método SetValue observa el argumento DocumentProperty, verifica que en realidad es una propiedad que pertenece a RichTextBox y que value es el tipo correcto, y luego almacena su nuevo valor ... algun lado. La documentación para DependencyObject es tímida en este detalle de implementación, porque realmente no necesita saberlo. En mi modelo mental de cómo funciona esto, supongo que hay una propiedad de tipo Dictionary<DependencyProperty, object> que es privada para DependencyObject, por lo que las clases derivadas (como RichTextBox) no pueden verla pero GetValue y y SetValue pueden actualizarla. Pero quién sabe, tal vez está escrito en pergamino por los monjes.

En cualquier caso, este valor ahora es lo que se denomina un "valor local", lo que significa que es un valor que es local para este RichTextBox específico, al igual que una propiedad común.

El punto de todo esto es:

  1. código CLR no necesita saber que una propiedad es una propiedad de dependencia. Se ve exactamente como cualquier otra propiedad. Usted puede llamar al GetValue y SetValue para obtenerlo y configurarlo, pero a menos que esté haciendo algo con el sistema de propiedad de la dependencia, probablemente no sea necesario.
  2. A diferencia de una propiedad normal, algo que no sea el objeto al que pertenece puede involucrarse para obtenerlo y configurarlo. (Podría hacer esto con la reflexión, concebiblemente, pero la reflexión es lenta. Buscar cosas en los diccionarios es rápido.)
  3. Este algo, que es el sistema de propiedad de la dependencia, se ubica esencialmente entre un objeto y sus propiedades de dependencia. Y puede hacer todo tipo de cosas.

¿Qué tipo de cosas? Bueno, veamos algunos casos de uso.

Encuadernación. Cuando se vincula a una propiedad, tiene que ser una propiedad de dependencia. Esto se debe a que el objeto Binding no establece propiedades en el destino, sino que llama al SetValue en el objeto de destino.

Estilos. Cuando establece la propiedad de dependencia de un objeto en un nuevo valor, SetValue le dice al sistema de estilo que lo ha hecho. Así es como funcionan los desencadenantes: no descubren que el valor de una propiedad ha cambiado a través de la magia, el sistema de propiedad de la dependencia les dice.

Recursos dinámicos. Si escribe XAML como Background={DynamicResource MyBackground}, puede cambiar el valor del recurso MyBackground y se actualizará el fondo del objeto que hace referencia a él. Esto tampoco es magia; el recurso dinámico llama al SetValue.

Animaciones. Las animaciones funcionan manipulando valores de propiedad. Esos tienen que ser propiedades de dependencia, porque la animación llama al SetValue para obtenerlos.

Cambiar la notificación. Cuando registra una propiedad de dependencia, también puede especificar una función a la que llamará SetValue cuando establezca el valor de la propiedad.

Herencia de valor. Cuando registra una propiedad de dependencia, puede especificar que participe en la herencia de valor de propiedad. Cuando llama al GetValue para obtener el valor de la propiedad de dependencia de un objeto, GetValue busca para ver si hay un valor local. Si no lo hay, atraviesa la cadena de objetos principales mirando sus valores locales para esa propiedad.

Así es como puedes configurar el FontFamily en un Window y mágicamente (estoy usando esa palabra mucho) cada control en la ventana usa la nueva fuente. Además, es cómo se pueden tener cientos de controles en una ventana sin que cada uno de ellos tenga una variable de miembro FontFamily para rastrear su fuente (ya que no tienen valores locales) pero aún puede establecer FontFamily en cualquier control (debido al diccionario de valores ocultos de seekrit que tiene cada DependencyObject).

+1

Esto es muy importante: " Podrías hacer esto con la reflexión, pero la reflexión es lenta. Buscar cosas en los diccionarios es rápido ". Este y el evento modificado son dos buenas razones para las propiedades de dependencia. –

+1

Estoy de acuerdo: Si tuviera que volver a escribir esto, enfatizaría mucho más los problemas de rendimiento en el juego. La mayor parte de la rareza y complejidad del sistema de propiedad de dependencia proviene del hecho de que si no fuera rápido, rompería WPF. Además, agregaría herencia de valor a su lista de buenas razones. La herencia de valor es enormemente importante para WPF. Y valor herencia + notificación de cambio = magia. –

+0

¡La mejor explicación que he visto para este tema! – Sandeep

9

Puede ser útil comprender qué problema está intentando resolver la propiedad de dependencia.

Si ponemos el modelo Enlace, Animación y Evento de cambio a un lado, como se ha discutido en otras respuestas, el beneficio es el uso de memoria y escalabilidad para alojar muchos objetos WPF en una ventana.

Si una ventana contiene 1000 Label objetos con cada Label objeto que tiene la costumbre Foreground, Background, FontFamily, FontSize, FontWeight, etc., entonces tradicionalmente este consumirían memoria porque cada propiedad tendría un campo privado de respaldo para almacenar el valor .

La mayoría de las aplicaciones cambiarán solo unas pocas propiedades, la mayoría de las cuales quedarán en sus valores predeterminados.Básicamente es una información muy desperdiciada y redundante (cada objeto solo tiene los mismos valores predeterminados en la memoria)

Aquí es donde las propiedades de dependencia son diferentes.

// Lets register the Dependency Property with a default value of 20.5 
public static readonly DependencyProperty ColumnWidthProperty = 
    DependencyProperty.Register("ColumnWidth", typeof(double), typeof(MyWPFControl), new UIPropertyMetadata(20.5, ColWitdhPropChanged)); 

public double ColumnWidth 
{ 
    get { return (double)GetValue(ColumnWidthProperty); } 
    set { SetValue(ColumnWidthProperty, value); } 
} 

No hay campo de respaldo privado. Cuando se registra la propiedad de dependencia, se puede especificar un valor predeterminado. Por lo tanto, en la mayoría de los casos, el valor devuelto desde GetValue es el valor predeterminado que solo se ha almacenado una vez para cubrir todas las instancias del objeto Label en todas las ventanas de su aplicación.

Cuando se establece una propiedad de dependencia utilizando SetValue, almacena el valor no predeterminado en una colección identificada por la instancia del objeto, que se devolverá en todas las llamadas posteriores GetValue.

Por lo tanto, este método de almacenamiento solo consumirá memoria para las propiedades de los objetos WPF que han cambiado desde el valor predeterminado. es decir, solo las diferencias con respecto al valor predeterminado.

0

Una diferencia simple/fundamental - Notificación de cambio: los cambios en las propiedades de dependencia se reflejan/actualizan en la interfaz de usuario en los cambios, mientras que las propiedades de CLR no lo hacen.

<Window x:Class="SampleWPF.MainWindow" 
     x:Name="MainForm" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:local="clr-namespace:SampleWPF" 
     Title="About WPF Unleashed" SizeToContent="WidthAndHeight" 
     Background="OrangeRed" 
     > 
    <StackPanel DataContext="{Binding ElementName=MainForm}"> 
     <!-- Bind to Dependency Property --> 
     <Label Name="txtCount1" FontWeight="Bold" FontSize="20" Content="{Binding ElementName=MainForm, Path=Count1, Mode=OneWay}" /> 

     <!-- Bind to CLR Property --> 
     <Label Name="txtCount2" Content="{Binding ElementName=MainForm, Path=Count2, Mode=OneWay}"></Label> 

     <!-- Bind to Dependency Property (Using DataContext declared in StackPanel) --> 
     <Label Name="txtCount3" FontWeight="Bold" FontSize="20" Content="{Binding Count1}" /> 

     <!-- Child Control binding to Dependency Property (Which propagates down element tree) --> 
     <local:UserControl1 /> 

     <!-- Child Control binding to CLR Property (Won't work as CLR properties don't propagate down element tree) --> 
     <local:UserControl2 /> 

     <TextBox Text="{Binding ElementName=txtCount1, Path=Content}" ></TextBox> 
     <TextBox Text="{Binding ElementName=txtCount2, Path=Content}" ></TextBox> 

     <Button Name="btnButton1" Click="btnButton1_Click_1">Increment1</Button> 
     <Button Name="btnButton2" Click="btnButton1_Click_2">Increment2</Button> 
    </StackPanel> 
</Window> 

<UserControl x:Class="SampleWPF.UserControl1" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"> 
    <StackPanel> 
     <Label Content="{Binding Count1}" ></Label> 
     <!-- 
     <Label Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=Count1}"></Label> 
     --> 
    </StackPanel> 
</UserControl> 

<UserControl x:Class="SampleWPF.UserControl2" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"> 
    <StackPanel> 
     <Label Content="{Binding Count2}" ></Label> 
     <!-- 
     <Label Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=Count2}"></Label> 
     --> 
    </StackPanel> 
</UserControl> 

Y el código detrás de aquí (Declarar el CLR y propiedad de dependencia):

using System; 
    using System.Collections.Generic; 
    using System.Linq; 
    using System.Text; 
    using System.Threading.Tasks; 
    using System.Windows; 
    using System.Windows.Controls; 
    using System.Windows.Data; 
    using System.Windows.Documents; 
    using System.Windows.Input; 
    using System.Windows.Media; 
    using System.Windows.Media.Imaging; 
    using System.Windows.Navigation; 
    using System.Windows.Shapes; 
    namespace SampleWPF 
    { 
     /// <summary> 
     /// Interaction logic for MainWindow.xaml 
     /// </summary> 
     public partial class MainWindow : Window 
     { 
      public static readonly DependencyProperty Count1Property; 
      private int _Count2 = 2; 
      public int Count2 
      { 
       get { return _Count2; } 
       set { _Count2 = value; } 
      } 
      public MainWindow() 
      { 
       return; 
      } 
      static MainWindow() 
      { 
       // Register the property 
       MainWindow.Count1Property = 
        DependencyProperty.Register("Count1", 
        typeof(int), typeof(MainWindow), 
        new FrameworkPropertyMetadata(1, 
        new PropertyChangedCallback(OnCount1Changed))); 
      } 
      // A .NET property wrapper (optional) 
      public int Count1 
      { 
       get { return (int)GetValue(MainWindow.Count1Property); } 
       set { SetValue(MainWindow.Count1Property, value); } 
      } 
      // A property changed callback (optional) 
      private static void OnCount1Changed(
       DependencyObject o, DependencyPropertyChangedEventArgs e) { 

      } 
      private void btnButton1_Click_1(object sender, RoutedEventArgs e) 
      { 
       Count1++; 
      } 
      private void btnButton1_Click_2(object sender, RoutedEventArgs e) 
      { 
       Count2++; 
      } 
     } 
    } 

Otra característica proporcionada por las propiedades de dependencia es el valor de herencia - valor establecido en los elementos de nivel superior se propaga hacia abajo del árbol de elementos - en siguiente ejemplo tomado de http://en.csharp-online.net, Tamaño de Letra y FontStyle declarados en la etiqueta de "ventana" se aplica a todos los elementos secundarios debajo:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    Title="About WPF Unleashed" SizeToContent="WidthAndHeight" 
    FontSize="30" FontStyle="Italic" 
    Background="OrangeRed"> 
    <StackPanel> 
    <Label FontWeight="Bold" FontSize="20" Foreground="White"> 
     WPF Unleashed (Version 3.0) 
    </Label> 
    <Label>© 2006 SAMS Publishing</Label> 
    <Label>Installed Chapters:</Label> 
    <ListBox> 
     <ListBoxItem>Chapter 1</ListBoxItem> 
     <ListBoxItem>Chapter 2</ListBoxItem> 
    </ListBox> 
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> 
     <Button MinWidth="75" Margin="10">Help</Button> 
     <Button MinWidth="75" Margin="10">OK</Button> 
    </StackPanel> 
    <StatusBar>You have successfully registered this product.</StatusBar> 
    </StackPanel> 
</Window> 

Referencias: http://www.codeproject.com/Articles/29054/WPF-Data-Binding-Part-1 http://en.csharp-online.net/WPF_Concepts%E2%80%94Property_Value_Inheritance

Cuestiones relacionadas