Paradox Community
Search:

 Welcome |  What is Paradox |  Paradox Folk |  Paradox Solutions |
 Interactive Paradox |  Paradox Programming |  Internet/Intranet Development |
 Support Options |  Classified Ads |  Wish List |  Submissions 


Paradox Programming Articles  |  Beyond Help Articles  |  Tips & Tricks Articles  


Multiple Selection List Boxes
© 2003 Kim A. Howarter

Download the sample files used in this article.

Preface

Paradox lists and combo boxes only allow one item to be selected at a time. This article shows how to get around this limitation by using a dialog box with two lists to make multiple selections. One list shows the list to select from and the other list shows the selections. This is very useful for selection queries or other actions that need to use one or more items from a list or table. In addition, the dual list dialog box is table driven so that you can use this one dialog box across your entire application.


Introduction

The table driven dual list dialog box (shown below) is one way to provide this feature in Paradox. The dialog box, table, library, and setup form are the only items needed to use the dialog box in your application. They will unzip to a directory named SelectionList. They can be put in any directory you choose, but it needs to be the working directory for them to work. This article describes how to add an alias for ease of use.

The dialog box is opened by passing a global variable through a library. Then, the dialog box opens a TCursor and locates this global variable in the SelectBX.db table. The fields in the record set up and operate the dialog box. There are only four library methods (three on the dialog box and one on the calling form) used. The sample files include all of the files to use the dialog box along with a button on the SelBxSU.fsl form to call the dialog box. This easily integrates with an application by using the existing library, or moving the four library methods to your library, or changing the library calls to use existing methods. The reason I suggest this is that most applications will already use some variation of these methods, so you may want to use your own methods to more fully integrate this into your coding style. The changes to accomplish this should be minimal.

Dialog Box List Selection

The selection list is shown on the left and the selected items are displayed on the right. Each list displays up to 15 selections and Paradox automatically adds scroll bars if the list is larger. The four buttons in the center box between the two lists control the addition and removal of items from the selected list. This allows the addition or removal of one item at a time or all at once. If a button is pressed and there is nothing to add/remove, a beep is heard and nothing else happens. Also, the return key, space bar, or a mouse double click will add/remove the active highlighted item. For details, see the page's keyPhysical method of the dialog box.


The SelectBX.db Table

The table contains the field values used to setup and operate the various parts of the dialog box and is keyed on the CriteriaName field. This ensures that there are no duplicate criteria names in the table (some of you might want to see a long integer key field, but I chose not to. If you like, add the integer key field, but include validation for duplicate entries in the CriteriaName field.).

All of the fields that are associated with the selection choices list (left side list box on the form) start with "List", and the fields associated with the selected list (right side list box on the form) start with "Item". Here are the table fields and their descriptions:

Field Name Field
Type
Description
CriteriaName A25* This field is the table key field to ensure that Criteria Names are not duplicated. Used to store the name assigned to the record. This field value is passed as a variable to the dialog box from the calling form, script, or library.
DialogBoxTitle A30 This is the title at the top of the Dialog Box. It should match or say something about what you are asking the user to select.
ItemOutputTable A30 This field stores the name of the table and its alias used to store the selected list items. This table will contain the selection information for your application use. This table will be created, emptied, or added to when the list is stored based upon the entry in the ItemProcess field. I usually store this table in the "PRIV" directory with a double underscore prefix so that Paradox deletes it upon exit, but it could be stored anywhere.
ItemOutputField A25 This is the name of the field in the ItemOutputTable used to store the selected list items in. It must be a field name from the ItemSourceTable.
ItemProcess A10 This field tells the dialog box whether to Create, Empty, or Add to the ItemOutputTable. If Empty or Add are used, then the table must already exist or an error will occur. See cmSortList() below for details.
ItemCopy A10 This field has two choices: Field or Record. Field only copies the values from the selected items field from the ListTable to the ItemOutputTable. Record copies the whole record. See text below for additional details.
ItemSourceTable A30 This table’s structure is used to create the ItemOutputTable that is used to store the selected list items in. It will normally be the ListTable. However, if you need a different table structure for the output table, then see the Output Table Setup below.
ListTable A30 This is the name of the table and its alias used to populate the list.
ListField A25 This is the name of the field that is used to populate the list. It comes from the table shown in the ListTable field above.
ListSortBy A15 This is the type of sort used to put the list in the proper order. The choices are: Alpha, Index, or Number. Alpha sorts the list alphabetically. Index uses a secondary table index to sort the list. Number uses numbers from a field in the list table to sort the list. One use of the Number order could specify the most frequent selections to be at the top of the list.
ListSortFrom A30 Used with the ListSortBy field when either Index or Number are selected. When Index is selected in the ListSortBy field, then the table index name is input in this field. When Number is selected in the ListSortBy field, then the ListTable field name is input in this field. For Alpha, this field is not used.
ListText M10 This is the text below the two list boxes that is used to give the user instructions. While the field is a memo type, it should not be more than about 130 characters or it will exceed the two lines allowed in the form. If you need more text, then make the form taller and enlarge the txtDescription box on the form to accommodate the additional text.
SystemUsage M10 This is the place to put any text that you want to add to the record that helps you identify the record or where it is used in your application. This field is not used to run the dialog box, only for your notes, etc.


The SelBxSU.fsl Form

The SelBxSU.fsl form makes it easy to set up the dialog box in the SelectBX.db table described above.

Dialog Box List Selection Setup

The List Table and Output Table field entries set up most of the drop down fields with list data.

List Table Setup:
  1. The List Field is automatically populated with the fields in the List Table.
  2. The List Sort By field is hard code populated with Alpha, Index, and Number. This field is not dependent upon another field entry to select from the list. If changed, it deletes the entry in the List Sort From field to prevent the wrong type of entry. For example, if the List Sort By field has Number for its entry and the List Sort From has a field name entry, then if the List Sort By field is changed to Index, the List Sort From entry is wrong and will cause an error, so it is deleted in the changeValue method. See List Sort From below.
  3. The List Sort From field is populated based upon the selection of Index or Number in the List Sort By field.
    1. If Index is selected then it is populated with the secondary indexes in the List Table.
    2. If Number is selected then it is populated with the fields in the List Table.
    3. If Alpha is selected then no entry is required in this field.
Output Table Setup:
  1. The Item Output Field is populated with the fields in the Item Source Table that are of the same type as the List Field. This drop down requires the List Table, List Field, and the Item Source Table entries to be made before it will be populated.
  2. The Item Process drop down is hard code populated with Create, Empty, and Add. This field is not dependent upon another field entry to select from the list.If Empty or Add are selected it will produce an error if the Item Output Table has not been created ahead of time.
    1. Create makes a new table that uses the Item Source Table as the source and names it according to the Item Output Table entry.
    2. Empty deletes the records from an existing table so that the selections can repopulate the existing table.
    3. Add just inserts the new selections into the existing table.
  3. The Item Copy drop down is hard code populated with Field and Record. This field is not dependent upon another field entry to select from the list.
    1. Field means that only the selected list values are copied to the Item Output Field.
    2. Record means that for each selected list value the whole record from the List Table is copied to the Item Output Table.
  4. The ItemSourceTable normally uses the same table as the ListTable. However as mentioned in the SelectBX.db Table section above, it can use another table name and structure. But be careful, as the structure of the table must include fields of the same type as the ListField and will only work reliably if the ItemCopy selection is Field. If the selection is Record, it uses the TCursor.copyRecord() method to copy the source table record and the method fails if any field in the source table record cannot be converted to the data type of the corresponding field in the destination record.
These drop downs should make setting up the dialog box easier to do. The rest of the fields should be self-explanatory with the descriptions in the SelectBX.db table description above.

On the bottom of the SelBxSU.fsl form, there are "Run" and "Close" buttons in a box that makes them self contained. I left room for an additional button for your use, such as a report button to document the setup information for the dialog box. The icons are put on as described in Ken Loomis's article How To Put An Icon On A Paradox Button Object. The SelectBX.fsl form is set up the same way with "OK" and "Cancel" buttons.. I like the looks of these buttons with icons, so I use them.


The Calling Form Button on the SelBxSU.fsl Form

I have included a "Run" button on the "SelBxSU.fsl" form to call the dialog box. It uses the criteria from the active record. After making your selections, click "OK" on the dialog box, a table window shows the items that you selected. This is just for test purposes so you can see your selections. However, in actual practice, a query or some other action would be taken. This dialog box can be called from a button, library method or a script depending upon the application's needs. I have made the button self-contained so it can be copied to your forms and your code built around it. In actual practice if you move the four library methods to your library, then this will need to change. The "Run" button code is shown below. The comments in the code explain its operation.
Const
;// This sets the table name and library name so you can easily change it in one place.
  stSelectBX = "selectBX.fsl"
  stLibSystem = "System.lsl"
endConst

Var
  libSystem  Library
endVar

method open(var eventInfo Event)
;// Open the System Library.
  if not libSystem.open(stLibSystem,GlobalToDesktop) then
     errorShow("Cannot open the System Library, please notify the System Administrator.")
    close()
  endIf
endMethod

Uses ObjectPal
;//System Library System.lsl
  cmSetGlobalVariable(const stVarName String, const atVarValue AnyType) Logical
endUses

method pushButton(var eventInfo Event)
Var
  foSelectBX             Form
  loResult               Logical
  stErrorCode,stErrorMsg String
  tvSelected             TableView
endVar

;// Unlock the record before proceeding so that any changes will take affect.
if isEdit() then
  if active'locked then
    if not action(DataUnlockRecord) then
      eventInfo.SetErrorCode(UserError)
      Return
    endIf
  endIf
endIf

;// Make sure that the error Environment String is empty.
  writeEnvironmentString("SelectBXErrorCode","")

;// Set the Global Variable to the current record's CriteriaName
  libSystem.cmSetGlobalVariable("stCriteriaName",CriteriaName.value)

;// Open the dialog box and handle the error if it doesn't open.
if not foSelectBX.open(stSelectBX) then
  if readEnvironmentString("SelectBXErrorCode") <> "" then
    stErrorCode = readEnvironmentString("SelectBXErrorCode")
    stErrorMsg = readEnvironmentString("SelectBXErrorMsg")
    msgStop(stErrorCode,stErrorMSg)
  else
    errorShow()
  endIf
endIf

;// Bring the dialog box to the top.
  foSelectBX.BringToTop()

;// Wait for user to make their selection.
  loResult = foSelectBX.wait()

;// Handle the dialog box return value.
if not loResult = True then
  Try
    foSelectBX.close() ;// User clicked the <Cancel> button.
    return
  OnFail
    errorClear()
  EndTry
else
  Try
    foSelectBX.close() ;// User Clicked <OK> button.
  OnFail
    errorClear()
  EndTry
endif

;// View your selections.
  tvSelected.open(ItemOutputTable)
  tvSelected.wait()
  tvSelected.close()
endMethod
The SelBxSU.fsl form makes it easy to input the setup information for each different use of the dialog box to the SelectBX.db table. Since the "CriteriaName" field is used as the key field, the only requirements to call the dialog box are to use the "CriteriaName" to identify the record in the SelectBX.db table for each different use.

If the requirements are the same from multiple calling locations, one "CriteriaName" and record for all of the locations should be used. An example would be two buttons that produce different results, but use a criteria table selected from the same list or two buttons on different forms that do the same thing. This allows reuse of the list source table to load the list for as many different uses as desired. For different uses of the dialog box, just enter a new record in the SelectBX.db table.

One note on the SelBxSU.fsl form. There is no alias used with the table, so if you move them to different directories it will require opening the form with a new table and resaving it.


The SelectBX.fsl Form

Now, the dialog form, where it all happens. The form starts out by opening the library and reading the global variable loaded from the calling form's button. If it fails to open the library, it loads an error message into two environment variables and sends them back to the calling form to display the error. This prevents two errors from displaying, one from the dialog box and one from the calling button. Then a TCursor is opened on the SelectBX.db table, which locates the record identified by the global variable stCriteriaName. Finally the title is assigned and the text box below the two lists is filled. All of these actions are in the "init" method and are performed based upon information from the SelectBX.db table. If desired, additional fields could be added to include changing the titles of the lists and the titles under the lists, which show the number of items in the lists. Just add the field(s) to the table, add the TCursor calls to the "init" method in the dialog box, and assign the variables to the feature in the dialog box. The table and library names are assigned in the Const section of the form. This makes it easy to change to the alias and names that you might want to use.

The selection list is populated on the Page Open method by setting both list.count properties to zero to first empty both lists. Next, the DataSource Property is used to load the Selection list. This requires less coding than using a ForNext loop and incrementing the list.count. Dynamic TCursors are used to open the tables and then they are closed in the Close method of the dialog box. Then a UIObject variable is attached to the Available Selections list to use in the sorting process and finally a call to the cmSortList() method. See the code and comments below.
method open(var eventInfo Event)
;// Set the background color to match the current windows setting.
;// Then finish executing the built-in code before populating the list.
  self.color = cl3dFace
  doDefault

;// Empty the two list box objects.
  AvailableSelectionsList.list.count = 0
  CurrentlySelectedList.list.count = 0

;// Re-populate the selection list using the DataSource property
;// Get the table path and name from the ListTable field.
;// Get the field name from the ListField field.
  uiListTo.attach(AvailableSelections.First)
  uiListTo.dataSource = dyTC["tcSelectBX"]."ListTable".value +
                        "." +
                        dyTC["tcSelectBX"]."ListField"

;// Attach to and sort the contents of the selection list box.
  uiListTo.attach(AvailableSelectionsList)
  bxActions.cmSortList()
endmethod
The page Arrive method recalculates the number of items in each list, clears any messages, and sets the timer for 1/2 second to delay setting the highlight on the selection list until the dialog box is fully opened. In the keyPhysical method, shortcut keys have been setup to process selections, and activate the "OK" and "Cancel" buttons. A passEvent is used to send the keyPhysical events on the list, listField, and box containers to the page for handling. This eliminates the need for the keyPhysical event code to be duplicated in several places. Review the code on the page to see how the shortcuts are set up and adjust them to meet your needs.

By double-clicking on an item in a list, it is immediately transferred to the other list. This is accomplished by making a call to the appropriate add or remove button from the mouseDouble method of each list.


cmSortList() custom method

The custom method "cmSortList" is used to sort both lists and it is located on the "bxActions" box that contains the four buttons, which is used for both lists. It is called from the page Open method as well as the four Add and Remove buttons. These are the only times the list changes, so too the only times it needs sorting.

There are three sort options (Alpha, Index, and Number) in this method. They are to sort the list alphabetically, use a second field in the ListTable to sort the list by numbers in any order you desire, or to use a different table index. By looking at the sample list with the numbers spelled out it becomes very apparent that being able to sort the list numerically is important. Another reason might be to sort a list of names that you want the most frequently used names at the top of the list. By allowing the use of a secondary index, it opens up many other possibilities.

A Switch statement is used to select between the three choices.
  1. The Alpha sort is accomplished by using the list value as both the index and the assigned value of the dynArray. The dynArray is then automatically sorted by its index/key value.
  2. The Number sort is accomplished by using the numVal property inside of a format statement that makes the string 10 characters wide by adding zeros on the left and converting back to a string. I originally used a while loop to add zeros until it had 10 characters using three lines of code. Between Larry DiGiovanni and Steven Green from the news groups I now have the one liner shown below. Thanks Guys! I chose 10 characters, as it seems to be long enough to cover any number in a list that would be used with this box, but you can make it longer if you wish. Then this 10-character string is used as the index/key in the dynArray, which again sorts the list for us.
  3. The Index sort is accomplished just like the Number sort except it uses the record number to populate the dynArray index/key.
Read through each sort option of the cmSortList() method to see that the information is pulled out of the SelectBX.db table by the TCursor to set up each different sort and then it steps through the list to populate the dynArray. The ListIndex field contains either "Alpha", "Index", or "Number" to determine the type of sort used. If you want to add other sort options, then add a new name to the ListSortBy field in the SelBxSU form and another Case statement to the Switch in the cmSortList() method. I will leave that coding up to you.
method cmSortList()
var
  dyList                      DynArray[]AnyType
  liListSortBy                LongInt
  siCounter                   SmallInt
  stListSortBy,stListSortFrom String
endVar

;// Case = Alpha: Read the list into the dynArray using the value as the
;// key to the dynArray.  This sorts the list alphabetically.
;// Case = Number: Read the list into the dynArray using the value from the
;// ListSortFrom as the index to create the number sort order.
;// Case = Index: Read the list into the dynArray using the value from the
;// ListSortFrom with a switchIndex to create the sort order.
Switch
  case dyTC["tcSelectBX"]."ListSortBy" = "Alpha" :
    ;// Copy the list into the dynArray.
    for siCounter from 1 to uiListTo.list.count
      uiListTo.list.selection = siCounter
      dyList[uiListTo.list.value] = uiListTo.list.value
    endFor
  case dyTC["tcSelectBX"]."ListSortBy" = "Number" :
    ;// Open the List Table.
    if not dyTC["tcPopulateList"].open(dyTC["tcSelectBX"]."ListTable") then
      msgStop("System Error","The cmSortList method failed to open the "+
              (dyTC["tcSelectBX"]."ListTable") + "table\n"+
              "Please notify the System Administrator.")
      Return
    endIf
    ;// Get the ListSortFrom Field value and put it in a string variable
    stListSortFrom = dyTC["tcSelectBX"]."ListSortFrom".value

    ;// This loop loads the list into a dynArray using the index to sort by.
    for siCounter from 1 to uiListTo.list.count

      ;// Load the list.selection into a smallInt variable.
      uiListTo.list.selection = siCounter

      ;// Locate the list.value in the table that populated the list.
      if not dyTC["tcPopulateList"].locate(dyTC["tcSelectBX"]."ListField",uiListTo.list.value)
      then
        msgStop("System Error","The cmSortList method failed to locate the list value.\n"+
                "Please notify the System Administrator.")
        Return
      endIf

      ;// Get the value from the index field of the List table.
      stListSortBy = dyTC["tcPopulateList"].(stListSortFrom)

      ;// This makes the list index 10 characters long so it will sort by the number.
      stListSortBy = String(format("W10,EZC", numVal(stListSortBy)))

      ;// Load the values and index into the dynArray.
      dyList[stListSortBy] = uiListTo.list.value
    endFor
  case dyTC["tcSelectBX"]."ListSortBy" = "Index" :

    ;// Open the List Table.
    if not dyTC["tcPopulateList"].open(dyTC["tcSelectBX"]."ListTable") then
      msgStop("System Error","The cmSortList method failed to open the "+
              (dyTC["tcSelectBX"]."ListTable") + " table\n"+
              "Please notify the System Administrator.")
      Return
    endIf

    ;// Switch to the index from the selectBX.db ListSortFrom Field.
    dyTC["tcPopulateList"].switchIndex(dyTC["tcSelectBX"]."ListSortFrom")
    for siCounter from 1 to uiListTo.list.count

      ;// Load the list.selection into a smallInt variable.
      uiListTo.list.selection = siCounter

      ;// Locate the list.value in the table that populated the list.
      if not dyTC["tcPopulateList"].locate(dyTC["tcSelectBX"]."ListField",uiListTo.list.value)
      then
        msgStop("System Error","The cmSortList method failed to locate the list value.\n"+
                "Please notify the System Administrator.")
        Return
      endIf

      ;// Put the current records Record Number in a variable.
      liListSortBy = dyTC["tcPopulateList"].RecNo()

      ;// This makes the list index 10 characters long so it will sort by the number.
      stListSortBy = String(format("W10,EZC", liListSortBy))

      ;// Load the value and index into the dynArray.
      dyList[stListSortBy] = uiListTo.list.value
    endFor
endSwitch

;// Copy the dynArray back to the list.
siCounter = 1
ForEach element in dyList
  uiListTo.list.selection = siCounter
  uiListTo.list.value = dyList[element]
  siCounter = siCounter + 1
endForEach

;// Allow the screen to refresh.
delayScreenUpdates(No)
endmethod

Adding And Removing Items From The Lists

The four buttons that Add and Remove items from the lists are powered by two custom methods attached to the "bxActions" box. Both custom methods receive an "Add" or "Remove" string, which is used with a Switch statement to attach the source and destination lists to UIObject variables. By controlling how the UIObjects are assigned it allows us to use one method to either add or remove items from the lists. Each custom method is described and the code listed below.

The cmAddRemoveAll() custom method first uses a switch statement to assign the two UIObjects based upon either "Add" or "Remove" being sent to the method. Next it checks to see if the source list is empty and if so, it beeps and returns because there is nothing to copy. Then it checks to see if the destination list is empty. If not empty, it copies the items back to the source list. Next it steps backward through the source list and copies it to the destination list. Then the source list count is set to zero to empty the list. Finally, move to the destination list, recalculate the totals at the bottom of the lists, and sort the list with the cmSortList() method. Below is the cmAddRemoveAll custom method code:
method cmAddRemoveAll(stAction String)
var
  siCounterRemove,siCounterAdd SmallInt
endVar

;// Attach to the "To" or "From" lists based upon the calling button.
  Switch
    case stAction = "Add" :
      uiListFrom.attach(AvailableSelectionsList)
      uiListTo.attach(CurrentlySelectedList)
    case stAction = "Remove" :
      uiListFrom.attach(CurrentlySelectedList)
      uiListTo.attach(AvailableSelectionsList)
    otherwise:
      Return
  endSwitch

;// If the list is empty, then beep because there is nothing to copy.
  if uiListFrom.list.count = 0 then
    beep()
    message("The List is Empty.")
    return
  endif

;// Delay any screen updates.
  DelayScreenUpdates(Yes)

;// Check to make sure the destination list is empty before we take action.
;// If not empty, then put the values back into the source list so they won't
;// get lost when the copy process is performed.
  if uiListTo.list.count > 0 then
    for siCounterRemove from uiListTo.list.count to 1 step -1
      uiListTo.list.selection = siCounterRemove
      uiListFrom.list.count = uiListFrom.list.count + 1
      uiListFrom.list.selection = uiListFrom.list.count
      uiListFrom.list.value = uiListTo.list.value
      uiListTo.list.value = ""
    endFor
  endif

;// Step through the list from the last item to the first
;// in order to add each item to the "other" list object.
  for siCounterAdd from uiListFrom.list.count to 1 step -1
    uiListTo.list.selection = siCounterAdd
    uiListFrom.list.selection = siCounterAdd
    uiListTo.list.value = uiListFrom.list.value
    uiListFrom.list.value = ""
  endFor

;// Empty the source list by setting its count to 0.
  uiListFrom.list.count = 0

;// Refresh the counters under the lists.
  AvailableSelectionsCount.action(DataRecalc)
  CurrentlySelectedCount.action(DataRecalc)

;// Sort the list and then allow screen updates.
  cmSortList()
  DelayScreenUpdates(No)

;// Move the Highlight to the copied list.
  uiListTo.list.selection = 1
  uiListTo.moveTo()
endMethod
The cmAddRemoveOneItem() custom method first uses a switch statement to assign the two UIObjects based upon either "Add" or "Remove" being sent to the method. Next another switch checks to see if the source list is empty or nothing is selected. Next the selected item is added to the destination list and assigned to a variable, and removed from the source list. Then the counters are recalculated, the highlight is repositioned on the item just added to the list, and if the source list is empty the active highlight is moved to the destination list. Below is the cmAddRemoveOneItem() custom method code:
method cmAddRemoveOneItem(stAction String)
var
  siCounter   SmallInt
  stFindValue String
endVar

;// Attach to the "To" or "From" lists based upon the calling button.
  Switch
    case stAction = "Add" :
      uiListFrom.attach(AvailableSelectionsList)
      uiListTo.attach(CurrentlySelectedList)
    case stAction = "Remove" :
      uiListFrom.attach(CurrentlySelectedList)
      uiListTo.attach(AvailableSelectionsList)
    otherwise:
      Return
  endSwitch

;// If the list is empty or nothing selected don't do anything.
  Switch
    case uiListFrom.list.count = 0 :
      beep()
      message("The List is Empty")
      return
    case uiListFrom.list.selection = 0 :
      beep()
      message("Nothing Selected")
      return
  endSwitch

;// Delay Screen updates.
  DelayScreenUpdates(Yes)

;// Add the selected (i.e., highlighted) item to the other list.
  uiListTo.list.selection = uiListTo.list.count + 1
  uiListTo.list.value = uiListFrom.list.value

;// Store the selected item that was just added so that we can reposition
;// the highlight on that item after it has been added to the list.
  stFindValue = uiListFrom.list.value

;// Go back and remove the item from the source list.
  uiListFrom.list.value = ""

;// Refresh the counters under the lists.
  AvailableSelectionsCount.action(DataRecalc)
  CurrentlySelectedCount.action(DataRecalc)

;// Sort the contents of the list.
  cmSortList()

;// Reposition the highlight on the item just added to the list.
  for siCounter from 1 to (uiListTo.list.count)
    uiListTo.list.selection = siCounter
    if (stFindValue = uiListTo.list.value) then
      quitLoop
    endif
  endfor

;// Move the active highlight to the other list if the active list is empty.
  Switch
    case uiListFrom.list.count <> 0 :
      uiListFrom.moveTo()
    case uiListTo.list.count <> 0 :
      uiListTo.moveTo()
  endSwitch

;// Allow the screen to refresh.
  DelayScreenUpdates(No)
endmethod

Summary

So there you have it. The table controlled dialog list provides many choices when using list selection boxes in your applications. This Selection List Dialog box is self-contained so it can be plugged in anywhere in your application. The library, the setup form, and a button to run the code from are included. I have done the same thing with drop down edit boxes on a dialog box to further reduce the number of these little dialog boxes and it ensures that they all look the same. They take much less code than this. By using this as a pattern, you should be able to create your own. I will leave it up to you to make use of these techniques.

Editor's note: Code was developed with Paradox 9 SP4.


Discussion of this article


 Feedback |  Paradox Day |  Who Uses Paradox |  I Use Paradox |  Downloads 


 The information provided on this Web site is not in any way sponsored or endorsed by Corel Corporation.
 Paradox is a registered trademark of Corel Corporation.


 Modified: 03 Feb 2004
 Terms of Use / Legal Disclaimer


 Copyright © 2001- 2004 Paradox Community. All rights reserved. 
 Company and product names are trademarks or registered trademarks of their respective companies. 
 Authors hold the copyrights to their own works. Please contact the author of any article for details.