dBASE Novice Notes
Example of Preview.wfm
and Report Parameters
by Steve Hawkins
February 14, 2001 — with minor revisions on May 14, 2002

Author’s note:  The scope of this document is limited to providing a review and example of how I put Ken Mayer’s PREVIEW.WFM report viewing utility to use. Included is an example of passing parameters to a report either by directly printing or by using PREVIEW.WFM. It is assumed that you already know how to create forms and reports. If you are not at least a little familiar with the Report Designer, queries and rowsets, and object oriented concepts, please first review the knowledgebase files on these subjects, located at the dB2K website. I would also note as a disclaimer that some of the code used in this document was ‘borrowed’ from others on the dB2K newsgroup. A sincere thanks to all of the folks there.


Introduction

dB2K offers a built in “ReportViewer” class which is defined in the online help as “A control to display a report on a form”. It does what it says and will display a report (.REP file) on screen. It is not horribly functional on its own though, and putting it to proper use requires some enhancements. To make life easier for us, Ken Mayer created a form called Preview.wfm.

Preview.wfm uses a ReportViewer object and allows reports to be viewed on-screen. It also provides pushbuttons for printing and traversing pages and even allows for passing parameters to your report. Preview.wfm is among the many helpful, useful and downright handy pieces of dB2K code contained in the dBASE Users’ Function Library Project (dUFLP). The library is available for download from Ken Mayer’s web site.

As a novice dB2K user, I had a difficult time making my reports work with the PREVIEW utility when I wanted to pass parameters to the report. Then, one day, I finally “got it”. Since others have had many of the same questions as I, it seemed worthwhile to provide an example here in the dBulletin. Please keep in mind that I am a novice. This is my way of doing it, but better ways may exist. I hope you find the examples helpful.

What I wanted to accomplish

I am re-writing an old DOS application designed to keep track of video tapes for a video rental store. The data and the reporting needs are fairly simple. I wanted the user to have the following options:

The user dialog for making the selections is started by way of a menu selection and looks like this:


To see a sample PREVIEW form (from clicking the “Screen” button), click here.

To handle the pushbuttons, I only had to create four methods for this form.

Now let’s talk about the the individual methods in detail.

PBCancel_onClick()

The PBCancel_onClick() method doesn’t leave much to talk about. Since all it is supposed to do is close the form, I typed form.close() in the method. ’Nuff said.
 
 
Function PBCancel_onClick
   form.close()
   return
   

PBPrint_onClick()

The PBPrint_onClick() method is straightforward. It establishes an instance of the report, gets the parameter array, sends the array to the report by creating a report property called PARAMS, sets the report to go to the printer and then prints it. Comments in the code explain what each step does.
 
 
Function PBPrint_onClick
   // method of RepDialog.WFM
   local r, aParam

   // instantiate report for sending to printer
   set proc to tapelist.rep additive
   r = new tapelistreport()

   // get the parameter array to pass to the report
   aParams = class::ParamSetup()

   // pass aParams array to report
   // NOTE that the property name of 'params' in the report
   // is important.  This is the name of the property that
   // the report object will be looking for
   r.params = aParams

   // setting the output property to 1 directs the report to printer
   r.output = 1

   // print report
   r.render()

   return

   

PBPreview_onClick()

Like PBPrint_onClick(), the PBPreview_onClick() method gets a parameter array from ParamSetup(). However, it then passes the array along to the Preview.wfm form (rather than the report itself) and Preview.wfm takes over from there.
 
 
Function PBPreview_onClick
   // Method of RepDialog.wfm
   // Revised 01/28/2001

   // get the parameter array to pass to the report
   aParams = class::ParamSetup()

   // That's all we have to do. Preview.wfm takes care of the rest
   // Just pass the information to Preview.wfm (see below)

   // Note: I don't know why but calling PreviewForm()
   // this way doesn't seem to work!!!
   // -----------------------------
   // fPreview = new PreviewForm()
   // fPreview.viewer.filename = "tapelist.rep"
   // fPreview.params = aParams
   // fPreview.Open()

   // so instead, call it this way
   do preview.wfm with "tapelist.rep",true,aParams 

   // The parameters above are:
   // "Tapelist.rep" is the name of the report to run
   // "true" is the modal flag
   // "aParam" is the parameter array

   return

   

ParamSetup()

I allow the user to send the report directly to the printer or to view the report using Preview.wfm. Since both PBPrint_onClick() and PBPreview_onClick() require the same array setup, I’m using ParamSetup() to fill the bill. If you were offering only a single choice (just the ‘Screen’ pushbutton for example), you wouldn’t need a separate setup method. You could instead just include the ParamSetup() steps within the onClick() method for the ‘Screen’ pushbutton.

First, ParamSetup() establishes an array to contain the parameters. (Please note: The ReportViewer base class uses an associative array by default. For compatibility, ParamSetup() also uses an associative array.)

Next, ParamSetup() checks the group of radio buttons on the form to determine how the user wants to sort the report data.

Then, it changes the text of the report title based on this information.

Last, it looks at the date entered by the user (if any). If available, it sets the value of the RecDateStart array element to use as a filter). Easy to understand, yes? I hope so. If not, please drop me an email at databoy_dBulletin_@sehawk.com (take out “_dBulletin_”) to let me know what you don’t understand. I’ll try to explain it more thoroughly and I’ll use your comments to refine this page.
 
 
Function ParamSetup()
   // Method of RepDialog.wfm
   // this sets up the parameter array and passes it back to 
   // the calling method [PBPrint_onClick() or PBPreview_onClick()]

   // setup associative array for passing to tapelist.rep
   aParams = new assocarray()
   aParams ["Title"] = "Video Listing - Sorted by "
   aParams ["RecDateStart"]= ctod("  /  /  ")
   aParams ["SortChoice"] = ""

   // Set value of array's "SortChoice" element
   // according to sort order radio button selected
   DO CASE 
      Case form.RBTAPENUM.value    // sort by tapenumber
         aParams ["SortChoice"] := "Tape Number"
      Case form.RBRECDATE.value    // sort by rec date
         aParams ["SortChoice"] := "Date"
      Otherwise                    // sort by title
         aParams ["SortChoice"] := "Title"
   EndCase

   // Set title according to above choice
   aParams ["Title"] += aParams ["SortChoice"]

   // Set date filter element in aParams
   // If user didn't enter a date, set the value to null,
   // othewise, use the date that was entered
   // ---------------------------
   // Note that you should have some method of validating the date
   aParams ["RecDateStart"] := ;
      iif (empty(form.entryfield1.value),null,form.entryfield1.value)

   return aParams

   

Ok, we’re almost done. The last thing I need to talk about is the report itself and how it uses the parameter array to change the report to your liking. Report objects have a built-in method called Render(). This method, when called, simply prints the report by default. It doesn’t check for any kind of parameters passed by the user. So, in order to make this parameter array work, we need to create our own version of Render().Our version of Render() does check for parameters and changes report properties accordingly.

What I want to do is to get my version of Render() to run just once, when the report starts up. This allows me to set and/or change properties of the report. After all the report properties are set to my liking, my version of Render() no longer need to execute though the default/built-in Render() does. We accomplish this by writing code that allows my version of Render() to execute only once while allowing the default Render() to execute as many times as is necessary.

I began by overriding the default Render() method. In brief, to do this, open the report in design mode and click on the Inspector. Make sure the report itself is selected in the Inspector and not one of the elements of the report. Then select the Methods tab. There, you should see the default Render() method in the list of methods. Select this method and click on the wrench to the right of it. This should open the source editor and allow you to type your own custom Form_Render() method. Here is what my Form_Render() method looks like. Don’t forget that this method is in the REPORT (.rep), not in the report dialog form like the previous methods.
 
 
Function Form_Render
   // method of TapeList.rep
   // overridden render method
   // We only want this code to run once (just for the filter setup, etc).
   // Note that except for the call to super::render() at the bottom,
   // this code only runs if hasn't been called previously

   // Check for existence of the runOnce logical property
   // to see if the code has been run yet
   if type ( "this.runOnce") # "L"
     this.runOnce = true

      // Check for existence of a ReportViewer object
     if type ( "this.reportViewer") == "O"
         // must be a reportViewer that called this report
         // check to see if the reportViewer's form has a params array
        if type( "this.reportViewer.form.params") == "O"
            // set the report's "params" array
            this.params = this.reportViewer.form.params
        endif

     endif

      // if we have a params array, we need to set parameters for the report
     if type ( "this.params") == "O"
         // Creating abbreviated references to rowset and "SortChoice"
         // just saves a few keystrokes later in this method
        this.rSet  = form.stockdatamodule1.stock1.rowset
         this.cSort = this.params["SortChoice"]
         // set the main title
         this.PAGETEMPLATE1.TEXT1.text := this.params["Title"]

         if this.params ["RecDateStart"] # null
            // If not null, a filter date was entered by user
            dStartDate = this.params['RecDateStart']
            form.stockdatamodule1.stock1.rowset.filter := ;
               [date_rec > ' ] + dStartDate + [ ' ]

            // Add subtitle listing presence of filter
            this.PAGETEMPLATE1.FILTBYTEXT.text := ;
               "Items dated after "+ dStartDate
         endif  // if RecDateStart # null

         if this.cSort # null
            // is the same as 'If this.params["SortChoice"] # null'
            Do Case
               Case this.cSort == "Title"
                  // set index to Title
                  this.rSet.indexname := "StockName"
               Case this.cSort == "Tape Number"
                  // set index to tape number
                  this.rSet.indexname := "StockNum"
               Case this.cSort == "Date"
                  // set index to date recorded
                  this.rSet.indexname := "Date_Rec"
               Otherwise
                  // invalid "SortChoice" sent
                  msgbox("OOP....wrong index parameter")
                  this.rSet.indexname := "StockName"
            EndCase

         endif  // if params SortChoice # null
      endif     // if type ( "this.params") == "O"
   endif        // if type this.runonce # L

   return super::render()

   

This is a somewhat long method but really not too difficult to understand if we break it down into separate parts. Once you get past the this.runOnce part, here is what happens:

I hope this at least gives you some ideas on how you can put Preview.wfm to work if you want to use it. If not, it at least gives you an idea on how to get print criteria from a user and implement it in your report.

This is my first attempt at writing any kind of technical document. So, if you have any questions in reference to this document or if you wish to offer constructive criticism as to how I could make it better, please drop me an email at: databoy AT sehawk DOT com. I would like to hear from you.

Please note that I cannot supply any kind of technical support for dB2K or any other software product. Nor can I offer general programming assistance except for that which I might post in the dB2K newsgroups.

Steve Hawkins
SeHawk Services
dBASE Plus - the only way to fly


Note: The author would like to thank his proofreaders, David L. Stone and Nate Salsbury, for the improvements they brought to this text.