Wednesday, February 20, 2008

3D Cube Button And A Simple Animation


One of the amazing things in WPF is how easy can be make 3D stuff, and even animate it. I'm gonna show my first experiment with 3D animations. The “CubeButtonStyle” Style was taken from the book “Windows Presentation Foundation Unleashed” (btw, If you want learn WPF you MUST have this book).


This is the code for our Window, no code behind needed this time :)


<Window x:Class="BlogStuff.ButtonWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Title="Cube Button" Height="600" Width="600">

<Window.Resources>

<Style x:Key="CubeButtonStyle" TargetType="{x:Type Button}">

<Setter Property="Template">

<Setter.Value>

<ControlTemplate>

<ControlTemplate.Triggers>

<!-- When the button is pressed, spin the cube -->

<Trigger Property="Button.IsPressed" Value="true">

<Trigger.EnterActions>

<BeginStoryboard>

<Storyboard TargetName="RotateY" TargetProperty="Angle">

<DoubleAnimation Duration="0:0:1" From="0" To="360" DecelerationRatio="1.0"/>

</Storyboard>

</BeginStoryboard>

</Trigger.EnterActions>

</Trigger>

</ControlTemplate.Triggers>

<Viewport3D>

<Viewport3D.Camera>

<PerspectiveCamera Position="2.9,2.65,2.9" LookDirection="-1,-1,-1"/>

</Viewport3D.Camera>

<Viewport3D.Children>

<ModelVisual3D x:Name="Light">

<ModelVisual3D.Content>

<DirectionalLight Direction="-0.3,-0.4,-0.5"/>

</ModelVisual3D.Content>

</ModelVisual3D>

<ModelVisual3D x:Name="Cube">

<ModelVisual3D.Transform>

<RotateTransform3D>

<RotateTransform3D.Rotation>

<AxisAngleRotation3D x:Name="RotateY" Axis="0,1,0" Angle="0"/>

</RotateTransform3D.Rotation>

</RotateTransform3D>

</ModelVisual3D.Transform>

<ModelVisual3D.Content>

<GeometryModel3D>

<GeometryModel3D.Material>

<DiffuseMaterial>

<DiffuseMaterial.Brush>

<!-- Use a VisualBrush to display the Button's original

Background and Content on the faces of the cube. ViewboxUnits="RelativeToBoundingBox"-->

<VisualBrush Stretch="Fill" Transform="1,0,0,-1,0,1">

<VisualBrush.Visual>

<Label Content="{Binding Path=Content,

RelativeSource={RelativeSource TemplatedParent}}"

Background="{Binding Path=Background,

RelativeSource={RelativeSource TemplatedParent}}"

Foreground="{Binding Path=Foreground,

RelativeSource={RelativeSource TemplatedParent}}"

FontSize="9"

Margin="5,5,5,5"

HorizontalContentAlignment="Center"

VerticalAlignment="Center"/>

</VisualBrush.Visual>

</VisualBrush>

</DiffuseMaterial.Brush>

</DiffuseMaterial>

</GeometryModel3D.Material>

<GeometryModel3D.Geometry>

<MeshGeometry3D

Positions="1,1,-1 1,-1,-1 -1,-1,-1 -1,1,-1 1,1,1 -1,1,1 -1,-1,1

1,-1,1 1,1,-1 1,1,1 1,-1,1 1,-1,-1 1,-1,-1 1,-1,1 -1,-1,1 -1,-1,-1

-1,-1,-1 -1,-1,1 -1,1,1 -1,1,-1 1,1,1 1,1,-1 -1,1,-1 -1,1,1"

TriangleIndices="0 1 2 0 2 3 4 5 6 4 6 7 8 9 10 8 10 11 12

13 14 12 14 15 16 17 18 16 18 19 20 21 22 20 22 23"

TextureCoordinates="0,1 0,0 1,0 1,1 1,1 0,1 0,-0 1,0 1,1

0,1 0,-0 1,0 1,0 1,1 0,1 0,-0 0,0 1,-0 1,1 0,1 1,-0

1,1 0,1 0,0"/>

</GeometryModel3D.Geometry>

</GeometryModel3D>

</ModelVisual3D.Content>

</ModelVisual3D>

</Viewport3D.Children>

</Viewport3D>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

</Window.Resources>

<Grid>

<Grid.RowDefinitions>

<RowDefinition Height="auto"/>

<RowDefinition Height="*"/>

</Grid.RowDefinitions>

<Label Content="Click on the button to spin the cube!" FontSize="18"/>

<Button Style="{StaticResource CubeButtonStyle}" Content="Click Me"

Background="LightGreen" Foreground="Yellow" Grid.Row="1"/>

</Grid>

</Window>


Get fun! :)

Tuesday, February 19, 2008

Do Your Free WPF Line Chart Yourself


There are some free WPF charting controls around the web but, sometimes what you get is a limited version of something that soon will not be free, that's why develop your own controls is a great idea and thanks to WPF is not so difficult.


In order to do this we will use the Polyline class and a ItemsControl to draw the grid lines.


XAML:


<Window x:Class="PolylineTest.Window1"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Title="Polyline Test" Height="700" Width="700">

<Viewbox Stretch="Fill">

<Grid x:Name="myGrid">

<ItemsControl x:Name="itemsControl" ItemsSource="{Binding}">

<ItemsControl.ItemsPanel>

<ItemsPanelTemplate>

<StackPanel IsItemsHost="True" Orientation="Horizontal"/>

</ItemsPanelTemplate>

</ItemsControl.ItemsPanel>

<ItemsControl.ItemTemplate>

<DataTemplate>

<Border BorderBrush="LightGray" BorderThickness="0.1" Width="10" Loaded="Border_Loaded"/>

</DataTemplate>

</ItemsControl.ItemTemplate>

</ItemsControl>

</Grid>

</Viewbox>

</Window>


Code Behind:


using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;


using System.Windows.Threading;

using System.Collections.ObjectModel;


namespace PolylineTest

{

/// <summary>

/// Interaction logic for Window1.xaml

/// </summary>

public partial class Window1 : Window

{

Polyline myPolyline;

DispatcherTimer timer = new DispatcherTimer();

Random gen = new Random(DateTime.Now.Millisecond);

int x = 0;

int step = 10;

ObservableCollection<Point> points = new ObservableCollection<Point>();


public Window1()

{

InitializeComponent();


myPolyline = new Polyline();

myPolyline.Stroke = System.Windows.Media.Brushes.SlateGray;

myPolyline.StrokeThickness = 1;

myPolyline.FillRule = FillRule.EvenOdd;


myGrid.Children.Add(myPolyline);


timer.Interval = TimeSpan.FromMilliseconds(500);

timer.Tick += new EventHandler(timer_Tick);

timer.Start();


this.DataContext = points;

}


private void timer_Tick(object sender, EventArgs e)

{

Point point = new Point(x, gen.Next(1000));

x += step;

points.Add(point);

}


private void Border_Loaded(object sender, RoutedEventArgs e)

{

Point point = (Point)(sender as Border).DataContext;

myPolyline.Points.Add(point);

}

}

}


Every 0.5 seconds an item is added to the source of the ItemsControl and then a Border is created, just after that we take the Point within the DataContext of the Border and add it to the Polyline. Easy! :)


Please, if this is helpful leave your comment. If there are any questions just fire away!

FeR.

How To Make A Simple Grid In WPF


Well, there are a lot of paths that you can take if you want to create a grid in WPF. However, I'll show the most simple (at least for me).


Definitely the ListView is one of the most powerful (maybe the number one) controls in WPF so we can create a simple grid easily using this control. Now we need a source of data and we're going to use an ObservableCollection, the msdn description of this class is the following:


“Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.“


So as you can realize this blows away a lot of painful things to do because we can do what we want with our collection and the framework updates the layout automatically.


This is the XAML:


<Window x:Class="BlogStuff.Window1"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Title="My Simple Grid" Height="300" Width="300">

<ListView ItemsSource="{Binding}">

<ListView.View>

<GridView>

<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=Name}">

<GridViewColumn.CellTemplate>

<DataTemplate>

<Label/>

</DataTemplate>

</GridViewColumn.CellTemplate>

</GridViewColumn>

<GridViewColumn Header="Age" DisplayMemberBinding="{Binding Path=Age}">

<GridViewColumn.CellTemplate>

<DataTemplate>

<Label/>

</DataTemplate>

</GridViewColumn.CellTemplate>

</GridViewColumn>

<GridViewColumn Header="Random Number" DisplayMemberBinding="{Binding Path=RandomNumber}">

<GridViewColumn.CellTemplate>

<DataTemplate>

<Label/>

</DataTemplate>

</GridViewColumn.CellTemplate>

</GridViewColumn>

</GridView>

</ListView.View>

</ListView>

</Window>



When you create the ListView you should set the ItemsSource property to {Binding}, this way the items will be taken from the DataContext property of the ListView, in this case the DataContext is the ObservableCollection.


Inside the GridView you can create the columns, then you can set which property of the item will be displayed in the current column with the DisplayMemberBinding property. Remember that the items are the items from the collection. Also you can define a DataTemplate for the cells, so if you want to show your info in buttons instead of labels you can change them easily without changing the code behind.


This is the Code Behind (C#):


using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;


//ObservableCollection namespace:

using System.Collections.ObjectModel;


namespace BlogStuff

{

///

/// Interaction logic for Window1.xaml

///

public partial class Window1 : Window

{

ObservableCollection<Guest> guests = new ObservableCollection<Guest>();

Random gen = new Random(DateTime.Now.Millisecond);


public Window1()

{

InitializeComponent();

this.DataContext = guests;


guests.Add(new Guest()

{

Name = "Fernando Romero",

Age = 23,

RandomNumber = gen.Next(100)

});


guests.Add(new Guest()

{

Name = "Luis Del Vasto",

Age = 22,

RandomNumber = gen.Next(100)

});


guests.Add(new Guest()

{

Name = "Nestor Martinez",

Age = 26,

RandomNumber = gen.Next(100)

});

}

}


public class Guest

{

public string Name { get; set; }

public int Age { get; set; }

public int RandomNumber { get; set; }

}

}


Just after the first line of code in the constructor we set the DataContext property (of the window) with the guests collection, maybe you are thinking: “but the collection should be the DataContext of the ListView”, well that's exactly what it is, In WPF the DataContext property is passed to the children by default, so as long as the ListView is contained by the window, then it will inherit this property. However, if you set the DataContext property of the ListView to other object then it will be overwritten.


I hope this could be useful for all of you guys.

Best,
FeR