I\'m convinced this must be a common problem, but I can\'t seem to find a simple solution...
I want to use a combobox control with name value pairs as the items. ComboBo
I agree with Marjan Venema's solution, as it uses already built-in support for storing objects in a TStringList.
I've also dealt with this and I first derived my own combobox component using a tweaked version of the posted solution above with "csOwnerDrawFixed". I actually needed to store an ID (usually from a database) along with a text. The ID would be hidden from the user. I think this is a common scenario. The ItemIndex is used just to retrieve data from the list, it isn't really a meaningful variable, like in the posted example above.
So my idea was to concatenate the ID with the displayed text, separated by "#" for example, and override DrawItem() so it would only paint the text with the ID stripped. I extended this to keep more than an ID, in the form "Name#ID;var1;var2" eg. "Michael Simons#11;true;M". DrawItem() would strip everything after #.
Now that is good to start with, when you have few items in a combo. But when dealing with a larger list, scrolling the combo intensively uses CPU, as at every item draw, the text needs to be stripped.
So, the second version I made used the AddObject method. That traded CPU for a little more memory consumption, but It's a fair trade, because things were a lot faster.
Text that the user sees is stored normally in combo.Items, and all other data is stored in a TStringList associated with every element. No need to override DrawItem, so you can derive from eg. TmxFlatComboBox and keep its flat look as is.
Here's some of the most important functions of the derived component:
procedure TSfComboBox.AddItem(Item: string; Lista: array of string);
var ListaTmp: TStringList;
i: integer;
begin
ListaTmp:= TStringList.Create;
if High(Lista)>=0 then
begin
for i:=0 to High(Lista) do
ListaTmp.Add(Lista[i]);
end;
Items.AddObject(Item, ListaTmp);
//ListaTmp.Free; //no freeing here! we override .Clear() also and the freeing is done there
end;
function TSfComboBox.SelectedId: string;
begin
Result:= GetId(ItemIndex, 0);
end;
function TSfComboBox.SelectedId(Column: integer): string;
begin
Result:= GetId(ItemIndex, Column);
end;
function TSfComboBox.GetId(Index: integer; Column: integer = 0): string;
var ObiectTmp: TObject;
begin
Result:= '';
if (Index>=0) and (Items.Count>Index) then
begin
ObiectTmp:= Items.Objects[Index];
if (ObiectTmp <> nil) and (ObiectTmp is TStringList) then
if TStringList(ObiectTmp).Count>Column then
Result:= TStringList(ObiectTmp)[Column];
end;
end;
function TSfComboBox.SelectedText: string;
begin
if ItemIndex>=0
then Result:= Items[ItemIndex]
else Result:= '';
end;
procedure TSfComboBox.Clear;
var i: integer;
begin
for i:=0 to Items.Count-1 do
begin
if (Items.Objects[i] <> nil) and (Items.Objects[i] is TStringList) then
TStringList(Items.Objects[i]).Free;
end;
inherited Clear;
end;
procedure TSfComboBox.DeleteItem(Index: Integer);
begin
if (Index < 0) or (Index >= Items.Count) then Exit;
if (Items.Objects[Index] <> nil) and (Items.Objects[Index] is TStringList) then
TStringList(Items.Objects[Index]).Free;
Items.Delete(Index);
end;
In both versions, all data (even ID's) are represented as strings, because it keeps things more general, so when using them, you need to do a lot of StrToInt and vice versa conversions.
Usage example:
combo1.AddItem('Michael Simons', ['1', '36']); // not using Items.Add, but .AddItem !
combo1.AddItem('James Last', ['2', '41']);
intSelectedID:= StrToIntDef(combo1.SelectedId, -1); // .ItemIndex would return -1 also, if nothing is selected
intMichaelsId:= combo1.GetId(0);
intMichaelsAge:= combo1.GetId(0, 1); // improperly said GetId here, but you get the point
combo1.Clear; // not using Items.Clear, but .Clear directly !
Also, a
GetIndexByValue(ValueToSearch: string, Column: integer = 0): integer
method is useful, in order to retrieve the index of any ID, but this answer is already too long to post it here.
Using the same principle, you can also derive a custom ListBox or CheckListBox.