The idea for this post came from a question that was asked on Facebook. Someone wanted to create a subfile that would be projected onto a screen at an event. The subfile would scroll to the next screen of results every so many seconds without the need for someone to press a key on the keyboard.
I was surprised by the replies to the question, some said it was not possible, others gave over complicated examples of how they thought it could be achieved. I knew that to do this was simple, and all the information needed to do this exists in various posts on this website.
I decided to write a program to do what was asked before. I decided to make this a little more interesting:
- Sort the data by the person's name
- Subfile is displayed in person's name order
- Sort the data by the place of birth
- Subfile is displayed in place of birth order
- Return to number 1
First I need to show you the source of data. The DDL table PERSON contains just three columns. I am not going to explain what the columns contain as I think their long names do that for me:
01 CREATE TABLE MYLIB.PERSON 02 (FIRST_NAME FOR "FNAME" VARCHAR(25), 03 LAST_NAME FOR "LNAME" VARCHAR(30), 04 PLACE_OF_BIRTH FOR "PLACEBIRTH" VARCHAR(50)) ; |
My display file has three record formats:
- SFL01: Subfile record format
- CTL01: Subfile control record format
- SCREEN2: Just a regular non-subfile record format
01 A DSPSIZ(24 80 *DS3) 02 A INDARA *------------------------------------------------------- 03 A R SFL01 SFL 04 A SFLRRN 4Y 0O 2 2EDTCDE(Z) 05 A NAME 30A O 2 7 06 A PLACE 30 O 2 38 *------------------------------------------------------- 07 A R CTL01 SFLCTL(SFL01) 08 A SFLSIZ(9999) 09 A SFLPAG(0010) 10 A OVERLAY 11 A LOCK 12 A 31 SFLDSP 13 A 30 SFLDSPCTL 14 A N30 SFLDLT 15 A 30 SFLEND(*MORE) 16 A SCREENTOP 4S 0H SFLRCDNBR(CURSOR *TOP) 17 A 1 2'Auto-scrolling subfile' 18 A DSPATR(HI) *------------------------------------------------------- 19 A R SCREEN2 20 A LOCK 21 A 1 2'Going back to the + 22 A first record in the SFL' 23 A DSPATR(HI) 24 A 2 2'Changing sort order' |
Interesting things to note in this display file's definition are:
Line 2: I always use the indicator area/data structure for the indicator handling between a display file and a RPG program as it allows me to give the indicators meaningful names in the program.
Lines 8 and 9: This is a "load all" subfile, therefore, the subfile size is greater than the subfile page number.
Lines 11 and 20: The LOCK keyword prevents keyboard input while these record formats are displayed.
Line 16: The SFLRCDNBR(CURSOR *TOP) keyword means that when I put a number into the field SCREENTOP it will position the subfile to display that number record at the top of the screen.
When I compile this display file I must ensure that Defer Write parameter must be "*NO":
Defer write . . . . . . DFRWRT > *NO |
I think the RPG program is simple. The definition part of the program is:
01 **free 02 ctl-opt option(*nodebugio:*srcstmt) dftactgrp(*no) ; 03 dcl-f TESTDSPF workstn indds(Dspf) sfile(SFL01:SFLRRN) ; 04 dcl-ds Dspf qualified ; 05 SflDspInds char(2) pos(30) ; 06 end-ds ; 07 dcl-ds Data qualified dim(100) ; 08 PersonName char(40) ; 09 PlaceOfBirth char(50) ; 10 end-ds ; 11 dcl-s Rows int(5) inz(%elem(Data)) ; 12 dcl-s SortOrder ind ; |
Line 1: In 2021 it has to be totally free RPG.
Line 2: My favorite control options.
Line 3: Definition of the display file. The INDDS gives the name of the indicator data structure.
Lines 4 – 6: The definition of the indicator data structure. One of the many things I like about indicator data structures is that I do not have to define every subfield as an indicator. Here I have defined a two long character field that overlaps indicators 30 and 31.
Lines 7 – 10: This data structure array that will contain the data I fetch from the DDL table.
Line 11: This variable contains the number of elements that the Data data structure array has. I will be using this in the SQL fetch.
Line 12: I will explain the use of this variable when I use it in the program.
Now to the "main" part of the program.
13 GetData() ; 14 dow (1 = 1) ; 15 LoadSubfile() ; 16 SCREENTOP = 1 ; 17 dou (SCREENTOP > Rows) ; 18 write CTL01 ; 19 exec sql CALL QSYS2.QCMDEXC('DLYJOB DLY(2)') ; 20 SCREENTOP += 10 ; 21 enddo ; 22 write SCREEN2 ; 23 exec sql CALL QSYS2.QCMDEXC('DLYJOB DLY(3)') ; 24 enddo ; 25 *inlr = *on ; |
Line 13: I fetch the data from the file into the Data structure array in this subprocedure. I will explain more below.
Lines 14 – 24: What I call a "never ending loop". This loop will be performed until I cancel the program.
Line 15: I load the subfile from Data.
Line 16: By moving 1 to SCREENTOP the first record of the subfile will be displayed. SCREENTOP has to contain a number that is the equivalent of a record in the subfile. If it does not, for example it is zero, then I will receive a "session or device error".
Lines 17 – 21: This loop is performed until all of the subfile records are displayed.
Line 18: I write the subfile control record so that the program continues without needing external intervention.
Line 19: I prefer to use the SQL QCMDEXC procedure as all I have to pass to it is the string I want executed, no need for string length etc. Here I delay the program by two seconds, meaning that the screen I displayed will be seen for two seconds before the program advances to the next statement.
Line 20: To advance to the next screen all I have to do is to increment SCREENTOP with the size of the subfile page, 10. This ensures that the 11th, 21st, 31st, 41st, etc. record of the subfile is the next one displayed.
When the value of SCREENTOP is greater than the number of rows of data fetched from the DDL table I exit this loop.
Line 22: I write SCREEN2, which informs the user that I have finished display all of the subfile entries.
Line 23: I delay the job again to give the user time to read it. Then the program loops up to line 14.
The GetData subprocedure is what gets the data from the DDL table. This is only executed once, as when the data has been loaded into the Data data structure I use it rather than have to get more data from the DDL table.
26 dcl-proc GetData ; 27 exec sql DECLARE C0 CURSOR FOR 28 SELECT LAST_NAME || ', ' || FIRST_NAME, 29 IFNULL(PLACE_OF_BIRTH,' ') 30 FROM PERSON 31 FOR READ ONLY ; 32 exec sql OPEN C0 ; 33 exec sql FETCH C0 FOR :Rows ROWS INTO :Data ; 32 exec sql GET DIAGNOSTICS :Rows = ROW_COUNT ; 34 exec sql CLOSE C0 ; 35 end-proc ; |
Lines 27 – 31: This is my SQL statement to get the data from the DDL table.
Line 28: The first column of the result is the combined name of the person. As the LAST_NAME column is variable length I do not have to trim it before concatenating it with FIRST_NAME.
Line 29: I know that I do not have the place of birth for some of the people, in this DDL table this is null. For the place of birth I am using the IFNULL function to convert any null values to blanks.
Line 33: Here I am doing the multiple row fetch from the DDL table into the data structure array, Data. Multiple row fetches are so much faster than a RPG Read operation or SL single row fetch to get all the data from a DDL table or DDS file.
Line 32: Using GET DIAGNOSTICS I can get the number of rows that were fetched from the file. I can now use the value in the variable Rows when I load the subfile.
The other procedure is the one that loads the subfile from the data structure array.
36 dcl-proc LoadSubfile ; 37 if (SortOrder) ; 38 sorta %subarr(Data : 1 : Rows) %fields(PlaceOfBirth : PersonName) ; 39 SortOrder = *off ; 40 else ; 41 sorta %subarr(Data : 1 : Rows) %fields(PersonName : PlaceOfBirth) ; 42 SortOrder = *on ; 43 endif ; 44 Dspf.SflDspInds = '00' ; 45 write CTL01 ; 46 Dspf.SflDspInds = '11' ; 47 for SFLRRN = 1 to Rows ; 48 NAME = Data(SFLRRN).PersonName ; 49 PLACE = Data(SFLRRN).PlaceOfBirth ; 50 write SFL01 ; 51 endfor ; 52 end-proc ; |
Line 37 – 43: I can only perform this code as I have the Fall 2021 Technology Refresh for IBM i 7.4 TR5 and 7.3 TR11.
Line 37: Now you see how I am using the indicator SortOrder.
Line 38: If SortOrder is "on" then I will sort the data structure array by the place of birth and person's name. I have to use the %SUBARR built in function to only sort the part of the array that contains data, which is from the first element to the number of rows retrieved. If I did a SORTA without the %SUBARR the unused elements of the array would come first.
Line 41: If the indicator is "off" I sort the data structure array by person's name and place of birth.
Lines 44 – 46: Here I set "off" both of the indicators that condition the subfile in the display file. This will delete the subfile if one exists. Then by setting them "on" the subfile and its contents will be displayed when I write the control record.
Lines 47 – 51: Here I load the subfile. I use a For group and perform the loop the same number of times as there were rows fetched from the DDL table.
I think that is a simple program.
So what does that look like when it is run:
Auto-scrolling subfile 1 ALLEN, REG MARYLEBONE 2 ASTON JR, JOHN MANCHESTER 3 BENNION, RAYMOND 4 BERRY, JOHNNY ALDERSHOT 5 BIRCH, BRIAN SALFORD 6 BLANCHFLOWER, JACKIE BELFAST 7 BOND, ERNEST PRESTON 8 BULLOCK, JAMES GORTON 9 BYRNE, ROGER GORTON 10 CAREY, JOHNNY DUBLIN More... |
Auto-scrolling subfile 11 CASSIDY, LAURENCE MANCHESTER 12 CHESTERS, ARTHUR SALFORD 13 CHILTON, ALLENBY SOUTH HYLTON 14 CLEMPSON, FRANK SALFORD 15 COCKBURN, HENRY ASHTON-UNDER-LYNE 16 CROMPTON, JACK HULME 17 DALE, WILLIAM MANCHESTER 18 DOWNIE, JOHN LANARK 19 GALLIMORE, STANLEY BUCKLOW HILL 20 GIBSON, DONALD MANCHESTER More... |
Auto-scrolling subfile 21 HILDITCH, LAL HARTFORD 22 HOPKINSON, SAMUEL KILLAMARSH 23 JONES, MARK WOMBWELL 24 JONES, THOMAS STANTON HILL 25 LYDON, GEORGE NEWTON HEATH 26 MCGLEN, WILLIAM BEDLINGTON 27 MCLENAHAN, HUGH WEST GORTON 28 MCNULTY, THOMAS SALFORD 29 MCSHANE, HAROLD HOLYTOWN 30 MELLOR, JACK OLDHAM More... |
Auto-scrolling subfile 31 PARKER, THOMAS ECCLES 32 PEARSON, STAN SALFORD 33 RAMSDEN, CHARLES BUCKLOW 34 REDMAN, BILLY MANCHESTER 35 ROWLEY, JACK WOLVERHAMPTON 36 SILCOCK, JACK WIGAN 37 STEWARD, ALFRED MANCHESTER 38 WALTON, JOHN HORWICH 39 WHITEFOOT, JEFFREY CHEADLE 40 WILLIAMS, FRANK CEFN-Y-BEDD More... |
Auto-scrolling subfile 41 WILSON, JACK LEADGATE Bottom |
Going back to the first record in the SFL Changing sort order |
Now the program goes back into the LoadSubfile subprocedure, where it sorts the data structure array by place of birth and name.
Auto-scrolling subfile 1 BENNION, RAYMOND 2 BERRY, JOHNNY ALDERSHOT 3 COCKBURN, HENRY ASHTON-UNDER-LYNE 4 MCGLEN, WILLIAM BEDLINGTON 5 BLANCHFLOWER, JACKIE BELFAST 6 RAMSDEN, CHARLES BUCKLOW 7 GALLIMORE, STANLEY BUCKLOW HILL 8 WILLIAMS, FRANK CEFN-Y-BEDD 9 WHITEFOOT, JEFFREY CHEADLE 10 CAREY, JOHNNY DUBLIN More... |
Auto-scrolling subfile 11 PARKER, THOMAS ECCLES 12 BULLOCK, JAMES GORTON 13 BYRNE, ROGER GORTON 14 HILDITCH, LAL HARTFORD 15 MCSHANE, HAROLD HOLYTOWN 16 WALTON, JOHN HORWICH 17 CROMPTON, JACK HULME 18 HOPKINSON, SAMUEL KILLAMARSH 19 DOWNIE, JOHN LANARK 20 WILSON, JACK LEADGATE More... |
Auto-scrolling subfile 21 ASTON JR, JOHN MANCHESTER 22 CASSIDY, LAURENCE MANCHESTER 23 DALE, WILLIAM MANCHESTER 24 GIBSON, DONALD MANCHESTER 25 REDMAN, BILLY MANCHESTER 26 STEWARD, ALFRED MANCHESTER 27 ALLEN, REG MARYLEBONE 28 LYDON, GEORGE NEWTON HEATH 29 MELLOR, JACK OLDHAM 30 BOND, ERNEST PRESTON More... |
Auto-scrolling subfile 31 BIRCH, BRIAN SALFORD 32 CHESTERS, ARTHUR SALFORD 33 CLEMPSON, FRANK SALFORD 34 MCNULTY, THOMAS SALFORD 35 PEARSON, STAN SALFORD 36 CHILTON, ALLENBY SOUTH HYLTON 37 JONES, THOMAS STANTON HILL 38 MCLENAHAN, HUGH WEST GORTON 39 SILCOCK, JACK WIGAN 40 ROWLEY, JACK WOLVERHAMPTON More... |
Auto-scrolling subfile 41 JONES, MARK WOMBWELL Bottom |
Going back to the first record in the SFL Changing sort order |
As the indicator is "on" the data structure array is sorted by name and place of birth, then repeats itself until I cancel the program.
This article was written for IBM i 7.4 TR5 and 7.3 TR11.
“Thanks for sharing”
ReplyDeleteSimon, thanks for sharing.. great write up and examples. Keep the teaching moments coming and I will keep reading and learning,, again, thanks..
ReplyDeleteVery elegant example. Lots to learn here!
ReplyDeleteGreat article as always.
ReplyDeleteThere's a wee typo in the create table statement.
It should be :
CREATE TABLE MYLIB.PERSON (
FIRST_NAME FOR "FNAME" VARCHAR(25),
LAST_NAME FOR "LNAME" VARCHAR(30),
PLACE_OF_BIRTH FOR "PLACEBIRTH" VARCHAR(50)) ;
Oops! Thank you for bringing that to my attention. I have made the correction.
DeleteI'd suggest to look at the INVITE keyword.
ReplyDeleteVery informative. Thank you for sharing.
ReplyDeleteThank you for share
ReplyDeleteThanks for sharing
ReplyDeleteAnother endless loop:
ReplyDeleteDoU (%ShtDn);
Ringer