Introduction
This is a simple, beginner level project that takes a few dimensions from the user and then draws a 3 dimensional representation of a cylinder, using only 2D geometry. Basic concepts such as simple data binding and property change notification, as well as simple Geometry and Path objects are covered here.
The project as included in the source code is not useful for anything other than a graphical display of cylinders of various sizes. The intent of this article was to provide a starting point for basic WPF windows, and basic graphics drawing on a Canvas
object. From this simple base, there are a great number of features and options that could be added to make this project more useful.
Background
This project/article served two purposes for me:
- It's about time that I learned C#. I've been a VB coder for years, so this was a quick and simple project with which to get started.
- I needed to brush up on my 2D drawing skills in WPF.
I was also intrigued by a question on StackOverflow that presented just this type of problem. It seemed interesting enough to come up with a full solution.
Using the Code
The window layout in WPF is very basic:
<Grid>
<Grid.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFBABABA" Offset="1" />
<GradientStop Color="#FFE5E5E5" Offset="0" />
</LinearGradientBrush>
</Grid.Background>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Margin="10,0">
<TextBlock Margin="10" Text="Draw a cylinder with gridlines"
TextWrapping="Wrap" MaxWidth="150"
TextAlignment="Center"/>
<TextBlock Text="Height:"/>
<TextBox Text="{Binding Path=CylinderHeight}"/>
<TextBlock Text="Radius:"/>
<TextBox Text="{Binding Path=CylinderRadius}"/>
<TextBlock Text="Divisions:"/>
<TextBox Text="{Binding Path=CylinderDivisions}"/>
<Button Margin="5,10" Click="ClickButton">
Draw Cylinder
</Button>
</StackPanel>
<Canvas Name="Canvas1" Grid.Column="1">
<Canvas.Background>
<LinearGradientBrush EndPoint="0,0" StartPoint="1,1">
<GradientStop Color="#FFE2C98B" Offset="0" />
<GradientStop Color="#FFFCEED2" Offset="1" />
</LinearGradientBrush>
</Canvas.Background>
</Canvas>
</Grid>
You will notice in the XAML above that the only control with a name is the Canvas
, because this control is passed to another class when the 'Draw Cylinder' button is clicked. This demonstrates one of the nicer features of WPF. Even the textboxes that are bound to data do not require explicit names.
The data binding is very basic and just binds the Text
property of the 3 textboxes to corresponding properties of the DrawingClass
Class. To complete the data binding, the constructor of the window instantiates a new instance of the DrawingClass
and assigns it as the data context for the window (shown below):
public DrawingClass dc;
public MainWindow()
{
InitializeComponent();
dc = new DrawingClass();
this.DataContext = dc;
}
Shown below is an excerpt of the code used to draw the shape of the cylinder body and the arc that represents the bottom of the cylinder. The Canvas
object is passed to this subroutine so that the various pieces of geometry can be added directly to it.
public void DrawCylinder(System.Windows.Controls.Canvas cnv)
{
cnv.Children.Clear();
The ratio of the top of the cylinder's width to its height is arbitrarily set to 0.3 in order to give the impression of viewing the cylinder in 3D from a position slightly above the cylinder.
Then 4 points describing the 'corners' of the cylinder object are calculated, as well as a point ptC
that is the center of the ellipse that makes up the top surface of the cylinder.
int ellipseHeight = Convert.ToInt32( Math.Floor(cylinderRadius * 0.3));
Point ptUpperLeft = new Point(30, ellipseHeight*2);
Point ptUpperRight = new Point(30 + (cylinderRadius * 2), ptUpperLeft.Y);
Point ptLowerLeft = new Point(30, ptUpperLeft.Y + cylinderHeight);
Point ptLowerRight = new Point(ptUpperLeft.X +
(cylinderRadius * 2), ptUpperLeft.Y + cylinderHeight);
Point ptC = new Point(30 + cylinderRadius,ptUpperLeft.Y);
A new Path
object is created that will contain the various pieces of Geometry
and PathFigure
Segments.
This path is then added to the Canvas
. The remainder of the code is not shown here, but uses similar techniques to add the rest of figures to the Canvas
.
Path pth = new Path();
LineSegment ln = new LineSegment(ptLowerLeft,true);
ArcSegment arc = new ArcSegment(ptLowerRight,new Size(cylinderRadius,ellipseHeight),
0,false,System.Windows.Media.SweepDirection.Counterclockwise,true);
PathFigure pf = new PathFigure();
pf.StartPoint = ptUpperLeft;
pf.Segments.Add(ln);
pf.Segments.Add(arc);
ln = new LineSegment(ptUpperRight,true);
pf.Segments.Add(ln);
PathGeometry pg = new PathGeometry();
pg.Figures.Add(pf);
pth.Stroke = new SolidColorBrush(Colors.Black);
pth.StrokeThickness = 2;
pth.Fill = new SolidColorBrush(Colors.White);
pth.Data = pg;
cnv.Children.Add(pth);
Points of Interest
When drawing an arc, the simplest object to use is the ArcSegment
object. A simple line can be represented equally easily by a LineGeometry
or a LineSegment
object. A complete ellipse is most easily drawn using an EllipseGeometry
object.
History
- 27th January, 2012: Initial post