Pages

Wednesday, August 26, 2015

Display screens of results without having to press Enter

dspf lock rcvf

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 ;

Return

11 comments:

  1. I must add that you have to do a change of DSP file changing the DFRWRT VALUE from *YES To *NO
    CHGDSPF FILE(Libr/SCREEN) DFRWRT(*NO)

    ReplyDelete
  2. 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.

    Chris Ringer

    ReplyDelete
  3. 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.

    ReplyDelete
    Replies
    1. You can change dspf record wait time to timeout value, use write and read commands instead of exfmt and catch read error in pgm.

      Delete
    2. Yes, but there is so much more you can do using that method. More next week. 😀

      Delete
  4. A 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.

    ReplyDelete
  5. If 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) ;

    D 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) ;

    ReplyDelete
  6. 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.

    Adding 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 :-)

    ReplyDelete
  7. I saw a program working like that, but I never figured out how was created and conceived... Your post is very interesting!!! Thanks

    ReplyDelete
  8. I 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

    ReplyDelete
    Replies
    1. As the LOCK keyword is in the display file you cannot use F3.
      The 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.

      Delete

To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.

Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.