WPF: Что отличает свойство зависимости от обычного свойства CLR? - PullRequest
5 голосов
/ 24 февраля 2010

В WPF что на самом деле означает «свойство зависимости»?

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

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

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

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

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

Ответы [ 4 ]

27 голосов
/ 24 февраля 2010

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

Свойства зависимости - это свойства-подобные значения, которые получают и устанавливают с помощью методов класса 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, как обычное свойство.

Точка все это:

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

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

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

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

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

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

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

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

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

9 голосов
/ 24 февраля 2010

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

Если мы поместим модель Binding, Animation и Change Event в одну сторону, как они обсуждались в других ответах, выгода от использования памяти и, следовательно, масштабируемости для размещения в окне многих тысяч объектов 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, которые изменились по сравнению со значением по умолчанию. т.е. только отличия от значения по умолчанию.

8 голосов
/ 24 февраля 2010

В WPF что на самом деле означает «свойство зависимости»?

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

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

Это то, что делает свойство стилевого оформления рабочим, но также важно разрешить присоединенные свойства, «наследование» свойств через визуальное дерево и многое другое, на что опирается WPF.

Например, взять свойство зависимости DataContext. Обычно вы устанавливаете свойство зависимостей DataContext для Window или UserControl. По умолчанию все элементы управления в этом окне «наследуют» proeprty DataContext своего родителя автоматически, что позволяет указывать привязки данных для элементов управления. Со стандартным свойством CLR вам нужно будет определить этот DataContext для каждого элемента управления в окне, просто чтобы привязка работала правильно.

0 голосов
/ 09 января 2013

Простое / фундаментальное отличие - Уведомление об изменениях: изменения в свойствах зависимостей отражаются / обновляются в пользовательском интерфейсе при изменениях, а свойства 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 и Dependency):

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

Еще одна особенность, предоставляемая Dependency Properties, - это наследование значений - значение, заданное в элементах верхнего уровня, распространяется вниз по дереву элементов. Тег "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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...