Главная » Разработка для Windows Phone 7 » В чем отличие своиств-зависимостеи Windows Phone 7

0

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

Локальные параметры имеют приоритет над

Параметрами стиля, которые являются более приоритетными, чем

Стиль темы, который является более приоритетным, чем

Унаследованные свойства, которые имеют приоритет над

Значениями по умолчанию

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

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

Silverlight обеспечивает инфраструктуру для управления всеми возможными способами задания свойств и устанавливает тем самым некоторый порядок. Свойства-зависимости являются основной составляющей этой инфраструктуры. Их называют свойствами- зависимостями потому, что они зависят от ряда внешних факторов и выступают посредниками между ними и приложением.

Свойства-зависимости строятся на базе существующих .NET-свойств. Для этого надо немного потрудиться и пописать код, но вы будете создавать свойства-зависимости автоматически уже до того, как осознаете это.

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

Практически все свойства классов Silverlight, с которыми мы до сих пор сталкивались, были на самом деле свойствами-зависимостями. Проще назвать свойства, которые ими не были бы! Первыми на ум приходят свойство Children класса Panel и свойство Text класса Run[15].

Все классы, реализующие свойства-зависимости, должны наследоваться от DependencyObject. DependencyObject – один из базовых классов в иерархии классов Silverlight. Многие классы

Silverlight наследуются от него, включая и самый значимый: UIElement. Это означает, что класс Button наследуется от DependencyObject, из чего, несомненно, следует, что любой класс, унаследованный от Button, может реализовывать свойства-зависимости.

Класс BetterGradientButton (Более сложная кнопка с градиентом) со свойствами- зависимостями начинается, как обычно:

public class BetterGradientButton : Button {

}

Как и NaiveGradientButton, BetterGradientButton описывает два свойства: Color1 и Color2. Свойства-зависимости начинаются с открытого поля типа DependencyProperty, имя которого совпадает с именем свойства, но с добавлением слова Property. Итак, первым шагом в определении свойства Color1 в классе BetterGradientButton является описание открытого поля типа DependencyProperty под именем Color1Property.

public class BetterGradientButton : Button {

public static readonly DependencyProperty Color1Property = DependencyProperty.Register("Color1", typeof(Color),

typeof(BetterGradientButton),

new PropertyMetadata(Colors.Black, OnColorChanged));

}

Это не просто поле, но открытое статическое поле, и обычно оно также является полем только для чтения (это означает, что после того, как оно определено, его нельзя изменить). DependencyProperty (Свойство-зависимость) создается для определенного класса и после этого не меняется и используется совместно всеми экземплярами этого класса.

Как правило, объект типа DependencyProperty создается путем вызова статического метода DependencyProperty.Register (Зарегистрировать). (Единственным исключением являются присоединенные свойства.) Первый аргумент – это текстовая строка имени свойства; второй аргумент типа свойства, в данном случае это Color; третий аргумент – это класс, описывающий свойство, в данном примере это класс BetterGradientButton.

Последний аргумент – объект типа PropertyMetadata (Метаданные свойства). В конструкторе PropertyMetadata могут быть предоставлены только два типа сведений. Первое – значение свойства по умолчанию, т.е. значение, используемое для свойства, если оно не задано никаким другим способом. Если для ссылочного типа не обеспечить значение по умолчанию, оно принимается равным null. Для типа значения – это значение по умолчанию этого типа.

Я решил, что значением по умолчанию свойства Color1 должно быть Colors.Black.

Вторая часть конструктора PropertyMetadata – имя обработчика, вызываемого при изменении значения свойства. Этот обработчик вызывается, только если значение свойства действительно меняется. Например, если значением свойства по умолчанию является Colors.Black, и свойству присваивается значение Colors.Black, обработчик события изменения значения свойства вызываться не будет.

Кажется странным для сущности, названной свойством-зависимостью типа DependencyProperty с именем Color1Property, быть определенной как поле, но так оно и есть.

Эти два класса, DependencyObject и DependencyProperty, легко спутать. Все классы, имеющие свойства-зависимости, должны наследоваться от DependencyObject, так же как обычные

классы наследуются от Object. После этого класс создает объекты типа DependencyProperty, как и любой другой обычный класс определял бы обычные свойства.

Нет необходимости определять весь DependencyProperty в статическом поле. Некоторые разработчики предпочитают инициализировать поле DependencyProperty в статическом конструкторе:

public class BetterGradientButton : Button {

public static readonly DependencyProperty Color1Property;

static BetterGradientButton() {

Color1Property = DependencyProperty.Register("Color1",

typeof(Color),

typeof(BetterGradientButton),

new PropertyMetadata(Colors.Black, 0nColorChanged));

}

}

На самом деле между этими двумя техниками нет разницы.

Кроме статического поля типа DependencyProperty, нам понадобиться обычное описание .NET-свойства для свойства Color1:

public class BetterGradientButton : Button {

public static readonly DependencyProperty Color1Property = DependencyProperty.Register("Color1", typeof(Color),

typeof(BetterGradientButton),

new PropertyMetadata(Colors.Black, 0nColorChanged));

public Color Color1 {

set { SetValue(Color1Property, value); }

get { return (Color)GetValue(Color1Property); }

}

}

Это описание свойства Color1 является стандартным. Метод доступа set (задать) вызывает SetValue, который ссылается на свойство-зависимость Color1Property, и метод доступа get (получить) вызывает метод GetValue (Получить значение), также ссылающийся на Color1Property.

Откуда взялись эти два метода, SetValue и GetValue? SetValue и GetValue – два открытых метода, которые описаны DependencyObject и наследуются всеми производными от него классами. Обратите внимание, что второй аргумент в SetValue – это значение, которое было задано свойству. Возвращаемое значение GetValue типа object, поэтому оно должно быть приведено к Color.

В связи со свойствами-зависимостями описание свойства Color1 можно назвать описанием CLR-свойства (свойства общеязыковой среды выполнения .NET), чтобы отличить его от объекта DependencyProperty, определенного как открытое статическое поле. Иногда говорят, что CLR-свойство Color1 «продублировано» свойством-зависимостью Color1Property. Такая терминология удобна, если вы хотите различить описание свойства от описания открытого статического поля. Но так же часто обе части – и открытое статическое поле, и описание свойства – вместе называют «свойством-зависимостью» или (для самых крутых) «DP[16]»"

Очень важно, чтобы CLR-свойство не делало ничего, кроме вызова методов SetValue и GetValue, здесь не место для проведения каких-либо проверок достоверности или обработки событий изменения значения свойства. Причиной этому является то, что мы никогда не можем точно знать, как задается свойство-зависимость. Можно подумать, что свойство всегда задается так:

btn.Color1 = Colors.Red;

Но методы SetValue и GetValue, описанные DependencyObject, являются открытыми, поэтому свойство вполне может быть задано и так:

btn.SetValue(GradientButton2.Color1Property, Colors.Red);

Или оно может быть задано способом, известным только создателям Silverlight.

С другой стороны, не следует ошибочно полагать, что CLR-свойство можно опустить. Иногда, если задать только поле DependencyProperty и забыть о CLR-свойстве, некоторые вещи не работают.

Рассмотрим также класс, определяющий DependencyProperty и CLR-свойство для Color2:

public class BetterGradientButton : Button {

public static readonly DependencyProperty Color1Property = DependencyProperty.Register("Color1", typeof(Color),

typeof(BetterGradientButton),

new PropertyMetadata(Colors.Black, OnColorChanged));

public static readonly DependencyProperty Color2Property = DependencyProperty.Register("Color2", typeof(Color),

typeof(BetterGradientButton),

new PropertyMetadata(Colors.White, OnColorChanged));

public Color Color1 {

set { SetValue(Color1Property, value); }

get { return (Color)GetValue(Color1Property); }

}

public Color Color2 {

set { SetValue(Color2Property, value); }

get { return (Color)GetValue(Color2Property); }

}

}

В описании DependencyProperty для Color2 в качестве значения по умолчанию я задал Colors.White.

Оба поля DependencyProperty ссылаются на обработчик события изменения значения свойства OnColorChanged (При изменении цвета). Поскольку ссылка на этот метод выполняется в описании статического поля, сам метод тоже должен быть статическим. Вот как это выглядит:

static void OnColorChanged(DependencyObject obj,

DependencyPropertyChangedEventArgs args)

{ }

Это статический метод, т.е. один и тот же метод используется для всех экземпляров BetterGradientButton, и в этом кроется небольшая проблема. Обычно из статического метода

невозможен доступ к любым нестатическим свойствам или методам. Отсюда можно предположить, что этот метод не может ссылаться на что-либо, связанное с конкретным экземпляром BetterGradientButton.

Но обратите внимание, что первый аргумент, передаваемый в этот обработчик события изменения значения свойства, типа DependencyObject. Фактически этим аргументом является конкретный экземпляр BetterGradientButton, свойство которого изменилось. То есть этот первый аргумент можно свободно приводить к объекту типа BetterGradientButton:

static void 0nColorChanged(Dependency0bject obj,

DependencyPropertyChangedEventArgs args)

{

BetterGradientButton btn = obj as BetterGradientButton;

}

После этого можно воспользоваться переменной btn для доступа ко всем свойствам и методам экземпляра в классе.

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

Привожу класс BetterGradientButton полностью:

Проект Silverlight: BetterGradientButtonDemo Файл: BetterGradientButton.cs (фрагмент)

public class BetterGradientButton : Button {

GradientStop gradientStop1, gradientStop2;

public static readonly DependencyProperty Color1Property = DependencyProperty.Register("Color1", typeof(Color),

typeof(BetterGradientButton),

new PropertyMetadata(Colors.Black, 0nColorChanged));

public static readonly DependencyProperty Color2Property = DependencyProperty.Register("Color2", typeof(Color),

typeof(BetterGradientButton),

new PropertyMetadata(Colors.White, 0nColorChanged));

public BetterGradientButton() {

LinearGradientBrush brush = new LinearGradientBrush(); brush.StartPoint = new Point(0, 0); brush.EndPoint = new Point(1, 0);

gradientStop1 = new GradientStop(); gradientStop1.0ffset = 0; gradientStop1.Color = Color1; brush.GradientStops.Add(gradientStop1);

gradientStop2 = new GradientStop(); gradientStop2.0ffset = 1; gradientStop2.Color = Color2; brush.GradientStops.Add(gradientStop2);

Foreground = brush;

}

public Color Color1 {

set { SetValue(Color1Property, value); }

get { return (Color)GetValue(Color1Property); }

public Color Color2 {

set { SetValue(Color2Property, value); }

get { return (Color)GetValue(Color2Property); }

}

static void OnColorChanged(DependencyObject obj,

DependencyPropertyChangedEventArgs args)

{

BetterGradientButton btn = obj as BetterGradientButton;

if (args.Property == Color1Property)

btn.gradientStop1.Color = (Color)args.NewValue;

if (args.Property == Color2Property)

btn.gradientStop2.Color = (Color)args.NewValue;

}

}

Как и ранее в NaiveGradientButton, в данном классе имеется два закрытых поля экземпляра типа gradientStop1 и gradientStop2. Конструктор также довольно похож на предыдущую версию, за исключением одного существенного отличия: свойство Color каждого объекта GradientStop инициализируется из свойств Color1 и Color2:

gradientStop1.Color = Color1; gradientStop2.Color = Color2;

Попытки доступа к свойствам Color1 и Color2 приводят к вызову метода GetValue с передачей в него аргументов Color1Property и Color2Property. GetValue возвращает значения по умолчанию, определенные в поле DependencyProperty^. Colors.Black и Colors.White. Это обеспечивает создание LinearGradientBrush, использующего цвета по умолчанию.

Существует множество способов написания обработчика события изменения значения свойства. Обычно этот метод описывается в конце класса. В

DependencyPropertyChangedEventArgs (Аргументы события изменения значения свойства- зависимости) класса BetterGradientButton я использовал два свойства. Свойство Property типа DependencyProperty указывает на конкретное свойство-зависимость, значение которого изменилось. Это очень удобно в случае, если обработчик события изменения значения свойства совместно используется несколькими свойствами, как в нашем примере.

DependencyPropertyChangedEventArgs также определяет свойства OldValue (Старое значение) и NewValue (Новое значение). Эти два значения всегда будут различными. Обработчик события изменения значения свойства вызывается, только если значение свойства действительно изменяется.

На момент вызова обработчика события изменения значения свойства значение уже изменилось, так что обработчик может быть реализован через доступ к этим свойствам напрямую. Рассмотрим простой альтернативный вариант:

static void OnColorChanged(DependencyObject obj,

DependencyPropertyChangedEventArgs args)

{

BetterGradientButton btn = obj as BetterGradientButton;

btn.gradientStop1.Color = btn.Color1; btn.gradientStop2.Color = btn.Color2;

В данной версии не проверяется, какое именно свойство изменило значение, поэтому для любого отдельно взятого вызова OnColorChanged одно из этих выражений является избыточным. Полезно знать, что GradientStop наследуется от DependencyObject, и свойство Color является свойством-зависимостью, поэтому обработчик события изменения значения свойства в GradientStop вызывается только в случае, если значение одного из свойств действительно изменилось.

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

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

static void 0nColorChanged(Dependency0bject obj,

DependencyPropertyChangedEventArgs args)

{

(obj as BetterGradientButton).0nColorChanged(args);

}

void 0nColorChanged(DependencyPropertyChangedEventArgs args) {

if (args.Property == Color1Property)

gradientStop1.Color = (Color)args.NewValue;

if (args.Property == Color2Property)

gradientStop2.Color = (Color)args.NewValue;

}

Данный метод экземпляра может делать все то же самое, что и статический метод, но без всех этих сложностей, связанных с использованием ссылки на конкретный экземпляр класса.

Теперь посмотрим на этот новый класс в действии. Будет обидно, если все наши усилия пойдут насмарку и никак не улучшат ситуацию. Как в предыдущем приложении, коллекция Resources в MainPage.xaml включает элемент Style, целью которого является пользовательская кнопка. Но теперь теги Setter для Color1 и Color2 оптимистично раскомментированы:

Проект Silverlight: BetterGradientButtonDemo Файл: MainPage.xaml (фрагмент)

<phone:PhoneApplicationPage.Resources> <Style x:Key="gradientButtonStyle"

TargetType="local:BetterGradientButton"> <Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="Color1" Value="Cyan" /> <Setter Property="Color2" Value="Pink" /> </Style>

</phone:PhoneApplicationPage.Resources>

Область содержимого в целом не изменилась:

Проект Silverlight: BetterGradientButtonDemo Файл: MainPage.xaml (фрагмент)

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <StackPanel>

<local:BetterGradientButton Content="Better Gradient Button #1"

HorizontalAlignment="Center" />

<local:BetterGradientButton Content="Better Gradient Button #2"

Color1="Blue" Color2="Red"

HorizontalAlignment="Center" />

<local:BetterGradientButton Content="Better Gradient Button #3"

Color1="{StaticResource PhoneForegroundColor}" Color2="{StaticResource PhoneBackgroundColor}" HorizontalAlignment="Center" />

<local:BetterGradientButton Content="Better Gradient Button #4"

Style="{StaticResource gradientButtonStyle}" />

</StackPanel> </Grid>

На самом деле радует то, что теперь можно увидеть на экране:

Первая кнопка представляет эффект от применения значений по умолчанию – свойства- зависимости имеют встроенную реализацию этой концепции – и последняя кнопка демонстрирует, что техника с применением Style работает.

Обратим внимание на несколько моментов:

Как можно было заметить, мы не имеем прямого доступа к фактическим значениям свойств- зависимостей. Без всякого сомнения, они хранятся где-то в закрытой сущности, доступ к которой возможен только посредством методов SetValue и GetValue. Вероятно, в DependencyObject есть словарь для хранения коллекции свойств-зависимостей и их значений. Это должен быть словарь, потому что SetValue может использоваться для хранения определенных типов свойств-зависимостей – в частности, присоединенных свойств – в любом объекте DependencyObject.

Обратите внимание на первый аргумент конструктора PropertyMetadata. Он определен типа object. Предположим, мы создаем DependencyProperty для свойства типа double и хотим задать ему значение по умолчанию 10:

public static readonly DependencyProperty MyDoubleProperty = DependencyProperty.Register("MyDouble", typeof(double), typeof(SomeClass),

new PropertyMetadata(10, OnMyDoubleChanged));

Компилятор C# интерпретирует это значение как int и сформирует код для передачи целого значения 10 в конструктор PropertyMetadata. Конструктор сделает попытку сохранить целое значение в свойство-зависимость типа double. Возникнет ошибка времени выполнения. Типы данных должны быть заданы явно:

public static readonly DependencyProperty MyDoubleProperty = DependencyProperty.Register("MyDouble", typeof(double), typeof(SomeClass),

new PropertyMetadata(10.0, 0nMyDoubleChanged));

Может возникнуть необходимость в создании свойства-зависимости только для чтения. (Например, для свойств ActualWidth и ActualHeight класса FrameworkElement имеются только методы доступа, возвращающие значения.) На первый взгляд, кажется, все просто:

public double MyDouble {

private set { SetValue(MyDoubleProperty, value); } get { return (double)GetValue(MyDoubleProperty); }

}

Теперь только сам класс может задавать свойство.

Но подождите! Как говорилось выше, метод SetValue является открытым, так что любой класс может вызвать SetValue, чтобы задать значение этого свойства. Чтобы защитить свойство- зависимость от неавторизованного доступа, понадобится создать исключение, которое будет формироваться при попытке задания значения свойства из кода, внешнего по отношению к классу. Вероятно, самой простой логикой будет устанавливать закрытый флаг при задании свойства внутри класса и затем в обработчике события изменения значения свойства проводить проверку на наличие этого закрытого флага.

Из документации без труда можно понять, подкреплено ли конкретное свойство существующего класса свойством-зависимостью. Просто поищите в разделе Fields (Поля) статическое поле типа DependencyProperty, имя которого соответствует имени свойства, дополненному словом Property.

Наличие статического поля DependencyProperty позволяет коду или разметке ссылаться на конкретное свойство, описанное классом, независим от какого-либо экземпляра данного класса, даже если этот экземпляр еще не создан. В некоторых методах – например, методе SetBinding (Задать привязку), определенным классом FrameworkElement – есть аргументы, которые позволяют ссылаться на конкретное свойство, и свойство-зависимость идеально подходит для этого.

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

Например, для обеспечения возможности пользователю выбирать объект типа Color планируется использовать несколько элементов управления RadioButton. Для этого можно создать класс, производный от RadioButton, и определить в нем свойство, которое связывало бы каждый RadioButton с объектом Color:

public class ColorRadioButton : RadioButton {

public Color ColorTag { set; get; }

}

Теперь данное свойство можно задавать в XAML и затем использовать в коде для определения того, какой Color представляет каждый RadioButton. В таком простом случае свойство-зависимость ни к чему.

Источник: Чарльз Петзольд, Программируем Windows Phone 7, Microsoft Press, © 2011.

По теме:

  • Комментарии