Главная » Разработка для Windows Phone 7 » ContentControl и DataTemplate

0

Два шаблона

Шаблоны в Silverlight – это описанные в XAML деревья визуальных элементов и элементов управления. Особыми эти деревья делает то, что они используются как шаблоны или трафареты для создания идентичных деревьев визуальных элементов. Шаблоны практически всегда определяются как ресурсы, поэтому они используются совместно, и практически всегда включают привязки, поэтому могут быть ассоциированы с разными объектами и предполагают разное представление.

Один тип шаблонов (DataTemplate) используется для формирования визуального представления объектов, которые в противном случае его не имеют. Другой тип шаблонов (ControlTemplate) используется для настройки визуального представления элементов управления. На самом деле есть еще один, третий тип шаблонов (ItemsPanelTemplate (Шаблон панели элементов)). Он очень прост и имеет специальное применение, которое мы обсудим в следующей главе.

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

В главе 10 мною было продемонстрировано, как в качестве значения свойства Content производного от ContentControl класса (например, Button) можно задавать практически любой объект. Если класс этого объекта наследуется от FrameworkElement (например, TextBlock или Image), элемент отображается внутри ContentControl. Но также свойству Content можно присвоить объект, класс которого не является производным от FrameworkElement. Рассмотрим Button, в качестве значения свойства Content которого задан RadialGradientBrush:

<Button HorizontalAlignment="Center" VerticalAlignment="Center">

<RadialGradientBrush>

<GradientStop Offset="0" Color="Blue" /> <GradientStop Offset="1" Color="AliceBlue" /> </RadialGradientBrush>

</Button>

Обычно в Button кисть используется для свойства Foreground, или Background, или даже свойства BorderBrush, но никак не для Content. В чем здесь смысл?

Если объект, заданный как значение свойства Content объекта ContentControl, не наследуется от FrameworkElement, формирование его визуального представления осуществляется его методом ToString. А если класс не имеет перегрузки для метода ToString, на экран выводится полное имя класса. В этом случае данная конкретная кнопка выглядит следующим образом:

Это не совсем то, чем можно похвастать перед своими друзьями, демонстрируя свои навыки в программировании.

Вставим в Button класс Clock из главы 12:

<Button HorizontalAlignment="Center" VerticalAlignment="Center">

<petzold:Clock />

</Button>

И в этом случае в Button не отображается ничего ценного:

Но, несомненно, существует способ разумного представления такого объекта. Для этого в качестве значения свойства ContentTemplate нашего Button зададим объект типа DataTemplate. Рассмотрим синтаксис с пустым DataTemplate:

<Button HorizontalAlignment="Center" VerticalAlignment="Center">

<petzold:Clock />

<Button.ContentTemplate> <DataTemplate>

</DataTemplate> </Button.ContentTemplate> </Button>

ContentTemplate – это одно из двух свойств, описываемых ContentControl и наследуемых Button; вторым свойством является сам Content.

И теперь осталось лишь разместить между тегами DataTemplate дерево визуальных элементов, включающее привязки к свойствам класса Clock:

<Button HorizontalAlignment="Center" VerticalAlignment="Center">

<petzold:Clock />

<Button.ContentTemplate> <DataTemplate> <StackPanel>

<TextBlock Text="The time is:"

TextAlignment="Center" /> <StackPanel Orientation="Horizontal"

HorizontalAlignment="Center"> <TextBlock Text="{Binding Hour}" /> <TextBlock Text=":" /> <TextBlock Text="{Binding Minute}" /> <TextBlock Text=":" /> <TextBlock Text="{Binding Second}" /> </StackPanel> </StackPanel> </DataTemplate> </Button.ContentTemplate> </Button>

Объект Button использует дерево визуальных элементов для отображения объекта Content. Привязки в этом дереве обычно довольно простые. Этим привязкам данных не нужно свойство Source, потому что ассоциированный с этим деревом визуальных элементов DataContext – это и есть объект, заданный как значение свойства Content. Для представленных здесь привязок требуется задать лишь свойства Path, и часть «Path=» расширения разметки Binding можно опустить.

Теперь время отображается и динамически обновляется. Конечно, чтобы минуты и секунды всегда были представлены двумя разрядами, привязки для свойств Minute и Second должны ссылаться на конвертер, форматирующий строковые значения.

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

<Button HorizontalAlignment="Center" VerticalAlignment="Center">

<RadialGradientBrush>

<GradientStop Offset="0" Color="Blue" /> <GradientStop Offset="1" Color="AliceBlue" /> </RadialGradientBrush>

<Button.ContentTemplate> <DataTemplate>

<Ellipse Width="100" Height="10 0" Fill="{Binding}" /> </DataTemplate>

</Button.ContentTemplate> </Button>

Обратите внимание на значение свойства Fill объекта Ellipse. Для него просто задано расширение разметки Binding без задания параметра Path. Свойству Fill не нужно какое-то отдельное свойство RadialGradientBrush. Ему нужен объект в целом. И вот какую кнопку мы получаем:

Эта техника может использоваться с любым производным от ContentControl классом или даже с самим ContentControl.

Опишем DataTemplate в коллекции Resources файла MainPage.xaml:

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

<phone:PhoneApplicationPage.Resources> <DataTemplate x:Key="brushTemplate"> <Ellipse Width="10 0" Height="10 0" Fill="{Binding}" /> </DataTemplate> </phone:PhoneApplicationPage.Resources>

Разместим на панели содержимого этой страницы три экземпляра Button. В качестве значения свойства ContentTemplate всех трех экземпляров зададим этот ресурс, но в каждом случае для свойства Content будем использовать разные типы объектов Brush:

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

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

<Button HorizontalAlignment="Center"

ContentTemplate="{StaticResource brushTemplate}"> <SolidColorBrush Color="{StaticResource PhoneAccentColor}" /> </Button>

<Button HorizontalAlignment="Center"

ContentTemplate="{StaticResource brushTemplate}"> <RadialGradientBrush>

<GradientStop Offset="0" Color="Blue" /> <GradientStop Offset="1" Color="AliceBlue" /> </RadialGradientBrush> </Button>

<Button HorizontalAlignment="Center"

ContentTemplate="{StaticResource brushTemplate}"> <LinearGradientBrush>

<GradientStop Offset="0" Color="Pink" />

В результате получаем следующее:

DataTemplate определен как ресурс, поэтому он может использоваться совместно всеми элементами управления Button. Но на основании этого шаблона для каждого Button a строится собственное дерево визуальных элементов. Где-то в дереве визуальных элементов для каждого Button определен Ellipse, в качестве значения свойства Content которого используется привязка к SolidColorBrush.

Разделение DataTemplate между несколькими элементами управления Button удобно, но не демонстрирует всей мощи этой техники. Только представьте, что можно описать DataTemplate для отображения элементов ListBox или ComboBox. Как это делать, будет показано в следующей главе.

Анализ дерева визуальных элементов

В предыдущем разделе были упомянуты деревья визуальных элементов. Рассмотрим несколько примеров.

В приложении ButtonTree (Дерево кнопки) создается дерево визуальных элементов для довольно стандартного Button (свойству Content которого задан просто текст); Button, значением свойства Content которого задан элемент Image; и еще двух кнопок, значениями свойств Content которых заданы объекты RadialGradientBrush и Clock (как показано в примере выше) посредством ContentTemplate. Каждый Button отображается в отдельной ячейке Grid:

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

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

<GradientStop Offset="1" Color="Red" /> </LinearGradientBrush> </Button> </StackPanel> </Grid>

<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />

<RowDefinition Height="*" />

</Grid.RowDefinitions>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />

</Grid.ColumnDefinitions>

<Button Grid.Row="0" Grid.Column="0" Content="Click to Dump" HorizontalAlignment="Center" VerticalAlignment="Center" Click="OnButtonClick" />

<Button Grid.Row="0" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" Click="OnButtonClick"> <Image Source="ApplicationIcon.png" Stretch="None" />

</Button>

<Button Grid.Row="1" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center" Click="OnButtonClick"> <Button.Content>

<RadialGradientBrush>

<GradientStop Offset="0" Color="Blue" /> <GradientStop Offset="1" Color="AliceBlue" /> </RadialGradientBrush> </Button.Content>

<Button.ContentTemplate> <DataTemplate>

<Ellipse Width="100" Height="10 0" Fill="{Binding}" /> </DataTemplate> </Button.ContentTemplate>

</Button>

<Button Grid.Row="1" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" Click="OnButtonClick"> <Button.Content>

<petzold:Clock /> </Button.Content>

<Button.ContentTemplate> <DataTemplate> <StackPanel>

<TextBlock Text="The time is:"

TextAlignment="Center" /> <StackPanel Orientation="Horizontal"

HorizontalAlignment="Center"> <TextBlock Text="{Binding Hour}" /> <TextBlock Text=":" /> <TextBlock Text="{Binding Minute}" /> <TextBlock Text=":" /> <TextBlock Text="{Binding Second}" /> </StackPanel> </StackPanel> </DataTemplate> </Button.ContentTemplate>

</Button>

<ScrollViewer Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"

HorizontalScrollBarVisibility="Auto" <StackPanel Name="stackPanel" /> </ScrollViewer> </Grid>

В конце этого фрагмента в ScrollViewer описан StackPanel для отображения этого визуального дерева. В файле выделенного кода с помощью рекурсивного метода статического класса VisualTreeHelper (Вспомогательный класс для работы с визуальным деревом) выполняется перечисление дочерних объектов элемента, и затем их имена отображаются в виде списка иерархии:

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

void OnButtonClick(object sender, RoutedEventArgs args) {

Button btn = sender as Button; stackPanel.Children.Clear(); DumpVisualTree(btn, 0);

}

void DumpVisualTree(DependencyObject parent, int indent) {

TextBlock txtblk = new TextBlock();

txtblk.Text = String.Format("{0}{1}", new string(‘ ‘, 4 * indent),

parent.GetType().Name);

stackPanel.Children.Add(txtblk);

int numChildren = VisualTreeHelper.GetChildrenCount(parent);

for (int childIndex = 0; childIndex < numChildren; childIndex++) {

DependencyObject child = VisualTreeHelper.GetChild(parent, childIndex); DumpVisualTree(child, indent + 1);

}

Щелкните кнопку в правом верхнем углу, свойству Content которого задан текст, и приложение выведет на экран дерево визуальных элементов объекта Button:

Нет ничего удивительного в присутствии элемента Border, он ясно виден на Button с TextBlock, используемом для отображения текста. Очевидно, что первый Grid, в котором размещается Border, является Grid с одной ячейкой; позднее в этой главе я покажу предназначение этого Grid. Цель использования Grid для размещения TextBlock не так очевидна. Если в качестве значения свойства Content элемента Button явно задать TextBlock, второй Grid исчезает, и дерево становится больше похожим на дерево для Button, в качестве значения свойства Content которого задан элемент Image:

Часть дерева визуальных элементов вплоть до ContentPresenter (Средство отображения содержимого) определяет представление стандартного Button. (Вскоре мы увидим, как эту часть можно заменить, если задать свойству Template элемента управления объект типа ControlTemplate.) Все что располагается ниже ContentPresenter, используется для отображения содержимого Button. Рассмотрим, как выглядит дерево, если свойству Content задан RadialGradientBrush, но ContentTemplate задан Ellipse, ссылающийся на эту кисть:

Те кто знает шаблоны элементов управления по Windows Presentation Foundation или веб- версии Silverlight, несомненно, ожидают увидеть ContentPresenter. Это производный от FrameworkElement класс, специально предназначенный для размещения содержимого. Именно ContentPresenter форматирует некоторые объекты как текст в отсутствие DataTemplate или фактически применяет DataTemplate. Но ContentControl может привести в замешательство. Я тоже был сбит им с толку сначала. Button наследуется от ContentControl, но это совсем не означает, что дерево визуальных элементов должно включать еще один ContentControl! Я еще вернусь к странному представлению ContentControl позже в этой главе.

Наконец, вот такое дерево визуальных элементов мы получаем при использовании более развернутого DataTemplate для отображения объекта Clock:

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

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

По теме:

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