Extensions to DrawTools - The Code Project - ...

来源:百度文库 编辑:神马文学网 时间:2024/04/29 22:37:03

4,470,390 members and growing!   7,412 now online. Email Password Remember me? Password problem?
HomeMFC/C++C#ASP.NETVB.NETArchitectSQLAll Topics  Help!ArticlesMessage BoardsLounge
All Topics,C#,.NET >>C# Programming >>Unedited Reader Contributions
Extensions to DrawTools
ByMark Miller.
DrawTools library extended to include Layers, Zoom, Pan, Rotation
C# (C# 2.0)
Windows, .NET (.NET 2.0)
Win32, VS (VS2005), GDI+, WinForms
Dev
Posted: 6 Mar 2007
Updated: 25 Aug 2007
Views: 17,861
Note:This is an unedited reader contribution
Announcements
Monthly Competition

Search   Articles Authors   Advanced Search
Sitemap
PrintBroken Article?BookmarkDiscussSend to a friend
10 votes for this article.
Popularity: 4.54. Rating: 4.54 out of 5.
Download demo - 55.0 KBDownload source - 469.0 KB
Updated 8/25/2007

Introduction
Alex Fr provided an excellent set of drawing tools in hisDrawTools 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 the reader has a working understanding of GDI+ fundamentals, including Matrices. For an excellent introduction to GDI+, seewww.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:
/// /// Create a new layer at the head of the layers list and set it to Active and Visible. /// /// The name to assign to the new layer 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 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 is 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:
Collapse
/// /// Back Track the Mouse to return accurate coordinates regardless of zoom or pan effects. /// Courtesy of BobPowell.net /// /// Point to backtrack /// Backtracked point 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 fromBob 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 it's 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:
Collapse
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 all for reporting!
History
Original Article uploaded to CP 3/6/2007.
Updated 3/6/2007 to include more information on zoom/pan/rotation
Updated 8/25/2007 - Individual Object Rotation
About Mark Miller

I develop custom accounting software, websites, etc.
Languages include Visual FoxPro, C#, Crystal Reports, and MS-SQL. Clickhere to view Mark Miller's online profile.
Other popular C# Programming articles:
A flexible charting library for .NET Looking for a way to draw 2D line graphs with C#? Here's yet another charting class library with a high degree of configurability, that is also easy to use.
I/O Ports Uncensored - 1 - Controlling LEDs (Light Emiting Diodes) with Parallel Port Controlling LEDs (Light Emiting Diodes) with Parallel Port
Asynchronous Method Invocation How to use .NET to call methods in a non-blocking mode.
I/O Ports Uncensored Part 2 - Controlling LCDs (Liquid Crystal Displays) and VFDs (Vacuum Fluorescent Displays) with Parallel Port Controlling LCDs (Liquid Crystal Displays) and VFDs (Vacuum Fluorescent Displays) with Parallel Port

[Top]Sign in to vote for this article:     PoorExcellent

Attention: You mustconfirm your email address before you can post to this forum.
Note: You mustSign in to post to this message board.
FAQ  Message score threshold 1.0 2.0 3.0 4.0 5.0    Search comments
View Normal (slow) Preview (slow) Message View Topic View Thread View Expanded    Per page 10 25 50 (must logon)
  Msgs 1 to 25 of 38 (Total: 38) (Refresh) First PrevNext
Subject  Author  Date

 Link to updated source broken  dvasilio  7:58 29 Aug '07
  The link to the newly updated source appears broken. Any chance this can be fixed.
[Sign in |View Thread |PermaLink |Go to Thread Start]


 Out of Memory Crash  centraltheory  16:28 21 Aug '07
  I added a tiny line in the top left hand corner of the drawing window that amounted to a dot, and then used the mouse to hover over the spot and got an out of memory exception on:
AreaPath.Widen(AreaPen); (drawline.cs line 273)
btw, thanks for this submission, this is a great help in doing my own vector graphics app.
[Sign in |View Thread |PermaLink |Go to Thread Start]

 Re: Out of Memory Crash  centraltheory  16:39 21 Aug '07
  The problem is caused by the line above the one I provided.
AreaPath.AddLine(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y);
if startPoint = endPoint, then boom.
[Sign in |View Thread |PermaLink |Go to Thread Start]

 Re: Out of Memory Crash  Mark Miller  15:03 25 Aug '07
  I added a line to the code to check for this condition, and increment endPoint by 1 if necessary.
That change is in the updated code.
Thanks for the catch!
Sincerely,
Mark D. Miller
mark@msdcweb.com
http://www.msdcweb.com
[Sign in |View Thread |PermaLink |Go to Thread Start]


 Loading a Background Image on Startup  alias47  22:23 19 Aug '07
  Hi,
Can you suggest the best way to load an image into the bottom layer as a background on startup so that it covers the entire working area?
Regards
Andrew
[Sign in |View Thread |PermaLink |Go to Thread Start]

 Re: Loading a Background Image on Startup  Mark Miller  15:07 25 Aug '07
  Andrew,
My first thought is you should be able to automatically add an image in the DrawArea() constructor code, however it would take some additional logic to automatically resize, tile or stretch the background object as the drawing area changes size from panning, drawing, etc.
If I get some time I will work on this, but please note that is a pretty big "IF" right now!
Sincerely,
Mark D. Miller
mark@msdcweb.com
http://www.msdcweb.com
[Sign in |View Thread |PermaLink |Go to Thread Start]


 Is there a problem in Polyline tool  J Navaneethan  23:13 29 Jul '07
  Great work. Saved me allot of time.
Is there a problem in Polyline tool? It takes a while to draw the lines initially and also only one polyline can be drawn at the moment.
Thanks.
Neet
[Sign in |View Thread |PermaLink |Go to Thread Start]

 Re: Is there a problem in Polyline tool  J Navaneethan  23:43 29 Jul '07
  Realised that the Right MouseDown event in ToolPolygon is never triggered.
In DrawArea.cs modify the else portion as below to fix the problem.
else if (e.Button == MouseButtons.Right)
{
if (_panning == true)
_panning = false;
if (activeTool == DrawToolType.PolyLine)
tools[(int)activeTool].OnMouseDown(this, e);
ActiveTool = TETemplateDrawArea.DrawToolType.Pointer;
}
Thanks
Neet
[Sign in |View Thread |PermaLink |Go to Thread Start]

 Re: Is there a problem in Polyline tool  Mark Miller  11:55 30 Jul '07
  Thanks for the fix!
I didn't realize this was a problem until you mentioned it. Thanks again.
Sincerely,
Mark D. Miller
mark@msdcweb.com
http://www.msdcweb.com
[Sign in |View Thread |PermaLink |Go to Thread Start]


 Xml File  krish_knight326  6:46 24 Jul '07
  How to save into xmlfile? please i need urgent.
krishna
[Sign in |View Thread |PermaLink |Go to Thread Start]

 Re: Xml File  Mark Miller  22:28 24 Jul '07
  Serializing to XML would not be easy to accomplish.
XML Serializing will not serialize any private properties - only public properties. This would require a lot of rework of the code to implement.
Sincerely,
Mark D. Miller
mark@msdcweb.com
http://www.msdcweb.com
[Sign in |View Thread |PermaLink |Go to Thread Start] Score: 1.0 (1 vote).


 How to add line curve?  piyachatel  1:15 24 May '07
  I need to draw a line curve on drawarea.
How to add it?
Thanks in advance
[Sign in |View Thread |PermaLink |Go to Thread Start]

 Re: How to add line curve?  Mark Miller  13:21 25 May '07
  I think the simplest way would be to add a modified DrawPolygon class (perhaps named DrawCurve()) and replace the g.DrawPath(pen, gp); line with g.DrawCurve(pen, pts); which would make your freeform line a smooth curve that can later be adjusted like other objects on the draw surface.
You could also implement Bezier curves by creating a DrawBeziers() class which would duplicate the existing DrawPolygon() with the following code in the Draw() method of the new class instead of the g.DrawPath(pen, gp);:
// For DrawBeziers() to work, the pts array must have a minimum of 4 points.
// The pts array may have more than 4 points, but if so, remaining points
// must be in sets of 3 for the call to work.
// The following code will adjust the pts array to properly fit these requirements.
int numPoints = pts.Length;
if (numPoints - 4 <= 0)
{
// Cannot call DrawBeziers() so return, drawing nothing.
gp.Dispose();
pen.Dispose();
return;
}
while ((numPoints - 4) % 3 != 0 && numPoints - 4 > 0)
{
// Chop off the last point from the pts array
numPoints--;
Array.Resize(ref pts, numPoints);
}
g.DrawBeziers(pen, pts);
HTH
Sincerely,
Mark D. Miller
mark@msdcweb.com
http://www.msdcweb.com
[Sign in |View Thread |PermaLink |Go to Thread Start]

 Re: How to add line curve?  piyachatel  23:02 27 May '07
  Thanks for your kindly suggestion.
I am not quite sure whether i can do it or not.
However, I would try to modify followed your advice.
A million thanks
[Sign in |View Thread |PermaLink |Go to Thread Start]


 Very Useful Source  meeashish2004  3:21 4 May '07
  Hi Mark,
You guys(you and Alex) have done excellent job.
I have been looking for this kind of implementation and your code library seems very promissing to build our application. We are developing an application, a workflow modeler, wherein end user can define a workflow (using shapes.. such as Task, Decision Box, Actions etc. ) with the help of easy to use graphic user interface.
Hopefuly I'll complete and post this here very soon.
Thanks
Ashish K.
[Sign in |View Thread |PermaLink |Go to Thread Start]

 Re: Very Useful Source  Mark Miller  12:05 10 May '07
  Ashish,
I am glad you can use this work - I look forward to seeing what you are building with it!
Sincerely,
Mark D. Miller
mark@msdcweb.com
http://www.msdcweb.com
[Sign in |View Thread |PermaLink |Go to Thread Start]


 Base class for drawing  chongkj  23:05 26 Apr '07
  hi Where is the base class of drawing for this program ??Is it under DrawArea.cs?? this code here??
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();
}
[Sign in |View Thread |PermaLink |Go to Thread Start]

 Re: Base class for drawing  Mark Miller  14:32 27 Apr '07
  Chongkj,
This article is an extension of another article on CodeProject located here:http://www.codeproject.com/csharp/drawtools.asp[^]
If you look at the original article, you will find a class diagram showing the inheritence order, etc.
Hope this helps
Sincerely,
Mark D. Miller
mark@msdcweb.com
http://www.msdcweb.com
[Sign in |View Thread |PermaLink |Go to Thread Start]


 Transparent  Hamid Qureshi  1:56 25 Apr '07
  Is there any way to make the draw area transparent. i.e lets say I place the drawArea over a picture box and I want the picture box to be visible?.
I have tried setting the drawArea BackColor to Transparent but that did not help.
Any suggestions?
Hamid
[Sign in |View Thread |PermaLink |Go to Thread Start]

 Re: Transparent  Mark Miller  12:07 25 Apr '07
  Hamid,
No, there is not which is one of the reasons I implemented layers.
If you want to draw on top of a picture box, you could place the picture in the drawing on layer 1 and then do all your drawing on layer 2 or 3, etc.
This allows you to draw on top of an image.
Hope this helps,
Sincerely,
Mark D. Miller
mark@msdcweb.com
http://www.msdcweb.com
[Sign in |View Thread |PermaLink |Go to Thread Start]


 printing  chongkj  4:17 29 Mar '07
  hi how can i add a printpreview,page setup and print to this program?? i simply type the codes in the event handler for the print??any codes to share thank =)
chongkj
[Sign in |View Thread |PermaLink |Go to Thread Start] Score: 5.0 (1 vote).

 Re: printing  Mark Miller  12:09 29 Mar '07
  I have not attempted to add any printing functionality.
I would be interested in seeing anything you come up with however.
Sincerely,
Mark D. Miller
mark@msdcweb.com
http://www.msdcweb.com
[Sign in |View Thread |PermaLink |Go to Thread Start]


 New Tools  crockettk  22:59 28 Mar '07
  Great Job on this.
How would you add a new tool like a Line with dashes and a arrow point at the end?
Also, I don't think the Move to Front or Move to Back is working.
Kurt Crockett
[Sign in |View Thread |PermaLink |Go to Thread Start]

 Re: New Tools  Mark Miller  23:59 28 Mar '07
  Thanks, Kurt,
To add a new line tool takes 4 steps:
1. In DrawingPens.cs, add a new entry to the enumeration, before the last entry:
public enum PenType
{
Generic,
RedPen,
BluePen,
GreenPen,
RedDottedPen,
RedDotDashPen,
DoubleLinePen,
DashedArrowPen,
NumberOfPens
};
2. In DrawingPens.cs, add a new branch to the switch statement to a new method:
case PenType.DashedArrowPen:
pen = DashedArrowLinePen();
break;
3. In DrawingPens.cs, add a new method to define the new line tool:
/// Returns a 10 pixel wide red dashed pen with an arrow on one end
private static Pen DashedArrowLinePen()
{
Pen p = new Pen(Color.Red);
p.LineJoin = LineJoin.Round;
p.Width = 10f;
p.DashStyle = DashStyle.Dash;
p.EndCap = LineCap.ArrowAnchor;
p.DashCap = DashCap.Flat;
return p;
}
4. In MainForm.cs, add a new menu option to the Pens menu and connect the click method to assign the new line tool:
private void dashedArrowLineToolStripMenuItem_Click(object sender, EventArgs e)
{
drawArea.PenType = DrawingPens.PenType.DashedArrowPen;
drawArea.CurrentPen = DrawingPens.SetCurrentPen(DrawingPens.PenType.DashedArrowPen);
}
You are right - the move to front / move to back is broken. I was working on implementing a ZOrder property to allow moving one object behind / in front of another object, without moving the object in front of / behind all other objects and this is not implemented yet.
To make the existing move to front / back code work, simply comment out the graphicsList.Sort(); line in the GraphicsList.cs Draw() method.
Cheers!
Sincerely,
Mark D. Miller
mark@msdcweb.com
http://www.msdcweb.com
[Sign in |View Thread |PermaLink |Go to Thread Start]

 Re: New Tools  crockettk  18:38 30 Mar '07
  Thanks. That works.
Is there an easy way to export the (untitled.dtl) to a .bmp, .gif, or .jpg?
Thanks for your time.
-Kurt
[Sign in |View Thread |PermaLink |Go to Thread Start]

Last Visit: 9:36 Tuesday 4th September, 2007 First PrevNext
General comment   News / Info   Question   Answer   Joke / Game   Admin message
Updated: 25 Aug 2007 Article content copyright Mark Miller, 2007
everything else Copyright ©CodeProject, 1999-2007.
Web10 |Advertise on The Code Project |Privacy

The Ultimate Toolbox •ASP Alliance •Developer Fusion •Developersdex •DevGuru •Programmers Heaven •Planet Source Code •Tek-Tips Forums •
Help!
Articles
Message Boards
Lounge
What is 'The Code Project'?
General FAQ
Post a Question
Site Directory
About Us
Latest
Most Popular
Search
Site Directory
Submit an Article
Update an Article
Article Competition
Windows Vista
Visual C++
ATL / WTL / STL
COM
C++/CLI
C#
ASP.NET
VB.NET
Web Development
.NET Framework
Mobile Development
SQL / ADO / ADO.NET
XML / XSL
OS / SysAdmin
Work Issues
Article Requests
Collaboration
General Discussions
Hardware
Algorithms / Math
Design and Architecture
Subtle Bugs
Suggestions
The Soapbox