15,347,685 members
Articles / Multimedia / GDI+
Article
Posted 6 Mar 2007

547.3K views
196 bookmarked

Extensions to DrawTools

Rate me:
DrawTools library extended to include Layers, Zoom, Pan, Rotation

Introduction

Alex Fr provided an excellent set of drawing tools in his DrawTools article and these tools serve as a basis for this article, which expands on the original toolset in the following ways:

1. In addition to the basic Rectangle, Ellipse, Line and Scribble tools, this version adds PolyLine, Filled Ellipse, Filled Rectangle, Text and Image tools
2. Multiple drawing Layers
3. Zooming
4. Panning
5. Rotation

In this article, I will describe how Layers were implemented, as well as the Text and Image tools.

Background

See the original DrawTools article for details on how the basic application is built, class structure, etc.

It is also assumed that the reader has a working understanding of GDI+ fundamentals, including Matrices. For an excellent introduction to GDI+, see www.bobpowell.net.

Implementing Layers

Adding Layers to the application involved adding two classes, `Layer` and `Layers`, where `Layer` defines a single `Layer `and `Layers` defines the collection of `Layers `in an `ArrayList`.

Each `Layer `exposes the following properties:

C#
```private string _name;
private bool _isDirty;
private bool _visible;
private bool _active;
private GraphicsList _graphicsList;
```

Note that the `Layer` contains the `GraphicsList` - this is the key to the whole thing - each `Layer` contains its own list of drawing objects instead of `DrawArea`. `DrawArea` is modified to declare a `Layers` collection instead of a `GraphicsList` collection:

C#
```// Define the Layers collection
private Layers _layers;
```

When `DrawArea `is initialized, the `Layers` are initialized by creating the first `Layer` and setting it `Active `and `Visible`:

C#
```public DrawArea()
{
// create list of Layers, with one default active visible layer
_layers = new Layers();
_layers.CreateNewLayer("Default");
_panning = false;
_panX = 0;
_panY = 0;
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();
}
```

In the `Layers `class, the `CreateNewLayer()` method actually creates the new `Layer`:

C#
```/// <summary>
/// Create a new layer at the head of the layers list and set it
/// to Active and Visible.
/// </summary>
/// <param name="theName">The name to assign to the new layer</param>
public void CreateNewLayer(string theName)
{
// Deactivate the currently active Layer
if(layerList.Count > 0)
((Layer)layerList[ActiveLayerIndex]).IsActive = false;
// Create new Layer, set it visible and active
Layer l = new Layer();
l.IsVisible = true;
l.IsActive = true;
l.LayerName = theName;
// Initialize empty GraphicsList for future objects
l.Graphics = new GraphicsList();
}
```

Note that any one or all `Layers `can be visible at the same time, but only one `Layer `may be active at any time.

You can control the `Layers `in the sample application by clicking on the Current `Layer`: name at the bottom of the application window - Click on the name ("`Default`") to open the `Layers `dialog:

From this dialog, you can Add new `Layers`, change the names of the `Layer`(s), and change the `Layer`(s) visibility and which `Layer `is `Active`. The "New Layer" column is checked whenever you click the "Add Layer" button. To delete Layer(s), simply check the "Deleted" column and close the dialog with the "Close" button. Remember only one Layer may be active at any one time. You will be reminded of this if you attempt to have more than one `Layer `active. Also note the Active `Layer `must be `Visible`.

When the application runs, each object that is drawn is added to the `GraphicsList` maintained by the active `Layer`. Note this relationship is preserved through saving and re-opening a drawing file.

Layers come in very handy when you want to draw "on top of" another image. For example, the image at the top of this article contains two layers. The following image shows the same picture with the Background Layer turned off:

Here is the same drawing with the Drawing Layer invisible and the Background Layer visible:

Objects on Layers which are visible but not active cannot be selected, moved, deleted, etc.

Each drawing object is added to the correct `Layer `by the `AddNewObject()` method in the `ToolObject` class:

C#
```protected void AddNewObject(DrawArea drawArea, DrawObject o)
{
int al = drawArea.TheLayers.ActiveLayerIndex;
drawArea.TheLayers[al].Graphics.UnselectAll();
o.Selected = true;
o.Dirty = true;
drawArea.Capture = true;
drawArea.Refresh();
} ```

Implementing Zooming, Panning, and Rotation

Zooming, Panning, and Rotation are implemented by adding a few variables and some code to the `MainForm` and `DrawArea` classes.

Zooming is controlled by buttons on the form, and also by the mouse wheel when Ctrl is held down.

Pan is controlled by the Hand button on the form, and can be cancelled by a right-click.

Rotation is controlled by buttons on the form - note Rotation affects the entire drawing.

Here is an example of all three in use:

The heart of this code is the `BackTrackMouse()` method, which takes the "apparent" mouse position and converts it to a valid point based on the current Zoom level, Pan position, and Rotation:

C#
```/// <summary>
/// Back Track the Mouse to return accurate coordinates regardless of
/// zoom or pan effects.
/// Courtesy of BobPowell.net <seealso cref="http://www.bobpowell.net/backtrack.htm"/>
/// </summary>
/// <param name="p">Point to backtrack</param>
/// <returns>Backtracked point</returns>
public Point BackTrackMouse(Point p)
{
// Backtrack the mouse...
Point[] pts = new Point[] { p };
Matrix mx = new Matrix();
mx.Translate(-this.ClientSize.Width / 2, -this.ClientSize.Height / 2,
MatrixOrder.Append);
mx.Rotate(_rotation, MatrixOrder.Append);
mx.Translate(this.ClientSize.Width / 2 + _panX, this.ClientSize.Height / 2 +
_panY, MatrixOrder.Append);
mx.Scale(_zoom, _zoom, MatrixOrder.Append);
mx.Invert();
mx.TransformPoints(pts);
return pts[0];
}```

This routine comes from Bob Powell's excellent website. Through the use of the GDI+ Matrix class, the mouse point passed to this method is moved (Translate), Rotated, and Scaled based on the current PanX, PanY, Zoom, and Rotation values. The important thing to remember is that anytime you need to determine where the mouse pointer actually is in your drawing, you must call this method. You will see this method used throughout the program in the `DrawArea` class as well as others. An example of its usage is shown here:

C#
```private void DrawArea_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
lastPoint = BackTrackMouse(e.Location);
if (e.Button == MouseButtons.Left)
tools[(int)activeTool].OnMouseDown(this, e);
else if (e.Button == MouseButtons.Right)
{
if (_panning == true)
_panning = false;
ActiveTool = DrawArea.DrawToolType.Pointer;
}
}```

The current zoom level is controlled by the following simple routine:

C#
```private void AdjustZoom(float _amount)
{
drawArea.Zoom += _amount;
if (drawArea.Zoom < .1f)
drawArea.Zoom = .1f;
if (drawArea.Zoom > 10)
drawArea.Zoom = 10f;
drawArea.Invalidate();
SetStateOfControls();
}```

Then in the `DrawArea.Paint()` method, the zoom, pan, and rotation values are used to alter the way the canvas is painted:

C#
```private void DrawArea_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Matrix mx = new Matrix();
mx.Translate(-this.ClientSize.Width / 2, -this.ClientSize.Height / 2,
MatrixOrder.Append);
mx.Rotate(_rotation, MatrixOrder.Append);
mx.Translate(this.ClientSize.Width / 2 + _panX, this.ClientSize.Height / 2 +
_panY, MatrixOrder.Append);
mx.Scale(_zoom, _zoom, MatrixOrder.Append);
e.Graphics.Transform = mx;

SolidBrush brush = new SolidBrush(Color.FromArgb(255, 255, 255));
e.Graphics.FillRectangle(brush,
this.ClientRectangle);
// Draw objects on each layer, in succession so we get the correct layering.
// Only draw layers that are visible
if (_layers != null)
{
int lc = _layers.Count;
for (int i = 0; i < lc; i++)
{
if(_layers[i].IsVisible == true)
if(_layers[i].Graphics != null)
_layers[i].Graphics.Draw(e.Graphics);
}
}
DrawNetSelection(e.Graphics);
brush.Dispose();
}```

Update - 8/25/2007 - Individual Object Rotation & Bug Fixes

The primary advancement in this update is the ability to rotate individual objects - when one or more objects are selected, clicking the Rotate tools will rotate those objects instead of the entire drawing surface.

There is one caveat, however - the selection rectangle for the rotated object is not rotated - if someone can help with this, I would greatly appreciate it!

This update also includes several small bug fixes reported by users - thanks to all for reporting!

History

• 3/6/2007
• Original article uploaded to The Code Project
• 3/6/2007
• 8/25/2007
• Updated Individual Object Rotation
• 9/27/2007
• 12/23/2009
• Added Tooltip control which appears when mouse is over an object. Tooltip displays the Center coordinates of the object for `Rectangle`, `Ellipse `and `Image `objects. For other objects, Tooltip displays starting and ending coordinates. `Text `objects do not display Tooltip.
This was implemented adding the Tooltip control to the `ToolPointer `class. Each `Draw `Object fills the `TipText `property and the `MouseMove `event in `ToolPointer `controls when the Tooltip is displayed and removed from the canvas. This implementation is not perfect, as the Tooltip flashes when displayed so is meant as an example of one way information about the object can be displayed.
Perhaps a better way to do this would be to display information about the object in a separate "Information Window" and then only when the object is selected.
• See the new source code for details.
• 6/23/2010
• Updated project to include object ordering fix that corrects the way objects are stacked when a file is opened
• Updated project to Visual Studio 2010
• See the new source code for details
• 10/4/2011
• Corrected several issues with Layers and layering

Share

 Software Developer (Senior) United States
I develop software for a leading healthcare system in Northern Illinois.

 outstanding samurai92615-Nov-07 18:07 samurai926 15-Nov-07 18:07
 Link Objects with a line move line while moving object Célio13-Nov-07 0:39 Célio 13-Nov-07 0:39
 Are you familiar with Paint.NET? sherifffruitfly27-Sep-07 11:41 sherifffruitfly 27-Sep-07 11:41
 Re: Are you familiar with Paint.NET? Mark Miller27-Sep-07 11:58 Mark Miller 27-Sep-07 11:58
 Update - 8/25/2007 - Individual Object Rotation & Bug Fixes crockettk27-Sep-07 7:45 crockettk 27-Sep-07 7:45
 Re: Update - 8/25/2007 - Individual Object Rotation & Bug Fixes Mark Miller27-Sep-07 9:12 Mark Miller 27-Sep-07 9:12
 Drawing outside client area crockettk27-Sep-07 7:23 crockettk 27-Sep-07 7:23
 A few problems + a few suggestions biceman25-Sep-07 10:01 biceman 25-Sep-07 10:01
 First of all, thanks for the great sample. It took a lot of searching to find your sample but it was just what I needed to get started. I'm a VB'er so while converting and testing, I came across these items: • In DrawArea_Paint, call FillRectangle before transform since the background coordinates are unaffected by any transformation; otherwise, the background doesn't get painted correctly after a Pan • In MainForm.ResizeDrawArea, you need to account for the Status tool strip when calculating the height • A few of the "Sub New"s in DrawRectangle are missing their calls to “Initialize” • In Layers.CreateNewLayer method, you set the active layer to false but do not unselect the selected objects. This is inconsistent with the SetActiveLayer method. (My suggestion would be to move the call to “UnselectAll” to the Set portion of the layer's “IsActive” property) Also, I might offer these suggestions: • In Tool Pointer, when "Move" mode is detected, holding Ctrl down should toggle select / unselect of shape (at least that's what happens in VS). Copy Code ```
If Control.ModifierKeys = Keys.Control Then
shape.IsSelected = Not shape.IsSelected
Else
If shape.IsSelected Then
Else
DrawArea.Layers.ActiveLayer.Shapes.UnselectAll()
shape.IsSelected = True
End If
End If
``` (I created a property "ActiveLayer" in Layers class and reference it directly instead of "ActiveLayerIndex" and then drawArea.TheLayers[al].xxxx) • Add logic to “IsActive” property of Layer to automatically set “IsVisible” to true when setting “IsActive” to true • Move the code for “Delete Selected”, “Select All”, “Unselect All”, Zoom, Rotate, Pan to DrawArea so that MainForm calls the encapsulated methods; the same goes for the MouseWheel and KeyPress event trapping. Define an event in DrawArea which gets raised anytime the SetStateOfControls needs to be updated (e.g., call to Zoom, Rotate, Pan logic now inside of DrawArea) • Base the Layers and GraphicsList classes on generic collections instead of ArrayList (this allowed me to create a "Selected" collection which I could enumerate over as VB does not support the Yield command) bryan
 Link to updated source broken dvasilio29-Aug-07 1:58 dvasilio 29-Aug-07 1:58
 Out of Memory Crash centraltheory21-Aug-07 10:28 centraltheory 21-Aug-07 10:28
 Re: Out of Memory Crash centraltheory21-Aug-07 10:39 centraltheory 21-Aug-07 10:39
 Re: Out of Memory Crash Mark Miller25-Aug-07 9:03 Mark Miller 25-Aug-07 9:03
 Re: Loading a Background Image on Startup Mark Miller25-Aug-07 9:07 Mark Miller 25-Aug-07 9:07
 Is there a problem in Polyline tool J Navaneethan29-Jul-07 17:13 J Navaneethan 29-Jul-07 17:13
 Re: Is there a problem in Polyline tool J Navaneethan29-Jul-07 17:43 J Navaneethan 29-Jul-07 17:43
 Re: Is there a problem in Polyline tool Mark Miller30-Jul-07 5:55 Mark Miller 30-Jul-07 5:55
 Xml File krish_knight32624-Jul-07 0:46 krish_knight326 24-Jul-07 0:46
 Re: Xml File Mark Miller24-Jul-07 16:28 Mark Miller 24-Jul-07 16:28
 How to add line curve? piyachatel23-May-07 19:15 piyachatel 23-May-07 19:15
 Re: How to add line curve? Mark Miller25-May-07 7:21 Mark Miller 25-May-07 7:21
 Re: How to add line curve? piyachatel27-May-07 17:02 piyachatel 27-May-07 17:02
 Very Useful Source meeashish20043-May-07 21:21 meeashish2004 3-May-07 21:21
 Re: Very Useful Source Mark Miller10-May-07 6:05 Mark Miller 10-May-07 6:05
 Base class for drawing chongkj26-Apr-07 17:05 chongkj 26-Apr-07 17:05
 Last Visit: 31-Dec-99 18:00     Last Update: 26-Jun-22 16:08 Refresh ᐊ Prev1234567 Next ᐅ