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.