Главная » Разработка для Windows Phone 7 » Старомодный Canvas

0

Безусловно, Canvas является самым старомодным видом панелей. Размещение элементов в Canvas осуществляется через задание их координат по вертикали и горизонтали относительно верхнего левого угла.

Canvas обладает двумя необычными характеристиками:

•         В методе MeasureOverride Canvas всегда вызывает Measure своего дочернего элемента, передавая в него неограниченные ширину и высоту. (Соответственно, в ArrangeOverride Canvas определяет размеры дочерних элементов на основании их DesiredSize.)

•         Метод MeasureOverride элемента Canvas возвращает размер, включающий нулевую ширину и нулевую высоту.

Первое означает, что дочерний элемент Canvas всегда отображается минимально возможного размера, что для Ellipse и Rectangle означает вообще ничего, а для Image – оригинальный размер растрового изображения. Любые заданные для дочерних элементов значения свойств HorizontalAlignment или VerticalAlignment не имеют никакого эффекта в Canvas.

Второе подразумевает, что Canvas не имеет собственного места в системе компоновки Silverlight. (Это можно исправить, явно задав Width или Height для Canvas.) На самом деле это очень полезное свойство в случаях, когда требуется, чтобы элемент существовал где-то «вне» системы компоновки и не оказывал влияния на позиционирование других элементов.

Рассмотрим приложение, использующее Canvas для вывода на экран семи элементов Ellipse в виде цепочки с перекрывающимися звеньями. Объект Style (описанный в коллекции Resources самого Canvas) обеспечивает каждый эллипс конечными значениями Width и Height; в противном случае они не отображались бы.

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

<Grid x:Name="ContentPanel" Grid.Row="1"> <Canvas>

<Canvas.Resources>

<Style x:Key="ellipseStyle" TargetType="Ellipse"> <Setter Property="Width" Value="100" /> <Setter Property="Height" Value="100" />

<Setter Property="Stroke" Value="{StaticResource PhoneAccentBrush}"

/>

<Setter Property="StrokeThickness" Value="10" /> </Style> </Canvas.Resources>

<Ellipse Style="{StaticResource ellipseStyle}" Canvas.Left="0" Canvas.Top="0" />

<Ellipse Style="{StaticResource ellipseStyle}" Canvas.Left="52" Canvas.Top="53" />

<Ellipse Style="{StaticResource ellipseStyle}" Canvas.Left="116" Canvas.Top="92" />

<Ellipse Style="{StaticResource ellipseStyle}" Canvas.Left="190" Canvas.Top="107" />

<Ellipse Style="{StaticResource ellipseStyle}" Canvas.Left="2 63" Canvas.Top="92" />

<Ellipse Style="{StaticResource ellipseStyle}" Canvas.Left="32 6" Canvas.Top="53" />

<Ellipse Style="{StaticResource ellipseStyle}" Canvas.Left="3 8 0" Canvas.Top="0" />

</Canvas> </Grid>

Обратите внимание, что я удалил Margin панели содержимого, поэтому значения достигают 480. Вот как это выглядит на экране:

Canvas является идеальным контейнером для размещения элементов произвольным образом. Конечно, это больше относится к вопросам программирования векторной графики, чем компоновки элементов управления.

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

<Ellipse Style=M{StaticResource ellipseStyle}" Canvas.Left=«190» Canvas.Top=«107» />

Эти свойства Left и Top позиционируют верхний правый угол элемента относительно верхнего правого угла Canvas. Свойства, кажется, определены классом Canvas, но заданы для элемента Ellipse! Когда я впервые увидел такой синтаксис много лет назад, я просто был сбит с толку. Зачем классу Canvas определять свойства Left и Top? Разве это должен делать не FrameworkElement?

Конечно, в графических средах разработки прошлого у всех элементов были свойства Left и Top, потому что так работала система.

Но в Silverlight эти свойства не имеют особого смысла. Canvas требует, чтобы для его дочерних элементов были заданы Left и Top, для других панелей этого не нужно. Для дочерних элементов других панелей, включая пользовательские панели, которые вам еще предстоит написать или даже понять, могут понадобиться совсем другие свойства.

Поэтому Silverlight поддерживает концепцию присоединенных свойств. Свойства Left и Top в самом деле определены классом Canvas (и как именно это делается, будет показано в главе 11), но задаются эти свойства для дочерних элементов Canvas. (Их можно задать и для элементов, не являющихся дочерними элементами Canvas, но они будут проигнорированы.)

Давайте рассмотрим приложение, в коде которого задаются эти присоединенные свойства. В приложении EllipseMesh (Сетка из эллипсов) в сетке для содержимого создается ряд перекрывающихся эллипсов. Файл XAML включает пустой Canvas с заданным обработчиком событий SizeChanged:

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

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

SizeChanged="0nCanvasSizeChanged" />

</Grid>

Несмотря на то что Canvas не имеет собственного места в системе компоновки, у него есть размер и событие SizeChanged. При каждом вызове SizeChanged обработчик события опустошает Canvas (просто для удобства) и заполняет его вновь новыми объектами Ellipse:

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

public partial class MainPage : PhoneApplicationPage {

public MainPage() {

InitializeComponent();

}

void 0nCanvasSizeChanged(object sender, SizeChangedEventArgs args) {

canvas.Children.Clear();

for (double y = 0; y < args.NewSize.Height; y += 75)

for (double x = 0; x < args.NewSize.Width; x += 75) {

Ellipse ellipse = new Ellipse {

Width = 100, Height = 100,

Stroke = this.Resources["PhoneAccentBrush"] as Brush, StrokeThickness = 10

Canvas.SetLeft(ellipse, x); Canvas.SetTop(ellipse, y);

canvas.Children.Add(ellipse);

}

}

}

Вот как это выглядит на экране:

Эти два выражения задают присоединенные свойства Left и Top:

Canvas.SetLeft(ellipse, x); Canvas.SetTop(ellipse, y);

Эти два статических метода определены классом Canvas. Их можно вызвать либо до, либо после добавления дочернего элемента в коллекцию Children элемента Canvas. Это статические методы, поэтому они могут вызываться даже, когда объекта Canvas еще не существует.

Еще более показательным является то, как эти статические методы определены в классе Canvas. Прямо в приложении EllipseMesh эти два статических метода можно заменить следующими выражениями:

ellipse.SetValue(Canvas.LeftProperty, x); ellipse.SetValue(Canvas.TopProperty, y);

Эти эквивалентные вызовы подтверждают, что какие-то значения действительно были заданы для объектов Ellipse. Метод SetValue (Задать значение) определен классом DependencyObject (самый базовый класс в иерархии классов Silverlight), а LeftProperty (Свойство слева) и RightProperty (Свойство справа), несмотря на их имена, на самом деле являются статическими полями типа DependencyProperty, определенными Canvas.

Если я не ошибаюсь, SetValue выполняет доступ к внутреннему словарю, создаваемому и сохраняемому DependencyObject. При этом первый передаваемый в SetValue аргумент – это ключ словаря, и второй – значение. Когда Canvas компонует свои дочерние элементы в методе ArrangeOverride, он может обращаться к этим значениям для конкретного элемента child, используя следующий синтаксис:

double x = GetLeft(child); double y = GetTop(child);

или эквивалентный ему:

double x = (double)child.GetValue(LeftProperty); double y = (double)child.GetValue(TopProperty);

Метод GetValue выполняет доступ к внутреннему словарю в дочернем элементе и возвращает объект типа object, который должен быть приведен к типу double.

В главе 11 я покажу клон Canvas и то, как определять собственные присоединенные свойства.

Внимание! Я описал, как заменять в EllipseMesh вызовы Canvas.SetLeft и Canvas.SetTop эквивалентными вызовами SetValue. Но этот вызов:

Canvas.SetLeft(ellipse, 57);

не является эквивалентным данному вызову:

ellipse.SetValue(Canvas.LeftProperty, 57);

Второй аргумент Canvas.SetLeft должен быть типа double, но второй аргумент метода общего назначения SetValue определен типа object. Когда компилятор C# будет проводить синтаксический разбор этого вызова SetValue, он предположит, что число типа int. Ошибка будет выявлена только во время выполнения. Избежать проблемы поможет явное задание числа как double:

ellipse.SetValue(Canvas.LeftProperty, 57.0);

Мы говорим здесь о присоединенных свойствах Left и Top класса Canvas, но на самом деле в Canvas нет ни одного члена с именем Left или Top! Canvas определяет статические поля LeftProperty и TopProperty и статические методы SetLeft, SetTop, GetLeft и GetTop, но ничего с именами Left или Top. Представленный здесь синтаксис XAML:

<Ellipse Style="{StaticResource ellipseStyle}" Canvas.Left="190" Canvas.Top="107" />

на самом деле сформирован за счет вызовов Canvas.SetLeft и Canvas.SetTop.

Мы будем рассматривать и другие присоединенные свойства. В стандартном файле MainPage.xaml присоединенное свойство задано для корневого элемента:

shell:SystemTray.IsVisible="True"

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

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

По теме:

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