|
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).
|
|