当用户完成输入数据时,我想在字符串网格中返回单元格的内容。当按下键盘上的回车键,或者单击或双击另一个单元格时,用户就完成了。
在 Lazarus 中有 FinishedCellEditing 的方法,但在 Delphi 中没有。如何在 Delphi 中检测到它?
当用户完成输入数据时,我想在字符串网格中返回单元格的内容。当按下键盘上的回车键,或者单击或双击另一个单元格时,用户就完成了。
在 Lazarus 中有 FinishedCellEditing 的方法,但在 Delphi 中没有。如何在 Delphi 中检测到它?
我有完全相同的问题,但更简单的解决方案,因为我强制用户按 Enter 键...
诀窍:我不让用户在编辑一个单元格时更改到另一个单元格,所以我强制用户必须按 Intro/Enter 结束编辑,然后我允许更改到另一个单元格。
不好的部分是 OnKeyPress 发生在 OnSetEditText 之前,所以我尝试使用 OnKeyUp ...
我发现,就在编辑单元格时,按下 Enter/Intro 后,OnKeyUp 没有被触发……这是 VCL 上的一个 BUG……一个键已被释放,而 OnKeyUp 没有被触发。
所以,我做了另一个技巧来绕过它......使用计时器来改变我会做的事情,所以我让时间来触发 OnSetEditText 之前的事件。
让我解释一下我为成功所做的一切......
我通过在 OnSelectCell 上放置代码来锁定选择另一个单元格,与此非常相似:
CanSelect:=Not UserIsEditingOneCell;
在 OnSetEditText 我放了这样的代码:
UserIsEditingOneCell:=True;
所以现在,需要检测用户何时按下 Enter/Intro ......我发现了一件可怕的事情,正如我所说...... OnKeyUp 不会为这样的键触发......所以,我将通过使用计时器并使用 OnKeyPress,因为 OnKeyPress 被触发,但 OnKeyUp 没有,对于 Enter 键...
所以,在 OnKeyPress 我放了类似的东西:
TheTimerThatIndicatesUserHasPressEnter.Interval:=1; // As soon as posible
TheTimerThatIndicatesUserHasPressEnter.Enabled:=True; // But after event OnSetEditText is fired, so not jsut now, let some time pass
此类计时器事件:
UserIsEditingOneCell:=False;
// Do whatever needed just after the user has finished editing a cell
那行得通,但我知道这很可怕,因为我需要使用计时器......但我不知道更好的方法......而且因为我不需要让用户进入另一个单元格,而正在编辑的单元格没有有一个有效的价值......我可以使用它。
为什么没有像 OnEndingEditing 这样的事件?
PD:我还注意到 OnSetEditText 会为每个按下的键触发多次,并且在 Value 参数上具有不同的值……至少在使用 OnGetEditMask 事件上设置的 EditMask 值“00:00:00”时。
使用 VCL 的 TStringGrid 您需要 OnSetEditText 事件。但是请注意,每次用户更改任何单元格中的内容时,它都会触发。因此,如果您只想在用户完成编辑后执行某项操作,则必须查看事件参数的 row 和 col 值。当然,当用户结束编辑一个单元格并且没有编辑另一个单元格时,您需要注意这种情况,例如通过单击 TStringGrid 外部。就像是:
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;
我通过响应发送到就地编辑器的 WM_KILLFOCUS 消息来做到这一点。我必须对就地编辑器进行子类化才能做到这一点。
我从 Raymond Chen 的博客中了解到,如果您随后执行更改焦点的验证,这是不合适的。
这是最终版本...哇,我改进了自己的代码(我之前发布的另一篇文章是我使用多年的代码,直到今天...我看到了这篇文章,我把我拥有的代码...然后我试图修复我自己的代码,我得到了它,哇!,我已经尝试了很多年,现在我终于得到了它)。
这很棘手,因为我怎么能想象一个单元格可以在编辑器处于活动状态时被选中?
让我们看看如何做到这一点:
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;
这对我来说就像一个魅力。
请注意,它需要两个变量来保存最后编辑的单元格坐标。
请记住goEditing
必须True
在Options
.
请对另一篇文章感到抱歉......其他代码是我多年来一直使用的代码,因为我没有得到任何更好的解决方案......直到现在。
我希望这对其他人有帮助。
最好只使用虚拟字符串网格,因为 Delphi 中的字符串网格控件似乎并不能很好地支持这一点。
解决方案:
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;
基本上,用户可以通过多种方式结束编辑,但并非所有这些总是一个好的拦截点:
您需要问自己在什么情况下要更新内容。
例如:当用户取消模式表单或结束应用程序时,您想更新它吗?
——杰伦