Inplace editor events
Intercept value transfer, customize editor creation, validate close/exit, and use keyboard lookup when editing is disabled.
Overview
The basic Editing cells guide covers enabling editors and choosing editor types. This chapter focuses on the event points around the editor lifecycle.
| Stage | Events |
|---|---|
| Open editor | OnBeforeOpenInplaceEditor, OnAfterOpenInplaceEditor |
| Load value into editor | OnBeforeInplaceEditorGetValue, OnAfterInplaceEditorGetValue |
| Write value back | OnBeforeInplaceEditorSetValue, OnAfterInplaceEditorSetValue |
| Close editor | OnBeforeCloseInplaceEditor, OnAfterCloseInplaceEditor |
| Grid focus leaves | OnBeforeExitInplaceEditor, OnAfterExitInplaceEditor |
| Keyboard lookup | Options.Lookup, OnBeforeLookup, OnAfterLookup |
Open and close
Prevent editing for specific cells in OnBeforeOpenInplaceEditor:
procedure TForm1.GridBeforeOpenInplaceEditor(Sender: TObject;
ACell: TTMSFNCDataGridCellCoord;
var AValue: TTMSFNCDataGridCellValue; var ACanOpen: Boolean);
begin
if ACell.Column = ReadOnlyColumn then
ACanOpen := False;
end;
void __fastcall TForm1::GridBeforeOpenInplaceEditor(TObject *Sender,
const TTMSFNCDataGridCellCoord &ACell,
TTMSFNCDataGridCellValue &AValue, bool &ACanOpen)
{
TTMSFNCDataGridCellCoord Cell = ACell;
if (Cell.Column == ReadOnlyColumn)
ACanOpen = false;
}
Validate before the editor closes:
procedure TForm1.GridBeforeCloseInplaceEditor(Sender: TObject;
ACell: TTMSFNCDataGridCellCoord;
AInplaceEditor: TTMSFNCDataGridInplaceEditor;
ACancelled: Boolean;
var AValue: TTMSFNCDataGridCellValue;
var ACanClose: Boolean);
begin
if not ACancelled and AValue.IsEmpty then
begin
ShowMessage('Value is required.');
ACanClose := False;
end;
end;
void __fastcall TForm1::GridBeforeCloseInplaceEditor(TObject *Sender,
const TTMSFNCDataGridCellCoord &ACell,
TTMSFNCDataGridInplaceEditor *AInplaceEditor,
bool ACancelled,
TTMSFNCDataGridCellValue &AValue,
bool &ACanClose)
{
if (!ACancelled && AValue.IsEmpty)
{
ShowMessage("Value is required.");
ACanClose = false;
}
}
Use exit events when focus leaves the grid entirely. Use close events when an edit session ends while the grid remains active.
Value transfer
Use the "before" value events to override the default transfer.
procedure TForm1.GridBeforeInplaceEditorGetValue(Sender: TObject;
ACell: TTMSFNCDataGridCellCoord;
AInplaceEditor: TTMSFNCDataGridInplaceEditor;
var AValue: TTMSFNCDataGridCellValue; var AHandled: Boolean);
begin
if ACell.Column = AmountColumn then
begin
AValue := TValue.From<string>(FormatFloat('0.00', Grid.ValueToFloat(AValue)));
AHandled := True;
end;
end;
void __fastcall TForm1::GridBeforeInplaceEditorGetValue(TObject *Sender,
const TTMSFNCDataGridCellCoord &ACell,
TTMSFNCDataGridInplaceEditor *AInplaceEditor,
TTMSFNCDataGridCellValue &AValue, bool &AHandled)
{
TTMSFNCDataGridCellCoord Cell = ACell;
if (Cell.Column == AmountColumn)
{
AValue = TValue::From<System::UnicodeString>(FormatFloat("0.00", Grid->ValueToFloat(AValue)));
AHandled = true;
}
}
Validate before writing back:
procedure TForm1.GridBeforeInplaceEditorSetValue(Sender: TObject;
ACell: TTMSFNCDataGridCellCoord;
AInplaceEditor: TTMSFNCDataGridInplaceEditor;
var AValue: TTMSFNCDataGridCellValue; var AHandled: Boolean);
begin
if (ACell.Column = QuantityColumn) and (AValue.AsInteger < 0) then
begin
ShowMessage('Quantity cannot be negative.');
AHandled := True;
end;
end;
void __fastcall TForm1::GridBeforeInplaceEditorSetValue(TObject *Sender,
const TTMSFNCDataGridCellCoord &ACell,
TTMSFNCDataGridInplaceEditor *AInplaceEditor,
TTMSFNCDataGridCellValue &AValue, bool &AHandled)
{
TTMSFNCDataGridCellCoord Cell = ACell;
if ((Cell.Column == QuantityColumn) && (AValue.AsInteger() < 0))
{
ShowMessage("Quantity cannot be negative.");
AHandled = true;
}
}
Data-level cell edit events
Three events operate at the data layer rather than the renderer layer. They receive TTMSFNCDataGridCellValue directly and fire independently of which editor type is active — including custom editors.
| Event | When it fires | What you can do |
|---|---|---|
OnCellEditGetData |
Just before the editor is loaded with the cell's current value. | Replace the value the editor sees (e.g. reformat it). |
OnCellEditSetData |
Just before the edited value is written back to the cell. | Transform the value before it is stored (e.g. strip whitespace, apply units). |
OnCellEditValidateData |
Before the edit is accepted; AValid defaults to True. |
Reject invalid input by setting AValid := False. The editor stays open with the rejected text still in it, so the user corrects what they typed instead of retyping it from scratch; the grid does not move to the next cell. |
procedure TForm1.GridCellEditGetData(Sender: TObject;
ACell: TTMSFNCDataGridCellCoord;
AInplaceEditor: TTMSFNCDataGridInplaceEditor;
var AValue: TTMSFNCDataGridCellValue);
begin
// Show stored temperature in °F; it is stored in °C
if ACell.Column = TempColumn then
AValue := TValue.From<Double>(Grid.ValueToFloat(AValue) * 9 / 5 + 32);
end;
procedure TForm1.GridCellEditSetData(Sender: TObject;
ACell: TTMSFNCDataGridCellCoord;
AInplaceEditor: TTMSFNCDataGridInplaceEditor;
var AValue: TTMSFNCDataGridCellValue);
begin
// Convert °F back to °C before storing
if ACell.Column = TempColumn then
AValue := TValue.From<Double>((Grid.ValueToFloat(AValue) - 32) * 5 / 9);
end;
procedure TForm1.GridCellEditValidateData(Sender: TObject;
ACell: TTMSFNCDataGridCellCoord;
AInplaceEditor: TTMSFNCDataGridInplaceEditor;
var AValue: TTMSFNCDataGridCellValue;
var AValid: Boolean);
begin
// Reject negative temperatures in Kelvin column
if ACell.Column = KelvinColumn then
AValid := Grid.ValueToFloat(AValue) >= 0;
end;
void __fastcall TForm1::GridCellEditGetData(TObject *Sender,
const TTMSFNCDataGridCellCoord &ACell,
TTMSFNCDataGridInplaceEditor *AInplaceEditor,
TTMSFNCDataGridCellValue &AValue)
{
// Show stored temperature in Fahrenheit; it is stored in Celsius
TTMSFNCDataGridCellCoord Cell = ACell;
if (Cell.Column == TempColumn)
AValue = TValue::From<double>(AValue.AsExtended() * 9 / 5 + 32);
}
void __fastcall TForm1::GridCellEditSetData(TObject *Sender,
const TTMSFNCDataGridCellCoord &ACell,
TTMSFNCDataGridInplaceEditor *AInplaceEditor,
TTMSFNCDataGridCellValue &AValue)
{
// Convert Fahrenheit back to Celsius before storing
TTMSFNCDataGridCellCoord Cell = ACell;
if (Cell.Column == TempColumn)
AValue = TValue::From<double>((AValue.AsExtended() - 32) * 5 / 9);
}
void __fastcall TForm1::GridCellEditValidateData(TObject *Sender,
const TTMSFNCDataGridCellCoord &ACell,
TTMSFNCDataGridInplaceEditor *AInplaceEditor,
TTMSFNCDataGridCellValue &AValue,
bool &AValid)
{
// Reject negative temperatures in Kelvin column
TTMSFNCDataGridCellCoord Cell = ACell;
if (Cell.Column == KelvinColumn)
AValid = Grid->ValueToFloat(AValue) >= 0;
}
Tip:
OnCellEditGetDataandOnCellEditSetDataare the right place for unit conversions, lookup formatting, or any round-trip transform. For simple validation (range checks, required fields),OnCellEditValidateDatais cleaner thanOnBeforeCloseInplaceEditorbecause it receives the parsedTValuerather than a raw string.
Custom editors and the first keystroke
When the user starts editing by typing rather than by pressing F2 (see the
direct-edit shortcuts in Editing), the character that triggered
the edit is not lost: it is handed to the editor as its initial value, so
"type 4 to start editing" leaves a 4 in the editor rather than an empty box.
This holds for custom editors too. A getCustom editor built on an edit or memo
control receives that first character the same way the built-in editors do, so a
column with a custom editor behaves like every other column when the user just
starts typing. An editor that is not text-based — a picker, a checkbox, a button
— simply ignores it.
Keyboard lookup
Keyboard lookup moves the focused cell to the first matching value while editing is disabled.
Grid.Options.Editing.Enabled := False;
Grid.Options.Lookup.Enabled := True;
Grid.Options.Lookup.Incremental := True;
Grid.Options.Lookup.CaseSensitive := False;
Grid.Options.Lookup.ResetInterval := 4000;
Grid->Options->Editing->Enabled = false;
Grid->Options->Lookup->Enabled = true;
Grid->Options->Lookup->Incremental = true;
Grid->Options->Lookup->CaseSensitive = false;
Grid->Options->Lookup->ResetInterval = 4000;
Programmatic lookup is available too:
Grid.Lookup('Smith', True, True);
Grid.LookupInColumn(1, 'Smith');
Grid.LookupInColumnFromRow(1, 5, 'Smith');
Grid->Lookup(L"Smith", true, true);
Grid->LookupInColumn(1, L"Smith");
Grid->LookupInColumnFromRow(1, 5, L"Smith");
Use OnBeforeLookup to modify or cancel the search string:
procedure TForm1.GridBeforeLookup(Sender: TObject;
var ALookupString: string; var ACanLookup: Boolean);
begin
if Length(ALookupString) < 2 then
ACanLookup := False;
end;
void __fastcall TForm1::GridBeforeLookup(TObject *Sender,
System::UnicodeString &ALookupString, bool &ACanLookup)
{
if (Length(ALookupString) < 2)
ACanLookup = false;
}
Combining value transfer and validation
Open, close, transfer, and validation events are most useful together, because an edit session passes through all of them: the editor receives a display value, the user types, the entry is validated, and a converted value is stored. Wire them as a set so the stored data and what the user sees stay consistent. This combination fits unit conversions with input rules, such as showing Fahrenheit while storing Celsius and refusing empty or impossible entries. The example below converts a temperature column in both directions, rejects negative Kelvin values, and blocks closing the editor on an empty value.
procedure TForm1.FormCreate(Sender: TObject);
begin
Grid.OnCellEditGetData := GridCellEditGetData;
Grid.OnCellEditSetData := GridCellEditSetData;
Grid.OnCellEditValidateData := GridCellEditValidateData;
Grid.OnBeforeCloseInplaceEditor := GridBeforeCloseInplaceEditor;
end;
procedure TForm1.GridCellEditGetData(Sender: TObject;
ACell: TTMSFNCDataGridCellCoord;
AInplaceEditor: TTMSFNCDataGridInplaceEditor;
var AValue: TTMSFNCDataGridCellValue);
begin
// Stored in Celsius, edited in Fahrenheit
if ACell.Column = 1 then
AValue := TValue.From<Double>(Grid.ValueToFloat(AValue) * 9 / 5 + 32);
end;
procedure TForm1.GridCellEditSetData(Sender: TObject;
ACell: TTMSFNCDataGridCellCoord;
AInplaceEditor: TTMSFNCDataGridInplaceEditor;
var AValue: TTMSFNCDataGridCellValue);
begin
if ACell.Column = 1 then
AValue := TValue.From<Double>((Grid.ValueToFloat(AValue) - 32) * 5 / 9);
end;
procedure TForm1.GridCellEditValidateData(Sender: TObject;
ACell: TTMSFNCDataGridCellCoord;
AInplaceEditor: TTMSFNCDataGridInplaceEditor;
var AValue: TTMSFNCDataGridCellValue;
var AValid: Boolean);
begin
// Kelvin column cannot be negative
if ACell.Column = 2 then
AValid := Grid.ValueToFloat(AValue) >= 0;
end;
procedure TForm1.GridBeforeCloseInplaceEditor(Sender: TObject;
ACell: TTMSFNCDataGridCellCoord;
AInplaceEditor: TTMSFNCDataGridInplaceEditor;
ACancelled: Boolean;
var AValue: TTMSFNCDataGridCellValue;
var ACanClose: Boolean);
begin
if not ACancelled and AValue.IsEmpty then
ACanClose := False;
end;
void __fastcall TForm1::FormCreate(TObject *Sender)
{
Grid->OnCellEditGetData = GridCellEditGetData;
Grid->OnCellEditSetData = GridCellEditSetData;
Grid->OnCellEditValidateData = GridCellEditValidateData;
Grid->OnBeforeCloseInplaceEditor = GridBeforeCloseInplaceEditor;
}
void __fastcall TForm1::GridCellEditGetData(TObject *Sender,
const TTMSFNCDataGridCellCoord &ACell,
TTMSFNCDataGridInplaceEditor *AInplaceEditor,
TTMSFNCDataGridCellValue &AValue)
{
// Stored in Celsius, edited in Fahrenheit
TTMSFNCDataGridCellCoord Cell = ACell;
if (Cell.Column == 1)
AValue = TValue::From<double>(AValue.AsExtended() * 9 / 5 + 32);
}
void __fastcall TForm1::GridCellEditSetData(TObject *Sender,
const TTMSFNCDataGridCellCoord &ACell,
TTMSFNCDataGridInplaceEditor *AInplaceEditor,
TTMSFNCDataGridCellValue &AValue)
{
TTMSFNCDataGridCellCoord Cell = ACell;
if (Cell.Column == 1)
AValue = TValue::From<double>((AValue.AsExtended() - 32) * 5 / 9);
}
void __fastcall TForm1::GridCellEditValidateData(TObject *Sender,
const TTMSFNCDataGridCellCoord &ACell,
TTMSFNCDataGridInplaceEditor *AInplaceEditor,
TTMSFNCDataGridCellValue &AValue,
bool &AValid)
{
// Kelvin column cannot be negative
TTMSFNCDataGridCellCoord Cell = ACell;
if (Cell.Column == 2)
AValid = Grid->ValueToFloat(AValue) >= 0;
}
void __fastcall TForm1::GridBeforeCloseInplaceEditor(TObject *Sender,
const TTMSFNCDataGridCellCoord &ACell,
TTMSFNCDataGridInplaceEditor *AInplaceEditor,
bool ACancelled,
TTMSFNCDataGridCellValue &AValue,
bool &ACanClose)
{
if (!ACancelled && AValue.IsEmpty)
ACanClose = false;
}
Related API
TTMSFNCDataGrid.Options.EditingTTMSFNCDataGrid.Options.LookupTTMSFNCDataGrid.LookupTTMSFNCDataGrid.LookupInColumnTTMSFNCDataGrid.LookupInColumnFromRowOnBeforeOpenInplaceEditor/OnAfterOpenInplaceEditorOnBeforeInplaceEditorGetValue/OnAfterInplaceEditorGetValueOnBeforeInplaceEditorSetValue/OnAfterInplaceEditorSetValueOnBeforeCloseInplaceEditor/OnAfterCloseInplaceEditorOnBeforeExitInplaceEditor/OnAfterExitInplaceEditorOnCellEditGetData— data-level: override the value loaded into the editor.OnCellEditSetData— data-level: transform the value before it is written back.OnCellEditValidateData— data-level: reject invalid values withAValid := False.OnBeforeLookup/OnAfterLookup
See also
- Editing cells - editor types and validation.
- Keyboard and mouse - navigation and keyboard behavior.