Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
03.11.13 / ! / Создаем контекстно.doc
Скачиваний:
54
Добавлен:
08.06.2015
Размер:
2.64 Mб
Скачать

#019 Введение в возможности 3d на wpf

Windows Presentation Foundation предлагает действительно широкие возможности в сфере отображения контента - будь то растровая графика, векторная анимация, видео или 3D. Именно о трехмерной графике мы и начнем говорить в сегодняшней статье. Этот материал можно считать кратким вступлением в технологии трехмерной графики в WPF . Создайте новый проект типа "WinFX Windows Application" и задайте ему имя "my3Dtest". Внесем небольшие изменения в свойства окна для того, чтобы оно больше соответствовало отображаемому контенту:

Код:

<Window x:Class="Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Возможности 3D в WPF" Height="600" Width="800" MinHeight ="480" MinWidth ="640" > <Window.Background > <LinearGradientBrush > <LinearGradientBrush.RelativeTransform > <RotateTransform Angle ="90"/> </LinearGradientBrush.RelativeTransform> <LinearGradientBrush.GradientStops > <GradientStop Offset ="0" Color ="Gold"/> <GradientStop Offset ="1" Color ="White"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Window.Background> <Grid> </Grid> </Window>

Здесь мы задали новые значения для размеров формы, а также определили градиентную заливку для фона окна (Window.Background). Обратите внимание как можно изменить заливку, просто развернув ее на 90 градусов (LinearGradientBrush.RelativeTransform). Внимание - материал данной статьи повышенной сложности. Если вы не изучали предыдущие статьи, возможно у Вас не получится выполнить задание этого проекта! Давайте определимся с тем, что мы реализуем в данном проекте. Мы разделим контейнер Grid на две строки - в верхней будет находиться трехмерная сцена, а в нижней элемент ListBox: В ListBox мы будем загружать изображения автоматически из некой папки (допустим C:\Images) а в трехмерной сцене будет отображаться выбранное изображение в трех экземплярах с эффектом отражения. Давайте организуем нужную разметку контейнера Grid и сразу поместим во вторую строку ListBox. Трехмерной сценой мы займемся позже:

Код:

<Grid> <Grid.RowDefinitions > <RowDefinition /> <RowDefinition Height ="Auto"/> </Grid.RowDefinitions> <ListBox Grid.Row ="1"/> </Grid>

Разбивку контейнера Grid на строки и столбцы, а также размещение элемента в нужной строке или столбце мы подробно рассматривали в статье №017, поэтому я не буду комментировать этот блок кода. Мы уже рассматривали размещение изображений в качестве контента для ListBox, однако сегодня перед нами стоит более сложная задача. Источником данных для списка будет папка C:\Images. Перед выполнением дальнейших инструкций создайте такую папку и разместите в ней несколько изображений в формате JPG. Выберите команду меню Visual Studio "Project->Add Class" и в появившемся окне задайте имя "images.vb". Замените содержимое этого класса на следующее:

Код:

Imports System Imports System.Collections.ObjectModel Imports System.IO Imports System.Windows.Media.Imaging Public Class Photo Public Sub New(ByVal path As String) _source = path End Sub Public Overrides Function ToString() As String Return Source End Function Private _source As String Public ReadOnly Property Source() As String Get Return _source End Get End Property End Class Public Class PhotoList Inherits ObservableCollection(Of Photo) Public Sub New() End Sub Public Sub New(ByVal path As String) Me.New(New DirectoryInfo(path)) End Sub Public Sub New(ByVal directory As DirectoryInfo) _directory = directory Update() End Sub Public Property Path() As String Get Return _directory.FullName End Get Set(ByVal Value As String) _directory = New DirectoryInfo(Value) Update() End Set End Property Public Property Directory() As DirectoryInfo Get Return _directory End Get Set(ByVal Value As DirectoryInfo) _directory = Value Update() End Set End Property Private Sub Update() Clear() Dim f As FileInfo For Each f In _directory.GetFiles("*.jpg") Add(New Photo(f.FullName)) Next End Sub Dim _directory As DirectoryInfo End Class

Примеры я привожу на базе Visual Basic. Код достаточно прост и, при необходимости, его легко транслировать на C# если есть такая необходимость. Здесь мы создали два класса - один с именем Photo - содержит описание файла для отображения, а второй с именем PhotoList - представляет собой коллекцию элементов типа Photo. Пожалуйста обратите внимание на этот код, так как этот метод является базовым для выполнения задач, сходных с нашей. Вы можете в последствии использовать его как базис для собственного приложения! При помощи команды "Project - > Add new Item…" добавьте к проекту новый файл типа "WinFX ResourceDictionary" и задайте ему имя "MyStyles.xaml". Перейдите в режим редактирования XAML-кода только что созданного файла и внесите такой код:

Код:

<Style TargetType="{x:Type ListBox}"> <Setter Property="Foreground" Value="White" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBox}" > <Border Width="120" Padding="5" BorderThickness="0.5" CornerRadius="6" VerticalAlignment="Center" HorizontalAlignment="Center" > <Border.BorderBrush> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <LinearGradientBrush.GradientStops> <GradientStop Offset="0.0" Color="#88000000" /> <GradientStop Offset="1.0" Color="#DDFFFFFF" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Border.BorderBrush> <Border.Background> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <LinearGradientBrush.GradientStops> <GradientStop Offset="0.0" Color="#CCFFFFFF" /> <GradientStop Offset="1.0" Color="#55FFFFFF" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Border.Background> <ScrollViewer VerticalScrollBarVisibility="Auto"> <StackPanel IsItemsHost="true" Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center" /> </ScrollViewer> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="{x:Type ListBoxItem}" TargetType="{x:Type ListBoxItem}"> <Setter Property="FocusVisualStyle" Value="{x:Null}" /> <Setter Property="Opacity" Value="0.5" /> <Setter Property="MaxWidth" Value="75" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <Border x:Name="ContentBorder" BorderThickness="1" BorderBrush="Transparent"> <ContentPresenter /> </Border> <ControlTemplate.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter Property="Opacity" Value="1.0" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <EventTrigger RoutedEvent="Mouse.MouseEnter"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="MaxWidth" To="90" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Mouse.MouseLeave"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:0:1" Storyboard.TargetProperty="MaxWidth" To ="75" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Style.Triggers> </Style>

Так же необходимо подключить этот словарь ресурсов к нашему окну Window1:

Код:

<Window.Resources> <ResourceDictionary > <ResourceDictionary.MergedDictionaries > <ResourceDictionary Source ="MyStyles.xaml"/> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Window.Resources>

Использование стилей и словарей стилей рассматривалось в статье №013. Просмотрите эту статью, если у вас возникли трудности в выполнении этой части проекта. Теперь необходимо добавить шаблоны данных (с этим понятием мы знакомились в статье №017) в секцию Window.Resources внутрь блока ResourceDictionary:

Код:

<Window.Resources> <ResourceDictionary > <ResourceDictionary.MergedDictionaries > <ResourceDictionary Source ="MyStyles.xaml"/> </ResourceDictionary.MergedDictionaries> <ObjectDataProvider x:Name="PhotosODP" x:Key="Photos" ObjectType="{x:Type ps:PhotoList}"/> <DataTemplate DataType="{x:Type ps:Photo}"> <Border VerticalAlignment="Center" HorizontalAlignment="Center" Padding="4" Margin="2" Background="White"> <Image Source="{Binding Source}" /> </Border> </DataTemplate> </ResourceDictionary> </Window.Resources>

Необходимо отредактировать описание окна Window1. Приведите код в соответствии с указанным ниже:

Код:

<Window x:Class="Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Возможности 3D в WPF" Height="600" Width="800" MinHeight ="480" MinWidth ="640" xmlns:ps="clr-namespace:my3Dtest" Loaded="WindowLoaded" > …дальнейший код</Window>

Дело в том, что созданные недавно шаблоны данных ссылаются на классы, определенные в файле images.vb и для того, чтобы эти классы были "видны" в XAML-коде мы создали ссылку с именем "ps" указывающим на пространство имен нашего приложения. В дальнейшем мы использовали эту ссылку для обозначения наших шаблонов данных - x:Type ps:Photo и т.д. Также мы определили процедуру WindowLoaded которая будет выполняться при загрузке окна. Давайте внесем необходимый код. Перейдите в режим редактирования VB-кода окна и внесите следующие изменения:

Код:

Partial Public Class Window1 Inherits Window Dim CurrentDir As String = vbNullString Dim Photos As PhotoList Public Sub New() InitializeComponent() End Sub Private Sub WindowLoaded(ByVal sender As Object, _ ByVal e As System.Windows.RoutedEventArgs) _ Handles Me.Loaded Photos = CType((CType(Me.Resources("Photos"), _ ObjectDataProvider)).Data, PhotoList) CurrentDir = "C:\Images" Photos.Path = CurrentDir End Sub

Что делает этот код? Мы создали две переменные - CurrentDir для хранения адреса той папки, из которой будут загружаться изображения и Photos - образец класса PhotoList для хранения изображений. В методе WindowLoaded мы присваеваем созданной переменной Photos значение того шаблона данных, который мы определили в XAML-коде. Именно так мы смогли объединить данные определенные в xaml и код visual basic. После этого мы передаем шаблону данных адрес нашей папки - C:\Images Все, что нам осталось сделать - это подключить созданные шаблоны данных к списку ListBox:

Код:

<ListBox Grid.Row ="1" Name="PhotosListBox" DataContext="{Binding Source={StaticResource Photos}}" ItemsSource="{Binding }"/>

Давайте запустим проект и посмотрим на результат: Мы добились желаемого - ListBox отображает содержимое папки C:\Images.! Так же при помощи стилей мы придали нашему списку необычный вид и необычное поведение. Обратите внимание, как плавно и красиво двигаются элементы списка при наведении мыши! Единственное, что мы недоглядели - это ориентация списка. В данный момент она вертикальная, а мы задумывали ее как горизонтальную. Давайте это исправим. Внесите небольшое изменение в код стиля списка в соответствии с листингом:

Код:

<Style TargetType="{x:Type ListBox}"> <Setter Property="Foreground" Value="White" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBox}" > <Border Height="120" Padding="5" BorderThickness="0.5" CornerRadius="6" VerticalAlignment="Center" HorizontalAlignment="Center" > <Border.BorderBrush> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <LinearGradientBrush.GradientStops> <GradientStop Offset="0.0" Color="#88000000" /> <GradientStop Offset="1.0" Color="#DDFFFFFF" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Border.BorderBrush> <Border.Background> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <LinearGradientBrush.GradientStops> <GradientStop Offset="0.0" Color="#CCFFFFFF" /> <GradientStop Offset="1.0" Color="#55FFFFFF" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Border.Background> <ScrollViewer VerticalScrollBarVisibility="Auto"> <StackPanel IsItemsHost="true" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" /> </ScrollViewer> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="{x:Type ListBoxItem}" TargetType="{x:Type ListBoxItem}"> <Setter Property="FocusVisualStyle" Value="{x:Null}" /> <Setter Property="Opacity" Value="0.5" /> <Setter Property="MaxHeight" Value="75" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <Border x:Name="ContentBorder" BorderThickness="1" BorderBrush="Transparent"> <ContentPresenter /> </Border> <ControlTemplate.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter Property="Opacity" Value="1.0" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <EventTrigger RoutedEvent="Mouse.MouseEnter"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="MaxHeight" To="90" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Mouse.MouseLeave"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:0:1" Storyboard.TargetProperty="MaxHeight" To ="75" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Style.Triggers> </Style>

Давайте запустим проект: Итак, мы закончили выполнение первой части нашего задания. Давайте перейдем к созданию трехмерной сцены в первой строке нашего контейнера Grid. Как видно из схемы проекта, который я привел в начале статьи, нам необходимо создать три фигуры в трехмерном пространстве. Давайте определим эти три фигуры. Внесите нижеизложенный код в секцию Window.Resources в подсекцию ResourceDictionary после определения шаблонов для списка:

Код:

<Window.Resources> <ResourceDictionary > …код относящийся к ListBox… <MeshGeometry3D x:Key="Mesh" Positions="-0.52,1.2,0 0.44,1,0 0.5,0,0 -0.5,0.2,0 0.5,-1,0.2 -0.5,-.7,0.2" Normals="0,0,1 0,0,1 0,0,1 0,0,1 0,0,1 0,0,1" TextureCoordinates="0,0 1,0 1,1 0,1 1,0 0,0" TriangleIndices="1 0 3 2 1 3 4 3 5 4 2 3" /> <MeshGeometry3D x:Key="Mesh1" Positions="-0.485,1,0 0.485,1,0 0.5,0,0 -0.5,0,0 0.5,-1,0 -0.5,-1,0 " Normals="0,0,1 0,0,1 0,0,1 0,0,1 0,0,1 0,0,1" TextureCoordinates="0,0 1,0 1,1 0,1 1,0 0,0" TriangleIndices="1 0 3 2 1 3 4 3 5 4 2 3" /> <MeshGeometry3D x:Key="Mesh2" Positions="-0.44,01,0 0.52,1.2,0 0.5,0.2,0 -0.5,0,0 0.5,-1,0 -0.5,-1.2,0 " Normals="0,0,1 0,0,1 0,0,1 0,0,1 0,0,1 0,0,1" TextureCoordinates="0,0 1,0 1,1 0,1 1,0 0,0" TriangleIndices="1 0 3 2 1 3 4 3 5 4 2 3" /> </ResourceDictionary> </Window.Resources>

С помощью элемента MeshGeometry3D мы создали три фигуры с именами Mesh, Mesh1 и Mesh2. Обратите внимание на то, как много параметров приходится определять, чтобы построить трехмерную фигуру. При этом все координаты указываются не в двух, а в трех измерениях - по осям X, Y и Z. Пусть на данный момент вас не беспокоит вопрос о том, как определить нужные координаты, так как целью сегодняшнего введения в 3D стоит изучение концепции внедрения трехмерных объектов, а не их определение. В будущем мы обязательно рассмотрим средства визуального моделирования трехмерных фигур. Для отображения трехмерного контента чаще всего используется специальный контейнер Viewport3D. Именно его мы разместим в верхней строке нашего Grid для отображения этих трех фигур. Для удобства мы вложим наш Viewport3D в еще один Grid. Теперь давайте определим еще один шаблон, который будет отображать наше изображение с эффектом отражения. По аналогии с предыдущими шаблонами внесите нижеизложенный код в секцию Window.Resources в подсекцию ResourceDictionary после определения шаблонов для списка:

Код:

<DataTemplate x:Key="ReflectedPhoto" > <Grid HorizontalAlignment="Center"> <Grid.RowDefinitions> <RowDefinition Height="180" /> <RowDefinition /> </Grid.RowDefinitions> <!-- reflection--> <Border Grid.Row="1" VerticalAlignment="Top" Width="{Binding ElementName=MainBorder, Path=ActualWidth}" Background="White" BorderBrush="#EEEEEE" BorderThickness="1"> <Border.LayoutTransform> <ScaleTransform ScaleX="1" ScaleY="-0.5"/> </Border.LayoutTransform> <Border.OpacityMask> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <LinearGradientBrush.GradientStops> <GradientStop Offset="0.0" Color="Transparent" /> <GradientStop Offset="1.0" Color="#50000000" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Border.OpacityMask> <Image Margin="7" Width="{Binding ElementName=MainImage, Path=ActualWidth}" Source="{Binding Source}"/> </Border> <!-- shadow --> <Rectangle Grid.Row="0" VerticalAlignment="Bottom" HorizontalAlignment="Center" Width="{Binding ElementName=MainBorder, Path=ActualWidth}" Height="30"> <Rectangle.RenderTransform> <TranslateTransform X="0" Y="15" /> </Rectangle.RenderTransform> <Rectangle.Fill> <RadialGradientBrush> <RadialGradientBrush.GradientStops> <GradientStop Offset="0.0" Color="#B0000000" /> <GradientStop Offset="1.0" Color="Transparent" /> </RadialGradientBrush.GradientStops> </RadialGradientBrush> </Rectangle.Fill> </Rectangle> <!-- main image --> <Border Grid.Row="0" x:Name="MainBorder" Background="White" BorderBrush="#DDDDDD" BorderThickness="1" HorizontalAlignment="Center" VerticalAlignment="Bottom" > <Image Margin="7" x:Name="MainImage" Source="{Binding Source}" /> </Border> </Grid> </DataTemplate>

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

Код:

<Grid Grid.Row ="0" Background="Black" Height="600" Width="800"> <Viewport3D ClipToBounds="false" Width="400" Margin="10"> <Viewport3D.Camera> <PerspectiveCamera FarPlaneDistance="20" LookDirection="0,-2,-15" UpDirection="0,1,0" NearPlaneDistance="1" Position="0,1,4" FieldOfView="45" /> </Viewport3D.Camera> <Viewport3D.Children> <ModelVisual3D> <ModelVisual3D.Content> <Model3DGroup> <Model3DGroup.Children> <DirectionalLight Color="#FFFFFFFF" Direction="2,-4,-5" /> <GeometryModel3D Geometry="{StaticResource Mesh1}"> <GeometryModel3D.Material> <DiffuseMaterial> <DiffuseMaterial.Brush> <VisualBrush Visual="{Binding ElementName=Image}"/> </DiffuseMaterial.Brush> </DiffuseMaterial> </GeometryModel3D.Material> </GeometryModel3D> <GeometryModel3D Geometry="{StaticResource Mesh2}"> <GeometryModel3D.Transform> <Transform3DGroup> <RotateTransform3D> <RotateTransform3D.Rotation> <AxisAngleRotation3D Axis="0,1,0" Angle="20"/> </RotateTransform3D.Rotation> </RotateTransform3D> <TranslateTransform3D OffsetX="-1" OffsetZ="1"/> </Transform3DGroup> </GeometryModel3D.Transform> <GeometryModel3D.Material> <DiffuseMaterial> <DiffuseMaterial.Brush> <VisualBrush Visual="{Binding ElementName=Image}"/> </DiffuseMaterial.Brush> </DiffuseMaterial> </GeometryModel3D.Material> </GeometryModel3D> <GeometryModel3D Geometry="{StaticResource Mesh}"> <GeometryModel3D.Transform> <Transform3DGroup> <RotateTransform3D> <RotateTransform3D.Rotation> <AxisAngleRotation3D Axis="0,1,0" Angle="-20"/> </RotateTransform3D.Rotation> </RotateTransform3D> <TranslateTransform3D OffsetX="1" OffsetZ="1"/> </Transform3DGroup> </GeometryModel3D.Transform> <GeometryModel3D.Material> <DiffuseMaterial> <DiffuseMaterial.Brush> <VisualBrush Visual="{Binding ElementName=Image}"/> </DiffuseMaterial.Brush> </DiffuseMaterial> </GeometryModel3D.Material> </GeometryModel3D> </Model3DGroup.Children> </Model3DGroup> </ModelVisual3D.Content> </ModelVisual3D> </Viewport3D.Children> </Viewport3D> <ContentControl Name ="Image" Content="{Binding ElementName=PhotosListBox, Path=SelectedItem}" ContentTemplate="{StaticResource ReflectedPhoto}"/> </Grid>

Итак, мы добавили внутрь Grid еще один Grid с двумя элементами - Viewport3D и ContentControl. Первый создает сцену для отображения трех зарание определенных фигур Mesh, Mesh1 и Mesh2. Каждая из этих фигур при помощи кисти VisualBrush (см. Статью №015) заливается элементом ContentControl с именем "Image". В свою очередь внешний вид ContentControl (который и служит заливкой для трех геометрических фигур) определен стилем ReflectedPhoto, который мы описали ранее. Запускаем проект: Проект работает именно так как и задумывалось. В качестве самостоятельного упражнения предлагаю вам поэкспериментировать с шаблонами отображения изображений. Изменяйте значения различных свойств, заливок и т.д. - придайте своему проекту индивидуальный вид! Подведем итог этого проекта: - закреплена работа с контейнером Grid - закреплена работа со стилями и словарями стилей - изучены нестандартные методы подключения данных к проекту из внешнего источника (в данном случае из папки на жестком диске) - рассмотрены основы построения трехмерной сцены - закреплен опыт работы с VisualBrush

Espoir, TheVista.ru Team

Соседние файлы в папке !