dB2K Novice Notes |
An Example of Preview.WFM and Report Parameters |
by Steve Hawkins |
February 14, 2001
Introduction 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 had many of the same questions as I, it seemed worthwhile to post an example. 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
The user dialog for making the selections is started by way of a menu selection and looks like this:
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.
dB2K offers a built in "ReportViewer" class which is defined in 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 it's 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.
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:
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
--------------------------------------------
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
--------------------------------------------
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 addi 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
-----------------------------------------
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 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
-----------------------------------------
The ParamSetup() method: 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 'PREVIEW' for example), you wouldn't need a separate setup method. You could instead just include the ParamSetup() steps within the onClick method for the PREVIEW ('Screen') pushbutton.
ParamSetup() first 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 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.
We do this by overriding the built-in Render method. In brief, to do this, open the report in design mode and click Method | New Method on the menu. This will allow you to type your own render() method which will be called "form_render". Take note that when the source editor opens, dB2K will give your intended method a default name other than form_Render. Be sure you change the name of the method to "form_render".
Here is what my form_render method looks like. Don't forget that this method is in the REPORT, 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 if type ( "this.runOnce" ) # "L" this.runOnce = true 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 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 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. 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
dB2K programmer-in-training
SeHawk Services
dB2K - the only way to fly