Элементы Border, Rectangle и Ellipse в WinRT

109

Программа Windows Runtime обычно состоит из одного или нескольких классов, производных от Page. Каждая страница содержит визуальное дерево элементов, объединенных в иерархию «родитель-потомок». В свойстве Content объекта Page может быть задан только один потомок, но чаще всего он представляет собой панель - экземпляр класса, производного от Panel. Класс Panel определяет свойство с именем Children, относящееся к типу UIElementCollection - коллекции элементов, производных от UIElement, включая другие классы панелей.

Классы, производные от Panel, образуют ядро системы динамического формирования макета Windows Runtime (компоновку приложений). При изменении размеров или ориентации страницы панели могут переупорядочивать своих потомков для оптимального заполнения доступного пространства. Панели разных типов по-разному упорядочивают своих потомков. Например, Grid размещает их по строкам и столбцам; StackPanel - по вертикали или по горизонтали; VariableSizedWrapGrid также использует вертикальное или горизонтальное размещение, но использует дополнительные строки или столбцы, по аналогии с начальным экраном Windows 8. Панель Canvas поддерживает размещение своих потомков в конкретных позициях.

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

Аналогичные концепции хорошо известны в мире веб-дизайна. Например, ширина простой страницы HTML ограничивается шириной экрана или окна браузера С другой стороны, высота страницы зависит от ее содержимого. Если она превысит высоту окна браузера, потребуются средства прокрутки.

С начальным экраном Windows 8 все наоборот: количество плиток приложений, которые могут быть выстроены по вертикали, зависит от высоты экрана, а ширина экрана зависит от потомков. Если плитки по горизонтали выходят за пределы экрана, их приходится выводить на экран посредством горизонтальной прокрутки.

Давайте рассмотрим некоторые элементы компоновки и фигур, которые можно использовать в Windows Runtime.

Элемент Border

Два важнейших свойства макета, относящихся к формированию макета, - HorizontalAlignment и VerticalAlignment. Эти свойства определяются элементом FrameworkElement, а их значениями являются элементы одноименных перечислений: HorizontalAlignment и VerticalAlignment.

Как было показано ранее, по умолчанию свойства HorizontalAlignment и VerticalAlignment содержат значения HorizontalAlignment.Stretch и VerticalAlignment.Stretch (а не Left и Top, как можно было бы предположить). Эти значения по умолчанию подразумевают формирование макета родителем: элементы автоматически растягиваются по размерам своих родителей. Это не всегда очевидно по внешнему виду приложения, но ранее был приведен пример с элементом TextBlock растянутым по размерам родителя и получавшим все события Tapped из любой точки в его границах.

Когда свойствам HorizontalAlignment или VerticalAlignment задаются значения отличные от Stretch, ширина и высота элемента определяются на основании его содержимого.

Важность HorizontalAlignment и VerticalAlignment также становится очевидной при добавлении родителей и потомков к странице. Допустим, вы захотели вывести элемент TextBlock в рамке. Неожиданно выясняется, что у TextBlock нет свойств, относящихся к рамкам, однако в пространстве имен Windows.UI.Xaml.Controls имеется элемент Border со свойством Child. Соответственно TextBlock помещается в Border, a Border включается в Grid:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Border BorderBrush="LimeGreen" BorderThickness="10"
                CornerRadius="22" Background="LightBlue">
            <TextBlock Text="Привет, Windows 8!" 
                       HorizontalAlignment="Center"
                       VerticalAlignment="Center"
                       FontSize="80"/>
        </Border>
</Grid>
Использование рамки для надписи в Windows Runtime

Свойству BorderThickness, определяемому Border, можно задать разные значения для четырех сторон рамки. Для этого достаточно указать четыре числа по порядку для левой, верхней, правой и нижней стороны. Если задать только два значения, то первое будет относиться к левой и правой сторонам, а второе - к верхней и нижней. Свойство CornerRadius определяет радиус закругления углов. Ему можно задать одно общее значение или четыре разных для левого верхнего, правого верхнего, правого нижнего и левого нижнего углов.

Обратите внимание на задание свойств HorizontalAlignment и VerticalAlignment для TextBlock. Разметка выглядит логично, но результат, вероятно, будет не тем, который вы ожидали.

Так как класс Border наследует от FrameworkElement, он также содержит свойства HorizontalAlignment и VerticalAlignment, которым назначены значения по умолчанию Stretch; соответственно элемент Border растягивается по размерам своего родителя. Чтобы добиться желаемого эффекта, нужно переместить настройки HorizontalAlignment и VerticalAlignment из TextBlock в Border:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Border BorderBrush="LimeGreen" BorderThickness="10"
                CornerRadius="22" Background="LightBlue"
                HorizontalAlignment="Center" 
                VerticalAlignment="Center">
            <TextBlock Text="Привет, Windows 8!" 
                       Margin="24"
                       FontSize="80"/>
        </Border>
</Grid>

Я также назначил элементу TextBlock поля шириной 1/4 дюйма (свойство Margin). В результате рамка со всех четырех сторон отодвинута от текста на расстояние 1/4 дюйма:

Использование рамки с отступами для надписи в Windows Runtime

Свойство Margin определяется элементом FrameworkElement, поэтому оно поддерживается всеми элементами. Оно относится к типу Thickness (как и свойство Border.Thickness) структуре с четырьмя свойствами Left, Top, Right и Bottom. Свойство Margin чрезвычайно удобно для определения небольшого пустого пространства вокруг элементов, чтобы они не «налезали» друг на друга, поэтому оно очень часто встречается в реальной разметке XAML. Как и BorderThickness, свойство Margin может содержать до четырех разных значений. В XAML ширина полей с разных сторон перечисляется в порядке «левая-верхняя-правая-нижняя». Если заданы только два значения, то первое относится к левой и правой сторонам, а второе - к верхней и нижней.

Кроме того, элемент Border определяет свойство Padding, сходное с Margin, но применяемое к элементу внутри, а не снаружи. Попробуйте удалить свойство Margin из TextBlock и вместо этого задать для Border свойство Padding:

<Border BorderBrush="LimeGreen" BorderThickness="10"
                CornerRadius="22" Background="LightBlue"
                HorizontalAlignment="Center" VerticalAlignment="Center"
                Padding="24">
       <TextBlock Text="Привет, Windows 8!"
                  FontSize="80"/>
</Border>

Результат остается неизменным. В обоих случаях настройки HorizontalAlignment и VerticalAlignment элемента TextBlock ни на что не влияют.

При формировании макета поля считаются частью размера элемента, но в остальном они абсолютно неподконтрольны элементу. Например, элемент не может управлять цветом фона своих полей (этот цвет зависит от родителя элемента). Элемент не может получать пользовательский ввод из области полей. Если прикоснуться внутри полей элемента, событие Tapped получит родитель элемента.

Свойство Padding тоже относится к типу Thickness, но оно определяется лишь несколькими классами: Control, Border, TextBlock, RichTextBlock и RichTextBlockOverflow. Свойство Padding определяет область внутри элемента, которая считается частью элемента во всех отношениях, включая пользовательский ввод.

Если вы хотите, чтобы элемент TextBlock реагировал на касания не только на самом тексте, но и в пределах 100-пиксельной зоны, окружающей текст, задайте свойству Padding элемента TextBlock значение 100 (вместо того, чтобы задавать свойство Margin).

Rectangle и Ellipse

Пространство имен Windows.UI.Xaml.Shapes содержит классы для вывода векторной графики: линий, кривых и заполненных фигур. Класс Shape является производным от FrameworkElement и определяет различные свойства, включая Stroke (кисть, используемая для рисования прямых и кривых), StrokeThickness и Fill (кисть, используемая для закраски замкнутых областей).

У Shape существуют шесть производных классов. Line, Polyline и Polygon рисуют прямые линии по заданным координатам, a Path использует классы из Windows.UI.Xaml.Media для рисования наборов отрезков, дуг и кривых Безье.

Два оставшихся класса, производных от Shape, - Rectangle и Ellipse. Несмотря на безобидные имена, эти элементы весьма необычны; они определяют фигуры без использования координатных точек. Ниже приведен крошечный фрагмент разметки XAML для рисования эллипса:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Ellipse Stroke="LimeGreen"
                 StrokeThickness="20"
                 Fill="LightBlue" />
</Grid>

Обратите внимание на то, как эллипс заполняет свой контейнер:

Фигура эллипса в приложении WinRT

Как и у других классов, производных от FrameworkElement, у класса Ellipse свойства HorizontalAlignment и VerticalAlignment по умолчанию равны Stretch. Впрочем, Ellipse в большей степени, чем другие элементы, выставляет напоказ последствия от задания этих свойств.

Что произойдет, если задать свойству HorizontalAlignment или VerticalAlignment этого элемента Ellipse значение, отличное от используемого по умолчанию? Попробуйте! Эллипс просто исчезнет. Да и трудно представить себе какое-то другое поведение: если вы не хотите, чтобы эллипс или прямоугольник заполнял свой контейнер, остается только один путь: явное задание его ширины и высоты.

Класс Shape также определяет свойство Stretch (не путайте со значениями Stretch свойств HorizontalAlignment и VerticalAlignment). Это свойство похоже на свойство Stretch, определяемое элементами Image и Viewbox. Например, если в программе задать свойству Stretch значение Uniform, вы получите частный случай эллипса с одинаковыми горизонтальными и вертикальными радиусами, то есть окружность. В качестве диаметра выбирается меньшее из значений ширины и высоты контейнера. Если задать свойству Stretch значение UniformToFill, вы также получите окружность, но на этот раз диаметр будет равен большему из значений ширины и высоты контейнера, поэтому часть окружности окажется усеченной:

Использование эллипса со значение Stretch равным UniformToFill

Свойства HorizontalAlignment и VerticalAlignment позволяют управлять тем, какая именно часть окажется отсеченной.

Элемент Rectangle очень похож на Ellipse; он также имеет ряд общих характеристик с Border, хотя свойства называются по-другому:

Border Rectangle
BorderBrush Stroke
BorderThickness StrokeThickness
Background Fill
CornerRadius RadiusX / RadiusY

Принципиальное различие между Border и Rectangle заключается в том, что у Border есть свойство Child, а у Rectangle его нет, т.е. Border является панелью, а Rectangle фигурой.

Пройди тесты
Лучший чат для C# программистов