Creating a Custom Preview Container

Describes how to replace the default preview container with your own custom component that can provide report preview functionality throughout your application automatically when you SET REPORTBEHAVIOR 90.

Prerequisites

ReportListener Object

The Preview Container API

The Simplest Preview

The simplest possible preview example does not use the Preview Container API at all, but uses methods of the ReportListener object to demonstrate the essential mechanism behind the API.

The following commands set up a target Shape object, run a report in object-assisted mode, and use the methods of the ReportListener object to display the first page of the report on the target shape:

* Set up a target for the preview rendering:
_SCREEN.AddObject("canvas","Shape")
_SCREEN.canvas.Width  = 250
_SCREEN.canvas.Height = 300

* Create a base ReportListener and buffer the entire report:
rl = NEWOBJECT("ReportListener")
rl.ListenerType = 3  && Buffer all pages, do not preview automatically

* Process the report:
REPORT FORM (_SAMPLES+"\solution\reports\colors.frx") OBJECT rl

* Render page 1 to the target:
rl.OutputPage( 1, _SCREEN.canvas, 2 )

After executing these statements in the Command Window, you will see a thumbnail-size image of the report rendered on the surface of the screen.

Things to note:

  • The third parameter passed to the ReportListener's OutputPage() method is 2, which indicates that the target handle passed in the second parameter is a Visual FoxPro object reference - the Shape control.

  • The Shape object did not have to be visible in order for the report to display.

  • Either a Container or a Shape control can be used as the target.

  • Dragging the command window over the preview erases it from the screen, because when the screen receives a paint message, it repaints itself with no knowledge of the preview rendering.

Implementing a Simple Preview Container

The next step is to encapsulate the process described above into a class that implements the preview container API, allowing you to re-use the previewing logic.

The following example program, simplepreview.prg, contains a class definition which also uses a Shape object as the render target, this time on a form class that implements the preview container API:

*-----------------------------------------
* simplepreview.prg
*-----------------------------------------
DEFINE CLASS SimplePreview AS Form
    Caption     = "Click for next page"
    ListenerRef = .NULL.
    PageNo      = 1
    AllowOutput = .F.

    ADD OBJECT Canvas AS Shape WITH ;
        Top = 12, Left = 8, ;
        Height = 252, Width = 209, ;
        Name = "Canvas"

    PROCEDURE Canvas.Click
        WITH THISFORM
            IF .PageNo < .ListenerRef.OutputPageCount
                .PageNo = .PageNo + 1
                .Refresh()
            ENDIF
        ENDWITH
    ENDPROC

    PROCEDURE SetReport
        LPARAMETER oListenerRef
        THIS.ListenerRef = oListenerRef
    ENDPROC

    PROCEDURE QueryUnload
        IF NOT ISNULL( THIS.ListenerRef )
            THIS.ListenerRef.OnPreviewClose(.F.)
            THIS.ListenerRef = .NULL.
        ENDIF
        THIS.Hide()
        NODEFAULT
    ENDPROC

    PROCEDURE Paint
        IF NOT ISNULL( THIS.ListenerRef )
            THIS.ListenerRef.OutputPage( THIS.PageNo, THIS.Canvas, 2 )
        ENDIF
    ENDPROC

ENDDEFINE

This class implements the two methods of the preview container API by explicitly defining a SetReport() method; and by being derived from the Form base class, which already has an appropriate Show() method.

Things to note:

  • This class resolves the repainting issues of the previous example by adding code to the form's Paint() event to ensure that the preview image is redrawn when required.

  • The Reportlistener object will call SetReport(), passing a reference to itself when it requires the preview container to perform basic non-report-specific initialization. SetReport() will also be called with null (.NULL.) when its OnPreviewClose() method is invoked.

  • The code in the QueryUnload() event informs the report listener that you have finished viewing the report, and then nulls out the reference to the report listener object to avoid a dead-lock condition that would prevent the form from closing.

  • The code in the Shape.Click() method uses the report listener's OutputPageCount property to ensure that the preview does not request a page that does not exist.

The following code demonstrates how to re-use a preview container component, by instantiating the SimplePreview class and assigning it to the PreviewContainer property of a ReportListener object. Try in the Command Window:

pc = NEWOBJECT("SimplePreview", "simplepreview.prg")
rl = NEWOBJECT("ReportListener")
rl.ListenerType     = 1 && Buffer all pages, use preview container
rl.PreviewContainer = pc
REPORT FORM (_SAMPLES+"\solution\reports\colors.frx") OBJECT rl
REPORT FORM (HOME()+"Tools\Filespec\60frx2.frx") OBJECT rl

After running this code, you will observe that the SimplePreview form is displayed automatically. This is because the report listener object invoked the PreviewContainer.Show() method after the report has completed processing.

If you change the last line of the example above to include the NOWAIT clause, you will observe that the preview form is no longer modal when it is displayed. The report listener object recognizes the NOWAIT clause and invokes Show(0) instead of Show(1).

Hide or Release?

  • In SimplePreview.prg above, the QueryUnload() event suppresses default behavior with NODEFAULT and manually hides the form by calling Hide().

This is for two reasons:

  • There is an outstanding reference to the form held by the report listener PreviewContainer property which means that, without additional code, the form won't close and release when you click the close box. The close box will disable and the form will stay visible.

  • The Hide() allows the preview container to be re-used in successive report previews, as the previous example code shows.

If you do not wish your custom preview container to stay available for further report previews with the report listener, you can modify the QueryUnload method as shown below:

    PROCEDURE QueryUnload
        IF NOT ISNULL( THIS.ListenerRef )
            THIS.ListenerRef.PreviewContainer = .NULL.
            THIS.ListenerRef.OnPreviewClose(.F.)
            THIS.ListenerRef = .NULL.
        ENDIF
    ENDPROC

This alternative implementation results in the preview container being discarded after one report preview. Try the steps above again in the Command Window with the revised preview container class, and you will see that only the first preview uses SimplePreview. The second report run reverts to the default preview container because the ReportListener object detects that it no longer has a reference in its PreviewContainer property and requests a new one via _REPORTPREVIEW.

Scaling and Printing

Scaling

In the previous example, the dimensions of the rendered preview page are dictated solely by the size of the Shape control. If you change the Width of the Shape to be twice its current size, then you will observe that the preview images is similarly distorted.

You can obtain additional information about the report layout from the ReportListener reference in order to scale the page representation appropriately. Add the following method code to the class definition above:

    PROCEDURE Show
        LPARAMETER iMode
        IF NOT ISNULL( THIS.ListenerRef )
            LOCAL nWidthInches, nHeightInches
            nWidthInches  = THIS.ListenerRef.GetPageWidth()/960
            nHeightInches = THIS.ListenerRef.GetPageHeight()/960
            * Assume: Scale by 50% on a 96 DPI screen:
            THIS.Canvas.Height = INT( nHeightInches * 96 * 0.5 )
            THIS.Canvas.Width  = INT( nWidthInches * 96 * 0.5 )
        ENDIF
        DODEFAULT( iMode )
    ENDPROC

This is the appropriate place for this code because the ReportListener instance is guaranteed to invoke the preview container's Show() method at a point where the GetPageHeight() and GetPageWidth() methods will return correct values for the report being previewed. The SetReport() method will be called before accurate information is available.

Printing

You can specify that the report be printed by passing a parameter of .T. to the report listener's OnPreviewClose() method as the form is closed.

Setting as Default

You can replace the default preview container used by object-assisted reporting preview in Visual FoxPro by adding the following lines to the top of the program containing the SimplePreview class definition introduced earlier in this topic:

*-----------------------------------------
* Add to the top  of simplepreview.prg
*-----------------------------------------
LPARAMETER loPreviewContainerRef
* parameter is passed by reference
loPreviewContainerRef = CREATEOBJECT("SimplePreview")
RETURN

DEFINE CLASS SimplePreview AS Form
...

This program is now suitable for assigning to _REPORTPREVIEW, specifying the preview container to be returned to Visual FoxPro whenever one is requested by a ReportListener object performing a REPORT… PREVIEW in object-assisted mode.

You can test it out in the Command Window:

_REPORTPREVIEW = "simplepreview.prg"
SET REPORTBEHAVIOR 90
REPORT FORM (HOME()+"Tools\Filespec\60frx2.frx") preview

Next Steps

See Leveraging the Default Preview Container

See Also

Reference

ReportListener Object
_REPORTOUTPUT System Variable
_REPORTPREVIEW System Variable
OutputPage Method
Container Object
Shape Control