CodeProject - DrawTools(画图工具)

本文介绍了一个使用C#编写的绘图工具箱DrawTools,该工具箱可以在Windows窗体上利用鼠标绘制矩形、椭圆、直线和多边形等图形。DrawTools由DrawTools窗体应用程序和DocToolkit类库组成,后者提供了文件管理和状态保存等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原作者: Alex Farber
原文: http://www.codeproject.com/csharp/DrawTools.asp(源代码请参见原文)

DrawTools1.jpg

介绍

DrawTools示例告诉你怎么创建一个Windows窗体,来使用鼠标和画图工具在窗体上的可用区域画图。这个示例中实现了以下画图工具:矩形,椭圆形,直线和铅笔。其中有一些众所周知的技术来创建这个程序,比如说:鼠标的交互,无闪烁画图,实现了画图和工具选择,物体选择,控制物体的Z轴次序,等等。MFC开发者可以从DRAWCLI.这个MFC示例中了解到所有这些特性。DrawTools这个C#程序复制了一些DRAWCLI的功能,并且在这个例子中使用了一些设计决定。

DrawTools 解决方案包括两个工程: DrawTools 窗体应用程序和 DocToolkit 类库。 DrawTools 实现了一些特别的应用程序功能, DocToolkit 包含有一些用于文件管理的标准类库。

以下描述了DrawTools 解决方案的主要特点:

 


DrawToolsClass.jpg

 

  • DrawArea用来填充主程序在窗体上可用区域的用户控件。包括GraphicsList 类的实例。绘制图形对象, 处理鼠标输入,把(鼠标)命令传给GraphicsList

  • GraphicsList图形对象的列表。包括图形对象的ArrayList。通过使用DrawObject类中的方法与其他图形对象通讯

  • DrawObject所有图形对象的抽象基类。

  • DrawRectangle矩形图形对象。

  • DrawEllipise椭圆形图形对象。

  • DrawLine直线图形对象。

  • DrawPolygon多边形图形对象。

  • Tool所有画图工具的抽象基类。

  • ToolPointer箭头工具 (中性工具)。包含选择,移动,改变图形对象大小的实现。

  • ToolObject所有工具的抽象基类,用来创建新的图形对象。ToolRectangle矩形工具。

  • ToolEllipse椭圆形工具。

  • ToolLine直线工具。

  • ToolPolygon多边形工具。

DocToolkit 类库

DocToolkit类库包含一些类的集合,这些类用来创建文档中心(document-centric)的窗体应用程序。从DocToolkit类库输出的类的实例被保存在DrawTools工程的主窗口中,用作一般的文件操作。

 

  • DocManager : 处理文件操作:打开,新建,保存,更新窗体标题,为Windows Shell注册文件类型。创建这个类引用了Chris Sells 的文章 Creating Document-Centric Applications in Windows Forms

  • DragDropManager : 在Windows Form应用程序中允许你通过拖拽的方式从浏览器(文件浏览器)中打开文件。

  • MruManager : 管理大多数最近使用的文件列表。

  • PersistWindowState : 允许你把最近一次的窗口状态保存到注册表 ,在窗体载入的时候重新获得。来源: Saving and Restoring the Location, Size and Windows State of a .NET Form By Joel Matthias.

在程序空闲的时候操作Windows的控件状态

每一个Windows窗体应用程序都会有许多控件,比如说菜单项,按钮,工具栏按钮等等。根据当前状态和用户的命令,这些控件可能有不同的状态:enabled/disabledchecked/unchecked, visible/invisible 等等。用户的每一个操作可能改变这些状态。在每一个消息句柄中改变控件的状态可能导致错误。取而代之的方法是,在用户每一个操作后调用一些函数,在这些函数中管理控件的状态。MFC有一个很好的特性叫ON_UPDATE_COMMAND_UI它允许在应用程序的空闲时间更新工具栏按钮的状态。这个特性也可以在.NET程序中实现。


考虑一下这种情形:当用户点击工具栏上的Rectangle按钮,这个按钮就要显示为被选中,前一个激活的工具就要显示为没选中。Rectangle按钮的消息句柄并没有改变窗体控件的状态,它只是在一些变量中保存了当前的选择。空闲的消息句柄选择激活的工具,取消未激活工具的选择。


None.gif private   void  Form1_Load( object  sender, System.EventArgs e)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
// Submit to Idle event to set controls state at idle time
InBlock.gif
    Application.Idle += new EventHandler(Application_Idle);
ExpandedBlockEnd.gif}

None.gif
None.gif
private   void  Application_Idle( object  sender, EventArgs e)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    SetStateOfControls();
ExpandedBlockEnd.gif}

None.gif
None.gif
public   void  SetStateOfControls()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
// Select active tool
InBlock.gif
    tbPointer.Pushed = (drawArea.ActiveTool == DrawArea.DrawToolType.Pointer);
InBlock.gif    tbRectangle.Pushed 
= (drawArea.ActiveTool == DrawArea.DrawToolType.Rectangle);
InBlock.gif    tbEllipse.Pushed  
= (drawArea.ActiveTool == DrawArea.DrawToolType.Ellipse);
InBlock.gif    tbLine.Pushed 
= (drawArea.ActiveTool == DrawArea.DrawToolType.Line);
InBlock.gif    tbPolygon.Pushed 
= (drawArea.ActiveTool == DrawArea.DrawToolType.Polygon);
InBlock.gif
InBlock.gif    menuDrawPointer.Checked 
= 
InBlock.gif                      (drawArea.ActiveTool 
== DrawArea.DrawToolType.Pointer);
InBlock.gif    menuDrawRectangle.Checked 
= 
InBlock.gif                      (drawArea.ActiveTool 
== DrawArea.DrawToolType.Rectangle);
InBlock.gif    menuDrawEllipse.Checked 
= 
InBlock.gif                      (drawArea.ActiveTool 
== DrawArea.DrawToolType.Ellipse);
InBlock.gif    menuDrawLine.Checked 
= (drawArea.ActiveTool == DrawArea.DrawToolType.Line);
InBlock.gif    menuDrawPolygon.Checked 
= 
InBlock.gif                      (drawArea.ActiveTool 
== DrawArea.DrawToolType.Polygon);
InBlock.gif
InBlock.gif    
// dot.gif
ExpandedBlockEnd.gif
}

None.gif
None.gif
//  Rectangle tool is selected
None.gif
private   void  CommandRectangle()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif     drawArea.ActiveTool 
= DrawArea.DrawToolType.Rectangle;
ExpandedBlockEnd.gif}

None.gif

Hit Test

DrawObject 类有一个叫HitTest的虚拟函数,用来侦测是否Point属于图形对象。

None.gif public   virtual   int  HitTest(Point point)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
return -1;
ExpandedBlockEnd.gif}

None.gif

继承类使用虚拟的PointInObject来做点击测试。这个函数调用自HitTestDrawRectangle类使用了一种简单的方法实现了这个函数:

None.gif protected   override   bool  PointInObject(Point point)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
return rectangle.Contains(point);
InBlock.gif    
// rectangle is class member of type Rectangle
ExpandedBlockEnd.gif
}

None.gif

DrawLine对这个函数的实现更加复杂:

None.gif protected   override   bool  PointInObject(Point point)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    GraphicsPath areaPath;
InBlock.gif    Pen areaPen;
InBlock.gif    Region areaRegion;
InBlock.gif
InBlock.gif    
// Create path which contains wide line
InBlock.gif    
// for easy mouse selection
InBlock.gif
    AreaPath = new GraphicsPath();
InBlock.gif    AreaPen 
= new Pen(Color.Black, 7);
InBlock.gif    AreaPath.AddLine(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y);
InBlock.gif        
// startPoint and EndPoint are class members of type Point
InBlock.gif
    AreaPath.Widen(AreaPen);
InBlock.gif
InBlock.gif    
// Create region from the path
InBlock.gif
    AreaRegion = new Region(AreaPath);
InBlock.gif
InBlock.gif    
return AreaRegion.IsVisible(point);
ExpandedBlockEnd.gif}

None.gif

DrawPolygon函数使用了同样的方法,但是AreaPath包含了多边形的所有线。

序列化

GraphicList类实现了ISerializable接口,这个接口用作类对象的二进制序列化。DrawObject类有两个虚函数用来做序列化。


None.gif public   virtual   void  SaveToStream(SerializationInfo info,  int  orderNumber)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
// dot.gif
ExpandedBlockEnd.gif
}

None.gif
None.gif
public   virtual   void  LoadFromStream(SerializationInfo info,  int  orderNumber)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  
// dot.gif
ExpandedBlockEnd.gif
}

None.gif

这些函数在每一个继承类中都实现了。二进制文件有以下格式:

这样就可以在GraphicList类里写普通的序列化代码,而不需要了解序列化对象的任何细节。

None.gif private   const   string  entryCount  =   " Count " ;
None.gif
private   const   string  entryType  =   " Type " ;
None.gif
None.gif
None.gif
//  Save list to stream
None.gif
[SecurityPermissionAttribute(SecurityAction.Demand, 
None.gif                         SerializationFormatter
= true )]
None.gif
public   virtual   void  GetObjectData(SerializationInfo info, 
None.gif                                     StreamingContext context)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
// number of objects
InBlock.gif
    info.AddValue(entryCount, graphicsList.Count);
InBlock.gif
InBlock.gif    
int i = 0;
InBlock.gif
InBlock.gif    
foreach ( DrawObject o in graphicsList )
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
// object type
InBlock.gif
        info.AddValue(
InBlock.gif            String.Format(CultureInfo.InvariantCulture,
InBlock.gif                
"{0}{1}",
InBlock.gif                entryType, i),
InBlock.gif            o.GetType().FullName);
InBlock.gif
InBlock.gif        
// object itself
InBlock.gif
        o.SaveToStream(info, i);
InBlock.gif
InBlock.gif        i
++;
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif
None.gif
//  Load from stream
None.gif
protected  GraphicsList(SerializationInfo info, StreamingContext context)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    graphicsList 
= new ArrayList();
InBlock.gif
InBlock.gif    
// number of objects
InBlock.gif
    int n = info.GetInt32(entryCount);
InBlock.gif    
string typeName;
InBlock.gif    
object drawObject;
InBlock.gif
InBlock.gif    
for ( int i = 0; i < n; i++ )
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
// object type
InBlock.gif
        typeName = info.GetString(
InBlock.gif            String.Format(CultureInfo.InvariantCulture,
InBlock.gif                
"{0}{1}",
InBlock.gif            entryType, i));
InBlock.gif
InBlock.gif        
// create object by type name using Reflection
InBlock.gif
        drawObject = Assembly.GetExecutingAssembly().CreateInstance(
InBlock.gif            typeName);
InBlock.gif
InBlock.gif        
// fill object from stream
InBlock.gif
        ((DrawObject)drawObject).LoadFromStream(info, i);
InBlock.gif
InBlock.gif        graphicsList.Add(drawObject);
ExpandedSubBlockEnd.gif    }

InBlock.gif
ExpandedBlockEnd.gif}

None.gif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值