Introduction
In many different applications, graphical input is needed. Along with creating new graphical objects such as lines, rectangles and polygons, objects editing and viewing in different scale are required. In some cases, graphics events such as shape creation, selection and moving should be bound to some application logic.
Canvas or graphical editor is a component that provides an application with such functionality.
Background
Canvas control or widget is a component containing different types of graphical objects responsible for its viewing and editing.
Most of the GUI toolkits have their own canvas components. For example, there are canvases in Qt toolkit, Borland C++ Builder and Tcl/Tk. Somehow, it�s missing in .NET.
For canvas implementation and functionality, I use my experience with Qt and Tcl/Tk toolkits. In both toolkits, canvas provides rich functionality to create and manipulate graphical objects.
In both toolkits, scaling and translating of canvas affects geometry of graphical objects stored inside. It�s inconvenient when you're storing �real world� measurements in your canvas, so each time you edit it in the canvas, you should keep track on changes of the �real world� coordinate system.
To avoid this inconvenience, this implementation is logically divided to geometrical model and view. When you're editing objects inside the canvas, geometrical model (presented in �real world� coordinates) is changing and the view (client coordinates) stays the same, and when you rescale and scroll the canvas, the geometrical model stays unchanged.
Canvas geometrical model could have several views showing different parts of the model. All views are updated automatically when the model changes, it means that if you moving or editing shape in one of the views you will see changes in others.
Using canvas
Canvas
is implemented in Canvas.cs file in CanvasControl
namespace.
Example how to use it is in CanvasDemo.cs file.
To create a new Canvas
and connect to its events:
canvas1 = new CanvasControl.Canvas();
canvas1.CoordChangeEvent += new
CanvasControl.Canvas.CoordChangeEventHandler(OnCoordChainged);
canvas1.ItemNewEvent += new
CanvasControl.Canvas.ItemEventHandler(OnNewCanvasItem);
canvas1.ItemEnterEvent += new
CanvasControl.Canvas.ItemEventHandler(OnEnterCanvasItem);
where:
CoordChangeEvent
event is generated when cursor moves inside the Canvas
. A second parameter of the event handler CoordChangeEventHandler
contains coordinates of the cursor and R,G,B values of the background image.
public void OnCoordChainged(object sender,
CanvasControl.CoordEventArgs e)
{
textBox1.Text = e.X.ToString() + "," + e.Y.ToString();
}
ItemNewEvent
and ItemEnterEvent
� events are generated when cursor enters and leaves graphical object. The second parameter contains ID (index) of the object. Graphical object could be accessed by its ID.
public void OnEnterCanvasItem(object sender,
CanvasControl.ItemEventArgs e)
{
textBox3.Text = e.indx.ToString();
}
In order to create additional view of existing Canvas (its geometrical model) we should create second Canvas
, say canvas2
and attach it to previous one:
canvas2 = new CanvasControl.Canvas();
canvas2.setGeomModel(canvas1.getGeomModel());
Canvas
itself does not contain other graphical controls, it�s controlled by its API. But there is an option to connect Scrollbar controls to update scrollbars controls during canvas navigation.
canvas1.connectScrolls(hScrollBar1,vScrollBar1);
To create, move or select graphical objects, we need to change Canvas
state as follows:
canvas1.CreateRectangle();
canvas1.CreateLine();
canvas1.CreatePolygon();
canvas1.StartMoving();
canvas1.StartSelection();
Graphical object could be accessed by its ID and geometrical and other properties could be retrieved or updated. In the function bellow, ID-s of selected items are retrieved and updated with user defined color.
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();
}
You can create your own geometrical object, to do it, you should implement CanvasItem
interface (see Canvas.cs file) and use Canvas.AddShape
method to put it into the canvas.
Design issues
Several design patterns are used in the design of the canvas.
Template pattern is used in CanvasItem
class to implement some shapes common functionality in the base class.
Observer pattern is used to notify subscribers about canvas changes, it's implemented using C# events.
Facade pattern is used to hide internal canvas objects from the user.
In the class diagram above classes creating canvas contol are presented
Canvas
- encapsulates view of the canvas control (Model/View pattern). Canvas is syncronized with geometrical model CanvasGeomModel
via C# events. Every time the model had changed the model generates ItemChangedEvent
or ModelChangedEvent
forcing canvas update the view. Each time the view Canvas
needs to display the model, it calls draw
method of CanvasGeomModel
specifying graphical context (or painter object) Graphics
and a Matrix
transformaion between "client" coordinate system of view and "real world" coordinate system of the model. Eventually from CanvasGeomModel
draw request is delegated to concreat shapes, which implement CanvasItem
draw
interface to draw itself on the view.
CanvasGeomModel
- encapsulates geometrical model of the canvas control. It is responsable for storing, editing and displaying of different shapes, derived from CanvasItem
class. Geometrical objects are represented in the model in "real world" coordinates independently from "client" coordinates of different views.
CanvasItem
- encapsulates common behavior of geometrical objects or shapes stored in geometrical model CanvasGeomModel
. It represents interface using by Canvas
to manipulate and display differents types of shapes. In order to create new shape one should implement methods defined in CanvasItem
interface. public class LineCanvasItem : CanvasItem
{
public float x1;
public float y1;
public float x2;
public float y2;
private bool is_selected = false;
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;
}
public override bool isSelected()
{
return is_selected;
}
public override void select(bool m)
{
is_selected = m;
}
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;
}
public override RectangleF boundingBox()
{
return new RectangleF(x1,y1,x2-x1,y2-y1);
}
public override float X()
{
return x1;
}
public override float Y()
{
return y1;
}
public override void move(PointF p)
{
float dx = p.X-x1;
float dy = p.Y-y1;
x1 += dx;
y1 += dy;
x2 += dx;
y2 += dy;
}
public override void moveBy(float xs,float ys)
{
x1+=xs;
y1+=ys;
x2+=xs;
y2+=ys;
}
public override void draw(Graphics graph,
System.Drawing.Drawing2D.Matrix trans)
{
PointF [] points = {new PointF(x1,y1), new PointF(x2,y2)};
trans.TransformPoints(points);
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);
}
}
Then Canvas.AddShape
method should be used to add new shape to the canvas: canvas1.AddShape(new LineCanvasItem(10,10,100,100));
Geom
- Geometrical procedures used in Canvas
.
History
- 28/02/2004 Initial version.
- 17/03/2004 Geometrical model separated from canvas view + design explanations.