How ABC Handles Multiple Sort Orders (Part III)
Posted June 21 1999
When I first undertook the project to allow users to store additional sort orders in a file and automatically provide those orders on browses and reports, there were several things I knew.
I knew that additional orders could be created and I also knew that additional tab controls could be created. I had only a vague idea of how to do either of these tasks, never having had to before, but I did know these things could be done.
Having looked at the code generated by the templates for different orders on different tabs and the Solodex example for runtime tab creation, I knew that implementing each feature reduced to only a few lines of code each, very few as it turns out. I also "knew" that understanding what was happening, especially in adding sort orders, would mean tracing method calls and that would surely not be a matter of just "a few lines of code."
While I will certainly settle for code that works, I've always preferred understanding how and why it works. Understanding allows me to not only extend solutions into strategies but helps avoid problems in the first place.
I was right on both counts.
From the beginning, it seemed to me that the really hard part was going to be associating the additional sort orders with the additional (runtime) tabs. That is, it would be hard to make sure that the sort order records in the file, the selected tab and the applied sort actual mapped one-to-one. Not simply one-to-one but predictably and without variation. Everything has to come together at runtime in just the right way.
I do most of my programming with a pencil and paper. And as I
write this I am looking at my original notes. They are exclusively
concerned with setting up the tabs so that each links to a single,
predictable sort order. My notes indicate that I finally decided to
loop through the file containing the sort orders and read the
needed fields to a queue. At the same time, I determined how many
additional tabs had been created, the ordinal position of each new
tab relative to the current record and adding a number indicating
that ordinal position to the queue (mine, not the browse
object's). When the user changed tabs, I was planning on using
the tab position (Choice(?CurrentTab)) to retrieve the
matching queue record and ... you get the idea.
A typical programmer's solution.
I could not have been more wrong. Ensuring that the selected, additional tab and the additional sort order match could not have been easier. (Ok, it could have been easier but no one has a template available that does what I need.)
Basic Considerations
In the last installment I
demonstrated that user defined sort orders need to be added to the
Sort property after the sort orders supplied by the
templates. Where n is the number of tabs created within the IDE,
adding custom orders after the template's ensures that the
first n queue entries and the first n tabs match in the standard
way, as shown in Listing 1.
Listing 1. Sorting by tabs.
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
The key to this kingdom is that, as the developer of the application, I know the value of n. I know how many tabs were created at design time and, therefore, I know the initial number of entries in the sort queue. More important, I know the ordinal position of each.
That also means that I know that any supplemental orders and any runtime tabs start at n + 1. To ensure that queue entries and tabs are mapped in the same order, to ensure the mandatory correspondence, they only need to be created in the same order, using the same Key.
Too simple. (Proper prior planning ....)
That leaves only:
Activating Sorts on Tab Change
For all of this work to pay off, the additional tabs must actually activate the corresponding additional order.
The code in Listing 1 above is the standard template generated tab-changing code for a four-tabbed browse. It contains all the clues needed to finish this project.
The logical core of this code is the use of
CHOICE(?CurrentTab) to calculate the first parameter
of the SetSort() method. SetSort() then
does the actual work of re-setting the sort order.
For the sake of example, suppose you have a sheet with two tabs.
This means that the first custom order created at runtime will be
queue entry number three and the first runtime-created tab will be
CHOICE(?CurrentTab) = 3.
This means that for all dynamically created tabs:
Listing 2.
RETURN SELF.SetSort( Choice(?CurrentTab), Force )
ought to retrieve the correct sort queue record. In turn, this means that the desired order will be correctly set. I'm just "requisitioning" the standard code, after all.
Of course, it cannot possibly be quite so simple. It isn't.
The statement in Listing 2 will give incorrect sorts for the default tabs, tabs created in the IDE. Remember that in the case of two tabs, the queue entry for tab two is added first then the default order (tab one) is added to the queue. So, what you really need to do is use this code for runtime tabs only, leaving the generated code to handle "static" tabs, something more like:
Listing 2.
IF CHOICE(?CurrentTab) > n RETURN SELF.SetSort( Choice(?CurrentTab) , Force ) End
The question now is:
Where is this code placed?
And the answer is: "It depends."
It does depend. If the underlying browse has only one key
and, therefore, only one tab, no tab switching code will be
generated by the templates. There will be no check on
Choice(?CurrentTab) because ?CurrentTab
only has one position.
If there is more than one tab, code like that above will be generated. In this case, a structure like that in Listing 1 will be generated by the templates.
One Tab: In this case, since the templates supply
nothing, you need to supply everything. The code in Listing 2 does
everything necessary and it seems pretty clear that it should go in
the ?CurrentTab ... NewSelection embed.
Unfortunately, the code in Listing 2 will not work. It will fail to compile.
It will not compile because the browse object is not in scope
here (the NewSelection embed is not a derived virtual
method of the browse object, it is an event embed). But
SetSort is a property of the browse object and the
procedure knows about that, so:
RETURN BRW1.SetSort(Choice(?CurrentTab), 1 )
will do the job.
In the sample app that accompanies this article, check the
WithNewTabs_Corrected procedure ("corrected" for
supplementary orders being incorrectly activated when the browse is
first opened; this was discussed in the previous installment). This
placement of the revised code does just what we want.
Multiple Tabs: When there is more than one tab in the sheet before the ad hoc tabs, you want to use the code in Listing 3 as is.
Where?-Check the template code (Listing 1) and you will see that
there is an ELSE clause.
Runtime tabs will always return a value greater than n and will,
therefore, trigger the ELSE clause.
This means that code handling the created tabs must come before the template generated code. As this code tests only for values greater than the number of tabs created in the window formatter, the standard generated code will still do its job correctly.
Opening the Embeditor and finding the standard tab change code
shows that the embed immediately preceding is Before Refresh
Window for Browse Box (see Figure 1).
Figure 1. Embedding the tab change code.

If you check the Embed tree, this is a Legacy embed. I
can't say that I much care but if you're an OOP purist,
Local Objects ... BRW1 ... ResetSort is the
corresponding ABC virtual method.
So, there you have it, easy as 1-2-3:
Figure 2. The embed tree showing the embed point.

- create your tabs After the window is opened
- add your new sort orders after any existing key are added
- add the code to act on a tab change.
One caveat: unless you limit the number of records in the sort file, you have neither any idea of how many tabs will be created nor any control. Created tabs could well end up stacked on top of each other in a most unattractive fashion. Play it safe and select a scrolling option for the tabs (on the second tab on the Sheet's Property Worksheet) whether you think you need it or not.
An interesting consequence of this solution is that it will
continue to work even if Topspeed changes how it adds sort orders
to the queue. If the templates are changed so that tab one is added
to the queue first or so that there is no ELSE clause,
this solution will still do what is expected of it. The only thing
that Topspeed must do is maintain the AddSortOrder and
AppendOrder methods.
Reports
I mentioned reports as requiring runtime sorts.
This particular requirement would not seem to be solved by the
technique developed here. Indeed, it is not. However, the People
example provides a variation applicable to reports. See the
PrintPEO:KeyID report, ThisWindow
(ReportManager) ... OpenReport (before Parent Call)
embed.
Summary
Once you understand how the ABC templates implement multiple sort orders, duplicating and adapting that behavior to whole new program functionalities is not at all hard. The total code required is 22 lines or less.
What is difficult is tracing and understanding the ABC method calls. However, it is not necessary to master all the complexities of the browse object, just enough to comprehend the logic.
Article comments
Search ClarionMag
From the archives
Unit Testing Webinar Workshop Takes On Dates/Times
3/31/2011 12:00:00 AM
Recently John Hickey and David Harms hosted a webinar workshop on unit testing, using Pierre du Toit's article on Clarion and Excel dates and times as a source for a utility class. John and Dave learned a few things about the process, and hopefully the participants did too.

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.