Conditional Browse Formats
Posted September 1 1997
Clarion provides a powerful feature in their BrowseBox template to change orders, ranges, filters, etc., using one or more conditions. It's missing one important ability, though. When a user changes views, he may need to see the information presented in a different format. For example, when he switches from NameKey to CityKey, it might be nice to see the City field moved to the first column of the list box.
This is the purpose of this issue's article: Provide developer-specified conditional formats for the Clarion's BrowseBox template. I'll quickly touch on how this can be done manually, so that you understand the required method. The main focus, however, will be the template solution that offers more features along with easier implementation.
A Little Theory
Clarion's LIST controls are really quite versatile. You can specify either a QUEUE or a string of delimited choices for the FROM attribute. This attribute specifies where the list box is to get its information. If you specify a QUEUE, then you can use an optional format string to control the display of individual fields. If you don't use the format string, the entire QUEUE structure is treated a string and is displayed in the list box as-is. If you have only one field in your queue, then this would be fine.
This is not the standard way of doing things, though. Clarion's BrowseBox allows you to include multiple fields in your list box. The template automatically creates a field in the queue for each column in the list box. If you add the "Icon" attribute, it adds another queue field after the data field to hold the icon number. If you add the "Color" attribute, it adds another four queue fields.
If you look at the FORMAT string in the window structure, you'll discover that there are no field names specified. As long as the order of fields in the FORMAT string matches that of the QUEUE declared by the FROM attribute, this will work fine. In our case, however, we want to change the display order of fields. This requires indicating which queue field is needed for each field in the format string.
This is done with the #queue_field_number# attribute. If a field is out of order with the queue fields, then it needs to use this property. Then all columns after this one will also require the queue field number. (Actually, this isn't quite true, but the assumption is harmless and it simplifies our task somewhat.)
If there are any fields remaining in the queue after the last column in the format string, they are ignored.
Manual Implementation
If you search through Clarion's on-line help, you'll find a topic entitled, "How to Display the Sort Field First on a Multi-Key Browse". It covers the basics of reordering your list box columns. The essential steps are:
- Extract the list box's FORMAT string from the window structure.
- Parse the format string into individual columns.
- Add the queue field numbers to the format string. Remember that it doesn't hurt to explicitly include the field number, even if the format string field order matches that of the queue fields.
- Build a conditional structure (e.g.: IF/ELSIF/END) in an embed to change the list's format when the conditions change.
There are a few drawbacks to doing this manually:
- You can use only fields specified in the original browse. (However, you could specify extra fields in the BrowseBox control, then remove those from the various format strings as desired.)
- Format strings are complex, and it's difficult to manually change any aspects other than the field order. It's definitely more arduous than using the built in list box formatter.
- You have to manually perform this task for each browse that needs multiple formats. If TopSpeed changes the way list boxes work in a future version of Clarion, you may be forced to go back and manually change it everywhere it's been done. (This includes trying to remember all the procedures where you used the technique.)
There are a couple of additional shortcomings to the method described in Clarion's help topic:
- You are told place your format switching code into the NewSelection embed for the ?CurrentTab SHEET. If, however, your conditions don't involve changing tabs, you'll need to use a different embed and some extra code.
- If the user changes the column widths, then changes to another format, then changes back, the column resizing will be lost.
Template Implementation
By using a template to perform this task, we will be able to overcome all of the problems of the manual solution. The basic steps performed by the templates are roughly the same, but first we must enable the developer to indicate what he wants to achieve. This will require two separate templates.
Initially I thought I would be able to achieve it with a single template with the use of the FORMAT prompt type. This calls the list box formatter, but it doesn't allow population of fields from the dictionary. That's why the first template is a simple control template called "BrowseFormat", enabling the developer to specify the format using the familiar list box formatter with full dictionary support. For each different format, the developer populates one BrowseFormat control template onto his window. The template looks like this:
#CONTROL(BrowseFormat, 'Additional BrowseBox Format'), REQ(BrowseBox(Clarion)), DESCRIPTION('Browse Format'), MULTI
CONTROLS
LIST,AT(,,25,12),USE(?BrowseFormat),FROM(''),HIDE
END
This template can be populated only if an existing BrowseBox is present. Also, the MULTI attribute allows the developer to populate multiple formats for each BrowseBrowse. The control that's populated is a small LIST box that is hidden at run time and doesn't even contain any data (FROM('')). It is used only to allow the developer to format the columns.
The second template is an extension template called "SetBrowseFormat". It is here that the developer specifies one or more conditions. For each condition, he indicates which ?Control is associated with the condition. This enables you do specify the same format for more than one condition. If none of the conditions apply, then the original BrowseBox format is used. The header of this template looks like this:
#EXTENSION(SetBrowseFormat,'Support multiple BrowseBox formats'), DESCRIPTION('Set Browse Formats'), REQ(BrowseBox(Clarion))
#BOXED('Set Browse Formats')
#DISPLAY('If none of the following conditions apply, then the Browse''s default format is used.'), AT(10,,180,20)
#BUTTON('Conditional Browse Format'),MULTI(%BrowseFormats,%FormatCondition & ' - ' & %FormatControl), INLINE
#PROMPT('Condition:', @S255), %FormatCondition, REQ
#PROMPT('Format Control:', FROM(%Control, %ControlTemplate = 'BrowseFormat(MikeHanson)')), %FormatControl
#ENDBUTTON
#ENDBOXED
The #BUTTON,INLINE line is similar to Clarion's BrowseBox for the Conditional entries. Each entry is attached to a parent symbol called "%BrowseFormats". The actual entry values are "%FormatCondition" and "%FormatControl". The %FormatControl prompt could have used CONTROL as the prompt type, but the FROM(%Symbol, <Condition>) type narrows the list of controls for easier selection.
There is a good chance that the developer might include fields in the alternate formats that are not in the originalBrowseBox. To accommodate this, we must make sure that the fields are added to the%QueueField symbol. This is done with the following#ATSTART code:
#FOR(%BrowseFormats)
#FIX(%Control, %FormatControl)
#FIX(%ActiveTemplate, %ControlTemplate)
#FIX(%ActiveTemplateInstance, %ControlInstance)
#FOR(%ControlField)
#SET(%ValueConstruct, %ControlField)
#IF(INSTRING('[', %ValueConstruct, 1, 1))
#INSERT(%ReplaceCharacter,'[', '_')
#INSERT(%ReplaceCharacter,',', '_')
#INSERT(%ReplaceCharacter,']', '_')
#ENDIF
#FIX(%QueueField, %InstancePrefix & ':' & %ValueConstruct)
#IF(NOT %QueueField)
#ADD(%QueueField,%InstancePrefix & ':' & %ValueConstruct)
#SET(%QueueFieldAssignment, %ControlField)
#SET(%QueueFieldHasColor,%ControlFieldHasColor)
#SET(%QueueFieldHasIcon, %ControlFieldHasIcon)
#IF(%ControlFieldHasIcon)
#IF(%ControlFieldIcon)
#INSERT(%AddBrowseIcon(Clarion),%ControlFieldIcon)
#ENDIF
#FOR(%ConditionalIcons)
#INSERT(%AddBrowseIcon(Clarion),%ConditionalControlFieldIcon)
#ENDFOR
#ENDIF
#ENDIF
#ENDFOR
#FIX(%ActiveTemplate, %MyTemplate)
#FIX(%ActiveTemplateInstance, %MyInstance)
#ENDFOR
After we've added all possible fields to %QueueField, we must add a field number attribute. Note that the field number jumps if the column is using colors or icons:
#DECLARE(%QueueFieldNumber, %QueueField)
#DECLARE(%N)
#SET(%N, 1)
#FOR(%QueueField)
#SET(%QueueFieldNumber, %N)
#SET(%N, %N+1)
#IF(%QueueFieldHasColor)
#SET(%N, %N+4)
#ENDIF
#IF(%QueueFieldHasIcon)
#SET(%N, %N+1)
#ENDIF
#ENDFOR
The next step is to parse each of the list box format strings and add the necessary queue field numbers. Remember that we have the original BrowseBox control, plus our own BrowseFormat controls.
#DECLARE(%FormatStringNumber, LONG),UNIQUE #DECLARE(%FormatStringControl, %FormatStringNumber) #DECLARE(%FormatString, %FormatStringNumber) #ADD(%FormatStringNumber, 0) #SET(%FormatStringControl, %ListControl) #FIX(%Control, %ListControl) #INSERT(%BuildFormatString) #FOR(%BrowseFormats) #ADD(%FormatStringNumber, INSTANCE(%BrowseFormats)) #SET(%FormatStringControl, %FormatControl) #FIX(%Control, %FormatControl) #INSERT(%BuildFormatString) #ENDFOR
We declare the %FormatStringNumber as a UNIQUE symbol. Element zero will represent the format from the BrowseBox. For each control, the %BuildFormatString group is called.
#GROUP(%BuildFormatString),AUTO
#SET(%FormatString, EXTRACT(%ControlStatement, 'FORMAT', 1))
#SET(%FormatString, SUB(%FormatString, 2, LEN(%FormatString)-2))
#LOOP
#SET(%N, INSTRING(''' &|', %FormatString, 1))
#IF(%N > 0)
#SET(%FormatString, SUB(%FormatString, 1, %N-1) & SUB(%FormatString, INSTRING('''', %FormatString, 1, %N+4)+1, LEN(%FormatString)))
#ELSE
#BREAK
#ENDIF
#ENDLOOP
#!-----
#DECLARE(%P, LONG)
#DECLARE(%IgnoreUntil)
#DECLARE(%EndGroup, LONG)
#DECLARE(%C)
#DECLARE(%DoneOne)
#CLEAR(%IgnoreUntil)
#CLEAR(%DoneOne)
#SET(%N, 1)
#SET(%P, 1)
#FOR(%ControlField)
#FIND(%QueueFieldAssignment, %ControlField)
#CLEAR(%EndGroup)
#IF(SUB(%FormatString, %P, 1) = '[')
#SET(%P, %P+1)
#ENDIF
#LOOP,WHILE(NUMERIC(SUB(%FormatString, %P, 1)))
#IF(%P > LEN(%FormatString))
#ERROR(%Proceure & '(' & %Control & '): Error in Format String!')
#BREAK
#ENDIF
#SET(%P, %P+1)
#ENDLOOP
#LOOP
#IF(%P > LEN(%FormatString))
#BREAK
#ENDIF
#SET(%C, SUB(%FormatString, %P, 1))
#IF(%IgnoreUntil)
#IF(%C = %IgnoreUntil)
#CLEAR(%IgnoreUntil)
#ENDIF
#ELSE
#CASE(%C)
#OF('~')
#SET(%IgnoreUntil, '~')
#OF('@')
#SET(%IgnoreUntil, '@')
#OF('(')
#SET(%IgnoreUntil, ')')
#OF(']')
#SET(%EndGroup, %P)
#OF('[')
#BREAK
#ELSE
#IF(NUMERIC(%C))
#BREAK
#ENDIF
#ENDCASE
#ENDIF
#SET(%P, %P+1)
#ENDLOOP
#IF(%N <> %QueueFieldNumber OR %DoneOne)
#SET(%ValueConstruct, '#' & %QueueFieldNumber & '#')
#IF(%EndGroup)
#SET(%FormatString, SUB(%FormatString, 1, %EndGroup-1) & %ValueConstruct & SUB(%FormatString, %EndGroup, LEN(%FormatString)))
#CLEAR(%EndGroup)
#ELSE
#SET(%FormatString, SUB(%FormatString, 1, %P-1) & %ValueConstruct & SUB(%FormatString, %P, LEN(%FormatString)))
#ENDIF
#SET(%P, %P+LEN(%ValueConstruct))
#SET(%DoneOne, %True)
#ENDIF
#SET(%N, %N+1)
#IF(%ControlFieldHasColor)
#SET(%N, %N+4)
#ENDIF
#IF(%ControlFieldHasIcon)
#SET(%N, %N+1)
#ENDIF
#ENDFOR
The first section must extract the FORMAT attribute from the %ControlStatement. This is not as easy as it sounds, as it may be spread across two or more lines. We must extract it, then strip any line continuation sections. When we finally have the basic format string, we can proceed.
Each field within the string is represented by a width followed by justification. After this comes any number of additional attributes, including the @picture@, ~heading~, (offset), | (border), M (resizable), etc. None of these have any bearing on our task, so we can just ignore them. We also have to watch for the grouping delimiters ([ and ]).
For each column we compare the actual list format column number with the queue field number. If it doesn't match, or if the field number was inserted in an earlier column, then the queue field number is inserted for this column.
Again, the color and icon settings can affect the field count. It is important that these settings are not ON in one of the BrowseFormats if it is not also ON in the same field of the original BrowseBox control. All color and icon settings will be handled in the BrowseBox settings.
In the generated procedure, the format strings will be stored in local variables called BRWx::ST::Format:y, where "x" is the BrowseBox instance, and the "y" is the format string instance. Finally, we need two variables to manage the changing of the format. These will be called BRWx::ST::OldFormat and BRWx::ST::NewFormat. Notice that OldFormat is initialized with a value of -1, while the rest of the variables are AUTO.
#AT(%DataSectionAfterWindow),WHERE(ITEMS(%BrowseFormats)) #FOR(%FormatStringNumber) #SET(%ValueConstruct, %InstancePrefix & ':ST::Format:' & %FormatStringNumber) %[20]ValueConstruct CSTRING(512),AUTO #ENDFOR #SET(%ValueConstruct, %InstancePrefix & ':ST::OldFormat') %[20]ValueConstruct SHORT(-1) #SET(%ValueConstruct, %InstancePrefix & ':ST::NewFormat') %[20]ValueConstruct SHORT,AUTO #ENDAT
When the procedure starts, the initial formats are stored in the local variables. When the window is refreshed, the conditions are checked with the following code.
#FOR(%BrowseFormats)
#IF(INSTANCE(%BrowseFormats) = 1)
IF %FormatCondition
#ELSE
ELSIF %FormatCondition
#ENDIF
%InstancePrefix:ST::NewFormat = %(INSTANCE(%BrowseFormats))
#ENDFOR
ELSE
%InstancePrefix:ST::NewFormat = 0
END!IF
IF %InstancePrefix:ST::NewFormat <> %InstancePrefix:ST::OldFormat
EXECUTE %InstancePrefix:ST::OldFormat + 1
#FOR(%FormatStringNumber)
%InstancePrefix:ST::Format:%FormatStringNumber = %ListControl{PROP:Format}
#ENDFOR
END!EXECUTE
EXECUTE %InstancePrefix:ST::NewFormat + 1
#FOR(%FormatStringNumber)
%ListControl{PROP:Format} = %InstancePrefix:ST::Format:%FormatStringNumber
#ENDFOR
END!EXECUTE
%InstancePrefix:ST::OldFormat = %InstancePrefix:ST::NewFormat
END!IF
If you have one conditional browse format, it would create code like this:
IF CHOICE(?CurrentTab) = 2
BRW1::ST::NewFormat = 1
ELSE
BRW1::ST::NewFormat = 0
END!IF
IF BRW1::ST::NewFormat <> BRW1::ST::OldFormat
EXECUTE BRW1::ST::OldFormat + 1
BRW1::ST::Format:0 = ?Browse:1{PROP:Format}
BRW1::ST::Format:1 = ?Browse:1{PROP:Format}
END!EXECUTE
EXECUTE BRW1::ST::NewFormat + 1
?Browse:1{PROP:Format} = BRW1::ST::Format:0
?Browse:1{PROP:Format} = BRW1::ST::Format:1
END!EXECUTE
BRW1::ST::OldFormat = BRW1::ST::NewFormat
END!IF
Notice that the conditions are first checked. If the format needs to change, it saves the current format string (in case the user has changed the column widths). Then it assigns the new format string. Remember the default value of -1 in OldFormat. This prevents the saving stage the first time through.
Conclusion
As you can see, this is not a very complicated template. The toughest part is parsing the format string. Also the symbiotic relationship between the BrowseFormat and SetBrowseFormat templates is interesting. In the end, it adds much more flexibility when you are creating conditional browse views.
If you want to some free sample templates, including this one, just go to www.BoxsoftDevelopment.com and look for MHTPL*.ZIP.
Article comments
Post a comment
You must be logged on to post comments.
Talk To Us!
Search ClarionMag
From the archives
Sending Clarion Reports as Email Attachments (Part 1)
1/9/2001 12:00:00 AM
The email capability in version 5.5 is a nice addition to the Clarion toolset. What is still missing however, is the ability to easily send a report as an email attachment. In this article David Potter demonstrates one possible solution to this problem. Part 1 of 2.
