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

0

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

В Windows Presentation Foundation есть панель, которую я нахожу весьма полезной. Это UniformGrid (Унифицированная сетка). Как можно предположить из ее имени, UniformGrid разделяет свою область содержимого на равные ячейки.

По умолчанию UniformGrid автоматически определяет число строк и столбцов по округленному в большую сторону значению квадратного корня из количества дочерних элементов. Например, если имеется 20 дочерних элементов, UniformGrid создаст 5 строк и столбцов, даже несмотря на то что более логичным кажется получить 5 строки и 4 столбца или 4 строки и 5 столбцов. Такое поведение по умолчанию можно переопределить, задав свойству Rows (Строки) или Columns (Столбцы) объекта UniformGrid отличные от нуля числовые значения.

Практически всегда я задаю Rows или Columns равным 1, получая в результате один столбец или строку, разбитые на равные ячейки. В данном случае все происходит не так, как со StackPanel, который выходит за границы экрана, если содержит слишком много дочерних элементов. Это скорее похоже на поведение Grid с одним столбцом или одной строкой, когда свойство GridLength (Протяженность сетки) в RowDefinition или ColumnDefinition имеет значение Star (Звезда), и, следовательно, обеспечивается равномерное распределение имеющегося пространства между ячейками.

Своей версии UniformGrid я дал имя UniformStack (Унифицированный стек). В этом классе нет свойства Rows или Columns, но имеется свойство Orientation – такое же свойство описано в StackPanel – для обозначения того, как будет ориентирована панель, вертикально или горизонтально.

Рассмотрим фрагмент класса UniformStack, в котором описывается единственное свойство- зависимость и обработчик события изменения значения свойства:

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

public class UniformStack : Panel {

public static readonly DependencyProperty 0rientationProperty = DependencyProperty.Register("0rientation", typeof(0rientation), typeof(UniformStack),

new PropertyMetadata(0rientation.Vertical, 0n0rientationChanged));

public 0rientation 0rientation {

set { SetValue(0rientationProperty, value); }

get { return (0rientation)GetValue(0rientationProperty); }

}

static void 0n0rientationChanged(Dependency0bject obj,

DependencyPropertyChangedEventArgs args)

{

(obj as UniformStack).InvalidateMeasure();

}

}

В описаниях свойства-зависимости и CLR-свойства нет ничего сложного. Обработчик события изменения значения свойства приводит первый аргумент к типу класса, как обычно, и затем просто вызывает метод InvalidateMeasure (Аннулировать размеры). Этот метод определен в UIElement и буквально говорит системе компоновки: «Даже если ты думаешь, что знаешь мои размеры, забудь. Я совершенно другой». Этот код инициирует пересчет размеров всех элементов компоновки, начиная от корневого элемента вниз по дереву визуальных элементов, потому что размер этой панели может повлиять на размеры родительских элементов. За пересчетом размеров элементов компоновки сразу же автоматически следует их перекомпоновка. (Пересчет размеров выполняется при каждом изменении размеров панели, либо при добавлении или удалении элементов из ее коллекции Children, либо при изменении размеров любого из имеющихся дочерних элементов.)

Также имеется метод InvalidateArrange (Аннулировать компоновку), который запускает вторую половину процесса перекомпоновки, но такой сценарий используется довольно редко. Этот метод пригодится для панели, элементы которой могут перемещаться, но при этом ни они, ни сама панель не меняют размеров.

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

Рассмотрим горизонтальный UniformStack. Предположим, на этой панели расположено пять дочерних элементов. Согласно значению availableSize, панель может занять область, шириной (Width) 400 и высотой (Height) 200 пикселов. Каждому дочернему элементу должна быть предложена область шириной 80 (1/5 общей доступной ширины) и высотой 200 пикселов. Таков принцип панели.

Ну а если свойство Width объекта availableSize имеет бесконечное значение? Что происходит в этом случае?

А вот это не вполне ясно. Безусловно, у панели нет другого выбора, кроме как предложить каждому дочернему элементу неограниченную ширину. После этого единственным логичным решением будет возвращение методом MeasureOverride значения Width, которое в пять раз больше значения Width самого широкого дочернего элемента.

Именно это я и делаю в следующем фрагменте кода:

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

protected override Size MeasureOverride(Size availableSize) {

if (Children.Count == 0) return new Size();

Size availableChildSize = new Size(); Size maxChildSize = new Size(); Size compositeSize = new Size();

// Вычисляем доступный размер для каждого дочернего элемента if (Orientation == Orientation.Horizontal)

availableChildSize = new Size(availableSize.Width / Children.Count,

availableSize.Height);

else

availableChildSize = new Size(availableSize.Width,

availableSize.Height / Children.Count);

// Перебираем все дочерние элементы и находим максимальную ширину и высоту

foreach (UIElement child in Children) {

child.Measure(availableChildSize);

maxChildSize.Width = Math.Max(maxChildSize.Width, child.DesiredSize.Width); maxChildSize.Height = Math.Max(maxChildSize.Height,

child.DesiredSize.Height); }

// Теперь определяем совокупный размер, зависящий от доступной // неограниченной ширины или высоты

if (Orientation == Orientation.Horizontal) {

if (Double.IsPositiveInfinity(availableSize.Width))

compositeSize = new Size(maxChildSize.Width * Children.Count,

maxChildSize.Height);

else

compositeSize = new Size(availableSize.Width, maxChildSize.Height);

}

else {

if (Double.IsPositiveInfinity(availableSize.Height)) compositeSize = new Size(maxChildSize.Width,

maxChildSize.Height * Children.Count);

else

compositeSize = new Size(maxChildSize.Width, availableSize.Height);

}

return compositeSize;

}

Метод начинается с проверки наличия дочерних элементов у панели, это предупреждает деление на нуль впоследствии.

availableChildSize (Доступный размер дочернего элемента) вычисляется на основании значения свойства Orientation. При этом наличие неограниченного размера в availableSize для панели игнорируется. (Бесконечность, деленная на количество дочерних элементов, все равно останется бесконечностью; именно это требовалось в данном случае.) При переборе дочерних элементов для каждого из них вызывается метод Measure с этим availableChildSize. Логика вычисления DesiredSize дочернего элемента также игнорирует неограниченные размеры, но находит maxChildSize (Максимальный размер дочернего элемента). Это свойство представляет ширину самого широкого дочернего элемента и высоту самого высокого дочернего элемента. Возможно также, что несколько дочерних элементов будут иметь размеры, соответствующие параметрам maxChildSize.

При окончательном вычислении compositeSize учитываются и Orientation, и возможность наличия неограниченного размера. Обратите внимание, что compositeSize иногда берет один из размеров availableSize за базовый; вообще это не очень правильно, но метод делает это, только если знает, что этот размер не бесконечный.

Метод ArrangeOverride вызывает Arrange для каждого дочернего элемента, передавая в него один и тот же размер (в методе это параметр finalChildSize (Окончательный размер дочернего элемента)), но разные координаты x и y относительно панели, они зависят от ориентации:

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

protected override Size Arrange0verride(Size finalSize) {

if (Children.Count > 0) {

Size finalChildSize = new Size(); double x = 0; double y = 0;

if (0rientation == 0rientation.Horizontal)

finalChildSize = new Size(finalSize.Width / Children.Count,

finalSize.Height);

else

finalChildSize = new Size(finalSize.Width,

finalSize.Height / Children.Count);

foreach (UIElement child in Children) {

child.Arrange(new Rect(new Point(x, y), finalChildSize));

if (0rientation == 0rientation.Horizontal) x += finalChildSize.Width;

else

y += finalChildSize.Height;

}

}

return base.Arrange0verride(finalSize);

}

Давайте на базе UniformStack создадим гистограмму!

Вообще в приложении QuickBarChart используется три панели UniformStack: Проект Silverlight: QuickBarChart Файл: MainPage.xaml (фрагмент)

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

<petzold:UniformStack x:Name="barChartPanel"

Orientation="Horizontal" />

<petzold:UniformStack Orientation="Horizontal">

<Button Content="Add 10 Items"

HorizontalAlignment="Center" VerticalAlignment="Center" Click="OnButtonClick" />

<TextBlock Name="txtblk" Text="0"

HorizontalAlignment="Center" VerticalAlignment="Center" /> </petzold:UniformStack> </petzold:UniformStack> </Grid>

Первый вертикальный UniformStack просто разделяет область содержимого на две равные области. (Посмотрите, насколько проще использовать UniformStack, чем обычный Grid!) В верхней области располагается другой UniformStack, который пока пуст. В нижней области находится горизонтальный UniformStack, в котором мы разместим кнопку (Button) и текстовое поле (TextBlock).

По щелчку Button файл выделенного кода добавляет по 10 элементов Rectangle на панель UniformStack, которую я назвал barChartPanel (Панель гистограммы):

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

public partial class MainPage : PhoneApplicationPage {

Random rand = new Random();

public MainPage() {

InitializeComponent();

}

void OnButtonClick(object sender, RoutedEventArgs args) {

for (int i = 0; i < 10; i++) {

Rectangle rect = new Rectangle();

rect.Fill = this.Resources["PhoneAccentBrush"] as Brush; rect.VerticalAlignment = VerticalAlignment.Bottom; rect.Height = barChartPanel.ActualHeight * rand.NextDouble(); rect.Margin = new Thickness(0, 0, 0.5, 0);

barChartPanel.Children.Add(rect);

}

txtblk.Text = barChartPanel.Children.Count.ToString();

}

}

Обратите внимание, что у каждого Rectangle справа есть небольшое поле (Margin) в полпиксела. Это обеспечивает разделение элементов. Просто удивительно, как много прямоугольников можно поместить на экране, прежде чем падет логика отображения:

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

По теме:

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