Рассмотренные свойства элементов, как например, Width или Height, являются не просто стандартными свойствами языка C#. Они фактически скрывают свойства зависимостей или dependency property. Без свойств зависимостей были бы невозможны многие ключевые особенности WPF, как привязка данных, стили, анимация и т.д.
Рассмотрим, как они определяются. Возьмем, к примеру, элемент TextBlock, у которого есть свойство Text:
public class TextBlock : FrameworkElement, IContentHost, IAddChildInternal, IServiceProvider
{
// свойство зависимостей
public static readonly DependencyProperty TextProperty;
static TextBlock()
{
// Регистрация свойства
TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(TextBlock),
new FrameworkPropertyMetadata(
string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnTextChanged),
new CoerceValueCallback(CoerceText)));
// остальной код
}
// Обычное свойство .NET - обертка над свойством зависимостей
Статическое свойство TextProperty является свойством зависимостей, представляя объект System.Windows.DependencyProperty. По соглашениям по именованию все свойства зависимостей представляют статические публичные поля (public static) с суффиксом Property.
Затем в статическом конструкторе класса происходит регистрация свойства с помощью метода DependencyProperty.Register(), в который передается ряд параметров:
имя свойства (в данном случае "Text"). Как правило, соответствует названию свойства зависимостей без суффикса Property
тип свойства (в данном случае string)
тип, который владеет свойством - собственно тот тип, в котором свойство определено или в данном случае тип TextBlock
Необязательный параметр FrameworkPropertyMetadata устанавливает дополнительные настройки свойства
В качестве пятого необязательного параметра может использоваться ссылка на метод, который производит валидацию свойства. В данном случае этот параметр опущен.
В данном случае применяется один из конструкторов:
new PropertyChangedCallback(OnTextChanged), new CoerceValueCallback(CoerceText)))
Этот конструктор устанавливает в качестве значения по умолчанию пустую строку, указывает, что при изменении значения элемент будет перерисовываться (собственно, что мы и видим - при изменении значения свойства Text новое значение отображается), и при изменении значения свойства будут вызываться методы OnTextChanged и CoerceText.
Далее после регистрации свойства идет обертка - обычное свойство .NET, которое имеет сеттер и геттер и которое вызывает методы GetValue и SetValue для получения и установки значения соответственно. Эти методы определены в классе System.Windows.DependencyObject, который является базовым для всех элементов WPF, в том числе и для TextBlock.
Последнее обстоятельство привносит ограничение - свойства зависимостей могут быть определены только в тех классах, которые наследутся от DependencyObject.
Кроме того, DependencyObject поддерживает еще ряд свойств для управления свойствами зависимостей:
ClearValue: очищает значение объекта DependencyProperty
InvalidateProperty: повторно вычисляет действующее значение объекта DependencyProperty
ReadLocalValue: считывает значение объекта DependencyProperty
Например:
TextBlock textBlock = new TextBlock();
textBlock.Text = "Hello";
string text = (string) textBlock.ReadLocalValue(TextBlock.TextProperty); // Hello
textBlock.ClearValue(TextBlock.TextProperty); // теперь значение отсутствует
Провайдеры свойств
В WPF определение значения свойств представляет многоэтапный процесс. На различных стадиях этого процесса применяются различные провайдеры свойств, которые помогают получить значение для свойства зависимостей.
При извлечении значения свойства система использует 10 провайдеров: Получение локального значение свойства (то есть то, которое установлено разработчиком через XAML или через код C#)
Вычисление значения с помощью триггеров из шаблона родительского элемента
Вычисление значения из шаблона родительского элемента
Вычисление значения с помощью триггеров из применяемого шаблона
Получение значения из сеттеров применяемых стилей
Вычисление значения с помощью триггеров из применяемых тем
Получение значения из сеттеров применяемых тем
Получение унаследованного значения (если свойство FrameworkPropertyMetadata.Inherits имеет значение true)
Извлечение значения по умолчанию, которое устанавливается через объект FrameworkPropertyMetadata
Все эти этапы выполняются последовательно сверху вниз. Если на одном этапе было получено значения, то этапы ниже уже не выполняются. Получение значения свойства - представляет сложный многоэтапный процесс. И даже если в XAML для элемента не установлено значение какого-либо свойства, то все равно оно может иметь значение, полученное на одном из выше перечисленных шагов.
Все десять перечисленных этапов обычно объединяются в одну стадию - получение базового значения.
Но кроме получения значения есть еще процесс установки значения. Он вовлекает ряд дополнительных шагов: Вышеописанные 10 шагов - получение базового значения
Если значение свойства, полученное на шаге 1, представляет собой сложное выражение (например, выражение привязки данных), то WPF вычисляет значение этого выражения и получает конкретный результат
Если для свойства применяется анимация, то далее она используется для получения нового значения
После получения значения WPF применяет делегат CoerceValueCallback, который задается в объекте FrameworkPropertyMetadata при регистрации свойства. С помощью метода, на который указывает данный делегат, проверяется, входит ли значение в диапазон допустимых значений. Если не входит, то в заисимости от логики задается новое значение
В конце применяется делегат ValidateValueCallback (если он указан при регистрации свойства в качестве пятого параметра), который выполняет валидацию. Метод, на который ссылается делегат, возвращает true при прохождении валидации. Иначе возвращается false и генерируется исключение
Маршрутизация событий
Чтобы взаимодействовать с элементами управления, нам надо использовать модель событий. WPF предлагает новую концепцию событий - маршрутизированные события (routed events).
Для элементов управления в WPF определено большое количество событий, которые условно можно разделить на несколько групп:
События клавиатуры
События мыши
События стилуса
События сенсорного экрана/мультитач
События жизненного цикла
Подключение обработчиков событий
Подключить обработчики событий можно декларативно в файле xaml-кода, а можно стандартным способом в файле отделенного кода.