IViewObject2::Draw

Because the Draw member function encompasses such rich functionality, it probably has the longest argument list in all of OLE. It allows a client to tell an object exactly what to draw (dwAspect, lindex, pvAspect), where to draw it (hDC, prcBounds, prcWBounds), and how to draw it (ptd, hicTargetDev). In addition, a client can supply a callback function to break out of long repaints (pfnContinue and dwContinue). We'll first look at the basic form of Draw and then at the specific areas of handling a device, drawing into a metafile, and breaking out of long repaints.

The simplest way to call Draw is expressed in the following code:


pIViewObject2->Draw(DVASPECT_CONTENT, -1, NULL, NULL, 0, hDC, &rcBounds
, NULL, NULL, 0);

This statement says, "Draw the full rendering of the object (DVASPECT_CONTENT, -1) in this rectangle (rcBounds) on this device context (hDC)." The object draws its full-content rendering (whatever it is) directly to hDC, scaled to fit into rcBounds. The rectangle must be expressed in the current mapping mode of hDC, which can be a screen, a printer, a metafile, or a memory device context. An object that typically generates bitmaps can implement this function with a simple StretchBlt call; if it normally uses a metafile, it can call PlayMetafile after setting the extents properly for the rectangle. The object is also required (as common sense would dictate) to leave hDC in the same state as it was received. If the object needs to change mapping modes or another state of hDC, it must call the Windows API SaveDC on entry to Draw and match it with RestoreDC on exit.

Again, the RECTL pointed to by prcBounds specifies the scaling rectangle in units appropriate to hDC. This is not a clipping rectangle: the object is required to draw its full presentation into this rectangle. Where the scaling percentage is the ratio of the prcBounds dimensions to the object's extents:

prcBounds ->right - prcBounds ->left

xScale = -------------------------------------------------------------- * 100%

xExtent

prcBounds ->bottom - prcBounds ->top

yScale = ----------------------------------------------------------------- * 100%

yExtent

If the client actually wants a clipped rendering, it must create a clipping rectangle, select it into hDC before calling Draw, and specify prcBounds so that the intersection of the clipping rectangle and prcBounds results in the correct viewing window, as shown in Figure 11-1. In short, clipping is always the client's concern, never the viewable object's.

Figure 11-1.

Clipping through IViewObject2::Draw is achieved through a combination of a prcBounds and a clipping rectangle selected into hDC.

Because a client often wants no more than to specify the aspect, the device context, and the bounding rectangle for any drawing operation, OLE provides the API function OleDraw, whose implementation is as follows:


STDAPI OleDraw(LPUNKNOWN pIUnknown, DWORD dwAspect, HDC hDC
, LPCRECTL prcBounds);
{
HRESULT hr;
LPVIEWOBJECT pIViewObject;

if (NULL!=pIUnknown)
{
hr=pIUnknown->QueryInterface(IID_IViewObject
, (VOID **)&pIViewObject);

if (SUCCEEDED(hr))
{
pIViewObject2->Draw(dwAspect, -1, NULL, NULL, 0
, hDC, prcBounds, NULL, NULL, 0);

pIViewObject2->Release();
}
}

return;
}

This API function is a convenient wrapper because a client does not need to query for IViewObject or hold onto some IViewObject pointer for later drawing. But OleDraw doesn't help in cases in which you want more control over the exact rendering. In those cases—rendering for a specific device, drawing into a metafile, and stopping long repaints—you can always query for IViewObject[2] and call Draw.

Rendering for a Specific Device

Many applications, especially high-end graphics and desktop publishing packages, are concerned about getting both the highest quality and the fastest possible output on a printer. Two arguments to Draw allow applications to tell a viewable object about the intended device:

There are two primary cases in which a client might pass non-NULL values in these arguments. The first and most obvious occurs when hDC is a printer device context and you want the objects to render as accurately as possible for that printer. In this case, you can describe the printer device in ptd and pass either an information context you have on hand in hicTargetDev or pass your hDC as this argument. (A device context is an information context.) When ptd is non-NULL, a client must pass something in hicTargetDev to tell the object that the device context in hDC is for a printer. As an example of when you might use these arguments, consider printing to a PostScript printer for an object that understands PostScript directly. The object, knowing that hDC is a real printer device context, could send PostScript commands directly to the printer (through the Windows Escape function) instead of calling Windows GDI functions. The result is better performance and highly optimized output.

The second use of these arguments is for situations such as print preview, in which the client wants the object to draw to the screen what that object would draw to the printer. In this case, ptd points to a valid DVTARGETDEVICE, but hicTargetDev is NULL. This means that the object should call GDI functions on hDC to draw itself but should use colors and resolution appropriate for the device described by ptd. For example, an object that normally shows a magenta shading on the screen would print a dither pattern on a black-and-white printer. In this situation, the object creates its own information context according to ptd in order to know the color capability of that device and in order to modify its output to hDC accordingly.

Drawing into a Metafile

A metafile device context is a rather special beast when it comes to drawing the object at a specific location within that metafile. The object in this case needs to know both the window extents and the window origin of the hDC to draw itself in the correct location within the scope of the entire metafile. To do this, the prcWBounds argument to Draw contains the window extent and the window origin for the metafile, not a real rectangle. This argument will be non-NULL only if hDC is a metafile device context.

The origin is the point (prcWBounds->left, prcWBounds->top). The horizontal extent is prcWBounds->right, and the vertical extent is prcWBounds->bottom. To account for these values, a viewable object must offset its GDI calls into the metafile by the origin coordinates and must scale the points it passes to those GDI calls according to the ratio of the window extents to the object's own extents. Do not call the Windows API SetWindowsOrgEx or SetWindowExtEx for this purpose.

The prcWBounds argument really matters when an object plays another metafile as part of its own rendering. Usually each record in such a metafile will assume a certain origin and extent, so to account for those assumptions the object must enumerate the metafile and massage each record individually to modify the origin and scaling appropriately.

Stopping Long Repaints

The pfnContinue and dwContinue arguments to IViewObject2::Draw give a client the ability to terminate long repaints for complicated drawings:

A typical client implements the continue function to test the Esc key status:


BOOL CALLBACK ContinuePaint(DWORD dwContinue)
{
return !(GetAsyncKeyState(VK_ESCAPE) < 0);
}

How often this function is actually called depends on the object and how it draws itself. As a general guideline, an object should call the function once for every 16 operations, an operation being a GDI call or the playing of a metafile record. Obviously this will not work to break out of a single, time-consuming call to StretchBlt, but if the object draws more than one large bitmap, it should call the function after each BitBlt or StretchBlt. A really kind object might even try to draw a large bitmap in separate bands. In any case, call the continuation function if possible, and if the object draws quickly, you can ignore it altogether.