I received a message asking me if there was an easy way in RPG to have a screen constantly update without the user needing to press a key. The idea is that as the program progresses it would write a message to the screen to inform the user of what is happening. Fortunately there is, otherwise I would not have be written this post.
First we need to understand what RPG's operation code Execute Format, EXFMT, does. Simply put it is a combination of a Write operation of the record format to the workstation, followed by a Read operation to retrieve the input from the record format. I can easily replace the EXFMT in any program with a WRITE followed by a READ. If I want to just write a display file's record format to the screen I could just perform a write, but how can I stop the user for using the keyboard while the record format is displayed?
I can use Lock keyboard keyword, LOCK, within the record format to stop the use of the keyboard while this record format is displayed.
Below is a very simple display file I will be using. While the program is running I want to display what step has finished and what step is currently being performed. When all the steps have been performed I will display a screen that informs the user that it has finished and they have to press the Enter key to continue.
01 A DSPSIZ(24 80 *DS3) 02 A R SCREEN1 03 A LOCK 04 A 3 3'What has finished :' 05 A FLD001 50 O 3 23 06 A 4 3'What is going on :' 07 A FLD002 R O 4 23REFFLD(FLD001 *SRC) 08 A R SCREEN2 09 A 3 3'Job has finished' 10 A 4 3'Press Enter' |
Update: Thank you to Chris Ringer and Mario Alexis Salgado Montenegro for pointing out something I overlooked. The display file must be compiled with "Defer Write" set to no, DFRWRT(*NO). My poor excuse is that this is that I have changed my default CRTDSPF to have RSTDSP(*YES) and DFTWRT(*NO), therefore, I never caught this error.
The LOCK keyword, line 3, is in the SCREEN1 record format and while that record format is displayed the user will not be able to use the keyboard. Depending on how your users are configured you will find that any key they press while SCREEN1 is displayed is buffered, and will be executed when SCREEN2, which does not have the lock, is displayed.
The RPG code is simple too:
01 ctl-opt dftactgrp(*no) ; 02 dcl-pr QCMDEXC extpgm ; 03 *n char(13) const ; 04 *n packed(15:5) const ; 05 end-pr ; 06 dcl-f TESTDSPF workstn ; 07 dcl-s i packed(2) ; 08 for i = 1 to 10 ; 09 FLD001 = FLD002 ; 10 FLD002 = 'Step = ' + %char(i) ; 11 write SCREEN1 ; 12 QCMDEXC('DLYJOB DLY(2)':13) ; 13 endfor ; 14 exfmt SCREEN2 ; 15 *inlr = *on ; |
For those of using servers that do not have the all free RPG you can see a version with fixed format definitions here.
The first thing you are going to wonder is why I am using the QCMDEXC API in this program. It has only been added to help demonstrate how this program works. If I did not stop the program for a couple of seconds you would not be able to see what is displayed on the screen as it would complete too quickly. By using the QCMDEXC to execute a Delay Job command, DLYJOB, the program is stopped long enough for us to see what is displayed on the screen. In a live program this would not be present. I have defined QCMDEXC as an external procedure, if you are not familiar with doing this see Defining Procedures in RPG all free.
There is nothing special with the definition of the display file, line 6.
I start a For group, line 8, to execute ten times. If you have not used a For group see FOR replaces DO. Lines 9 and 10 are self explanatory. On line 11 I write the record format, as I only want to display what is in the record format. On line 12 I delay the program for two seconds so we can observe the changes on the screen.
After the tenth iteration of the For group we reach line 14 and display the second record format. This time I use the EXFMT as I want the user to press Enter.
When the program runs it looks like this:
What has finished : What is going on : Step = 1 -- // -- What has finished : Step = 1 What is going on : Step = 2 -- // -- What has finished : Step = 2 What is going on : Step = 3 -- // --Repeats 6 times until… -- // -- What has finished : Step = 9 What is going on : Step = 10 -- // -- Job has finished Press Enter |
If I had multiple record formats with the LOCK keyword I could display one after another like a PowerPoint presentation auto playing:
11 write SLIDE1 ; 12 QCMDEXC('DLYJOB DLY(5)':13) ; 13 write SLIDE2 ; 14 QCMDEXC('DLYJOB DLY(5)':13) ; 15 write SLIDE3 ; 16 QCMDEXC('DLYJOB DLY(5)':13) ; 24 exfmt LASTSLIDE ; 25 *inlr = *on ; |
While the original question was about RPG, I can do the same as the original RPG program in CL.
For IBM i 7.2 the program could look like as in that release the %CHAR built in function was added to CL. If you are unfamiliar with it see Char built in function added to CL.
01 PGM 02 DCL VAR(&COUNT) TYPE(*DEC) LEN(2 0) VALUE(1) 03 DCLF FILE(TESTDSPF) 04 DOUNTIL COND(&COUNT > 10) 05 CHGVAR VAR(&FLD001) VALUE(&FLD002) 06 CHGVAR VAR(&FLD002) VALUE('Step = ' || %CHAR(&COUNT)) 07 SNDF RCDFMT(SCREEN1) 08 DLYJOB DLY(2) 09 CHGVAR VAR(&COUNT) VALUE(&COUNT + 1) 10 ENDDO 11 SNDRCVF RCDFMT(SCREEN2) 12 ENDPGM |
For earlier releases I would need to use a character version of the &COUNT variable, see lines 3, 7, and 8 below:
01 PGM 02 DCL VAR(&COUNT) TYPE(*DEC) LEN(2 0) VALUE(1) 03 DCL VAR(&COUNTA) TYPE(*CHAR) LEN(2) 04 DCLF FILE(TESTDSPF) 05 DOUNTIL COND(&COUNT > 10) 06 CHGVAR VAR(&FLD001) VALUE(&FLD002) 07 CHGVAR VAR(&COUNTA) VALUE(&COUNT) 08 CHGVAR VAR(&FLD002) VALUE('Step = ' || &COUNTA) 09 SNDF RCDFMT(SCREEN1) 10 DLYJOB DLY(2) 11 CHGVAR VAR(&COUNT) VALUE(&COUNT + 1) 12 ENDDO 14 SNDRCVF RCDFMT(SCREEN2) 15 ENDPGM |
The SNDF command, on line 7 or 9, is CL's equivalent as RPG's Write operation.
If you have not used a DOUNTIL in CL see CL does DO.
You can learn more about the LOCK keyword in DDS on the IBM web site here.
This article was written for IBM i 7.2, and should work for earlier releases too.
Fixed format version of the RPG program
01 H dftactgrp(*no) 02 FTESTDSPF CF E WORKSTN 03 D QCMDEXC PR extpgm('QCMDEXC') 04 D 13 const 05 D 15 5 const 06 D i S 2 0 /free 07 for i = 1 to 10 ; 08 FLD001 = FLD002 ; 09 FLD002 = 'Step = ' + %char(i) ; 10 write SCREEN1 ; 11 QCMDEXC('DLYJOB DLY(2)':13) ; 12 endfor ; 13 exfmt SCREEN2 ; 14 *inlr = *on ; |
I must add that you have to do a change of DSP file changing the DFRWRT VALUE from *YES To *NO
ReplyDeleteCHGDSPF FILE(Libr/SCREEN) DFRWRT(*NO)
Do you have the display file compiled as DFRWRT(*NO)? I've done the same trick before but if the display file was DFRWRT(*YES) which is normally the default value, only the last WRITE message appeared. Thanks.
ReplyDeleteChris Ringer
Great article. How would you do it for the times you don't want to lock the keyboard? You might want to have a screen being displayed to allow the user to do actions but if they don't, perform an action after a timeout.
ReplyDeleteYou can change dspf record wait time to timeout value, use write and read commands instead of exfmt and catch read error in pgm.
DeleteYes, but there is so much more you can do using that method. More next week. 😀
DeleteA small definition correction. You wrote: "I have defined QCMDEXC as an external procedure". It's actually an external program. Even though the syntax to execute the call is almost the same, there are restrictions, such as there can be no return value and you can't use the VALUE keyword on any parms. For anyone still using the CALLP opcode, it stands for "Call Prototype(d)", not call procedure.
ReplyDeleteIf you need to delay the screen refresh for any reason another option in RPG is "sleep" - you can then replace QCMDEXC('DLYJOB DLY(2)':13) ;
ReplyDeleteD sleep PR 10U 0 EXTPROC(*CWIDEN : 'sleep')
D wDelay 10U 0 VALUE
D DlyTime1 DS DTAARA(SLEEPTIME)
D DelaySec1 1 6 0
// Stop the program for the time determined from Data area
EVAL wDelay = DelaySec1 ;
sleep(wDelay) ;
There a very simple method to have the screen displayed with the updated content each time you do the WRITE (or SNDF). That is to add the keyword FRCDTA at the record format in question.
ReplyDeleteAdding FRCDTA will make Simon's display file source look like this:
02 A R SCREEN1
03 A LOCK
A FRCDTA
04 A 3 3'What has finished :'
05 A FLD001 50 O 3 23
This does not require changes to the CRTDSPF command and you do not have to remember to change DFRWRT() when you compile :-)
I saw a program working like that, but I never figured out how was created and conceived... Your post is very interesting!!! Thanks
ReplyDeleteI have the situation where i need to give the option to exit from screen to the user.that is F03 to exit..how can we do that
ReplyDeleteAs the LOCK keyword is in the display file you cannot use F3.
DeleteThe way I cancel these kind of jobs is I have them check a data area after the delay. If the data error is let's say "1" the program will end.
The data area would be set by another program on a different menu option.