原文:http://www.codeproject.com/csharp/DrawTools.asp(源代码请参见原文)
介绍
DrawTools示例告诉你怎么创建一个Windows窗体,来使用鼠标和画图工具在窗体上的可用区域画图。这个示例中实现了以下画图工具:矩形,椭圆形,直线和铅笔。其中有一些众所周知的技术来创建这个程序,比如说:鼠标的交互,无闪烁画图,实现了画图和工具选择,物体选择,控制物体的Z轴次序,等等。MFC开发者可以从DRAWCLI.这个MFC示例中了解到所有这些特性。DrawTools这个C#程序复制了一些DRAWCLI的功能,并且在这个例子中使用了一些设计决定。
DrawTools解决方案包括两个工程:DrawTools窗体应用程序和DocToolkit类库。DrawTools实现了一些特别的应用程序功能,DocToolkit包含有一些用于文件管理的标准类库。
以下描述了DrawTools 解决方案的主要特点:
- 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/disabled,checked/unchecked, visible/invisible 等等。用户的每一个操作可能改变这些状态。在每一个消息句柄中改变控件的状态可能导致错误。取而代之的方法是,在用户每一个操作后调用一些函数,在这些函数中管理控件的状态。MFC有一个很好的特性叫ON_UPDATE_COMMAND_UI
,
它允许在应用程序的空闲时间更新工具栏按钮的状态。这个特性也可以在.NET程序中实现。
考虑一下这种情形:当用户点击工具栏上的Rectangle按钮,这个按钮就要显示为被选中,前一个激活的工具就要显示为没选中。Rectangle按钮的消息句柄并没有改变窗体控件的状态,它只是在一些变量中保存了当前的选择。空闲的消息句柄选择激活的工具,取消未激活工具的选择。
{
// Submit to Idle event to set controls state at idle time
Application.Idle += new EventHandler(Application_Idle);
}
private void Application_Idle(object sender, EventArgs e)
{
SetStateOfControls();
}
public void SetStateOfControls()
{
// Select active tool
tbPointer.Pushed = (drawArea.ActiveTool == DrawArea.DrawToolType.Pointer);
tbRectangle.Pushed = (drawArea.ActiveTool == DrawArea.DrawToolType.Rectangle);
tbEllipse.Pushed = (drawArea.ActiveTool == DrawArea.DrawToolType.Ellipse);
tbLine.Pushed = (drawArea.ActiveTool == DrawArea.DrawToolType.Line);
tbPolygon.Pushed = (drawArea.ActiveTool == DrawArea.DrawToolType.Polygon);
menuDrawPointer.Checked =
(drawArea.ActiveTool == DrawArea.DrawToolType.Pointer);
menuDrawRectangle.Checked =
(drawArea.ActiveTool == DrawArea.DrawToolType.Rectangle);
menuDrawEllipse.Checked =
(drawArea.ActiveTool == DrawArea.DrawToolType.Ellipse);
menuDrawLine.Checked = (drawArea.ActiveTool == DrawArea.DrawToolType.Line);
menuDrawPolygon.Checked =
(drawArea.ActiveTool == DrawArea.DrawToolType.Polygon);
//
}
// Rectangle tool is selected
private void CommandRectangle()
{
drawArea.ActiveTool = DrawArea.DrawToolType.Rectangle;
}
Hit Test
DrawObject
类有一个叫HitTest
的虚拟函数,用来侦测是否Point属于图形对象。
{
return -1;
}
继承类使用虚拟的PointInObject
来做点击测试。这个函数调用自HitTest
。DrawRectangle
类使用了一种简单的方法实现了这个函数:
{
return rectangle.Contains(point);
// rectangle is class member of type Rectangle
}
DrawLine
对这个函数的实现更加复杂:
{
GraphicsPath areaPath;
Pen areaPen;
Region areaRegion;
// Create path which contains wide line
// for easy mouse selection
AreaPath = new GraphicsPath();
AreaPen = new Pen(Color.Black, 7);
AreaPath.AddLine(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y);
// startPoint and EndPoint are class members of type Point
AreaPath.Widen(AreaPen);
// Create region from the path
AreaRegion = new Region(AreaPath);
return AreaRegion.IsVisible(point);
}
DrawPolygon
函数使用了同样的方法,但是AreaPath
包含了多边形的所有线。
序列化
GraphicList
类实现了ISerializable
接口,这个接口用作类对象的二进制序列化。DrawObject
类有两个虚函数用来做序列化。
{
//
}
public virtual void LoadFromStream(SerializationInfo info, int orderNumber)
{
//
}
这些函数在每一个继承类中都实现了。二进制文件有以下格式:
Number of objects
Type name
Object
Type name
Object
...
Type name
Object
这样就可以在GraphicList类里写普通的序列化代码,而不需要了解序列化对象的任何细节。
private const string entryType = "Type";
// Save list to stream
[SecurityPermissionAttribute(SecurityAction.Demand,
SerializationFormatter=true)]
public virtual void GetObjectData(SerializationInfo info,
StreamingContext context)
{
// number of objects
info.AddValue(entryCount, graphicsList.Count);
int i = 0;
foreach ( DrawObject o in graphicsList )
{
// object type
info.AddValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}",
entryType, i),
o.GetType().FullName);
// object itself
o.SaveToStream(info, i);
i++;
}
}
// Load from stream
protected GraphicsList(SerializationInfo info, StreamingContext context)
{
graphicsList = new ArrayList();
// number of objects
int n = info.GetInt32(entryCount);
string typeName;
object drawObject;
for ( int i = 0; i < n; i++ )
{
// object type
typeName = info.GetString(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}",
entryType, i));
// create object by type name using Reflection
drawObject = Assembly.GetExecutingAssembly().CreateInstance(
typeName);
// fill object from stream
((DrawObject)drawObject).LoadFromStream(info, i);
graphicsList.Add(drawObject);
}
}