Gaining Image Control in Reports

This article was first published in FoxTalk  in 2006. As far as I know, it is not publicly available  elsewhere-but-here now. It contains essential information about a feature that was added late in RTM, and is therefore scantily documented. The (very limited source) for this article is available in Spacefold downloads as BlobImageSource.zip.

Eugen Wirsing and Zaheer Mohammad both requested a bit of help when working with image controls in reports. This feature, added late in the VFP 9.0 development cycle, has only minimal documentation (see How to: Add Pictures to Reports). Some clarification is obviously in order.

When two experienced VFP developers ask me almost the same question within months of each other, it always makes me think harder about that question. I'll discuss their initial points of confusion, but I'll also add some general advice that may help you in related scenarios.

Where does the object reference go?

Having read MSDN KB article 895602, Zaheer Mohammad was unsure how to associate the object reference of the image control with the layout control representing the picture in the report. It's no wonder he was confused; the KB article adds this row to the FRX table programmatically.

In the ReportBuilder's Picture/OLE Bound Properties dialog, use a control source type of Expression or variable name. Place a variable name that will be in scope during the report run, and into which you'll place the reference to the image control, in the Control source expression.

In Figure 1, I've used a report variable named BlobImage for this purpose, but you can use a _VFP member property, a member property of your application object, or anything you prefer, as long as it has appropriate scope. (The KB article uses a member property of the running ReportListener, loRL.oBlobImage, for this purpose, where loRL is a variable declared LOCAL in the calling program. Even if this worked, ordinarily you don't tie programs or FRX expressions to LOCAL variables declared elsewhere.)

figure 1
Figure 1. The control source for a picture hosted by an image control is a reference to that image control.

In your sample code, a report variable scopes the image control to the report form. As you see in Figure 2, I've initialized this variable with a reference to a VFP baseclass image and I've used the variable's own name as its Value to store.

figure 2
Figure 2. You can use a report variable to hold the required object reference.

Caveat: object references and dynamic ReportListener code

I've used a report variable in your sample code and, in most cases, I recommend that you use this simple approach to scope your object reference to the report. However, if you are using a ReportListener to render your report and if your ReportListener or its class hierarchy includes any EvaluateContents or AdjustObjectSize code, be aware that a bug in VFP 9 RTM and SP1 may cause the object reference held in a report variable to be destroyed. Use an externally-declared variable or member property instead. If you're using the FFC ReportListener classes or the standard REPORTOUTPUT.APP mechanism, this issue does not affect you. The RTM and SP1 versions of these classes do not include any code in these two methods.

How do you script dynamic changes to the image?

Once you've established the correct object reference for your image control, use its PictureVal property to hold binary data representing the image you wish to display. You can get this string using FILETOSTR() on an image file on disk, or from a Blob field to which you've previously stored the result of a similar FILETOSTR() function call. In your sample code, you run ShowBlobData.PRG, which creates a cursor with a Blob field for you, before displaying the contents of this cursor in the sample report.

The cursor contains multiple records, each of which stores a string representing a different image file, so you need to change the contents of your ImageControl.PictureVal property during the report run. This was the part that puzzled Eugen Wirsing.

How often, and in what report band, you display the image determines where you put the code to re-load the PictureVal contents. In my sample report, the Blob field is displayed once for each detail band, so I used the detail band's On Entry Run expression to store the new value; in other cases, you might display it in a page header, or on a group level, using appropriate reporting events.

When you examine the Detail Band Properties dialog, you'll find the following code in its On Entry run expression:

        EXECSCRIPT("BlobImage.PictureVal = MyCursor.BlobField")

You can also place the same line of code you see in the EXECSCRIPT function call above in a ReportListener' event, but this means you're dependent on a specific ReportListener-derived class to process your report. If you don't like the idea of embedding the code in the FRX, I think you're better off using a UDF, holding the same line of code, to do the work. You can even run a UDF assigning the new value using the report variable's Value to store expression, rather than using a separate band event, like this:

        IIF(MyUDF(),BlobImage, BlobImage)

The one thing you can't do is place the line of code directly in a band's On Entry or On Exit run expression, without embedding it in EXECSCRIPT(), a UDF, or a ReportListener event. When placed directly in a run expression, BlobImage.PictureVal = MyCursor.BlobField appears to VFP only as an expression to be evaluated, not a value assignment. The expression evaluates to .T. or .F. and — just like any return value, of any type, that you might return from a UDF in a band's run expression — this value is discarded.

Why is this better than General fields?

When you use a Image.PictureVal reference for your report images, you get a data-driven, dynamic display of pictures rather than a static image file, just as you do when you bind a report layout control to a General field — but there are some important differences.

First, you don't necessarily have to store the Blob data in your table if you don't want it there. You can store only the filenames, using whatever relative pathing scheme appeals to you, and load directly using FILETOSTR() during the report run:

BlobImage.PictureVal=FILETOSTR(MyCursor.FileNameFld)
    

It has always been possible to do something similar, using indirect references to image files, or a hidden form with an OLE Bound control, but it took extra work to ensure that images weren't inappropriately cached.

More significantly, General fields do not benefit from the high resolution rendering available in VFP 9's object-assisted reporting mode; they alone are rendered at 96DPI no matter how good your original image was or how high a resolution your printer can display. This is a significant concern when, as in Zaheer Mohammad's case, your report output includes bar code images.

Using image controls as picture control sources for reports combines the best of both worlds: the dynamic capabilities of General field-sourced images with the rendering fidelity of file-sourced images.

You don't need a ReportListener

Although the documentation states that results aren't guaranteed when you SET REPORTBEHAVIOR 80, in fact for quick previewing purposes it often works very well.  In SP2 we've introduced the idea of draft mode to describe old-style reporting, and it's often a good idea to use this mode to preview often and easily while you are in the early stages of report design.

You can run the sample program with REPORTBEHAVIOR at both settings, to verify this. You won't get the advantages of high-resolution output when you SET REPORTBEHAVIOR 80, of course, and the images may not print properly in this mode.