ANSI Standard PL/B Language and Visual PL/B
E-Mail
This section discusses the TAB CONTROL.
Here are the hand written notes.
These came mostly from a discussion with Steve Sansom at
Sunbelt
in Mid September 1988.
More discussion will come soon.
PURPOSE
Makes better use of screen space.
You put up a box with "tabs" at the top.
Click on a tab and that subject comes to the front.
This is a very commonly used WINDOWS convention.
In particular it shows up in many of W95's own stuff.
THEORY
The TAB CONTROL is similar to a panel.
The form itself is nothing but a rectangle with tabs at the top.
The TABS have no names unless you give them names. You can do that in the designer or the program.
When done in the program the tabs are simply numbered.
The DATA SCREENS that go on the various tabs are NOT RELATED to the tab control form itself.
They are loaded onto the main form and positioned where the tab control will be.
You need to pay attention to sized and you need to make these PLForms objects only.
When the program loads you start by loading the main form. This gives you the tab control block with nothing on it.
Next you load all of the "sub-forms" that go on the tab control.
You load them onto the main form by name.
If you just leave them alone they will all be visible. In practice you load a form and disable it immediately so it doesn't show.
When a tab is clicked you get two events. First you get a CLICK telling you the tab you're leaving. Next you get a CHANGE telling you the tab you're entering. The Eventresult gives the tab number (1, 2, 3, 4, etc.)
When leaving a tab you can do something if you want, like get objects and give errors, etc.
If you're DONE with that form, you can just disable it so it dissapears.
When ENTERING a new tab you want to ENABLE that form so that it shows.
In practice you hide the tab you're leaving and show the one you're entering.
The forms on the tabs have no relation to the tab control. They're just loaded on the main form. When a tab is clicked you get the event, hide forms you don't want and show the one you do want.
In practice we once hid the specific form we were leaving. The new method as of 1/29/2012 is to just disable (hide) ALL of the forms on exiting a tab. Next we'll show the one we move into.
DESIGNER SETUP
Drag the tab box onto the screen like you would any other box.
Define the tabs in the TABDATA property, which is a list.
The number of tabs you have in the list is the number on the tab control.
You should give each tab a name of some sort.
In practice we just number the tabs when building the form then give them specific names
when the form is loaded by the program.
You can also GETITEM to get the current caption back:
SETITEM {tabcontrolname}, {number of tab}, "TEXT STRING"
GETITEM {tabcontrolname}, {number of tab}, "TEXT STRING"
This list ls like the one for a combo box or other box.
You have buttons for adding and deleting names.
Double click on it in the property list to get a box up.
Use the buttons or just type a name and tap enter to insert it into the list.
Once you've defined the tabs, SAVE them.
(They may not all show up immediatly in some versions of the designer, but they'll
show up at some point.)
You can add more tabs later.
NOTE: You must set the MultipleRows property to true if you
want more than one row.
FORMS THAT APPEAR ON THE TAB CONTROL TABS (or PAGES):
You must define the objects that appear on
of each tab (or "page") of the tab control.
These are done as separate forms.
Set the WindowType property on these FORMS to be
Object Only. That will insure that they don't have
messy borders or anything that lands on top of your
tab control box.
(One reason to do each form that is to be on a
tab control page as a separate form.)
You'll also load, hide and show these forms by name.
(The other reason.)
You DO have to be concerned with the positioning on the screen.
If you make the object too large or position it in the wrong place
then it could look lousy when laid on top of the tab control.
In practice we design the main screen with the tab control.
Then we note the height and width of the form and the position of the tab control.
Each sub-form is designed to the same dimensions as the MAIN form.
The objects are then designed to coincide with the position of the tab control.
(We're doing objects only, after all).
We typically the first tab control form then clone it several
times and use the clones for each page that goes on top of
the tab control box. That helps insure correct size as long
as we don't change the original sizes.
The designer gives you all the sizes and positions in the
properties box for each object. You can check those numbers
to be sure that things line up properly.
Other Designer Considerations
The tab control is like a group of several forms.
The main form is considered the "parent" form.
Each of the sub-forms are "children" and are loaded onto the parent.
The tab control is an object on the parent.
Look at these properties on the child window:
- WindowPos
tells the runtime how to position the window on the screen.
The choices are:
- Absolute which will place the window in the same
position that it's in when you build it in the designer
- Center which will place the window in the
center of the current screen regardless of the
resolution or anything else.
- Parent Center which will center the child window
on the parent window. Note that the actual size of the entire
child window should be the same as the parent window.
(Setting WindowType (below) to Objects only will insure that
the surrounding parts of the window don't show.)
WindowType
defines what parts of the window to use.
The choices are:
- Modal Dialog
- Modeless Dialog
- Objects Only
- Primary Fixed
- Primary Sizable
For the child screens we want to use the Objects Only
property. This will display only the object and allow
them to sit on top of the tab control in the
parent window.
Z-Order
May have some effect but we're not sure if it matters
since the child/parent window relationship probably
insures what goes on top.
PROGRAMMING
Loading the forms
When the program starts you can load all of your forms.
Start with the main form (parent) as usual.
Then load all of the sub-forms (children) onto the main form.
Disable each after loading so they don't show up.
This procedure insures that the forms are all loaded and ready to run but they don't show on the screen.
The order in which you load the sub-forms doesn't matter.
You just want them loaded and hidden.
The appropriate form will be activeated when a tab is clicked.
As a last step you can enable the form/tab that you want to start with.
Processing Tab Clicks
The tab control itself doesn't do anything.
It's just a box on the screen.
You lay other screen objects on top of it.
When the forms were loaded we hid (deactivated) all the forms
except the one that we want to be showing.
When the user clicks a tab you'll get two events.
First is for the tab they're leaving.
Second is the tab that they're going to.
There should be actions in the form for those two events.
You can tell the number of the tab that is currently being reported
by checking the value of #EventResult.
This is a local variable within the designer code only.
It's a FORM 12 field.
If you want to have access to this value outside of the code attached
to the form you should put in a code segment in the form to save the
value somewhere.
We use the following code in the form for the CLICK and the CHANGE events in the forms designer:
For the CLICK action (leaving the tab):
MOVE "TAB-CLICK", ACTION_TEXT
MOVE #EventResult, EVENT_RESULT
For the CHANGE action (going to a new tab):
MOVE "TAB-CHANGE", ACTION_TEXT
MOVE #EventResult, EVENT_RESULT
On the CLICK event #EventResult
will be the number of the object that you're LEAVING.
Use the CLICK event to
DE-activate whatever form is currently displayed.
On the CHANGE event #EventResult
will be the number of the object that you're GOING TO.
Use the CHANGE event to
ACTIVATE the new form for the tab.
Although not guaranteed, practice has shown that the
events occur in the order shown: CLICK then CHANGE.
OTHER NOTES
Seems to be the concensus that you should do all your SAVE
and other processing indendently of the tabs.
That is, you can have a tab control with several pages.
BELOW the tab control you could have a SAVE and CANCEL button.
The user would tab around in the control from one page to
another setting data. When they're all done they would
click the SAVE or CANCEL at the bottom and you can process
ALL of the pages at that time.
That seems to be most consistent with the way
that the majority of windows apps work.
EXAMPLE
(These were not updated 1/29/2012 when the above stuff was done)
For our purposes let's use a main form (window) called
"W20" which has a tab control with two tabs.
The two tabs will be represented by two more forms
called "W21" and "W22".
In the designer:
We would put code with two events in the designer for
the W20 parent window. This code goes with the tab
control.
On the CLICK event we'd code:
MOVE "TAB-CLICK", ACTION_TEXT
MOVE #EventResult, EVENT_RESULT
On the CHANGE event we'd code:
MOVE "TAB-CHANGE", ACTION_TEXT
MOVE #EventResult, EVENT_RESULT
In the mainline program:
In our mainline program we define each of the forms:
FORMW20 FORM FORMW20.PLF
FORMW21 FORM FORMW21.PLF
FORMW22 FORM FORMW22.PLF
The first thing that we do in the body of the program
is to load the forms and get everything active:
FORMLOAD FORMW20
FORMLOAD FORMW22, FORMW20_WINDOW
DEACTIVATE FORMW22
FORMLOAD FORMW21, FORMW20_WINDOW
We start with FORMW20 which is the parent window.
The two tab windows are loaded next and each names
the main window. This makes them children of the
parent.
Note that we loaded the forms in reverse order
and deactivated the higher number forms as they
were loaded. This left them loaded but not showing.
If there were six tabs we'd load 6, 5, 4, 3, 2, 1
and leave tab 1 active.
Next we could do any other house keeping code that
would be appropriate before getting the program running.
The main loop of the program is a typical EVENTWAIT.
The way we do it is like this:
LOOP
EVENTWAIT
IF (ACTION_DONE = $TRUE |:
ACTION = "MENU")
BREAK
ENDIF
IF (ACTION = "SAVE")
BREAK
ELSE IF (ACTION = "CANCEL")
BREAK
ELSE IF (ACTION = "CLICK-TAB")
CALL CLICK_TAB_ACTION
ELSE IF (ACTION = "CHANGE-TAB")
CALL CHANGE_TAB_ACTION
ENDIF
REPEAT
{end of run code}
In that loop we are waiting for things to happen.
The code in the form itself will do very little.
Each object usually has just one line of code
which moves a name to the "ACTION" field.
We test here to see which field caused the EVENTWAIT
to trigger.
The CLICK-TAB and the CHANGE-TAB actions are done
on those two events for the tab control object.
The code associated with the CLICK-TAB and the
CHANGE-TAB will be in charge of getting the
proper child window active.
Remember that the CLICK will identify the tab that
was previously active. On the screen that was the
tab that was "in front".
The CHANGE event identifies the tab that was just clicked.
What we have to do is to deactivate the child form which
was just left and activate the child form that was clicked.
We do it like this:
CLICK_TAB_ACTION
IF (EventResult = 1)
DEACTIVATE FORMW21
ELSE IF (EventResult = 2)
DEACTIVATE FORMW22
ENDIF
RETURN
CHANGE_TAB_ACTION
IF (EventResult = 1)
ACTIVATE FORMW21
ELSE IF (EventResult = 2)
ACTIVATE FORMW22
ENDIF
RETURN
What we're doing is just to drop the child form
that's up and bring up the one that the user
wants to see.
The "EventResult" field that we're testing is setup
by the code in the form itself. Remember that the
form has the "#EventResult" avaible. But that's
a local variable to the form only.
To get the information from the form to the mainline,
we move the "#" variable to a regular (non-#) variable that
all of the routine can see.
More code is required to do the rest of the
processing. For example, all of the child windows
can be pre-loded with data if you're bringing up
an existing record. When the SAVE action is clicked
all of the data can be retrieved from the CHILD
windows and processed then.