【问题标题】:How can I detect when a user is finished editing a TStringGrid cell?如何检测用户何时完成编辑 TStringGrid 单元格?
【发布时间】:2011-06-30 15:50:44
【问题描述】:

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

在 Lazarus 中有 FinishedCellEditing 的方法,但在 Delphi 中没有。如何在 Delphi 中检测到它?

【问题讨论】:

  • 我了解到,适应这种情况的最佳方法是实现数据网格(使用客户端数据集)。实际上实现起来有点乏味,但一旦完成,它实际上更容易使用,尤其是在用户从 UI 编辑数据的意义上。然而,我仍在寻找实现字符串网格的方法。
  • 可惜 Delphi 不是 Lazarus。

标签: delphi events tstringgrid


【解决方案1】:

我有同样的问题,但更简单的解决方案,因为我强制用户按 Enter 键...

诀窍:我不允许用户在编辑一个单元格时更改到另一个单元格,所以我强制用户必须按 Intro/Enter 键结束编辑,然后我允许更改到另一个单元格.

不好的部分是 OnKeyPress 发生在 OnSetEditText 之前,所以我尝试使用 OnKeyUp...

我发现,就在编辑单元格时,按下 Enter/Intro 后,OnKeyUp 没有被触发……这是 VCL 上的一个 BUG……一个键已被释放,而 OnKeyUp 没有被触发。

所以,我做了另一个技巧来绕过它......使用计时器来改变我会做的事情,所以我让时间来触发 OnSetEditText 之前的事件。

让我解释一下我为成功所做的一切......

我已经通过在 OnSelectCell 上放置代码来锁定选择另一个单元格,与此非常相似:

CanSelect:=Not UserIsEditingOneCell;

在 OnSetEditText 上我写了这样的代码:

UserIsEditingOneCell:=True;

所以现在,需要检测用户何时按下 Enter/Intro ......我发现了一件可怕的事情,正如我所说...... OnKeyUp 不会为这样的键触发......所以,我将模拟那个通过使用 Timer 并使用 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

这行得通,但我知道这很可怕,因为我需要使用计时器......但我不知道更好的方法......而且因为我不需要让用户去另一个单元格,而那个是edinting 没有有效值...我可以使用它。

为什么没有像 OnEndingEditing 这样的事件?

PD:我还注意到 OnSetEditText 会为每个被按下的键触发多次,并且在 Value 参数上具有不同的值...至少在使用 OnGetEditMask 事件上设置的 EditMask 值“00:00:00”时。

【讨论】:

  • 我正在使用 Delphi XE2 仍然有这个错误 :(
  • 这甚至不是合适的解决方案。
  • 请不要发布多个答案。请将您的答案合并为一个!!!!
  • @Frosty 这些是不同的用户。此外,关于多个答案,请参阅:1234
【解决方案2】:

使用 VCL 的 TStringGrid 您需要 OnSetEditText 事件。但是请注意,每次用户更改任何单元格中的内容时,它都会触发。因此,如果您只想在用户完成编辑后做某事,您将不得不观察事件参数的行和列值。当然,当用户结束编辑一个单元格并且不编辑另一个单元格时,您需要注意这种情况,例如通过单击 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;

【讨论】:

  • 类中声明的私有实例变量以 F 为前缀(根据 Delphi 编码约定)。但是,当您在使用的所有三个事件处理程序中使用这些变量时,您错过了 F 前缀。
【解决方案3】:

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

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

【讨论】:

  • 另一条消息是 WM_ACTIVATE 检查值 WA_INACTIVE
【解决方案4】:

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

这很棘手,我怎么能想象一个单元格可以在编辑器处于活动状态时被选中?

让我们看看怎么做:

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;

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

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

请记住goEditingOptions 中必须是True

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

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

【讨论】:

  • 请不要发布多个答案。请将您的答案合并为一个!!!!
  • @Frosty 这些是不同的用户。此外,关于多个答案,请参阅:1234
【解决方案5】:

最好只使用virtual string grid,因为 Delphi 中的字符串网格控件似乎并不能很好地支持这一点。

【讨论】:

    【解决方案6】:

    解决方案:

      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;
    

    【讨论】:

      【解决方案7】:

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

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

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

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

      --杰罗恩

      【讨论】:

      • 那么 5. 用户按下 Enter 和 6. 用户按下 Tab 怎么样?
      • @Jeroen Wiert Pluimers - 有太多的可能性。您需要一个处理所有这些的中心位置。我使用 WndProc。我刚刚发布了一个例子。它就像一个魅力。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-01-25
      • 2012-05-03
      • 1970-01-01
      • 1970-01-01
      • 2013-08-29
      • 1970-01-01
      • 2017-05-12
      相关资源
      最近更新 更多