How ABC Handles Multiple Sort Orders (Part II)

by Steven Parker

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:

  1. add additional sort orders
  2. and

  3. correctly associate created tabs with their target data,

which will complete the implementation of sorts on the run.

Creating Tabs

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.

Listing 1. Creating tabs.

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.

Listing 2. Setting sort orders by tab choices.

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.

Adding New Sort Orders

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

Figure 1. Embed points for adding sort orders.

sort2_1.gif (6908 bytes)

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.

Listing 3. Creating the sort orders.

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.

Listing 4. Final code for adding sort orders.

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

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.

Printer-friendly version

Reader Comments

To add a comment to this article you must log in.

 
 

Search

 

Advanced Search
Topical Index

Related Articles

Subscribe to
ClarionMag

One year: $189

(includes all back issues since '99)

Renewals from $139

Two years: $289

Renewals from $239

More Info

Subscribe Now!

ClarionMag Blog

RSS Feeds

Updates via Email

Enter your Email


Powered by FeedBlitz

Quick Links