Recent

Author Topic: Making a ListBox effectively work as 'tabs' for stringgrid  (Read 3929 times)

Lamb3234

  • New Member
  • *
  • Posts: 16
Making a ListBox effectively work as 'tabs' for stringgrid
« on: December 05, 2019, 03:39:14 pm »
Hello,

I have a form (see 'Screenshot1' in attached) and this code:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button2Click(Sender: TObject);
  2. begin
  3.   StringGrid1.InsertRowWithValues(StringGrid1.RowCount +
  4.     0, ['a', 'b', 'c']);
  5. end;
  6.  
  7. procedure TForm1.Button1Click(Sender: TObject);
  8. begin
  9.   ListBox1.Items.Add('example');
  10. end;    

I am trying to make the ListBox on the form essentially work as a set of tabs for my StringGrid. So whichever item in the ListBox is selected, when I click "Add Row", I will be adding a row for the StringGrid that is "connected" with the selected item in the ListBox - thus grouping those StringGrid rows (and values) with that ListBox item.

I will also be allowing the user to add and remove rows and items during runtime, so that complicated things.

Can anybody think of a solution for this?
Many Thanks!

Handoko

  • Hero Member
  • *****
  • Posts: 5129
  • My goal: build my own game engine using Lazarus
Re: Making a ListBox effectively work as 'tabs' for stringgrid
« Reply #1 on: December 05, 2019, 05:53:10 pm »
Hello Lamb3234,
Welcome to the forum.

Unable to fully understand your explanation but I wrote a demo maybe can be useful to you.

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Dialogs, StdCtrls, Grids;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     btnItemAdd: TButton;
  16.     btnItemDelete: TButton;
  17.     btnRowAdd: TButton;
  18.     btnRowDelete: TButton;
  19.     ListBox1: TListBox;
  20.     StringGrid1: TStringGrid;
  21.     procedure btnItemAddClick(Sender: TObject);
  22.     procedure btnItemDeleteClick(Sender: TObject);
  23.     procedure btnRowAddClick(Sender: TObject);
  24.     procedure btnRowDeleteClick(Sender: TObject);
  25.     procedure FormCreate(Sender: TObject);
  26.   end;
  27.  
  28. var
  29.   Form1: TForm1;
  30.  
  31. implementation
  32.  
  33. {$R *.lfm}
  34.  
  35. { TForm1 }
  36.  
  37. procedure TForm1.FormCreate(Sender: TObject);
  38. begin
  39.   // Prepare StringGrid1
  40.   with StringGrid1 do
  41.   begin
  42.     InsertRowWithValues(0, [' Name ', ' Count ']);
  43.     AutoSizeColumns;
  44.     ColCount  := 2;
  45.     FixedCols := 0;
  46.     FixedRows := 1;
  47.     RowCount  := 1;
  48.     Options   := Options + [goRowSelect];
  49.   end;
  50.   // Some sample items
  51.   ListBox1.Items.Add('Banana');
  52.   ListBox1.Items.Add('Watermelon');
  53.   ListBox1.Items.Add('Coconut');
  54.   ListBox1.ItemIndex := 0;
  55. end;
  56.  
  57. procedure TForm1.btnItemAddClick(Sender: TObject);
  58. var
  59.   S: string;
  60.   Z: Integer;
  61. begin
  62.   S := InputBox('New Item', 'Item Name: ', '');
  63.  
  64.   // Empty name is not allowed
  65.   S := S.Trim;
  66.   if S = '' then Exit;
  67.  
  68.   // Exact duplicate is not allowed
  69.   Z := ListBox1.Items.IndexOf(S);
  70.   if Z >= 0 then
  71.     if ListBox1.Items[Z] = S then Exit;
  72.  
  73.   ListBox1.Items.Add(S);
  74. end;
  75.  
  76. procedure TForm1.btnItemDeleteClick(Sender: TObject);
  77. begin
  78.   if (ListBox1.ItemIndex < 0) then Exit;
  79.   ListBox1.Items.Delete(ListBox1.ItemIndex);
  80. end;
  81.  
  82. procedure TForm1.btnRowAddClick(Sender: TObject);
  83. var
  84.   Index : Integer;
  85.   S     : string;
  86.   i, Z  : Integer;
  87. begin
  88.   if (ListBox1.ItemIndex < 0) then Exit;
  89.   S := ListBox1.Items[ListBox1.ItemIndex];
  90.  
  91.   // Does it already exist?
  92.   Index := -1;
  93.   for i := 1 to StringGrid1.RowCount-1 do
  94.     if StringGrid1.Cells[0, i] = S then
  95.     begin
  96.       Index := i;
  97.       Break;
  98.     end;
  99.  
  100.   // Inc(Count)
  101.   if Index > 0 then
  102.   begin
  103.     Z := StringGrid1.Cells[1, Index].ToInteger;
  104.     Inc(Z);
  105.     StringGrid1.Cells[1, Index] := Z.ToString;
  106.   end
  107.   // Add new item
  108.   else
  109.     StringGrid1.InsertRowWithValues(1, [S, '0']);
  110.  
  111.   StringGrid1.AutoSizeColumns;
  112. end;
  113.  
  114. procedure TForm1.btnRowDeleteClick(Sender: TObject);
  115. var
  116.   Z: Integer;
  117. begin
  118.   if (StringGrid1.Row <= 0) then Exit;
  119.   Z := StringGrid1.Cells[1, StringGrid1.Row].ToInteger;
  120.   Dec(Z);
  121.   StringGrid1.Cells[1, StringGrid1.Row] := Z.ToString;
  122. end;
  123.  
  124. end.

Lamb3234

  • New Member
  • *
  • Posts: 16
Re: Making a ListBox effectively work as 'tabs' for stringgrid
« Reply #2 on: December 05, 2019, 07:24:40 pm »
Thank you very much for your response. What you have provided will be extremely helpful for my project down the line, but does not resolve the issue I'm currently facing.

Please allow me to better explain what I need. Refer to the attached screenshot. Imagine that I have "added" the three rows in the StringGrid to only the selected/highlighted item in the ListBox. If I choose to add more rows to the StringGrid, the rows will only be visible when that item in the ListBox is selected. So, as you can see, the ListBox is essentially acting as tab pages for the contents of the StringGrid.

I hope this is clearer and I appreciate your time.

lucamar

  • Hero Member
  • *****
  • Posts: 4219
Re: Making a ListBox effectively work as 'tabs' for stringgrid
« Reply #3 on: December 05, 2019, 07:49:14 pm »
You would be better off by using a TPageControl with a separate TStringGrid in each page. Then you can really use the ListBox to represent the tabs in the page control.

Otherwise you could have to save/load the grid each time a "tab"  in the list box is selected, which would imply managing storage for the separate grid data. It can be done with not much difficulty but IMHO using a PageControl (or similar one) is way easier.

ETA: Note that you can hide the tabs of the page control by setting its ShowTabs property to False, if that's what you wish.
« Last Edit: December 05, 2019, 07:53:54 pm by lucamar »
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

Handoko

  • Hero Member
  • *****
  • Posts: 5129
  • My goal: build my own game engine using Lazarus
Re: Making a ListBox effectively work as 'tabs' for stringgrid
« Reply #4 on: December 06, 2019, 04:25:19 am »
@Lamb3234

You should try lucamar's suggestion first. Because manually writing the code that behaves as what you want isn't easy. But you're interested to try the hard way, I already wrote a demo for you:

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Dialogs, StdCtrls, Grids;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     btnGroupAdd: TButton;
  16.     btnRowAdd: TButton;
  17.     ListBox1: TListBox;
  18.     StringGrid1: TStringGrid;
  19.     procedure btnGroupAddClick(Sender: TObject);
  20.     procedure btnRowAddClick(Sender: TObject);
  21.     procedure btnRowDeleteClick(Sender: TObject);
  22.     procedure FormCreate(Sender: TObject);
  23.     procedure ListBox1SelectionChange(Sender: TObject; User: boolean);
  24.   private
  25.     procedure GroupAdd(const Group: string);
  26.     procedure RowAdd(GroupIndex: Integer; const AName, ADescription: string);
  27.     procedure ShowData;
  28.   end;
  29.  
  30. const
  31.   NameLength = 10;
  32.   DescLength = 30;
  33.  
  34. type
  35.   TDataItem = record
  36.     Name        : string[NameLength];
  37.     Description : string[DescLength];
  38.   end;
  39.   TDataList = array of TDataItem;
  40.  
  41. var
  42.   Form1 : TForm1;
  43.   Data  : array of TDataList;
  44.  
  45. implementation
  46.  
  47. {$R *.lfm}
  48.  
  49. { TForm1 }
  50.  
  51. procedure TForm1.FormCreate(Sender: TObject);
  52. begin
  53.  
  54.   // Prepare StringGrid1
  55.   with StringGrid1 do
  56.   begin
  57.     ColCount  := 2;
  58.     FixedCols := 0;
  59.     FixedRows := 0;
  60.     RowCount  := 0;
  61.     Options   := Options + [goRowSelect];
  62.   end;
  63.  
  64.   // Some sample data
  65.   GroupAdd('Fruits');
  66.   RowAdd(0, 'Orange', 'Good source of vit C' );
  67.   RowAdd(0, 'Banana', 'Rich in potassium');
  68.   RowAdd(0, 'Apple', 'Crunchy');
  69.   GroupAdd('Animal');
  70.   RowAdd(1, 'Cheetah', 'Fastes land animal');
  71.   RowAdd(1, 'Bird', 'Can fly');
  72.   GroupAdd('Flower');
  73.   RowAdd(2, 'Roses', 'Are red');
  74.   RowAdd(2, 'Violets', 'Are blue');
  75.   ListBox1.ItemIndex := 0;
  76.  
  77. end;
  78.  
  79. procedure TForm1.ListBox1SelectionChange(Sender: TObject; User: boolean);
  80. begin
  81.   ShowData;
  82. end;
  83.  
  84. procedure TForm1.GroupAdd(const Group: string);
  85. var
  86.   Count : Integer;
  87.   S     : string;
  88. begin
  89.   S := Group.Trim;
  90.   if S = '' then Exit;
  91.   Count := Length(Data);
  92.   SetLength(Data, Count+1);
  93.   ListBox1.Items.Add(S);
  94. end;
  95.  
  96. procedure TForm1.RowAdd(GroupIndex: Integer; const AName, ADescription: string);
  97. var
  98.   GroupedItems       : ^TDataList;
  99.   ItemName, ItemDesc : string;
  100.   Count              : Integer;
  101. begin
  102.  
  103.   // Bad input?
  104.   if (GroupIndex < 0) or (GroupIndex > Length(Data)-1) then Exit;
  105.   ItemName := LeftStr(AName.Trim, NameLength);
  106.   ItemDesc := LeftStr(ADescription.Trim, DescLength);
  107.   if (ItemName = '') or (ItemDesc = '') then Exit;
  108.  
  109.   // Store the data
  110.   GroupedItems := @(Data[GroupIndex]);
  111.   Count        := Length(GroupedItems^);
  112.   SetLength(GroupedItems^, Count+1);
  113.   GroupedItems^[Count].Name        := ItemName;
  114.   GroupedItems^[Count].Description := ItemDesc;
  115.  
  116. end;
  117.  
  118. procedure TForm1.ShowData;
  119. var
  120.   GroupedItems : ^TDataList;
  121.   Count, i     : Integer;
  122. begin
  123.   if (ListBox1.ItemIndex < 0) then Exit;
  124.   StringGrid1.Clear;
  125.   GroupedItems         := @(Data[ListBox1.ItemIndex]);
  126.   Count                := Length(GroupedItems^);
  127.   StringGrid1.RowCount := Count;
  128.   for i := 0 to Count-1 do
  129.   begin
  130.     StringGrid1.Cells[0, i] := GroupedItems^[i].Name;
  131.     StringGrid1.Cells[1, i] := GroupedItems^[i].Description;
  132.   end;
  133.   StringGrid1.AutoSizeColumns;
  134. end;
  135.  
  136. procedure TForm1.btnGroupAddClick(Sender: TObject);
  137. var
  138.   S: string;
  139.   Z: Integer;
  140. begin
  141.   S := InputBox('New Group', 'Group Name: ', '');
  142.  
  143.   // Empty name is not allowed
  144.   S := S.Trim;
  145.   if S = '' then Exit;
  146.  
  147.   // Exact duplicate is not allowed
  148.   Z := ListBox1.Items.IndexOf(S);
  149.   if Z >= 0 then
  150.     if ListBox1.Items[Z] = S then Exit;
  151.  
  152.   GroupAdd(S);
  153. end;
  154.  
  155. procedure TForm1.btnRowAddClick(Sender: TObject);
  156. var
  157.   ItemName : string;
  158.   ItemDesc : string;
  159. begin
  160.   if (ListBox1.ItemIndex < 0) then Exit;
  161.  
  162.   ItemName := InputBox('Add New Item', 'Item Name: ', '');
  163.   ItemName := LeftStr(ItemName.Trim, NameLength);
  164.   if ItemName = '' then Exit;
  165.  
  166.   ItemDesc := InputBox('Add New Item', 'Item Description: ', '');
  167.   ItemDesc := LeftStr(ItemDesc.Trim, DescLength);
  168.   if ItemDesc = '' then Exit;
  169.  
  170.   RowAdd(ListBox1.ItemIndex, ItemName, ItemDesc);
  171.   ShowData;
  172. end;
  173.  
  174. procedure TForm1.btnRowDeleteClick(Sender: TObject);
  175. var
  176.   Z: Integer;
  177. begin
  178.   ShowData; exit;
  179.   if (StringGrid1.Row <= 0) then Exit;
  180.   Z := StringGrid1.Cells[1, StringGrid1.Row].ToInteger;
  181.   Dec(Z);
  182.   StringGrid1.Cells[1, StringGrid1.Row] := Z.ToString;
  183. end;
  184.  
  185. end.

The demo has many missing features, you can't delete group nor item, no sorting ability, no grid title (fixed row), no duplicate item checking, etc. You can improve the code adding the features but you need to be proficient in using dynamic array and pointer, which are usually difficult for beginners.

Lamb3234

  • New Member
  • *
  • Posts: 16
Re: Making a ListBox effectively work as 'tabs' for stringgrid
« Reply #5 on: December 06, 2019, 11:36:43 pm »
Thank you all for your advice and for your example, Handoko. I have decided to take your advice and use TPageControl, creating TabSheets and a StringGrid in the TabSheets dynamically. Here is my code:

Code: Pascal  [Select][+][-]
  1. procedure TForm2.OKButtonClick(Sender: TObject);
  2. var
  3.   k: integer;
  4.   NewTab: TTabSheet;
  5.   NewStringGrid: TStringGrid;
  6. begin
  7.   k := Form1.PageControl1.PageCount + 1;
  8.   NewTab := TTabSheet.Create(Form1.PageControl1);
  9.   NewTab.PageControl := Form1.PageControl1;
  10.   NewTab.Name := 'tab'+inttostr(k);
  11.   NewStringGrid := TStringGrid.Create(NewTab);
  12.   NewStringGrid.Anchors := [akTop,akLeft,akRight,akBottom];
  13.   NewStringGrid.BorderStyle := bsNone;
  14.   NewStringGrid.ColCount := 4;
  15.   NewStringGrid.FixedCols := 0;
  16.   NewStringGrid. Height := 256;
  17.   // NewStringGrid.Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRangeSelect, goColSizing, goRowSelect, goThumbTracking, goSmoothScroll, goCellEllipsis, goRowHighlight];
  18.   NewStringGrid.RowCount := 1;
  19.   NewStringGrid.ScrollBars := ssAutoHorizontal;
  20.   NewStringGrid.TitleStyle := tsNative;
  21.   NewStringGrid.Name := 'stringgrid'+inttostr(k);
  22.   NewStringGrid.Left := 0;
  23.   NewStringGrid.Top := 0;
  24.   NewStringGrid.Width := 558;
  25.   NewStringGrid.Visible := True;
  26.   NewStringGrid.Parent := NewTab;
  27. end;                    

As you can see, the code to make the StringGrid with the properties that I need is quite heavy and messy. Do you have any advice on tidying up big large chunks of code like this?

Also, some of the properties that I need don't work - see NewStringGrid.Options above - I just copied it from the designer code and added a semicolon at the end, but it throws up an error - why is this?

Thanks guys.

dsiders

  • Hero Member
  • *****
  • Posts: 1052
Re: Making a ListBox effectively work as 'tabs' for stringgrid
« Reply #6 on: December 07, 2019, 12:10:49 am »
Thank you all for your advice and for your example, Handoko. I have decided to take your advice and use TPageControl, creating TabSheets and a StringGrid in the TabSheets dynamically. Here is my code:

Code: Pascal  [Select][+][-]
  1. procedure TForm2.OKButtonClick(Sender: TObject);
  2. var
  3.   k: integer;
  4.   NewTab: TTabSheet;
  5.   NewStringGrid: TStringGrid;
  6. begin
  7.   k := Form1.PageControl1.PageCount + 1;
  8.   NewTab := TTabSheet.Create(Form1.PageControl1);
  9.   NewTab.PageControl := Form1.PageControl1;
  10.   NewTab.Name := 'tab'+inttostr(k);
  11.   NewStringGrid := TStringGrid.Create(NewTab);
  12.   NewStringGrid.Anchors := [akTop,akLeft,akRight,akBottom];
  13.   NewStringGrid.BorderStyle := bsNone;
  14.   NewStringGrid.ColCount := 4;
  15.   NewStringGrid.FixedCols := 0;
  16.   NewStringGrid. Height := 256;
  17.   // NewStringGrid.Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRangeSelect, goColSizing, goRowSelect, goThumbTracking, goSmoothScroll, goCellEllipsis, goRowHighlight];
  18.   NewStringGrid.RowCount := 1;
  19.   NewStringGrid.ScrollBars := ssAutoHorizontal;
  20.   NewStringGrid.TitleStyle := tsNative;
  21.   NewStringGrid.Name := 'stringgrid'+inttostr(k);
  22.   NewStringGrid.Left := 0;
  23.   NewStringGrid.Top := 0;
  24.   NewStringGrid.Width := 558;
  25.   NewStringGrid.Visible := True;
  26.   NewStringGrid.Parent := NewTab;
  27. end;                    

As you can see, the code to make the StringGrid with the properties that I need is quite heavy and messy. Do you have any advice on tidying up big large chunks of code like this?

Also, some of the properties that I need don't work - see NewStringGrid.Options above - I just copied it from the designer code and added a semicolon at the end, but it throws up an error - why is this?

Thanks guys.

Because the assignment operator is := and not =.
Preview Lazarus 3.99 documentation at: https://dsiders.gitlab.io/lazdocsnext

Lamb3234

  • New Member
  • *
  • Posts: 16
Re: Making a ListBox effectively work as 'tabs' for stringgrid
« Reply #7 on: December 07, 2019, 12:14:14 am »
Oops, I should have seen that, thanks. How come in .frm files, the operator used is '=' and there are no semicolons used?

Lamb3234

  • New Member
  • *
  • Posts: 16
Re: Making a ListBox effectively work as 'tabs' for stringgrid
« Reply #8 on: December 07, 2019, 01:06:08 am »
Also, I see in the .frm files this code:

Code: Pascal  [Select][+][-]
  1. Columns = <          
  2.             item
  3.               Title.Caption = '1'
  4.               Width = 139
  5.             end          
  6.             item
  7.               Title.Caption = '1'
  8.               Width = 139
  9.             end          
  10.             item
  11.               Title.Caption = '1'
  12.               Width = 139
  13.             end          
  14.             item
  15.               ReadOnly = True
  16.               Title.Caption = '1'
  17.               Width = 141
  18.             end>

I can't figure out how to add this to my code when dynamically creating TStringGrids with the properties that I need. It always throws an error to do with 'ITEM'. Does anyone know know how I can name my columns during runtime with this code?

dsiders

  • Hero Member
  • *****
  • Posts: 1052
Re: Making a ListBox effectively work as 'tabs' for stringgrid
« Reply #9 on: December 07, 2019, 02:30:56 am »
Also, I see in the .frm files this code:

Code: Pascal  [Select][+][-]
  1. Columns = <          
  2.             item
  3.               Title.Caption = '1'
  4.               Width = 139
  5.             end          
  6.             item
  7.               Title.Caption = '1'
  8.               Width = 139
  9.             end          
  10.             item
  11.               Title.Caption = '1'
  12.               Width = 139
  13.             end          
  14.             item
  15.               ReadOnly = True
  16.               Title.Caption = '1'
  17.               Width = 141
  18.             end>

I can't figure out how to add this to my code when dynamically creating TStringGrids with the properties that I need. It always throws an error to do with 'ITEM'. Does anyone know know how I can name my columns during runtime with this code?

Forms (.lfm) are not Pascal code, they are a plain text object serialization format.

As far as run-time column creation goes:

Code: Pascal  [Select][+][-]
  1.   with StringGrid1.Columns.Add do
  2.   begin
  3.     Title.Caption := '1';
  4.     Width := 139;
  5.   end;
  6.  
Preview Lazarus 3.99 documentation at: https://dsiders.gitlab.io/lazdocsnext

Lamb3234

  • New Member
  • *
  • Posts: 16
Re: Making a ListBox effectively work as 'tabs' for stringgrid
« Reply #10 on: December 07, 2019, 03:29:01 am »
Thanks. How do you add multiple columns in this way?

winni

  • Hero Member
  • *****
  • Posts: 3197
Re: Making a ListBox effectively work as 'tabs' for stringgrid
« Reply #11 on: December 07, 2019, 03:44:14 am »
Hi


Code: Pascal  [Select][+][-]
  1. for i := 1 to 10 do
  2. begin
  3. with StringGrid1.Columns.Add do
  4.   begin
  5.     Title.Caption := 'I am '+ IntToStr(i);
  6.     Width := 139;
  7.   end;
  8. end;
  9.  

Winni
« Last Edit: December 07, 2019, 03:47:13 am by winni »

dsiders

  • Hero Member
  • *****
  • Posts: 1052
Re: Making a ListBox effectively work as 'tabs' for stringgrid
« Reply #12 on: December 07, 2019, 03:45:54 am »
Thanks. How do you add multiple columns in this way?

Okay... now I assume you're just trolling. Have fun.
Preview Lazarus 3.99 documentation at: https://dsiders.gitlab.io/lazdocsnext

 

TinyPortal © 2005-2018