Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Canvas implementation for C#

0.00/5 (No votes)
21 Mar 2004 1  
Design and implementation of canvas in C#

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:

// 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);

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:

// create graphical objects

canvas1.CreateRectangle();

canvas1.CreateLine();

canvas1.CreatePolygon();

// move objects

canvas1.StartMoving();
// select objects

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 CanvasGeomModelvia 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;
    
        // 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);
        }
      }
            
    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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here