2010-02-24 6 views
5

In WPF, was bedeutet es wirklich, eine "Abhängigkeitseigenschaft" zu sein?WPF: Was unterscheidet eine Abhängigkeitseigenschaft von einer regulären CLR-Eigenschaft?

Ich lese Microsofts Dependency Properties Overview, aber es ist nicht wirklich für mich sinken. Teil dieses Artikels sagt:

Stile und Vorlagen sind zwei der wichtigsten motivierenden Szenarien für die Verwendung von Abhängigkeitseigenschaften. Stile sind besonders nützlich zum Festlegen von Eigenschaften, die die Benutzeroberfläche der Anwendung definieren. Stile werden normalerweise als Ressourcen in XAML definiert. Stile interagieren mit dem Eigenschaftensystem, da sie normalerweise "Setter" für bestimmte Eigenschaften sowie "Auslöser" enthalten, die einen Eigenschaftswert basierend auf dem Echtzeitwert für eine andere Eigenschaft ändern.

Und dann der Beispielcode ist dies:

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

Aber ich bin nicht immer, was über diese besondere ist. Bedeutet es nur, dass, wenn ich Style auf den Knopf zum gegebenen Stil setze, dass ich implizit Background implizit setze? Ist das der Kern davon?

Antwort

8

In WPF, was bedeutet es wirklich, eine "Abhängigkeitseigenschaft" zu sein?

Um eine Abhängigkeitseigenschaft zu sein, muss die Eigenschaft statisch in der Klasse als DependencyProperty definiert sein. Das Abhängigkeitseigenschaftssystem unterscheidet sich erheblich von einer Standard-CLR-Eigenschaft.

Die Abhängigkeitseigenschaften werden jedoch sehr unterschiedlich behandelt. Ein Typ definiert eine Abhängigkeitseigenschaft statisch und stellt einen Standardwert bereit. Die Laufzeit generiert tatsächlich keinen Wert für eine Instanz, bis sie benötigt wird. Dies bietet einen Vorteil - die Eigenschaft existiert erst, wenn sie für einen Typ angefordert wird, sodass Sie eine große Anzahl von Eigenschaften ohne Overhead haben können.

Dies ist, was das Styling funktioniert Eigenschaft, ist aber auch wichtig, um angefügte Eigenschaften, Eigenschaft "Vererbung" durch die visuelle Struktur, und viele andere Dinge, auf die WPF angewiesen ist, zu ermöglichen.

Nehmen Sie zum Beispiel die DataContext Abhängigkeitseigenschaft. In der Regel legen Sie die DataContext Abhängigkeitseigenschaft für ein Fenster oder ein UserControl fest. Alle Steuerelemente in diesem Fenster "erben" automatisch die DataContext-Eigenschaft des übergeordneten Elements, wodurch Sie Datenbindungen für Steuerelemente angeben können. Bei einer CLR-Standardeigenschaft müssten Sie diesen DataContext für jedes Steuerelement im Fenster definieren, um die korrekte Funktionsweise zu gewährleisten.

+0

Ah, gute Informationen, danke. Ok, wie funktioniert die Magie, die es einem Kind-Steuerelement ermöglicht, auf den DataContext des Vorfahren-Fensters zuzugreifen? Kannst du mir einen Link zu diesem Artikel geben? – Cheeso

+1

@Cheeso: Suche nach "Wertvererbung von Eigenschaften" hier: http://msdn.microsoft.com/en-us/library/ms753391.aspx Es gibt ein bisschen auf dieser Seite ... –

27

Hier ist die Erklärung für die Funktionsweise von Abhängigkeitseigenschaften, die ich mir immer gewünscht hätte, dass jemand für mich geschrieben hat. Es ist unvollständig und möglicherweise falsch, aber es wird Ihnen helfen, genug Verständnis für sie zu entwickeln, dass Sie in der Lage sein werden, die Dokumentation, die Sie lesen, zu erfassen.

Abhängigkeitseigenschaften sind eigenschaftsähnliche Werte, die über Methoden der Klasse DependencyObject abgerufen und festgelegt werden. Sie können (und tun es auch) sehr ähnlich wie CLR-Eigenschaften aussehen, sind es aber nicht. Und das führt zu der ersten verwirrenden Sache über sie. Eine Abhängigkeitseigenschaft besteht tatsächlich aus ein paar Komponenten.

Hier ist ein Beispiel:

Document eine Eigenschaft des RichTextBox Objekt ist. Es ist eine echte CLR-Eigenschaft. Das heißt, es hat genau wie jede andere CLR-Eigenschaft einen Namen, einen Typ, einen Getter und einen Setter. Im Gegensatz zu "normalen" Eigenschaften erhält die Eigenschaft RichTextBox jedoch nicht nur einen privaten Wert in der Instanz und legt diesen fest. Intern ist es wie folgt umgesetzt:

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

Wenn Sie Document gesetzt, der Wert, den Sie in weitergegeben SetValue geben wird, zusammen mit DocumentProperty. Und was ist dass? Und wie bekommt GetValue seinen Wert? Und warum?

Zuerst das was. Es gibt eine statische Eigenschaft, die auf RichTextBox mit dem Namen DocumentProperty definiert ist. Wenn diese Eigenschaft deklariert ist, wird es so gemacht:

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

Die Register Verfahren, in diesem Fall sagen die Abhängigkeitseigenschaft System, das RichTextBox - die Art, die nicht die Instanz - jetzt eine Abhängigkeitseigenschaft hat Document des Typs mit dem Namen FlowDocument. Diese Methode speichert diese Informationen ... irgendwo. Wo genau ist ein Implementierungsdetail, das vor uns verborgen ist?

Wenn der Setter für die Document Eigenschaft ruft SetValue, die SetValue Methode betrachtet das DocumentProperty Argument überprüft, dass es wirklich eine Eigenschaft, die zu RichTextBox gehört und dass value ist die richtige Art und speichert dann den neuen Wert ... irgendwo. Die Dokumentation für DependencyObject ist bei diesem Implementierungsdetail geheim, weil Sie es nicht wirklich wissen müssen. In meinem mentalen Modell, wie dieses Zeug funktioniert, nehme ich an, dass es eine Eigenschaft des Typs Dictionary<DependencyProperty, object> gibt, die für die DependencyObject privat ist, so dass abgeleitete Klassen (wie RichTextBox) es nicht sehen können, aber GetValue und SetValue können es aktualisieren. Aber wer weiß, vielleicht ist es auf Pergament von Mönchen geschrieben.

Auf jeden Fall ist dieser Wert jetzt ein sogenannter "lokaler Wert", was bedeutet, dass es sich um einen Wert handelt, der genau wie eine gewöhnliche Eigenschaft lokal für diese spezifische RichTextBox ist.

Der Punkt alle dies:

  1. CLR-Code nicht wissen müssen, dass eine Eigenschaft eine Abhängigkeitseigenschaft ist. Es sieht genau wie jede andere Eigenschaft aus. Sie können Anruf GetValue und SetValue zu bekommen und zu setzen, aber wenn Sie etwas mit dem Abhängigkeits-Property-System tun, müssen Sie wahrscheinlich nicht.
  2. Im Gegensatz zu einer normalen Eigenschaft kann etwas anderes als das Objekt, an dem es beteiligt ist, in das Abrufen und Festlegen einbezogen werden. (Sie könnten dies mit Reflektion machen, denkbar, aber Reflektion ist langsam. Dinge in Wörterbüchern zu suchen ist schnell.)
  3. Dieses Etwas - was das Abhängigkeits-System ist - befindet sich im Wesentlichen zwischen einem Objekt und seinen Abhängigkeitseigenschaften. Und es kann alle Arten von Dingen tun.

Welche Arten von Dingen? Nun, schauen wir uns einige Anwendungsfälle an.

Bindung. Beim Binden an eine Eigenschaft muss es sich um eine Abhängigkeitseigenschaft handeln. Dies liegt daran, dass das Objekt Binding keine Eigenschaften für das Ziel festlegt, sondern SetValue für das Zielobjekt aufruft.

Stile. Wenn Sie die Abhängigkeitseigenschaft eines Objekts auf einen neuen Wert setzen, teilt SetValue dem Stilsystem mit, dass Sie dies getan haben. So funktionieren Trigger: Sie erfahren nicht, dass sich der Wert einer Eigenschaft durch Magie verändert hat, sagt das Abhängigkeitseigenschaftssystem.

Dynamische Ressourcen. Wenn Sie XAML wie Background={DynamicResource MyBackground} schreiben, können Sie den Wert der Ressource MyBackground ändern, und der Hintergrund des darauf referenzierenden Objekts wird aktualisiert. Das ist auch keine Magie; Die dynamische Ressource ruft SetValue auf.

Animationen. Animationen funktionieren durch Bearbeiten von Eigenschaftswerten. Diese müssen Abhängigkeitseigenschaften sein, weil die Animation SetValue aufruft, um an sie heranzukommen.

Benachrichtigung ändern. Wenn Sie eine Abhängigkeitseigenschaft registrieren, können Sie auch eine Funktion angeben, die SetValue aufruft, wenn sie den Wert der Eigenschaft festlegt.

Wert Vererbung. Wenn Sie eine Abhängigkeitseigenschaft registrieren, können Sie angeben, dass sie an der Vererbung von Eigenschaftenwerten beteiligt ist. Wenn Sie GetValue aufrufen, um den Wert der Abhängigkeitseigenschaft eines Objekts abzurufen, überprüft GetValue, ob ein lokaler Wert vorhanden ist. Wenn dies nicht der Fall ist, durchläuft es die Kette der übergeordneten Objekte, die ihre lokalen Werte für diese Eigenschaft betrachten.

Dies ist, wie Sie das FontFamily auf einem Window setzen können und magisch (ich benutze dieses Wort sehr) jedes Steuerelement im Fenster verwendet die neue Schriftart. Es ist auch so, dass Sie Hunderte von Steuerelementen in einem Fenster haben können, ohne dass jede eine FontFamily Mitgliedsvariable hat, um ihre Schriftart zu verfolgen (da sie keine lokalen Werte haben), aber Sie können die FontFamily immer noch auf einem beliebigen Steuerelement setzen (wegen des seekrit versteckten Wörterbuchs von Werten, die jeder DependencyObject hat).

+1

Dies ist sehr wichtig: " Sie können dies mit Reflexion tun, aber die Reflexion ist langsam. Dinge in Wörterbüchern zu suchen ist schnell ". Dies und das geänderte Ereignis sind zwei gute Gründe für die Abhängigkeitseigenschaften. –

+1

Ich stimme zu: Wenn ich das umschreiben würde, würde es die Leistungsprobleme im Spiel viel mehr betonen. Der größte Teil der Ungereimtheit und Komplexität des Abhängigkeitseigenschaftssystems kommt von der Tatsache, dass, wenn es nicht schnell wäre, es WPF brechen würde. Außerdem würde ich Wertvererbung Ihrer Liste der guten Gründe hinzufügen. Wertvererbung ist für WPF enorm wichtig. Und Wert Vererbung + Änderungsbenachrichtigung = Magie. –

+0

Die beste Erklärung, die ich für dieses Thema gesehen habe! – Sandeep

9

Es kann hilfreich sein zu verstehen, welches Problem die Abhängigkeitseigenschaft zu lösen versucht.

Wenn wir das Binding-, Animations- und Change-Event-Modell auf eine Seite legen, wie sie in anderen Antworten besprochen wurden, ist die Speichernutzung und damit die Skalierbarkeit für das Hosten von vielen tausend WPF-Objekten in einem Fenster von Vorteil.

Wenn ein Fenster enthält 1000 Label Objekte mit jedem Label Objekt mit den üblichen Foreground, Background, FontFamily, FontSize, FontWeight usw., dann traditionell diesem Speicher verbrauchen würde, weil jede Eigenschaft ein privates Trägerfeld haben würde den Wert zu speichern .

Die meisten Anwendungen ändern nur einige wenige Eigenschaften, von denen die meisten ihre Standardwerte beibehalten.Grundsätzlich sehr verschwenderische und redundante Information (jedes Objekt hat nur die gleichen Standardwerte im Speicher)

Hier unterscheiden sich die Abhängigkeitseigenschaften.

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

Es gibt kein privates Backing-Feld. Wenn die Abhängigkeitseigenschaft registriert ist, kann ein Standardwert angegeben werden. In den meisten Fällen ist also der zurückgegebene Wert GetValue der Standardwert, der nur einmal gespeichert wurde, um alle Instanzen des Objekts Label in allen Fenstern Ihrer Anwendung abzudecken.

Wenn eine Abhängigkeitseigenschaft eingestellt wird unter Verwendung der SetValue es den Nicht-Standardwert in einer Sammlung von der Objektinstanz identifiziert speichert, um in allen nachfolgenden GetValue Anrufen zurückgeführt werden.

Diese Speichermethode belegt daher nur Speicher für die Eigenschaften der WPF-Objekte, die sich vom Standardwert geändert haben. d.h. nur die Unterschiede zum Standardwert.

0

Ein einfacher/grundlegender Unterschied - Änderungsbenachrichtigung: Änderungen an Abhängigkeitseigenschaften werden in der Benutzeroberfläche bei Änderungen reflektiert/aktualisiert, während dies bei CLR-Eigenschaften nicht der Fall ist.

<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> 

Und der Code hinter hier (Um die CLR und Abhängigkeitseigenschaft zu deklarieren):

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

Ein weiteres Merkmal von Abhängigkeitseigenschaften versehen ist Wert Vererbung - Wert in Top-Level-Elementen gesetzt pflanzt den Baum Element - Im folgenden Beispiel aus http://en.csharp-online.net genommen, Schriftgröße und Fontstyle erklärt auf „Window“ Tag auf alle untergeordneten Elemente darunter angewendet wird:

<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> 

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

Verwandte Themen