MS Windows TAB-control emuliation in PROGRESS 4GL

Synopsis

  This is an alternate MS Windows Tab-control solution, written in pure 4GL, without the use of Active-X or COM objects.

Description

  This alternate solution is based on the use of overlapping predefined static frames and static widgets within them. This is opposite to well-known solution, which uses standard MS Windows Tab-control object. The main disadvantage of using MS Tab control is the need of special message handling in the main PROGRESS message loop (WAIT-FOR), which presumes the number of sophisticated calls to Windows API functions to be done. This also makes further programming of a big application quite inflexible. You always must have in mind that special message handilng (remember, - one world - one WAIT-FOR?).

Our alternate solution just imitates tab-control behaviour in all its aspects, but uses only conventional widgets (buttons, rectangles and frames). It also gives you very similar look-and-feel of tab-control (so what do you need more?). The program skeleton below shows the technique that you can use in your programs, together with actual code of tab-control imitation (create_tab.i).

First you create static frames and static widgets within these frames. Of course, you can create dynamic widgets also, but make sure they are created before creation of the "tabs", using gf_create_tab function (see below). All widget contents of all frames are accessible from any place of your program, and you also can enable and disable them at any moment. You even can disable the "tabs" whenever it is suitable for you.

Here is the sample code for widgets and frames (one frame for each "tab"):

/*  asmfr.i 
*/

/* Frame 1 */

DEFINE VARIABLE t-adrreg AS CHARACTER FORMAT "X(256)":U 
     LABEL "Region" 
     VIEW-AS COMBO-BOX INNER-LINES 5
     LIST-ITEM-PAIRS ""
     SORT
     DROP-DOWN-LIST
     SIZE-PIXELS 126 BY 24 NO-UNDO.

DEFINE VARIABLE t-ctr AS CHARACTER FORMAT "X(256)":U 
     LABEL "Country" 
     VIEW-AS COMBO-BOX INNER-LINES 10
     LIST-ITEM-PAIRS ""
     SORT
     DROP-DOWN-LIST
     SIZE-PIXELS 133 BY 26 NO-UNDO.

DEFINE VARIABLE t-doctip AS CHARACTER FORMAT "X(256)":U
     LABEL "Document" 
     VIEW-AS COMBO-BOX INNER-LINES 5
     LIST-ITEM-PAIRS ""
     SORT
     DROP-DOWN-LIST
     SIZE-PIXELS 154 BY 24 NO-UNDO.

DEFINE VARIABLE t-sex AS CHARACTER FORMAT "X(256)":U 
     LABEL "Sex" 
     VIEW-AS COMBO-BOX INNER-LINES 5
     LIST-ITEM-PAIRS ""
     SORT
     DROP-DOWN-LIST
     SIZE-PIXELS 133 BY 26 NO-UNDO.

DEFINE VARIABLE t-abbr AS CHARACTER FORMAT "X(256)":U 
     LABEL "ShortName" 
     VIEW-AS FILL-IN NATIVE 
     SIZE-PIXELS 336 BY 26 NO-UNDO.

DEFINE VARIABLE t-birthday AS DATE FORMAT "9999/99/99":U 
     LABEL "Birth date" 
     VIEW-AS FILL-IN NATIVE 
     SIZE-PIXELS 98 BY 26 NO-UNDO.

DEFINE VARIABLE t-cod AS CHARACTER FORMAT "X(256)":U 
     LABEL "Personal ID" 
     VIEW-AS FILL-IN NATIVE
     SIZE-PIXELS 154 BY 26 NO-UNDO.

DEFINE VARIABLE t-doccod AS CHARACTER FORMAT "X(256)":U 
     LABEL "Nr" 
     VIEW-AS FILL-IN NATIVE
     SIZE-PIXELS 154 BY 26 NO-UNDO.

DEFINE VARIABLE t-fname AS CHARACTER FORMAT "X(256)":U 
     LABEL "First Name" 
     VIEW-AS FILL-IN NATIVE
     SIZE-PIXELS 231 BY 26 NO-UNDO.

DEFINE VARIABLE t-sname AS CHARACTER FORMAT "X(256)":U 
     LABEL "Last Name" 
     VIEW-AS FILL-IN NATIVE
     SIZE-PIXELS 231 BY 26 NO-UNDO.


/* ************************  Frame Definitions  *********************** */

DEFINE FRAME FR_1
     t-sname AT Y 63 X 112 COLON-ALIGNED
     t-fname AT Y 98 X 112 COLON-ALIGNED
     t-abbr AT Y 133 X 112 COLON-ALIGNED
     t-ctr AT Y 168 X 112 COLON-ALIGNED
     t-adrreg AT Y 168 X 322 COLON-ALIGNED
     t-cod AT Y 203 X 112 COLON-ALIGNED
     t-birthday AT Y 238 X 112 COLON-ALIGNED
     t-sex AT Y 238 X 315 COLON-ALIGNED
     t-doctip AT Y 275 X 112 COLON-ALIGNED
     t-doccod AT Y 275 X 294 COLON-ALIGNED
    WITH 1 DOWN NO-BOX KEEP-TAB-ORDER OVERLAY 
         SIDE-LABELS THREE-D 
         AT X 5 Y 5
         .


FRAME FR_1:FRAME = FRAME tabframe:handle.
FRAME FR_1:VISIBLE = false.


/* Frame 2 */

/* ..... widget definitions */


DEFINE FRAME FR_2
    .... widgets
    ....
    WITH 1 DOWN NO-BOX KEEP-TAB-ORDER OVERLAY 
         SIDE-LABELS NO-UNDERLINE THREE-D 
         AT X 5 Y 5
         .

FRAME FR_2:FRAME = FRAME tabframe:handle.
FRAME FR_2:VISIBLE = false.

/* Frame 3 */

.....


/* Frame 4 */

DEFINE BUTTON bt-ca-del 
     LABEL "Delete" 
     SIZE-PIXELS 105 BY 26.

DEFINE BUTTON bt-ca-edit 
     LABEL "Edit" 
     SIZE-PIXELS 105 BY 26.

DEFINE BUTTON bt-ca-new 
     LABEL "New" 
     SIZE-PIXELS 105 BY 26.

DEFINE BUTTON bt-ca-view 
     LABEL "Go to companies registry ..." 
     SIZE-PIXELS 183 BY 26.

DEFINE BUTTON bt-ca-cmp 
     IMAGE-UP FILE "icons/cbbtn.bmp":U
     LABEL "Button 1" 
     SIZE-PIXELS 21 BY 26.

DEFINE VARIABLE ca-tip AS CHARACTER FORMAT "X(256)":U 
     VIEW-AS COMBO-BOX INNER-LINES 5
     LIST-ITEM-PAIRS "Item 1","Item 1"
     DROP-DOWN-LIST
     SIZE-PIXELS 144 BY 24 NO-UNDO.

DEFINE VARIABLE ca-cmp AS CHARACTER FORMAT "X(256)":U 
     VIEW-AS FILL-IN NATIVE 
     SIZE-PIXELS 98 BY 24 NO-UNDO.

DEFINE VARIABLE ca-ikits AS DATE FORMAT "9999.99.99":U 
     VIEW-AS FILL-IN NATIVE 
     SIZE-PIXELS 98 BY 24 NO-UNDO.

DEFINE VARIABLE ca-nuots AS DATE FORMAT "9999.99.99":U INITIAL ? 
     VIEW-AS FILL-IN NATIVE 
     SIZE-PIXELS 98 BY 24 NO-UNDO.

DEFINE RECTANGLE fr-4-rc-1
     EDGE-PIXELS 2 GRAPHIC-EDGE  NO-FILL 
     SIZE 70.43 BY 1.58.

/* Query definitions                                                    */
DEFINE QUERY cmpasm-br FOR 
      tt-cmpasmb SCROLLING.

/* Browse definitions                                                   */
DEFINE BROWSE cmpasm-br
  QUERY cmpasm-br DISPLAY
       tt-cmpasmb.del COLUMN-LABEL "" FORMAT "x/ "
       tt-cmpasmb.cmpname COLUMN-LABEL "Company" FORMAT "x(15)"
       tt-cmpasmb.tipname COLUMN-LABEL "Role  " FORMAT "x(20)"
       tt-cmpasmb.nuots COLUMN-LABEL "Worked from  " FORMAT "xxxx.xx.xx    "
       tt-cmpasmb.ikits COLUMN-LABEL "to   " FORMAT "xxxx.xx.xx"
    ENABLE tt-cmpasmb.del
    WITH NO-ROW-MARKERS SEPARATORS
         SIZE-PIXELS 487 BY 161
         TITLE "Person's occupations" 
         EXPANDABLE.


/* ************************  Frame Definitions  *********************** */


DEFINE FRAME fr_4
     cmpasm-br AT Y 23 X 18
     bt-ca-cmp AT Y 205 X 119
     ca-cmp AT Y 206 X 7 COLON-ALIGNED NO-LABEL
     ca-tip AT Y 206 X 129 COLON-ALIGNED NO-LABEL
     ca-nuots AT Y 206 X 274 COLON-ALIGNED NO-LABEL
     ca-ikits AT Y 206 X 375 COLON-ALIGNED NO-LABEL
     bt-ca-new AT Y 264 X 21
     bt-ca-edit AT Y 264 X 139
     bt-ca-del AT Y 264 X 381
     bt-ca-view AT Y 318 X 303
     fr-4-rc-1 AT ROW 8.65 COL 2.86
    WITH 1 DOWN NO-BOX KEEP-TAB-ORDER OVERLAY 
         SIDE-LABELS NO-UNDERLINE THREE-D 
         AT X 5 Y 5
         .

FRAME FR_4:FRAME = FRAME tabframe:handle.

FRAME FR_4:VISIBLE = false.

/* Frame 5 */

/* .... */

/* Frame 6 */

/* .... */

/* end of frames source */

You can create the contents of each frame using UIB (User Interface Builder), by creating a fake frame of corresponding size, and then putting in all needed widgets. Then you just copy and paste the generated source of frame widgets to the final code.

The next step is to create main window with the container frame for the "tab widget". Then you create "tabs" within the standard UIB Enable_UI procedure (or inside your own equivalent of it), by subsequent calls to gf_create_tab function. Each call returns the handler to the "tab" controlling widget, which is actually a semi-hided button widget. You can use later these handlers to access "tabs" using "ON" event handlers in your program, as well as control "tab-controls" directly (change their properties).
There is the fourth parameter to this function, where you can pass the pre-process and post-process procedures names, which are called when you press a "tab". Function names are separating by comma. The fifth (last) parameter is a procedure handle, where these procedures reside (in case of using persistent procedures).

Here is a skeleton of the sample application:

/*------------------------------------------------------------------------

  File: _asm.p

  Description: Personnel registry

  Author: Vladas Saulis (PRODATA)

  Created: 2001.06.20
*/


{ create_tab.i }  /* This can also be put into global persistent section */

DEF VAR hTabFrame as handle.

DEF VAR hBut1 as widget-handle.
DEF VAR hBut2 as widget-handle.
DEF VAR hBut3 as widget-handle.
DEF VAR hBut4 as widget-handle.
DEF VAR hBut5 as widget-handle.
DEF VAR hBut6 as widget-handle.

/* ***********************  Control Definitions  ********************** */

/* Define the widget handle for the window                              */
DEFINE VAR C-Win AS WIDGET-HANDLE NO-UNDO.


/* Widget definitions goes here
   ....
*/

/* ************************  Frame Definitions  *********************** */



DEFINE FRAME asm-fr
    ..... widgets
    .....
    WITH 1 DOWN NO-BOX KEEP-TAB-ORDER
         SIDE-LABELS NO-UNDERLINE THREE-D 
         AT X 0 Y 0
         SIZE-PIXELS 760 BY 470.


/* Example of container frame - the parent of all "tab" frames */
DEFINE FRAME tabframe
    WITH THREE-D
    .


FRAME tabframe:frame = FRAME asm-fr:handle.

ASSIGN
    hTabFrame = FRAME tabframe:handle
    htabFrame:WIDTH-PIX = 545
    hTabFrame:HEIGHT-PIX = 425
    hTabFrame:X = 215
    hTabFrame:Y = 40
    hTabFrame:BOX = false
    .


{ asmfr.i }  /* All "tabs" content */


/* Main window */
  CREATE WINDOW C-Win 
      ASSIGN
         TITLE              = "Personnel registry"
	 ....
	 ....
         SENSITIVE          = yes
         .


/* Event processing */

{ events.i }
  /* The placeholder for event processing */



PROCEDURE enable_UI : /* This is a part of UIB code */


  ENABLE ......
      WITH FRAME asm-fr IN WINDOW C-Win.


  VIEW C-Win.


  /* gf_create_tab parameters:
    
    1. The "tab" element screen label
    2. Container frame handle
    3. "tab's" frame handle 
    4. Optional: Pre- and post-processing
 procedures' names 
	(separated by comma)
    5. Optional: Procedure handle of pre-/post- procedures.
    
    Returns: Controlling "tab" button handle.
  */

  hBut1 = gf_create_tab("Main info", FRAME tabframe:HANDLE, 
			FRAME FR_1:HANDLE, ?, ?). 
  hBut2 = gf_create_tab("Extra info", FRAME tabframe:HANDLE, 
			FRAME FR_2:HANDLE, ?, ?). 
  hBut3 = gf_create_tab("Addresses", FRAME tabframe:HANDLE, 
			FRAME FR_3:HANDLE, "init_3_tab", THIS-PROCEDURE). 
  hBut4 = gf_create_tab("Occupation", FRAME tabframe:HANDLE, 
			FRAME FR_4:HANDLE, "init_4_tab", THIS-PROCEDURE). 
  hBut5 = gf_create_tab("Events", FRAME tabframe:HANDLE, 
			FRAME FR_5:HANDLE, "init_5_tab", THIS-PROCEDURE). 
  hBut6 = gf_create_tab("Other", FRAME tabframe:HANDLE, 
			FRAME FR_6:HANDLE, "init_6_tab", THIS-PROCEDURE). 

  ENABLE ALL WITH FRAME tabframe.
  /* enable widgets in some (or all) "tabs" */
  ENABLE ALL WITH FRAME FR_2.
  ENABLE ALL WITH FRAME FR_1.
  /* you can hide some frames */
  FRAME fr_4:VISIBLE = FALSE.
  FRAME fr_3:VISIBLE = FALSE.

  run dis_frames_content. /* optionally disable all frames content */


END PROCEDURE.
 /* Enable_UI */


Download source code

  You can download or view create_tab.i source code from here.

Screen shots

  These are example screens of some personnel registry application, which uses our 4GL "tab-control".


This is an opening tab when creating or editing the personal information. Note, that some "tabs" can be in disabled state.


The browse in this "tab" doesn't need special handling, because it is static, and enabled just like all other widgets.


Some extra notes about using 4GL "tab-control"

  As it always is, this implementation of "tab-control" have some limitations when using in real applications. The main limitation in fact is a PROGRESS r-code size limit. Because you must handle all events of all "tabs" widgets within the one code segment, you may quickly fall into that limit. Of course, it is possible to surpass this problem by immediately calling internal procedure from within the event handler. But in real life we have had this problem even doing that. The other solution is to call external persistent procedures for each "tab", but it rises another problem - to implement rather sophisticated control over those persistent procedures.

Another limitation is the limited horizontal size of the "tab-control" itself when you want to create a big number of "tabs" with some longer names of them. It is possible to enhance a code by adding "scrolling tab-control" feature. We leave this enhancement for you, and hope to get a feedback from you in that case.

In some cases, it is possible also to create or delete "tabs" dynamically. However it requires some extra technique in your program, and some extra testing as well. We haven't been tested this possibility, but we can see no obstacles on this way.

Authors, license and feedback

  The original implementation and idea of this implementation of 4GL "tab-control" belongs to Vladas Saulis (PRODATA) and Saulius Sakarauskas (Baltic Amadeus). All rights reserved. You can use this code for your projects. The reference to authorship is mandatory. Please send all questions and comments to the authors (see contacts page).


-->