Приоритет значения свойств зависимостей

В этом разделе рассказывается, как работа системы свойств Windows Presentation Foundation (WPF) может повлиять на значение свойства зависимости, и описывается приоритет применения аспектов системы свойств к действительному значению свойства.

Необходимые компоненты

Предполагается, что вы имеете представление о свойствах зависимости с точки зрения потребителя существующих свойств зависимостей в классах WPF и ознакомились с разделом Общие сведения о свойствах зависимости. Чтобы следовать примерам в этом разделе, вы также должны понять XAML и узнать, как писать приложения WPF.

Система свойств WPF

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

Свойства зависимостей могут быть "установлены" в нескольких расположениях

Ниже приведен пример на языке XAML, где у одного свойства (Background) имеется три различных «набора» операций, которые могут повлиять на значение.

<StackPanel>
    <StackPanel.Resources>
        <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
            <Border Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" 
                    BorderBrush="{TemplateBinding BorderBrush}">
                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
            </Border>
        </ControlTemplate>
    </StackPanel.Resources>

    <Button Template="{StaticResource ButtonTemplate}" Background="Red">
        <Button.Style>
            <Style TargetType="{x:Type Button}">
                <Setter Property="Background" Value="Blue"/>
                <Style.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="Background" Value="Yellow" />
                    </Trigger>
                </Style.Triggers>
            </Style>
        </Button.Style>
        Which color do you expect?
    </Button>
</StackPanel>

Как вы считаете, какой цвет будет здесь использован: красный, зеленый или синий?

За исключением динамических значений и приведения наборы локальных свойств устанавливаются с наивысшим приоритетом. Если значение задается локально, можно ожидать, что значение будет соблюдаться, даже если выше все стили и шаблоны элемента управления. В этом примере для Background локально задано значение «Red». Следовательно, стиль, определенный в этой области, даже если это неявный стиль, который в противном случае будет применен ко всем элементам этого типа в этой области, имеет не наивысший приоритет для передачи своего значения свойству Background. Если удалить локальное значение Red из этого экземпляра Button, то стиль получит приоритет и кнопка получит значение Background из стиля. Внутри стиля приоритет получают триггеры, поэтому кнопка будет синей, если указатель мыши находится над ней, и зеленым в других случаях.

Список приоритета настройки свойств зависимости

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

  1. Приведение системы свойств. Дополнительные сведения о приведении см. в теме Приведение, анимация и базовое значение далее в этом разделе.

  2. Активные анимации или анимации с поведением Hold. Анимация свойства имеет практический эффект только в том случае, если она может иметь приоритет над базовым (неанимированным) значением, даже если это значение было задано локально. Дополнительные сведения см. в теме Приведение, анимация и базовое значение далее в этом разделе.

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

  4. Свойства шаблона TemplatedParent. У элемента имеется свойство TemplatedParent, если он создан как часть шаблона (ControlTemplate или DataTemplate). Дополнительные сведения о случаях его применения см. в теме TemplatedParent далее в этом разделе. В шаблоне действует следующий приоритет:

    1. Триггеры из шаблона TemplatedParent.

    2. Наборы свойств (передаваемые обычно посредством атрибутов XAML) в шаблоне TemplatedParent.

  5. Неявный стиль. Применяется только к свойству Style. Свойство Style заполняется любым ресурсом стиля с ключом, соответствующим типу этого элемента. Ресурс стиля должен существовать либо на странице, либо в приложении; поиск ресурса неявного стиля в темах не выполняется.

  6. Триггеры стилей. Триггеры в стилях со страницы или из приложения (эти стили могут быть явными или неявными, но не стилями по умолчанию, поскольку последние имеют более низкий приоритет).

  7. Триггеры шаблонов. Любой триггер из шаблона внутри стиля или непосредственно применяемый шаблон.

  8. Методы задания стилей. Значения из метода Setter в стилях со страницы или из приложения.

  9. Стиль (тема) по умолчанию. Подробные сведения о применении этих стилей и связи стилей тем с шаблонами в стилях тем см. в теме Стили (темы) по умолчанию далее в этом разделе. В стиле по умолчанию применяется следующий порядок приоритета:

    1. Активные триггеры в тематическом стиле.

    2. Методы задания в тематическом стиле.

  10. Наследование. Некоторые свойства зависимостей наследуют свои значения от родительского элемента к дочерним элементам, так что их не требуется задавать по отдельности на каждом элементе в приложении. Подробные сведения см. в разделе Наследование значений свойств.

  11. Значение по умолчанию из метаданных свойства зависимости. Любое заданное свойство зависимостей может иметь значение по умолчанию, заданное при регистрации системы свойств конкретного свойства. Кроме того, производные классы, которые наследуют свойства зависимостей, имеют возможность переопределить эти метаданные (включая значение по умолчанию) на уровне отдельных типов. Дополнительные сведения см. в разделе Метаданные свойств зависимостей. Поскольку наследование для наследуемого свойства проверяется до значения по умолчанию, значение по умолчанию родительского элемента имеет приоритет над дочерним элементом. Следовательно, если наследуемое свойство нигде не задано, используется значение по умолчанию, как указано в корневом элементе, либо родительское значение используется вместо значения по умолчанию дочернего элемента.

TemplatedParent

TemplatedParent как приоритетный элемент не применяется ни к какому свойству элемента, объявляемому непосредственно в стандартной разметке приложения. Понятие TemplatedParent существует только для дочерних элементов в пределах визуального дерева, которые создаются в результате применения шаблона. Когда система свойств выполняет поиск шаблона TemplatedParent для какого-либо значения, она ищет шаблон, который создал этот элемент. Значения свойств из шаблона TemplatedParent обычно функционируют, как если бы они были заданы в качестве локального значения для дочернего элемента; однако они имеют более низкий приоритет по сравнению с локальным значением, потому что шаблоны потенциально доступны для совместного доступа. Дополнительные сведения см. в разделе TemplatedParent.

Свойство стиля

Порядок поиска, описанный выше, применяется ко всем возможным свойствам зависимостей, кроме одного: свойства Style. Свойство Style является уникальным в том, что применить к нему стиль невозможно, поэтому элементы приоритета с 5 по 8 не применяются. Также не рекомендуется применять для Style анимацию или приведение (а для анимации Style необходим настраиваемый класс анимации). Таким образом остается три способа, чтобы задать свойство Style:

  • Явный стиль. Свойство Style задается напрямую. В большинстве случаев стиль не определяется внутри объекта, а используется ссылка на стиль как ресурс с применением явного ключа. В этом случае само свойство Style действует как локальное значение с приоритетом 3.

  • Неявный стиль. Свойство Style не задается напрямую. Однако свойство Style существует на определенном уровне последовательности поиска ресурсов (страница, приложение) и шифруется с помощью ключа ресурса, соответствующего типу, к которому применяется стиль. В этом случае само свойство Style функционирует с приоритетом, определенным в последовательности как элемент 5. Это условие обнаруживается путём применения DependencyPropertyHelper к свойству Style с последующим поиском в результатах ImplicitStyleReference.

  • Стиль по умолчанию, который также называется стиль темы. Свойство Style не задано напрямую и фактически имеет значение null до времени выполнения программы. В этом случае стиль поступает из оценки темы времени выполнения, которая является частью механизма презентации WPF.

Для неявных стилей не в темах необходимо точное соответствие типа: производный от MyButtonButton класс не будет неявно использовать стиль для Button.

Стили (темы) по умолчанию

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

Наиболее важная информация, хранимая в стиле по умолчанию для элемента управления, — это шаблон элемента управления, который существует в тематическом стиле в качестве метода задания для свойства Template. При отсутствии шаблона из стилей по умолчанию элемент управления без пользовательского шаблона в составе пользовательского стиля вообще бы не имел визуального представления. Шаблон из стиля по умолчанию придает визуальному представлению каждого элемента управления базовую структуру, а также определяет подключения между свойствами, определенными в визуальном дереве шаблона и соответствующем классе элементов управления. Каждый элемент управления предоставляет набор свойств, которые могут повлиять на внешний вид элемента управления без полной замены шаблона. Рассмотрим, например, внешний вид по умолчанию элемента управления Thumb, который является компонентом ScrollBar.

У Thumb имеются некоторые настраиваемые свойства. Шаблон Thumb по умолчанию создает базовую структуру или визуальное дерево с несколькими вложенными компонентами Border для создания выпуклого вида. Если свойство, которое является частью шаблона, должно быть предоставлено для настройки с помощью класса Thumb то это свойство необходимо представить в шаблоне, используя класс TemplateBinding. Применительно к Thumb, у различных свойств этих границ имеется общая привязка шаблона к таким свойствам, как Background или BorderThickness. Однако некоторые другие свойства или визуальные представления заданы в коде шаблона элемента управления или привязаны к значениям, которые поступают непосредственно из темы; чтобы изменить их, необходимо заменить весь шаблон. Как правило, если свойство поступает из шаблонного родительского элемента и не предоставляется привязкой шаблона, его невозможно скорректировать с помощью стилей, поскольку нет простого способа указать его в качестве целевого объекта. Однако это свойство может зависеть от наследования значения свойства в примененном шаблоне или значения по умолчанию.

Тематические стили используют тип в качестве ключа в своих определениях. Однако при применении тем к экземпляру данного элемента поиск тем для данного типа выполняется путем проверки свойства DefaultStyleKey элемента управления. Это отличается от сценария неявных стилей, где используется литеральный тип. Значение DefaultStyleKey наследуется производными классами, даже если разработчик не изменяет его (чтобы изменить свойство, нужно не переопределить его на уровне свойства, а изменить его значение по умолчанию в метаданных свойства). Такая опосредованность позволяет базовым классам определять тематические стили для производных элементов, которые в противном случае не имеют стиля (или, что более важно, не имеют шаблона внутри стиля и поэтому вообще не имеют визуального представления по умолчанию). Таким образом можно создать MyButton на основе Button и все равно получить шаблон Button по умолчанию. Если бы вы были создателем элемента управления MyButton и вам бы потребовалось другое поведение, можно было бы переопределить метаданные свойства зависимости для DefaultStyleKey в MyButton, чтобы возвращать другой ключ, а затем определить соответствующие тематические стили, включая шаблон для MyButton, который необходимо упаковать со своим элементом управления MyButton. Дополнительные сведения о темах, стилях и создании элементов управления см. в разделе Общие сведения о создании элементов управления.

Ссылки на динамические ресурсы и привязки

В операциях со ссылками на динамические ресурсы и привязками учитывается приоритет расположения при настройке. Например, динамический ресурс, применяемый к локальному значению, действует с приоритетом 3, привязка для метода задания свойства в тематическом стиле действует с приоритетом 9 и так далее. Поскольку ссылки на динамические ресурсы и привязки должны иметь возможность получения значений из состояния времени выполнения приложения, это предполагает, что фактический процесс определения приоритета значения свойства для какого-либо свойства распространяется и на время выполнения.

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

Динамические ресурсы и привязки получают приоритет расположения, в котором они были заданы, но значение откладывается. Следствием этого является тот факт, что если задать для динамического ресурса или привязки локальное значение, то при любом изменении локального значения динамический ресурс или привязка будут меняться полностью. Даже если вызвать метод ClearValue для очистки локально заданного значения, динамический ресурс или привязка не будет восстановлена. На самом деле, если вызвать метод ClearValue для свойства, у которого имеется динамический ресурс или привязка (без литерального локального значения), они также очищаются путём вызова ClearValue.

SetCurrentValue

Метод SetCurrentValue является другим способом задания свойства, но без соблюдения порядка приоритетов. Напротив, метод SetCurrentValue позволяет изменить значение свойства, не перезаписывая источник предыдущего значения. Метод SetCurrentValue можно использовать каждый раз, когда необходимо задать значение, не предоставляя этому значению приоритет локального. Например, если свойство задается триггером, а затем ему присваивается другое значение с помощью SetCurrentValue, система свойств по-прежнему учитывает этот триггер, и свойство меняется, если происходит какое-либо действие триггера. SetCurrentValue позволяет изменить значение свойства, не предоставляя ему источник с более высоким приоритетом. Аналогичным образом можно использовать SetCurrentValue для изменения значения свойства без перезаписи привязки.

Приведение, анимации и базовое значение

Приведение и анимация используют значение, которое в SDK определяется как «базовое значение». Базовое значение — это любое значение, определяемое посредством восходящей оценки элементов до тех пор, пока не будет достигнут элемент 2.

Для анимации базовое значение может повлиять на анимированное значение, если анимация не указывает значения From и To для определенных моделей поведения, либо если анимация умышленно возвращается к базовому значению по завершении выполнения. Чтобы увидеть это на практике, запустите Пример целевых значений анимации From, To, By. Попробуйте установить локальные значения высоты прямоугольника в примере, чтобы начальное локальное значение отличалось от любого значения From в анимации. Обратите внимание, что анимация сразу же начинает использовать значения From и заменяет базовое значение после запуска. Можно указать, чтобы анимация возвращалась к значению, обнаруженному перед анимацией, по завершении выполнения. Для этого нужно задать поведение остановки FillBehavior. После этого для определения базового значения используется обычный приоритет.

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

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

Поведения триггера

Элементы управления часто определяют поведение триггера в составе своего стиля по умолчанию, в темах. Установка локальных свойств для элементов управления может препятствовать реагированию триггеров на инициированные пользователями события (визуально или своими действиями). Наиболее часто триггер свойств используется для свойств элементов управления и состояний, таких как IsSelected. Например, по умолчанию, когда элемент Button отключен (триггером для IsEnabled является значение false), тогда значение Foreground в стиле темы определяет то, что элемент управления становится закрашен серым. Однако если задать локальное значение Foreground, приоритет стандартного серого цвета недоступности элемента будет переопределен набором локальных свойств даже в этом сценарии, инициируемом свойством. Будьте осторожны при установке значений для свойств, которые имеют поведения триггеров на уровне темы; кроме того, необходимо убедиться, что не создается ненужных помех работе пользователя с этим элементом управления.

ClearValue и приоритет значения

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

См. также