DependencyProperty: проверка, принуждение и изменению Обработка (Часть III: свойства зависимостей фрагментов кода)
Введение
Это последняя часть из трех частей о том, как реализовать свойство зависимостей с проверкой, принуждения и троеборье на WPF и Silverlight. В первой части , я реализовал простой свойства зависимостей и отлажена до продемонстрировать, как свойство зависимостей работает на WPF и общий шаблон для реализации свойства зависимостей WPF. В части , я сделал то же самое на Silverlight. Потому что Silverlight поддерживает только PropertyChangedCallback, но не CoerceValueCallback и ValidateValueCallback, поэтому проверка, принуждение и обработки всех изменений должны быть реализованы с PropertyChangedCallback. Это вызывает PropertyChangedCallback называют рекурсивно при проверке или принуждения изменения эффективного значения свойства зависимостей. Плюс другие ограничения системы собственности Silverlight, он может получить очень сложно реализовать свойство зависимостей правильно. Это сообщение я покажу некоторые неясные поведения Silverlight управления RangeBase и их причины в дальнейшем продемонстрировать, как сложно это может быть. И наконец, я приведу фрагмент кода, который реализует полную картину зависимость реализации собственности я обсуждал в части , в качестве награды для тех, кто прочитал серию ![]()
Silverlight RangeBase управления
Обзор
ScrollBar, ProgressBar и Slider наследует от RangeBase, который реализует свойства, минимальное, максимальное и значение зависимости, с принуждением ограничение, что минимальное <= значение <= максимум.
Это звучит просто
, Но поведение RangeBase элементов управления можно получить очень странно. Возьмем несколько примеров:
- <Slider X:Name="sl"/> дает вам слайдер с минимальным = 0, значение = 0, Максимальное = 10, а <Slider x:Name="sl2" Minimum="-1" /> выпускает слайдер с минимальным = -1, значение = 0, а максимальная = 0.
- Нажмите на сопротивление пальцем ползунок sl2, чтобы сделать sl2 в центре внимания, нажмите вправо или вверх стрелку, а затем изменить sl2.Maximum положительное число 10, sl2.Value волшебно изменяется от 0 до 0,1.
- Установите свойство sl2 в как значения для Double.NaN, недопустимое значение, исключение будет бросать, но sl2.Value меняется на Double.NaN любом случае, и не ValueChanged событие.
Исходный код
Ниже приведен код, мы будем отлаживать и экспериментировать с:
- Page.xaml:
- page.xaml.cs:
x:Class ="SLApp2.Page" <UserControl х: Class = "SLApp2.Page" XMLNS = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" XMLNS: моя = "CLR-пространство имен: SLApp2" XMLNS: х = "http://schemas.microsoft.com/winfx/2006/xaml"> x:Name ="LayoutRoot" Background ="White" > <StackPanel х: Name = "LayoutRoot" Background = "Белая"> x:Name ="sl" /> <Slider X: Name = "SL" /> x:Name ="sl2" Minimum ="-1" /> <Мой: Slider2 х: Name = "sl2" Минимальное = "-1" /> x:Name ="btn" Content ="Break!" <Кнопка х: Name = "BTN" Content = "! Break" HorizontalAlignment ="Center" /> Click = "btn_Click" HorizontalAlignment, = "Центр" /> > </ StackPanel> > </ UserControl>
использование System.Windows; использование System.Windows.Controls; использование System.Windows.Controls.Primitives; Пространство имен SLApp2 { / / Подкласс слайдер легко сломать при изменении собственности и клавишу событий. открытый класс Slider2: слайдер { oldMinimum, double newMinimum) защита коррекции недействительным OnMinimumChanged (двойной oldMinimum дважды newMinimum) { . база OnMinimumChanged (oldMinimum, newMinimum); } oldMaximum, double newMaximum) защита коррекции недействительным OnMaximumChanged (двойной oldMaximum дважды newMaximum) { база OnMaximumChanged (oldMaximum, newMaximum). } oldValue, double newValue) защита коррекции недействительным OnValueChanged (двойной OldValue дважды NewValue) { база OnValueChanged (OldValue, NewValue). } защита коррекции недействительным OnKeyDown (System.Windows.Input.KeyEventArgs д) { . база OnKeyDown (е); } } общественных частичный класс страницы: UserControl { общественных страницу () { InitializeComponent (); } / / События нажатия кнопки обработчик дает шанс ворваться в отладчик / / И экспериментировать с RangeBase проверки, принуждения и изменения управления. sender, RoutedEventArgs e) частных недействительным btn_Click (объект отправителя, RoutedEventArgs д) { } } }
Отладка и эксперимент
Постройте и запустите выше простого приложения Silverlight, вы увидите ниже скриншоте. Обратите внимание на разницу между большим пальцем место два ползунка:
Нажмите на перерыв! кнопку, чтобы ворваться в его обработчик событий в Visual Studio, проверьте значение слайдера SL и sl2:
- <Slider X:Name="sl"/>: SL имеет правильное значение Mininum = Value = 0, Максимальное = 10, а <Slider x:Name="sl2" Minimum="-1"/> sl2 есть Минимальное = -1, но Максимальное = Value = 0;
Для тех, кто читал Часть II и теперь знакомы с реализацией модели, это основная причина странного поведения sl2 в:
- RangeBase оставляет _initialMax, _initialVal, _requestedMax, _requestedVal для инициализации CLR по умолчанию значение 0. Это нормально для стоимости имущества, так как по умолчанию равно нулю, но это не так для максимального имущество, которое по умолчанию равно 1 в DependencyProperty.Register вызов и используют его значение по умолчанию в качестве эффективного значения:
- Установка sl2.Minimum -1 вызывает OnMinimumPropertyChanged -> CoerceMaximum, который использует CLR инициализируется _requestedMax заставить Максимум. С _requestedMax 0, а текущее действующее значение Максимальная от 1 и 0 оказывается больше текущей стоимости минимальных о-1, так это максимальное изменение в 0:
- Причина <Slider x:Name="sl"> имеет максимальное значение 10 вместо 1 по умолчанию является то, что шаблон по умолчанию имеет <Setter Property="Maximum" Value="10"/>. Эта линия также исправляет проблемы, _requestedMax не инициализирован. Вот почему sl._requestedMax 10 в выше снимке экрана.
Далее, установить точку останова в каждой функции, не говоря выполнения приложения. Выберите палец sl2, чтобы сделать его в фокусе, нажмите вправо или вверх стрелка, он разбивается на OnKeyDown обработчик событий. Проверьте значения в sl2: _requestedVal 0; пошагово base.OnKeyDown (е), проверьте еще раз sl2: _requestedVal становится 0.1. Это потому, что base.OnKeyDown ручки вправо / вверх стрелка удар и пытается увеличить Значение по SmallIncrement 0,1; это вызывает OnValuePropertyChanged -> CoerceValue, которые затем принуждает значение к 0, так как максимальный равен 0, но он помнит просьбе шаг, установив _requestedVal до 0,1.
В то время как еще в открывшееся окно установить sl2.Maximum до 10. Это вызывает OnMaxiumPropertyChanged:
который в свою очередь триггеров CoerceValue, которая проверяет _requestedVal, стоимость которых в настоящее время 0.1. С 0.1 в диапазоне [-1, 10], так что значение изменено на 0.1:
Наконец, давайте установим значение для Double.NaN. Это вызывает ArgumentException, как и ожидалось:
Но значение меняется на Double.NaN любом случае, и не ValueChanged событие происходит из-за ArgumentException:
Зависимость свойств фрагмента кода
К этому моменту вы, вероятно, пришла в голову мысль, что это довольно сложно реализовать свойство зависимостей с проверкой, принуждения и изменения обработки правильно Silverlight. Для облегчения этой задачи, ниже фрагмент кода, который обеспечивает большую часть кода для реализации выше картину. Этот фрагмент кода изначально автором Тед Глаза, и я изменил его представить полную картину. Вы можете удалить материал вам не нужно для простого свойства зависимостей, как те, которые не требуют проверки, принуждения или изменение обработки. Вы по-прежнему необходимо заполнить собственную проверку, принуждения, а также изменять логику обработки, изменив функции, такие как IsValid $ $ имущество, принуждения $ свойство $, о $ свойство $ PropertyChanged, или на $ свойство $ Изменена и т.д., и добавить одно определение частных Int _nestLevel. Но по крайней мере, вам не придется беспокоиться о наиболее различий между ВПП и Silverlight системы собственности, и сосредоточиться только на проверки, принуждения и изменение логики обработки самих себя.
> < Shortcut > sdp </ Shortcut > < Description > Code snippet for a dependency property with validation, coercion and changed event. </ Description > < Author > Ning Свойстве зависимостей </ Title> <> Ярлык SDP </ ярлык> <Описание> Фрагмент кода для свойства зависимостей с проверкой, принуждения и изменил случай. </ Description> <Автор> Нин > < Default > MyProperty </ Default > </ Literal > < Literal > < ID > type </ ID > < ToolTip > Property type </ ToolTip > < Default > object </ Default > </ Literal > < Literal > < ID > defaultValue </ ID > < ToolTip > Default Название </ Подсказка> <по умолчанию> MyProperty </ Default> </ Literal> <литерала> <Код> типа </ ID> <Подсказка> Тип недвижимости </ Подсказка> <по умолчанию> объект </ Default> </ Literal> < Буквальный> <ID> DefaultValue </ ID> <подсказка> Default > /// Gets or sets the value of $property$ dependency property. общественные $ вида $ $ $ собственность / / / <резюме> / / / Возвращает или задает значение $ свойство $ свойства зависимостей. > public $type$ $property$ { get { return ($type$)GetValue($property$Property); } set { SetValue($property$Property, value); } } /// < summary > /// Identifies the $property$ dependency property. / / / </ Резюме> общественный $ вида $ $ свойство $ {get {возврата ($ типа $) GetValue ($ $ собственность недвижимости);} множество {SetValue ($ $ собственность имущества, стоимость);}} / / / < резюме> / / / Определяет свойство $ $ свойства зависимостей. > public static readonly $SystemWindowsDependencyProperty$ $property$Property = $SystemWindowsDependencyProperty$.Register( "$property$", typeof($type$), typeof($classname$), new $SystemWindowsPropertyMetadata$($defaultValue$, On$property$PropertyChanged)); /// < summary > /// $property$Property property changed handler. / / / </ Резюме> общественности статической чтения SystemWindowsDependencyProperty $ $ $ $ собственность недвижимости = $ SystemWindowsDependencyProperty $. Регистрация ("$ $ собственность", TypeOf ($ типа $), TypeOf ($ ClassName $), $ новое SystemWindowsPropertyMetadata $ ($ DefaultValue $ О $ свойство $ PropertyChanged)); / / / <резюме> / / / $ $ собственность недвижимости изменился обработчик. > /// < param name ="d" > $classname$ that changed its $property$. </ param > /// < param name ="e" > Event arguments. </ param > private static void On$property$PropertyChanged($SystemWindowsDependencyObject$ d, $SystemWindowsDependencyPropertyChangedEventArgs$ e) { $classname$ source = ($classname$)d; $type$ newValue = ($type$)e.NewValue; $type$ oldValue = ($type$)e.OldValue; // validate newValue if (!IsValid$property$(newValue)) { // revert back to e.OldValue source._nestLevel++; source.SetValue(e.Property, e.OldValue); source._nestLevel--; // throw ArgumentException throw new ArgumentException("Invalid $property$Property value", "e"); } if (source._nestLevel == 0) { // remember initial state source._initial$property$ = oldValue; source._requested$property$ = newValue; } source._nestLevel++; // coerce newValue $type$ coercedValue = ($type$)Coerce$property$(d, e.NewValue); if (newValue != coercedValue) { // always set $property$Property to coerced value source.$property$ = coercedValue; } source._nestLevel--; if (source._nestLevel == 0 && source.$property$ != source._initial$property$) { // fire changed event only at root level and when there is indeed a change source.On$property$Changed(oldValue, source.$property$); } } /// < summary > /// $property$Property validation handler. / / / </ Резюме> / / / <имя параметра = "г"> $ $ имя класса, который изменил свою собственность $ $. </ Параметр> / / / <имя параметра = "е"> Аргументы событий. </ Параметров> частные статической силы на $ свойство $ PropertyChanged ($ SystemWindowsDependencyObject $ г, $ SystemWindowsDependencyPropertyChangedEventArgs $ е) {$ $ ClassName источник = ($ ClassName $) г, $ типа $ NewValue = ($ типа $) e.NewValue, $ типа $ OldValue = ($ типа $) e.OldValue / / проверка NewValue если {/ / вернуться к e.OldValue source._nestLevel + + (IsValid $ свойство $ (NewValue!)); source.SetValue (e.Property, e.OldValue) ; source._nestLevel-- / / выбросить ArgumentException пролить новый ArgumentException ("Неверная $ свойство $ Стоимость недвижимости", "д");} если (source._nestLevel == 0) {/ / запоминаем начальное состояние source._initial $ имущество $ = OldValue; source._requested $ свойство $ = новое;} source._nestLevel + +; / / принудить NewValue $ типа $ coercedValue = ($ типа $) принуждать $ свойство $ (г, e.NewValue);!, если (NewValue = coercedValue ) {/ / всегда равен $ свойство $ недвижимости принудительной исходного значения $ свойство $ = coercedValue,.} source._nestLevel--;.! если (source._nestLevel == 0 && $ источник собственность $ = source._initial $ свойство $ ) {/ / огонь событие изменения только на корневом уровне и, когда это действительно изменение source.On $ свойство $ изменилась (OldValue, источник собственность $ $).}} / / / <резюме> / / / $ $ собственность недвижимость Проверка обработчиков. > /// < param name ="value" > New value of $property$Property. </ param > /// < returns > /// Returns true if value is valid for $property$Property, false otherwise. / / / </ Резюме> / / / <имя параметра = "значение"> Новое значение $ $ собственность имущества. </ Параметр> / / / <возврат> / / / Возвращает истину, если значение является допустимым для $ $ собственность недвижимость , в противном случае. > private static bool IsValid$property$($type$ value) { return true; } /// < summary > /// $property$Property coercion handler. / / / </ Возвращает> частные статические BOOL IsValid $ свойство $ ($ типа $ значение) {return TRUE;} / / / <резюме> / / / $ $ собственность недвижимости принуждения обработчик. > /// < param name ="d" > $classname$ that changed its $property$. </ param > /// < param name ="value" > Event arguments. </ param > /// < returns > /// Coerced effective value of $property$Property from input parameter value. / / / </ Резюме> / / / <имя параметра = "г"> $ $ имя класса, который изменил свою собственность $ $. </ Параметр> / / / <имя параметра = "значение"> Event аргументов. </ Параметров> / / / <возврат> / / / Принудительные эффективное значение $ $ собственность имущества из входного параметра. > private static object Coerce$property$($SystemWindowsDependencyObject$ d, object value) { $classname$ source = ($classname$)d; $type$ newValue = ($type$)value; return newValue; } /// < summary > /// $property$Property changed event. / / / </ Возвращает> частных статических принудить объект $ свойство $ ($ SystemWindowsDependencyObject $ D, значение объекта) {$ $ ClassName источник = ($ ClassName $) г, $ типа $ NewValue = ($ типа $) стоимости; возвращение NewValue ;} / / / <резюме> / / / $ $ собственность недвижимости изменилась события. > public event RoutedPropertyChangedEventHandler < $type$ > $property$Changed; /// < summary > /// Called by On$property$PropertyChanged static method to fire $property$Changed event. / / / </ Резюме> общественный RoutedPropertyChangedEventHandler событие <$ типа $> $ свойство $ изменилась / / / <резюме> / / / Вызывается О $ свойство $ PropertyChanged статический метод стрелять $ свойство $ Изменен события. > /// < param name ="oldValue" > The old value of $property$. </ param > /// < param name ="newValue" > The new value of $property$. </ param > protected virtual void On$property$Changed($type$ oldValue, $type$ newValue) { RoutedPropertyChangedEventArgs < $type$ > e = new RoutedPropertyChangedEventArgs < $type$ > (oldValue, newValue); if ($property$Changed != null) { $property$Changed(this, e); } } /// < summary > /// Cached previous value of $property$Property. / / / </ Резюме> / / / <имя параметра = "OldValue"> старое значение $ свойство $. </ Параметр> / / / <имя параметра = "NewValue"> новое значение $ свойство $. < / параметр> защищенных виртуальных недействительными О $ свойство $ изменилась ($ типа $ OldValue, $ типа $ NewValue) {RoutedPropertyChangedEventArgs <$ типа $> е = новый RoutedPropertyChangedEventArgs <$ типа $> (OldValue, NewValue), если ($ свойство $ Изменена ! = NULL) {$ свойство $ изменен (это, е);}} / / / <резюме> / / / Снимок предыдущее значение $ свойство $ собственности. > private $type$ _initial$property$ = $defaultValue$; /// < summary > /// Cached originally requested value of $property$Property by user. / / / </ Резюме> частные $ типа $ _initial $ свойство $ = $ DefaultValue $ / / / <резюме> / / / Снимок изначально просил стоимостью $ свойство $ собственности пользователем. > private $type$ _requested$property$; #endregion public $type$ $property$ ]] > </ Code > </ Snippet > </ CodeSnippet > </ CodeSnippets > / / / </ Резюме> частные $ типа $ _requested $ свойство $, # EndRegion общественной $ вида $ $ $ имущество]]> </ Код> </ Snippet> </ CodeSnippet> </ CodeSnippets>
Заключение
Я надеюсь, что серия помогла вам в понимании и реализации зависимость свойств WPF и Silverlight. Как WPF и Silverlight большие платформы, и будет иметь фундаментальное значение для разработки программного обеспечения, возможно, больше, чем Win32 раньше.
Как всегда, отзывы, предложения, поправки приветствуются. Спасибо!








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