Paintbox Handled Seeker
by Todd Kreuter

The Paintbox Handled Seeker is an inheritable custom control that provides search and navigation capabilities. Handled by the paintbox class, the control utilizes the extensive list of key codes recognized by the paintbox key related events to perform incremental searches, rowset navigation, entryfield emulation, as well as other tasks. The functionality provided by the Paintbox Handled Seeker class can be incorporated into other classes to create more intuitive controls, including seeker and combobox type controls.

The following article is primarily intended to provide information on the Paintbox Handled Seeker class. The discussion and sample code herein is limited to using the class with seeker type controls. Note however, that in an attempt to fully describe the Paintbox Handled Seeker class, some discussion or reference may not apply to seeker type controls.

Features

Incremental Search with Auto-Complete

With each “printable” key press, the control performs an incremental search. If the resulting input string is found, the remaining text in the edit control is automatically completed and selected. If the resulting input string is not found, the value in the edit control remains unchanged. Input is always restricted to strings found in the dataSource rowset. Below, Carolina Golf & Tennis is found among a list of customers after the string “car” has been typed in the Paintbox handled seeker.

Rowset Navigation

Rowset navigation is performed by row, by page, to first, or to last when respective navigation key presses are made.

Indexed and Non-Indexed Searches

The control employs two search methods, an indexed search and a non-indexed search, the method used dependent on the controls `findKey` property. The indexed search uses the rowset’s `findKey()` method while the non-indexed search uses the rowset’s `applyLocate()` method.

Multiple Edit Controls

The control is capable of containing multiple edit controls (entryFields), each representing an alternate seeker or a read-only display.

Datalinked

The edit controls acting as seekers are emulated through the paintbox and never receive focus, allowing the seekers to be dataLinked. Therefore, in addition to being a seeker and navigation tool, the control displays live dataLinked field values. This dual purpose allows for more flexibility in designing the layout of a form.

Planning

Metric

The Paintbox Handled Seeker uses entryfield emulation to make it appear as though edit controls have focus. In reality, they do not. This emulation requires various calculations which are currently based on the `metric` being pixels. Therefore, in order to use controls based on the Paintbox Handled Seeker class, the forms `metric` must be pixels (`metric = 6`).

Sub-Class

The Paintbox Handled Seeker, in addition to having the ability to handle a variable number of edit controls, can be used as the foundation for other types of controls. Because of its versatility, the SeekerPX class is designed as a base class with the intention that it be sub-classed. The SeekerPX class itself does not include hard coded edit controls, as the decision of how many edit controls to include and how they are named is left up to the developer with the use of a sub-class. For example:

| `class MySeeker(parentObj) of SeekerPX(parentObj) from "SeekerPX.cc"` `this.edit1 = new SPXEdit(this)` `with (this.edit1)` `width = 100` `endwith` `endclass`
| ``

This is a relatively simple piece of code which sub-classes the SeekerPX class to create a single edit seeker control. Sub-classed controls can obviously get more involved, defining multiple edit controls and or other controls such as textLabels, images and buttons. In addition, sub-classed controls can be created for generic or specific use.

Rowsets

The SeekerPX class has two types of rowsets, a dataLinked rowset and a dataSource rowset. The dataLinked rowset is the rowset from which the fields dataLinked to the edit controls belong. The dataSource rowset, also refered to as the control’s rowset, is the rowset from which the incremental search and navigation is perfomed. For seeker type controls, the dataLinked and dataSource rowsets are one in the same. For combobox type controls, the dataLinked and dataSource rowsets are two separate rowsets where the dataSource rowset supplies values to the dataLinked edit controls.

Edit Controls

The edit controls used by the Paintbox Handled Seeker must be SPXEdit controls (included in `SeekerPX.cc`). The SPXEdit control is specifically designed for and recognized by the Paintbox Handled Seeker class. An edit control can be used three ways, as a seeker, as a display, and or as a master edit:

- Edit controls that are dataLinked, visible and enabled are considered seekers.
- Edit controls that are dataLinked, visible, and not enabled, are considered displays.
- The master edit is used as the controlling link between the dataLinked rowset and the dataSource rowset, and can be a seeker, a display, or neither (datalinked and not visible). By default, the master edit is the first dataLinked edit. [Knowledge that the master edit exists is all that is required for the scope of this document.]
- An edit control that is not dataLinked is ignored.

In order for the incremental search and entryfield emulation to work properly, edit controls used as seekers have the following restrictions:

- DataLinked fields must be of type character.
- Values must be left aligned (the default for character type data).
- Format templates (`function` and `picture`) must be simple or literal character insert type templates, if used.
- Border related properties are protected and cannot be changed.

Indexed Search

Indexed searches, compared to non-indexed searches, are likely the best alternative when indexes are available as indexed searches are generally quicker and the rowset is sorted in a meaningful order. To use indexed searches, the rowset must have an active index and the controls `findKey` property must be `true`. Indexed searches expect uppercase index expressions, therefore, any character field which can have a lowercase character must have an uppercase index expression to work with indexed searches.

While you can prepare the rowset and control for indexed searches from various places in your code, consider using the controls `onEditSel()` and `setIndex()` methods which are provided for this purpose.

Non-Indexed Search

Unfortunately, required indexes are not always available to use indexed searches. This may be inherent, or a matter of design preference. Where indexes are not available, a non-indexed search is necessary. To use non-indexed searches, the controls `findKey` property must be `false` (default), this is regardless of whether the rowset has an active index or not. The non-indexed search uses the rowset’s `applyLocate()` method with `locateOptions` set to match partial length and ignore case.

The speed at which `applyLocate()` finds a match in a non-indexed search can vary depending on a couple factors:

1. The search is quickest when the rowset has no active index and a simple index expression on the field being searched exists (but is not active).
2. The search is slowest when the rowset has an active index.

Basically, the speed at which `applyLocate()` finds a match in a non-indexed search suffers when the rowset has an active index. Note that there are cases where using the SQL `Order By` may affect the speed as well. When `Order By` is used for a field where a simple index expression exists, that index becomes active, although the rowset’s `indexName` is empty. All said, the speed of non-indexed searches should be fairly quick.

The downside to using non-indexed searches is that the rowset is not sorted in a meaningful order. However, this may not be a factor if data is displayed one row at a time, as apposed to using a grid, or when using SQL `Order By`. To compliment non-indexed searches, the use of the rowset’s `locateNext()` method is available through the controls own `locateNext()` method.`LocateNext()` applies the previous locate criteria to search for another row. The controls `locate` property can be used to determine when the control is in `locate` mode and when `locateNext` is applicable.

Built-In Methods

The SeekerPX class has many built-in methods which are responsible for the operation of the control. While most of these methods are strictly for internal use, some can be used to enhance a sub-classed control by calling these methods directly or by overriding the method.

As an example, the `onNavigateKey()` method controls navigation of the dataSource rowset when a navigation key is pressed. Assume a sub-classed control includes two navigation buttons, Previous and Next. The `onclick()` event for these buttons could call `onNavigateKey()` to perform the navigation, passing the appropriate key code as a parameter (see keyHandler chart). This not only facilitates things, but ensures that navigation is controlled within the class.

Overriding a method is a bit more advanced and may be necessary when creating other types of controls. For example, a combobox type control sub-classed from the SeekerPX class may override the keyHandler’s `onKeyDown()` to provide an `autoDrop` feature. Something less complicated would be the use of `onGotFocus()` and `onLostFocus()`, which could be overridden, for example, to change the color of the control.

Use of the built-in methods requires that you gain a general understanding of the methods, or that you base your code on existing examples. For more information, refer to the `SeekerPX.cc` source code.

Reference

In-Design

Following are some basic steps to setting up a seeker type control that is sub-classed from PaintBox Handled Seeker. Note that the exact steps will vary from control to control.

Required

- Ensure the forms `metric` is pixels
- Drag and drop the control from the control palette to the form.
- Set the `dataLink` for one or more edit controls using the Inspector.
- Set edit control position properties (`left` and `width`).
- Set the controls position properties (`top`,`left`, and `width`).
- Create code to call the controls `beforeOpen()` method in the forms overriden `open()` method using the Source editor.

Optional

- Set edit control `function` and `picture` properties.
- Set edit control `enabled` property to `false` where the edit control is used as a display only.
- Create `canSelChange()` function using the Source editor.
- Create `onSelChange()` function using the Source editor.
- Create `onSelChange()` function using the Source editor.

Properties

The control uses custom properties to store variables and objects needed to function. Internal properties are changed from within the control and are available for reference only. External properties are available to be changed. The following is a list of the relevant internal use and available external use properties:

Internal Use `Focus` - Whether the control has focus or not. `Modify` - Whether the master edit value is updated for changes in the dataSource rowset. The default value is `false`. This must always be `false` for seeker type controls! `Rowset` - Reference to the controls dataSource rowset. `Seeker` - Reference to the edit control currently acting as the controls seeker. `Master` - Reference to the edit control acting as the controls master edit. `Locate` - Whether the control is in `locate` mode. The `locate` custom property is `true` when the previous `applyLocate()` or `locateNext()` resulted in a match in a non-indexed search. Use the `locate` property to determine when the controls `locateNext()` method is applicable. External Use `FindKey` - Whether the control uses an indexed (`true`) or non-indexed (`false`) search. The default value is `false`. This can be set though the controls `setIndex()` method. `PageNext` - The number of rows navigated when page navigation keys are pressed. The default value is `1`. This is useful when the dataSource rowset is dataLinked to a grid. `RangeKey` - The key used to constrain the controls dataSource rowset using `setRange()`.

Events

There are three event related methods available to execute additional code when the related event occurs. In order to utilize these event related methods, a function pointer must exist either in the sub-class or the form class (added using the Source editor). When providing the method in the sub-class, the function pointer must be in the following format:

| `Function <Method Name>` `// do something` `return`
| ``

When providing the method in the form class, the function pointer must be in the following format:

| `Function <Control Name>_<Method Name>` `// do something` `return`
| ``

canSelChange()

The `canSelChange()` event fires when the user attempts to change the control value through a change in the dataSource rowset. The return value (`true` or `false`) determines whether the change can be made.

While the control has focus, pressing a searchable or navigation key will attempt to search or navigate the dataSource rowset. If the search or navigation is successful, and the `canSelChange()` returns `true`, the controls value is updated. Note that for seeker type controls, the controls value is updated through the dataLink rather than by the control because the dataLinked rowset and the dataSource rowset are the same.

onSelChange()

The `onSelChange()` event fires when the user successfully changes the control value through a change in the dataSource rowset. The controls value ( master edit value) is passed as a parameter to the `onSelChange()` method.

onEditSel()

The `onEditSel()` event fires when the control instantiates and when the user selects another edit in a multi-edit control. Use `onEditSel()` to setup the control for the selected edit control, which becomes the controls seeker. The name of the edit control selected is passed as a parameter to the `onEditSel()` method. The following example uses the `onEditSel()` to set the rowset `indexName` and `findKey` flag through the controls `setIndex()` method:

| `function Seeker1_onEditSel(cName)` `if cName = "EDIT1"` `this.setIndex("Name")` `elseif cName = "EDIT2"` `this.setIndex("ID")` `endif`
| ``

Methods

The control contains many built-in methods, most of which are reserved for internal use, and some provided for external use (in your code). The following are the available external use methods:

BeforeOpen()

Required to finish instantiating the control and must be called from the forms overridden `open()` method.

SetFocus()

Gives focus to the control. If the control already has focus, the effect of calling `setFocus()` is that the controls seeker is reset (value is selected).

SetIndex()

Sets the dataSource rowset `indexName` and the controls `findKey` property.

| `// Syntax` `SetIndex(<IndexName expC> [, <FindKey expL>])` `// <IndexName expC>: The name of a valid indexName or an empty string (null, or "").` `// <FindKey expL>: The findKey setting. This is optional and defaults to true when a valid indexName is passed, and false when an empty indexName is passed.`
| ``

locateNext()

Calls the dataSource rowset's `locateNext()` method when the control is in `locate` mode. `LocateNext()` applies the previous locate criteria to search for another row in a non-indexed search. See also,`Locate` property.

KeyHandler

The `keyHandler` is the paintbox control which handles all key presses while the control has focus. The following chart lists the various keys that are recognized by the `keyHandler`, the class method called, and the resulting action. The key code is provided where relevant (`bCtrl` is whether the Ctrl Key is down).

| Key (Code , bCtrl) | Class Method | Resulting Action
| Printable Keys | onSearchKey( ) | Incremental Search
| ArrowUp (38) | onNavigateKey( ) | Rowset Navigation: – 1
| ArrowDown (40) | onNavigateKey( ) | Rowset Navigation: + 1
| PageUp (33) | onNavigateKey( ) | Rowset Navigation: – pageNext
| PageDown (34) | onNavigateKey( ) | Rowset Navigation: + pageNext
| Ctrl + PageUp (33, True) | onNavigateKey( ) | Rowset Navigation: First
| Ctrl + PageDown (34, True) | onNavigateKey( ) | Rowset Navigation: Last
| Ctrl + ArrowLeft | nextEdit( ) | Edit Select Left
| Ctrl + ArrowRight | nextEdit( ) | Edit Select Right
| Ctrl + C | Copy( ) | Copy selected text to clipboard
| ArrowLeft | onCursorKey( ) | Cursor Position: – 1
| ArrowRight | onCursorKey( ) | Cursor Position: + 1
| Home | onCursorKey( ) | Cursor Position: First
| End | onCursorKey( ) | Cursor Position: Last
| Shift + Cursor Key | onCursorKey( ) | Cursor Position and Text Select

Samples

There are two sample forms included with this article demonstrating sub-classed seeker type controls. The two forms are functionally identical, however, each use a different sub-classed control. Following is some basic information about the forms and sub-classed controls used.

SPXDemo1.wfm

This sample form uses SeekerME class which is designed for generic use. The SeekerME class defines five edit controls and includes an overriden `beforeOpen()` method to release unused edit controls before the controls opens. The textLabels used to label the edit controls are defined in the form class, and the `onEditSel()` function is included in the form class.

SPXDemo2.wfm

This sample form uses the SeekerCR class which is designed for specific use (in this case, a customer form). The SeekerCR class defines a predetermined number of edit controls, textLabels, and navigation buttons. In addition, dataLinks are set within an overridden `beforeOpen()` method, and the `onEditSel()` function is included in the sub-class.

Both sample forms use a four edit seeker-type control. Three of the edit controls are seekers, one is a display only. Of the three seekers, two use an indexed search (`Name`,`ID`), and one a non-indexed search (Region). The `onEditSel()` event is used to prepare each seeker (set the `indexName` and `findKey` properties) when that seeker is selected. When using the non-indexed seeker, the use of `locateNext()` is made available through the Edit menu.

Conclusion

If you have come this far, hopefully you have gained enough knowledge about the Paintbox Handled Seeker to be able to create your own seeker type controls, or at least incorporate the samples. For those of you who beg for more, a second article on using the Paintbox Handled Seeker to create a grid combobox control will follow, when time permits.

To download the included application, click here
(it is a 25 Kb zipped file)