![]() |
|
Published 1999-05-10 Printer-friendly version
In the first installment of this series I explained that when browse tabs are created, information is taken from the prompts on the Browse Box Behavior ... Default Behavior and Conditional Behavior options. These include locator, key, filter and Additional Sort Fields information.
Information is collected starting (logically) with the second
tab. The default locator, filter, key and Additional Sort Fields
are collected last. In this way, options on the Default Behavior
tab become the target of an ELSE clause (i.e., the
default action).
These data are added to a private queue
BRWx.SORT in the browse object. When the end
user changes tabs, the tab number
(Choice(?CurrentTab), actually) is used to calculate a
pointer to retrieve the information from the queue.
The AddSortOrder method actually adds the
information to the queue. AppendOrder can add
additional information to the sort specified in
AddSortOrder. And, most importantly,
AppendOrder can take a variable as its parameter.
But First ...
Before attempting to implement additional orders there is a small housekeeping issue that must be resolved: dynamically creating the required number of tabs. After that, the remaining tasks are:
and
which will complete the implementation of sorts on the run.
If, like me, you are not familiar or comfortable with creating controls dynamically, now would be a good time to briefly check out "Create (create new control)" in the on-line help, before proceeding.
The code to create controls should be placed after the window is
opened so that the structure required to receive the controls (the
window) is already created in memory. But new controls must also be
created before the window is displayed. (Technically, you can
create a control any time you want. Because it is hidden, you will
need to both unhide it and refresh the window. Considering what
I'm trying to accomplish, however, after opening the window is
the appropriate place to create the new controls.) In this respect,
creating a control is no different than modifying a control when
opening a window. This implies that the ThisWindow ...
Init ... Open the window embed is where the tab creation
code should be placed. Creating tabs is a bit different than
creating controls used for data capture.
First, unless you need to address the new tab directly in code, for example
SELECT(?MyNewTab)
a Use variable, the first parameter of
Create, is not necessary. Nor is it necessary to set
{Prop:Use}.
Second, because a tab must be on a sheet, the
Create statement's parent (third) parameter
is required.
Generically, the code to create a tab looks like:
MyNewTab = CREATE(0,Create:Tab,?CurrentTab)
Note the zero in the first parameter. The documentation states that this parameter is optional. However, as sometimes happens, the compiler does not agree. When the documentation and the compiler disagree (and until that disagreement is resolved), it is wise to go with the compiler. Therefore, supply a dummy value for the first parameter, the "field number or field equate."
By default the tab has no text, so the next line of code should assign some text:
MyNewTab{Prop:Text} = 'Hey look me over!'
And, per the docs, should you want the end user to actually see the tab:
UNHIDE(MyNewTab)
(This last step is not, strictly speaking, necessary. But why go to the effort otherwise?)
It's time to apply this knowledge and create the tabs.
The easy way to create exactly the right number of new tabs and
to do so in one go is to put the CREATE statement in a
loop.
Suppose the file containing the user defined sorts is
UserSort, its prefix is USR and it
contains a field called Description. Then the code in
Listing 1 should do the job.
i = 0
NDX = 0
ACCESS:UserSort.UseFile() !Ensure file is opened
SET(UserSort)
LOOP NDX = 1 TO RECORDS(UserSort) !Only as many as the user
!has actually "requested"
NEXT(UserSort)
i = CREATE(0,Create:Tab,?CurrentTab)
i{Prop:Text} = CLIP(USR:Description)
UNHIDE(i)
END
If you are thinking that a safety check, like:
If
ACCESS:UserSort.Next()
Break
End
should be placed after the Next() statement and you
routinely do this sort of check, the code will fail to work as
expected. If there is only one user defined sort in the file, one
record, reading it will trigger the
Access:UserSort.Next() condition and the tab will not
be created.
If you are uncomfortable without an error check, try placing it
after the UNHIDE() statement.
If you stop at this point and make your app, the tabs will be
created just as expected. They won't do anything, of course,
but they will be there and they will be there in record order. (For
this reason, in a real application, you would probably not
SET() the file but a key.)
So far, so good.
Note: if you also intend to use a toolbar, your variable
must be at least a SHORT. Toolbar buttons use field
equates from 2000-13. The CREATE statement returns the
field equate, so the first CREATE with a toolbar in
place will return 2014. The variable must be long enough to receive
a value this large. A LONG would be better since
that's the return value of CREATE. (Tip: It
sometimes helps to read the documentation all the way to the end,
not simply stopping at the point where you understand. This is the
voice of experience speaking.)l
The greatest concern with these created tabs must be the
accuracy of Choice(?CurrentTab) when a runtime tab is
selected. If you, wearing your developer hat, cannot be absolutely
certain of this value, there will be absolutely no way to set the
desired order.
Without sure and certain knowledge of the value of
CHOICE(?CurrentTab) (or a way of calculating it), you
have no way to calculate the requested order, retrieving the
correct queue entry.
Remember, when a new tab is selected,
CHOICE(?CurrentTab)is used to calculate a pointer into
the queue containing the sort information, similar to Listing
2.
IF CHOICE(?CurrentTab) = 2 RETURN SELF.SetSort(1,Force) ELSIF CHOICE(?CurrentTab) = 3 RETURN SELF.SetSort(2,Force) ELSIF CHOICE(?CurrentTab) = 4 RETURN SELF.SetSort(3,Force) ELSE RETURN SELF.SetSort(4,Force) END
This is an issue of some import. You need the queue entry
number and Choice(?CurrentTab) is the only publicly
available datum related to it.
However, it turns out that this is not a problem. Because the new tabs are created after the tabs created during window design, the new ones will automatically attach to the Sheet after those tabs. That is, if you create three tabs in the window painter, the first dynamically created tab will, in fact, be in position four.
You may easily satisfy yourself in this matter by embedding:
MESSAGE( CHOICE(?CurrentTab) )
in the ?CurrentTab ... NewSelection embed. You
will always get the return value you expect.
So all is well here.
Ok, you now know how to create tabs in the desired order and you know, in principle, how to add sort orders. It's time to add some actual sort orders.
Two things should be obvious.
First, supposing the file variable you want to use is labeled
USR:Order, you can add USR:Order to the
queue with:
BRW1.AddSortOrder(,)
BRW1.AppendOrder(USR:Order)
(Note the absence of apostrophes, indicating that the use of a variable.)
Second, you need to add the new order(s) after the template generated orders.
If new orders are added before the generated key sorts, the calculation to retrieve information from the queue will be hopelessly mashed. The added orders will start at entry number one but the templates will retrieve those entries when tabs two, etc., are selected.
The first available embeds after the generated
AddSortOrders are in the Resizer method (see Figures 1
and 2).

Adding entries to the sort order queue in the Resizer! Don't let this make you crazy. This is just a place in the code, no more. The important thing is that nothing affecting the window, sheet or any existing tabs has occurred at this point.
i = 0 SET(USR:SortOrderKey) LOOP i = 1 TO RECORDS(UserSort) NEXT(UserSort) BRW1.AddSortOrder(,) BRW1.AppendOrder(USR:Order) END
Unfortunately, because the Sort property and,
specifically, the queue referred to in the documentation is
private, the only way to be certain this code is good code is if it
works. Unfortunately, the problem is that if the browse does not
behave as expected you cannot immediately conclude that there is
something wrong with the solution. This is the stuff of which bugs
are made. The problem may be in the execution of the solution.
Unfortunately, I can tell you with some confidence that the solution described here will work; it adds the expected data to the queue. But, it does not work quite as expected (a sample .APP, as well as a finished program are available for download; if you run the app, you will see that the "Single Tab - Uncorrected" menu option will behave as described below).
If you create both embeds discussed here and test the app - without yet creating the code to apply new sort orders - your browse will behave bizarrely. When the browse is opened, it will be sorted by the last order added. It will not be sorted by the default key, if any, or first order added.
Nothing in the documentation suggests this should happen. But it
is obvious that AddSortOrder/AppendOrder not only adds
information to the Sort queue, it also applies it.
Thus, the last added order is active - instead of the expected
order - when the browse is first opened.
A fix-up is equally obvious.
It's clear that the SetSort method sets the
active order and that the browse object is
BRWx. You know exactly how many tabs you placed
on the window in the window painter.
Therefore, where n is the number of tabs on the window before adding custom tabs, the queue entry corresponding to the default sort is also n.
Using the code in Listing 2 as a guide, queue entry n is actually the default order for the browse.
To reset the browse, just add:
BRW1.SetSort(n,1)
immediately after adding all the supplemental sorts.
The final code for adding the sort orders, then, looks like Listing 4.
i = 0 SET(USR:SortOrderKey) LOOP i = 1 TO RECORDS(UserSort) NEXT(UserSort) BRW1.AddSortOrder(,) BRW1.AppendOrder(USR:Order) END BRW1.SetSort( n , 1 )
Unfortunately (aren't you getting tired of that word?), I'm also out of space for this installment.
Next time I'll finish the application by adding code to associate the supplemental sort orders with the correct tab. This will ensure that the desired sort will activate on a tab change.
In the meantime, download the sample .APP to see how adding tabs and orders is accomplished.
Steve
Parker started his professional life as a Philosopher but now tries to imitate
a Clarion developer. He has been attempting to subdue Clarion since version 2007 (DOS,
that is). He reports that, so far, Clarion is winning. Steve has been writing
about Clarion since 1993.
Copyright © 1999-2008 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $189
(includes all back issues since '99)
Renewals from $139
Two years: $289
Renewals from $239