Recent

Author Topic: TTICombobox Binding with TStringList  (Read 1673 times)

Weitentaaal

  • Hero Member
  • *****
  • Posts: 554
TTICombobox Binding with TStringList
« on: August 25, 2022, 09:23:55 am »
Hello,

i recently was searching for an example but could not find anything. Then i found this thread:

https://forum.lazarus.freepascal.org/index.php?topic=46876.0

but it seems like no one knew about it/ or it just didn't work. I just wanted to ask if its Possible to Bind "Strings" Property with the Combobox.
I have no idea how this could work, but i would like to bind my Stringlist to the combobox.
Do i have to use an Enum for that or can i continue using my Stringlist. And i Have String Pairs in my List so i would have to use Names Property.
Is this possible ?

jamie

  • Hero Member
  • *****
  • Posts: 7063
Re: TTICombobox Binding with TStringList
« Reply #1 on: August 26, 2022, 03:41:44 am »
Why not simply use the TheCOmboBox.ITEMS.Assign(YourStringList);
The only true wisdom is knowing you know nothing

Weitentaaal

  • Hero Member
  • *****
  • Posts: 554
Re: TTICombobox Binding with TStringList
« Reply #2 on: August 29, 2022, 09:16:48 am »
Why not simply use the TheCOmboBox.ITEMS.Assign(YourStringList);

Heythanks, works !

But i have String Pairs. Do i have to override the Function or is there already a way to Handle  that ?

dje

  • Full Member
  • ***
  • Posts: 134
Re: TTICombobox Binding with TStringList
« Reply #3 on: August 29, 2022, 01:09:39 pm »
Assuming you have a TStringList containing name/value pairs, and you want a TComboBox showing just the Values, while being able to get the selected key:
This will setup the values in the TComboBox:
Code: Pascal  [Select][+][-]
  1. procedure ComboBoxSetValues(AComboBox: TCustomComboBox; AStrings: TStrings);
  2. var LIndex: integer;
  3. begin
  4.   AComboBox.Clear;
  5.   AComboBox.Tag := PtrInt(AStrings);
  6.   for LIndex := 0 to AStrings.Count - 1 do begin
  7.     AComboBox.Items.Add(AStrings.ValueFromIndex[LIndex]);
  8.   end;
  9. end;  
And, this will return the selected key:
Code: Pascal  [Select][+][-]
  1. function ComboBoxGetSelectedKey(AComboBox: TComboBox): string;
  2. begin
  3.   if AComboBox.ItemIndex < 0 then begin
  4.     Result := EmptyStr;
  5.   end else begin
  6.     Result := (TObject(AComboBox.Tag) as TStrings).Names[AComboBox.ItemIndex];
  7.   end;
  8. end;
Call ComboBoxSetValues() in the TComboBox.OnGetItems event, and ComboBoxGetSelectedKey() in TComboBox.OnSelect.

I have a feeling I know what you were hoping for. As far as I can tell, there is no way to intercept access to the internal TStrings class. At line 856 of customcombobox.inc a TStringListUTF8Fast is created with no way to intercept. There is also no way to intercept FItems access either, since all code accesses the private FItems field directly or the via Items property (indirectly). Ideally, the class would provide a virtual CreateStings method with which you could override and provide your own magic TStrings derived class.

On top of that, TString's doesn't provide a way to intercept access either. It provides a 'get' abstract method, which TStringList thens overides and access its private Flist^[Index].FString data. ie: No way to intercept to filter strings.

So, no, I dont see a way to intercept item access to filter what actually gets shown in the TComoBox. That said, you can use ownerdraw, which is what TComboBoxEx does, but then you are limited to csOwnerDrawFixed. Which means, no more text selection editing.

If you look in TCustomComboBox.DrawItem, you find a call to InternalDrawItem. You can copy InternalDrawItem out, write your own super class and change the call from:
Code: Pascal  [Select][+][-]
  1. InternalDrawItem(Self, FCanvas, ARect, Items[Index])
to
Code: Pascal  [Select][+][-]
  1. InternalDrawItem(Self, Canvas, ARect, ValueFromIndex[Index])
This creates a TComboBox class which will display only the Values of the internal Items. But, again, no editing.

I don't see a clean solution to create filtered TComboBox.Items, without adding a virtual CreateStings method. I'm sure I can come up with more hackery. But thats all I have ATM.

Feature request? A CreateStings() method with matching OnCreateStrings for TCustomComboBox?
« Last Edit: August 29, 2022, 01:22:05 pm by dje »

jamie

  • Hero Member
  • *****
  • Posts: 7063
Re: TTICombobox Binding with TStringList
« Reply #4 on: August 29, 2022, 04:08:21 pm »
If u use a ownerdraw combo box then you draw the text for each item as the box is getting painted

 This comes into play with the ondraw event at which point all info is provided to allow you to customize the display per index.

 Simply copy the part you want displayed for the textout.
The only true wisdom is knowing you know nothing

Weitentaaal

  • Hero Member
  • *****
  • Posts: 554
Re: TTICombobox Binding with TStringList
« Reply #5 on: August 30, 2022, 08:16:26 am »
Assuming you have a TStringList containing name/value pairs, and you want a TComboBox showing just the Values, while being able to get the selected key:
This will setup the values in the TComboBox:
Code: Pascal  [Select][+][-]
  1. procedure ComboBoxSetValues(AComboBox: TCustomComboBox; AStrings: TStrings);
  2. var LIndex: integer;
  3. begin
  4.   AComboBox.Clear;
  5.   AComboBox.Tag := PtrInt(AStrings);
  6.   for LIndex := 0 to AStrings.Count - 1 do begin
  7.     AComboBox.Items.Add(AStrings.ValueFromIndex[LIndex]);
  8.   end;
  9. end;  
And, this will return the selected key:
Code: Pascal  [Select][+][-]
  1. function ComboBoxGetSelectedKey(AComboBox: TComboBox): string;
  2. begin
  3.   if AComboBox.ItemIndex < 0 then begin
  4.     Result := EmptyStr;
  5.   end else begin
  6.     Result := (TObject(AComboBox.Tag) as TStrings).Names[AComboBox.ItemIndex];
  7.   end;
  8. end;
Call ComboBoxSetValues() in the TComboBox.OnGetItems event, and ComboBoxGetSelectedKey() in TComboBox.OnSelect.

I have a feeling I know what you were hoping for. As far as I can tell, there is no way to intercept access to the internal TStrings class. At line 856 of customcombobox.inc a TStringListUTF8Fast is created with no way to intercept. There is also no way to intercept FItems access either, since all code accesses the private FItems field directly or the via Items property (indirectly). Ideally, the class would provide a virtual CreateStings method with which you could override and provide your own magic TStrings derived class.

On top of that, TString's doesn't provide a way to intercept access either. It provides a 'get' abstract method, which TStringList thens overides and access its private Flist^[Index].FString data. ie: No way to intercept to filter strings.

So, no, I dont see a way to intercept item access to filter what actually gets shown in the TComoBox. That said, you can use ownerdraw, which is what TComboBoxEx does, but then you are limited to csOwnerDrawFixed. Which means, no more text selection editing.

If you look in TCustomComboBox.DrawItem, you find a call to InternalDrawItem. You can copy InternalDrawItem out, write your own super class and change the call from:
Code: Pascal  [Select][+][-]
  1. InternalDrawItem(Self, FCanvas, ARect, Items[Index])
to
Code: Pascal  [Select][+][-]
  1. InternalDrawItem(Self, Canvas, ARect, ValueFromIndex[Index])
This creates a TComboBox class which will display only the Values of the internal Items. But, again, no editing.

I don't see a clean solution to create filtered TComboBox.Items, without adding a virtual CreateStings method. I'm sure I can come up with more hackery. But thats all I have ATM.

Feature request? A CreateStings() method with matching OnCreateStrings for TCustomComboBox?

Thank you very much! im not realy sure if i get everything u did write but your Code did give me a push in the right direction.

If u use a ownerdraw combo box then you draw the text for each item as the box is getting painted

 This comes into play with the ondraw event at which point all info is provided to allow you to customize the display per index.

 Simply copy the part you want displayed for the textout.

This did work great ! but the Text Property was still a Pair. The Items did only show the Values. i did look for another Draw event for the Text but did not find anything.

dje

  • Full Member
  • ***
  • Posts: 134
Re: TTICombobox Binding with TStringList
« Reply #6 on: August 30, 2022, 09:07:47 am »
This did work great ! but the Text Property was still a Pair. The Items did only show the Values. i did look for another Draw event for the Text but did not find anything.
? There are a plethora of options: (Assuming ItemIndex > -1)
Code: Pascal  [Select][+][-]
  1.   ComboBox1.Items.GetNameValue(ComboBox1.ItemIndex, LName, LValue);
  2.   ComboBox1.Items.ValueFromIndex[ComboBox1.ItemIndex];
  3.   ComboBox1.Items.Names[ComboBox1.ItemIndex];

OwnerDraw is cool, but lacks a text editor. I have another idea. Given me 10 minutes.

Weitentaaal

  • Hero Member
  • *****
  • Posts: 554
Re: TTICombobox Binding with TStringList
« Reply #7 on: August 30, 2022, 09:46:29 am »
i think you did not understand me correctly, please take a look at the attached Screenshot.

The Code i Use:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.ComboBox5DrawItem(Control: TWinControl; Index: Integer;
  2.    ARect: TRect; State: TOwnerDrawState);
  3. begin
  4.    with Control as TComboBox do begin
  5.        Canvas.FillRect(ARect);
  6.        Canvas.TextOut(ARect.Left + 2, ARect.Top, Items.Names[Index]);
  7.    end;
  8. end;
  9.  
  10.  

Edit: Sorry first time i ever used a Windows Screenshot  :-[
« Last Edit: August 30, 2022, 09:56:28 am by Weitentaaal »

dje

  • Full Member
  • ***
  • Posts: 134
Re: TTICombobox Binding with TStringList
« Reply #8 on: August 30, 2022, 10:01:12 am »
O, right. Yes.  For some reason my eyes didn't see the csOwnerDrawEditableFixed option  8-)
csOwnerDrawFixed fixes that issue, but takes away editing.

dje

  • Full Member
  • ***
  • Posts: 134
Re: TTICombobox Binding with TStringList
« Reply #9 on: August 30, 2022, 10:03:17 am »
So, ok. Back to your original request: "i would like to bind my Stringlist to the combobox". This is the only way I can think of. Use a custom proxy/adapter TStringList.

USAGE:
Code: Pascal  [Select][+][-]
  1.   FMyStringList := TProxyStringList.Create(ComboBox1.Items);
  2.   FMyStringList.CommaText := 'Peter=Orange,Paul=Pear,Dave=Grapes,Bob=Apple';
I get Orange, Pear, Grapes & Apple in my TComboBox. Then if I run:
Code: Pascal  [Select][+][-]
  1.   FMyStringList.Values['Peter'] := 'Pie';
  2.   FMyStringList.Values['Frank'] := 'Pizza';
I then have "Pie, Pear, Grapes, Apple & Pizza" in my TComboBox. I think its a cool trick, but there are a number of gotchas if you arn't careful.
Code: Pascal  [Select][+][-]
  1. unit ProxyStringList;
  2.  
  3. interface
  4.  
  5. uses Classes, SysUtils;
  6.  
  7. type
  8.  
  9.   TProxyStringList = class(TStringList)
  10.   strict private
  11.     FProxy: TStrings;
  12.   public
  13.     constructor Create(AProxy: TStrings);
  14.   protected
  15.     function FilterIndex(AIndex: integer): string; virtual;
  16.     procedure Put(AIndex: integer; const AString: string); override;
  17.   public
  18.     procedure Clear; override;
  19.     procedure InsertItem(AIndex: integer; const AString: string; AObject: TObject); override;
  20.     procedure Delete(AIndex: integer); override;
  21.   end;
  22.  
  23. implementation
  24.  
  25. constructor TProxyStringList.Create(AProxy: TStrings);
  26. begin
  27.   inherited Create;
  28.   Assert(Assigned(AProxy));
  29.   FProxy := AProxy;
  30. end;
  31.  
  32. function TProxyStringList.FilterIndex(AIndex: integer): string;
  33. begin
  34.   Result := ValueFromIndex[AIndex];
  35. end;
  36.  
  37. procedure TProxyStringList.Clear;
  38. begin
  39.   inherited;
  40.   FProxy.Clear;
  41. end;
  42.  
  43. procedure TProxyStringList.Put(AIndex: integer; const AString: string);
  44. begin
  45.   inherited;
  46.   FProxy[AIndex] := FilterIndex(AIndex);
  47. end;
  48.  
  49. procedure TProxyStringList.InsertItem(AIndex: integer; const AString: string; AObject: TObject);
  50. begin
  51.   inherited;
  52.   FProxy.Insert(AIndex, FilterIndex(AIndex));
  53. end;
  54.  
  55. procedure TProxyStringList.Delete(AIndex: integer);
  56. begin
  57.   inherited;
  58.   FProxy.Delete(AIndex);
  59. end;
  60.  
  61. end.
« Last Edit: August 30, 2022, 10:08:50 am by dje »

Weitentaaal

  • Hero Member
  • *****
  • Posts: 554
Re: TTICombobox Binding with TStringList
« Reply #10 on: August 30, 2022, 10:23:53 am »
Wow creative idea !

i guess this way i don't even have to use RTTI Comboboxes. i w ill  update this thread if i run into any Problems.

Thank you Guys, especially @dje, for those Solutions. Glad someone could help me :)

 

TinyPortal © 2005-2018