Главная » Разработка для Windows Phone 7 » Простой сервер привязки Windows Phone 7

0

Иногда я рассматривают бизнес-объекты, на которые предполагается ссылаться в XAML- файлах посредством привязок, как серверы привязок. Они предоставляют открытые свойства и формируют события PropertyChanged при изменении значений этих свойств.

Например, в приложении для Windows Phone 7 должно отображаться текущее время, при этом требуется обеспечить довольно большую гибкость в части выводимых на экран данных, например, показывать только секунды. Вы хотите реализовать все это полностью в XAML. Скажем, вывод будет реализован в виде фразы «Текущее время в секундах – », за которой следует число, изменяющееся каждую секунду. Технику, которую я продемонстрирую здесь, можно распространить на многие другие типы приложений, не только для реализации часов.

Даже несмотря на то что мы собираемся реализовывать все визуальные элементы в XAML, нам понадобиться некоторый код, возможно, класс с простым именем Clock и свойствами Year, Month, Day, DayOfWeek (День недели), Hour, Minute и Second. Экземпляр этого класса будем создавать в XAML-файле и выполнять доступ к свойствам посредством привязок данных.

Как известно, в .NET уже есть готовая структура со свойствами Year, Month, Day и т.д. Это DateTime. И хотя DateTime необходим для написания класса Clock, он не удовлетворяет всем целям, потому что свойства класса DateTime не меняются динамически. Каждый объект DateTime представляет конкретные фиксированные дату и время. Для сравнения, класс Clock, который будет продемонстрирован, имеет свойства, изменяющиеся для отражения текущего значения времени. Об этих изменениях класс Clock будет уведомлять с помощью события PropertyChanged.

Класс Clock находится в библиотеке Petzold.Phone.Silverlight. Рассмотрим его:

Проект Silverlight: Petzold.Phone.Silverlight Файл: Clock.cs

using System;

using System.ComponentModel; using System.Windows.Threading;

namespace Petzold.Phone.Silverlight {

public class Clock : INotifyPropertyChanged {

int hour, min, sec; DateTime date;

public event PropertyChangedEventHandler PropertyChanged;

public Clock() {

0nTimerTick(null, null);

DispatcherTimer tmr = new DispatcherTimer(); tmr.Interval = TimeSpan.FromSeconds(0.1); tmr.Tick += 0nTimerTick; tmr.Start();

}

public int Hour {

protected set {

if (value != hour) {

hour = value;

0nPropertyChanged(new PropertyChangedEventArgs("Hour"));

}

}

get {

return hour;

}

}

public int Minute

protected set {

if (value != min) {

min = value;

OnPropertyChanged(new PropertyChangedEventArgs("Minute"));

}

}

get {

return min;

}

}

public int Second {

protected set {

if (value != sec) {

sec = value;

OnPropertyChanged(new PropertyChangedEventArgs("Second"));

}

}

get {

return sec;

}

}

public DateTime Date {

protected set {

if (value != date) {

date = value;

OnPropertyChanged(new PropertyChangedEventArgs("Date"));

}

}

get {

return date;

}

}

protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) {

if (PropertyChanged != null)

PropertyChanged(this, args);

}

void OnTimerTick(object sender, EventArgs args) {

DateTime dt = DateTime.Now; Hour = dt.Hour; Minute = dt.Minute; Second = dt.Second; Date = DateTime.Today;

}

}

}

Класс Clock реализует интерфейс INotifyPropertyChanged и, следовательно, включает открытое событие PropertyChanged. Ближе к концу кода класса можно также увидеть защищенный метод OnPropertyChanged, который фактически формирует событие. Конструктор класса

задает обработчик события Tick класса DispatcherTimer, которое формируется с интервалом в 1/10 секунды. Обработчик OnTimerTick (в самом конце класса) задает новые значения свойствам Hour, Minute, Second и Date, которые имеют очень схожую структуру.

Например, посмотрим на свойство Hour:

public int Hour {

protected set {

if (value != hour) {

hour = value;

0nPropertyChanged(new PropertyChangedEventArgs("Hour"));

}

}

get {

return hour;

}

}

Метод доступа set является защищенным. Значение задается только внутри приложения, и мы не хотим, чтобы внешние классы вмешивались в это. Метод доступа set сравнивает значение, задаваемое свойству, и значение, хранящееся как поле. Если они не равны, он задает полю hour (час) новое значение и вызывает метод OnPropertyChanged для формирования события.

Некоторые разработчики не включают выражение if для проверки того, действительно ли новое значение свойства отличается от его текущего значения. В результате событие PropertyChanged формируется при любом задании свойства, даже если его значение не меняется. Это неправильно, особенно для таких классов, как этот. Нам на самом деле не нужно, чтобы событие PropertyChanged оповещало об изменении свойства Hour каждую 1/10 секунды, если значение фактически меняется только раз в час.

Чтобы использовать класс Clock в XAML-файле, необходимо включить в него ссылку на библиотеку Petzold.Phone.Silverlight и объявление пространства имен XML:

xmlns:petzold="clr-

namespace:Petzold.Phone.Silverlight;assembly=Petzold.Phone.Silverlight"

Если источник привязки не наследуется от DependencyObject, в Binding используется не ElementName, а Source. Привязки, которые мы хотим создать, в качестве значения Source задают объект Clock, описанный в библиотеке Petzold.Phone.Silverlight.

При использовании синтаксиса свойство-элемент ссылку на класс Clock можно вставить непосредственно в Binding:

<TextBlock>

<TextBlock.Text>

<Binding Path="Second"> <Binding.Source>

<petzold:Clock /> </Binding.Source> </Binding> </TextBlock.Text> </TextBlock>

Свойство Source объекта Binding вынесено как свойство-элемент, и в качестве его значения задан экземпляр класса Clock. Свойство Path указывает на свойство Second класса Clock.

Или, что еще более удобно, Clock можно определить как XAML-ресурс:

<phone:PhoneApplicationPage.Resources> <petzold:Clock x:Key="clock" />

</phone:PhoneApplicationPage.Resources>

Тогда расширение разметки Binding может ссылаться на этот ресурс:

TextBlock Text="{Binding Source={StaticResource clock}, Path=Second}" />

Обратите внимание на встроенное расширение разметки для StaticResource.

Этот подход продемонстрирован в проекте TimeDisplay, в котором для конкатенации текста используется горизонтальный StackPanel:

Проект Silverlight: TimeDisplay Файл: MainPage.xaml

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

HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBlock Text="The current seconds are " /> <TextBlock Text="{Binding Source={StaticResource clock},

Path=Second}" />

</StackPanel> </Grid>

И вот что получаем:

Обращаю внимание еще раз: цель привязки (свойство Text объекта TextBlock) должно быть свойством-зависимостью. Это обязательное условие. Чтобы целевой объект обновлялся с изменением значений объекта-источника привязки (свойство Second объекта Clock), источник должен реализовывать некоторый механизм уведомления, что он и делает.

Конечно, мне не нужен StackPanel с множеством элементов TextBlock. Применяя StringFormatConverter (который я включил в TimeDisplay как ресурс с ключом «stringFormat», чтобы можно было поэкспериментировать с ним), я просто вставляю весь текст следующим образом:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"

Text="{Binding Source={StaticResource clock}, Path=Second,

Converter={StaticResource stringFormat}, ConverterParameter=’The current seconds are {0}’}" />

</Grid>

Теперь расширение разметки Binding включает два встроенных расширения разметки.

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

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

HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBlock Text="{Binding Source={StaticResource clock},

Path=Hour}" />

<TextBlock Text="{Binding Source={StaticResource clock},

Path=Minute,

Converter={StaticResource stringFormat}, ConverterParameter=':{0:D2}’}" /> <TextBlock Text="{Binding Source={StaticResource clock},

Path=Second,

Converter={StaticResource stringFormat}, ConverterParameter=':{0:D2}’}" />

</StackPanel> </Grid>

Как видите, все три привязки включают один и тот же параметр Source. Можно ли каким-то образом избежать повторений? Да, можно, и эта техника также иллюстрирует очень важную концепцию.

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

По теме:

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