Using ComboBox to select normal and complex values

The combobox (TComboBox) is a component that presents a list of several values where we typically choose one. Imagine a list of fruits:

  • Passion fruit
  • Açaí
  • Hazelnut
  • Melon
  • Litter
  • Papaya

And you need to present a form of choice to the user that is economical in a form, that is, that doesn't take up a lot of space, so we use this:

Selecting an element of a ComboBox

A ComboBox can be fed with literal values, that is, what the eye sees will be exactly what will be manipulated later. But there is also the option of choosing with the eyes a value, but having other attributes to use. It sounds strange, but it's easier to understand in practice.

Literal values in a ComboBox can be fed like this:

cbList.Clear;
cbList.Items.Add('Passion fruit'); cbList.Items.Add('Acaí'); cbList.Items.Add('Hazelnut'); cbList.Items.Add('Melon'); cbList.Items.Add('Apple'); cbList.Items.Add('Papaya'); 

However, the above example is not good programming practice in almost any language. If you work as a team, they would never let you implement the code above, because changing a value of one of the elements in the list would have to change all occurrences of that same value within the program as well, and if you forgot to change one of them it could create serious problems. , so whenever you feed a ComboBox with literal values, make use of constants.

Usually the constants should be in the section implementation, as this would ensure that the constants would only be visible to the project unit, declare constants outside the implementation section would mean that you intend to use these same constants on other drives - something very rare that if used wrongly confuses the IDE and can cause hard-to-find logic errors. So our setup would look like this:

var Form1: TForm1;

implementation

{$R *.lfm} { TForm1 }

const Maracuja='Passion fruit'; Acai='Açaí'; Hazel='Hazelnut'; Melao='Melon'; Maca='Apple'; Mamao='Papaya';

procedure TForm1.FormCreate(Sender: TObject); begin cbList.Clear;
  cbList.Items.Add(Passion fruit); cbList.Items.Add(Acai); cbList.Items.Add(Hazel); cbList.Items.Add(Melao); cbList.Items.Add(Maca); cbList.Items.Add(Mamao);
  cbList.ItemIndex:=-1;   

And to retrieve the chosen value we simply do this:

varSelectedCodeFruit:String; begin if cbLista.ItemIndex>=0 then begin FrutaCodigoEspanha:=cbLista.Items.Objects[cbLista.ItemIndex]; Memo1.Lines.Add('chosen fruit: '+IntToStr(FrutaCodigoEspanha)); end;  

There's nothing simpler than the code above and it's the most used method, but what if it was something more sophisticated? Imagine that by selecting Passion fruit you want the system to continue saying that the code has been selected 5, that's right, a number instead of the text that was displayed and selected. In this case you would use a method called AddObject which serves to link a list item to any previously created object, but creating an object implies creating constructors, destructors and properties and it's a lot of sand for our little truck so let's use a simplified form of object that will use an integer as object (that's weird, I'll explain later), it would look like this for us to populate our ComboBox:

procedure TForm1.FormCreate(Sender: TObject); begin cbList.Clear;
  cbList.Items.AddObject(Passion fruit, TObject(5)); cbList.Items.AddObject(Acai, TObject(10)); cbList.Items.AddObject(Avela, TObject(15)); cbList.Items.AddObject(Melao, TObject(20)); cbList.Items.AddObject(Maca, TObject(25)); cbList.Items.AddObject(Mamao, TObject(30));
  cbList.ItemIndex:=-1;

In the example above we were able to make a number pass as an object – we call this casting – casting numbers to objects is something very creative and would not work in this simplified way with other primitive types, but it works with numbers because the result is a pointer to memory. To retrieve the selected value, it would suffice then:

var ChosenCodeFruit:Integer; begin if cbLista.ItemIndex>=0 then begin FrutaCodigoEspanha:=PtrUInt(cbLista.Items.Objects[cbLista.ItemIndex]); Memo1.Lines.Add('chosen code: '+IntToStr(FrutaCodigoEspanha)); end; 
Combobox - selecting an element and returning a code

But what if it wasn't to return a number but another String, let's say when selecting Passion fruit I would like to return an alphanumeric code like 'cod_maracuja'? A practical example would be to select by description, but return a barcode, but to be didactic I will summarize in one word. This can be done in two ways, using a key and value pair or using objects. I'll show you the method that is simpler the key and value pair, you may have already noticed that properties like TItems and TStrings can be used with indices (ItemIndex:=x) and also pair/value, the latter usually used in something like MyStringList .Values['var'] to return the value linked to 'var', well, let's call it AddPair because the tactic is to assign a value to a variable from the list hence the name: AddPair from English 'add pair'. We will still use the same list as in our example, let's populate our base as follows:

procedure TForm1.FormCreate(Sender: TObject); begin cbList.Clear;
  cbLista.Items.AddPair(Passion fruit, 'cod_maracuja'); cbList.Items.AddPair(Acai, 'cod_acai'); cbList.Items.AddPair(Avela, 'cod_avela'); cbLista.Items.AddPair(Melao, 'cod_melao'); cbList.Items.AddPair(Liner, 'cod_maca'); cbList.Items.AddPair(Mamao, 'cod_mamao');
  cbList.ItemIndex:=-1;

But that wouldn't solve the problem yet, why not? Because our ComboBox would look like this:

Without csOwnerDraw, our user would see the pair of elements

So we need to change the way our ComboBox will be displayed, technically it is still the way above internally, but externally, that is, the way it will be displayed must be modified. Since we are going to design our own ComboBox, we will need to modify the property cbList.Style for csOwnerDrawFixed or csOwnerDrawVariable:

You need to modify the property cbList.Style for csOwnerDrawFixed or csOwnerDrawVariable

And then add a programming code to the event OnDrawItem responsible for designing the appearance of the ComboBox, I'll make it as simple as possible, later you can complement it by creating alternate colors if you think it's necessary:

procedure TForm1.cbListDrawItem(Control: TWinControl; Index: Integer; ARect: TRect; State: TOwnerDrawState); begin with (Control as TComboBox) do begin if odSelected in State then // requires unit LCLType begin Brush.Color := clWhite; Font.Color := clBlack; end; Canvas.FillRect(ARect); Canvas.TextOut(ARect.Left, ARect.Top, Items.Names[Index]); end; end;  

Now our elements will look like this:

Now we only see the first pair, without seeing its value

And to redeem the selected value, we will do this:

var Chosen_Fruit_Par_Name:String; Chosen_Fruit_Par_Value:String; begin if cbLista.ItemIndex>=0 then begin Fruta_Espanha_Par_Nome := cbLista.Items.Names[cbLista.ItemIndex]; Fruit_Selected_Par_Valor:= cbLista.items.Values[Fruta_Espanha_Par_Name]; Memo1.Lines.Add('Fruit chosen: '); Memo1.Lines.Add(' Name: '+Selected_Fruit_Par_Name); Memo1.Lines.Add(' Value: '+Fruta_Espanha_Par_Valor); end; 

And even more complex values? Imagine that by selecting Passion fruit you want the system to return a series of properties such as name, code and the weight of the fruit (our example) so in this case we cannot use a casting or the pair/value method as in the previous examples, we have to create a complete object to store These information. Let's declare our object with everything an object needs: constructor, destructor and properties:

  { TFruit } type TFruit=class(TObject) private FName: string; FPWeightGram: Integer; FCode: Integer; public constructor Create(AName:String; AWeightGram:Integer; ACode:Integer); destroyer Destroy; override; published property Name: string read FName write FName; property GramWeight: Integer read FPesoGram write FPesoGram; property Code:Integer read FCodigo write FCodigo; end;  

The create method with parameters is just to save us at the time of creation, so let's go to the code:

constructor TFruta.Create(AName: String; AWeightGram: Integer; ACode: Integer); begin Name := AName; WeightGram:=AWeightGram; Code:=ACode; end; destructor TFruta.Destroy; begin inherited Destroy; end;
       

Having created our object so now let's populate the ComboBox as follows:

procedure TForm1.FormCreate(Sender: TObject); begin cbList.Clear;
  cbList.Items.AddObject(Passion fruit, TFruta.Create(Passion fruit,550,5)); cbList.Items.AddObject(Acai, TFruta.Create(Acai,300,10)); cbList.Items.AddObject(Hazelnut, TFruit.Create(Hazelnut,15,15)); cbList.Items.AddObject(Melao, TFruta.Create(Melao,500,20)); cbList.Items.AddObject(Maca, TFruit.Create(Maca,55,25)); cbList.Items.AddObject(Mamao, TFruta.Create(Mamao,700,30));
  cbList.ItemIndex:=-1;      

And to display the value that was chosen then:

var Picked_Fruit:TFfruit; begin if cbLista.ItemIndex>=0 then begin Fruit_Selected := TFruta(cbLista.Items.Objects[cbLista.ItemIndex]); Memo1.Lines.Add('Chosen fruit: '+Chosen_Fruit.Name); Memo1.Lines.Add(' Code: '+IntToStr(Fruta_Espanha.Codigo)); Memo1.Lines.Add(' Approximate weight grams: '+IntToStr(Selected_Fruit.Gram Weight)); end;
Combobox - selecting an element and returning an object

Using an object instead of a text is more common than you might think, especially with databases, where with methods like getters and setters (property … get … set ….) you can get the desired results directly from the database at the same time. instead of prepopulating the list like we did. Our example was meant to be didactic.

I hope I have clarified the most common – perhaps the only – ways of using a ComboBox and returning data in different ways. If you need the source code of the examples used, you can download them from the following link:

1. Demonstration of using the TCombobox component in Delphi and FreePascal - common usage

2. Demo of using combobox - returning selected element code

3. Demonstration of using combobox - returning a different string from the selected element

4. Demonstration of using the combobox - returning the object of the selected element

In the following video, I demonstrate how to use these four different forms and an additional one that is an object derivative, but returning information from a database:

Demonstration video of how to apply the techniques mentioned in this article

If you want to study the examples, they can be obtained here:

https://github.com/gladiston/lazdemos_gsl