| Украина |
Проектирование приложения с учетом использования единого опыта разработки для настольных и 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 этот класс даже не был реализован.