WPF: что отличает свойство зависимостей от обычного свойства среды CLR?



В WPF, что на самом деле означает быть "свойством зависимости"?



Я читал обзор свойств зависимостей от Microsoft , но это не очень хорошо для меня. В части этой статьи говорится:



Стили и шаблоны-это два основных мотивирующих сценария использования свойств зависимостей. Стили особенно полезны для настройки свойств, определяющих пользовательский интерфейс приложения. Стили обычно определяются как ресурсы в XAML. Стили взаимодействуют с системой свойств, потому что они обычно содержат "задатчики" для определенных свойств, а также "триггеры", которые изменяют значение свойства на основе значения в реальном времени для другого свойства.


И тогда пример кода таков:



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


Но я не понимаю, что в этом особенного. Означает ли это, что, когда я устанавливаю Style на кнопку в заданном стиле, я на самом деле устанавливаю Background неявно? В этом-то все и дело?
883   4  

4 ответов:

В WPF, что на самом деле означает быть "свойством зависимости"?

Для того, чтобы быть свойством зависимости, свойство фактически должно быть определено как DependencyProperty, статически, от класса. Система свойств зависимостей сильно отличается от стандартного свойства CLR.

Однако свойства зависимостей обрабатываются очень по-разному. Тип определяет свойство зависимостей статически и предоставляет значение по умолчанию. Время выполнения фактически не генерирует значение для экземпляра, пока оно не понадобится. Это дает одно преимущество-свойство не существует, пока не запрошено для типа, поэтому вы можете иметь большое количество свойств без накладных расходов. Это то, что делает стиль рабочим свойством, но также важно, чтобы разрешить прикрепленные свойства, свойство "наследование" через визуальное дерево и многие другие вещи, на которые опирается WPF.

Например, возьмем свойство зависимостей DataContext. Как правило, вы устанавливаете DataContext свойство зависимостей для окна или элемента управления пользователя. Все элементы управления в этом окне, по умолчанию, "наследуют" Родительский DataContext proeprty автоматически, что позволяет указать привязки данных для элементов управления. Со стандартным свойством CLR вам нужно будет определить этот DataContext для каждого элемента управления в окне, просто чтобы заставить привязку работать должным образом.

Вот объяснение того, как работают свойства зависимостей, которое я всегда хотел, чтобы кто-то написал для меня. Он неполон и, вполне возможно, ошибочен, но он поможет вам развить достаточное понимание их, чтобы вы могли понять документацию, которую Вы читаете.

Свойства зависимостей-это значения типа свойств, которые получают и задают с помощью методов класса DependencyObject. Они могут выглядеть (и обычно выглядят) очень похоже на свойства CLR, но это не так. А это доходит до первой запутанной вещи о них. Свойство зависимостей на самом деле состоит из нескольких компонентов.

Вот пример:

Document является свойством объекта RichTextBox. Это настоящая собственность CLR. То есть у него есть имя, тип, геттер и сеттер, как и у любого другого свойства CLR. Но в отличие от "обычных" свойств, СВОЙСТВО RichTextBox не просто получает и задает частное значение внутри экземпляра. Внутренне он реализован следующим образом: это:

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

Когда вы устанавливаете Document, значение, которое вы передали, передается в SetValue вместе с DocumentProperty. А что такое это ? И как GetValue получает свое значение? И...почему?

Сначала что. Существует статическое свойство, определенное на RichTextBox с именем DocumentProperty. Когда это свойство объявляется, это делается следующим образом:

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

Метод Register в этом случае сообщает системе свойств зависимостей, что RichTextBox - тип, а не экземпляр - теперь имеет свойство зависимостей назван Document типа FlowDocument. Этот метод хранит эту информацию...где-то. Где именно находится деталь реализации, которая скрыта от нас.

Когда сеттер для свойства Document вызывает SetValue, метод SetValue смотрит на аргумент DocumentProperty, проверяет, что это действительно свойство, которое принадлежит RichTextBox и что value является правильным типом, а затем сохраняет его новое значение...где-то. Документация для DependencyObject является скромной на этой детали реализации, потому что вам действительно не нужно знать это. В моей ментальной модели того, как это работает, я предполагаю, что есть свойство типа Dictionary<DependencyProperty, object>, которое является частным для DependencyObject, поэтому производные классы (например, RichTextBox) не могут его видеть, но GetValue и SetValue могут его обновить. Но кто знает, может быть, это написано на пергаменте монахами.

Во всяком случае, это значение теперь называется "локальным значением", то есть значением, локальным для данного конкретного RichTextBox, как и обычное свойство.

Суть всего этого is:

  1. коду CLR не обязательно знать, что свойство является свойством зависимости. Он выглядит точно так же, как и любой другой объект недвижимости. Вы можете вызвать GetValue и SetValue, чтобы получить и установить его, но если вы не делаете что-то с системой свойств зависимостей, вам, вероятно, не нужно.
  2. В отличие от обычного свойства, в получении и установке его может быть задействовано нечто иное, чем объект, которому оно принадлежит. (Вы могли бы сделать это с помощью отражения, возможно, но отражение-это медленный. Поиск вещей в словарях происходит быстро.) Это нечто - система свойств зависимостей-по существу находится между объектом и его свойствами зависимостей. И он может делать Все виды вещей.

Какие вещи? Ну, давайте рассмотрим некоторые примеры использования.

Связывание. когда вы привязываетесь к свойству, оно должно быть свойством зависимости. Это происходит потому, что объект Binding на самом деле не задает свойства цели, он вызывает SetValue на целевом объекте.

Стили. когда вы устанавливаете свойство зависимостей объекта в новое значение, SetValue сообщает системе стилей, что вы это сделали. Вот как работают триггеры: они не обнаруживают, что значение свойства изменилось с помощью магии, говорит им система свойств зависимостей.

Динамические ресурсы. если вы пишете XAML как Background={DynamicResource MyBackground}, вы можете изменить значение ресурса MyBackground и фон объекта, ссылающегося на него обновляется. Это тоже не магия; динамический ресурс вызывает SetValue.

Анимация.Анимация работает путем манипулирования значениями свойств. Это должны быть свойства зависимостей, потому что анимация вызывает SetValue, чтобы добраться до них.

Уведомление об изменениях. при регистрации свойства зависимостей можно также указать функцию, которая будет вызывать SetValue при установке значения свойства.

Наследование ценностей. при регистрации свойство зависимости, можно указать, что оно участвует в наследовании значения свойства. Когда вы вызываете GetValue, чтобы получить значение свойства зависимости объекта, GetValue ищет, есть ли локальное значение. Если нет, он проходит вверх по цепочке родительских объектов, глядя на их локальные значения для этого свойства.

Вот как это происходит, что вы можете установить FontFamily на Window и волшебным образом (я использую это слово много) каждый элемент управления в окне использует новый шрифт. Также, это то, как это происходит, что вы можете иметь сотни элементов управления в окне без каждого из них, имеющих переменную-член FontFamily для отслеживания их шрифта (так как они не имеют локальных значений), но вы все еще можете установить FontFamily на любом одном элементе управления (из-за скрытого словаря значений seekrit, который имеет каждый DependencyObject).

Может быть полезно понять, какую проблему пытается решить свойство зависимостей.

Если мы отложим привязку, анимацию и модель событий изменения в одну сторону, как они обсуждались в других ответах, то преимущество будет заключаться в использовании памяти и, следовательно, в масштабируемости для размещения многих тысяч объектов WPF в окне.

Если окно содержит 1000 Label объектов с каждым Label объектом, имеющим обычный Foreground, Background, FontFamily, FontSize, FontWeight, и т.д., то традиционно это потребляло бы память, потому что каждое свойство будет иметь частное резервное поле для хранения значения.

Большинство приложений изменят только несколько свойств, большинство из которых останутся со значениями по умолчанию. В основном очень расточительная и избыточная информация (каждый объект просто держит в памяти те же значения по умолчанию)

Здесь свойства зависимостей различны.

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

Нет никакого частного резервного поля. Если свойство зависимости прописан по умолчанию значение может быть указано. Таким образом, в большинстве случаев возвращаемое значение из GetValue является значением по умолчанию, которое было сохранено только один раз, чтобы охватить все экземпляры объекта Label во всех окнах вашего приложения.

Когда свойство зависимостей задано с помощью SetValue, оно сохраняет значение, отличное от значения по умолчанию, в коллекции, идентифицированной экземпляром объекта, которое будет возвращено во всех последующих вызовах GetValue.

Таким образом, этот метод хранения будет использовать память только для свойств Объекты WPF, которые изменились по сравнению со значением по умолчанию. то есть только отличия от значения по умолчанию.

Простое/фундаментальное различие-уведомление об изменении: изменения свойств зависимостей отражаются / обновляются в пользовательском интерфейсе при изменениях, в то время как свойства среды CLR-нет.

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

И код за этим (для объявления CLR и свойства зависимостей):

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

Еще одной особенностью, обеспечиваемой свойствами зависимостей, является наследование значений-значение, установленное в элементах верхнего уровня, распространяется вниз по дереву элементов-в следующем примере, взятом из http://en.csharp-online.net , FontSize и FontStyle, объявленный на теге" Window", применяется ко всем дочерним элементам под ним:

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

Список литературы: http://www.codeproject.com/Articles/29054/WPF-Data-Binding-Part-1 http://en.csharp-online.net/WPF_Concepts%E2%80%94Property_Value_Inheritance

Comments

    Ничего не найдено.