一个优秀的C#开源绘图软件 DrawTools
1、Extensions to DrawTools
Author
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:
- In addition to the basic Rectangle, Ellipse, Line and Scribble tools, this version adds PolyLine, Filled Ellipse, Filled Rectangle, Text and Image tools
- Multiple drawing Layers
- Zooming
- Panning
- 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:
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
- 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
andImage
objects. For other objects, Tooltip displays starting and ending coordinates.Text
objects do not display Tooltip.
This was implemented adding the Tooltip control to theToolPointer
class. EachDraw
Object fills theTipText
property and theMouseMove
event inToolPointer
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
2、DrawTools 2014
Author
Arnault Bonafos, 16 Jan 2014
Introduction
Current alternative is based on the CodeProject DrawTools 2005, built under Visual Studio C# 2010.
The main goal of this tip is to post and save the DrawTools project back to the community after some enhancements. The tip is short, as the improvements are minor.
A few improvements have been made, as follows:
- Add scroll bars to the draw area
- Handling of
AutoScrollPosition
in the different tools provided by the original project - Export of the drawing with jpeg format
Background
Knowledge of C# is required, but this project shows the basics of UI programming under '.NET'.
Architecture
Dynamic handling for showing scroll bars
A dirty boolean is handled in GraphicsList
, the canvas document, where it should be. To this Dirty
property is associated a DocumentDirtyObserver
interface.
- public interface DocumentDirtyObserver
- {
- void IsDirty(GraphicsList gList);
- }
Every time the document is modified, look with the different draw tools, its Dirty
property is set to true
and any DocumentDirtyObserver
is fired.
- public bool Dirty
- {
- get
- {
- return this.dirty;
- }
- set
- {
- this.dirty = value;
- if (this.dirty)
- {
- NotifyDirty();
- }
- }
- }
The class DrawArea
implements the DocumentDirtyObserver
, and every time the document is 'dirty
', it calls AdjustRendering
, which in turn computes the bounding box of the canvas document (GraphicsList
).
- void AdjustRendering()
- {
- Size docSize;
- if (this.GraphicsList != null)
- {
- docSize = this.GraphicsList.GetSize();
- docSize.Width += 20;
- docSize.Height += 20;
- AutoScrollMinSize = docSize;
- }
- else
- {
- AutoScrollMinSize = new Size(0, 0);
- }
- Invalidate();
- }
This way, it implements the Mode/View/Controller design pattern, the GraphicsList
being the model, DrawArea
being the View and the draw Tools
being the controller.
In the future, Dirty
property should be handled in only one place (it is also handled in the DrawArea
class).
Export the graph to JPEG format
The DrawTools 2014 architecture is good enough to be followed to implement a new menu strip option.
Add a menu item to the menu bar
Simply open the MainForm designer and add the option
Link the
MenuItem
to aMainForm
method to trigger theCommandExportToJpg
Hide Copy Code- private void CommandExportToJpg()
- {
- docManager.ExportToJpg();
- }
- private void CommandExportToJpg()
- Implement the user interface logic in the
DocManager ExportToJpg
I let you look at the
DocManager.ExportToJpg
- Subscribe
MainForm ExportEvent
method implementation toDocManager
event.
- docManager.ExportEvent += docManager_ExportEvent;
- Implement
MainForm
logic to actually transform image bitmap into JPEG file.All is in the source code, I don't copy it here as it is essentially technical stuff.
Result
The result is best shown by the following graph, obtained with a customized version of the DrawTools.
Points of Interest
Architecture is already well thought in the original project, please read the original document for insight about it.
There was a bug where the status bar was hiding the horizontal scroll bar, but after some thorough inspection of the code, it has been fixed.
History
- Added scroll bars to the draw area
- Added a status bar
- Fixed a bug on the context menu when the scroll bar position is not 0
- Export of the drawing with jpeg format
- Fixed an
OutOfMemoryException
with JPEG format export
Special thank you to Alex, who originally posted the DrawTools project on CodeProject.
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
一个优秀的C#开源绘图软件 DrawTools的更多相关文章
- Dia Diagram Editor(流程图、UML)免费开源绘图软件
近期工作各种繁忙,导致很少分享自己喜欢和常用的一些工具,今天有点时间再次给各位喜欢开源的小伙伴介绍一个好用.免费.开源的软件Dia Diagram Editor. 首先给大家看看这个软件的主界面吧! ...
- 推荐优秀的开源GIS软件
推荐优秀的开源GIS软件(以后会补充) 从GIS入门到现在,我已经接触不少优秀的GIS软件,这里列出我使用过优秀的开源GIS软件. 桌面GIS软件: Qgis(基于Qt使用C++开发的跨平台桌面软件, ...
- Golang优秀开源项目汇总, 10大流行Go语言开源项目, golang 开源项目全集(golang/go/wiki/Projects), GitHub上优秀的Go开源项目
Golang优秀开源项目汇总(持续更新...)我把这个汇总放在github上了, 后面更新也会在github上更新. https://github.com/hackstoic/golang-open- ...
- 开源GIS软件初探
谈到GIS软件,首先让我们想到的便是GIS界的龙头大哥ESRI公司旗下的ArcGIS产品,从最初接触的version 9.2到如今的version 10.1,其发展可谓风生水起.MapInfo软件也不 ...
- 介绍n款计算机视觉库/人脸识别开源库/软件
计算机视觉库 OpenCV OpenCV是Intel®开源计算机视觉库.它由一系列 C 函数和少量 C++ 类构成,实现了图像处理和计算机视觉方面的很多通用算法. OpenCV 拥有包括 300 多个 ...
- 【Android 应用开发】GitHub 优秀的 Android 开源项目
原文地址为http://www.trinea.cn/android/android-open-source-projects-view/,作者Trinea 主要介绍那些不错个性化的View,包括Lis ...
- Mac 设计师必备的设计绘图软件 推荐与下载
Mac设计师必备的设计绘图软件,为广大设计师推荐一些Mac上实用且强大的软件,使用好的软件,事半功倍,设计出精美的作品. Mac上优秀的设计类软件非常多,绝对不止这几款软件,看看以下内容,希望对你有帮 ...
- [转]GitHub上优秀的Go开源项目
转载于GitHub上优秀的Go开源项目 正文 近一年来,学习和研究Go语言,断断续续的收集了一些比较优秀的开源项目,这些项目都非常不错,可以供我们学习和研究Go用,从中可以学到很多关于Go的使用.技巧 ...
- 这13个开源GIS软件,你了解几个?【转】
泰伯网有看点的空间地理信息资讯都在这,你还在等什么? 这些开源GIS软件,你了解几个?本文内容部分来源于一份罗列了关于GIS软件应用的文章,笔者将其编译整合. 地理信息系统(Geographic In ...
随机推荐
- LeetCode() Valid Anagram 有问题!!!
为什么第一个通过,第二个不行呢? class Solution { public: bool isAnagram(string s, string t) { if(s.size() != t.size ...
- 《苹果开发之Cocoa编程》挑战2 创建一个数据源 练习
<苹果开发之Cocoa编程>第4版 P87 创建一个to-do list应用程序,在文本框中输入任务.当用户单击Add按钮时,添加字符串到一个变长队列,新任务就出现在list的末尾. 关键 ...
- 黑马程序员——JAVA基础之包,权限
------- android培训.java培训.期待与您交流! ---------- 包(package) 对类文件进行分类管理. 给类提供多层命名空间. 写在程序文件的第一行. 类名的全称的是:包 ...
- NetStatusEvent info对象的状态或错误情况的属性
代码属性 级别属性 意义 "NetStream.Buffer.Empty" "status" 数据的接收速度不足以填充缓冲区.数据流将在缓冲区重新填充前中 ...
- html5图片懒加载
http://www.178.com/s/js/jquery.lazyload.min.js
- FreeSWITCH在呼叫失败的情况下播放语音提示
看到好多网友问到这个问题.一般我们在打电话时会听到“您拨的电话正在通话中,请稍后再拨....”,或“电话无应答...”之类的提示,我们在 freeswitch 里也可以这样做. 其实很简单,默认的配置 ...
- freeswitch 接通后再录音
需求 录音时不要将前置媒体录制进去. 用法 <action application="set" data="media_bug_answer_req=true&qu ...
- Unity3D研究院之Editor下监听Transform变化
美术有可以直接在Editor下操作Transform,我想去修正他们编辑的数值,所以我就得监听Transform. C# 1 2 3 4 5 6 7 8 9 10 11 12 13 1 ...
- Python3下map函数的显示问题
map函数是Python里面比较重要的函数,设计灵感来自于函数式编程.Python官方文档中是这样解释map函数的: map(function, iterable, ...) Return an it ...
- linux小包集合
mingetty包 getty是Unix类操作系统启动时必须的三个步骤之一,用来开启终端,进行终端的初始化.目前一般常用的getty程序有如下几种:1.agetty(有时直接称为getty):容易设置 ...