EyeDemo Application
Introduction
This article demonstrates how to make an Eye implemented as a custom controls based on WPF & C#.
The idea is from one of my previous articles: Eyes (Building the perfect useless control).
Background
Creating the Eyes article made me wonder if it would be easier to do the same using WPF and 3D objects. I went ahead and tried some ideas, the results came fairly quickly but the XAML was messy. Hiding the mess using a custom control felt natural.
Classes & Architecture
There are 3 classes involved in creating the application. The Eye
& Ball
class map the properties to the style and the Sphere
class creates a resource for the Eye
style.
Using the Eye
control can be done by dragging the Eye
control from the toolbox or using the following example XAML code:
<EyeControl:Eye RotationYAxis="180" RotationXAxis="0" PupilSize="1" IrisSize="0.7" />
Remember to set the namespace
:
xmlnss:EyeControl="clr-namespace:EyeControl;assembly=EyeControl"
Design & Style
Design Elements
Creating the Eye
is done with the following elements:
The implementation of the elements is found in the Eye
style (contained in /Themes/Generic.xaml).
"Eye" Style
<!---->
<Style TargetType="{x:Type local:Eye}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Eye}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Viewport3D x:Name="viewPort"
Margin="4" Grid.Column="0">
<Viewport3D.Camera>
...
</Viewport3D.Camera>
<Viewport3D.Children>
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup>
<GeometryModel3D Geometry="{Binding Source=
{StaticResource sphere}, Path=Geometry}">
<GeometryModel3D.Material>
<MaterialGroup>
<!---->
<DiffuseMaterial Brush="#FFFFFFFF" />
<!---->
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<VisualBrush Stretch="None" Opacity="1">
<VisualBrush.Visual>
<Ellipse Width=".16"
Height=".29" StrokeThickness=".005"
Stroke="{Binding Path=IrisRimColor,
RelativeSource={RelativeSource TemplatedParent}}">
<Ellipse.Fill>
<RadialGradientBrush GradientOrigin=".5,.5">
<GradientStop Color="{Binding Path=IrisInnerColor,
RelativeSource={RelativeSource TemplatedParent}}"
Offset="0.0" />
<GradientStop Color=
"{Binding Path=IrisMiddleColor,
RelativeSource={RelativeSource TemplatedParent}}"
Offset="0.5" />
<GradientStop Color=
"{Binding Path=IrisOuterColor,
RelativeSource={RelativeSource TemplatedParent}}"
Offset="1.0" />
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
</VisualBrush.Visual>
<VisualBrush.Transform>
<ScaleTransform ScaleX="{Binding Path=IrisSize,
RelativeSource={RelativeSource TemplatedParent}}"
ScaleY="{Binding Path=IrisSize, RelativeSource=
{RelativeSource TemplatedParent}}" CenterX=".5"
CenterY=".5"/>
</VisualBrush.Transform>
</VisualBrush>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
<!---->
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<VisualBrush Stretch="None" Opacity="1">
<VisualBrush.Visual>
<Ellipse Width=".016" Height="0.029"
Fill="{Binding Path=PupilColor,
RelativeSource={RelativeSource TemplatedParent}}"/>
</VisualBrush.Visual>
<VisualBrush.Transform>
<ScaleTransform ScaleX="{Binding Path=PupilSize,
RelativeSource={RelativeSource TemplatedParent}}"
ScaleY="{Binding Path=PupilSize, RelativeSource=
{RelativeSource TemplatedParent}}" CenterY=".5"
CenterX=".5" />
</VisualBrush.Transform>
</VisualBrush>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
<!---->
<SpecularMaterial SpecularPower="100">
<SpecularMaterial.Brush>
<SolidColorBrush Color="#DBDBDB" Opacity="1.000000"/>
</SpecularMaterial.Brush>
</SpecularMaterial>
<EmissiveMaterial Brush="#05FF0000" />
</MaterialGroup>
</GeometryModel3D.Material>
<!---->
<GeometryModel3D.Transform>
<Transform3DGroup>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="1,0,0"
Angle="{Binding Path=RotationXAxis,
RelativeSource={RelativeSource TemplatedParent}}"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="0,1,0"
Angle="{Binding Path=RotationYAxis,
RelativeSource={RelativeSource TemplatedParent}}"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
</Transform3DGroup>
</GeometryModel3D.Transform>
</GeometryModel3D>
<!---->
<AmbientLight Color="#FF646464" />
<SpotLight InnerConeAngle="29"
OuterConeAngle="31" Color="#666666"
Direction=".94,1.2,3.1" Position="-2,-0.8,-8"
Range="20"/>
<DirectionalLight Color="#CC666666"
Direction="1,-1,1"/>
<DirectionalLight Color="#FF444444"
Direction="0,1,5"/>
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D.Children>
</Viewport3D>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Point of Interest
ToolboxBitmaps
Setting up a toolbox bitmap icon is quite easy, add the following attribute to the class:
namespace EyeControl
{
[ToolboxBitmap(typeof(Eye))]
public class Eye : Control
{...
Add a bitmap to the Resources with a file name like: namespace.class.icon.bmp (the resource name is not important). In the example, I use the file: EyeControl.Eye.icon.bmp.
Eye & Ball ... eyeball (haha)
Categories on Properties
Setting up the category attribute for the properties is the same with WFP as with Windows Forms, here is an example:
[Category("Eye"), Browsable(true)]
[Description("The size of the pupil (0-5).")]
public double PupilSize
{
get { return (double)GetValue(PupilSizeProperty); }
set { SetValue(PupilSizeProperty, value); }
}
Categorized properties.
And oh...
I spent some time figuring out that it was necessary to cast the default value of a dependency property to its value type which seemed totally redundant.
Summary
Creating the Eye
custom controls using WPF was not exactly easy, but the possibilities seems endless and performance is great.
Most difficulties I faced derived from the separation of layers where usage (XAML), code behind (C#) & styles (XAML) all must be combined to form the control.
History
This is the first version.