Emulating the FileMaker Pro Book Object in 4D
By Steve Hussey, CEO, Alto Stratus LLC
Technical Note 00-24
Technical Notes for Technical Notes for 00-05 May 2000
Introduction
FileMaker Pro uses a book object to navigate through records. Clicking on the top page moves back one record (unless you are at the first record), and clicking on the lower page takes you to the next record (unless you are at the last record). Clicking and dragging the tab slider allows you to move backwards and forwards through a selection of records (known in FileMaker Pro as the found set). This technical note implements similar behavior in 4D.
In a future technical note, the 4D ruler object will be used to implement a simpler solution.
Behavior of the FileMaker Pro Book Object
The picture above shows the standard view of the FileMaker Pro book object:
In this example there are five records in the database, and five in the found set.
The user is displaying the fifth record.
The bottom page is disabled, since the current record is the last record in the found set.
The top page is enabled, to allow the user to step back through the found set.
The tab slider is at the bottom of the book object. It can be dragged upwards to move backwards through the found set.
The record count is at five.
The records are not sorted (as indicated by the "Unsorted" status).
The behavior of the book object changes slightly when there is a found set that is smaller than the number of records in the database. In the picture above, note the addition of the "Found:" count. Again the status indicates that the found set is unsorted. If the records are sorted, the text changes to "Sorted." The user is at record one of the found set, which contains two records in a database of six records.
In the picture above, the book object is displayed in the middle of a found set. Both the top and bottom pages are enabled, and the tab slider is in an intermediate position. The user is at record three of the found set (which is all records).
Why keep the book object in 4D?
If you have converted a FileMaker Pro database to 4D, you may want to keep some of the FileMaker Pro features, such as the book object, to maintain visual and behavioral consistency for the user.
4D does not have any similar objects, but the book object can be emulated with code and a few graphic objects and invisible buttons.
Using the Sample Book Object in 4D
Launch the sample database. To use the book object on a found set of all records:
Select List Zip Codes from the File menu.
Double-click any record in the list.
Click the top page of the book object (if the text is visible).
Click the bottom page (if the text is visible).
Drag the slider.
Cancel.
Sort the selection using the Sort button. This displays the Order by... editor.
Sort the selection.
Double-click any record.
Note the status values below the book object.
Cancel.
Create a subselection of records:
Click, Shift+Click or Command+Click (Control+Click on Windows) any selection of records.
Click the Subset button to make the selected records the current selection.
Double-click any record.
Note the differences in the status variables below the book object.
Creating the Book Object
The first stage is to create the book object and the tab slider. This can be achieved by taking a screen shot of the book object in FileMaker Pro. On Mac OS you can press Command+Shift-4, or use Flash-It (or a similar utility), and on Windows you can use PaintShop Pro.
The tab slider is created next. You will need to separate it from the background by using a paint type program such as PaintShop or PhotoShop:
This will be placed next to the book object at the top. The text icon is then copied:
(In FileMaker Pro up to v4, the text icon was black on white. In FileMaker Pro v5 it changed to light blue on white.) These parts can be put together as follows:
The tab is duplicated until there are nine copies next to the book object. The text objects are placed over the top of the two "pages."
The text pictures are given the Object Names of "pict_Text_Back" and "pict_Text_Next". (This is done using the Object Properties palette.)
The book object needs no name.
The tab sliders are named "pict_Slider_00" through "pict_Slider_08".
Next, two invisible buttons are placed over the two pages:
These are sized to precisely fit over the two pages.
The top button has an object method that calls the project method FMPB_Button_Back.
The bottom button has an object method that calls the project method FMPB_Button_Next.
Each of the tab slider objects needs to have a unique name. The first tab is named like this:
Note that it is the object name that is set (not the variable name). The next object is named "pict_Slider_01" and so on. The last slider in this example is named "pict_Slider_08".
Next, add the current record counter variable long_SlctRecNum_Crnt below the lower page. This indicates the current selected record number:
This will display the selected record number of the current record. Variables can then be added to display the current record count and the sort status:
The variable text_SortState is placed on the form twice: but with two different object names. Depending on whether a selection is being displayed, the sort state text label can appear in one of two different positions. Although they will display the same variable by using two different object names, you can make either one visible or invisible. The top variable is long_RecCount_Table. The other record count (the third variable from the top) is the selection count.
The lines consist of four lines (two grouped pairs) named "Line1", "Line2", "Line3" and "Line 4". Lines 1 and 3 are gray, 2 and 4 are white.
Setting Up the Form Method
The book object is placed on the detail form for any desired table. The form has a form method:
Case of : (Form event = On Load) SET TIMER (1) ` Set the timer event to run every tick FMPB_Setup ` Sets up the record counters, text labels and variables, etc. : (Form event = On Timer) FMPB_Mouse_Track ` Every time the Timer event runs, track the mouse position End case
When the form is loaded, the timer event is set to trigger once every tick (one sixtieth of a second). Then the FMPB_Setup project method runs. Then once every tick the FMPB_Mouse_Track project method runs. This method checks to see if the mouse button is clicked, and it then checks the mouse position to determine whether the tab slider needs to be moved.
FMPB_Setup
First all necessary variables need declaring, to be compiler friendly:
` Compiler declarations ` Number of tab sliders... C_INTEGER (int_Tab_NmbrOf; $Tab) ` For tracking mouse position & button state C_LONGINT (int_MouseX; int_MouseY; int_MouseClick) ` Record numbers C_LONGINT (long_SlctRecNum_First; long_SlctRecNum_Last; long_SlctRecNum_Crnt; long_RecCount_Slctn; long_RecCount_Table; long_Range) C_LONGINT (int_Tab_NmbrOf) ` Number of slider tabs on book C_REAL ($Pos) C_TEXT (text_SortState) ` Indicates sort status C_TEXT ($Text)
This variable defines how many tab slider objects there are.
int_Tab_NmbrOf := 9 ` Defines how many tabs you are using
To manage the states of the book object you will need to know how many records there are in both the current table and the current selection:
` How many records in the selection? long_RecCount_Slctn := Records in selection (Current form table->) ` As opposed to how many records we have in the table... long_RecCount_Table := Records in table (Current form table->)
Current form table returns a pointer to the table that "owns" the current form. Dereferenced, this can be used to determine which table is being referred to. This means that the code will work with whatever form the book object is placed upon.
Next you will need to know the current selected record number:
` Get current record position relative to the selection long_SlctRecNum_Crnt := Selected record number (Current form table->)
This is the selected record number of the record the user has just double-clicked. The selected record number is used, since the user may be working with either all the records in a table or a subselection of records.
Now the selected record numbers of the first and last records in the selection are stored in variables:
` Position of first record in current selection long_SlctRecNum_First := 1 ` Position of last record in current selection long_SlctRecNum_Last := long_RecCount_Slctn
All the slider tabs can now be made invisible, prior to making the appropriate one visible:
SET VISIBLE (*; "pict_Slider_@"; False) ` Turn off all the slider tabs
The "*" character means that 4D will use the object name (rather than the variable name). With this syntax, the "@" symbol can be used as a wildcard. Using this wildcard will enable one line of code to make all tab slider objects invisible (all objects with names that start with "pict_Slider_").
Now you can determine which tab to make visible: if the selection is no larger than one record, none of the tabs will be made visible. If the user has double-clicked the first record (or one near the beginning of the selection) the first tab will be made visible. If the record is near or at the end of the selection then the last tab (the ninth one) will be made visible. If the record is somewhere in-between, then the relative position is calculated and the nearest tab is made visible. This is managed within a Case statement:
Case of : (long_RecCount_Slctn < 2) ` If there isn't more than one record in the selection ` Don't make any of the tabs visible : (long_SlctRecNum_Crnt = 1) ` You are the first record... SET VISIBLE (*; "pict_Slider_00"; True) ` Set the first tab on
If you are at the last record:
: (long_SlctRecNum_Crnt = long_SlctRecNum_Last) ` We are at the last record in the selection $TabOn := "pict_Slider_0" + String (int_Tab_NmbrOf - 1) ` (Tab numbers start at zero) SET VISIBLE (*; $TabOn; True) ` Set the last tab on
Otherwise you are somewhere in-between. This code calculates the relative position within the selection of the record that was double-clicked:
Else ` We are elsewhere in the selection ... ` Calculate the relative position of the tab $Pos := (long_SlctRecNum_Crnt / long_RecCount_Slctn) * int_Tab_NmbrOf $Tab := Int ($Pos) + Num (Dec ($Pos) > 0) ` Round up to a whole number $Tab := $Tab - 1 $TabOn := "pict_Slider_0" + String ($Tab) SET VISIBLE (*; $TabOn; True) ` Set the tab on End case
Now the other variables need to be calculated or set:
If (long_RecCount_Slctn = long_RecCount_Table) ` Then we are displaying all records SET VISIBLE (*;"Found"; False) ` Turn off "Found" status label... SET VISIBLE (long_RecCount_Slctn; False) ` Turn off Found counter ` Turn off the shaded lines - each is a pair - one gray - one white - grouped SET VISIBLE (*;"Line1"; False) SET VISIBLE (*;"Line2"; False) SET VISIBLE (*;"Line3"; False) SET VISIBLE (*;"Line4"; False)
By giving the variables two different names you are able to turn off either one (without affecting its value).
` Turn on the "Sort" status label in the upper position SET VISIBLE (*; "Sort_Upper"; True) ` Turn off the "Sort" status label in the lower position SET VISIBLE (*; "Sort_Lower"; False) Else ` We are displaying only a subselection of the records
In this case we are displaying a subselection of records:
SET VISIBLE (*; "Found"; True) ` Turn on "Found" status label SET VISIBLE (long_RecCount_Slctn; True) ` Display Found count ` Turn ON the shaded lines - each is a pair - one gray - one white - grouped SET VISIBLE (*; "Line1"; True) SET VISIBLE (*; "Line2"; True) SET VISIBLE (*; "Line3"; True) SET VISIBLE (*; "Line4"; True) ` Turn OFF the "Sort" label in the upper position SET VISIBLE (*; "Sort_Upper"; False) ` Turn ON the "Sort" label in the lower position SET VISIBLE (*; "Sort_Lower"; True) End if
The next stage is to locate the positions of the slider tabs. Since the book object can be pasted in at any position on the detail form, you will need to locate the positions of the tab sliders. At first sight you may think that this project method could be called from the startup project method. But this would not work if there were multiple detail forms with book objects.
` Get the coordinates of however many tabs we have next to the book FMPB_Tab_GetCoords
The FMPB_TabGetCoords method fills a two-dimensional array with the top and bottom coordinates for the slider tabs:
` To hold coordinates of each tab ARRAY LONGINT (int_Tab_Coords; int_Tab_NmbrOf; 2) C_LONGINT (int_Left; $Top; int_Right; $Bottom)
To make the code easier to read, the top and bottom coordinates are extracted first into two local variables.
For ($Tab; 0; (int_Tab_NmbrOf - 1)) ` Since the first slider is number 0 $TabName := "pict_Slider_0" + String ($Tab) ` Build the slider tab names
Then get the coordinates:
GET OBJECT RECT (*; $TabName; int_Left; $Top; int_Right; $Bottom)
int_Tab_Coords{$Tab + 1}{1} := $Top
int_Tab_Coords{$Tab + 1}{2} := $Bottom
End for
` We don't need to store all the left and right coordinates
` since the tabs are aligned vertically
Now the text icons need to be made visible or invisible depending on the user's position within the selection:
` Update the text icon according to the position of the current record FMPB_TextIcon_Updt
The FMPB_TextIcon_Updt method sets the text icons visible or invisible depending on the position of the record the user selected:
` There are no existing records/selection/new record If ((long_RecCount_Slctn < 2) | (long_RecCount_Table < 2) | (Record number (Current form table->) = New record)) SET VISIBLE (*; "pict_Text_@"; False) ` Turn off both text icons End if ` User is at first record If ((long_SlctRecNum_Crnt = long_SlctRecNum_First) & (long_RecCount_Slctn > 1)) SET VISIBLE (*; "pict_Text_Back"; False) ` Turn off upper text icon SET VISIBLE (*; "pict_Text_Next"; True) ` Turn on lower text icon End if ` User is somewhere in the middle... If ((long_SlctRecNum_Crnt > long_SlctRecNum_First) & (long_SlctRecNum_Crnt < long_SlctRecNum_Last)) SET VISIBLE (*; "pict_Text_Back"; True) ` Turn on upper text icon SET VISIBLE (*; "pict_Text_Next"; True) ` Turn on lower text icon End if ` User is at last record If ((long_SlctRecNum_Crnt=long_SlctRecNum_Last) & (long_RecCount_Slctn > 1)) SET VISIBLE (*; "pict_Text_Back"; True) ` Turn on upper text icon SET VISIBLE (*; "pict_Text_Next"; False) ` Turn off lower text icon End if
FMPB_Mouse_Track
` Where's the mouse? Is the button down? GET MOUSE (int_MouseX; int_MouseY; int_MouseClick)
If the mouse button is up there is nothing to do …
If (int_MouseClick = 1) ` < Only when they click the mouse...
If (long_RecCount_Slctn > 1) ` If there is more than one record in the selection
` Then if we are between the limits of the tabs + 5-pixel margin
If ((int_MouseX > (int_Left - 5)) & (int_MouseX < (int_Right + 5))
& (int_MouseY > int_Tab_Coords{0}{1})
& (int_MouseY < int_Tab_Coords{int_Tab_NmbrOf}{2}))
The code checks that:
There is more than one record in the selection.
The mouse (with button down) is right of the left-hand edge of the slider tabs. There is a 5-pixel margin to the left of the left-hand edge.
The mouse is to the left of the right-hand edge of the slider tabs with a 5-pixel margin to the right.
The mouse is below the top edge of the uppermost slider tab.
The mouse is above the lowest edge of the bottom slider tab.
The code now loops through all the elements of the array to determine which tab should be made visible:
` Loop through all the tabs looking to see if mouse is over tab
For ($Tab; 1; int_Tab_NmbrOf)
` See if mouse is over a particular tab...
If ((int_MouseY > int_Tab_Coords{$Tab - 1}{1})
& (int_MouseY < int_Tab_Coords{$Tab}{1}))
SET VISIBLE (*; "pict_Slider_@"; False) ` Turn off all slider tabs
$TabOn := "pict_Slider_0" + String ($Tab - 1)
` (Remember tab numbers start from 0)
SET VISIBLE (*; $TabOn; True) ` Turn on the tab
Then the current record needs to be set to whichever record the user has indicated by dragging the slider:
Case of : ($Tab = 1) ` First tab = first record of selection GOTO SELECTED RECORD (Current form table ->; long_SlctRecNum_First) long_SlctRecNum_Crnt := long_SlctRecNum_First : ($Tab = int_Tab_NmbrOf) ` Last tab = last record of selection GOTO SELECTED RECORD (Current form table ->; long_SlctRecNum_Last) long_SlctRecNum_Crnt := long_SlctRecNum_Last Else ` Somewhere in-between... ` This line calcs the relative position of the tab to the selection... long_SlctRecNum_Crnt := Round (($Tab / int_Tab_NmbrOf) * long_RecCount_Slctn; 0) If (long_SlctRecNum_Crnt < 1) long_SlctRecNum_Crnt := 1 End if GOTO SELECTED RECORD (Current form table ->; long_SlctRecNum_Crnt) End case FMPB_TextIcon_Updt ` Update the text icons End if End for End if End if End if
Alternative solutions
This solution makes predefined tabs visible and invisible to simulate the movement of the slider up and down the side of the book. With only nine tabs, the movement can look slightly jerky. This can be improved by increasing the number of tabs. You would then also need to set the variable int_Tab_NmbrOf to the number of tabs used. The tabs would need object names that follow on from the existing names.
An alternative would be to have one slider tab and move it up and down relative to the back by determining the vertical coordinates of the mouse and moving the slider to that position. This solution has some challenges, since the coordinates will need to be calculated relative to the position of the book object (which may vary from form to form).
Implementing the Solution in Your Own Database
The book object
Copy the book object and paste it into your detail form. The position is unimportant, since the code will determine the coordinates of the tab slider objects.
Code
All the project methods prefixed with "FMPB_" need to be installed in your database.
Form method
The form that contains the book object needs a form method that contains at least the following code:
Case of : (Form event = On Load) SET TIMER (1) ` Set the timer event to run every minute FMPB_Setup : (Form event = On Timer) FMPB_Mouse_Track ` Every time the Timer event runs, track the mouse position End case
Form events
For these two events to run, you need to ensure that the form events:
On load
On Timer
are enabled. This is done in the Events tab page of the Form Properties dialog.
Summary
What at first sight appears to be a trivial problem is actually much more complex. But by implementing this solution in code, the behavior of the 4D book object can either be made to mimic FileMaker Pro's behavior, or any behavior that you desire. The technique of making objects visible or invisible has many uses: you can easily create objects that appear to slide (such as a volume control), or rotate (radio dials), or move in any other way. These techniques can be used to create complex custom controls for user interfaces.
In a future technical note, the 4D ruler object will be used to implement a solution that is simpler to code but does not have the same visual appearance as FileMaker Pro.