How can I detect when a user is finished editing a TStringGrid cell?
Asked Answered
B

8

13

I would like to return the contents of a cell in a string grid when the user finishes entering the data. The user is finished when pressing the enter key on the keyboard, or single- or double-clicking another cell.

In Lazarus there is a method of FinishedCellEditing, but not in Delphi. How can I detect it in Delphi?

Butter answered 20/2, 2011 at 7:26 Comment(2)
I have learned that the best way to accommodate for this scenario is to implement a data grid (with a client dataset). It's actually a bit more tedious to implement, but once done, it's actually much easier to work with, especially in the sense of users editing data from the UI. Yet still, I look for way to implement a string grid.Kelsey
Too bad Delphi is not Lazarus.Songful
H
5

I have quite the same problem, but easier solution since i force user to press Enter key...

The trick: I do not let the user change to another cell while is editing one, so i force user to must press Intro/Enter to end editing, then i allow to change to other cell.

The bad part is that OnKeyPress happens before OnSetEditText, so i tried with OnKeyUp...

And what i found is that just when editing a cell, after pressing Enter/Intro, OnKeyUp is not fired... that is a BUG on VCL... a key has being released and OnKeyUp has not being fired.

So, i make another trick to bypass that... use a Timer to differ what i would do just a little, so i let time to event OnSetEditText be fired before.

Let me explain what i have done to success...

I have locked selecting another cell by putting code on OnSelectCell, quite similar to this:

CanSelect:=Not UserIsEditingOneCell;

And on OnSetEditText i put code like this:

UserIsEditingOneCell:=True;

So now, what is needed is to detect when the user press Enter/Intro... and i found a horrible thing as i said... OnKeyUp is not fired for such key... so, i will simulate that by using a Timer and using OnKeyPress, because OnKeyPress is fired, but OnKeyUp not, for Enter key...

So, on OnKeyPress i put something like:

TheTimerThatIndicatesUserHasPressEnter.Interval:=1; // As soon as posible
TheTimerThatIndicatesUserHasPressEnter.Enabled:=True; // But after event OnSetEditText is fired, so not jsut now, let some time pass

An on such timer event:

UserIsEditingOneCell:=False;
// Do whatever needed just after the user has finished editing a cell

That works, but i know that is horrible because i need to use a Timer... but i do not know a better way... and since i need to not let user go to another cell while the one that is edinting does not have a valid value... i can use that.

Why on the hell there is not an event like OnEndingEditing?

P.D.: I have also noticed that OnSetEditText is fired multiple times for each key being pressed, and with different value on Value parameter... at least when working with EditMask value '00:00:00' set on OnGetEditMask event.

Hipparch answered 23/10, 2012 at 9:12 Comment(4)
I'm using Delphi XE2 and still have this bug :(Kelsey
This is not even by far the appropriate solution.Songful
Please do not post multiple answers. Please merge your answers into single one!!!!!Songful
@Frosty These are different users. Besides, regarding multiple answers, see: 1, 2, 3, 4.Jargon
B
4

With the VCL's TStringGrid you need the OnSetEditText event. Please note however that it fires everytime the user changes something in any cell. So, if you only want to do something after the user is finished editing, you will have to watch the row and col values of the event's parameters. And of course, you need to take care of the situation when a user ends editing a cell and does not edit another cell, for example by clicking outside the TStringGrid. Something like:

TForm1 = class(TForm)
...
private
  FEditingCol, FEditingRow: Longint;
...
end;

procedure Form1.DoYourAfterEditingStuff(ACol, ARow: Longint);
begin
...
end;

procedure Form1.StringGrid1OnEnter(...)
begin
  EditingCol := -1;
  EditingRow := -1;
end;

procedure Form1.StringGrid1OnSetEditText(Sender: TObject; ACol, ARow: Longint; const Value: string)
begin
  if (ACol <> EditingCol) and (ARow <> EditingRow) then
  begin
    DoYourAfterEditingStuff(EditingCol, EditingRow);
    EditingCol := ACol;
    EditingRow := ARow;
  end;
end;

procedure Form1.StringGrid1OnExit(...)
begin
  if (EditingCol <> -1) and (EditingRow <> -1) then
  begin
    DoYourAfterEditingStuff(EditingCol, EditingRow);
    // Not really necessary because of the OnEnter handler, but keeps the code
    // nicely symmetric with the OnSetEditText handler (so you can easily 
    // refactor it out if the desire strikes you)
    EditingCol := -1;  
    EditingRow := -1;
  end;
end;
Buller answered 20/2, 2011 at 8:6 Comment(1)
The private instance variables declared in the class are prefixed with an F (as per Delphi coding conventions). However you have missed off the F prefix when you are using these variables in all three of the event handlers used.Lukasz
W
3

I do this by responding to WM_KILLFOCUS messages sent to the inplace editor. I have to subclass the inplace editor to make this happen.

I understand from Raymond Chen's blog that this is not appropriate if you then perform validation that changes the focus.

Workbook answered 20/2, 2011 at 8:57 Comment(1)
An other message is WM_ACTIVATE check for value WA_INACTIVEKristenkristi
C
3

This is the final version... Wow, I improved my own code (the other post I put before was the code I was using for years until today... I saw this post and I put the code I had... then I tried to fix my own code and I got it, wow!, I was trying that for years, now I finally got it).

It is quite tricky since, how on the hell I could imagine a cell could be selected with editor active?

Let's see how to do it:

var
  MyStringGrig_LastEdited_ACol, MyStringGrig_LastEdited_ARow: Integer;
  //To remember the last cell edited

procedure TmyForm.MyStringGrigSelectCell(Sender: TObject; ACol, ARow: Integer;
  var CanSelect: Boolean);
begin
  //When selecting a cell
  if MyStringGrig.EditorMode then begin //It was a cell being edited
    MyStringGrig.EditorMode:= False;    //Deactivate the editor
    //Do an extra check if the LastEdited_ACol and LastEdited_ARow are not -1 already.
    //This is to be able to use also the arrow-keys up and down in the Grid.
    if (MyStringGrig_LastEdited_ACol <> -1) and (MyStringGrig_LastEdited_ARow <> -1) then
      MyStringGrigSetEditText(Sender, MyStringGrig_LastEdited_ACol, MyStringGrig_LastEdited_ARow,
        MyStringGrig.Cells[MyStringGrig_LastEdited_ACol, MyStringGrig_LastEdited_ARow]);
    //Just make the call
  end;
  //Do whatever else wanted
end;

procedure TmyForm.MyStringGrigSetEditText(Sender: TObject; ACol, ARow: Integer;
  const Value: string);
begin
  //Fired on every change
  if Not MyStringGrig.EditorMode         //goEditing must be 'True' in Options
  then begin                             //Only after user ends editing the cell
    MyStringGrig_LastEdited_ACol:= -1;   //Indicate no cell is edited
    MyStringGrig_LastEdited_ARow:= -1;   //Indicate no cell is edited
    //Do whatever wanted after user has finish editing a cell
  end else begin                         //The cell is being editted
    MyStringGrig_LastEdited_ACol:= ACol; //Remember column of cell being edited
    MyStringGrig_LastEdited_ARow:= ARow; //Remember row of cell being edited
  end;
end;

This works for me like a charm.

Please note it requires two variables to hold last edited cell coordinates.

Please remember goEditing must be True in Options.

Please sorry for the other post... that other code was the one I was using for years, since I did not get any better solution... until now.

I hope this helps others.

Cantonese answered 2/4, 2013 at 8:52 Comment(2)
Please do not post multiple answers. Please merge your answers into single one!!!!!Songful
@Frosty These are different users. Besides, regarding multiple answers, see: 1, 2, 3, 4.Jargon
D
1

Its probably best to just use virtual string grid as the string grid control in Delphi does not really seem to support this very well.

Dday answered 20/10, 2014 at 2:55 Comment(0)
S
1

SOLUTION:

  TMyGrid= class(TStringGrid)
   private
    EditorPrevState: Boolean;    //init this to false!
    EditorPrevRow  : LongInt;
    EditorPrevCol  : LongInt;
    procedure WndProc(VAR Message: TMessage); override;     
    procedure EndEdit (ACol, ARow: Longint);  // the user closed the editor      
    etc
end;


constructor TMyGrid.Create(AOwner: TComponent);
begin
 inherited Create(AOwner);     
 EditorPrevRow    := Row;   
 EditorPrevCol    := Col;
 EditorPrevState:= false; 
end;


procedure TMyGrid.WndProc(var Message: TMessage);                                                  
begin
 inherited;
 if EditorPrevState then   { The editor was open }
  begin
    if NOT EditorMode then                 { And not is closed }
     begin
      EditorPrevState:= EditorMode;
      EndEdit(EditorPrevCol, EditorPrevRow);     <------ editor is closed. process the text here
     end;
    EditorPrevRow := Row;
    EditorPrevCol := Col;
  End;

 EditorPrevState := EditorMode;
end;


procedure TMyGrid.EndEdit(aCol, aRow: Integer);         { AlwaysShowEditror must be true in Options }
begin

 Cells[ACol, ARow]:= StringReplace(Cells[ACol, ARow], CRLF, ' ', [rfReplaceAll]);                      { Replace ENTERs with space - This Grid cannot draw a text on multiple rows so enter character will he rendered as 2 squares. }

 if Assigned(FEndEdit)
 then FEndEdit(Self, EditorPrevCol, EditorPrevRow); // optional
end;
Songful answered 7/10, 2015 at 10:41 Comment(0)
N
0

Basically, there are many ways a user can end editing, and not all these are always a good interception point:

  1. it moves the focus to another cell in the grid
  2. it moves the focus to another control on the form
  3. it moves the focus to another form
  4. it moves the focus to another application.

You need to ask yourself under which circumstances you want to update the content.

For instance: do you want to update it, when the user cancels out of a modal form, or ends the application?

--jeroen

Niemi answered 20/2, 2011 at 23:8 Comment(2)
What about 5. User presses Enter, and 6. User presses Tab?Kelsey
@Jeroen Wiert Pluimers - There are too many possibilities. You need a central place where to process all these. I use WndProc. I just posted an example. It works like a charm.Songful
G
0

Answer from BCB 6:

String tmp = "";

void __fastcall TForm1::SetEditText(TObject *Sender, int ACol, int ARow, const AnsiString Value) {
  if (tmp != Value)
      tmp = Value;
  else
      ;// end editing 
}

void __fastcall TForm1::GetEditText(TObject *Sender, int ACol, int ARow, AnsiString &Value) {
  tmp = Value;
}
Glutamine answered 20/5, 2022 at 5:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.