Wednesday, July 26, 2017

Position cursor into field on screens

position cursor on a display file record format

The germ for this post's subject came from a two part question I was asked:

  1. How can I know which field the cursor is in when Enter is pressed?
  2. How can I position a cursor on some field without using indicators? No indicators are available in that very old screen.

Fortunately there are ways to do this by adding a few keywords and fields to the display file's DDS. In these examples I am going to use one display file with three screens, each showing a way to do what has been asked, and the RPG code needed too (and I am going to cheat on the second part).

Let me start with the file level keywords (header part) for the display file:

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

If you are regular reader of this site you should recognize my use of the indicator area, line 3, and the error subfile, line 4.

The RPG code for my program starts with these definitions:

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

03  dcl-f WA065D workstn indds(Dspf) ;

04  dcl-ds Dspf qualified ;
05    Exit ind pos(3) ;
06    PosToFields char(59) pos(40) ;
07    PosToField1 ind pos(41) ;
08    PosToField2 ind pos(42) ;
09    PosToField3 ind pos(43) ;
10  end-ds ;

Line 1: As I am on an IBM i running 7.3 I have no excuse for not using totally free RPG.

Line 2: My standard control options I use in all of my programs.

Line 3: This is the definition of the display file I will be using. The INDDS keyword shows I will be using an indicator data area, more on that below.

Lines 4 – 10: Here is the indicator data area, the other end of the INDARA keyword defined in the display file. I am not going to describe what the indicators contained within do until I use them.

And onto the first screen, the one to determine which field the cursor is in when Enter, or any other allowed function key, is pressed.

06 A          R SCREEN1
07 A                                      RTNCSRLOC(&Z1REC &Z1FLD)
08 A            Z1REC         10A  H
09 A            Z1FLD         10A  H
10 A                                  2  3'Field 1  .'
11 A            FIELD1         1A  B  2 14
12 A                                  3  3'Field 2  .'
13 A            FIELD2         1A  B  3 14
14 A                                  4  3'Field 3  .'
15 A            FIELD3         1A  B  4 14
16 A                                  6  3'Cursor is in field :'
17 A            FIELDNAME     10   O  6 24
18 A                                  7  4'and record format :'
19 A            RECFORMAT     10   O  7 24

The parts of this record format to notice are on lines 7 - 9.

Line 7: The Return cursor location keyword, RTNCSRLOC, returns to the program the field and record format names the cursor is in when Enter, or another allowed key, is pressed. The keywords contains two fields, the first to contain the name of the record format and the second for the field name. Notice how both of the field names start with the ampersand character ( & ).

Line 8: This is the field for the record format name. It needs to be 10 characters as that is the maximum length a record format name can be.

Line 9: The field name is placed in this field. This needs to be 10 characters as that is the maximum length a DDS field name should be.

The rest is fairly straight forward. Three entry fields, FIELD1, FIELD2, and FIELD3, and two output fields, FIELDNAME and RECFORMAT.

The RPG for this screen looks like:

20  dow (1 = 1) ;
21    exfmt SCREEN1 ;
22    if (Dspf.Exit) ;
23      leave ;
24    endif ;

25    FIELDNAME = Z1FLD ;
26    RECFORMAT = Z1REC ;
27  enddo ;

Yes, that's it. Just seven lines of RPG. I don't have to anything to move the display file record format name and field name into the Z1REC and Z1FLD fields, the display does that. I am just moving them to other fields, FIELDNAME and RECFORMAT so they will be displayed on the record format.

When I first call the program the screen looks like this.

Field 1  .  
Field 2  .  
Field 3  .  

Cursor is in field :
 and record format :

When I move the cursor to Field 3, and press Enter the name of the field and record format are displayed.

Field 1  .  
Field 2  .  
Field 3  .  

Cursor is in field : FIELD3
 and record format : SCREEN1

Pretty simple.

Next step is to position the cursor into a field. The record format for this example is:

21 A          R SCREEN2
22 A                                      RTNCSRLOC(&Z2REC &Z2FLD)
23 A            Z2REC     R        H      REFFLD(Z1REC  *SRC)
24 A            Z2FLD     R        H      REFFLD(Z1FLD  *SRC)
25 A                                  2  3'Field 1  .'
26 A            FIELD1    R        B  2 14REFFLD(FIELD1  *SRC)
27 A  41                                  DSPATR(PC)
28 A                                  3  3'Field 2  .'
29 A            FIELD2    R        B  3 14REFFLD(FIELD2  *SRC)
30 A  42                                  DSPATR(PC)
31 A                                  4  3'Field 3  .'
32 A            FIELD3    R        B  4 14REFFLD(FIELD3  *SRC)
33 A  43                                  DSPATR(PC)
34 A            Z2TEXT        50A     6  3

I have kept the RTNCSRLOC keyword, line 22, in this format, but changed the names of the fields. I do this as I like to keep this fields unique to the record format. As Z2REC, line 23, and Z2FLD, line 24, are the same as their equivalents in the first record format I have use a Reference Fields keyword, REFFLD, to define them.

The only difference between this and the first record format is that I have used the Display Attribute keyword to position the cursor, DSPATR(PC) on lines 27, 30, and 33, to the field. I have to use an indicator for each of these keywords to differentiate where I want to position the cursor. I know the question said no indicators, but bear with me until I explain the RPG code. I have also added a field, Z2TEXT, so I can display what I am doing.

Here is RPG I created for this screen.

30  dcl-s Screen2Indicators char(59) ;

31  Dspf.PosToFields = Screen2Indicators ;

32  Dspf.PosToField2 = *on ;
33  Z2TEXT = 'Position cursor to FIELD2' ;
34  exfmt SCREEN2 ;

35  Dspf.PosToFields = '0001' ;
36  Z2TEXT = 'Position cursor to FIELD3' ;
37  exfmt SCREEN2 ;

38  Screen2Indicators = Dspf.PosToFields ;

The questioner said that indicators could not be used as he/she did not have any left to use. Once upon a time a long, long time ago I had to maintain a big old program that had used all of the numbered indicators for the ten screens the program had. It was so long ago this was a RPGIII program that I had to maintain in RPGIII! I did need to move the cursor from field to another using DSPATR(PC), so how to do it when there were no unused indicators? I worked out I could store the indicators from each screen in a variable. I could move the contents of that variable into the indicator array at the start of the subroutine for each screen, and then move the part of the indicator array I used for screen indicators into the variable at the end of the subroutine. No, I am not going to show you how I did that in RPGIII code, I am going to show a modern RPG equivalent.

Line 30: This is the definition of that variable. It is 59 characters long, which corresponds to the indicator data structure subfield PosToFields, line 8.

Line 31: I move the contents of the variable into the indicator data structure subfield. Now the indicators are all set the way were last time this subprocedure or subroutine was executed.

Lines 32 – 34: I want to position the cursor to FIELD2, in this case I am setting on the individual indicator for this field. So now the cursor is in FIELD2:

Field 1  .  
Field 2  .  
Field 3  .  

Position cursor to FIELD2

Lines 35 – 37: To position the cursor to FIELD3 I need to turn off the indicator for FIELD2. I could set the FIELD2 indicator off on one line of code, and then set the FIELD3 indicator on on the next line. But why do that when I can do the same on one line, line 35. The cursor is now in FIELD3.

Field 1  .  
Field 2  .  
Field 3  .  

Position cursor to FIELD3

Line 38: Before my subprocedure or subroutine ends I want to move those indicator values to my variable I defined on line 30. After this line is executed if I am in debug and I look at the value in this variable I can see that indicator 43, position cursor to FIELD3 is on.

> EVAL Screen2Indicators
  SCREEN2INDICATORS =
   ----+----1----+----2----+----3 
  '000100000000000000000000000000
   00000000000000000000000000000'

I think this gives a good method of how I can still use indicators for positioning the cursor. If I had to absolutely not use indicators then I have to position the cursor by giving the fields position on the screen by line and column. This record format is pretty much the same as the last one.

37 A          R SCREEN3
38 A                                      RTNCSRLOC(&Z3REC &Z3FLD)
39 A                                      CSRLOC(Z3LINE Z3COLUMN)
40 A            Z3REC     R        H      REFFLD(Z1REC  *SRC)
41 A            Z3FLD     R        H      REFFLD(Z1FLD  *SRC)
42 A            Z3LINE         3S 0H
43 A            Z3COLUMN       3S 0H
44 A                                  2  3'Field 1  .'
45 A            FIELD1    R        B  2 14REFFLD(FIELD1  *SRC)
46 A                                  3  3'Field 2  .'
47 A            FIELD2    R        B  3 14REFFLD(FIELD2  *SRC)
48 A                                  4  3'Field 3  .'
49 A            FIELD3    R        B  4 14REFFLD(FIELD3  *SRC)
50 A            Z3TEXT    R           6  3REFFLD(Z2TEXT  *SRC)
51 A                                 23  3'F3=Exit'

The significant differences are:

Line 39: The Cursor Location keyword, CSRLOC, allows me to position the cursor anywhere on the screen. This keyword had two fields contained within, one for the line number, Z3LINE, and the other for the column, Z3COLUMN.

Lines 40 and 41: These two fields need to be defined as three signed numeric.

40  Z3TEXT = 'Position cursor to FIELD2' ;
41  Z3LINE = 3 ;
42  Z3COLUMN = 14 ;
43  exfmt SCREEN3 ;

44  Z3TEXT = 'Position cursor to FIELD3' ;
45  Z3LINE = 4 ;
46  Z3COLUMN = 14 ;
47  exfmt SCREEN3 ;

48  Z3TEXT = 'Somewhere else' ;
49  Z3LINE = 7 ;
50  Z3COLUMN = 7 ;
51  exfmt SCREEN3 ;

Lines 40 – 43: To position the cursor to FIELD2 I just need to give its location.

Field 1  .  
Field 2  .  
Field 3  .  

Position cursor to FIELD2

Lines 44 – 47: I do the same for FIELD3.

Field 1  .  
Field 2  .  
Field 3  .  

Position cursor to FIELD3

But I can position the cursor anywhere, even if there is not a field there.

Field 1  .  
Field 2  .  
Field 3  .  

Somewhere else
     

My concern in using this CSRLOC approach is if I move a field's position on the screen I have to make sure I change its "coordinates" in the RPG too. If I forget to change the RPG the cursor will position where the field is not. This is why I prefer the DSPATR(PC) method. I can move the field anywhere in the record format and not have to change the RPG code.

Some people are bound to ask: "Why not use Program to System fields?"

Alas, one of the few Display Attributes that Program to System fields do not emulate is DSPSATR(PC).

If you are working with a subfile there are times were you want to position a particular subfile record to the top of the display, you can see how to do that in this post about positioning records in a subfile display.

 

You can learn more about this from the IBM website:

 

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

3 comments:

  1. Nice topic,
    It's a very old technic. The limit is that you must evaluate the exact position of the fields, and that it can change on the screen.
    One issue is to use the QUSLFLD API by encapsulating it into a service program wich will retreive the field's line and row.
    Finaly, you just need to insert in your program a procedure like this :

    DCL-PROC setCursor;

    DCL-PI *N;
    p_file CHAR(10) CONST;
    p_record CHAR(10) CONST;
    p_field CHAR(10) CONST;
    END-PI;

    dsp_rtvPosition(
    p_file
    :p_record
    :p_field
    :z3column
    :z3line
    );


    return;
    END-PROC;

    and you can use it ! :

    setCursor('WA065D':'SCREEN3':'FIELD3'); //will position cursor on FIELD3.

    ReplyDelete
    Replies
    1. I must be missing something in what you mean in your comment.

      If I use the RTNCSRLOC and the DSPATR(PC) I know what record format and field the cursor is in, and can place it into the field I want just be use of an indicator.

      If I move the field to another position on the display I do not have to change any other code as the RTNCSRLOC will still tell me which field the cursor is in, and the DSPATR(PC) will still work too.

      What am I missing from your comment?

      (I do admit subfiles are the exception to this)

      Delete
  2. Sorry my english is very bad :

    I thought you wanted positionning cursor on a particular field (wich doesn't systematically correspond to current position returned by RTNCSRLOC) without using indicators, for example on a field in error.

    In my example a procedure dsp_rtvPosition retrieves the position of a named field and then CRSLOC puts cursor on it.

    ReplyDelete

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.