#021 Применение 3D в WPF — Часть 2

Автор: Topol Воскресенье, Май 6th, 2012 Нет комментариев

Рубрика: Операционные системы

В прошлой статье мы начали работу над проектом «my3dSample» и подготовили базовый костяк нашего приложения. Откройте ваш проект, чтобы продолжить работу по внедрению трехмерной графике.

По нашему замыслу в правой части окна будет находиться контейнер Frame, с которым вы уже знакомы по статье №017. Этот контейнер будет отображать содержимое нашего «справочного материала» в виде XAML-страниц (Page). Перед тем как разместить на форме Frame и использовать 3D для его анимации, давайте подготовим эти «страницы». При помощи команды меню Visual Studio  »Project -> Add New Item…» добавьте к проекту три файла типа «WinFX Page» с именами Page1.xaml, Page2.xaml и Page3.xaml .

Теперь давайте заполним эти файлы любым содержимым.

Код для файла Page1.xaml:

Код:
<Grid Background =»Gray»>
<TextBlock FontFamily =»SegoeUI» FontSize =»16″>
Заголовок первого раздела
</TextBlock>

<TextBlock Margin =»0,30,0,0″ FontFamily =»SegoeUI» FontSize =»12″>
Содержание первого раздела Содержание первого раздела
Содержание первого раздела
</TextBlock>

<TextBlock Margin =»0,60,0,0″ FontFamily =»SegoeUI» FontSize =»12″>
Содержание первого раздела Содержание первого раздела
Содержание первого раздела
</TextBlock>

<Rectangle Margin =»90,90,0,0″ HorizontalAlignment =»Left»
VerticalAlignment =»Top»
Fill =»Yellow» Width =»100″ Height =»50″/>
</Grid>

Код для файла Page2.xaml:

Код:
<Grid Background =»Gray»>
<TextBlock FontFamily =»SegoeUI» FontSize =»16″>
Заголовок второго раздела
</TextBlock>

<TextBlock Margin =»0,30,0,0″ FontFamily =»SegoeUI» FontSize =»12″>
Содержание второго раздела Содержание второго раздела
Содержание второго раздела
</TextBlock>

<TextBlock Margin =»0,60,0,0″ FontFamily =»SegoeUI» FontSize =»12″>
Содержание второго раздела Содержание второго раздела
Содержание второго раздела
</TextBlock>

<Rectangle Margin =»90,90,0,0″ HorizontalAlignment =»Left»
VerticalAlignment =»Top»
Fill =»Red» Width =»100″ Height =»50″/>

</Grid>

Код для файла Page3.xaml:

Код:
<Grid Background =»Gray»>
<TextBlock FontFamily =»SegoeUI» FontSize =»16″>
Заголовок третьего раздела
</TextBlock>

<TextBlock Margin =»0,30,0,0″ FontFamily =»SegoeUI» FontSize =»12″>
Содержание третьего раздела Содержание третьего раздела
Содержание третьего раздела
</TextBlock>

<TextBlock Margin =»0,60,0,0″ FontFamily =»SegoeUI» FontSize =»12″>
Содержание третьего раздела Содержание третьего раздела
Содержание третьего раздела
</TextBlock>

<Rectangle Margin =»90,90,0,0″ HorizontalAlignment =»Left»
VerticalAlignment =»Top»
Fill =»Blue» Width =»100″ Height =»50″/>
</Grid>

Мы внесли очень простое содержание в наши страницы (Page). Они содержат по две текстовые строки и по одному графическому элементу. Для выполения нашего задания нам не важно содержимое этих «страниц». Вы же можете воспользоваться возможностями подключения XML-файлов для заполнения содержимого этих страниц, если хотите. Технологию подключения внешних данных мы рассматривали в статье №019, а работу с XML в статье №017.

Итак, все готово для того, чтобы разместить на нашей форме Window1 контейнер Frame, который будет отображать только что созданные страницы. В XAML-коде окна найдите элемент DockPanel с именем Name=»Details» . Именно в эту зону мы будем вносить изменения. Отредактируйте эту часть кода так, чтобы она пришла в соответствие с изложенным ниже:

Код:
<DockPanel Name=»Details»  DockPanel.LastChildFill=»True»>

<Grid >

<Rectangle Fill=»White» RadiusX=»14″ RadiusY=»14″
Margin=»0,0,0,8″ StrokeDashArray=»2″/>

<Border Name=»scrollViewerBorder»>
<ScrollViewer Name=»myScrollViewer» Background=»White»
HorizontalScrollBarVisibility=»Auto»
VerticalScrollBarVisibility=»Auto»
HorizontalAlignment=»Stretch»
VerticalAlignment=»Stretch»>
<Frame Name=»mainFrame» Background=»White»
ContentRendered=»frameContentRendered»
NavigationUIVisibility=»Hidden» >
<Frame.RenderTransform>
<ScaleTransform />
</Frame.RenderTransform>
</Frame>
</ScrollViewer>
</Border>
<Border>
<Border ClipToBounds=»True»>
<Viewbox Stretch=»Fill»
Width=»{Binding ElementName=myScrollViewer, Path=ActualWidth}»
Height=»{Binding ElementName=myScrollViewer, Path=ActualHeight}»>

<Viewport3D
Name=»myViewport3D»
Opacity=»1″ IsHitTestVisible=»False»>
<Viewport3D.Camera>
<PerspectiveCamera
LookDirection=»0,0,-2″
UpDirection=»0,1,0″
Position=»0,0,5″
FieldOfView=»90″/>
</Viewport3D.Camera>

<ModelVisual3D>
<ModelVisual3D.Children>
<ModelVisual3D>
<ModelVisual3D.Content>
<AmbientLight Color=»#FFFFFFFF» />
</ModelVisual3D.Content>
</ModelVisual3D>
<ModelVisual3D>
<ModelVisual3D.Children>
<ModelVisual3D>
<ModelVisual3D.Content>
<GeometryModel3D x:Name=»myPlane»>
<GeometryModel3D.Geometry>
<MeshGeometry3D
x:Name=»myGeometry»
TriangleIndices=»0,1,2 3,4,5, 11,10,9,8,7,6″
TextureCoordinates=»0,0 0,-1 -1,-1 -1,-1 -1,0 0,0   -1,-1 -1,0 0,0 0,0 0,-1 -1,-1  »
Positions=»12,-9,0 12,9,0 -12,9,0 -12,9,0 -12,-9,0 12,-9,0″ />
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<VisualBrush
Visual=»{Binding ElementName=myScrollViewer}» />
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</GeometryModel3D.Material>
</GeometryModel3D>
</ModelVisual3D.Content>
<ModelVisual3D.Transform>
<Transform3DGroup>
<RotateTransform3D
x:Name=»myHorizontalRotation»
CenterX=»0″ CenterY=»0″ CenterZ=»0″>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D
Axis=»0,1,0″ Angle=»0″
x:Name=»MyHorizontalAxisAngleRotation3D» />
</RotateTransform3D.Rotation>
</RotateTransform3D>
<ScaleTransform3D
x:Name=»MyScaleTransform3D»
ScaleX=»1″ ScaleY=»1″ ScaleZ=»1″ />
</Transform3DGroup>
</ModelVisual3D.Transform>
</ModelVisual3D>
</ModelVisual3D.Children>
</ModelVisual3D>
</ModelVisual3D.Children>
</ModelVisual3D>

</Viewport3D>
</Viewbox>
</Border>
</Border>
</Grid>
</DockPanel>

Мы полностью изменили содержание контейнера DockPanel с именем Details. К нашему коду добавилось два контейнера типа Border — один из них содержит ScrollViewer (мы применили его, чтобы обеспечить прокрутку в том случае если отображаемая страница не будет помещаться в контейнер Frame) и контейнер Frame, который будет отображать наши «страницы»; второй Border содержит описание 3D-сцены на основе элементов ViewPort и ViewPort3D. Мы поступили по аналогии с тем, как реализовывали решение задания статьи №019 — в сцене присутствуют ссылки на элемент ScrollViewer. Что это значит? Это означает, что сцена будет заполняться при помощи VisualBrush нашим ScrollViewer-ом! Вероятно вы уже догадались, что анимировать мы будем именно ScrollViewer. Во время написания этого блока кода мы указали свойство ContentRendered=»frameContentRendered» для элемента Frame. По сути это означает следующее: «Выполнить код в процедуре frameContentRendered после того, как содержимое Frame будет отображено». Поэтому необходимо добавить описание этой функции к коду нашего проекта:

Код VB:

Код:
Private Sub frameContentRendered(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles mainFrame.ContentRendered

scrollViewerBorder.Visibility = Visibility.Visible
End Sub

Код С#:

Код:
private void frameContentRendered(object sender, EventArgs args)
{
scrollViewerBorder.Visibility = Visibility.Visible;
}

Мы сразу внесли небольшой код в эти процедуры. Он понадобится нам в будущем. На этом этапе я очень рекомендую вам запустить проект:

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

После описания свойств окна и перед описанием первого контейнера Grid. Внесите такой код (будет создан раздел Window.Triggers отвечающий за анимацию):

Код:
<Window.Triggers >
<EventTrigger RoutedEvent=»RadioButton.Checked»>
<BeginStoryboard>
<Storyboard
CurrentStateInvalidated=»storyboardStateInvalidated»
Name=»MyStory»>
<DoubleAnimation
Storyboard.TargetName=»MyHorizontalAxisAngleRotation3D»
Storyboard.TargetProperty=»Angle»
From=»0″ To=»360″ Duration=»0:0:1″
AutoReverse=»False» FillBehavior=»Stop»
BeginTime=»0:0:1″ AccelerationRatio=»1″
/>
<DoubleAnimation Storyboard.TargetName=»MyScaleTransform3D»
Storyboard.TargetProperty=»ScaleX»
From=»1″ To=»0″ Duration=»0:0:2″  AutoReverse=»False»
AccelerationRatio=»1″
/>
<DoubleAnimation Storyboard.TargetName=»MyScaleTransform3D»
Storyboard.TargetProperty=»ScaleY»
From=»1″ To=»0″ Duration=»0:0:2″  AutoReverse=»False»
AccelerationRatio=»1″
/>
<DoubleAnimation Storyboard.TargetName=»MyScaleTransform3D»
Storyboard.TargetProperty=»ScaleZ»
From=»1″ To=»0.1″ Duration=»0:0:2″  AutoReverse=»False»
AccelerationRatio=»1″
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>

Что содержит этот код? Четыре различных анимации, изменяющие разные свойства, но все эти анимации объединяет одно событие — когда любой элемент типа RadioButton будет выбран (то есть примет состояние Checked= «true»). Теперь вам должно быть понятно почему в самом начале мы указали ссылки для всех трех элементов RadioButton на одну и туже процедуру sampleSelected. В ней мы виртуально определим какой именно из трех RadioButton был выбран, чтобы выполнить требуемое действие.

Добавьте строку Loaded=»pageLoaded» к описанию свойств окна. На этом редактирование XAML-кода нашего проекта закончено. Переходим к кодированию кода VB/C#. Приведите ваш код в соответствие с этим листингом:

Код VB:

Код:
Imports System.Windows.Media
Imports System.Windows.Media.Media3D
Imports System.Windows.Media.Animation

Partial Public Class Window1
Inherits Window

Dim pages(3) As Page
Dim sampleIndex As Integer

Public Sub New()
InitializeComponent()

pages(0) = New Page1
pages(1) = New Page2
pages(2) = New Page3

End Sub

Private Sub pageLoaded(ByVal sender As Object, _
ByVal args As RoutedEventArgs)
mainFrame.Navigate(pages(0))
End Sub

Private Sub frameContentRendered(ByVal sender As Object, _
ByVal e As System.EventArgs)
scrollViewerBorder.Visibility = Visibility.Visible
End Sub

Private Sub storyboardStateInvalidated(ByVal sender As Object, _
ByVal args As EventArgs)
Dim myStoryboardClock As Clock = CType(sender, Clock)

If myStoryboardClock.CurrentState = ClockState.Filling Or _
myStoryboardClock.CurrentState = ClockState.Stopped Then
mainFrame.Navigate(pages(sampleIndex))
End If
End Sub

Private Sub sampleSelected(ByVal sender As Object, _
ByVal args As RoutedEventArgs)

Dim points As Point3DCollection = New Point3DCollection

Dim ratio As Double = _
myScrollViewer.ActualWidth / myScrollViewer.ActualHeight

points.Add(New Point3D(5, -5 * ratio, 0))
points.Add(New Point3D(5, 5 * ratio, 0))
points.Add(New Point3D(-5, 5 * ratio, 0))

points.Add(New Point3D(-5, 5 * ratio, 0))
points.Add(New Point3D(-5, -5 * ratio, 0))
points.Add(New Point3D(5, -5 * ratio, 0))

points.Add(New Point3D(-5, 5 * ratio, 0))
points.Add(New Point3D(-5, -5 * ratio, 0))
points.Add(New Point3D(5, -5 * ratio, 0))

points.Add(New Point3D(5, -5 * ratio, 0))
points.Add(New Point3D(5, 5 * ratio, 0))
points.Add(New Point3D(-5, 5 * ratio, 0))

myGeometry.Positions = points
myViewport3D.Width = 100
myViewport3D.Height = 100 * ratio

scrollViewerBorder.Visibility = Visibility.Hidden

Dim button As RadioButton = CType(sender, RadioButton)

If button IsNot Nothing Then
If button.Content.ToString = «Раздел справки 1″ Then
sampleIndex = 0
ElseIf button.Content.ToString = «Раздел справки 2″ Then
sampleIndex = 1
ElseIf button.Content.ToString = «Раздел справки 3″ Then
sampleIndex = 2
End If
End If
End Sub

End Class

Код С#:

Код:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Animation;

namespace my3dSample
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>

public partial class Window1 : Window
{
private Page[] pages;
private int sampleIndex;

public Window1()
{
InitializeComponent();
pages = new Page[3];

pages[0] = new Page1();
pages[1] = new Page2();
pages[2] = new Page3();

}

public void pageLoaded(object sender, RoutedEventArgs args)
{

mainFrame.Navigate(pages[0]);
}

private void storyboardStateInvalidated(object sender, EventArgs args)
{

Clock myStoryboardClock = (Clock)sender;
if (myStoryboardClock.CurrentState == ClockState.Filling || myStoryboardClock.CurrentState == ClockState.Stopped)
{

mainFrame.Navigate(pages[sampleIndex]);

}

}

private void frameContentRendered(object sender, EventArgs args)
{
scrollViewerBorder.Visibility = Visibility.Visible;
}

private void sampleSelected(object sender, RoutedEventArgs args)
{

Point3DCollection points = new Point3DCollection();

double ratio = _
myScrollViewer.ActualWidth / myScrollViewer.ActualHeight;

points.Add(new Point3D(5, -5 * ratio, 0));
points.Add(new Point3D(5, 5 * ratio, 0));
points.Add(new Point3D(-5, 5 * ratio, 0));

points.Add(new Point3D(-5, 5 * ratio, 0));
points.Add(new Point3D(-5, -5 * ratio, 0));
points.Add(new Point3D(5, -5 * ratio, 0));

points.Add(new Point3D(-5, 5 * ratio, 0));
points.Add(new Point3D(-5, -5 * ratio, 0));
points.Add(new Point3D(5, -5 * ratio, 0));

points.Add(new Point3D(5, -5 * ratio, 0));
points.Add(new Point3D(5, 5 * ratio, 0));
points.Add(new Point3D(-5, 5 * ratio, 0));

myGeometry.Positions = points;
myViewport3D.Width = 100;
myViewport3D.Height = 100 * ratio;

scrollViewerBorder.Visibility =Visibility.Hidden; // = 0;

RadioButton button = sender as RadioButton;

if (button != null)
{

if (button.Content.ToString() == «Раздел справки 1″)
sampleIndex = 0;

else if (button.Content.ToString() == «Раздел справки 2″)
sampleIndex = 1;

else if (button.Content.ToString() == «Раздел справки 3″)
sampleIndex = 2;

}
}
}
}

Итак, наш проект закончен. Вы можете запустить приложение и убедиться, что при нажатии на переключатели слева, справа отображается нужная страница, а переходы между страницами осуществляются динамически, в 3D-сцене:

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

Источник: thevista.ru

Оставить комментарий

Чтобы оставлять комментарии Вы должны быть авторизованы.

Похожие посты