Опубликован: 13.12.2011 | Доступ: свободный | Студентов: 1020 / 34 | Оценка: 4.29 / 4.57 | Длительность: 13:56:00
Лекция 10:

Проектирование приложения с учетом использования единого опыта разработки для настольных и Web-проектов

Решение проблем недостающего функционала

Отсутствие FrameworkPropertyMetadata

Как известно, в Silverlight отсутствует класс FrameworkPropertyMetadata. Одна из наиболее часто используемых возможностей этого FrameworkPropertyMetadata по сравнению с имеющемся в Silverlight PropertyMetadata – это логические переключатели, которые управляют влиянием свойства на различные аспекты прорисовки объекта, такие как AffectsMeasure, AffectsArrange, и другие. Если один из флагов установлен в истинное значение, соответствующий аспект объявляется недействительным.

К счастью, данное поведение достаточно легко эмулировать при помощи PropertyMetadata и PropertyChangedCallback. Так, если в случае разработки только для WPF можно написать:

   1: public static readonly DependencyProperty SomethingProperty
   2:	 = DependencyProperty.Register(
   3:     "Something", typeof(string), typeof(Window1),
   4:     new FrameworkPropertyMetadata(string.Empty,
   5:         FrameworkPropertyMetadataOptions.AffectsMeasure)
   6:         );

То универсальный WPF/Silverlight код, достигающий такого же эффекта, будет выглядеть следующим образом:

   1: public static readonly DependencyProperty SomethingProperty
   2:	 = DependencyProperty.Register(
   3:     "Something", typeof(string), typeof(Window1),
   4:     new PropertyMetadata(string.Empty,
   5:     new PropertyChangedCallback(
   6:	   	Window1.SomethingProperty_Changed)));
   7:  
   8: private static void SomethingProperty_Changed(
   9: 	   DependencyObject d, DependencyPropertyChangedEventArgs e)
  10: {
  11:     ((FrameworkElement)d).InvalidateMeasure();
  12: }

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

Отсутствие приведения значения

WPF имеет достаточно мощный механизм приведения значений зависимых свойств. К сожалению, такого функционала в Silverlight нет. Приведение значений возможно производить при помощи сочетания PropertyChangedCallback и обычных методов класса. В некоторых сложных, но не слишком критичных сценариях, возможно, имеет смысл использовать приведение значения в WPF версии и не проверять его совсем в Silverlight версии.

Отсутствие метода OverrideMetadata()

WPF позволяет переопределять метаданные зависимых свойств при помощи метода OverrideMetadata объекта DependencyProperty. В следующем коде переопределяется ширина по умолчанию у элемента управления, наследованного от класса Control:

   1: static MyControl()
   2: {
   3:     FrameworkPropertyMetadata newMetadata
   4:		= new FrameworkPropertyMetadata();
   5:     newMetadata.DefaultValue = 180.0;
   6:     Control.WidthProperty.OverrideMetadata(typeof(MyControl),
   7:		newMetadata);
   8: }

Так как в Silverlight такой метод отсутствует, при необходимости изменить значение по умолчанию какого-либо свойства необходимо просто установить это значение в конструкторе:

   1: public MyControl()
   2: {
   3:     this.Width = 180;
   4: }

Другая возможность метода OverrideMetadata() – это задание обработчика изменения зависимого свойства базового класса:

   1: static MyControl()
   2: {
   3: #if !SILVERLIGHT
   4:     FrameworkPropertyMetadata newMetadata
   5:		 = new FrameworkPropertyMetadata();
   6:     newMetadata.PropertyChangedCallback
   7:		 += MyControl.VisibilityProperty_Changed;
   8:     Control.VisibilityProperty.OverrideMetadata(
   9:		typeof(MyControl), newMetadata);
  10: #endif
  11: }

После этого в методе VisibilityProperty_Changed можно обрабатывать все изменения зависимого свойства Visibility.

   1: private static void VisibilityProperty_Changed(
   2:	   DependencyObject d, DependencyPropertyChangedEventArgs e)
   3: {
   4:     // do something here
   5: }

К сожалению, т.к. данного метода в Silverlight нет совсем, изящного решения данной проблемы не существует. Однако можно воспользоваться не очень чистым приемом, включающим определение дополнительного зависимого свойства и привязку исходного свойства к нему. Аналог приведенного выше примера для Silverlight приложения в таком случае будет выглядеть следующим образом:

   1: #if SILVERLIGHT
   2: // объявление дополнительного зависимого свойства, которое
   3: // использует такой же PropertyChangedCallback, что и WPF код
   4: private static readonly DependencyProperty 
   5:   VisibilityChangedWorkaroundProperty
   6:	  = DependencyProperty.Register(
   7:     "VisibilityChangedWorkaround", typeof(Visibility),
   8:	   typeof(MyControl),
   9:     new PropertyMetadata(MyControl.VisibilityProperty_Changed));
  10: #endif
  11:  
  12: public MyControl()
  13: {
  14: #if SILVERLIGHT
  15:     // привязка дополнительного зависимого свойства
  16:     Binding visibilityBnd = new Binding("Visibility");
  17:     visibilityBnd.Source = this;
  18:     visibilityBnd.Mode = BindingMode.TwoWay;
  19:     this.SetBinding(
  20:		MyControl.VisibilityChangedWorkaroundProperty,
  21:		visibilityBnd);
  22: #endif
  23: }

Как видно из примера, определено дополнительное зависимое свойство VisibilityChangedWorkaroundProperty, и в качестве его обработчика изменения указан тот же метод, что и в WPF коде, приведенном выше. Нет необходимости создавать CLR свойство-обертку для дополнительного свойства, и само дополнительное зависимое свойство определено как приватное, так что оно доступно только внутри класса и невидимо для внешних компонент.

Затем в конструкторе дополнительное свойство привязывается к свойству Visibility базового класса. Таким образом, когда свойство базового класса изменится, дополнительное свойство также будет изменено в результате срабатывания привязки, после чего будет вызван обработчик VisibilityProperty_Changed.

Отсутствие зависимых свойств только для чтения

Одна из действительно удобных возможностей WPF – определение зависимых свойств только для чтения. Такое свойство полезно, если необходимо отобразить какой-либо статус объекта, определяемый его внутренним состоянием (как, например, свойство IsMouseOver), и разработчик не хочет позволять внешним компонентам изменять данное значение извне. Такого эффекта можно с легкостью достичь при использовании обычных .NET свойств, но что если необходимо создать свойство только для чтения, которое будет обладать всеми возможностями зависимого свойства?

К сожалению, не существует простого решения для Silverlight. Безусловно, можно создать CLR свойство-обертку для зависимого свойства без метода доступа set, однако при этом любой компонент может установить значение свойству при помощи метода SetValue().

Таким образом, в данном случае приходится выбирать: совсем не использовать зависимые свойства только для чтения или использовать разделение кода (при помощи директив препроцессора или разделяемых классов) между WPF и Silverlight. Следует помнить, что в данном случае придется использовать различный код не только для объявление зависимого свойства, но и в вызове SetValue (т.к. необходимо вызывать SetValue для DependencyPropertyKey в случае зависимого свойства только для чтения). Легко понять, что в таком случае исходный код будет достаточно трудночитаем из-за многочисленных ветвлений препроцессора #if SILVERLIGHT.

Отсутствие класса Brushes

Класс Brushes в WPF содержит предопределенные сплошные кисти, которые достаточно полезны при использовании кистей в коде.

   1: border.Background = Brushes.White;

Данный класс отсутствует в Silverlight. При написании WPF/Silverlight совместимого кода следует использовать класс Colors, который достаточно похож на класс Brushes, только вместо предопределенных кистей содержит предопределенные цвета. Таким образом, код выше может быть переписан на:

   1: border.Background = new SolidColorBrush(Colors.White);

Конечно, символов стало больше, однако это лучше, чем дублирование кода из-за настолько небольшого отличия.

При использовании именованных кистей в XAML отличий между Silverlight и WPF нет:

   1: <Border Name="border" Background="White">  
 2: </Border>
Stroke и Pen

В WPF определен класс Pen, в то время как в Silverlight его нет. Класс Pen достаточно удобно инкапсулирует способ рисования контура фигуры в единственном объекте, который возможно повторно использовать. Так, в WPF он применяется в классе GeometryDrawing (которого, кстати, также нет в Silverlight).

Следует отметить, что во многих случаях даже в WPF его невозможно использовать – вместо него приходится применять свойства StrokeXxx. По-видимому, по этой причине в Silverlight этот класс даже не был реализован.