Главная » Разработка для Windows Phone 7 » Задание DataContext

0

Класс FrameworkElement описывает свойство DataContext (Контекст данных), в качестве значения которого может использоваться практически любой объект (в коде), но обычно это привязка (в XAML). DataContext – одно из тех свойств, которое передается вниз по дереву визуальных элементов и может сочетаться с локальными привязками. Как минимум DataContext позволяет упростить отдельные привязки, устраняя повторения. В более широком понимании DataContext – это средство для связывания данных с деревьями визуальных элементов.

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

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

<StackPanel DataContext="{Binding Source={StaticResource clock}}" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBlock Text="{Binding Path=Hour}" /> <TextBlock Text="{Binding Path=Minute,

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

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

</StackPanel> </Grid>

Теперь в качестве значения DataContext свойства DataContext класса StackPanel задан элемент Binding, который просто ссылается на объект-источник привязки: ресурс Clock. Всем потомкам StackPanel нет необходимости явно ссылаться на этот Source, он включен в привязки каждого отдельного элемента TextBlock.

В качестве значения DataContext можно задать объект Binding, как это сделал я:

DataContext="{Binding Source={StaticResource clock}}"

Или в данном случае для DataContext задается источник напрямую:

DataContext="{StaticResource clock}"

Любой из вариантов допустим, и оба можно найти в моих примерах.

Когда мы убрали свойство Source из отдельных расширений Binding, кажется, более естественным будет удалить из них и часть «Path=»:

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

<StackPanel DataContext="{Binding Source={StaticResource clock}}" 0rientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBlock Text="{Binding Hour}" /> <TextBlock Text="{Binding Minute,

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

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

</StackPanel> </Grid>

Помните, что часть «Path=» расширения разметки Binding может быть удалена, только если Path является первым элементом. Теперь кажется, что каждая привязка ссылается на определенное свойство DataContext:

<TextBlock Text="{Binding Hour}" />

Вот что мы получаем на экране:

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

Хотя, конечно, это не так распространено, но DataContext может использоваться с привязками ElementName. Вспомним дерево визуальных элементов файла BorderText.xaml, которое мы видели ранее:

<Border Background="{Binding ElementName=this, Path=Background}"

BorderBrush="{Binding ElementName=this, Path=BorderBrush}" BorderThickness="{Binding ElementName=this, Path=BorderThickness}" CornerRadius="{Binding ElementName=this, Path=CornerRadius}" Padding="{Binding ElementName=this, Path=Padding}">

<TextBlock Text="{Binding ElementName=this, Path=Text}"

TextAlignment="{Binding ElementName=this, Path=TextAlignment}" TextDecorations="{Binding ElementName=this, Path=TextDecorations}" TextWrapping="{Binding ElementName=this, Path=TextWrapping}" />

</Border>

Для DataContext объекта Border можно задать Binding с использованием ElementName. Это существенно упростит все остальные привязки:

<Border DataContext="{Binding ElementName=this}" Background="{Binding Background}" BorderBrush="{Binding BorderBrush}" BorderThickness="{Binding BorderThickness}" CornerRadius="{Binding CornerRadius}" Padding="{Binding Padding}">

<TextBlock Text="{Binding Path=Text}"

TextAlignment="{Binding Path=TextAlignment}" TextDecorations="{Binding Path=TextDecorations}" TextWrapping="{Binding Path=TextWrapping}" />

</Border>

Вернемся к Clock. Я немного поленился при написании класса и не определил многие свойства компонентов даты, такие как Month (Месяц) и Year (Год). Вместо этого я просто использовал свойство Date типа DateTime. Обработчик OnTimerTick задает в качестве значения этого свойства статическое свойство DateTime.Today (Сегодня). DateTime.Today- это объект DateTime, значение времени которого соответствует полуночи. То есть данное свойство Date не формирует события PropertyChanged каждую 1/10 секунды. Оно формирует одно событие при запуске приложения и затем каждый раз при наступлении полуночи.

Обратиться к отдельным свойствам свойства Date можно следующим образом:

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

<TextBlock Text="It’s day number " />

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

Path=Date.Day}" /> <TextBlock Text=" of month " />

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

Path=Date.Month}" />

</StackPanel>

<StackPanel Orientation="Horizontal"> <TextBlock Text=" of the year " />

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

Path=Date.Year}" />

<TextBlock Text=", a " />

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

Path=Date.DayOfWeek}" />

< TextBlock Text="." /> </StackPanel> </StackPanel> </Grid>

Либо можно задать DataContext для StackPanel, как раньше, и удалить часть «Path=» из привязок:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <StackPanel DataContext="{StaticResource clock}" HorizontalAlignment="Center" VerticalAlignment="Center"> <StackPanel Orientation="Horizontal">

<TextBlock Text="It’s day number " /> <TextBlock Text="{Binding Date.Day}" /> <TextBlock Text=" of month " /> <TextBlock Text="{Binding Date.Month}" /> </StackPanel>

<StackPanel Orientation="Horizontal"> <TextBlock Text=" of the year " /> <TextBlock Text="{Binding Date.Year}" /> <TextBlock Text=", a " />

<TextBlock Text="{Binding Date.DayOfWeek}" /> <TextBlock Text="." /> </StackPanel> </StackPanel> </Grid>

Любой из вариантов обеспечивает вывод на экран двух строк текста:

Date – это свойство класса Clock типа DateTime; Day, Month, Year и DayOfWeek – свойства DateTime. В них не выполняется никакого форматирования, кроме обеспечиваемого вызовами ToString (К строке) по умолчанию. Значения свойств Day, Month и Year отображаются как числа. Значениями свойства DayOfWeek являются члены перечисления DayOfWeek, поэтому на экран будет выводиться текст, например Wednesday (Среда), но он не будет отвечать локализации приложения. Члены DayOfWeek соответствуют английским названиям дней недели, они и будут отображаться.

Также можно задать DataContext так, чтобы оно ссылалось и на свойство Source, и на свойство Date. Тогда отдельные привязки будут просто ссылаться на свойства DateTime:

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

<StackPanel DataContext="{Binding Source={StaticResource clock},

Path=Date}" HorizontalAlignment="Center" VerticalAlignment="Center"> <StackPanel 0rientation="Horizontal">

<TextBlock Text="It’s day number " /> <TextBlock Text="{Binding Day}" /> <TextBlock Text=" of month " /> <TextBlock Text="{Binding Month}" /> </StackPanel>

<StackPanel 0rientation="Horizontal"> <TextBlock Text=" of the year " /> <TextBlock Text="{Binding Year}" /> <TextBlock Text=", a " /> <TextBlock Text="{Binding Day0fWeek}" /> <TextBlock Text="." /> </StackPanel> </StackPanel> </Grid>

Несомненно, существует множество вариантов форматирования дат, которые документируются классом DateTimeFormatInfo (Сведения о форматировании даты и времени) из пространства имен System.Globalization. Также можно использовать StringFormatConverter.

Предположим, требуется вставить имя текущего месяца где-то в середине абзаца, выводимого на экран с помощью TextBlock. При использовании TextBlock для отображения абзаца текста, свойству TextWrapping обычно задают значение Wrap. Но в данном случае

StackPanel не может использоваться для конкатенации множества элементов TextBlock. Здесь придется включить весь текст в один TextBlock, имя месяца в том числе. Как это сделать?

Считайте себя гением, если вспомнили о классе Run. В конце главы 8 мы рассматривали, как класс Run наследуется от Inline и позволяет задавать форматирование для отдельного фрагмента текста в рамках TextBlock. У класса Run есть свойство Text, так что он кажется идеальным средством для встраивания имени месяца (или другой привязки) в абзац:

<!– Это не будет работать! –> <TextBlock TextWrapping="Wrap">

Здесь располагается какой-то длинный текст. В него необходимо вставить имя месяца <Run Text="{Binding Source={StaticResource clock}, Path=Date,

Converter={StaticResource stringFormat}, ConverterParameter='{0:MMMM}’}" /> и затем продолжить повествование. </TextBlock>

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

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

На сегодняшний день эту задачу нельзя реализовать только в XAML. Объекту Run придется присвоить имя и задать его свойство Text из кода.

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

По теме:

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