C# 的画布实现






4.43/5 (27投票s)
2004年2月28日
4分钟阅读

261721

9670
C# 中画布的设计和实现
引言
在许多不同的应用程序中,都需要进行图形输入。除了创建新的图形对象(如线条、矩形和多边形)之外,还需要对对象进行编辑和以不同的比例进行查看。在某些情况下,图形事件(如形状创建、选择和移动)应与某些应用程序逻辑绑定。
画布或图形编辑器是一个为应用程序提供此类功能的组件。
背景
画布控件或小部件是一个包含不同类型图形对象的组件,负责其查看和编辑。
大多数 GUI 工具包都有自己的画布组件。例如,Qt 工具包、Borland C++ Builder 和 Tcl/Tk 中都有画布。然而,.NET 中却缺失了。
对于画布的实现和功能,我借鉴了 Qt 和 Tcl/Tk 工具包的经验。在这两个工具包中,画布都提供了丰富的功能来创建和操作图形对象。
在这两个工具包中,画布的缩放和平移会影响其中存储的图形对象的几何形状。当你在画布中存储“真实世界”的测量值时,这很不方便,因为每次你在画布中编辑它时,你都需要跟踪“真实世界”坐标系统的变化。
为了避免这种不便,此实现将逻辑上划分为几何模型和视图。当你在画布中编辑对象时,几何模型(以“真实世界”坐标表示)会发生变化,而视图(客户端坐标)保持不变;当你缩放和平移画布时,几何模型保持不变。
画布几何模型可以有多个视图,显示模型的不同部分。当模型发生变化时,所有视图都会自动更新,这意味着如果你在一个视图中移动或编辑形状,你会在其他视图中看到变化。
使用画布
Canvas
在 Canvas.cs 文件中的 CanvasControl
命名空间中实现。
用法示例在 CanvasDemo.cs 文件中。
创建新的 Canvas
并连接到其事件
// create new canvas
canvas1 = new CanvasControl.Canvas();
// connect listeners to canvas events
canvas1.CoordChangeEvent += new
CanvasControl.Canvas.CoordChangeEventHandler(OnCoordChainged);
canvas1.ItemNewEvent += new
CanvasControl.Canvas.ItemEventHandler(OnNewCanvasItem);
canvas1.ItemEnterEvent += new
CanvasControl.Canvas.ItemEventHandler(OnEnterCanvasItem);
其中
当光标在 Canvas
内移动时,会生成 CoordChangeEvent
事件。事件处理程序 CoordChangeEventHandler
的第二个参数包含光标的坐标和背景图像的 RGB 值。
public void OnCoordChainged(object sender,
CanvasControl.CoordEventArgs e)
{
textBox1.Text = e.X.ToString() + "," + e.Y.ToString();
}
ItemNewEvent
和 ItemEnterEvent
– 当光标进入和离开图形对象时会生成事件。第二个参数包含对象的 ID(索引)。可以通过其 ID 访问图形对象。
public void OnEnterCanvasItem(object sender,
CanvasControl.ItemEventArgs e)
{
textBox3.Text = e.indx.ToString();
}
为了创建现有 Canvas(其几何模型)的附加视图,我们应该创建第二个 Canvas
,例如 canvas2
,并将其附加到前一个。
canvas2 = new CanvasControl.Canvas();
canvas2.setGeomModel(canvas1.getGeomModel());
Canvas
本身不包含其他图形控件,它通过其 API 进行控制。但是,可以选择连接滚动条控件,以便在画布导航期间更新滚动条控件。
canvas1.connectScrolls(hScrollBar1,vScrollBar1);
要创建、移动或选择图形对象,我们需要按如下方式更改 Canvas
状态
// create graphical objects
canvas1.CreateRectangle();
canvas1.CreateLine();
canvas1.CreatePolygon();
// move objects
canvas1.StartMoving();
// select objects
canvas1.StartSelection();
可以通过 ID 访问图形对象,并且可以检索或更新其几何和其他属性。在下面的函数中,检索选定项目的 ID,并使用用户定义的颜色进行更新。
private void btnColor_Click(object sender, System.EventArgs
e)
{
ColorDialog colorDialog = new ColorDialog();
if(colorDialog.ShowDialog() != DialogResult.OK)
return;
Color color = colorDialog.Color;
int [] selected = canvas1.GetSelected();
foreach(int i in selected)
{
CanvasControl.CanvasItem item = canvas1.GetItem(i);
item.Icolor = color;
item.select(false);
}
canvas1.Invalidate();
canvas1.Update();
}
你可以创建自己的几何对象,为此,你应该实现 CanvasItem
接口(参见 Canvas.cs 文件)并使用 Canvas.AddShape
方法将其放入画布中。
设计问题
画布的设计中使用了几种设计模式。
模板模式用于 CanvasItem
类,以在基类中实现一些形状的通用功能。
观察者模式用于通知订阅者画布更改,它使用 C# 事件来实现。
外观模式用于隐藏用户内部的画布对象。
在上面的类图中,显示了创建画布控件的类。
Canvas
- 封装了画布控件的视图(模型/视图模式)。Canvas 通过 C# 事件与几何模型CanvasGeomModel
同步。每次模型更改时,模型会生成ItemChangedEvent
或ModelChangedEvent
,强制画布更新视图。每次视图Canvas
需要显示模型时,它都会调用CanvasGeomModel
的draw
方法,指定图形上下文(或画家对象)Graphics
和一个在视图的“客户端”坐标系与模型的“真实世界”坐标系之间的Matrix
变换。最终,从CanvasGeomModel
将绘制请求委托给具体的形状,这些形状实现了CanvasItem
draw
接口,以在视图上绘制自身。CanvasGeomModel
- 封装了画布控件的几何模型。它负责存储、编辑和显示派生自CanvasItem
类的不同形状。几何对象在模型中以“真实世界”坐标表示,独立于不同视图的“客户端”坐标。CanvasItem
- 封装了存储在几何模型CanvasGeomModel
中的几何对象或形状的通用行为。它表示Canvas
用于操作和显示不同类型形状的接口。要创建新形状,你应该实现CanvasItem
接口中定义的方法。public class LineCanvasItem : CanvasItem { public float x1; public float y1; public float x2; public float y2; private bool is_selected = false; // construct from 2 points coordinates public LineCanvasItem(float x_1,float y_1,float x_2,float y_2) { x1 = x_1; y1 = y_1; x2 = x_2; y2 = y_2; } // returns true if item is selected public override bool isSelected() { return is_selected; } // select item public override void select(bool m) { is_selected = m; } // returns true if the item is within the distanse public override bool isCloseToPoint(PointF pnt,float dist) { double curr_dist = Geom.segmentToPointSqrDist( new PointF(x1,y1),new PointF(x2,y2),pnt); return Math.Sqrt(curr_dist) < dist; } // return bounding box of the item public override RectangleF boundingBox() { return new RectangleF(x1,y1,x2-x1,y2-y1); } // start point X coordinate of the shape public override float X() { return x1; } // start point Y coordinate of the shape public override float Y() { return y1; } // move shape to specified location public override void move(PointF p) { float dx = p.X-x1; float dy = p.Y-y1; x1 += dx; y1 += dy; x2 += dx; y2 += dy; } // move shape by specified shift public override void moveBy(float xs,float ys) { x1+=xs; y1+=ys; x2+=xs; y2+=ys; } // draw shape on the view public override void draw(Graphics graph, System.Drawing.Drawing2D.Matrix trans) { // transform points to "client" view coordinate system PointF [] points = {new PointF(x1,y1), new PointF(x2,y2)}; trans.TransformPoints(points); // draw line in "client" coordinate system if(is_selected) graph.DrawLine(new Pen(Color.Cyan), (int)points[0].X,(int)points[0].Y, (int)points[1].X,(int)points[1].Y); else graph.DrawLine(new Pen(Icolor),(int)points[0].X, (int)points[0].Y, (int)points[1].X,(int)points[1].Y); } }
然后应该使用Canvas.AddShape
方法将新形状添加到画布中。canvas1.AddShape(new LineCanvasItem(10,10,100,100));
Geom
-Canvas
中使用的几何过程。
历史
- 2004/02/28 初始版本。
- 2004/03/17 从画布视图中分离几何模型 + 设计解释。