Guru: The SND-MSG Op Code And Message Subfiles
September 12, 2022 Ted Holt
If you’re one of the many IBM i programmers who still writes green-screen applications, think about how your programs communicate with the users. How do you tell a user that a value that he’s just entered is invalid, or that he needs to press a command key to proceed? I’ve seen several methods, but a common one — and my favorite — is to communicate through a message subfile.
The nice thing about message subfiles is that I can report two or more messages at one time. I like the computer to find as many errors as possible and let the user address all of them before he tries again. It annoys me to use an application (even Web pages) that tell me to fix one error at a time, yet I have seen green-screen applications that work this way. I have probably written some.
This story contains code, which you can download here.
To use a message subfile in a program, you need to define a message subfile and corresponding subfile control record in a display file. Here’s the source code I always use. The only thing I change is the value in the SFLMSGRCD keyword, depending on the display size (24×80 or 27×132) or window size.
A R MSGSFL SFL A SFLMSGRCD(24) A MSGKEY SFLMSGKEY A PGMNAME SFLPGMQ A R MSGCTL SFLCTL(MSGSFL) A OVERLAY A SFLDSP A SFLDSPCTL A SFLINZ A N99 SFLEND A SFLSIZ(10) A SFLPAG(1) A PGMNAME SFLPGMQ
The RPG program interacts with the message subfile in two ways:
- The program sends appropriate messages to the program message queue.
- After the user has had an opportunity to view the messages, the program clears the program message queue.
The usual way to send messages to the program message queue is to use the Send Program Message (QMHSNDPM) API. To remove the messages so that user doesn’t continue to see them, use the Remove Program Messages (QMHRMVPM) API. Through the years we’ve published quite a few articles that featured these APIs. Here’s one from 20 years ago. The source code is antiquated, but the principles still apply.
I was delighted to learn recently that IBM made this process even easier with the introduction of the SND-MSG op code. SND-MSG and QMHSNDPM do, more or less, the same thing, but SND-MSG is easier to use. You still need QMHRMVPM, as IBM did not give us an RPG counterpart for that.
Here’s an example you can compile and play with if you’re interested. First, here’s the DDS for display file THQ0051D.
A DSPSIZ(24 80 *DS3) A CA03(03 'F3=EXIT') A R REC1 A OVERLAY A 1 36'Data Entry' A 3 3'Fill in the blanks, press Enter.' A 5 3'Your name . . . . . :' A NAME 24 B 5 25 A 61 DSPATR(PC RI) A 7 3'Your age . . . . . :' A AGE 3 0B 7 25 A 62 DSPATR(PC RI) A 9 3'Your address . . . :' A STREET 24 B 9 25 A 63 DSPATR(PC RI) A 10 5'City, state, ZIP :' A CITY 16 B 10 25 A 64 DSPATR(PC RI) A STATE 2 B 10 43 A 65 DSPATR(PC RI) A ZIP 5 0B 10 47 A 66 DSPATR(PC RI) A 23 5'F3=Exit' A R MSGSFL SFL A SFLMSGRCD(24) A MSGKEY SFLMSGKEY A PGMNAME SFLPGMQ A R MSGCTL SFLCTL(MSGSFL) A OVERLAY A SFLDSP A SFLDSPCTL A SFLINZ A N99 SFLEND A SFLSIZ(10) A SFLPAG(1) A PGMNAME SFLPGMQ
REC1 is a simple data-entry format. The user enters a name, age, and address.
Here’s RPG program THQ0051R, which drives the screen.
**free ctl-opt actgrp(*new) option(*srcstmt: *nodebugio); dcl-f THQ0051D workstn; dcl-ds psds psds; PgmName char(10); end-ds; dcl-pr QMHRMVPM extpgm; CallStackEntry char(10) const; CallStackCounter int (10) const; MessageKey char( 4) const; MessagesToRemove char(10) const; ErrorCode int (10) const; end-pr QMHRMVPM; dcl-s DataIsValid ind; *inlr = *on; dou *in03 or DataIsValid; write MsgCtl; exfmt Rec1; QMHRmvPM (PgmName: *zero: *blanks: '*ALL': *zero); if not *in03; exsr Validate; endif; enddo; // Do something with the data return; begsr Validate; %subarr(*in: 61: 6) = *all'0'; if Name = *blanks; *in61 = *on; Snd-Msg 'Your name is required.'; endif; if Age < 18; *in62 = *on; Snd-Msg 'Age must be 18 or greater.'; endif; if not (State in %list('TX': 'OK': 'LA')); *in65 = *on; Snd-Msg 'The state "' + State + '" is not authorized.'; endif; // Similar code to validate the other fields. DataIsValid = not ( *in61 or *in62 or *in63 or *in64 or *in65 or *in66 ); endsr;
Let me direct your attention to the Validate subroutine. Notice the simplicity of the SND-MSG operations to send impromptu informational messages to the program message queue. I don’t see how it could be any easier.
In my zeal to make the example easy to understand, I kept the DDS and RPG source code short and simple. If I were writing this code for production, I would make a few enhancements.
- I would qualify the display file so that the compiler would not assign global variables to the fields.
- I would use a subprocedure instead of a subroutine to validate the data so that I could pass the data through parameters instead of using global data.
- I would add a global catch-all monitor so that the user would never see the Display Program Messages display. IBM kindly provides the Resend Escape Message (QMHRSNEM) API, which works well in this context.
Here is the same application beefed up a bit. The display file needs only one change: qualifying the file in the RPG program requires the display file DDS to have the INDARA keyword.
A DSPSIZ(24 80 *DS3) A INDARA A CA03(03 'F3=EXIT') . . . and so on . . .
Here’s the RPG program with the changes I mentioned.
**free ctl-opt main(THQ0054R) actgrp(*new) option(*srcstmt: *nodebugio); dcl-f Display workstn qualified extdesc('THQ0052D') extfile(*extdesc) alias indds(wsi) usropn; dcl-ds Rec1_t LikeRec(Display.REC1 : *all) template; dcl-ds MsgCtl_t LikeRec(Display.MsgCtl: *all) template; // Indicator data structure for display file dcl-ds wsi len(99) qualified inz; ExitRequested ind pos( 3); ErrorInds char(6) pos(61); ErrorInd_Name ind overlay(ErrorInds: 1); ErrorInd_Age ind overlay(ErrorInds: 2); ErrorInd_Street ind overlay(ErrorInds: 3); ErrorInd_City ind overlay(ErrorInds: 4); ErrorInd_State ind overlay(ErrorInds: 5); ErrorInd_Zip ind overlay(ErrorInds: 6); end-ds wsi; dcl-ds psds psds qualified; PgmName char(10); end-ds; dcl-pr QMHRMVPM extpgm; CallStackEntry char(10) const; CallStackCounter int (10) const; MessageKey char( 4) const; MessagesToRemove char(10) const; ErrorCode int (10) const; end-pr QMHRMVPM; dcl-pr QMHRSNEM extpgm; MessageKey char( 4) const; ErrorCode int (10) const; end-pr QMHRSNEM; dcl-proc THQ0054R; monitor; THQ0054R_Main (); on-error; QMHRsnEM (*blanks: *zero); endmon; end-proc THQ0054R; dcl-proc THQ0054R_Main; dcl-s DataIsValid ind; dcl-ds Rec1_data likeds(Rec1_t) inz; dcl-ds MsgCtl_Data likeds(MsgCtl_t) inz; open Display; dou wsi.ExitRequested or DataIsValid; MsgCtl_data.PgmName = psds.PgmName; write Display.MsgCtl MsgCtl_data; exfmt Display.Rec1 Rec1_data; QMHRmvPM (psds.PgmName: *zero: *blanks: '*ALL': *zero); if not wsi.ExitRequested; Validate (Rec1_data: DataIsValid); endif; enddo; // Do something with the data close Display; end-proc THQ0054R_Main; dcl-proc Validate; dcl-pi *n; inRec1_data likeds(Rec1_t) const; ouDataIsValid ind; end-pi; wsi.ErrorInds = *zeros; if inRec1_data.Name = *blanks; wsi.ErrorInd_Name = *on; Snd-Msg 'Your name is required.' %target(psds.PgmName); endif; if inRec1_data.Age < 18; wsi.ErrorInd_Age = *on; Snd-Msg 'Age must be 18 or greater.' %target(psds.PgmName); endif; if not (inRec1_data.State in %list('TX': 'OK': 'LA')); wsi.ErrorInd_State = *on; Snd-Msg ('The state "' + inRec1_data.State + '" is not authorized.') %target(psds.PgmName); endif; // Similar code to validate the other fields. ouDataIsValid = (wsi.ErrorInds = *zeros); end-proc Validate;
I see no need to try to explain all of this code, but I should point out the change to the SND-MSG operation, since that’s the topic of this article. Since SND-MSG is in a subprocedure, I have to use the %TARGET keyword to specify the name of the program message queue.
Snd-Msg 'Your name is required.' %target(psds.PgmName);
I also want to point out that the RPG program uses the message subfile control record, but not the message subfile record itself. That’s true whether the display file is qualified or not.
Communication skills are important in all areas of life, and it’s unfortunate that many people never learn to communicate effectively in either spoken or written form. Fortunately, it’s not difficult to make a green-screen program communicate with the user, and now that it we have the SND-MSG op code, it’s easier than ever.