Midrange News for the IBM i Community

Dynamic Popup Window TextBox Published by: Bob Cozzi on 06 Sep 2011 view comments(2)
© 2011 Robert Cozzi, Jr. All rights reserved.

© Robert Cozzi, Jr. All rights reserved. Reproduction/Redistribution prohibited.
A midrangeNews.com Publication

Popup Windows API

Believe it or not, back in the early 1980's I wrote the first application that demonstrated the ability to display popup/overlapping windows on 5250 green screens. Initially I used the cryptic User-Defined Data Stream, but eventually figured out how to do the same thing using the KEEP, ASSUME and PUTOVR keywords. The CLRL(*NO) keyword was a big help when it was introduced a couple years later.

The application I created was named PARTNER and when I ported it to the AS/400 in 1988 it was named Partner/400. This app contains a popup calendar, notepad, adding machine (with a scrolling tape), calculator, popup menu, address book, and more. If you ever used Borland's SideKick product back on PC DOS, PARTNER did just about everything SideKick did, and worked with 5250 screens.

IBM's first app with any kind of a popup window appeared in the Spell Checker in IBM Text Management. A list of words (spelling alternatives) would popup during a spell-check request.

Recently while helping one of my clients upgrade to IBM i v7r1, I ran across several custom Display files that were used to display text to an end-user. Each display panel had hard-coded text on it that was inside a format that used the WINDOW keyword. When a new message was necessary, they would copy the existing display file DDS source with the windowed text on it, then bring it into SDA, modify the text, save and compile it. This worked well for them but made me realize that tracking all those effectively unnecessary objects wasn't a good choice.

Sponsored by: BCD Software

My own RPG xTools has long included an AskUser subprocedure that pops up a window with user-supplied text, asking the end-user a Yes/No question and receiving a response from the user. Similar to an Inquiry Message but with a popup window design. This client's need, however, was for output-only--no end-user response (other than Enter) was needed. Something like an INFO break message but with more text.

Rather than continue their technique, I decided to start using the IBM-supplied QUILNGTX (Display Popup Message) API. This API displays a fixed-size window overtop of the current screen and displays user-supplied text. A "user" in this context means "programmer", of course.  Unlike most IBM i APIs, this one is remarkably easy to use and has only a couple of bogus parameters.  Here's the prototype:

.....D QUILNGTX        PR                  extPgm('QUILNGTX')
     D  szMsgText                     1A   OPTIONS(*VARSIZE)
     D  nMsgLen                      10U 0 CONST
     D  szMsgID                       7A   CONST
     D  szMsgFile                    20A   CONST
     D  api_error                          Like(QUSEC)
     D                                     OPTIONS(*VARSIZE)

The reason I selected this API is that you can send it a string of characters and it word-wraps text to the window and even displays the infamous "More..." at the bottom when there's more text to view.  This allows more text to be sent to the popup window than will fit in the window--while allowing the end-user to scroll through with the Page Up/Down keys. Here's a quick screen shot of what it looks like in action:

Example QUILNGTX API in Action

You can see that I've popped up this window over the PDM Member list display. Unfortunately, you cannot adjust the height or width of the popup window and it nearly covers the entire screen, so its use may be limited in some applications.

Note the F12=Cancel function key is enabled automatically to return to the caller, while F3 is not. However I have discovered that the ENTER key also works with this API, returning to the caller just like pressing F12.


This API has 5 required parameters. That is all parameters are required, but only the first two are useful in most situations. It would be nice if the first two were required and the rest were optional

Parm 1: Text to display. This parameter may contain (and a kid you not) up to 15728640 characters of text that are displayed in the window. I'm speculating that the subfile limit of 16MB has some structural overhead and that 15728640 figure is 16MB minus that overhead. So you can display as much text as you need. Note that you do have to be on V7r1 to start using 16MB field sizes otherwise you may have to use C APIs to prepare a variable of that size.

Parm 2: Text Length. Specify the length of the text being passed on parameter 1. Like all APIs, the length parameter is a 4-byte integer. in RPG IV you declare a 4-byte integers as a 10i0 variable.

Parm 3: Message ID. If you would like a "box title" to appear, you may specify a CPF message ID (or user message ID) that contains the title text. The title is centered in the window. The message ID must exist in the message file specified on the 4th parameter.

Parm 4: Message File. The name of the message file and its library where the message ID (parameter 3) exists. This is a standard 20 position object - library parameter.

Parm 5: API Error. The standard exception/error API used by many APIs until IBM decided to change it in some APIs but not others. The structure is QUSEC in QRPGLESRC in the QSYSINC library.

Practical Example

To illustrate this API I've written a program that loads a text file stored on the IFS into a variable and then displays that text. This allows me to use a PC-style editor to edit the text, or the editor built into the IFS interfaces. The example loads the entire file and sends the text from that file to the API. Here's the example code:

      /include qsysinc/qrpglesrc,qusec
      /include qsysinc/qrpglesrc,ifs
     D QUILNGTX        PR                  ExtPgm('QUILNGTX')
     D  szMsgText                 65535A   CONST OPTIONS(*VARSIZE)
     D  nMsgLen                      10U 0 CONST
     D  szMsgID                       7A   CONST
     D  szMsgFile                    20A   CONST
     D  api_error                          Like(QUSEC)
     D                                     OPTIONS(*VARSIZE)

         // Max Len is 15728640
     D len             S             10I 0
     D msg             S           4096A
     D LF              S              1A   Inz(X'0D')
     D CR              S              1A   Inz(X'25')
     D apiError        DS                  LikeDS(QUSEC) Inz
     C                   move      *ON           *INLR

         len = loadDailyMsg( msg : %size(msg) );
         msg = %ScanRpl( LF : ' ' : msg);
         msg = %ScanRpl( CR : ' ' : msg);
         quilngtx(msg : len :' ' : ' ' : apiError);

     P LoadDailyMsg    B                   Export
     D LoadDailyMsg    PI            20I 0
     D  szDailyMsg                 4096A
     D  length                       10I 0 Const

     D nBytes          S             20I 0
     D nFlags          S             10I 0
     D ifsFile         S            640A   Varying

     D hFile           S             10I 0
           ifsFile = '/home/daily.txt';
           nFlags = %BITOR(O_RDONLY : O_TEXTDATA);
           hFile = open(ifsFile : nFlags );
           if hFile < 0;
              return 0;
           nBytes = read(hFile : %addr(szDailyMsg) : Length);

           if (nBytes <= 0);
             return 0;

           callp close(hFile);
           return nBytes;
     P LoadDailyMsg    E

The subprocedure LoadDailyMsg loads the content of the IFS file named DAILY.TXT into a return variable. That text data is the filtered for ASCII-style CR/LF characters as these characters display on 5250 as little green boxes. So I xlate them using the %SCANRPL built-in function (see the highlighted lines in the above example).

To create the DAILY.TXT file I simply started Windows NOTEPAD application, typed in the text that appears in the example window above, and saved it. Then I FTP'd it up to the host, into the /HOME directory. The LoadDailyMsg subprocedure opens that file as an ASCII text file, as read-only. Then it reads the entire file in one input/read operation. The number of bytes read is returned by the READ API and is used by the QUILNGTX API as the length of the text being displayed.

Note the CALLP near the bottom of the LoadDailyMsg subprocedure; this is required when using the IBM-supplied prototypes. The READ, WRITE, OPEN, CLOSE IFS APIs have the same names as native RPG IV operation codes. This creates ambiguity that the compiler can not sort out. To help the compiler, the context in which these APIs are used must be cleared up. So the implied EVAL operation on the OPEN and READ APIs and the CALLP on the CLOSE put these APIs in context.  I prefer to change the prototype names, myself, as using IFSOPEN, IFSCLOSE, IFSREAD, and IFSWRITE is more clear to me than using APIs whose name match RPG IV operation codes. But IBM provided these API prototypes so we're stuck with them unless you copy them and alter their names.


Calling the QUILNGTX API

Calling the API in RPG IV requires the use of the prototype feature earlier in this article. To call this API from within ILE CL, you have to declare the length variable and also determine the length of the text being displayed. Sadly, CL still does not have any useful string handling features, such as %LEN or %CHAR so you have to force it. Here's a simply call to QUILNGTX from within CL:

            DCL        VAR(&MSGTEXT) TYPE(*CHAR) LEN(64)
            DCL        VAR(&LEN) TYPE(*INT) LEN(4)
            DCL        VAR(&QUSEC) TYPE(*CHAR) LEN(16) +
            MONMSG     MSGID(CPF0000)
            CHGVAR     VAR(&MSGTEXT) VALUE('The batch has been +  
                         sent. Press Enter to continue.') 
            DOFOR      VAR(&LEN) FROM(64) TO(1) BY(-1)
               if (%sst(&msgtext &len 1) *NE ' ') LEAVE

In this example, I use the relatively new DOFOR CL command to loop backwards through the MSGTEXT variable until I find a non-blank. The LEAVE command causes the DOFOR loop to end when a non-blank character is detected. Effectively this means that when the DOFOR loop ends, the LEN variable will contain the length of the text in the MSGTEXT field. (You're welcome!)

 The call to the QUILNGTX API passes only the MSGTEXT and LEN values. The message ID, message file and QUSEC parameters are specified with place-holders as they are not used by this example. The first 8 bytes of QUSEC are set to hex-zeros so that the API knows that no exception/error messages should be returned to the QUSEC variable.

The QUILNGTX API isn't the coolest API around, but it does solve a problem--dynamically displayed text without the need to design, code, compile and stub-out displays files just to inform the end-user with an interface beyond that of a simply SNDPGMMSG command.

Call Me

Bob Cozzi has been providing the solutions to midrange problems, in the form or articles and books since 1983. He is available for consulting/contract development or on-site RPG IV, SQL, and CGI/Web training. Currently many shops are contracting with Cozzi for 1 to 3 days of Q&A and consulting with their RPG staff. Your staff gets to ask real-world questions that apply to their unique development situations. To contact Cozzi, send an email to: bob at rpgworld.com

Bob also accepts your questions for use in future RPG Report articles or content for midrangeNews.com. Topics of interest include: RPG IV, Web development with RPG IV, APIs, C/C++ or anything else IBM i development related (except subfiles, data areas and RPGII/III because Bob doesn't care about that stuff) write your questions using the COMMENTS link at the bottom of any RPG Report article on the midrangeNews.com website. 

You can subscribe to RPG Report (we call it "follow") by visiting the RPG Report page on midrangeNews.com and then click the FOLLOW link in the table of contents for that page. To unsubscribe, simply click that same link. You must be signed up and signed in to midrangeNews.com to start following RPG Report.

Follow Bob Cozzi on Twitter

Return to midrangenews.com home page.
Sort Ascend | Descend


(Sign in to Post a Comment)
Posted by: bobcozzi
Site Admin ****
Comment on: RPG Report - 06 Sept 2011
Posted: 8 years 6 months 28 days 18 hours 30 minutes ago
Edited: Tue, 06 Sep, 2011 at 14:20:30 (3132 days ago)

Also note that I used the %BITOR() built-in function (line 40) in the above example. This is the correct way to combine IFS open-Flags. While adding them together using the plus sign (+) will work in this case (these flags were designed to work with + or OR) using the %BITOR built-in function is the proper way to combine C-style flags.

While adding flags will work with the 'open' and 'open64' APIs, doing so is building a bad habit--a habit that may cause you to be posting here in the future as to why some other IFS APIs don't work with your flags

Use %BITOR and it will always work and you'll build a good habit.

Posted by: bobcozzi
Site Admin ****
Comment on: RPG Report - 06 Sept 2011
Posted: 8 years 6 months 28 days 13 hours 52 minutes ago
Edited: Tue, 06 Sep, 2011 at 16:30:18 (3132 days ago)

Some people are asking about the first parameter in the prototype for the article's API being Char(1) vs Char(16MB).

When a parameter is passed by reference (the default way) and the CONST keyword is NOT specified, then your variable is passed to the called program or procedure "as is". Look at the 2nd line in this prototype:

.....D QUILNGTX        PR                  extPgm('QUILNGTX')
     D  szMsgText                     1A   OPTIONS(*VARSIZE)
     D  nMsgLen                      10U 0 CONST
     D  szMsgID                       7A   CONST
     D  szMsgFile                    20A   CONST
     D  api_error                          Like(QUSEC)
     D                                     OPTIONS(*VARSIZE)

 In the context of this prototype, the length of "1A" for the szMsgText parameter is effectively irrelevant.  Any length variable may be passed and a simple reference to that parameter variable is what is passed. So when accessing the value in the subprocedure, you can access the full variable.  Certainly RPG IV would restrict its access, but once you de-reference the parameter you can access everything passed to it.

So specifying a value of "1A  OPTIONS(*VARSIZE)" is almost like saying "accept any size parameter".