ActiveX Controls: Painting an ActiveX Control

Visual Studio 6.0 Do IFAQ

This article describes the ActiveX control painting process and how you can alter paint code to optimize the process. (See Optimizing Control Drawing for techniques on how to optimize drawing by not having controls individually restore previously selected GDI objects. After all of the controls have been drawn, the container can automatically restore the original objects.)

Examples in this article are from a control created by ControlWizard with default settings. For more information on creating a skeleton control application using ControlWizard, see the article Createa Program with the MFC ActiveX ControlWizard.

The following topics are covered:

The Painting Process of an ActiveX Control

When ActiveX controls are initially displayed or are redrawn, they follow a painting process similar to other applications developed using MFC, with one important distinction: ActiveX controls can be in an active or an inactive state.

An active control is represented in an ActiveX control container by a child window. Like other windows, it is responsible for painting itself when a WM_PAINT message is received. The control’s base class, , handles this message in its OnPaint function. This default implementation calls the OnDraw function of your control.

An inactive control is painted differently. When the control is inactive, its window is either invisible or nonexistent, so it can not receive a paint message. Instead, the control container directly calls the OnDraw function of the control. This differs from an active control’s painting process in that the OnPaint member function is never called.

As discussed in the preceding paragraphs, how an ActiveX control is updated depends on the state of the control. However, because the framework calls the OnDraw member function in both cases, you add the majority of your painting code in this member function.

The OnDraw member function handles control painting. When a control is inactive, the control container calls OnDraw, passing the device context of the control container and the coordinates of the rectangular area occupied by the control.

The rectangle passed by the framework to the OnDraw member function contains the area occupied by the control. If the control is active, the upper-left corner is (0, 0) and the device context passed is for the child window that contains the control. If the control is inactive, the upper-left coordinate is not necessarily (0, 0) and the device context passed is for the control container containing the control.

Note It is important that your modifications to OnDraw do not depend on the rectangle’s upper-left point being equal to (0, 0) and that you draw only inside the rectangle passed to OnDraw. Unexpected results can occur if you draw beyond the rectangle’s area.

The default implementation provided by ControlWizard in the control implementation file (.CPP), shown below, paints the rectangle with a white brush and fills the ellipse with the current background color.

void CSampleCtrl::OnDraw( CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid )
    pdc->FillRect( rcBounds,
    pdc->Ellipse( rcBounds );

Note   When painting a control, you should not make assumptions about the state of the device context that is passed as the pdc parameter to the OnDraw function. Occasionally the device context is supplied by the container application and will not necessarily be initialized to the default state. In particular, explicitly select the pens, brushes, colors, fonts, and other resources that your drawing code depends upon.

Optimizing Your Paint Code

Once the control is successfully painting itself, the next step is to optimize the OnDraw function.

The default implementation of ActiveX control painting simply paints the entire control area. This is sufficient for simple controls, but in many cases repainting the control would be faster if only the portion that needed updating was repainted, instead of the entire control.

The OnDraw function provides an easy method of optimization by passing rcInvalid, the rectangular area of the control that needs redrawing. Use this area, usually smaller than the entire control area, to speed up the painting process.

Painting Your Control Using Metafiles

In most cases the pdc parameter to the OnDraw function points to a screen device context (DC). However, when printing images of the control or during a print preview session, the DC received for rendering is a special type called a “metafile DC”. Unlike a screen DC, which immediately handles requests sent to it, a metafile DC stores requests to be played back at a later time. Some container applications, such as Microsoft Access 2.0, may also choose to render the control image using a metafile DC when in design mode.

Drawing requests can be made by the container through two interface functions: IViewObject::Draw (this function can also be called for non-metafile drawing) and IDataObject::GetData. When a metafile DC is passed as one of the parameters, the MFC framework makes a call to . Because this is a virtual member function, override this function in the control class to do any special processing. The default behavior calls COleControl::OnDraw.

To make sure the control can be drawn in both screen and metafile device contexts, you must use only member functions that are supported in both a screen and a metafile DC. Be aware that the coordinate system may not be measured in pixels.

Because the default implementation of OnDrawMetafile calls the control’s OnDraw function, use only member functions that are suitable for both a metafile and a screen device context, unless you override OnDrawMetafile. The following lists the subset of CDC member functions that can be used in both a metafile and a screen device context. For more information on these functions, see class in the Class Library Reference.

Arc Pie SetMapMode
Chord Polygon SetMapperFlags
Ellipse Polyline SetPixel
Escape PolyPolygon SetPolyFillMode
BibBlt RealizePalette SetROP2
ExcludeClipRect RestoreDC SetStretchBltMode
ExtTextOut RoundRect SetTextColor
FloodFill SaveDC SetTextJustification
IntersectClipRect ScaleViewportExt SetViewportExt
LineTo ScaleWindowExt SetViewportOrg
MoveTo SelectClipRgn SetWindowExt
OffsetClipRgn SelectObject SetWindowORg
OffsetViewportOrg SelectPalette StretchBlt
OffsetWindowOrg SetBkColor TextOut
PatBlt SetBkMode

In addition to CDC member functions, there are several other functions that are compatible in a metafile DC. These include , , and three member functions of CBrush: , , and .

Another point to consider when using a metafile DC is that the coordinate system may not be measured in pixels. For this reason, all your drawing code should be adjusted to fit in the rectangle passed to OnDraw in the rcBounds parameter. This prevents accidental painting outside the control because rcBounds represents the size of the control’s window.

Once you have implemented metafile rendering for the control, use Test Container to test the metafile.

To test the control’s metafile using Test Container

  1. On the Tools menu, click ActiveX Control Test Container.

  2. On the Test Container’s Edit menu, click Insert ActiveX Control.

  3. In the Insert ActiveX Control box, select the desired control and click OK.

    The control will appear in Test container.

  4. On the Control menu, click Draw Metafile.

    A separate window appears in which the metafile is displayed. You can change the size of this window to see how scaling affects the control’s metafile. You can close this window at any time.