Главная > Silverlight , WPF > DependencyProperty: проверка, принуждение и изменению Обработка (Часть I: WPF)

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:

    validation called for default value

Эффективное значение

Нажмите на экземпляр 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 не вызывается при запуске программы.

    DP has default value, no local storage

Проверка

В открывшееся окно установить mybtn.MyValue недопустимое значение -1, введя команду "mybtn.MyValue = -1":

  • Обертки CLR называется, которая в свою очередь, вызывает SetValue, который вызывает проверку на новое значение -1.

    validation called for setting value
  • IsValidMyValue возвращает ложь на новое значение -1, WPF свойство системы, то бросает ArgumentException:

    validation failure triggers argument exception in setting
  • Проверьте значение MyValue по трем направлениям: CLR обертки, GetValue, ReadLocalValue: ничего не изменилось. Это важно, так как на Silverlight состояние меняется, даже недействительными набор операций, поэтому мы должны написать код для восстановления состояния, как я покажу во второй части серии.

    No state change after invalid set operation

Принуждение

Опять же в окне интерпретации, попробуйте установить mybtn.MyValue до 8, что справедливо, а потому, что mybtn.IsEnabled правда, эффективное значение должно быть меньше или равна 5:

  • Во-первых, проверка призвал новое значение 8, через CLR обертки и SetValue:

    validation called during setting
  • IsValidMyValue (8) возвращает истину, так что логика принуждения CoerceMyValue называется, через CLR обертки, SetValue, UpdateEffectiveValue и ProcessCoerceValue:

    Coercion called during setting
  • CoerceMyValue (8) возвращает разные значения 5, так WPF собственности системных вызовов IsValidMyvalue на принудительное эффективное значение 5:

    Coerced value goes through validation as well
  • IsValidMyValue (5) возвращает истину, так WPF собственности системных вызовов изменилась логика OnMyValueChanged с OldValue как 0 и как NewValue 5. Оба значения являются эффективными значения, так как не было старое значение в mybtn например, и новое значение на самом деле 8. OnMyValueChanged по очереди называют свои proctected виртуальную версию, которая поднимает MyValueChanged перенаправленного события, и выполняет MyValueChanged обработчиков событий, если есть.
    Changed callback to raise changed event
  • Проверить стоимость имущества MyValue: как CLR обертки и GetValue возвращает новое эффективное значение 5, но GetLocalValue возвращает 8, вспоминая исходный запрос пользователя. Это очень важно, как мы увидим дальше.

    Coercion causes local value and effective value difference

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

Теперь набор давайте 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.

    Dependency change triggers coercion
  • С mybtn.IsEnabled сейчас ложно, первоначально запрошенный значение 8 вступает в силу, так CoerceMyValue возвращает 8.
  • 8 отличается от предыдущих эффективного значения 5, так UpdateEffectieValue называет NotifyPropertyChange, что в конечном итоге вызывает наша изменилась логика OnMyValueChanged с OldValue 5 и NewValue 8. Это происходит даже несмотря на отсутствие новой операции набора собственности MyValue. Это IsEnabled значение зависимость изменения, которые вызвали MyValue эффективное изменение стоимости, которая превращается в триггеры уведомления об изменении и обработки событий в собственность MyValue, и это все происходит синхронно.

    Coercion triggers change notification
  • После того, как принуждение, уведомление об изменениях и заканчивается обработка событий, проверьте значение свойства MyValue: теперь все три метода (mybtn.MyValue, mybtn.GetValue, mybtn.ReadLocalValue) возвращает тот же результат.

    Effective value and local value the same
  • последний эксперимент: тип mybtn.ClearValue (MyButton.MyValueProperty) в окне интерпретации, чтобы очистить местного значения (8) свойства зависимостей, которые, в свою очередь, вызывает изменения логики обработки OnMyValueChanged с NewValue 0, новых эффективных значение, полученное из MyValue в свойство по умолчанию.

    ClearValue triggers change handling

Заключение

Как мы видим из примера и опыта, WPF имеет мощную систему собственности, что делает исполнителей, так и используя зависимость свойств очень легко. Это гораздо сложнее, на Silverlight, хотя, потому что Silverlight реализует только подмножество функциональности WPF. Во второй части поста серии, я повторно реализовать на примере Silverlight, а также продемонстрировать разницу между WPF и Silverlight. В третьей части, я буду использовать RangeBase и NumericUpDown продемонстрировать Гоча на Silverlight, которая является оригинальной мотивация на этот пост серии. Оставайтесь с нами!

PS: первый пост серии оказался намного дольше и больше времени, чем я ожидал, так что я буду останавливаться на достигнутом. Если есть достаточный интерес, я могу загрузить проект для Вас, чтобы эксперимент, и фрагмент кода, который обеспечивает большую часть сантехники для реализации свойства зависимостей WPF. Спасибо!

Быть общительным, Share!
  1. Пока нет комментариев.
  1. Пока что нет уведомлений.


пять + = 12