Click here to Skip to main content
6,305,776 members and growing! (17,013 online)
Email Password   helpLost your password?
Languages » C# » General     Intermediate License: The Code Project Open License (CPOL)

Extensions to DrawTools

By Mark Miller

DrawTools library extended to include Layers, Zoom, Pan, Rotation
C# 2.0, Windows, .NET 2.0, GDI+, VS2005, Dev
Posted:6 Mar 2007
Updated:27 Sep 2007
Views:48,000
Bookmarked:86 times
Announcements
Loading...
 
Search    
Advanced Search
printPrint   Broken Article?Report       add Share
  Discuss Discuss   Recommend Article Email
16 votes for this article.
Popularity: 5.56 Rating: 4.62 out of 5

1

2
1 vote, 6.3%
3
3 votes, 18.8%
4
12 votes, 75.0%
5

Updated 8/25/2007

Draw Tools Redux Image

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:

 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:

 // 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:

 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:

 /// <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();
 // Add to Layers collection
 this.Add(l);
 }

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:

protected void AddNewObject(DrawArea drawArea, DrawObject o)
{
     int al = drawArea.TheLayers.ActiveLayerIndex;
     drawArea.TheLayers[al].Graphics.UnselectAll();
     o.Selected = true;
   o.Dirty = true;
   drawArea.TheLayers[al].Graphics.Add(o);
     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:

Screenshot - DrawToolsRedux-3.png

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:

/// <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:

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;
     }
     //OnContextMenu(e);
}

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

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:

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: Updated to include more information on zoom/pan/rotation
  • 8/25/2007: Updated Individual Object Rotation
  • 9/27/2007: Added the missing links to the new source code

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Mark Miller


Member
I develop software for a leading healthcare system in Northern Illinois.
Occupation: Software Developer (Senior)
Company: RHS
Location: United States United States

Other popular C# articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 25 of 64 (Total in Forum: 64) (Refresh)FirstPrevNext
QuestionDraw Image from File Pinmembervitsyn12:14 5 Feb '09  
QuestionDraw Polylines from File Pinmembervitsyn7:45 2 Feb '09  
AnswerRe: Draw Polylines from File PinsupporterMark Miller8:21 2 Feb '09  
GeneralRe: Draw Polylines from File Pinmembervitsyn14:32 2 Feb '09  
QuestionIs it possible to rebuild it to a web control using the same principle? PinmemberGIS_Developer20:39 3 Nov '08  
GeneralFill color in shapes Pinmemberesabarinath3:34 21 Aug '08  
GeneralRe: Fill color in shapes PinmemberGIS_Developer16:10 26 Aug '08  
Generalsave in sqlserver and reload Pinmemberham rez21:04 13 Jul '08  
GeneralRe: save in sqlserver and reload PinsupporterMark Miller7:26 14 Jul '08  
GeneralRe: save in sqlserver and reload PinmemberGIS_Developer23:34 18 Sep '08  
GeneralHow to get the Screen Location/Size of a DrawObject? Pinmemberalias4720:42 27 May '08  
GeneralIntermittent Inability to drag a Polyline/Polygon Pinmemberalias4717:22 26 May '08  
Generaleraser tool Pinmembersamurai9268:42 11 Mar '08  
GeneralRe: eraser tool PinsupporterMark Miller10:21 11 Mar '08  
GeneralRe: eraser tool Pinmembersamurai92610:47 11 Mar '08  
GeneralHow to modify the text object in the drawarea? Pinmemberhuangyaoen16:28 28 Jan '08  
GeneralRe: How to modify the text object in the drawarea? PinsupporterMark Miller8:24 30 Jan '08  
GeneralScroll bars to DrawArea Pinmemberadi_pasa10:00 7 Dec '07  
Generaloutstanding Pinmembersamurai92619:07 15 Nov '07  
GeneralLink Objects with a line move line while moving object PinmemberCélio1:39 13 Nov '07  
GeneralAre you familiar with Paint.NET? Pinmembersherifffruitfly12:41 27 Sep '07  
GeneralRe: Are you familiar with Paint.NET? PinsupporterMark Miller12:58 27 Sep '07  
GeneralUpdate - 8/25/2007 - Individual Object Rotation & Bug Fixes Pinmembercrockettk8:45 27 Sep '07  
GeneralRe: Update - 8/25/2007 - Individual Object Rotation & Bug Fixes PinsupporterMark Miller10:12 27 Sep '07  
GeneralDrawing outside client area Pinmembercrockettk8:23 27 Sep '07  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 27 Sep 2007
Editor: Deeksha Shenoy
Copyright 2007 by Mark Miller
Everything else Copyright © CodeProject, 1999-2009
Web11 | Advertise on the Code Project