* * *

Author Topic: [SOLVED] TListBox with separators  (Read 616 times)

tudi_x

  • Sr. Member
  • ****
  • Posts: 400
[SOLVED] TListBox with separators
« on: December 07, 2017, 05:27:00 pm »
hi All,
i am currently populating a TListBox with items.
these items can form groups of items.

please advise if there would be a possibility of having a separator line between the group of items or i should use a TStringGrid with 1 column and leave the horizontal line visible only between the group items.
or i should use another component.
thank you
« Last Edit: December 10, 2017, 03:04:12 pm by tudi_x »

howardpc

  • Hero Member
  • *****
  • Posts: 2422
Re: List Box with groups of items separated by separator
« Reply #1 on: December 07, 2017, 05:48:40 pm »
It all depends on what the listbox's purpose is.
Is it display-only? If so, a line of separator characters may help the aesthetics of your GUI.

Do you ever want to sort the items after they have been displayed, or add/delete items? Then coping with separator lines would most likely be a pain.
Do you need your user to be able to select item(s)? Here again coping with separator lines would give you a lot of trouble compared to using a control such as a treeview that is designed with nodes/groups in mind and already (optionally) shows separator lines.

GetMem

  • Hero Member
  • *****
  • Posts: 2464
Re: List Box with groups of items separated by separator
« Reply #2 on: December 07, 2017, 06:27:59 pm »
In my opinion a tree is more suitable for displaying groups, however if you choose to go with TListBox, owner draw the items and display different groups with different colors(see attachment).

howardpc

  • Hero Member
  • *****
  • Posts: 2422
Re: List Box with groups of items separated by separator
« Reply #3 on: December 07, 2017, 07:09:02 pm »
@GetMem - nice example. But you forgot to free the data objects.  :o
Code: Pascal  [Select]
  1. procedure TForm1.FormDestroy(Sender: TObject);
  2. var
  3.   i: Integer;
  4. begin
  5.   for i:=0 to ListBox.Items.Count-1 do
  6.     TData(ListBox.Items.Objects[i]).Free;
  7. end;
 

GetMem

  • Hero Member
  • *****
  • Posts: 2464
Re: List Box with groups of items separated by separator
« Reply #4 on: December 07, 2017, 09:17:45 pm »
@ howardpc
True. Thanks.

wp

  • Hero Member
  • *****
  • Posts: 3953
Re: List Box with groups of items separated by separator
« Reply #5 on: December 07, 2017, 11:52:37 pm »
Here's another owner-drawn example in which a separating line is encoded as listbox item '-', and if an item text begins with a '-' the item is centered over the dividing line.
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

tudi_x

  • Sr. Member
  • ****
  • Posts: 400
Re: List Box with groups of items separated by separator
« Reply #6 on: December 08, 2017, 11:23:23 am »
thank you All!
i think i am getting close with the below, shows nice on Linux also:
Code: Pascal  [Select]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, Types;
  9.  
  10. type
  11.   TForm1 = class(TForm)
  12.     Button1: TButton;
  13.     ListBox1: TListBox;
  14.     procedure FormCreate(Sender: TObject);
  15.     procedure ListBox1DrawItem(Control: TWinControl; Index: integer; ARect: TRect; State: TOwnerDrawState);
  16.   private
  17.  
  18.   public
  19.  
  20.   end;
  21.  
  22. var
  23.   Form1: TForm1;
  24.  
  25. implementation
  26.  
  27. {$R *.lfm}
  28.  
  29. uses
  30.   LCLType;
  31.  
  32. procedure TForm1.FormCreate(Sender: TObject);
  33. begin
  34.   with ListBox1.Items do
  35.   begin
  36.     Add('London');
  37.     Add('Berlin');
  38.     Add('-');
  39.     Add('Casablanca');
  40.     Add('Pretoria');
  41.     Add('-Cities in Asia');
  42.     Add('Tokyo');
  43.     Add('Manila');
  44.     Add('-Cities in America');
  45.     Add('New York');
  46.     Add('Chicago');
  47.     Add('Rio de Janeiro');
  48.     Add('Lima');
  49.     Add('-');
  50.   end;
  51. end;
  52.  
  53. procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: integer; ARect: TRect; State: TOwnerDrawState);
  54. const
  55.   MARGINLine = 42;
  56.   MARGINText = 12;
  57. var
  58.   lb: TListbox;
  59.   s: string;
  60.  
  61. begin
  62.   lb := Control as TListbox;
  63.   s := lb.Items[Index];
  64.   lb.Canvas.Brush.Style := bsSolid;
  65.  
  66.   if s[1] = '-' then
  67.   begin
  68.     if lb.Focused then lb.Canvas.Brush.Color:= clWhite;  //how can i not display the focus rectangle or make it transparent
  69.     lb.ItemRect(Index).Height:= 3;    //decrease height of line container
  70.  
  71.     lb.Canvas.Font.Style := [fsBold];
  72.     lb.Canvas.FillRect(ARect);
  73.     lb.Canvas.Pen.Style := psSolid;
  74.     lb.Canvas.Pen.Color := lb.Canvas.Font.Color;
  75.  
  76.     lb.Canvas.Line(ARect.Left + MARGINLine, (ARect.Top + ARect.Bottom) div 2, ARect.Right - MARGINLine, (ARect.Top + ARect.Bottom) div 2);
  77.     Delete(s, 1, 1);
  78.   end
  79.   else
  80.   begin
  81.     lb.Canvas.FillRect(ARect);
  82.     lb.Canvas.Font.Style := [];
  83.  
  84.     lb.Canvas.TextOut(ARect.Left + MARGINText, (ARect.Top + ARect.Bottom - lb.Canvas.TextHeight('Tg')) div 2, s);
  85.   end;
  86. end;
  87.  
  88. end.

please advise on how i could disable the rectangle drawn when selecting the line or make this rectangle transparent (or disable the line item) and how i could make only the line container less thick (drop height of the line field).
« Last Edit: December 08, 2017, 11:42:57 am by tudi_x »

wp

  • Hero Member
  • *****
  • Posts: 3953
Re: List Box with groups of items separated by separator
« Reply #7 on: December 08, 2017, 12:30:05 pm »
Here's another variant. The dividing lines are not in separate items now, and thus cannot be selected any more. They are drawn at the top of the lines which begin with a dash (or any other reserved character, of course). Since these lines are higher by 1 pixel the OnMeasureItem event is handled to return the new line height. The background of the selected row is drawn in two steps: first, the top part with the line to get a white background, and then the lower part with the text to get the selected color. It is advantageous to turn off option lbDrawFocusRect because the FocusRect is drawn around the full line rect, and this looks wrong to me.
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

tudi_x

  • Sr. Member
  • ****
  • Posts: 400
Re: List Box with groups of items separated by separator
« Reply #8 on: December 08, 2017, 02:03:34 pm »
thank you so very much!

i need to create the list dynamically as part of a group of objects.
i modified the code but the events do not fire in it. could you please advise on my mistake?

Code: Pascal  [Select]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, Types;
  9.  
  10. type
  11.   TForm1 = class(TForm)
  12.     procedure FormCreate(Sender: TObject);
  13.  
  14.   private
  15.     ListBox1: TListBox;
  16.     procedure ListBox1DrawItem(Control: TWinControl; Index: integer; ARect: TRect; State: TOwnerDrawState);
  17.     procedure ListBox1MeasureItem(Control: TWinControl; Index: integer; var AHeight: integer);
  18.   public
  19.  
  20.   end;
  21.  
  22. var
  23.   Form1: TForm1;
  24.  
  25. implementation
  26.  
  27. {$R *.lfm}
  28.  
  29. uses
  30.   LCLType;
  31.  
  32. { TForm1 }
  33.  
  34. procedure TForm1.FormCreate(Sender: TObject);
  35. begin
  36.   ListBox1 := TListBox.Create(self);
  37.   ListBox1.Align := alClient;
  38.   ListBox1.OnDrawItem := @ListBox1DrawItem;
  39.   ListBox1.OnMeasureItem := @ListBox1MeasureItem;
  40.   ListBox1.Parent := self;
  41.  
  42.   with ListBox1.Items do
  43.   begin
  44.     Add('Paris');
  45.     Add('Rome');
  46.     Add('London');
  47.     Add('Berlin');
  48.     Add('-Casablanca');
  49.     Add('Cairo');
  50.     Add('Khartoum');
  51.     Add('Pretoria');
  52.     Add('-Tokyo');
  53.     Add('Beijing');
  54.     Add('Manila');
  55.     Add('-New York');
  56.     Add('Chicago');
  57.     Add('Rio de Janeiro');
  58.     Add('Lima');
  59.     Add('-');
  60.   end;
  61. end;
  62.  
  63. const
  64.   MARGINLine = 2;
  65.   MARGINText = 12;
  66.   MARGINvert = 2;
  67.   HLine = 1;
  68.  
  69. procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: integer; ARect: TRect; State: TOwnerDrawState);
  70. var
  71.   lb: TListbox;
  72.   s: string;
  73.   hasLine: boolean;
  74.   dy: integer;
  75.  
  76. begin
  77.   lb := Control as TListbox;
  78.   s := lb.Items[Index];
  79.   hasLine := (s <> '') and (s[1] = '-');
  80.  
  81.   if hasLine then
  82.     dy := HLine
  83.   else
  84.     dy := 0;
  85.  
  86.   lb.Canvas.Brush.Style := bsSolid;
  87.   lb.Canvas.Font.Assign(lb.Font);
  88.  
  89.   if odSelected in State then
  90.   begin
  91.  
  92.     if hasLine then
  93.     begin
  94.       lb.Canvas.Brush.Color := lb.Color;
  95.       lb.Canvas.FillRect(ARect.Left, ARect.Top, ARect.Right, ARect.Top + dy);
  96.       Inc(ARect.Top, hLine + 2);
  97.     end;
  98.  
  99.     if lb.Focused then
  100.       lb.Canvas.Brush.Color := clHighlight
  101.     else
  102.       lb.Canvas.Brush.Color := clGray;
  103.  
  104.     lb.Canvas.Font.Color := clHighlightText;
  105.  
  106.   end
  107.   else
  108.   begin
  109.     lb.Canvas.Brush.Color := lb.Color;
  110.     lb.Canvas.Font.Color := clWindowText;
  111.     dy := 0;
  112.   end;
  113.  
  114.   lb.Canvas.FillRect(ARect);
  115.  
  116.   if hasLine then
  117.   begin
  118.     lb.Canvas.Pen.Style := psSolid;
  119.     lb.Canvas.Pen.Color := clWindowText;
  120.     lb.Canvas.Line(ARect.Left + MARGINLine, ARect.Top + hLine, ARect.Right - MARGINLine, ARect.Top + hLine);
  121.     Delete(s, 1, 1);
  122.   end;
  123.  
  124.   lb.Canvas.Brush.Style := bsClear;
  125.   lb.Canvas.Pen.Color := clWindowText;
  126.   lb.Canvas.Font.Color := clRed;
  127.   lb.Canvas.TextOut(ARect.Left + MARGINText, (ARect.Top + dy + ARect.Bottom - lb.Canvas.TextHeight('Tg')) div 2, s);
  128. end;
  129.  
  130. procedure TForm1.ListBox1MeasureItem(Control: TWinControl; Index: integer; var AHeight: integer);
  131. var
  132.   lb: TListbox;
  133.   h: integer;
  134.   s: string;
  135.  
  136. begin
  137.   lb := Control as TListBox;
  138.   lb.Canvas.Font.Assign(lb.Font);
  139.   h := lb.Canvas.TextHeight('Tg') + MARGINvert * 2;
  140.   s := lb.Items[Index];
  141.  
  142.   if (s <> '') and (s[1] = '-') then
  143.     Inc(h, HLine);
  144.   AHeight := h;
  145. end;
  146.  
  147. end.

howardpc

  • Hero Member
  • *****
  • Posts: 2422
Re: List Box with groups of items separated by separator
« Reply #9 on: December 08, 2017, 02:16:48 pm »
In your FormCreate add
Code: Pascal  [Select]
  1. ListBox1.Style := lbOwnerDrawVariable;

tudi_x

  • Sr. Member
  • ****
  • Posts: 400
Re: List Box with groups of items separated by separator
« Reply #10 on: December 08, 2017, 02:40:18 pm »

FTurtle

  • Full Member
  • ***
  • Posts: 204
Re: List Box with groups of items separated by separator
« Reply #11 on: December 08, 2017, 07:30:06 pm »
thank you!
created new entry in http://wiki.lazarus.freepascal.org/TListBox#See_also

I think it is a good idea to add screenshot and link to this thread on page.

 

Recent

Get Lazarus at SourceForge.net. Fast, secure and Free Open Source software downloads Open Hub project report for Lazarus