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:

In order for the incremental search and entryfield emulation to work properly, edit controls used as seekers have the following restrictions: 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

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