You always can owner-draw the listbox and thus create effects difficult to achieve otherwise: Set the Listbox.Style to lbOwnerDrawFixed and use the following event handler for OnDrawItem:
procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
ARect: TRect; State: TOwnerDrawState);
const
COL_DIST = 16;
var
sa: TStringArray;
x, y: Integer;
i: Integer;
begin
Listbox1.Canvas.FillRect(ARect);
sa := Listbox1.Items[Index].Split(#9);
x := ARect.Left + 2;
y := (ARect.Top + ARect.Bottom - Listbox1.Canvas.TextHeight('Tg')) div 2;
for i := 0 to High(sa) do
begin
Listbox1.Canvas.TextOut(x, y, sa[i]);
inc(x, FColWidths[i] + COL_DIST);
end;
end;
The array FColWidths here denotes the column widths which can be predefined or, better, must be measured in a previous step, after populating the listbox. Since there might not yet exist a valid canvas at this time I am using a temporary bitmap for text length determination:
var
FColWidths: array of Integer;
procedure TForm1.MeasureColWidths(AListBox: TListbox);
var
bmp: TBitmap;
i, j: Integer;
s: String;
sa: TStringArray;
w: Integer;
begin
SetLength(FColWidths, 0);
bmp := TBitmap.Create;
try
bmp.SetSize(1, 1);
bmp.Canvas.Font.Assign(AListbox.Font);
for i := 0 to AListbox.Items.Count-1 do
begin
sa := AListbox.Items[i].Split(#9);
if Length(sa) > Length(FColWidths) then
SetLength(FColWidths, Length(sa));
for j := 0 to High(sa) do
begin
w := bmp.Canvas.TextWidth(sa[j]);
if w > FColWidths[j] then FColWidths[j] := w;
end;
end;
finally
bmp.Free;
end;
end;
In the list items, finally, the columns are defined by putting tab characters into the item text:
procedure TForm1.FormCreate(Sender: TObject);
begin
Listbox1.Items.Add('Item 1'#9'Item 2'#9'Item 3');
Listbox1.Items.Add('A'#9'ABC'#9'abcdefg');
Listbox1.Items.Add('Item 4'#9'Item 5'#9'Item 6');
Listbox1.Items.Add('Testing...'#9'One more test'#9'ttt');
Listbox1.Items.Add('Some rather long text'#9#9'xyz');
Listbox1.Items.Add(#9'Only cell in this row');
MeasureColWidths(Listbox1);
end;