Historically, indicators played an important part in RPG, as they controlled logic and file I/O. As RPG was modernized, however, the need to use indicators decreased and now RPG looks more like other programming languages. The time of indicator programming is over, so get ready to code the 21st century way!
Whats Wrong with Indicators?
One of the big problems with indicators is that there are only 99 of them. If your shop is like mine, you burned those up years ago on a big order-entry program that forced you to come up with crazy schemes just to manage it. The other problem with indicators is they are not self-documenting, so it is easy to forget what theyre being used for. There are several techniques I use to take care of DDS indicator functionality, such as highlighting a field, polling for a function key, displaying an error message using subfiles, and positioning a cursor. RPG IV itself provides many ways to help you avoid using indicators. (If you havent converted to RPG IV yet, what are you waiting for? See
Breathing New Life into Old Code, MC, February 2000 for some pointers on getting started.)
Ive placed source code for a working customer file maintenance program and its related objects on the Midrange Computing Web site to implement and illustrate the techniques Ill discuss in the following paragraphs. You can download my code at www.midrangecomputing.com/mc.
BIFs Are Better
The first place to start getting rid of indicators is in the C-specs. Traditionally, performing I/O on a file required one or more resulting indicators to be assigned to test the success of an I/O operation. In RPG IV you can use the %Eof, %Found, %Equal, and %Open built-in functions (BIFs) to test the results of an I/O operation. These BIFs are set to true (*ON) or false (*OFF) after the operation. %Eof stands for end of file, and it becomes true when a READx op code fails. If you chain to a file and want to know if you locked and retrieved the record, just check to see if the %Found is true instead of the hi resulting indicator. If you need to determine whether SETLL was successful, just check %Equal. In Figure 1, there are several examples of I/O using BIFs and their indicator equivalents.
Another important thing to mention is the %Error BIF. To use this BIF, you must code an (e) op code extender after the I/O op code. This BIF will be set to true when an error occurs in the op code being executed. If you are reading a file and %Error is true, then the cause of the problem could be a record lock or that the file was not open. To find the exact problem, you can use the %Status BIF to retrieve the error code. For more information on the %Status, see IBMs ILE RPG for AS/400 Reference manual. Look at Figure 1 again and notice how the BIF operations document themselves. Theres no need to worry about reusing an indicator elsewhere in your code, and theres no need to document what each indicator is doing, because each BIF is tied to the whole file in which you are working.
Taming DDS with P-fields
Display files traditionally use many indicators, so heres the perfect place to replace them. In Figure 2, you can see that I have defined indicators for SFLNXTCHG, SFLDSP, and other keywords. Executing EVAL SFLNXTCHG = *On turns *IN08 on. Using field names instead of indicators makes source code self-documenting and easier to understand.
Instead of using an indicator to control the display of an I/O field, you can use program-to-system fields, or P-fields. P-fields allow you to control the attributes of a display field. To use P-fields, you need to code a unique field name for the field you are controlling. For example, if the field you are controlling is called CUST#, create a P-field called @CUST#. You can use any name you want, but I like to have a P-field name that is similar to the field it controls.
What capabilities do you want this field to have? Do you want to protect it so that its input-capable only? Do you want it to reverse image (RI) during an error? Do you want to make it blink? Or, do you want it to do all of these things? It doesnt matter, because you have complete control over the field.
Figure 3 (page 98) contains a listing of 5250 panel constants; notice that each abbreviated constant name has a corresponding hexadecimal value. Each constant lets you control the fields appearance and behavior. Suppose you have a field that needs to be input-capable, reverse image on an error, and blink when the user needs to be alerted to something; this would normally take three indicators, but you can now do this with just one controlling field. This method allows you to concentrate on your program logic and not on indicator management. For example, if I want the CUST# field to reverse image when I enter an incorrect customer number, I would set the controlling P-field @CUST# to constant RI (EVAL @CUST# = RI). Here, RI equals hexadecimal 21. As you can see in Figure 3, there are two sets of constant values: one set for protected fields (starting with PR) and another for unprotected fields. If you need to switch from input-capable to output- only, select constants beginning with PR; otherwise, select any constant from the unprotected section.
Continuing the CUST# example, I would need to code a P-field definition, as is done in Figure 4 (page 99). The P-field @CUST# is declared as a one-byte field with a P in position 38, and it needs to be inside the record I am displaying. The P-field @CUST#, when used by the keyword DSPATR, must be preceded by an ampersand (&) for your DSPF to compile. Your program will use P-field @CUST# to control the display of I/O field CUST# on the panel. Setting the control fields to UL (underline) or to normal prepares the fields for input. During validation, I will set the control fields to RI to show which fields are in error.
Another thing worth mentioning is that P-fields may also condition constants. For example, there may be fields you want to hide. When you hide these I/O fields, you may need to hide their captions as well. I will usually pick a P-field name for a text constant that has the input field name plus TX. If @CREDLM were the I/O field name, then @CREDLMTX would be the text caption P-field name. If @CREDLM (credit limit) is not
to be displayed, than @CREDLMTX could be set to ND or non-display, so that the text caption isnt displayed when the I/O field isnt showing.
For more information on P-field constants, see the DDS Reference manual. The code on the Web for this article contains a copybook called DSPATR, which contains all of the constants that the P-fields can use.
Positioning the Cursor
How do you position the cursor over a field in error without resorting to indicators? There is a keyword in DDS called CRSLOC (see Figure 4). This keyword has two 3-byte fields, which are both zoned-decimal hidden fields that control the row and column positioning of the cursor. The cursor will be positioned to whatever row and column number is sent to the DDS from the RPG program. For example, if CUST# had a bad customer number, I would send the proper coordinates to CRSLOC by setting hidden fields ROW# and COL# to force the panels cursor over this field.
How do you know the row and column number values for CUST# or, for that matter, for any other field? Number values for a field can be found using a service program I wrote called RTNFLDLOC.
RTNFLDLOC uses a system API called QDFRTVFD. This is a hard API to use and way beyond the scope of this article (see Unravel the Display File Maze, MC, January 2000), but using the service program itself is simple. The basic call to RTNFLDLOC is as follows:
CALLP RtnFldLoc(
ROW#: COL#:
fieldname:
panel record name:
File Name)
Two optional parameters, library name and ForceSize, may be passed after the file name parameter. Library name defaults to *LIBL; this is the library where the display file is located. The ForceSize parameter addresses panel width. This parameter forces a row/column calculation to be based on an 80-byte or a 132-byte panel. Normally, a panel is either designed to be 80 bytes or 132 bytes, and RTNFLDLOC can determine how to calculate the row and column automatically. If the panel width is defined by DSPSIZ (27 132 *DS4 24 80 *DS3) and you are using *DS3 or *DS4 indicators on your fields, then you must decide what to instruct RTNFLDLOC to do. Pass in a 3 to force an 80-byte column/row calculation for a field with a *DS3 indicator, when the default is 27-132 size and you need the *DS3 size. Pass a 4 for a field with *DS4, when the default is 24-80 size. The INFDS can tell you the size of the default panel. Positions 152 and 153 tell the number of rows24 for an 80-byte panel, and 27 for a 132-byte panel. Positions 154 and 155 specify the number of columns, which is 80 or 132. With this in mind, when you need the *DS3 or *DS4 fields position, just send in the correct value to the parameter.
Which Function Key Was Pressed?
You may be wondering how to know when the Enter key or function keys have been pressed. DDS doesnt support an ENTER keyword on which you can attach an indicator, but your program can detect the Enter key, function keys, and other special-usage keys through the INFDS attached to your WORKSTN file. First, you should have all of the function key keywords (such as CF01 and CF02) coded in the file or record section of the DDS for this method to work (see Figure 4). Next, there is a byte in the INFDS at position 369 called AID, or Attention Indicator. After the panel has been read, the AID byte will contain a hexadecimal number corresponding to a function or editing key (such as Enter or Print). If you need to know whether the F3 key was pressed, just compare the AID byte to
the F3 constant. If you are reading a subfile and need to know whether the operator paged up or down, simply compare the AID byte to the roll-key constants. I have created a copybook, called FNKEYS, which is also in the downloadable code. This copybook includes constants for all the function keys, Enter, Page up, Page down, and so on. Using this method, all of the logic for these special keys is kept in the program to avoid using indicators.
Send Out an SOS
As I mentioned, I use message subfiles to display user error messages. I do this to avoid assigning indicators in the DDS to display a particular message. Message subfiles are displayed at the bottom of the panel in conjunction with the reverse image field in error. Theres nothing special to remember about how to code a message subfile; the source code never changes and can be copied from program to program.
For message subfile DDS, you must have the SFLPAG, SFLDSP, SFLDSPCTL, and SFLSIZ keywords in the subfile control record. SFLPAG tells how many subfile messages to display at a time, SFLDSP and SFLDSPCTL display the subfile and control record, and SFLSIZ is the number of records in the subfile.
SFLEND is the one keyword in the SFL control record that must have an indicator. SFLEND generates the plus sign or More... message that tells you when theres more than one record to view. Other keywords that typically need indicators are those used for subfile control mechanics. You must have these keywords in the record portion of the subfile: SFLMSGRCD, SFLMSGKEY, and SFLPGMQ. SFLMSGRCD indicates the line number to start displaying messages. SFLMSGKEY allows your program to assign a key to the subfile message record. The field name specified in positions 29 through 39 in the DDS used for subfile message selection is not important for our purposes, but a name is still required. The SFLPGMQ keyword in the subfile record contains the name of the program message queue. A program message queue is typically where the system sends error messages about your program. In this case, you can use the queue to display your messages. SFLPGMQ (10) means that a 10-byte field is generated. The value 10 is the default and is not needed. The only other size available is 276.
To send a message to a message subfile, use the QMHSNDPM API, as I did in the SendSFLMsg service program module, or call a CL program or module that executes the Send Program Message (SNDPGMMSG) command. The service program I have created is called SFLMESSAGE and is available as part of the downloadable code that accompanies this article. SFLMESSAGE contains two modules, SendSFLMSG and ClearSFLMsg. ClearSFLMsg removes the records written to the subfile message queue, and SendSFLMSG writes them. Whenever I encounter an error during the validation of an input field, I not only reverse image the field but I also send a message to the subfile message queue describing the error. Using a subfile message queue lets you avoid having to hard code indicators in the DDS, and it gives full control to the program. You can either send a text message from the program to the subfile, or you can send a message ID, message file, and library. I choose to store error messages in an array and then retrieve and send them to the subfile as necessary. As mentioned before, the basic DDS code never changes and can be used in other programs.
Put It Together
Now that you know the techniques for indicatorless programs, here is the basic logic for indicatorless application programs:
1. Display the panel.
2. Press Enter or a function key on a panel. This panel has fields that are input-capable and its displays are controlled by P-fields.
3. Clear the subfile message queue.
4. Examine the AID byte, and determine whether a function key or the Enter key was pressed. Check to see if it is a valid function key for this program.
5. Reset all fields controlled by P-fields back to their default settings (e.g., underline for input fields [UL] or normal for constants).
6. Validate the controlled I/O fields and reverse image the fields in error. Position the cursor to the first error. Make sure to use %FOUND or any of the other %BIF as needed.
7. Send the error messages for each field in error to the SFL message queue.
8. Redisplay the panel and start again, or update the file and exit.
MNTCUS, the sample program Ive placed on the Web, is a simple and straightforward maintenance program with no indicators.
MNTCUS uses P-fields to prompt for a customer number while making the other fields invisible. Once validated, it displays the customer details and allows you to change them. Additionally, the program verifies the function keys pressed. If a function key is invalid, the program sends an error message and redisplays the panel, but if all is OK, it allows you to cancel back to the previous panel, exit the program, or continue. If the data is invalid, MNTCUS reverse images each invalid field, positions the cursor, and sends an error to the message subfile. Download the code, follow the directions in the COMPILE source member, and take a good look at MNTCUS. Soon youll know how to make your own programs go indicatorless.
REFERENCES AND RELATED MATERIALS
Breathing New Life into Old Code, Paul Ladouceur, MC, February 2000
ILE RPG for AS/400 Programmers Guide (SC09-2507)
ILE RPG for AS/400 Reference (SC09-2508)
OS/400 DDS Reference V4R4 (SC41-5712-03)
Unravel the Display File Maze, Gene Gaunt, MC, January 2000
***************************************************
* Traditional Way of Handling I/O
***************************************************
C CusKey Setll Customer 5350
C If *IN50
C Do *Hival
C CusKey Reade CustomerR 5351
C If *IN51
C Leave
C Endif
C If *IN53
C Message1 Dsply
C Leave
C Endif
C CrdKey Chain CreditCdR 5253
C If *IN52 = *On or *IN53
C Message2 Dsply
C Leave
C Endif
C Enddo
C Endif
***************************************************
* Indicatorless Way
***************************************************
C CusKey Setll(e) Customer
C If %Equal(Customer)
C Do *Hival
C CusKey Reade(e) CustomerR
C If %Eof
C Leave
C Endif
C If %Error
C Message1 Dsply
C Leave
C Endif
C CrdKey Chain (e) CreditCDR
C If Not %Found(CreditCD) or %Error
C Message2 Dsply
C Leave
C Endif
C Enddo
C Endif
Figure 1: BIFs beat indicator-laden code hands down!
* Declare Named Indicators
DIndicators DS Based(IndicatorP)
D SFLNXTCHG 8 8
D SFLCLR 40 40
D SFLCTLDSP 41 41
D SFLDSP 42 42
D SFLINZ 43 43
DIndicatorP S * Inz(%Addr(*In))
Figure 2: Indicators deserve to have meaningful names.
* RI=Reverse Image, HI=Hi Intensity, BL=blink, UL=Underline
* ND=Non Display
* NON Protect fields
d Normal c const(x'20')
d RI c const(x'21')
d HI c const(x'22')
d HIRI c const(x'23')
d UL c const(x'24')
d ULRI c const(x'25')
d ULHI c const(x'26')
d ND c const(x'27')
d BL c const(x'28')
d BLRI c const(x'29')
d BLHI c const(x'2A')
d BLHIRI c const(x'2B')
d BLUL c const(x'2C')
d BLULRI c const(x'2D')
d BLULHI c const(x'2E')
* Protect field
d PRNormal c const(x'A0')
d PRRI c const(x'A1')
d PRHI c const(x'A2')
d PRHIRI c const(x'A3')
d PRUL c const(x'A4')
d PRULRI c const(x'A5')
d PRULHI c const(x'A6')
d PRND c const(x'A7')
d PRBL c const(x'A8')
d PRBLRI c const(x'A9')
d PRBLHI c const(x'AA')
d PRBLHIRI c const(x'AB')
d PRBLUL c const(x'AC')
d PRBLULRI c const(x'AD')
d PRBLULHI c const(x'AE')
Figure 3: Using system-defined hexadecimal constants is a good way to eradicate indicators.
A**** For the full source code see MNTCUSFM ****
A DSPSIZ(24 80 *DS3)
A PRINT
A CF01
A CF02
. . . . Other Functions keys DECLARED . . .
A
A CF24
A PAGEDOWN
A PAGEUP
A*A R PANEL
A*A RTNCSRLOC(&RCD &FLD &POS)
A CLRL(10)
A CSRLOC(ROW# COL#)
A OVERLAY
A RCD 10A H
A FLD 10A H
A POS 4S 0H
A ROW# 3S 0H
A COL# 3S 0H
A @CUST# 1A P
A @CUST#TX 1A P
. . . . Other P Fields Declared . . .
A @CREDLM 1A P
A @CREDLMTX 1A P
A TITLE 30A O 1 25DSPATR(HI)
A 1 60DATE
A EDTCDE(Y)
A 1 70TIME
A 3 2'Customer Number :'
A DSPATR(&@CUST#TX)
A CUST# R B 3 20REFFLD(CUSTOMERR/CUST# CUSTOMER)
A DSPATR(&@CUST#)
A 3 30'Credit Code:'
A DSPATR(&@CREDCDTX)
A CREDCD R B 3 44REFFLD(CUSTOMERR/CREDCD *LIBL/CUSTOA MER)
A DSPATR(&@CREDCD)
A DESC R O 3 47REFFLD(CREDITCDR/DESC *LIBL/CREDITCA D)
A DSPATR(&@DESC)
A 5 2'Customer Name :'
A DSPATR(&@CNAMETX)
A CNAME R B 5 20REFFLD(CUSTOMERR/CNAME *LIBL/CUSTOMA ER)
A DSPATR(&@CNAME)
A 7 2'Address :'
A DSPATR(&@CADD1TX)
A CADD1 R B 7 20REFFLD(CUSTOMERR/CADD1 *LIBL/CUSTOMA ER)
A DSPATR(&@CADD1)
A 8 3'City/State/Zip'
A DSPATR(&@CADD1TX)
A CCITY R B 8 20REFFLD(CUSTOMERR/CCITY *LIBL/CUSTOMA ER)
A DSPATR(&@CCITY)
A CSTATE R B 8 37REFFLD(CUSTOMERR/CSTATE *LIBL/CUSTOA MER)
A DSPATR(&@CSTATE)
A CZIP R B 8 40REFFLD(CUSTOMERR/CZIP *LIBL/CUSTOMEA R)
A DSPATR(&@CZIP)
A 11 2'Credit Limit:'
A DSPATR(&@CREDLMTX)
A CREDLM R B 11 16REFFLD(CUSTOMERR/CREDLM *LIBL/CUSTOA MER)
A DSPATR(&@CREDLM)
A EDTCDE(Z)
A 23 3'F3=Exit'
A 23 23'Enter=Continue'
A 23 41'F12=Cancel'
A*A R MSFL
A*A SFL
A SFLMSGRCD(24)
A MSGKEY SFLMSGKEY
A PGMNAM SFLPGMQ(10)
A R MSFLC
A SFLCTL(MSFL)
A LOCK
A OVERLAY
A SFLSIZ(50)
A SFLPAG(1)
A N26 SFLEND
A SFLDSP
A SFLDSPCTL
A SFLINZ
A PGMNAM SFLPGMQ
Figure 4: Display files are among the heaviest users of indicators.
Click here to download code.
LATEST COMMENTS
MC Press Online