Lazarus

Programming => General => Topic started by: Jonny on February 06, 2025, 02:55:55 pm

Title: Unselect all rows in stringgrid
Post by: Jonny on February 06, 2025, 02:55:55 pm
This is probably really easy but I have not been able to find a solution.

How to deselect all rows in a string grid?

By clicking unused space under last row, is it possible to remove selection?

Sample minimal test project attached.

Title: Re: Unselect all rows in stringgrid
Post by: dsiders on February 06, 2025, 07:30:30 pm
How to deselect all rows in a string grid?

TStringGrid.ClearSelections
https://lazarus-ccr.sourceforge.io/docs/lcl/grids/tcustomgrid.clearselections.html

By clicking unused space under last row, is it possible to remove selection?

That is the default behavior. If you're talking about the focus rectangle, that's a matter of removing goDrawFocusSelected from Options if its enabled.

Title: Re: Unselect all rows in stringgrid
Post by: Jonny on February 06, 2025, 07:44:56 pm
Quote from: dsiders
TStringGrid.ClearSelections

Thank you. I am currently calling that from a button. But I want to replicate it by clicking the dead space below the grid. The trouble is detecting when that area is clicked.

Quote from: dsiders
That is the default behavior. If you're talking about the focus rectangle, that's a matter of removing goDrawFocusSelected from Options if its enabled.

The issue is not the focus rectangle. I want other stuff to happen when the dead space is clicked and all the rows become inactive. I have tried StringGrid.OnMouseDown but unable to check if that is in the correct area.
Title: Re: Unselect all rows in stringgrid
Post by: Jonny on February 06, 2025, 08:02:19 pm
I have found out how to detect a click in the dead space - use a mousedown event and check whether it is below the bottom row.

But calling ClearSelections from that procedure does not deselect any currently selected cells.

Is there another step involved? I have updated the project attachment in the first post with the new detection routine.
Title: Re: Unselect all rows in stringgrid
Post by: wp on February 06, 2025, 08:07:39 pm
In the MouseDown handler call Grid.MouseToGridZone(X, Y). When that returns "gzInvalid" (and you had set the OutBoundEvents property to false) you have clicked on the "dead space".

Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormCreate(Sender: TObject);
  2. begin
  3.   StringGrid1.AllowOutBoundEvents := false;
  4. end;
  5.  
  6. procedure TForm1.StringGrid1MouseDown(Sender: TObject; Button: TMouseButton;
  7.   Shift: TShiftState; X, Y: Integer);
  8. var
  9.   s: String;
  10.   gz: TGridZone;
  11. begin
  12.   gz := StringGrid1.MouseToGridZone(X, Y);
  13.   case gz of
  14.     gzNormal: Caption := 'gzNormal';
  15.     gzFixedCols: caption := 'gzFixedCols';
  16.     gzFixedRows: Caption := 'gzFixedRows';
  17.     gzFixedCells: Caption := 'gzFixedCells';
  18.     gzInvalid: Caption := 'gzInvalid';
  19.   end;
  20. end;
Title: Re: Unselect all rows in stringgrid
Post by: Jonny on February 06, 2025, 08:35:54 pm
Fantastic, thanks @wp, MouseToGridZone is much better than my solution.

Can you tell me why ClearSelections does not work from that procedure?
Title: Re: Unselect all rows in stringgrid
Post by: CM630 on February 06, 2025, 08:54:17 pm
I noticed something odd - when you start the app, cell [1;1] is already selected  :o
Maybe you should remove the focus from the stringgrid to get rid of the selection.

Unless someone offers something better:
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, Forms, Controls, Grids, StdCtrls, Types;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Memo1: TMemo;
  16.     StringGrid1: TStringGrid;
  17.     procedure FormCreate(Sender: TObject);
  18.     procedure Memo1Change(Sender: TObject);
  19.     procedure StringGrid1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  20.     procedure StringGrid1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  21.   private
  22.   public
  23.   end;
  24.  
  25. var
  26.   Form1: TForm1;
  27.   Inside: boolean;
  28.  
  29. implementation
  30.  
  31. {$R *.lfm}
  32.  
  33. procedure TForm1.FormCreate(Sender: TObject);
  34. begin
  35.   StringGrid1.AllowOutboundEvents := False;
  36. end;
  37.  
  38. procedure TForm1.StringGrid1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  39. begin
  40.   Memo1.Append('MOUSEDOWN');
  41.   if Y < StringGrid1.CellRect(0, StringGrid1.RowCount-1).Bottom then
  42.   begin
  43.     Inside := true;
  44.     Memo1.Append('ROW CLICKED');
  45.     StringGrid1.Options :=  StringGrid1.Options + [goAlwaysShowEditor,goAutoAddRows, goRowHighlight,goEditing,goColSpanning,goRelaxedRowSelect,goTabs];
  46.     StringGrid1.Options :=  StringGrid1.Options - [goRangeSelect];
  47.     StringGrid1.ExtendedSelect := False;
  48.     StringGrid1.Refresh;
  49.   end
  50.   else
  51.   begin
  52.     Memo1.Append('DEAD SPACE');
  53.     Memo1.Append('CLEAR SELECTIONS');
  54.     StringGrid1.ClearSelections;
  55.     Inside := False;
  56.   end;
  57. end;
  58.  
  59. procedure TForm1.StringGrid1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  60. begin
  61.     if (Inside = false) then
  62.     begin
  63.       StringGrid1.Options :=  StringGrid1.Options - [goAlwaysShowEditor,goAutoAddRows, goRowHighlight,goEditing,goColSpanning,goRelaxedRowSelect,goTabs];
  64.       StringGrid1.Options :=  StringGrid1.Options + [goRangeSelect];
  65.       StringGrid1.ExtendedSelect := True;
  66.       StringGrid1.Refresh;
  67.       Memo1.SetFocus;
  68.     end;
  69. end;
  70.  
  71. procedure TForm1.Memo1Change(Sender: TObject);
  72. begin
  73.   Memo1.CaretPos := Point(0, Memo1.Lines.Count-1)
  74. end;
  75.  
  76. end.
Title: Re: Unselect all rows in stringgrid
Post by: wp on February 06, 2025, 10:36:04 pm
Can you tell me why ClearSelections does not work from that procedure?
ClearSelections refers to multiple selections which are counted to be in addition to the normally selected cell, and it just clears these additional blocks without affecting the selected cell.

In order to remove the selected cell you could set its col/row indices to -1 which is outside the grid.
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. begin
  3.   StringGrid1.Row := -1;
  4.   StringGrid1.Col := -1;
  5. end;
Title: Re: Unselect all rows in stringgrid
Post by: Jonny on February 07, 2025, 12:58:37 am
Quote from: wp
In order to remove the selected cell you could set its col/row indices to -1 which is outside the grid.

I had considered that but get a "Grid index out of range" error.
Title: Re: Unselect all rows in stringgrid
Post by: Jonny on February 07, 2025, 01:07:15 am
Quote from: CM630
Unless someone offers something better:

Thanks @CM630, your code works visually, but if I add a line to display the current row, it still shows the last row selected.

Code: Pascal  [Select][+][-]
  1.       ..
  2.       Memo1.SetFocus;
  3.       Memo1.Append('ROW '+IntToStr(StringGrid1.Row)); // Add this
  4.     end;
  5. end;
  6.  
Title: Re: Unselect all rows in stringgrid
Post by: CM630 on February 07, 2025, 08:12:06 am
What if you make a procedure:
Code: Pascal  [Select][+][-]
  1. procedure SetFocusSG(State: boolean);
  2.   if  (State = true) then
  3.     begin
  4.       StringGrid1.Options :=  StringGrid1.Options + [goAlwaysShowEditor,goAutoAddRows, goRowHighlight,goEditing,goColSpanning,goRelaxedRowSelect,goTabs];
  5.       StringGrid1.Options :=  StringGrid1.Options - [goRangeSelect];
  6.       StringGrid1.ExtendedSelect := False;
  7.       StringGrid1.Refresh;
  8.       StringGrid1.SetFocus; //maybe focus is already set and this line is not necessary
  9.    end
  10.   else
  11.   begin
  12.       StringGrid1.Options :=  StringGrid1.Options - [goAlwaysShowEditor,goAutoAddRows, goRowHighlight,goEditing,goColSpanning,goRelaxedRowSelect,goTabs];
  13.       StringGrid1.Options :=  StringGrid1.Options + [goRangeSelect];
  14.       StringGrid1.ExtendedSelect := True;
  15.       StringGrid1.Refresh;
  16.       Memo1.SetFocus;
  17.   end;
  18. end;
And add SetFocusSG(False); after Memo1.Append('ROW '+IntT... ? (also in StringGrid1MouseDown)
Title: Re: Unselect all rows in stringgrid
Post by: Jonny on February 07, 2025, 10:28:34 am
Quote from: CM630
What if you make a procedure:

Thanks again, the new procedure does work the same as the old one. But StringGrid1.Row still reports that a row is selected (I have a thread that monitors it and reacts to changes). Setting grid values to -1 as suggested by @wp would be ideal. Or storing the value elsewhere, although I would rather monitor the grid rather than a duplicate copy.
Title: Re: Unselect all rows in stringgrid
Post by: wp on February 07, 2025, 12:02:28 pm
I am afraid that the grid internally relies heavily on the fact that there is a selected cell. Setting the grid's Row and Col to -1 worked only in a button click and only if grid.AllowOutboundEvents is true.
Title: Re: Unselect all rows in stringgrid
Post by: Jonny on February 07, 2025, 01:10:54 pm
Quote from: wp
I am afraid that the grid internally relies heavily on the fact that there is a selected cell. Setting the grid's Row and Col to -1 worked only in a button click and only if grid.AllowOutboundEvents is true.

Thanks for the clarification, that helps.
Title: Re: Unselect all rows in stringgrid
Post by: Jonny on February 08, 2025, 02:16:49 am
Quote from: wp
I am afraid that the grid internally relies heavily on the fact that there is a selected cell. Setting the grid's Row and Col to -1 worked only in a button click and only if grid.AllowOutboundEvents is true.

If AllowOutboundEvents is true then Grid.MouseToGridZone(X, Y) never returns gzInvalid - am I still missing something?
Title: Re: Unselect all rows in stringgrid
Post by: CM630 on February 21, 2025, 07:47:59 am
Did you solve your issue?
I remember that for some reasons I was not very happy with TStringGrid.
Maybe you can take a look at this thread: https://forum.lazarus.freepascal.org/index.php?topic=66162.0
Title: Re: Unselect all rows in stringgrid
Post by: Jonny on February 24, 2025, 08:30:25 pm
Sorry @CM630, I did not see that you had replied here.

Indeed, TStringGrid is very fiddly, but I am working around the issues, I am too invested in it now to swap to something else and learning lots with my patches!

If you are interested, currently stuck on this: select a row without any cell in that row becoming active for edits.

I want the user to select a row, then press various keys on the keyboard to do stuff relating to the row. But the key presses tinstantly activate the editor and the characters appear there.

What I need is: user clicks a row to select it, then optionally press a selection of keys, then click again on a cell in the row to start edit mode.

Tricky. Any ideas?
Title: Re: Unselect all rows in stringgrid
Post by: Jonny on February 24, 2025, 11:34:05 pm
Aha! In case anyone is interested (or want to criticise and offer a more elegant solution):

Code: Pascal  [Select][+][-]
  1. var
  2.   CurrentRow: Integer;
  3.  
  4. procedure TForm1.StringGrid1SelectCell(Sender: TObject; aCol, aRow: Integer; var CanSelect: Boolean);
  5. begin
  6.   if CurrentRow <> aRow then
  7.     StringGrid1.Options := StringGrid1.Options - [goEditing]
  8.   else
  9.     StringGrid1.Options := StringGrid1.Options + [goEditing];
  10.   CurrentRow := aRow;
  11. end;
  12.  
Title: Re: Unselect all rows in stringgrid
Post by: wp on February 25, 2025, 12:59:53 am
If you are interested, currently stuck on this: select a row without any cell in that row becoming active for edits.
The official way to prevent editing of specific cells is to handle the OnSelectEditor event and set the Editor argument to nil. The following code forbids editing of all cells in row 2:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.StringGrid1SelectEditor(Sender: TObject; aCol, aRow: Integer;
  2.   var Editor: TWinControl);
  3. begin
  4.   if aRow = 2 then Editor := nil;
  5. end;
Activate goRowSelect to mark the entire row as selected. You can still catch key presses in the OnKey* events of the grid event if a cell is disabled for editing as shown above.
Title: Re: Unselect all rows in stringgrid
Post by: Jonny on February 25, 2025, 12:49:30 pm
Quote from: wp
handle the OnSelectEditor event and set the Editor argument to nil

Thanks @wp, unfortunately that is not working for me. I check if the clicked row is a new row then set it to nil if it is, but typing into the keyboard always edits the cell.

Code: Pascal  [Select][+][-]
  1. var
  2.   CurrentRow: Integer;
  3. procedure TForm1.StringGrid1SelectEditor(Sender: TObject; aCol, aRow: Integer; var Editor: TWinControl);
  4. var
  5.   IsNewRow: Boolean;
  6. begin
  7.   IsNewRow := aRow <> CurrentRow;
  8.   if IsNewRow then Editor := nil;
  9.   CurrentRow := aRow;
  10. end;
  11.  

Can you please look at the attached project which also includes logging to confirm the logic?
Title: Re: Unselect all rows in stringgrid
Post by: wp on February 25, 2025, 06:08:48 pm
Your code is better suited to your requirement.
Title: Re: Unselect all rows in stringgrid
Post by: Jonny on February 26, 2025, 12:57:01 am
Thanks @wp, it is always reassuring to get your confirmation.

Last issue with the grid and I think that I am done:

When the user edits a cell and then presses the enter key then TStringGrid.OnValidateEntry happens correctly.

But if user edits cell then clicks another cell, events fire as OnMouseDown then SelectCell then OnValidateEntry.

Is it possible to change the order and trigger the OnValidateEntry first, before the other events?

The reason is that when another row is selected, I remove goEditing with causes OnValidateEntry to not trigger:

Code: Pascal  [Select][+][-]
  1. procedure sgdGridsSelectCell(Dummy: Pointer; Sender: TObject; aCol, aRow: Integer; var CanSelect: Boolean);
  2. begin
  3.   if CurrentGridRow <> aRow then
  4.     StringGrid1.Options := StringGrid1.Options - [goEditing,goRelaxedRowSelect,goAlwaysShowEditor] + [goRowSelect,goRowHighlight]
  5.     // the previous cell is now not validated
  6.  
Title: Re: Unselect all rows in stringgrid
Post by: Jonny on February 26, 2025, 06:59:31 pm
The ValidateEntry function cannot be triggered by my application because it is in the protected area of TCustomGrid=class(TCustomControl) therefore inaccessible.

Looking through lazarus/lcl/grids.pas shows no indication of order of event to my untrained eyes.

Is there a way to override the class somehow?
TinyPortal © 2005-2018