Фигуры и преобразования
123Silverlight 5 --- Фигуры и преобразования
Поддержка двухмерного рисования — фундамент многих изощренных средств Silverlight, таких как интерактивная графика, анимация и пользовательские элементы управления. Даже если вы не планируете создавать для приложения настраиваемые произведения искусства, вы должны основательно понимать встроенные в Silverlight средства рисования. Вы будете использовать их для добавления в приложение профессиональных эффектов и создания интерактивной графики. Например, фигуры могут изменяться или перемещаться в ответ на действия пользователя.
Платформа Silverlight позаимствовала у WPF целый набор средств рисования - фигуры, преобразования, маски прозрачности и т.д. Эти интерактивные средства аналогичны используемым в WPF и поэтому здесь не рассматриваются. За справкой обратитесь к разделу - Графика и анимация WPF. Ниже будут рассмотрены классы фигур и уникальный для Silverlight, класс перспективных преобразований.
Базовые фигуры
Создать двухмерный рисунок в пользовательском интерфейсе Silverlight проще всего с помощью фигур — встроенных классов, рисующих отрезки, эллипсы, прямоугольники, многоугольники и т.д. Другое название фигур — графические примитивы. Комбинируя примитивы разных типов, разработчик создает сложные изображения.
Все фигуры являются элементами и наследуют класс FrameworkElement. Отсюда вытекает ряд важных следствий:
Фигура рисует сама себя. Разработчику не нужно управлять процессом рисования. Например, при перемещении фигуры, изменении размеров страницы или свойств фигуры разработчику не нужно вручную задавать команды стирания старой фигуры и прорисовки новой.
Иерархически фигуры организованы так же, как и другие элементы. Фигуру можно разместить в контейнере. Чаще всего фигуры размещают в контейнере Canvas, поскольку он позволяет задавать координаты элементов, что важно при создании сложных рисунков из простых элементов.
Фигуры поддерживают те же события, что и другие элементы. Это означает, что для реагирования на щелчки мышью, перемещения мыши и нажатия клавиш не нужно вычислять координаты щелчка или программно выяснять, какой элемент имеет фокус; достаточно подключить к фигуре обработчик события, как к любому другому элементу.
Для ускорения прорисовки двухмерных рисунков на основе фигур в Silverlight используются специальные процедуры оптимизации. Например, когда фигуры перекрываются, Silverlight сначала выясняет, какие части фигур невидимы, чтобы не тратить время на их рисование.
Классы фигур
Каждая фигура наследует абстрактный класс System.Windows.Shapes.Shape:
На этой диаграмме видно, что в Silverlight определен относительно небольшой набор классов, производных от класса Shape (Фигура). Элементы Line (Отрезок), Ellipse (Эллипс) и Rectangle (Прямоугольник) довольно простые, их назначение очевидно из названия. Элемент Polyline выводит последовательность соединенных отрезков (ломаную линию), a Polygon (Многоугольник) — замкнутую ломаную линию. Класс Path (Контур) — наиболее мощный, он объединяет любые фигуры в одном элементе.
Базовый класс Shape сам по себе ничего не рисует. Он определяет набор важных свойств других фигур:
Свойство | Описание |
---|---|
Fill | Цвет фона замкнутой фигуры |
Stroke | Объект кисти, рисующий границу фигуры |
StrokeThickness | Толщина границы в пикселях |
StrokeStartLineCap и StrokeEndLineCap | Форма начала и конца отрезка. Эти свойства применимы только к фигурам Line, Polyline и (иногда) Path; все остальные фигуры замкнутые и не имеют начала и конца |
StrokeDashArray, StrokeDashOffset и StrokeDashCap | Свойства, управляющие выводом штриховой границы фигуры. С их помощью можно изменять длину и шаг штрихов, а также задавать, в каких точках границы начинаются штрихи |
StrokeLineJoin и StrokeMiterLimit | Свойства, определяющие форму вершин фигуры, т.е. точек, в которых пересекаются отрезки. На фигуры Line и Ellipse эти свойства не влияют |
Stretch | Свойство, определяющее заполнение фигурой доступного пространства. Его можно использовать, например, для создания фигуры, расширяющейся до размеров, позволяемых контейнером |
GeometryTransform | Свойство, позволяющее применить объект преобразования, который изменяет систему координат фигуры. С помощью этого свойства можно наклонять, поворачивать или смещать фигуру. Объекты преобразований особенно полезны в анимации |
Перспективные преобразования
В Silverlight версии ниже 5 нет полнофункционального набора инструментов для трехмерного рисования, но есть средства перспективных преобразований, позволяющие имитировать трехмерные поверхности. Как и при обычном преобразовании, объект перспективного преобразования манипулирует внешним видом существующего двухмерного элемента, проецируя его на повернутую поверхность. В результате манипулирования элемент выглядит так, будто находится на трехмерной поверхности.
Перспективные преобразования весьма полезны, однако им далеко до реальных трехмерных моделей. Их главный, самый очевидный, недостаток состоит в том, что преобразованию подвергается только одна фигура — плоский прямоугольник, на котором размещены элементы. Сравните: в действительно трехмерных моделях для построения сложных поверхностей используются крошечные совмещаемые треугольники. Для определения их координат и освещенности применяются сложные математические формулы, требующие огромного объема вычислений.
Если вам нужно всего лишь несколько трюков, чтобы имитировать простые трехмерные эффекты, не затрачивая слишком много труда, вам понравятся средства перспективного преобразования, встроенные в Silverlight. Они особенно полезны в сочетании с анимацией. Однако если вам необходимы настоящие трехмерные модели, перспективного преобразования будет явно недостаточно.
В Silverlight определен абстрактный класс Transform, наследуемый всеми классами двухмерных преобразований. Аналогично этому, все классы проецирования наследуют абстрактный класс System.Windows.Media.Projection. В настоящий момент Silverlight поддерживает два класса проецирования: PlaneProjection и намного более сложный Matrix3DProjection, в котором преобразования определяются с помощью трехмерных матриц.
Класс PlaneProjection позволяет поворачивать и смещать плоское изображение в трехмерном пространстве. Трехмерную плоскость можно повернуть вокруг оси X, Y или Z. На рисунке ниже показан поворот плоского изображения на 45° вокруг разных осей:
На этом рисунке элемент повернут вокруг своей центральной точки. С помощью свойств объекта PlaneProjection можно настроить параметры поворота.
Свойство RotationX определяет угол поворота вокруг оси Х. Свойство CenterOfRotationX содержит координату X центра поворота в относительных единицах. При значении 0 центр находится в крайней левой точке, 1 — крайней правой, 0.5 — посредине (это значение установлено по умолчанию). Поворот относительно других осей определяют аналогичные свойства.
Во многих случаях свойства поворота — единственное, что вам нужно будет изменить при использовании объекта PlaneProjection. Однако элемент, кроме этого, можно еще и сместить. Существуют два способа смещения:
Свойства GlobalOffsetX, GlobalOffsetY и GlobalOffsetZ определяют перемещение элемента в экранных координатах перед операцией проецирования.
Свойства LocalOffsetX, LocalOffsetY и LocalOffsetZ определяют перемещение элемента в преобразованных координатах после операции проецирования.
Когда элемент еще не повернут, глобальные и локальные свойства имеют одинаковые значения. Увеличение значений GlobalOffsetX или LocalOffsetX приведет к смещению элемента вправо.
Предположим, объект был повернут вокруг оси Y с помощью свойства RotationY. Теперь увеличение значения GlobalOffsetX приведет к смещению отображенного содержимого вправо (как и в предыдущем случае, когда элемент не был повернут), однако увеличение значения LocalOffsetX приведет к смещению не точно вправо, а вдоль новой оси X, которая теперь указывает новое трехмерное направление. В результате содержимое будет смещено вправо и вверх.
Операцию проецирования можно применить почти к любому элементу Silverlight, потому что каждый класс, производный от UIElement, имеет необходимое для этого свойство Projection. Чтобы получить для элемента эффект перспективы, нужно создать объект PlaneProjection и присвоить его свойству Projection в коде или разметке XAML.
Ниже приведена разметка, поворачивающая среднее изображение на приведенном выше рисунке:
<Grid Background="SlateGray">
<Border BorderBrush="#555" Background="White" BorderThickness="4" Margin="5">
<Border.Projection>
<PlaneProjection RotationY="45"/>
</Border.Projection>
<Path Data="F1 M 40,35L 40,18L 37,18L 37,19L 36,19L 36,28L 34,28L 34,26L 31.0209,26L 31,24L 30.5,14.5L 29,14L 27.5,9.00001L
26,14L 24.5,14.5L 24,24L 24,28L 23,28L 23,26L 19,26L 19,30L 17,30L 17,20L 11,20L 11,28L 9,28L 9,27L 8,27L 7.99999,35L 40,35 Z"
Stroke="#fbac14" Stretch="Fill" Fill="#fbac14"
/>
</Border>
</Grid>
Как и обычное, перспективное преобразование выполняется после размещения элементов. На рисунке этот факт проиллюстрирован с помощью закрашенных областей, расположенных в исходной позиции. Элементы получили новые позиции, однако области с закрашенным фоном по-прежнему используются для вычисления позиций. Выполняется правило, применимое почти ко всем элементам: если элементы перекрываются, поверх других выводится элемент, объявленный последним (некоторые элементы, например Canvas, подчиняются не этому правилу, а значению свойства ZIndex).
Чтобы лучше понять, как взаимодействуют свойства объекта PlaneProjection, поэкспериментируйте с простым тестовым приложением, в котором с помощью ползунков пользователь может поворачивать элемент вокруг осей X, Y и Z. Кроме того, элемент можно сместить локально или глобально вдоль оси Х с помощью свойств LocalOffsetX и GlobalOffsetX, описанных выше:
<Grid Background="#888">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid x:Name="transformGrid">
<Border Width="300" Height="200" BorderBrush="#444" BorderThickness="4">
<Border.Projection>
<PlaneProjection x:Name="planeProjection"/>
</Border.Projection>
<StackPanel>
<TextBlock FontSize="16" Text="Введите данные:" Margin="10,5"/>
<TextBox Margin="10,5" />
<Button Padding="5" Margin="0,5" Content="OK" HorizontalAlignment="Center" Width="80"/>
<Image Source="super_man.png" Width="64" Height="64"/>
</StackPanel>
</Border>
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="RotationX" FontSize="14" Margin="6"/>
<Slider Grid.Column="1" Minimum="-180" Maximum="180" Margin="6"
Value="{Binding ElementName=planeProjection, Path=RotationX, Mode=TwoWay}"/>
<TextBlock Text="RotationY" Grid.Row="1" FontSize="14" Margin="6"/>
<Slider Grid.Column="1" Grid.Row="1" Minimum="-180" Maximum="180" Margin="6"
Value="{Binding ElementName=planeProjection, Path=RotationY, Mode=TwoWay}"/>
<TextBlock Text="RotationZ" FontSize="14" Grid.Row="2" Margin="6"/>
<Slider Grid.Column="1" Minimum="-180" Maximum="180" Grid.Row="2" Margin="6"
Value="{Binding ElementName=planeProjection, Path=RotationZ, Mode=TwoWay}"/>
<StackPanel Margin="6" HorizontalAlignment="Center" Orientation="Horizontal" Grid.Row="3" Grid.ColumnSpan="2">
<Button x:Name="GlobalOffsetXUp" Content="+GlobalOffsetX" Margin="3" Padding="5" Click="cmdClick" />
<Button x:Name="GlobalOffsetXDown" Content="-GlobalOffsetX" Margin="3" Padding="5" Click="cmdClick" />
<Button x:Name="LocalOffsetXUp" Content="+LocalOffsetX" Margin="3" Padding="5" Click="cmdClick" />
<Button x:Name="LocalOffsetXDown" Content="+LocalOffsetX" Margin="3" Padding="5" Click="cmdClick" />
</StackPanel>
</Grid>
</Grid>
private void cmdClick(object sender, RoutedEventArgs e)
{
switch (((Button)e.OriginalSource).Name)
{
case "GlobalOffsetXUp":
planeProjection.GlobalOffsetX += 10;
break;
case "GlobalOffsetXDown":
planeProjection.GlobalOffsetX -= 10;
break;
case "LocalOffsetXUp":
planeProjection.LocalOffsetX += 10;
break;
case "LocalOffsetXDown":
planeProjection.LocalOffsetX -= 10;
break;
}
}
Проецирование можно применить к любому элементу. Эта операция часто полезна, когда необходимо разместить в контейнере больше элементов. Приведенный выше пример интересен тем, что проецируемые элементы (кнопка и текстовое поле) и в повернутом состоянии продолжают взаимодействовать стандартным образом: реагируют на щелчки мыши, получают фокус ввода и т.п.