Introduction
This article describes how you can create 3D gadgets for your desktop, using only C# and WPF. A gadget should integrate with the desktop, always staying on top of all open windows so that the user can use the shortcuts it offers. Let's see how we can achieve such results. The demo project contains the iPod model seen in this picture. If you first want to learn how to build something simpler, read on and you won't be disappointed.
Splitting the problem
First, lets split this seemingly impossible problem into bits:
- Creating a 3D environment
- Adding functionality to the 3D objects we create
- Transforming the window so that it is transparent and always stays on top
1. Populating a Viewport
The WPF control that hosts 3D objects is called a Viewport
. We will create a Viewport
using XAML markup, the preferred way in .NET 3.0. The code is given below:
<Viewport3D Name="mainViewport" ClipToBounds="True">
<Viewport3D.Camera>
<PerspectiveCamera Position ="0,500,500" LookDirection ="0,-100,-100">
</PerspectiveCamera>
</Viewport3D.Camera>
The camera was added because, with no light, we cannot see any of the objects that we create. To fully describe the camera we must set its Position
(where the camera is placed in the 3D environment) and the LookDirection
(the direction where the camera is looking )
Now let's add a simple object to the Viewport
:
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup>
<GeometryModel3D x:Name ="pyramid">
<GeometryModel3D.Geometry>
<MeshGeometry3D
Positions="0,0,37.5 -50,-50,-37.5 50,-50,-37.5 50,50,-37.5
-50,50,-37.5 0,0,-37.5 50,-50,-37.5 50,50,-37.5 -50,50,-37.5
-50,-50,-37.5 -50,-50,-37.5 50,-50,-37.5 50,-50,-37.5 -50,50,-37.5
-50,50,-37.5 -50,-50,-37.5 "
TriangleIndices="0,2,1,0,3,6,0,4,7,0,9,8,10,11,5,12,3,5,3,13,5,14,15,5"
Normals=""
/>
</GeometryModel3D.Geometry >
<GeometryModel3D.Material >
<DiffuseMaterial Brush ="White"/>
</GeometryModel3D.Material>
</GeometryModel3D>
</Model3DGroup>
</ModelVisual3D.Content>
The actual 3D object is composed of triangles. Each triangle is specified as a succession of Points; to create another object starting from this one, you have to:
- replace the points in the
Positions
collection
- specify triangles in the
TriangleIndices
collection.
But don't worry, there are easier ways to do it. The simplest way, for us graphically challenged developers is importing an existing model; the iPod model in the first screenshot is imported from a 3D Max model.
To import a 3D Studio Max model,
(*.3DS), of which there is an abundance on the internet, you can use ZAM 3D which is free to try and located here, or you can use an online free converter and its associated *.dll from here. This free online converter might not be updated for .NET 3.0 RC1 so you might have to replace some spaces with commas, but the rest works OK. In case you're wondering, the iPod model comes from the sample models that ZAM 3D provides.
After we import our model, we find that if we import several, all the models are on top of each other, so we must move each model to another place in the Viewport
. We do this by using a Transformation. We want to translate the object so we write:
<ModelVisual3D.Transform>
<TranslateTransform3D OffsetX ="-200"/>
</ModelVisual3D.Transform>
</ModelVisual3D>
Adding animation
To make things more interesting, we will add a simple animation; don't overdo it in your commercial app as animation distracts the user from his work and he also gets tired more quickly.
To animate elements in XAML we use Storyboard
s; so that we can use the animation multiple times. We add it to the Windows Resources, like this:
<Window.Resources>
<Storyboard x:Key="RotateStoryboard">
<ParallelTimeline RepeatBehavior="Forever" Storyboard.TargetName="myRotate"
Storyboard.TargetProperty="Angle" >
<DoubleAnimation From="0" To="360" Duration="0:0:30"/>
</ParallelTimeline>
</Storyboard>
</Window.Resources>
The animation must start when the window is loaded, so we use a OnLoad
handler to start it, like this:
void Window1_Loaded(object sender, RoutedEventArgs e)
{
Storyboard s;
s = (Storyboard)this.FindResource("RotateStoryboard");
this.BeginStoryboard(s);
}
2. Adding mouse handlers for the 3D objects
To add mouse handlers for 3D objects we first have to find out which object in the ViewPort
was clicked (or any other mouse action). To do this, we use the built-in hittesting engine of the ViewPort
, like this:
void mainViewport_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
System.Windows.Point mouseposition = e.GetPosition(mainViewport);
Point3D testpoint3D = new Point3D(mouseposition.X, mouseposition.Y, 0);
Vector3D testdirection = new Vector3D(mouseposition.X,
mouseposition.Y, 10);
PointHitTestParameters pointparams = new PointHitTestParameters
(mouseposition);
RayHitTestParameters rayparams = new RayHitTestParameters(testpoint3D,
testdirection);
VisualTreeHelper.HitTest(mainViewport, null, HTResult, pointparams);
}
The code that actually handles the click on the objects follows. Some tips for actions that you probably should offer:
3. Making the Window invisible
The standard definition of a Window in XAML looks like this:
<Window x:Class="WindowTransparency.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WindowTransparency" Height="314" Width="813"
To add transparency, just add these XAML tags:
WindowStyle="None" Background="Transparent" AllowsTransparency ="True"
We also want two more things: the form must not be visible in the taskbar and must always be on top. The corresponding tags are:
ShowInTaskbar="False" Topmost="True"
Notes on using the code
The code was built on Vista RC1, using the .NET 3.0 RC1 framework and Orcas development tools (http://www.microsoft.com/downloads/details.aspx?FamilyID=d1336f3e-e677-426b-925c-c84a54654414&DisplayLang=en).
It should also work under Windows XP, as I did not use any Vista-only feature (that I know of ) but it is not tested yet.
Conclusion
You can use this code to create something simple, like a 3D Mac OS X bar, or add a dancer like the ones that you can download from Microsoft, but this time in 3D. The possibilities are endless.
Notice how easy it is to do these things in XAML, just as simple as in a plain old VB6 application, simpler even than in .NET 2.0. WPF allows developers to achieve great visual effects without hassle, increasing productivity, but at the cost of having to learn how to properly use its power.
History
- Update 22/09/2006 : Added iPod demo project and screenshot.