Re: Fully Editable Combo Box with Array RowSourceType? by Ken
Ken
Sun Nov 02 21:28:46 CST 2003
>OK ... I've fiddled with this recently in a different scenario but maybe
>something like this will get you started.
>It's far from complete!
Thanks very much, Roger.
Your use of ASCAN() gave me an idea I hadn't even thought
of--detecting duplicate entries and giving the user a choice of
avoiding them. My solution doesn't do that all the time--it won't
detect a duplicate when typed in over an existing entry. That's
something that confuses me about comboboxes anyway--if you enter
something, you'd expect it to remain in the display. But if that
happens, you no longer have a blank display in which to enter the next
thing.
Anyway, here's where I'm at with it. Perhaps you'll find something you
can use here.
Ken
*** EDITABLE N-TIER DROP-DOWN COMBOBOX ***
oForm = CREATEOBJECT("Form")
oForm.Height = 460
oForm.AutoCenter = .T.
oForm.AddObject("TopLabel","MyLabel")
oForm.TopLabel.Caption = "FULLY EDITABLE N-TIER DROPDOWN COMBOBOX"
oForm.TopLabel.Width = 280
oForm.TopLabel.FontBold = .T.
oForm.TopLabel.Top = 3
oForm.TopLabel.Left = 50
oForm.TopLabel.Visible = .T.
oForm.AddObject("oData","mydata")
oForm.AddObject("Combo1","EditableCombo")
oForm.Combo1.Left = 5
oForm.Combo1.Top = 46
oForm.Combo1.Width = 100
oForm.Combo1.Visible = .T.
oForm.AddObject("Label1","MyLabel")
oForm.Label1.Caption = "States"
oForm.Label1.Top = 28
oForm.Label1.Left = 5
oForm.Label1.Visible = .T.
oForm.AddObject("Button1","SaveButton")
oForm.Button1.Top = 46
oForm.Button1.Left = 115
oForm.Button1.Visible = .T.
oForm.AddObject("SaveLabel","MyLabel")
oForm.SaveLabel.Caption = "<-- Saves changes to the underlying table"
oForm.SaveLabel.FontSize = 8
oForm.SaveLabel.Width = 204
oForm.SaveLabel.Top = 48
oForm.SaveLabel.Left = 170
oForm.SaveLabel.Visible = .T.
oForm.AddObject("Button2","NewButton")
oForm.Button2.Top = 80
oForm.Button2.Left = 115
oForm.Button2.Visible = .T.
oForm.AddObject("NewLabel","MyLabel")
oForm.NewLabel.Caption = "<-- Reconstructs original data"
oForm.NewLabel.FontSize = 8
oForm.NewLabel.Width = 204
oForm.NewLabel.Top = 82
oForm.NewLabel.Left = 170
oForm.NewLabel.Visible = .T.
oForm.AddObject("Shape2","Shape")
oForm.Shape2.SpecialEffect = 1
oForm.Shape2.BackColor = 16777215
oForm.Shape2.Width = 360
oForm.Shape2.Height = 157
oForm.Shape2.Left = 8
oForm.Shape2.Top = 112
oForm.Shape2.ZOrder(1)
oForm.Shape2.Visible = .T.
oForm.AddObject("InstructLabel1","MyLabel")
oForm.InstructLabel1.WordWrap = .T.
oForm.InstructLabel1.Width = 348
oForm.InstructLabel1.Height = 100
oForm.InstructLabel1.Top = 115
oForm.InstructLabel1.Left = 15
oForm.InstructLabel1.Caption = "You can enter a new item, edit an
existing item, or " ;
+ "delete an item from the combobox." ;
+ CHR(13) + CHR(13) ;
+ "If you type into the combobox when an
item is already " ;
+ "displayed, and press ENTER or TAB or
click the mouse, you'll be asked"
oForm.InstructLabel1.Visible = .T.
oForm.AddObject("InstructLabel2","MyLabel")
oForm.InstructLabel2.WordWrap = .T.
oForm.InstructLabel2.Width = 348
oForm.InstructLabel2.Height = 100
oForm.InstructLabel2.Top = 190
oForm.InstructLabel2.Left = 15
oForm.InstructLabel2.Caption = "if you want to create a new item.
Saying 'No' adds what you " ;
+ "typed to the displayed item. If the
combobox display is empty, " ;
+ "whatever you type is added as a new
item." ;
+ CHR(13) + CHR(13) ;
+ "CTRL+DEL deletes the displayed or
selected item from the list."
oForm.InstructLabel2.Visible = .T.
oForm.AddObject("Shape3","Shape")
oForm.Shape3.SpecialEffect = 1
oForm.Shape3.BackColor = 205
oForm.Shape3.Width = 360
oForm.Shape3.Height = 70
oForm.Shape3.Left = 8
oForm.Shape3.Top = 277
oForm.Shape3.ZOrder(1)
oForm.Shape3.Visible = .T.
oForm.AddObject("ProblemLabel","MyLabel")
oForm.ProblemLabel.ForeColor = 16777215
oForm.ProblemLabel.WordWrap = .T.
oForm.ProblemLabel.Width = 350
oForm.ProblemLabel.Height = 140
oForm.ProblemLabel.Top = 282
oForm.ProblemLabel.Left = 15
oForm.ProblemLabel.Caption = "The combo can't warn a user who types a
duplicate entry in over " ;
+ "an existing item, but it won't add the
duplicate either. Also, " ;
+ "if the user presses CTRL+DEL to delete
an item while the dropdown " ;
+ "is open, a blank line will appear in
the dropdown."
oForm.ProblemLabel.Visible = .T.
oForm.AddObject("ExplainLabel1","MyLabel")
oForm.ExplainLabel1.WordWrap = .T.
oForm.ExplainLabel1.Width = 350
oForm.ExplainLabel1.Height = 140
oForm.ExplainLabel1.Top = 360
oForm.ExplainLabel1.Left = 15
oForm.ExplainLabel1.Caption = "This system uses a data object to
manage retrieving " ;
+ "data from, and saving data to, the
table. Data shown " ;
+ "in the combobox is stored in an array
and manipulated " ;
+ "using array functions."
oForm.ExplainLabel1.Visible = .T.
oForm.AddObject("Shape1","Shape")
oForm.Shape1.SpecialEffect = 0
oForm.Shape1.Width = 360
oForm.Shape1.Height = 100
oForm.Shape1.Left = 8
oForm.Shape1.Top = 355
oForm.Shape1.ZOrder(1)
oForm.Shape1.Visible = .T.
oForm.AddObject("ExplainLabel2","MyLabel")
oForm.ExplainLabel2.WordWrap = .T.
oForm.ExplainLabel2.Width = 350
oForm.ExplainLabel2.Height = 140
oForm.ExplainLabel2.Top = 405
oForm.ExplainLabel2.Left = 15
oForm.ExplainLabel2.Caption = "Virtually all of this is " ;
+ "encapsulated within the combobox
class. Data is " ;
+ "tranferred between the combobox and
the data object " ;
+ "only when the SAVE or NEW buttons are
pressed."
oForm.ExplainLabel2.Visible = .T.
oForm.Show(1)
DEFINE CLASS EditableCombo AS ComboBox
Style = 0
OldListIndex = 0
OldDisplayValue = ""
PROCEDURE Init
THIS.AddProperty("aListItems[1,3]")
THIS.RowSourceType = 5
THIS.RowSource = "THIS.aListItems"
THIS.FillSourceArray()
ENDPROC
PROCEDURE FillSourceArray
LOCAL x
THIS.aListItems = ""
DIMENSION
THIS.aListItems(ALEN(THIS.PARENT.oData.aBusiness,1),3)
FOR x = 1 TO ALEN(THIS.PARENT.oData.aBusiness,1)
THIS.aListItems(x,1) = THIS.PARENT.oData.aBusiness(x,1)
THIS.aListItems(x,2) = THIS.PARENT.oData.aBusiness(x,2)
THIS.aListItems(x,3) = THIS.PARENT.oData.aBusiness(x,3)
ENDFOR
THIS.Requery()
*THIS.DisplayValue = THIS.aListItems(1,1)
ENDPROC
PROCEDURE InteractiveChange
IF THIS.OldListIndex = 0
THIS.OldListIndex = THIS.ListIndex
ENDIF
ENDPROC
PROCEDURE Valid
LOCAL theresult
theresult = .T.
IF NOT THIS.Value == THIS.DisplayValue
IF NOT EMPTY(THIS.DisplayValue) AND THIS.OldListIndex
> 0
IF MESSAGEBOX("New list item?",36,"") = 7
theresult = THIS.EditListItem()
ELSE
theresult = THIS.AddNewListItem()
ENDIF
ELSE
IF THIS.OldListIndex > 0
theresult = THIS.DeleteListItem()
ELSE
theresult = THIS.AddNewListItem()
ENDIF
ENDIF
ENDIF
THIS.OldListIndex = 0
RETURN theresult
ENDPROC
PROTECTED PROCEDURE EditListItem
LOCAL searchresult
searchresult =
ASCAN(THIS.aListItems,THIS.DisplayValue,1,THIS.ListCount,1,7)
IF searchresult > 0
IF MESSAGEBOX(ALLTRIM(THIS.DisplayValue) + " is already
in the list." + CHR(13) + CHR(13) ;
+ "Add it again anyway?",36,"") = 7
THIS.DisplayValue = THIS.Value
RETURN .F.
ENDIF
ENDIF
THIS.aListItems(THIS.OldListIndex,1) = THIS.DisplayValue
ASORT(THIS.aListItems,1)
THIS.Requery()
THIS.Value = THIS.DisplayValue
RETURN .T.
ENDPROC
PROTECTED PROCEDURE AddNewListItem
LOCAL searchresult, newvalue
IF EMPTY(THIS.DisplayValue)
RETURN .T.
ENDIF
searchresult =
ASCAN(THIS.aListItems,THIS.DisplayValue,1,THIS.ListCount,1,7)
IF searchresult > 0
IF MESSAGEBOX(ALLTRIM(THIS.DisplayValue) + " is already
in the list." + CHR(13) + CHR(13) ;
+ "Add it again anyway?",36,"") = 7
THIS.DisplayValue = ""
THIS.Value = THIS.DisplayValue
RETURN .F.
ENDIF
ENDIF
DIMENSION THIS.aListItems((ALEN(THIS.aListItems,1) + 1), 3)
newvalue = THIS.DisplayValue
AINS(THIS.aListItems,1)
THIS.aListItems(1,1) = THIS.DisplayValue
ASORT(THIS.aListItems,1)
THIS.Requery()
THIS.DisplayValue = newvalue
THIS.Value = THIS.DisplayValue
RETURN .T.
ENDPROC
PROTECTED PROCEDURE DeleteListItem
IF EMPTY(THIS.OldListIndex)
RETURN .T.
ENDIF
ADEL(THIS.aListItems,THIS.OldListIndex)
DIMENSION THIS.aListItems((ALEN(THIS.aListItems,1) - 1), 3)
ASORT(THIS.aListItems,1)
THIS.Requery()
THIS.Value = THIS.DisplayValue
RETURN .T.
ENDPROC
PROCEDURE SaveData
** This method transfers data from the combobox rowsource
array
** to the corresponding array in the Data Object. This is a
rough
** demo of the concept. The system I'm developing is much
more
** robustly automated. It will have a Data Manager to create
and
** control the Data Object, and it will use a separate
Business
** Object to handle validation of user input and mediate
between
** the user interface and the Data Manager.
LOCAL x
THIS.PARENT.oData.aBusiness = ""
DIMENSION
THIS.PARENT.oData.aBusiness(ALEN(THIS.aListItems,1),3)
FOR x = 1 TO ALEN(THIS.aListItems,1)
THIS.PARENT.oData.aBusiness(x,1) = THIS.aListItems(x,1)
THIS.PARENT.oData.aBusiness(x,2) = THIS.aListItems(x,2)
THIS.PARENT.oData.aBusiness(x,3) = THIS.aListItems(x,3)
ENDFOR
THIS.PARENT.oData.SaveData()
THIS.FillSourceArray()
ENDPROC
PROCEDURE KeyPress
** Lets users delete a displayed item from the list by
pressing CTRL+DEL.
LPARAMETERS nKeyCode, nShiftAltCtrl
IF nKeyCode = 147
* This gets us a list index number for the item to
delete.
THIS.InteractiveChange()
THIS.DeleteListItem()
* This was added to prevent a second item from being
deleted
* even though the display was empty, if the user
pressed CTRL+DEL
* again after deleting an item.
THIS.OldListIndex = 0
ENDIF
ENDPROC
ENDDEFINE
DEFINE CLASS SaveButton AS CommandButton
Width = 50
Height = 23
Caption = "SAVE"
PROCEDURE Click
THIS.PARENT.Combo1.SaveData()
ENDPROC
ENDDEFINE
DEFINE CLASS NewButton AS CommandButton
Width = 50
Height = 23
Caption = "NEW"
PROCEDURE Click
THIS.PARENT.oData.ReconstructData()
THIS.PARENT.Combo1.FillSourceArray()
THIS.Parent.Combo1.DisplayValue = ""
ENDPROC
ENDDEFINE
DEFINE CLASS mydata AS Custom
PROCEDURE Init
THIS.CheckForData()
THIS.AddProperty("aBusiness[1,3]")
THIS.FillBusinessArray()
ENDPROC
PROCEDURE CheckForData
IF NOT FILE("testtable.dbf")
SELECT 0
CREATE TABLE TestTable ( ;
Statename C(20), ;
StateID I, ;
ForKey I)
INSERT INTO TestTable (Statename, StateID, ForKey) ;
VALUES ("Virginia", 1, 20)
INSERT INTO TestTable (Statename, StateID, ForKey) ;
VALUES ("Maryland", 2, 30)
INSERT INTO TestTable (Statename, StateID, ForKey) ;
VALUES ("Ohio", 3, 40)
ENDIF
ENDPROC
PROCEDURE FillBusinessArray
THIS.aBusiness = ""
DIMENSION THIS.aBusiness(1,3)
SELECT * ;
FROM TestTable ;
INTO ARRAY THIS.aBusiness ;
WHERE NOT DELETED() ;
ORDER BY Statename
ENDPROC
PROCEDURE SaveData
THIS.SaveArrayToTable()
THIS.FillBusinessArray()
ENDPROC
PROTECTED PROCEDURE SaveArrayToTable
LOCAL x, foundit
STORE ALEN(THIS.aBusiness,1) TO rowcount
IF NOT USED("TestTable")
SELECT 0
USE TestTable
ELSE
SELECT TestTable
ENDIF
FOR x = 1 TO rowcount
IF EMPTY(THIS.aBusiness(x,2))
THIS.aBusiness(x,2) = THIS.GetNewKey()
APPEND BLANK
REPLACE StateID WITH THIS.aBusiness(x,2)
ENDIF
GO TOP
LOCATE FOR NOT DELETED() AND StateID =
THIS.aBusiness(x,2)
IF FOUND()
REPLACE StateName WITH THIS.aBusiness(x,1)
ENDIF
ENDFOR
SCAN FOR NOT DELETED()
x = 1
foundit = .T.
FOR x = 1 TO rowcount
IF StateID = THIS.aBusiness(x,2)
foundit = .T.
EXIT
ELSE
foundit = .F.
ENDIF
ENDFOR
IF foundit = .F.
DELETE
ENDIF
ENDSCAN
ENDPROC
PROTECTED PROCEDURE GetNewKey
RETURN (RECCOUNT() + 1)
ENDPROC
PROCEDURE KillData
IF USED("TestTable")
SELECT TestTable
USE
ENDIF
ERASE TestTable.dbf
ENDPROC
PROCEDURE ReconstructData
THIS.KillData()
THIS.CheckForData()
THIS.FillBusinessArray()
ENDPROC
PROCEDURE Destroy
IF MESSAGEBOX("Delete test data?",36,"") = 6
THIS.KillData()
ENDIF
ENDPROC
ENDDEFINE
DEFINE CLASS MyLabel AS Label
BackStyle = 0
ForeColor = 0
ENDDEFINE
*** END CODE ***