如何检测用户何时完成 TStringGrid 单元格的编辑?

发布于 2024-10-18 01:02:37 字数 139 浏览 8 评论 0原文

当用户完成输入数据时,我想返回字符串网格中单元格的内容。当用户按下键盘上的回车键或者单击或双击另一个单元格时,就完成了。

在Lazarus中有FinishedCellEditing的方法,但在Delphi中没有。我怎样才能在Delphi中检测到它?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

甜嗑 2024-10-25 01:02:37

我有完全相同的问题,但更简单的解决方案,因为我强制用户按 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”时。

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.

请持续率性 2024-10-25 01:02:37

对于 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;

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;
等你爱我 2024-10-25 01:02:37

我通过响应发送到就地编辑器的 WM_KILLFOCUS 消息来做到这一点。我必须对就地编辑器进行子类化才能实现这一点。

我从 Raymond Chen 的博客中了解到,如果您随后执行改变焦点的验证,则这是不合适的。

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.

开始看清了 2024-10-25 01:02:37

这是最终版本...哇,我改进了自己的代码(我之前放的另一篇文章是我多年来一直使用的代码,直到今天...我看到了这篇文章,然后我放了我的代码...然后我尝试修复自己的代码,然后我得到了它,哇!我尝试了很多年,现在我终于得到了)。

这是相当棘手的,因为我到底如何想象可以在编辑器处于活动状态时选择一个单元格?

让我们看看如何做到这一点:

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;

这对我来说就像一个魅力。

请注意,它需要两个变量来保存最后编辑的单元格坐标。

请记住,Options 中的 goEditing 必须为 True

请对另一篇文章感到抱歉...其他代码是我多年来使用的代码,因为我没有得到任何更好的解决方案...直到现在。

我希望这对其他人有帮助。

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.

奢华的一滴泪 2024-10-25 01:02:37

最好只使用 虚拟字符串网格 作为字符串网格Delphi 中的控件似乎并不能很好地支持这一点。

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.

蘑菇王子 2024-10-25 01:02:37

解决方案:

  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;

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;
明明#如月 2024-10-25 01:02:37

基本上,用户可以通过多种方式结束编辑,但并非所有这些总是一个好的拦截点:

  1. 它将焦点移动到网格中的另一个单元格
  2. 将焦点移动到表单上的另一个控件
  3. 将焦点移动到另一个表单
  4. 它将焦点转移到另一个应用程序。

你需要问自己在什么情况下你想要更新内容。

例如:当用户取消模态表单或结束应用程序时,您是否想要更新它?

——杰罗恩

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

为你拒绝所有暧昧 2024-10-25 01:02:37

来自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;
}

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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文