Recent

Author Topic: Programmatically focus and edit a cell in StringGrid  (Read 18192 times)

FangQ

  • Full Member
  • ***
  • Posts: 134
Programmatically focus and edit a cell in StringGrid
« on: June 22, 2017, 06:43:04 pm »
hi, I have a StringGrid, and want the user to type only numbers (integers, floating point), so I defined a test in the OnEditingDone event and test if the input is numerical value, if not, I want to give user a warning, and then set focus & enabling editing state of the offending cell. Here is my EditingDone function:

Code: Pascal  [Select][+][-]
  1. procedure TfmMCX.sgMediaEditingDone(Sender: TObject);
  2. var
  3.    grid: TStringGrid;
  4.    val: Extended;
  5.    ss: string;
  6.    rowid, colid: integer;
  7. begin
  8.   grid:= Sender as TStringGrid;
  9.   if(grid = nil) then exit;
  10.   try
  11.     try
  12.         ss:=grid.Cells[grid.Col,grid.Row];
  13.         rowid:=grid.Row;
  14.         colid:=grid.Col;
  15.         if(Length(ss)>0) then
  16.             val := StrToFloat(ss);
  17.     except
  18.         raise Exception.Create('Input is not a number!');
  19.     end;
  20.   except
  21.     On E : Exception do
  22.     begin
  23.       ShowMessage(E.Message);
  24.       grid.Row:=rowid;
  25.       grid.Col:=colid;
  26.       grid.EditorMode:=true;
  27.     end;
  28.   end;
  29. end;

however, I can only see a warning, but I can not either move the focused cell to the offending one, or enable the editor mode. My StringGrid already has the "goEditing" option enabled.

I am appreciated if you can suggest how to fix this. thanks!

Handoko

  • Hero Member
  • *****
  • Posts: 5122
  • My goal: build my own game engine using Lazarus
Re: Programmatically focus and edit a cell in StringGrid
« Reply #1 on: June 22, 2017, 07:31:59 pm »
Use a TTimer. You can download my test.zip to try.

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   SysUtils, Forms, Dialogs, Grids, ExtCtrls, LCLType;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     StringGrid1: TStringGrid;
  16.     Timer1: TTimer;
  17.     procedure StringGrid1EditingDone(Sender: TObject);
  18.     procedure Timer1Timer(Sender: TObject);
  19.   public
  20.     procedure GotoColRow(Col, Row: Integer);
  21.   end;
  22.  
  23. var
  24.   Form1: TForm1;
  25.   GotoCol, GotoRow: Integer;
  26.  
  27. implementation
  28.  
  29. {$R *.lfm}
  30.  
  31. { TForm1 }
  32.  
  33. procedure TForm1.StringGrid1EditingDone(Sender: TObject);
  34. var
  35.   Z: Integer;
  36. begin
  37.  
  38.   if not(Sender is TStringGrid) then Exit;
  39.  
  40.   with StringGrid1 do
  41.     if not(TryStrToInt(Cells[Col, Row], Z)) then
  42.       begin
  43.         ShowMessage('Input is not a number!');
  44.         GotoColRow(Col, Row);
  45.       end;
  46. end;
  47.  
  48. procedure TForm1.Timer1Timer(Sender: TObject);
  49. begin
  50.   StringGrid1.Col := GotoCol;
  51.   StringGrid1.Row := GotoRow;
  52.   Timer1.Enabled := False;
  53. end;
  54.  
  55. procedure TForm1.GotoColRow(Col, Row: Integer);
  56. begin
  57.   GotoCol := Col;
  58.   GotoRow := Row;
  59.   Timer1.Enabled := True;
  60. end;
  61.  
  62. end.

TStringGris is very customizable, if you want to learn more:
http://forum.lazarus.freepascal.org/index.php/topic,37181.0.html

FangQ

  • Full Member
  • ***
  • Posts: 134
Re: Programmatically focus and edit a cell in StringGrid
« Reply #2 on: June 26, 2017, 07:48:10 pm »
thanks Handoko for your prompt reply. I am wondering though why my code (setting grid.Row and grid.Col in the except...end block) does not work as desired? when I type a non-digit input and press enter, I see the exception I generated, but the focus of the cell shifts to the right no matter what I do.

also, can you set the selected cell into edit mode (as if you double clicked on the cell)? thanks

Handoko

  • Hero Member
  • *****
  • Posts: 5122
  • My goal: build my own game engine using Lazarus
Re: Programmatically focus and edit a cell in StringGrid
« Reply #3 on: June 26, 2017, 08:33:51 pm »
Done. You can download the test2.zip.

Information about exception:
https://freepascal.org/docs-html/ref/refch17.html

As the page said, exception is used for program to handle error and error recovery, it is not suitable for used in this user input case. I personally will avoid using exception but write my own validation rules and show more friendly messages to users.

This forum has many more experienced seniors, I will let them to answer your question, why exception doesn't work on your code.

The sample I provide should work as what you want. But I noticed something not friendly to users. If user accidentally edits a cell, he/she cannot cancel (or press Esc) until he/she provides a numeric value. It is very annoying, test my code then you will understand what I mean.

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   SysUtils, Forms, Dialogs, Grids, ExtCtrls, LCLType;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     StringGrid1: TStringGrid;
  16.     Timer1: TTimer;
  17.     procedure StringGrid1EditingDone(Sender: TObject);
  18.     procedure Timer1Timer(Sender: TObject);
  19.   public
  20.     procedure GotoColRow(Col, Row: Integer);
  21.   end;
  22.  
  23. var
  24.   Form1: TForm1;
  25.   GotoCol, GotoRow: Integer;
  26.  
  27. implementation
  28.  
  29. {$R *.lfm}
  30.  
  31. { TForm1 }
  32.  
  33. procedure TForm1.StringGrid1EditingDone(Sender: TObject);
  34. var
  35.   Z: Integer;
  36. begin
  37.  
  38.   if not(Sender is TStringGrid) then Exit;
  39.  
  40.   with StringGrid1 do
  41.     if not(TryStrToInt(Cells[Col, Row], Z)) then
  42.       begin
  43.         ShowMessage('Input is not a number!');
  44.         GotoColRow(Col, Row);
  45.       end;
  46. end;
  47.  
  48. procedure TForm1.Timer1Timer(Sender: TObject);
  49. begin
  50.   Timer1.Enabled := False;
  51.   StringGrid1.Col := GotoCol;
  52.   StringGrid1.Row := GotoRow;
  53.   StringGrid1.EditorMode := True;
  54. end;
  55.  
  56. procedure TForm1.GotoColRow(Col, Row: Integer);
  57. begin
  58.   GotoCol := Col;
  59.   GotoRow := Row;
  60.   Timer1.Enabled := True;
  61. end;
  62.  
  63. end.

FangQ

  • Full Member
  • ***
  • Posts: 134
Re: Programmatically focus and edit a cell in StringGrid
« Reply #4 on: June 26, 2017, 11:01:06 pm »
Done. You can download the test2.zip.

really appreciated! tried it and worked like a charm.

I suppose my old approach of modifying the selection/editormode failed to work because it was inside the OnEditDone event, which must have followed by another function that sets the focus. Anyways. It works now.

On a side note, I want to apply this test for more than 1 StringGrids. I currently added another global variable to save the Goto-grid, but keep wondering if there is a way I can avoid using global variables. Tried setting Timer1's Owner to the stringgrid, but it turns out to be read-only. any method that does not require global variables?

Handoko

  • Hero Member
  • *****
  • Posts: 5122
  • My goal: build my own game engine using Lazarus
Re: Programmatically focus and edit a cell in StringGrid
« Reply #5 on: June 27, 2017, 08:41:33 am »
You may try to write a new TTimer. You can download the test3.zip. Here are the improvements:

- Support multiple StringGrids
- Instead using global variables, it uses TGotoTimer
- TGotoTimer will be freed automatically
- The validation rules now accept empty input

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   SysUtils, Forms, Dialogs, Grids, ExtCtrls, LCLType, StdCtrls, Classes;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Label1: TLabel;
  16.     Label2: TLabel;
  17.     StringGrid1: TStringGrid;
  18.     StringGrid2: TStringGrid;
  19.     procedure StringGrid1EditingDone(Sender: TObject);
  20.     procedure StringGrid2EditingDone(Sender: TObject);
  21.   end;
  22.  
  23.   { TGotoTimer }
  24.  
  25.   TGotoTimer = class(TTimer)
  26.   private
  27.     TargetGrid: TStringGrid;
  28.     TargetCol: Integer;
  29.     TargetRow: Integer;
  30.     procedure GotoColRow(Sender: TObject);
  31.   public
  32.     constructor Create(Grid: TStringGrid; Col, Row: Integer); overload;
  33.   end;
  34.  
  35. var
  36.   Form1: TForm1;
  37.  
  38. implementation
  39.  
  40. {$R *.lfm}
  41.  
  42. { TGotoTimer }
  43.  
  44. procedure TGotoTimer.GotoColRow(Sender: TObject);
  45. begin
  46.   Enabled := False;
  47.   TargetGrid.SetFocus;
  48.   TargetGrid.Col := TargetCol;
  49.   TargetGrid.Row := TargetRow;
  50.   TargetGrid.EditorMode := True;
  51.   Form1.RemoveComponent(Self);
  52. end;
  53.  
  54. constructor TGotoTimer.Create(Grid: TStringGrid; Col, Row: Integer);
  55. begin
  56.   inherited Create(Form1);
  57.   TargetGrid := Grid;
  58.   TargetCol := Col;
  59.   TargetRow := Row;
  60.   OnTimer := @GotoColRow;
  61.   Interval := 20;
  62. end;
  63.  
  64. { TForm1 }
  65.  
  66. procedure TForm1.StringGrid1EditingDone(Sender: TObject);
  67. var
  68.   S: string;
  69.   Z: Integer;
  70. begin
  71.   with StringGrid1 do
  72.   begin
  73.     S := Cells[Col, Row];
  74.     if not(TryStrToInt(S, Z)) and (S <> '') then
  75.     begin
  76.       ShowMessage('Input is not a number!');
  77.       TGotoTimer.Create(StringGrid1, Col, Row);
  78.     end;
  79.   end;
  80. end;
  81.  
  82. procedure TForm1.StringGrid2EditingDone(Sender: TObject);
  83. var
  84.   S: string;
  85. begin
  86.   with StringGrid2 do
  87.   begin
  88.     S := Cells[Col, Row];
  89.     if (Length(S) <> 4) and (S <> '') then
  90.     begin
  91.       ShowMessage('Input must be 4 characters!');
  92.       TGotoTimer.Create(StringGrid2, Col, Row);
  93.     end;
  94.   end;
  95. end;
  96.  
  97. end.

FangQ

  • Full Member
  • ***
  • Posts: 134
Re: Programmatically focus and edit a cell in StringGrid
« Reply #6 on: June 27, 2017, 03:01:44 pm »
wonderful! that's an elegant solution, thank you very much!

 

TinyPortal © 2005-2018