Writing interactive applications is not hard. Writing good interactive applications is. Every shop I've worked in or known about had a mixture of styles of interactive programs. One program might ignore changed fields when the user presses F3, while another program would process them. Inconsistency makes life harder for users, and that makes life harder for you.
One place you can be consistent is in the way your applications communicate with people. A good way for an AS/400 program to communicate with a person is through subfiles.
Of the different types of subfiles, the one I want to discuss this month is called the message subfile. Message subfiles take advantage of two strengths of OS/400: built-in support for subfiles and the ability of programs to communicate through messages.
When a program begins to run, OS/400 creates a temporary message queue for that program's use. When the program ends, OS/400 deletes the message queue. This message queue is called a program message queue.
A message subfile is a special type of subfile designed to display messages from a program message queue. When you define a subfile to be a message subfile, the system retrieves messages from the program message queue and loads them into the subfile for you. Your obligations are to define the message file correctly in DDS and to issue an appropriate output operation to the message subfile control format in the program.
One reason message subfiles are so useful is that they provide an easy way to send more than one message at a time to the users. If there is not enough room on the display for all the messages, the system enables the roll keys, so the users can page through the messages.
The fact that message subfiles retrieve messages from message files gives you several advantages. First, you get better help text. Depending on the size of the display, users will see only 76 or 128 characters of the message on a line, but they can press the Help key and see the full first-level text as well as the second-level text.
Second, you don't have to recompile your program to change the message text. You just change the message description in the message file.
Third, you can use message data variables to make the message more specific. Suppose a user miskeys a customer number when entering billing information. If you use some other messaging techniques, your error message might read Unknown customer number. Using message variables gives you an easy way to insert the invalid data into the message, as in 12345 is not a valid customer number.
Another reason to use message subfiles is that they are the only type of subfile that can be used in CL programs. If you want to see a creative technique for building load-all subfiles in CL, see "CL Subfiles," TechTalk, MC, August 1995.
A message subfile has one record definition and two field definitions. You can see them in the record format MSGSFL in the display file of 1. The record format definition has the keywords SFL and SFLMSGRCD. SFLdefines the record as a subfile, and SFLMSGRCD tells what line of the display messages begin on-in this case, line 24. The presence of the SFLMSGRCD keyword makes the subfile a message subfile.
A message subfile has one record definition and two field definitions. You can see them in the record format MSGSFL in the display file of Figure 1. The record format definition has the keywords SFL and SFLMSGRCD. SFLdefines the record as a subfile, and SFLMSGRCD tells what line of the display messages begin on-in this case, line 24. The presence of the SFLMSGRCD keyword makes the subfile a message subfile.
The record definition is followed by two hidden fields. The first of these must have a field name and the SFLMSGKEY keyword. Every time a message is sent, OS/400 assigns the message a 4-byte value to uniquely identify the message. SFLMSGKEY makes the system store the message key value in the subfile. In this message subfile, I've named this hidden field MSGKEY.
The other hidden field is the name of the program message queue from which the messages are retrieved. I've called it PGMQ. Most languages will accept a single asterisk (*) in this field to mean the current program's message queue. CL programs won't accept the asterisk, so you have to put the program name there instead.
The message subfile control record is pretty much the same as for any subfile, except that you need to specify both the SFLINZ and SFLPGMQ keywords. Activating both keywords together in the same output operation loads all messages from the message queue at once.
You may load messages into a message subfile one at a time or load them all with one output operation. The one-at-a-time method requires a language that supports relative-record number (RRN) processing against a subfile, so your only choice in CL programs is to load all the messages at once.
I've chosen a CL menu to illustrate how to use message subfiles in a CL program (see 2). If you've worked on a S/38, you know all about CL menus. If you came to the AS/400 by some other route, you may not have used them.
I've chosen a CL menu to illustrate how to use message subfiles in a CL program (see Figure 2). If you've worked on a S/38, you know all about CL menus. If you came to the AS/400 by some other route, you may not have used them.
CL menus have some advantages over display file and UIM menus.
o You can include information from other sources on the display. In this example, there are two plant-specific fields-plant number and name-that are retrieved from a data area. The initial output queue name, used by option 70, is taken from the user profile. You can't do this sort of thing with a display file or UIM menu.
o You can make rolling menus very easily. You simply add more record formats and a little code to handle the roll keys. (I left out that logic to shorten this example.) If you want menu objects to roll, you have to use a UIM menu.
o You can allow the user to key parameter values into the display. In this example, there is an input field in which to key a customer number with option 1. This means you don't have to define display formats to prompt for a value before running a program.
o You can place many commands behind a menu option. A single option might submit 20 jobs to a job queue. You can even use controlling commands-like the If (IF), Monitor Message (MONMSG), and Go To (GOTO) commands-to make a menu option behave different ways under different circumstances.
Let me point out the parts of the CL program that relate to message subfiles, starting at label DSP_SFL. When the system executes the Send File (SNDF) command, it will load the subfile with all the messages in the program message queue. These messages could have arrived on the program message queue for several reasons:
o Some messages are sent by commands that complete successfully. For example, suppose a menu option clears a physical file. If the operation completes normally, the Clear Physical File Member (CLRPFM) command will respond with a message that the member was cleared.
o Some messages are sent by commands that did not complete successfully. If CLRPFM fails, it will send other messages.
o Some messages are sent by the program. There is a Send Program Message (SNDPGMMSG) command in this program at label UNDEF_OPT. If the user enters an option that is not recognized, the program sends a message to say that the option was ignored.
The fact that the user gets all the messages on the message queue is good, because it means the user doesn't miss a message. But it also presents a problem. If the system sends two messages the first time the user presses Enter, then two more messages the second time the user presses Enter, the message subfile will have four messages in it. Every time the user presses Enter, the message subfile will grow larger and larger, and the user will have to roll past all the old messages to get to the new ones.
The obvious solution is to clear the program message queue after every operation. This is the purpose of the Remove Message (RMVMSG) command, the third line under label DSP_MENU.
The menu works well now, but if you need a job log, you're out of luck. The job log won't show the messages because they've been removed from the program message queue. The short message forwarding loop at label RCV_MSG takes care of this. By sending the messages back to the caller, the messages remain in the job log.
I've used a CL menu program to illustrate message subfiles, mainly because that's the primary way I've used message subfiles through the years, and I had an abundance of material to draw from. But that's not the only way to use message subfiles. You can use message subfiles in other types of CL programs, like those that prompt for parameters and submit one or more programs to batch or CL programs that run interactively.
Other programming languages can also use message subfiles, but it's a little more difficult. Languages like RPG don't have direct support for program message queues, so you have to call CL programs or APIs to handle the messaging logic. It probably goes without saying that the APIs give you better performance. You can read about them in "More Powerful Message Subfiles" (MC, December 1993).
One feature that makes OS/400 such a powerful and robust operating system is the ability of processes and people to send and receive messages. Because the AS/400 has such a sophisticated messaging facility, we AS/400 professionals should learn to use it to its fullest potential.
Message subfiles are easy to use, and they're the only way to see the messages in a program message queue. If you're an AS/400 professional, you owe it to yourself to understand message subfiles.
Ted Holt is an associate editor for Midrange Computing. He can be reached by E-mail at
Message Subfiles 101
Figure 1: DDS for a CL Menu Program with a Message Subfile
*=============================================================== * To compile: * * CRTDSPF FILE(XXX/MNU001DF) SRCFILE(XXX/QDDSSRC) * *=============================================================== *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 ...+... 7 A DSPSIZ(*DS3) A PRINT A CA03(03) A CA12(12) A HOME(25) A R MNUFMT BLINK A OVERLAY A MENUNAME 10A O 1 2TEXT('Menu name') A MENUTITLE 40 1 21DSPATR(HI) A 1 72 DATE EDTCDE(Y) A 2 72 TIME A 3 2'Plant:' A PLNBR 4 3 9TEXT('Plant number') A PLLOC 20 3 14TEXT('Plant location') A 5 2'Enter option and press Enter.' A 7 5'1.' A 7 9'List orders for customer' A CUSTNBR 7 B 7 34 A 8 5'2.' A 8 9'Work with output queue' A OUTQ 10 B 8 32 A 22 2'Option:' A OPTION 2 B 22 10DSPATR(PC) A 23 5'F3=Exit F12=Cancel' A R MSGSFL SFL A SFLMSGRCD(24) A MSGKEY SFLMSGKEY A PGMQ SFLPGMQ A R SFLCTL SFLCTL(MSGSFL) A SFLSIZ(25) A SFLPAG(1) A SFLINZ A SFLDSPCTL A SFLDSP A N99 SFLEND(*PLUS) A PGMQ SFLPGMQ
Message Subfiles 101
Figure 2: A CL Menu Program
/* ============================================================= */ /* To compile: */ /* */ /* CRTCLPGM PGM(XXX/MNU001CL) SRCFILE(XXX/QCLSRC) */ /* CRTMNU MENU(XXX/MNU001CL) TYPE(*PGM) PGM(XXX/MNU001CL) */ /* */ /* ============================================================= */ MNU001CL: PGM PARM(&MENUNAME &MENULIB &RETURNCODE) DCL VAR(&MENUNAME) TYPE(*CHAR) LEN(10) DCL VAR(&MENULIB) TYPE(*CHAR) LEN(10) DCL VAR(&RETURNCODE) TYPE(*CHAR) LEN(2) DCL VAR(&ERROR) TYPE(*LGL) DCL VAR(&MSG) TYPE(*CHAR) LEN(512) DCL VAR(&MSGDTA) TYPE(*CHAR) LEN(512) DCL VAR(&MSGID) TYPE(*CHAR) LEN(7) DCL VAR(&MSGF) TYPE(*CHAR) LEN(10) DCL VAR(&MSGLIB) TYPE(*CHAR) LEN(10) DCL VAR(&PLANTINFO) TYPE(*CHAR) LEN(80) DCLF FILE(MNU001DF) MONMSG MSGID(CPF0000) EXEC(GOTO CMDLBL(ERROR)) CHGVAR VAR(%SST(&MENUTITLE 18 7)) VALUE('My Menu') RTVDTAARA DTAARA(PLTINFO) RTNVAR(&PLANTINFO) CHGVAR VAR(&PLNBR) VALUE(%SST(&PLANTINFO 1 4)) CHGVAR VAR(&PLLOC) VALUE(%SST(&PLANTINFO 5 20)) RTVUSRPRF OUTQ(&OUTQ) CHGVAR VAR(&PGMQ) VALUE(MNU001CL) DSP_MENU: CHGVAR VAR(&ERROR) VALUE('0') SNDRCVF DEV(*FILE) RCDFMT(MNUFMT) RMVMSG CLEAR(*ALL) F3: IF COND(&IN03) THEN(DO) CHGVAR VAR(&RETURNCODE) VALUE(X'FFFF') RETURN ENDDO F12: IF COND(&IN12) THEN(DO) CHGVAR VAR(&RETURNCODE) VALUE(X'FFFE') RETURN ENDDO HOME: IF COND(&IN25) THEN(DO) CHGVAR VAR(&RETURNCODE) VALUE(X'FFFC') RETURN ENDDO /* Handle defined options */ OPTION_1: IF COND(&OPTION *EQ '1') THEN(DO) CALL PGM(ARRPT) PARM(&CUSTNBR) GOTO CMDLBL(ENDOPTION) ENDDO OPTION_2: IF COND(&OPTION *EQ '2') THEN(DO) WRKOUTQ OUTQ(&OUTQ) GOTO CMDLBL(ENDOPTION) ENDDO /* Undefined option */ UNDEF_OPT: SNDPGMMSG MSGID(CPF9898) MSGF(QCPFMSG) + MSGDTA('Undefined option ignored') + TOPGMQ(*SAME) ENDOPTION: CHGVAR VAR(&OPTION) VALUE(' ') GOTO CMDLBL(RCV_MSG) ERROR: IF COND(&ERROR) THEN(DO) CHGVAR VAR(&RETURNCODE) VALUE(X'FFFE') RETURN ENDDO CHGVAR VAR(&ERROR) VALUE('1') RCV_MSG: RCVMSG RMV(*NO) MSG(&MSG) MSGDTA(&MSGDTA) + MSGID(&MSGID) MSGF(&MSGF) SNDMSGFLIB(&MSGLIB) IF COND(&MSG *NE ' ') THEN(DO) SNDPGMMSG MSGID(&MSGID) MSGF(&MSGLIB/&MSGF) + MSGDTA(&MSGDTA) GOTO CMDLBL(RCV_MSG) ENDDO DSP_SFL: SNDF RCDFMT(SFLCTL) GOTO CMDLBL(DSP_MENU) ENDPGM: ENDPGM
LATEST COMMENTS
MC Press Online