Title: Let the user draw a polygon in WPF and C#
In a Windows Forms application, you can let the user draw a polygon by tracking mouse movement and redrawing the picture whenever necessary to draw the partially completed polygon. A WPF program normally does not do its own drawing. Instead it uses classes such as Ellipse, Rectangle, and Polygon to display shapes.
This example shows how you can let the user draw a polygon. In my next post, I'll show how you can let the user edit polygons.
XAML Code
This example uses the following XAML code to define its user interface.
<Window x:Class="howto_wpf_draw_polygon.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="howto_wpf_draw_polygon" Height="300" Width="300">
<Grid>
<Border Margin="10" BorderBrush="Gray" BorderThickness="1">
<Canvas Name="canDraw" Background="Transparent" Cursor="Cross"
MouseDown="canDraw_MouseDown" MouseMove="canDraw_MouseMove"/>
</Border>
</Grid>
</Window>
The main Grid control contains a single Border, which holds the Canvas on which the user draws.
This code includes three properties of interest for this example. The two most obvious are that it declares event handlers for the Canvas control's MouseDown and MouseMove events.
The less obvious important property is the Canvas control's Background property. By default that property is null, and when that property is null, the control cannot receive mouse events. This example sets the property to Transparent. You can set it to some other value such as white, but you must set it to something or the program won't work.
NewPolyline
When you draw a new polygon, you click to define its vertices. Each time the user clicks a point, you could connect the points so far to form a polygon. If you try that, you'll see that the result seems really awkward.
It's more natural to connect the points that you have clicked so far but not close the shape to form a polygon. The result is a polyline like the one in the picture on the right, which is similar to a polygon except the final point is not connected to the first point.
This example uses a Polyline object to show the points that the user has clicked. When the user is finished picking points, it replaces the Polyline object with a Polygon.
The following variable, which is declared at the form level, keeps track of the new Polyline while the user is drawing.
private Polyline NewPolyline = null;
When the user presses the left mouse button, the program adds a new point to this polyline. When the user clicks the right button, the program converts the polyline into the final polygon.
The program keeps an extra point at the end of the polyline to keep track of the mouse's position as the user moves it. For example, suppose the user has left-clicked twice to define two points. In that case, the polyline contains three points, the two that the user clicked and a third that tracks the mouse position. When the user moves the mouse, the program updates that third point's location so the polyline shows where the next point would be if the user left-clicked again.
When the user right-clicks, the program discards that extra point and converts the remaining points into a Polygon.
MouseDown
Most of this example's interesting code occurs in the following MouseDown event handler.
private void canDraw_MouseDown(object sender, MouseButtonEventArgs e)
{
// See which button was pressed.
if (e.RightButton == MouseButtonState.Pressed)
{
// See if we are drawing a new polygon.
if (NewPolyline != null)
{
if (NewPolyline.Points.Count > 3)
{
// Remove the last point.
NewPolyline.Points.RemoveAt(NewPolyline.Points.Count - 1);
// Convert the new polyline into a polygon.
Polygon new_polygon = new Polygon();
new_polygon.Stroke = Brushes.Blue;
new_polygon.StrokeThickness = 2;
new_polygon.Points = NewPolyline.Points;
canDraw.Children.Add(new_polygon);
}
canDraw.Children.Remove(NewPolyline);
NewPolyline = null;
}
return;
}
// If we don't have a new polygon, start one.
if (NewPolyline == null)
{
// We have no new polygon. Start one.
NewPolyline = new Polyline();
NewPolyline.Stroke = Brushes.Red;
NewPolyline.StrokeThickness = 1;
NewPolyline.StrokeDashArray = new DoubleCollection();
NewPolyline.StrokeDashArray.Add(5);
NewPolyline.StrokeDashArray.Add(5);
NewPolyline.Points.Add(e.GetPosition(canDraw));
canDraw.Children.Add(NewPolyline);
}
// Add a point to the new polygon.
NewPolyline.Points.Add(e.GetPosition(canDraw));
}
If the right button was pressed and NewPolygon is not null, then the user is trying to finalize the polygon.
Remember that the program is going to discard the polyline's final point, which tracks the mouse position. If the polyline contains three or fewer points, then it does not include enough points to discard one and still define a polygon.
If NewPolyline contains more than three points, the code removes the last point from the polyline's Points collection. It then creates a new Polygon object, sets its Point collection equal to the collection used by the Polyline, and adds it to the canDraw Canvas control.
Note that the code gives the new Polygon a Stroke and StrokeThickness. If you don't do that, the Polygon will exist, but it will not be visible.
The event handler then removes NewPolyline from the canDraw control's Children collection, sets NewPolyline = null, and returns.
When the code sets NewPolyline = null, that object is no longer in use so it can be garbage collected. However, its Points collection is now being used by the new Polygon object, so that collection will not be garbage collected.
If the user pressed the right mouse button, then it gets no farther than this. If left button was pressed, then the user is trying to add a new point at the end of the polyline.
If NewPolyline is null, the program sets NewPolyline to a new Polyline object. It sets some stroke properties so the object will be visible and adds it to the canDraw control's Children collection.
The code also uses e.GetPosition to get the mouse's current position with relative to the canDraw control. This is one of the larger differences between Windows Forms and WPF mouse events. In Windows Forms the event handler's parameters give you the mouse's location. In WPF you need to call e.GetPosition, passing it the control whose coordinate system you want to use to get the location. The code adds the mouse's current position to the new polyline.
After it has created the new polyline if necessary, the code adds the mouse's current location to the polyline. If this is a new polyline, then that location is added twice. The first instance is the polyline's first point. The second instance is the one that gets updated as the user moves the mouse. That happens in the MouseMove event handler described next.
MouseMove
The MouseMove event handler shown in the following code is relatively simple.
private void canDraw_MouseMove(object sender, MouseEventArgs e)
{
if (NewPolyline == null) return;
NewPolyline.Points[NewPolyline.Points.Count - 1] =
e.GetPosition(canDraw);
}
If NewPolygon is null, this code simply returns. Otherwise the code sets the polyline's last point equal to the mouse's current location. (Notice how the code uses e.GetPosition again.) That updates the polyline's final point so it lies under the mouse. The polyline automatically redraws itself to show the new shape.
Conclusion
Drawing a new polygon in WPF is very different from the way you draw a new polygon in Windows Forms, but it's not too hard. The MouseDown event handler creates a Polyline, adds points to it, or converts it into a Polygon object. The MouseMove event handler updates the polyline's last point to show progress.
Download the example to experiment with it. In my next post I'll show how you can let the user edit Polygon objects.
Download the example to experiment with it and to see additional details.
|