Главная » Разработка для Windows Phone 7 » Построение гистограммы при помощи DataTemplate

0

Сочетая DataTemplate и ItemsPanelTemplate, можно получить ListBox или ItemsControl, не похожий ни на что ранее виденное.

Создадим новый проект и включим в него ссылки и объявления пространств имен XML для библиотек Petzold.Phone.Silverlight и ElPasoHighSchool. В корневом теге файла MainPage.xaml зададим свойства для обеспечения альбомной ориентации. Поместим StudentBodyPresenter в коллекцию Resources.

Это ItemsControl без ScrollViewer. ItemsSource – это свойство Students экземпляра StudentBodyPresenter. Для ItemsPanelTemplate задан UniformStack с горизонтальной ориентацией:

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

DataContext="{Binding Source={StaticResource studentBodyPresenter}, Path=StudentBody}">

<ItemsControl ItemsSource="{Binding Students}" VerticalAlignment="Bottom">

<ItemsControl.ItemsPanel> <ItemsPanelTemplate>

<petzold:UniformStack Orientation="Horizontal" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl> </Grid>

Без DataTemplate ItemsControl выводит на экран полное имя класса в виде строки «ElPasoHighSchool.Student». Но в панели UniformStack для каждого элемента выделяется одинаковое пространство, поэтому видимым остается только первая «Е»:

Выглядит малообещающим, но для DataTemplate зададим Rectangle, свойство Height которого связано посредством привязки со свойством GradePointAverage:

<ItemsControl ItemsSource="{Binding Students}" VerticalAlignment="Bottom"> <ItemsControl.ItemTemplate> <DataTemplate>

<Rectangle Fill="{StaticResource PhoneAccentBrush}" Height="{Binding GradePointAverage}" VerticalAlignment="Bottom" Margin="1 0" />

</DataTemplate> </ItemsControl.ItemTemplate>

<ItemsControl.ItemsPanel> <ItemsPanelTemplate>

<petzold:UniformStack Orientation="Horizontal" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl>

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

Безусловно, значения свойства GradePointAverage лежат в диапазоне от 0 до 5, поэтому столбики диаграммы очень малы. Как решить эту проблему?

Первым в голову приходит применить к Rectangle трансформацию ScaleTransform с постоянным коэффициентом масштабирования по вертикали равным, скажем, 50. Я сначала так и сделал, но не был доволен результатом. Кажется, перед масштабированием высоты прямоугольников были округлены до ближайшего пиксела. Поэтому я отказался от такого подхода и написал новый конвертер данных:

Проект Silverlight: Petzold.Phone.Silverlight Файл: MultiplyConverter.cs using System;

using System.Globalization; using System.Windows.Data;

namespace Petzold.Phone.Silverlight {

public class MultiplyConverter : IValueConverter {

public object Convert(object value, Type targetType,

object parameter, CultureInfo culture)

{

double multiplier;

if (value is IConvertible &&

parameter is string &&

Double.TryParse(parameter as string, out multiplier))

{

return (value as IConvertible).ToDouble(culture) * multiplier;

}

return value;

}

public object ConvertBack(object value, Type targetType,

object parameter, CultureInfo culture)

{

double divider;

if (value is IConvertible &&

parameter is string &&

Double.TryParse(parameter as string, out divider))

{

return (value as IConvertible).ToDouble(culture) / divider;

}

return value;

}

}

}

Этот конвертер умножает значение источника привязки на коэффициент, заданный как параметр конвертера. Определим один из них в коллекции Resources:

<phone:PhoneApplicationPage.Resources>

<elpaso:StudentBodyPresenter x:Key="studentBodyPresenter" /> <petzold:MultiplyConverter x:Key="multiply" /> </phone:PhoneApplicationPage.Resources>

Теперь дадим ссылку на конвертер в привязке, чтобы обеспечить умножение каждого значения на 50:

<DataTemplate>

<Rectangle Fill="{StaticResource PhoneAccentBrush}" Height="{Binding GradePointAverage,

Converter={StaticResource multiply}, ConverterParameter=50}" VerticalAlignment="Bottom" Margin="1 0" />

</DataTemplate>

И теперь все выглядит, как настоящая гистограмма:

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

Помните конвертер ValueToBrushConverter из библиотеки Petzold.Phone.Silverlight? С его помощью мы можем задать цветовой код для гистограммы и получать уведомления цветом, когда средний балл любого из учащихся опускается ниже 1, к примеру. Так конвертер выглядел бы в коллекции Resources:

<petzold:ValueToBrushConverter x:Key="valueToBrush"

Criterion="1"

GreaterThanBrush="{StaticResource PhoneAccentBrush}" EqualToBrush="{StaticResource PhoneAccentBrush}" LessThanBrush="Red" />

Вот новый DataTemplate:

<DataTemplate>

<Rectangle Fill="{Binding GradePointAverage,

Converter={StaticResource valueToBrush}}"

Height="{Binding GradePointAverage,

Converter={StaticResource multiply}, ConverterParameter=50}" VerticalAlignment="Bottom" Margin="1 0" />

</DataTemplate>

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

Можно ли определить, кому из учащихся соответствует тот или иной столбик гистограммы?

Один из возможных подходов реализован в проекте GpaBarChart. Он включает класс StudentBodyPresenter и два упоминаемых мною конвертера, которые определены как ресурсы:

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

<phone:PhoneApplicationPage.Resources>

<elpaso:StudentBodyPresenter x:Key="studentBodyPresenter" /> <petzold:MultiplyConverter x:Key="multiply" /> <petzold:ValueToBrushConverter x:Key="valueToBrush"

Criterion="1"

GreaterThanBrush="{StaticResource PhoneAccentBrush}" EqualToBrush="{StaticResource PhoneAccentBrush}" LessThanBrush="Red" /> </phone:PhoneApplicationPage.Resources>

Область содержимого, преимущественно, осталась прежней, я лишь добавил Border с именем «studentDisplay» (Монитор учащихся), который будет размещаться вверху экрана. Этот Border включает пару элементов TextBlock, свойства Text которых связаны посредством привязки данных со свойствами FullName и GradePointAverage, исходя из предположения о том, что DataContext этого Border является объектом типа Student. Как правило, это не так, поэтому свойству Visibility нашего Border задано начальное значение Collapsed:

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

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

DataContext="{Binding Source={StaticResource studentBodyPresenter}, Path=StudentBody}">

<Border x:Name="studentDisplay"

BorderBrush="{StaticResource PhoneForegroundBrush}" BorderThickness="{StaticResource PhoneBorderThickness}" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="24" Padding="12" CornerRadius="2 4" Visibility="Collapsed"> <StackPanel>

<TextBlock Text="{Binding FullName}"

HorizontalAlignment="Center" /> <StackPanel Orientation="Horizontal"> <TextBlock Text="GPA = " />

<TextBlock Text="{Binding GradePointAverage}" /> </StackPanel> </StackPanel> </Border>

<ItemsControl ItemsSource="{Binding Students}" VerticalAlignment="Bottom"> <ItemsControl.ItemTemplate> <DataTemplate>

<Rectangle Fill="{Binding GradePointAverage,

Converter={StaticResource valueToBrush}}"

Height="{Binding GradePointAverage,

Converter={StaticResource multiply}, ConverterParameter=50}" VerticalAlignment="Bottom" Margin="1 0" />

</DataTemplate> </ItemsControl.ItemTemplate>

<ItemsControl.ItemsPanel> <ItemsPanelTemplate>

<petzold:UniformStack Orientation="Horizontal" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel>

</ItemsControl> </Grid>

Файл выделенного кода восполняет недостающую логику. Страница обрабатывает событие Touch.FrameReported. Если Rectangle находится непосредственно под главной точкой касания, обработчик события получает DataContext этого Rectangle. Это объект типа Student. Далее этот объект задается как значение DataContext объекта Border. Свойство TouchAction используется для переключения значения Visibility^.

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

public partial class MainPage : PhoneApplicationPage {

public MainPage() {

InitializeComponent();

Touch.FrameReported += OnTouchFrameReported;

}

void OnTouchFrameReported(object sender, TouchFrameEventArgs args) {

TouchPoint touchPoint = args.GetPrimaryTouchPoint(this);

if (touchPoint != null && touchPoint.Action == TouchAction.Down) args.SuspendMousePromotionUntilTouchUp();

if (touchPoint != null && touchPoint.TouchDevice.DirectlyOver is Rectangle) {

Rectangle rectangle =

(touchPoint.TouchDevice.DirectlyOver as Rectangle);

// Этот DataContext является объектом типа Student object dataContext = rectangle.DataContext; studentDisplay.DataContext = dataContext;

if (touchPoint.Action == TouchAction.Down)

studentDisplay.Visibility = Visibility.Visible;

else if (touchPoint.Action == TouchAction.Up)

studentDisplay.Visibility = Visibility.Collapsed;

}

}

}

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

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

По теме:

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