DependencyProperty: проверка, принуждение и изменению Обработка (Часть I: WPF)
Введение
Зависимость собственности является одной из основных инноваций WPF. Свойство зависимостей имеет такой же простой и знакомый интерфейс программирования как свойство CLR, но позволяет его значение будет динамически решил / изменить многие факторы, как и другие свойства, стили, шаблоны, привязки данных, анимации, элементного состава и т.д. наследования классов , и могут быть реализованы для обеспечения автономного значения по умолчанию, проверка, принуждение и троеборье логики. Silverlight реализует подмножество функций WPF свойство системы, поэтому существуют значительные различия между WPF и Silverlight. Прямой перенос между WPF и Silverlight не может быть возможно, серьезных изменений исходного кода могут быть необходимы. Это три части после серии будет использовать простой пример, чтобы продемонстрировать общий принцип реализации свойства зависимостей WPF и Silverlight, разница между двумя системами собственности, и как порт от одного к другому, а затем использовать NumericUpDown контроля я написал для Silverlight Toolkit , чтобы продемонстрировать, насколько сложным и сложным свойства зависимостей можно получить на Silverlight. Я буду акцентировать внимание только на проверки, принуждения и изменения обработки в этой серии. Когда я нахожу время, я могу писать о других аспектах свойства зависимостей.
WPF свойства зависимостей Обзор
MSDN имеет хороший обзор свойства зависимостей , так что я просто позвонить из трех основных классов и два важных набора методов программирования интерфейса системы свойств WPF:
- DependencyProperty, который предоставляет свойства, как имя, OwnerType, PropertyType, и методы, такие как регистр, RegisterAttached, RegiterReadOnly, RegisterAttachedReadOnly:
(DependencyPropertyValueSerializer))] [TypeConverter ("System.Windows.Markup.DependencyPropertyConverter, PresentationFramework, Version = 4.0.0.0, культура = нейтральной, PublicKeyToken = 31bf3856ad364e35, Custom = NULL"), ValueSerializer (TypeOf (DependencyPropertyValueSerializer))]
общественных запечатанный класс DependencyProperty
{
/ / Поля
общественности статической чтения UnsetValue объекта;
/ / Методы
общественных DependencyProperty AddOwner (тип ownerType);
общественных DependencyProperty AddOwner (тип ownerType, PropertyMetadata typeMetadata);
общественных коррекции Int GetHashCode ();
общественных PropertyMetadata GetMetadata (тип forType);
общественных PropertyMetadata GetMetadata (DependencyObject DependencyObject);
общественных PropertyMetadata GetMetadata (DependencyObjectType DependencyObjectType);
); общественных Ьоо IsValidType (значение объекта);
); общественных Ьоо IsValidValue (значение объекта);
общественного недействительными OverrideMetadata (тип forType, PropertyMetadata typeMetadata);
общественного недействительными OverrideMetadata (тип forType, PropertyMetadata typeMetadata, DependencyPropertyKey ключ);
name, Type propertyType, Type ownerType); общественности статической DependencyProperty регистра (строка имя, тип PropertyType, тип ownerType);
name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata); общественности статической DependencyProperty регистра (строка имя, тип PropertyType, тип ownerType, PropertyMetadata typeMetadata);
name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback); общественности статической DependencyProperty регистра (строка имя, тип PropertyType, тип ownerType, PropertyMetadata typeMetadata, ValidateValueCallback ValidateValueCallback);
name, Type propertyType, Type ownerType); общественности статической DependencyProperty RegisterAttached (строка имя, тип PropertyType, тип ownerType);
name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata); общественности статической DependencyProperty RegisterAttached (строка имя, тип PropertyType, тип ownerType, PropertyMetadata defaultMetadata);
name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback); общественности статической DependencyProperty RegisterAttached (строка имя, тип PropertyType, тип ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback ValidateValueCallback);
name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata); общественности статической DependencyPropertyKey RegisterAttachedReadOnly (строка имя, тип PropertyType, тип ownerType, PropertyMetadata defaultMetadata);
name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback); общественности статической DependencyPropertyKey RegisterAttachedReadOnly (строка имя, тип PropertyType, тип ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback ValidateValueCallback);
name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata); общественности статической DependencyPropertyKey RegisterReadOnly (строка имя, тип PropertyType, тип ownerType, PropertyMetadata typeMetadata);
name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback); общественности статической DependencyPropertyKey RegisterReadOnly (строка имя, тип PropertyType, тип ownerType, PropertyMetadata typeMetadata, ValidateValueCallback ValidateValueCallback);
общественных строка переопределить ToString ();
/ / Свойства
общественных PropertyMetadata DefaultMetadata {get;}
общественных Int GlobalIndex {get;}
Имя общественных строка {get;}
общественных OwnerType типа {get;}
общественных PropertyType типа {get;}
общественных Ьоо ReadOnly {get;}
общественных ValidateValueCallback ValidateValueCallback {get;}
} - DependencyObject, который предоставляет методы, как GetValue, SetValue, ReadLocalValue, CoerceValue, ClearValue для доступа / изменения значения свойства зависимостей:
, typeof (NameScope))] [TypeDescriptionProvider (TypeOf (DependencyObjectProvider)), NameScopeProperty ("NameScope", TypeOf (NameScope))]
открытый класс DependencyObject: DispatcherObject
{
/ / Методы
общественных DependencyObject ();
общественного недействительными ClearValue (DependencyProperty др);
общественного недействительными ClearValue (DependencyPropertyKey ключ);
общественного недействительными CoerceValue (DependencyProperty др);
obj); общественных запечатанном переопределить Ьоо Equals (Object объект);
общественных запечатанном коррекции Int GetHashCode ();
общественных GetLocalValueEnumerator LocalValueEnumerator ();
общественных GetValue объекта (DependencyProperty др);
общественного недействительными InvalidateProperty (DependencyProperty др);
защищенных виртуальных недействительными OnPropertyChanged, (DependencyPropertyChangedEventArgs е);
общественный объект ReadLocalValue (DependencyProperty др);
); общественного недействительными SetCurrentValue (DependencyProperty др, стоимости объекта);
); общественных SetValue недействительными (DependencyProperty др, значение объекта);
); общественных SetValue недействительными (DependencyPropertyKey ключ, значение объекта);
/ / Свойства
общественных DependencyObjectType DependencyObjectType {get;}
set; } внутренний UINT EffectiveValuesCount {[FriendAccessAllowed] получить, частные набор;}
общественных Ьоо IsSealed {get;}
} - PropertyMetadata, которая позволяет по умолчанию, принуждения и логики обработки изменения должны быть указаны при регистрации свойств зависимостей:
открытый класс PropertyMetadata
{
/ / Методы
общественных PropertyMetadata ();
defaultValue); общественных PropertyMetadata (объект DefaultValue);
общественных PropertyMetadata (PropertyChangedCallback PropertyChangedCallback);
defaultValue, PropertyChangedCallback propertyChangedCallback); общественных PropertyMetadata (объект DefaultValue, PropertyChangedCallback PropertyChangedCallback);
defaultValue, PropertyChangedCallback propertyChangedCallback, CoerceValueCallback coerceValueCallback); общественных PropertyMetadata (объект DefaultValue, PropertyChangedCallback PropertyChangedCallback, CoerceValueCallback CoerceValueCallback);
защищенных виртуальных недействительными слияния (PropertyMetadata baseMetadata, DependencyProperty др);
защищенных виртуальных недействительными OnApply (DependencyProperty др, тип TargetType);
/ / Свойства
общественных CoerceValueCallback CoerceValueCallback {get; набор;}
общественных DefaultValue объекта {get; набор;}
охраняемой Ьоо IsSealed {get;}
общественных PropertyChangedCallback PropertyChangedCallback {get; набор;}
} - DependencyProperty.Register * методы и их перегрузки
- PropertyMetadata конструкторов
Пример
В приведенном ниже примере имеет простой MyButton класс, который наследует от класса Button и реализует MyValue свойства зависимостей. Стоимость имущества MyValue должна быть в пределах от 0 до 10, в жизнь свою логику проверки IsValidMyValue (). Эффективная стоимость имущества MyValue зависит от значения по умолчанию (0), пользовательский ввод (набор параметров запроса), и значение его IsEnabled значение свойство, унаследованное от базового класса. Последняя зависимость осуществляется путем принуждения CoerceMyValue логикой и зависимость изменения обработчик события OnIsEnabledChanged (). Общие реализации MyValue демонстрирует общую картину зависимых собственности на WPF:
- CLR обертки: общественный Int MyValue {get; набор;}
- Зависимость свойств идентификатор: общественности статической чтения DependencyProeprty MyValueProperty
- Регистрация в системе собственности: DependencyProperty.Register ()
- Проверка логики: IsValidMyValue статический метод
- Принуждение логика: CoerceMyValue статический метод
Она является общей и посоветовал также осуществлять изменил логику обработки с помощью:
- PropertyChangedCallback OnMyValueChanged статический метод
- защищенных виртуальных недействительными OnMyValueChanged (OldValue, NewValue), чтобы поднять событие изменения, так и для подкласса, чтобы переопределить
- общественности статической чтения RoutedEvent MyValueChangedEvent направляться идентификаторов событий и регистрация событий
- общественных <int> RoutedPropertyChangedEventHandler событие MyValueChanged событие для клиента
Ниже приведен исходный код примера:
- Window1.xaml:
- Window1.xaml.cs:
System.Windows; using System.Windows.Controls; namespace WpfApp1 { // sample class to demonstrate how to implement a DependencyProperty public class MyButton : Button { // DP: CLR wrapper public int MyValue { get { return ( int )GetValue(MyValueProperty); } set { SetValue(MyValueProperty, value ); } } // DP: dependency property identifier & registration public static readonly DependencyProperty MyValueProperty = using System; использование System.Windows; использование System.Windows.Controls; имен WpfApp1 {/ / пример класса для демонстрации, как реализовать DependencyProperty общественный класс MyButton: Button {/ / DP: CLR обертки общественных Int MyValue {get {возвращение (INT ) GetValue (MyValueProperty);} множество {SetValue (MyValueProperty, стоимость);}} / / DP: зависимость идентификатор собственности и регистрацию государственной статической чтения DependencyProperty MyValueProperty = IsValidMyValue( object value ) { int newValue = ( int ) value ; return newValue >= 0 && newValue <= 10; } // DP: coercion callback private static object CoerceMyValue(DependencyObject d, object value ) { MyButton ctrl = (MyButton)d; int newValue = ( int ) value ; return ctrl.IsEnabled ? DP: проверка обратного вызова частных статических Ьоо IsValidMyValue (значение объекта) {Int NewValue = (INT) стоимость; возвращение NewValue> = 0 && NewValue <= 10;} / / DP: принуждение обратного вызова частных статических объектов CoerceMyValue (DependencyObject г, значение объекта) {MyButton Ctrl = (MyButton) г; Int NewValue = (INT) стоимость; возвращение ctrl.IsEnabled? OnMyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { MyButton ctrl = (MyButton)d; int oldValue = ( int )e.OldValue; int newValue = ( int )e.NewValue; ctrl.OnMyValueChanged(oldValue, newValue); } // RE: changed event identifier & registration public static readonly RoutedEvent MyValueChangedEvent = EventManager.RegisterRoutedEvent( "MyValueChanged" , RoutingStrategy.Bubble, typeof (RoutedPropertyChangedEventHandler< int >), typeof (MyButton)); // RE: changed event public event RoutedPropertyChangedEventHandler< int > MyValueChanged; // RE: use a protected virtual to raise event protected virtual void OnMyValueChanged( int oldValue, int newValue) { RoutedPropertyChangedEventArgs< int > e = new RoutedPropertyChangedEventArgs< int >(oldValue, newValue); e.RoutedEvent = MyValueChangedEvent; RaiseEvent(e); } // DP: event handler to trigger re-calculation of MyValue because of dependency change private static void OnIsEnabledChanged( object sender, DependencyPropertyChangedEventArgs e) { MyButton ctrl = (MyButton)sender; ctrl.CoerceValue(MyButton.MyValueProperty); } public MyButton() { IsEnabledChanged += OnIsEnabledChanged; } } /// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : Window { public Window1() { InitializeComponent(); } private void mybtn_Click( object sender, RoutedEventArgs e) { } private void mybtn_MyValueChanged( object sender, RoutedPropertyChangedEventArgs< int > e) { int oldValue = e.OldValue; int newValue = e.NewValue; } } } Math.min (5 NewValue): Math.max (6 NewValue);} / / DP: изменилась обратного вызова частной статической силы OnMyValueChanged (DependencyObject г, DependencyPropertyChangedEventArgs е) {MyButton Ctrl = (MyButton) г; Int OldValue = (INT ) e.OldValue; Int NewValue = (INT) e.NewValue; ctrl.OnMyValueChanged (OldValue, NewValue);} / / RE: измененный идентификатор события и регистрации государственной статической чтения RoutedEvent MyValueChangedEvent = EventManager.RegisterRoutedEvent ("MyValueChanged", RoutingStrategy. Пузырь, TypeOf (RoutedPropertyChangedEventHandler <Int>), TypeOf (MyButton)) / / RE: событие изменения общественных RoutedPropertyChangedEventHandler событие <Int> MyValueChanged / / RE: использовать виртуальные защищенные поднять событие защищенных виртуальных недействительными OnMyValueChanged (INT OldValue, Int NewValue) {RoutedPropertyChangedEventArgs <Int> е = новый RoutedPropertyChangedEventArgs <Int> (OldValue, NewValue); e.RoutedEvent = MyValueChangedEvent; RaiseEvent (е);} / / DP: обработчик события, чтобы вызвать пересчет MyValue из-за зависимость изменения частных статической силы OnIsEnabledChanged (объект отправителя, DependencyPropertyChangedEventArgs е) {MyButton Ctrl = (MyButton) отправителя; ctrl.CoerceValue (MyButton.MyValueProperty);} общественной MyButton () {IsEnabledChanged + = OnIsEnabledChanged;}} / / / <summary> / / / Взаимодействие логики Window1.xaml / / / </ резюме> общественный частичного класса Window1: Window {общественных Window1 () {InitializeComponent ();} частных недействительным mybtn_Click (объект отправителя, RoutedEventArgs е) {} частных недействительным mybtn_MyValueChanged (объект отправителя, RoutedPropertyChangedEventArgs <Средний> е) {INT OldValue = e.OldValue; Int NewValue = e.NewValue;}}}
Как свойстве зависимостей работа по WPF
Чтобы продемонстрировать, как свойство зависимостей работает на WPF, я поставил точку останова на большинство функций, и использовать открывшееся окно отладки для проверки ценностей и изменение состояния.
Регистрация
- При запуске программы, DependencyProperty.Register выполнена, что, в свою очередь, вызывает IsValidMyValue для проверки по умолчанию значение 0:
Эффективное значение
Нажмите на экземпляр MyButton, чтобы вызвать точку останова на обработчик события mybtn_Click, а затем использовать открывшееся окно, чтобы проверить имущество MyValue. Как будет показано ниже:
- Хотя mybtn.MyValue не назначен никакой ценности, она имеет эффективное значение 0, то по умолчанию значение этого свойства зависимостей, как показали результаты mybtn.MyValue и mybtn.GetValue.
- Важно, чтобы оболочка CLR и GetValue / SetValue звонки всегда должны иметь тот же эффект. Это делается путем обертки CLR быть просто оболочкой для GetValue / SetValue и ничего более.
- mybtn.ReadLocalValue (MyButton.MyValueProperty) результаты показывают, что нет локального хранилища для MyValue собственности на экземпляр mybtn.
- Когда mybtn.MyValue принимает начальное значение, нет уведомления об изменении, а OnMyValueChanged не вызывается при запуске программы.
Проверка
В открывшееся окно установить mybtn.MyValue недопустимое значение -1, введя команду "mybtn.MyValue = -1":
- Обертки CLR называется, которая в свою очередь, вызывает SetValue, который вызывает проверку на новое значение -1.
- IsValidMyValue возвращает ложь на новое значение -1, WPF свойство системы, то бросает ArgumentException:
- Проверьте значение MyValue по трем направлениям: CLR обертки, GetValue, ReadLocalValue: ничего не изменилось. Это важно, так как на Silverlight состояние меняется, даже недействительными набор операций, поэтому мы должны написать код для восстановления состояния, как я покажу во второй части серии.
Принуждение
Опять же в окне интерпретации, попробуйте установить mybtn.MyValue до 8, что справедливо, а потому, что mybtn.IsEnabled правда, эффективное значение должно быть меньше или равна 5:
- Во-первых, проверка призвал новое значение 8, через CLR обертки и SetValue:
- IsValidMyValue (8) возвращает истину, так что логика принуждения CoerceMyValue называется, через CLR обертки, SetValue, UpdateEffectiveValue и ProcessCoerceValue:
- CoerceMyValue (8) возвращает разные значения 5, так WPF собственности системных вызовов IsValidMyvalue на принудительное эффективное значение 5:
- IsValidMyValue (5) возвращает истину, так WPF собственности системных вызовов изменилась логика OnMyValueChanged с OldValue как 0 и как NewValue 5. Оба значения являются эффективными значения, так как не было старое значение в mybtn например, и новое значение на самом деле 8. OnMyValueChanged по очереди называют свои proctected виртуальную версию, которая поднимает MyValueChanged перенаправленного события, и выполняет MyValueChanged обработчиков событий, если есть.
- Проверить стоимость имущества MyValue: как CLR обертки и GetValue возвращает новое эффективное значение 5, но GetLocalValue возвращает 8, вспоминая исходный запрос пользователя. Это очень важно, как мы увидим дальше.
Динамический Перерасчет как зависимость изменения
Теперь набор давайте mybtn.IsEnabled в false, в окне Интерпретация. Это меняет одну из зависимостей MyValue, которые должны вызывать пересчет эффективной стоимости имущества MyValue, если я правильно закодированы (что я и сделал, конечно,
:
- Во-первых, обратите внимание, что IsEnabled значение изменений обработка является синхронным, то есть, OnIsEnabledChanged вызывается перед "mybtn.IsEnabled = ложь" возвращается. Это важно, поскольку IsEnabled значение изменений обработка асинхронных на Silverlight.
- Мы должны инициировать пересчет, так как система свойств не знает MyValue зависит от IsEnabled. Это делается путем обработки IsEnabledChanged события (IsEnabledChanged + = OnIsEnabledChanged в MyButton ()) и назовем CoerceValue изнутри OnIsEnabledChanged обработчик событий.
- CoerceValue в Трун называет UpdateEffectiveValue, ProcessCoerceValue, а затем наша логика принуждения CoerceMyValue. Обратите внимание, что CoerceMyValue называется с NewValue 8, а не текущее действующее значение 5. Оригинальный набор параметров запроса 8 помнят mybtn например локально, как показал результат ReadLocalValue раньше. Это важно, поскольку Silverlight не делает этого, что делает реализацию принуждения очень сложно, как будет показано в п. 3 в блоге серии с ошибками в Silverlight RangeBase и его подклассы, как Slider и ScrollBar.
- С mybtn.IsEnabled сейчас ложно, первоначально запрошенный значение 8 вступает в силу, так CoerceMyValue возвращает 8.
- 8 отличается от предыдущих эффективного значения 5, так UpdateEffectieValue называет NotifyPropertyChange, что в конечном итоге вызывает наша изменилась логика OnMyValueChanged с OldValue 5 и NewValue 8. Это происходит даже несмотря на отсутствие новой операции набора собственности MyValue. Это IsEnabled значение зависимость изменения, которые вызвали MyValue эффективное изменение стоимости, которая превращается в триггеры уведомления об изменении и обработки событий в собственность MyValue, и это все происходит синхронно.
- После того, как принуждение, уведомление об изменениях и заканчивается обработка событий, проверьте значение свойства MyValue: теперь все три метода (mybtn.MyValue, mybtn.GetValue, mybtn.ReadLocalValue) возвращает тот же результат.
- последний эксперимент: тип mybtn.ClearValue (MyButton.MyValueProperty) в окне интерпретации, чтобы очистить местного значения (8) свойства зависимостей, которые, в свою очередь, вызывает изменения логики обработки OnMyValueChanged с NewValue 0, новых эффективных значение, полученное из MyValue в свойство по умолчанию.
Заключение
Как мы видим из примера и опыта, WPF имеет мощную систему собственности, что делает исполнителей, так и используя зависимость свойств очень легко. Это гораздо сложнее, на Silverlight, хотя, потому что Silverlight реализует только подмножество функциональности WPF. Во второй части поста серии, я повторно реализовать на примере Silverlight, а также продемонстрировать разницу между WPF и Silverlight. В третьей части, я буду использовать RangeBase и NumericUpDown продемонстрировать Гоча на Silverlight, которая является оригинальной мотивация на этот пост серии. Оставайтесь с нами!
PS: первый пост серии оказался намного дольше и больше времени, чем я ожидал, так что я буду останавливаться на достигнутом. Если есть достаточный интерес, я могу загрузить проект для Вас, чтобы эксперимент, и фрагмент кода, который обеспечивает большую часть сантехники для реализации свойства зависимостей WPF. Спасибо!










Последние комментарии