How can I detect when a user is finished editing a TStringGrid cell?
I would like to return the contents of a cell in a string grid w开发者_如何学Gohen 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?
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.
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;
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.
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.
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.
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;
Basically, there are many ways a user can end editing, and not all these are always a good interception point:
- it moves the focus to another cell in the grid
- it moves the focus to another control on the form
- it moves the focus to another form
- 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
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;
}
精彩评论