Canvas

During my programming career, I’ve ran into several cases where I absolutely had to use owner-draw to accomplish customized drawing. Originally, as a Delphi programmer, this required knowledge of the specific graphic wrappers around the Windows GDI (and GDI+) functions which Borland (appropriately) called TCanvas. The C#/.Net equivalent is called ‘Graphics’, which (admittingly) does not sound as fancy as Canvas.

That said: in my never-ending quest to fill a niche craving, I decided to look into the basics of photo-editing; that is, on a much smaller scale. The first step was to create a component that (given a specific image), drops a frame on it, which you can use to crop a photo (by either moving it and/or resizing it). Additionally, I always liked how some photo-editors integrate the Rule Of Thirds during cropping of photos, so, that had to be part of the custom-draw routine too.

There are couple of common tasks that need to be taken care of when doing own-draw stuff, all in C# (however, should be similar in Delphi):

  • The first thing is to decide which control you’re going to ‘descend’ from, or rather, which control is going to be your base-class
  • If your control requires user-interaction (i.e. mouse/key input), you should probably override the control’s MouseUp/Down and MouseMove events. Most likely you’ll need a couple of (private) flags that track down if a mouse button is still ‘pressed’. Add to that a couple of variables that track down the last positions clicked on the screen.
  • Separate the drawing routines and call these routines from an overridden OnPaint event.
  • Debugging (owner-draw) graphical routines is extremely painful, so think through your drawing routines.

Boring sample code is about to follow.

The order of processing:

  • Calculate the actual needed rectangle (deflate it by one pixel)
  • Draw the main rectangle (2 points thick)
  • Draw the ‘Rule of Thirds’ lines.
  • Draw the text for size and location of the current frame.
  • Draw the ‘size block’ in the bottom-right corner of the frame (we do a hittest for this region in the OnMouseDown event).

       protected virtual void DrawFrameBox(PaintEventArgs pe) {
            Graphics pes = pe.Graphics;
            Pen pen = new Pen(this.bordercolor);
            Pen lpen = new Pen(this.bordercolor);
            
            try {
                Rectangle nrect = Rectangle.Inflate(this.ClientRectangle, -1, -1);
                pen.DashStyle = 
                    Functions.ConvertFrameStyleToDashStyle(this.framestyle);
                pen.Width = 2f;
               
                pes.DrawRectangle(pen, nrect);

                lpen.DashStyle =
                    Functions.ConvertFrameStyleToDashStyle(this.framestyle);
                lpen.Width = 1f;


                int x1 = (int) ((1f / 3f) * (float)nrect.Width);
                int x2 = (int)((2f / 3f) * (float)nrect.Width);
                int y1 = (int) ((1f / 3f) * (float)nrect.Height);
                int y2 = (int) ((2f / 3f) * (float)nrect.Height);
                
                //Draw lines...
                pes.DrawLine(lpen, x1, nrect.Top, x1, nrect.Bottom);
                pes.DrawLine(lpen, x2, nrect.Top, x2, nrect.Bottom);
                pes.DrawLine(lpen, nrect.Left, y1, nrect.Width, y1);
                pes.DrawLine(lpen, nrect.Left, y2, nrect.Width, y2);

                String s = String.Format("Loc:{0},{1}", this.Top, this.Left);                
                pes.DrawString(s, new Font("Arial", 8), new SolidBrush(this.bordercolor), new PointF(0, 0));
                
                s = String.Format("Sz: {0}x{1}", this.Width, this.Height);
                pes.DrawString(s, new Font("Arial", 8), new SolidBrush(this.bordercolor), new PointF(0, 10));

                // Draw sizeblock...
                Rectangle sizerect = new Rectangle(nrect.Width - 10, nrect.Height - 10, 10, 10);
                pes.DrawRectangle(lpen, sizerect);

            } finally {
                lpen.Dispose();
                pen.Dispose();                
            }
        }

Setting the size and position of the selection frame happens in the appropriate mouse and key-events: If you make sure that the proper flags and variables are set in place and the calculations are correct, you should not have a problem having your control draw itself like you want it to.