Wednesday, January 30, 2019

Another type of subfile: screen at a time

screen at a time subfile

All of the examples of subfiles I have given in previous posts have been "load all" subfiles. The subfile size is at the maximum, 9,999 records, and the entire subfile is loaded all at once. When loaded the display file controls the scrolling up and down within it, without any need for code in the program.

If give a "position to" field when a value is entered in it the subfile is reloaded starting at the nearest matching record and continuing onto the end of the subfile or available records from the input file. When I have presented this to users they have understood that if they enter "Hutchinson" then the subfile will start at the closest match. If they wanted the name "Hughes", rather than "Hutchinson", they would change the value in the "position to" field rather than expect to be able to page up through subfile records less than the value "Hutchinson" until they reached "Hughes".

In my experience of working with users, in many different companies, they understood and have been satisfied with this functionality. When I have given examples in this blog I receive messages telling me that this is not acceptable, the users must be able to page up to values less than "Hutchinson". I have until now ignored these complaints as they are from IT people, not from the people who use these subfile programs every day.

Today I relent, and am giving my version of a program that would allow people to page up to records before that entered in a "position to". To accomplish this I need to use a different type of subfile, a "screen at a time" subfile. This type of subfile will only load the same number of records that are displayed on the screen. If either the page up or page down keys is pressed the program has to reload the subfile with data.

It has been so many years since I created a "screen at a time" subfile I had to find an example to use. This is based upon an example I found from IBM written in RPG3.

This subfile will display a list of names taken from the PERSONP file. I will display the names in key order, and concatenate them for display. The source for the file is simple enough that I am not going to explain it in detail.

01 A          R PERSONR
02 A            FIRSTNAME     25A
03 A            LASTNAME      30A
04 A          K LASTNAME
05 A          K FIRSTNAME

You have seen the file level keywords for the display file in many of my examples.

01 A                                      DSPSIZ(24 80 *DS3)
02 A                                      PRINT
03 A                                      INDARA
04 A                                      ERRSFL
05 A                                      REF(PERSONP)
06 A                                      CA03(03 'F3=Exit')

Line 3: As with all display files I use with RPG program I use the Indicator Area to convert the display's indicators to an indicator data structure in the RPG program.

Line 4: I always use the error subfile to display messages. Why not let have the display file handle any errors, rather than write a message subfile to do the same thing?

Line 5: I am referencing the PERSONP file so I do not have to give lengths and field types to fields from that file in this display file.

Line 6: The F3 key uses indicator 3. I know that sounds strange I would bother to mention that, but I could use any indicator. In my opinion it would not be wise to do so, but I could.

The display file consists of three record formats:

  1. Subfile
  2. Subfile control
  3. Footer

The subfile is really simple.

07 A          R SFL01                     SFL
08 A            ZRRN           2S 0H
09 A            NAME          40A  O  5  3
10 A            LASTNAME  R        H
11 A            FIRSTNAME R        H

Line 8: The subfile relative record number only needs to be two numeric as the subfile size is only 10 (see when I describe the subfile control).

Line 9: This is the field that will contain the concatenated last and first names.

Lines 10 and 11: I want to have the last and first name fields in the subfile record too, I will describe in the RPG program why. Notice that they are hidden, like the subfile relative record number, so they will not be displayed when the subfile is shown.

The subfile control record is different from that of a "load all" type subfile.

12 A          R CTL01                     SFLCTL(SFL01)
13 A                                      SFLSIZ(0010)
14 A                                      SFLPAG(0010)
15 A                                      OVERLAY
16 A                                      PAGEDOWN(25)
17 A                                      PAGEUP(26)
18 A  30                                  SFLDSPCTL
19 A  31                                  SFLDSP
20 A  32                                  SFLEND(*MORE)
21 A  33                                  SFLCLR
22 A                                  1  8'R P G P G M . C O M'
23 A                                  2  2'Screen at a time subfile+
24 A                                  3  2'Position to'
25 A            ZPOSITION R        B  3 14REFFLD(LASTNAME)
26 A                                  4  3'Person name'

Lines 13 and 14: The subfile size and page are the same, only 10 records.

Lines 16 and 17: In a "screen at a time" subfile I need to perform all the processing for page up and down, therefore, I need to use these keywords to assign indicators to these functions. In older display files you may see ROLLDOWN and ROLLUP instead, but as I do not have an old fashioned 5250 terminal keyboard, the keys on my keyboard are labelled PgUp and PgDn.

Lines 18 – 21: This are the indicators I will be using in the RPG program to initialize and display the subfile.

Line 25: The "position to" field is defined to be the same as the last name field from the PERSONP file.

I use the footer record format just to display what keys can be used with this subfile.

27 A          R FOOT01
28 A                                 23  3'F3=Exit'

And now to the RPG program, let me start with the definitions.

01  **free
02  ctl-opt option(*nodebugio:*srcstmt:*nounref)
              dftactgrp(*no) ;

03  dcl-f DSPFILE workstn indds(Dspf) sfile(SFL01:ZRRN) ;

04  dcl-ds Dspf qualified ;
05    Exit ind pos(3) ;
06    PageDown ind pos(25) ;
07    PageUp ind pos(26) ;

08    SflInds char(4) pos(30) ;
09      SflDspCtl ind pos(30) ;
10      SflDsp ind pos(31) ;
11      SflEnd ind pos(32) ;
12      SflClr ind pos(33) ;
13  end-ds ;

14  dcl-f PERSONP disk keyed ;

15  dcl-s SflSize like(ZRRN) inz(10) ;
16  dcl-s SavedPosition like(ZPOSITION) ;

Line 1: You know that I am going to write this in totally free RPG.

Line 2: My favorite control options make it easier to debug this program and reduce the size of the final object. I need the DFTACTGRP as I am using procedures within this program.

Line 3: The definition of the display file, I described above. The INDDS keywords is the other "side" of the indicator area I used in the display file. It allows me to redefine number indicators to have really meaningful names.

Lines 4 – 13: This is the redefinition of the display file's number indicators to have real names. As the data structure is qualified the subfields will need to be used prefixed with the data structure names.

Lines 8 – 12: This might confuse some. These are the indicators used to control the display, etc., of the subfile. Each indicator has an individual subfield name, lines 9 – 12. But I have also defined a character subfield, SflInds which overlays those indicators. More on why I did this later in the program.

Line 14: Definition of the input file, PERSONP.

Line 15: I have created this field to contain the maximum number that the subfile can have. I will use this variable rather than the hard-coded number. If in the future I decide to change the number of subfile records I can just change the initialized value here, not have to search the source code for where the maximum number of subfile records is used.

Line 16: I am defining this variable to hold the last value that was entered into the "position to" field. This way I can tell if the value in the "position to" has been changed.

On to the "main body" of the program.

17  SubfileDown() ;

18  dow (1 = 1) ;
19   write FOOT01 ;
20   exfmt CTL01 ;

21   if (Dspf.Exit) ;
22     leave ;
23   elseif (Dspf.PageDown) ;
24     if not(Dspf.SflEnd) ;
25       SubfileDown() ;
26     endif ;
27   elseif (Dspf.PageUp) ;
28     SubfileUp() ;
29   elseif (ZPOSITION <> SavedPosition) ;
30     setll ZPOSITION PERSONR ;
31     SubfileDown() ;
32     SavedPosition = ZPOSITION ;
33   endif ;
34  enddo ;

35  *inlr = *on ;

Line 17: Call the procedure for the page down, to perform the initial load of the subfile.

Line 21: If the F3 keyed is pressed on the screen then indicator Dspf.Exit, mapped in the indicator data structure, is on and I know to exit this Do loop.

Line 23: If the page down is pressed, see how much easier to know what this indicator is used for, I don't want to reload the subfile is I am displaying the last set of records. If the subfile end indicator, Dspf.SflEnd line 24, is on then I know I have reached the last set and am not going to do another page down routine, line 25. If it is not then I need to load the subfile with the next set of records from the file.

Line 27: If the page up was pressed I need to "move up" through the file to get the previous ten records. I do that in the SubfileUp procedure, line 28.

Line 29: I only need to perform the "position to" logic if the value in the screen field is different from that in the saved field, defined on line 16. If it has been used I need to position the file to the value in the position to field, line 30, and then execute the procedure that loads the subfile, line 31. When that is complete I need to move the value of the position to display file field to the saved position to variable, line 32.

The first procedure used by the program, SubfileDown, is the one that loads the subfile.

36  dcl-proc SubfileDown ;
37    Dspf.SflInds = '0001' ;
38    write CTL01 ;
39    Dspf.SflInds = '1000' ;

40    for ZRRN = 1 to SflSize ;
41      read PERSONR ;
42      if (%eof) ;
43        Dspf.SflEnd = *on ;
44        leave ;
45      endif ;

46      NAME = %trimr(LASTNAME) + ', ' + FIRSTNAME ;
47      write SFL01 ;
48    endfor ;

49    if (ZRRN > 1) ;
50      Dspf.SflDsp = *on ;
51    else ;
52      return ;
53    endif ;

54    if not(Dspf.SflEnd) ;
55      read PERSONR ;
56      if (%eof) ;
57        Dspf.SflEnd = *on ;
58      else ;
60      endif ;
61    endif ;
62  end-proc ;

Lines 37 – 39: Now you see why I created the subfield Dspf.SflInds. On line 37 I turn off indicators SflDspCtl, SflDsp, and SflEnd, and turn on SflClr so I clear the subfile when I write the subfile control on line 38. On line 39 I just turn on the indicator to display subfile control indicator, and leave the others off. That is a more efficient way to set these indicators without having to turn each one on or off individually.

Line 40: I use a For group to control the number of time I read the input file, PERSONP. The For will increment the value in the subfile relative record field, ZRRN until it reaches the value in the variable I defined to contain the number of records in the subfile.

Line 41: I read the file.

Lines 42 – 45: If I encounter the end of file I set on the indicator that is used to control whether "More" or "Bottom" is displayed. I then exit the For group.

Line 46: I concatenate the last and first name fields to make the name.

Line 47: When I write the subfile record remember that the last and first name fields are also included.

Line 49: After exiting the For group I test if any records were written to the subfile. If some where I set on the subfile display indicator, Dsp.SflDsp, line 50. If no records were written I return from this procedure to the "main body", line 52.

Line 54 – 61: One of the problems of using a "screen at a time" subfile is determining when the last record was read from the file, and turn on the end of subfile indicator. I have to read one more record to determine if the last record written was at the end of the file, line 55. If end of file was encountered, line 56 and 57, I set on the end of subfile indicator. If end of file was not encountered, lines 58 and 59, I need to reset the file pointer to just before the record that was read by using a SETLL.

Next procedure is the one used if the page up key was pressed to display records before the one on the current subfile.

63  dcl-proc SubfileUp ;
64    chain 1 SFL01 ;

66    for ZRRN = 1 to SflSize ;
67      readp PERSONR ;
68      if (%eof) ;
69        leave ;
70      endif ;
71    endfor ;


73    SubfileDown() ;
75  end-proc ;

Line 64: I am chaining to get the file's key fields from the first record of the current subfile.

Line 65: I use SETLL the set the file pointer just before the first record that was in the subfile.

Line 66: Another For group that does the same thing as the last one. It loops the same number of times as the size of the subfile.

Line 67: In this situation I need to read "backwards" to where the start of the "previous" subfile should be, I do this using the read prior operation code, READP.

Lines 68 – 70: If I encounter end of file, well really beginning of file, before I finish performing the For loop I exit.

Line 72: I use SETLL to position the file pointer just before the last record I read, which is going to be the first record of the subfile.

Line 73: I call the procedure SubfileDown to fill the subfile.

What does this look like when I call the program:

      R P G P G M . C O M
Screen at a time subfile example
Position to                               
 Person name
      R P G P G M . C O M
Screen at a time subfile example
Position to                               
 Person name
      R P G P G M . C O M
Screen at a time subfile example
Position to                               
 Person name

      R P G P G M . C O M
Screen at a time subfile example
Position to                               
 Person name


When I enter "M" into the position to field and press Enter the subfile is position to the first record in the file with a last name that starts with "M".

      R P G P G M . C O M
Screen at a time subfile example
Position to M                             
 Person name

When I press the page up key the subfile is loaded with the ten records from the file that were before the pattern I entered.

      R P G P G M . C O M
Screen at a time subfile example
Position to M                             
 Person name

In my opinion this is just silly. If wanted to see the people with the last name that started with "L" I would have entered "L" in the position to field instead.

The real problem I have with "screen at a time" subfiles versus the "load all" type is that if I page up, down, up, and down continuously I have performed a large number of reads of the same records from the file, wasting I/O resources, just to display the same data.


This article was written for IBM i 7.3, and should work for some earlier releases too.


  1. I've had to do something similar in the past but I took a different approach to it. My approach worked because I knew there would never be more than 9,999 records to read. What I did is load up the subfile with all of the records from the file then used the subfile records to position the screen where I wanted it. Just my two cents on this.

    1. You ought to read Using position to in a data structure array as this describes a similar manner in a load-all subfile.

    2. Your approach is exceedingly wasteful of resources. It be far faster, and easier to just load 1 page at a time, using a DB2 CURSOR whose SELECT uses the keywords LIMIT and OFFSET to jump around pages. Loading an entire SFL is junior programmer stuff...I am glad you posted anonymously.

  2. Good pivot, Simon. But not using DDL and an SQL cursor that takes advantage the SELECT keywords LIMIT and OFFSET makes for a very rudimentary example. Your example could be brought up to 2019 standards if you would use what DB2 provides and marry those features with RPG. Still, I applaud your pivot towards stateless, and one SFL page at a time SFL development.

    1. The rules are the same if you are loading a subfile from a DDS file or a DDL table, using RPG.

      Most of the people who are writing subfiles are creating them over existing DDS files from their ERP applications.

      As for how to do this with SQL you'll have to wait until next week.

  3. At the end of your blog, you speak about the additional I/O when user repeatedly presses pageup and pagedown. Just wanted to mention, the same argument can be made for a load-all subfile. For instance: If I load 100 records, but 95% of the users only need to view the first 10 records. Your applications is wasting I/O's and time for 95% of your users.

    Each subfile types have there purpose and needs.

    Good article

  4. Nicely done, thank you !


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.