簡體   English   中英

如何檢測用戶何時完成編輯 TStringGrid 單元格?

[英]How can I detect when a user is finished editing a TStringGrid cell?

當用戶完成輸入數據時,我想在字符串網格中返回單元格的內容。 當按下鍵盤上的回車鍵,或者單擊或雙擊另一個單元格時,用戶就完成了。

在 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;

這是最終版本...哇,我改進了自己的代碼(我之前發布的另一篇文章是我使用多年的代碼,直到今天...我看到了這篇文章,我把我擁有的代碼...然后我試圖修復我自己的代碼,我得到了它,哇!,我已經嘗試了很多年,現在我終於得到了它)。

這很棘手,因為我怎么能想象一個單元格可以在編輯器處於活動狀態時被選中?

讓我們看看如何做到這一點:

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

請對另一篇文章感到抱歉......其他代碼是我多年來一直使用的代碼,因為我沒有得到任何更好的解決方案......直到現在。

我希望這對其他人有幫助。

我通過響應發送到就地編輯器的 WM_KILLFOCUS 消息來做到這一點。 我必須對就地編輯器進行子類化才能做到這一點。

我從 Raymond Chen 的博客中了解到,如果您隨后執行更改焦點的驗證,這是不合適的。

最好只使用虛擬字符串網格,因為 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;

基本上,用戶可以通過多種方式結束編輯,但並非所有這些總是一個好的攔截點:

  1. 它將焦點移動到網格中的另一個單元格
  2. 它將焦點移動到表單上的另一個控件
  3. 它將焦點轉移到另一種形式
  4. 它將焦點轉移到另一個應用程序。

您需要問自己在什么情況下要更新內容。

例如:當用戶取消模式表單或結束應用程序時,您想更新它嗎?

——傑倫

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM