Standard Grid Control Tutorial 1: Display-Only Grid

For BBj-specific tutorials, see Grid Tutorial 1- Standard Grid Using SENDMSG() functions and Grid Tutorial 2 - Standard Grid Using BBjGrid Methods.

The Visual PRO/5 grid control comes with dozens of features for formatting and manipulating information; most programs will only use a few of the available features.

In general, there are three steps to follow when working with a standard grid control.

  1. Create the basic grid control using either a resource file (ResBuilder and the 'RESOURCE' mnemonic) or the 'GRID' mnemonic.

  2. Define various grid attributes using SENDMSG() functions.

  3. Have the user interact with the grid using Notify events processed in an event loop.

The application in this example is a simple ASCII table. It shows a complete character set in a 16x16 grid. A menu system is provided for changing the current font. Double-clicking on a cell displays the character from that cell, as well as its decimal and hexadecimal values.

Defining the Window and Menu

  1. Open the SYSGUI device (it is assumed that the device is defined in the config.bbx file as ALIAS X0 SYSGUI) and create a main window for this application. Define a menu bar (option flag $0800$) and a close button (option flag $0002$).

    sysgui=unt
    open (sysgui)"X0"
    print (sysgui)'window'(20,40,580,450,"ASCII Table: Double-click any cell",
    : $00000802$,$$)

  2. Get a list of all the fonts defined in the system and create a Font menu:

    menu$="&Font,9999,,"+$0a$
    fonts=pos($0a$=ctrl(sysgui,0,9),1,0)
    dim font$:"name["+str(fonts)+"]:c(16*=10)"
    font$=ctrl(sysgui,0,9)
    for font=1 to fonts

    menu$=menu$+" "+font.name$[font]+","+str(font)+",,"+$0a$
    next font
    menu$=menu$+$0a$
    print (sysgui)'setmenu',menu$

    Here is how the window will appear immediately after executing the code written thus far:

    stdgrd01.png

Creating the Grid Control

  1. Define control IDs for the main grid, column headings, and row headings:

    grid=100,colid=101,rowid=102

  2. Create the base grid control. The table following the code explains the meaning of the mnemonic arguments:

    print(sysgui)'grid'(grid,20,20,544,430,$9846$,16,16,16,20,colid,20,rowid)

    Argument

    Description

    grid

    Grid control ID (main)

    20,20,544,430

    Grid location and size (X,Y,W,H)

    $9846$

    $8000$ = Vertical lines between columns

    $1000$ = 3-dimensional raised edge

    $0800$ = 3-dimensional client edge

    $0040$ = Horizontal lines between rows

    $0004$ = Grid has a row heading

    $0002$ = Grid has a column heading

    16

    Initial number of rows

    16

    Initial number of columns

    16

    Maximum number of columns

    20

    Width of the grid column heading

    colid

    Grid control ID (column heading)

    20

    Height of the grid row heading

    rowid

    Grid control ID (row heading)

    For this example, the window and grid are defined using Visual PRO/5 code. In a production program, it is usually easier to define windows and controls using ResBuilder. Column and row headings are separate grids that work with the main grid.

    The following is an example of the grid immediately after executing the 'GRID' mnemonic:

    stdgrd02.png

  3. Use Set Up Grid - SENDMSG() Function 30 to set column widths to 32 pixels each, not adjustable by the user:

    dim ts$:"rows:u(4),cols:u(1),colwidth[16]:u(2),interspace[16]:u(2),
    usersize[16]:u(2)"
    ts.rows=16,ts.cols=16
    for col=1 to 16
    ts.colwidth[col]=32
    next col
    result$=sendmsg(sysgui,grid,30,0,ts$); rem ' initialize grid

  4. Set the row height to 24 pixels:

    result$=sendmsg(sysgui,grid,68,24,$$); rem ' set row height

    The following is an example of the grid after applying the adjustments:

    stdgrd03.png

  5. Set the column headers to 00, 01, 02, ... , 0D, OE, OF (0 through 15 in hexadecimal), using Set Cell - Grid SENDMSG() Function 22:

    for col=0 to 15
    result$=sendmsg(sysgui,colid,22,col,hta(chr(col))); rem ' set column headers
    next col

    Only the data in column headers is permanently cached, so only column headers will be set in this manner. The row header and main body cells are filled using a different technique, which is described below.

    stdgrd04.png

  6. Deselect all cells.

    result$=sendmsg(sysgui,grid,32,0,$$); rem ' deselect all cells

  7. Grid information blocks will be used to load data in the main grid and the row heading grid. Use a variation of the TMPL() function to assign templates and change the style of the row heading grid to recessed button:

    dim grid$:tmpl(sysgui,ind=1)
    grid$=sendmsg(sysgui,grid,20,0,$$)
    dim row_head_grid$:tmpl(sysgui,ind=1)

  8. Before starting the event loop, force a redraw of the main grid. This generates the Notify events that cause the event loop to execute routines for filling cells with data.

    result$=sendmsg(sysgui,grid,76,0,$$)

    The completed table appears below:

    stdgrd05.png

Defining the Event Loop

  1. With the grid completed, a standard event loop is entered until the user enters another command. For additional information on event loops, see "Event-Driven GUI Programming with Notify and SENDMSG() " in the Spring 1998 BASIS Advantage magazine at https://www.basis.cloud/.

    dim event$:tmpl(sysgui),generic$:noticetpl(0,0)while 1
       read record(sysgui,siz=len(event$))event$   if event.code$="N":     then
    :        generic$=notice(sysgui,event.x);
    :        dim notice$:noticetpl(generic.objtype,event.flags);
    :        notice$=generic$

    The grid control is a presentation structure only; it does not necessarily retain the data in a cache, except column headers, which are always cached. From time to time, the grid will request an update of one or more cells. For this reason, the program will need to maintain a data structure of the data that belongs in the grid, and it must be prepared to hand this information back to the grid control at any time.

  2. When the main grid or the row heading grid needs to refresh one or more cells and does not have the data cached, it sends Notify event number 22. When this message is received, code should be executed to redraw the specified cell(s). The values returned in the templated NOTICE$ string control which cells are redrawn. Draw Cell - SENDMSG() Function 54 uses the grid information block to transfer data to the control:

    if event.id=grid and event.code$="N" and event.flags=22
    :     then
    :        for row=notice.toprow to notice.botrow;
    :        grid.row%=row;
    :        for col=notice.leftcol to notice.rightcol;
    :            grid.col%=col;
    :            grid.textcolor$=$000000$, grid.backcolor$=$ffffff$
    :            grid.style%=0
    :            byte=row*16+col;
    :            grid.buf$=chr(byte);
    :            result$=sendmsg(sysgui,grid,54,0,grid$);
    :        next col;
    :     next row
       if event.id=rowid and event.code$="N" and event.flags=22
    :     then
    :        for row=notice.toprow to notice.botrow
    :           row_head_grid.row=row;
    :           row_head_grid.textcolor$=$000000$, row_head_grid.backcolor$=$ffffff0$
    :           grid.style%=2
    :           row_head_grid.buf$=hta(chr(row*16));
    :           tf$=sendmsg(sysgui,rowid,54,0,row_head_grid$)
    :        next row

    Note that Notify events cause this code to be executed before user action takes place, thus filling the row header and main grid cells immediately after the program begins.

  3. If the user double-clicks on a cell, grid Notify event number 3 will be returned. When this event is trapped, display the character from that cell, along with the equivalent value in decimal and hexadecimal:

       if event.id=grid and event.code$="N" and event.flags=3
    :     then
    :         byte=notice.row*16+notice.col;
    :         i=msgbox("Character: "+chr(byte)+$0a$+"Decimal: "+str(byte)+$0a$+
    :         "Hexadecimal: "+hta(chr(byte)))

    stdgrd06.png

  4. Enter code that responds to the user picking a new font from the menu. The 'FONT' mnemonic changes the font on the main grid, then Redraw Grid - SENDMSG() Function 76 causes the grid to be redrawn with the new font. Before redrawing, SENDMSG() Function 68 must be used to keep the cell height at 24 pixels:

       if event.code$="C"
    :     then
    :        font_name$=font.name$[event.id];
    :        print (sysgui)'font'(grid,font_name$,0,0);
            result$=sendmsg(sysgui,grid,68,24,$$);
    :        grid$=sendmsg(sysgui,grid,76,0,$$);
            rem ' change font & redraw grid

    stdgrd07.png

  5. End the program with the code to enable the user to terminate the event loop by clicking the close button, followed by the end of the event loop itself and the end of the program:

    if event.code$="X"
    :     then
    :        break
    wend
    end

The complete program is listed below:

0001 REM ' ASCII Table (Standard Grid Sample)
0010 LET SYSGUI=UNT
0020 OPEN (SYSGUI)"X0"
0030 PRINT (SYSGUI)'WINDOW'(20,40,580,450,"ASCII Table: Double-click on any cell",$00000802$,$$)
0040 LET MENU$="&Font,9999,,"+$0A$
0050 LET FONTS=POS($0A$=CTRL(SYSGUI,0,9),1,0)
0060 DIM FONT$:"name["+STR(FONTS)+"]:c(16*=10)"
0070 LET FONT$=CTRL(SYSGUI,0,9)
0080 FOR FONT=1 TO FONTS; LET MENU$=MENU$+" "+FONT.NAME$[FONT]+","+STR(FONT)+",,"+$0A$; NEXT FONT
0090 LET MENU$=MENU$+$0A$
0100 PRINT (SYSGUI)'SETMENU',MENU$
0110 LET GRID=100,COLID=101,ROWID=102
0120 PRINT (SYSGUI)'GRID'(GRID,20,20,548,430,$9846$,16,16,16,20,COLID,20,ROWID)
0130 DIM TS$:"rows:u(4),cols:u(1),colwidth[16]:u(2),interspace[16]:u(2),usersize[16]:u(2)"
0140 LET TS.ROWS=16,TS.COLS=16
0150 FOR COL=1 TO 16; LET TS.COLWIDTH[COL]=32; NEXT COL
0160 LET RESULT$=SENDMSG(SYSGUI,GRID,30,0,TS$); REM ' initialize grid
0170 LET RESULT$=SENDMSG(SYSGUI,GRID,68,24,$$); REM ' set row height
0180 FOR COL=0 TO 15; LET RESULT$=SENDMSG(SYSGUI,COLID,22,COL,HTA(CHR(COL))); NEXT COL; REM ' set column headers
0190 LET RESULT$=SENDMSG(SYSGUI,GRID,32,0,$$); REM ' deselect all cells
0200 DIM GRID$:TMPL(SYSGUI,IND=1)
0210 DIM ROW_HEAD_GRID$:TMPL(SYSGUI,IND=1)
0220 DIM EVENT$:TMPL(SYSGUI),GENERIC$:NOTICETPL(0,0)
0230 WHILE 1
0240 READ RECORD(SYSGUI,SIZ=LEN(EVENT$))EVENT$
0250 IF EVENT.CODE$="N" THEN LET GENERIC$=NOTICE(SYSGUI,EVENT.X); DIM NOTICE$:NOTICETPL(GENERIC.OBJTYPE,EVENT.FLAGS); LET NOTICE$=GENERIC$
0260 IF EVENT.ID=GRID AND EVENT.CODE$="N" AND EVENT.FLAGS=22 THEN FOR ROW=NOTICE.TOPROW TO NOTICE.BOTROW; LET GRID.ROW%=ROW; FOR COL=NOTICE.LEFTCOL TO NOTICE.RIGHTCOL; LET GRID.COL%=COL; LET GRID.TEXTCOLOR$=$000000$,GRID.BACKCOLOR$=$FFFFFF$; LET GRID.STYLE%=0; LET BYTE=ROW*16+COL; LET GRID.BUF$=CHR(BYTE); LET RESULT$=SENDMSG(SYSGUI,GRID,54,0,GRID$); NEXT COL; NEXT ROW
0270 IF EVENT.ID=ROWID AND EVENT.CODE$="N" AND EVENT.FLAGS=22 THEN FOR ROW=NOTICE.TOPROW TO NOTICE.BOTROW; LET ROW_HEAD_GRID.ROW=ROW; LET ROW_HEAD_GRID.TEXTCOLOR$=$000000$,ROW_HEAD_GRID.BACKCOLOR$=$FFFFFF$; LET ROW_HEAD_GRID.STYLE%=2; LET ROW_HEAD_GRID.BUF$=HTA(CHR(ROW*16)); LET TF$=SENDMSG(SYSGUI,ROWID,54,0,ROW_HEAD_GRID$); NEXT ROW
0280 IF EVENT.ID=GRID AND EVENT.CODE$="N" AND EVENT.FLAGS=3 THEN LET BYTE=NOTICE.ROW*16+NOTICE.COL; LET I=MSGBOX("Character: "+CHR(BYTE)+$0A$+"Decimal: "+STR(BYTE)+$0A$+"Hexadecimal: "+HTA(CHR(BYTE)))
0290 IF EVENT.CODE$="C" THEN LET FONT_NAME$=FONT.NAME$[EVENT.ID]; PRINT (SYSGUI)'FONT'(GRID,FONT_NAME$,0,0); LET RESULT$=SENDMSG(SYSGUI,GRID,68,24,$$); LET RESULT$=SENDMSG(SYSGUI,GRID,76,0,$$); REM ' change font & redraw grid
0300 IF EVENT.CODE$="X" THEN BREAK
0310 WEND
0320 END