Reentrancy на OnExit при in-place редактор на PDF form field възниква, когато TPDFlibViewer затваря редактора от неговия собствен OnExit event и промяната на Visible или Parent предизвиква второ извикване
Проблемът често се появява при бързо преминаване с Tab през текстови полета. Старият editor все още държи фокуса, когато новият field започва редактиране, затова commit логиката трябва да е безопасна при повторно влизане
Как TPDFlibViewer поставя истински editor върху rendered page
TPDFlibViewer рендерира PDF страницата като bitmap и по подразбиране не превръща полетата в живи VCL контроли. BeginEditFormField създава подходящ TEdit, TMemo или TComboBox върху координатите на полето
Защо скриването на editor извиква OnExit втори път?
VCL TWinControl може да премести фокуса синхронно при промяна на Visible или Parent на фокусиран control. Това извиква OnExit, докато CommitInplaceEditor вече изпълнява първото затваряне
Какво се обърква при повторно влизане в CommitInplaceEditor?
Наивният commit може да запише стойността два пъти или да освободи control, докато неговият event stack още работи. Резултатът е повредена стойност, достъп до освободена памет или нестабилност, която зависи от фокуса
// Naive version: reads fine in review, fails only under real typing speed
procedure TMyPdfViewer.EditorExit(Sender: TObject);
begin
CommitEditor; // still running inside FEditor's own OnExit
end;
procedure TMyPdfViewer.CommitEditor;
begin
if not Assigned(FEditor) then
Exit;
SaveFieldValue(FEditor.Text);
FEditor.Parent := nil; // focused control reparented here: OnExit
// fires again, re-entering this same method
FEditor.Free; // freed while a caller further down the
FEditor := nil; // stack is still inside its OnExit handler
end;Занулете reference преди да докоснете control-а
PDFlibPas първо записва editor в локална променлива, занулява полето, което сочи към активния control, и едва след това променя Visible или Parent. Така повторният OnExit вижда nil и приключва безопасно
procedure TPDFlibViewer.CommitInplaceEditor(Save: Boolean);
var
Editor: TWinControl;
begin
Editor := FInplaceEditor;
if not Assigned(Editor) then
Exit; // a reentrant call lands here and stops
FInplaceEditor := nil; // detach before the control is touched at all
if Save then
SaveEditorValue(Editor); // safe: FInplaceEditor is already nil
Editor.Visible := False;
Editor.Parent := nil; // may fire OnExit again; the guard above
// turns that reentrant call into a no-op
ReapDeadEditor; // free whatever was parked last cycle
FDeadEditor := Editor; // park this one instead of freeing it here
end;SaveEditorValue представя реалната логика, която различава TComboBox, TMemo и TEdit и извлича текущата стойност преди control-ът да бъде скрит
Никога не освобождавайте control от неговия собствен event
TPDFlibViewer.CommitInplaceEditor не извиква директно Editor.Free. Освобождаването се отлага за безопасна точка след приключване на dispatch-а, защото текущият stack frame може още да използва control-а
procedure TPDFlibViewer.ReapDeadEditor;
begin
if Assigned(FDeadEditor) then
begin
FDeadEditor.Free; // safe now: this control's own OnExit
FDeadEditor := nil; // finished at least one edit cycle ago
end;
end;
function TPDFlibViewer.BeginEditFormField(FieldIndex: Integer): Integer;
var
Edit: TEdit;
begin
Result := 0;
CommitInplaceEditor(True); // flush whatever editor is still open
// ... field lookup and rectangle conversion omitted ...
ReapDeadEditor; // now safe to free last cycle's parked editor
Edit := TEdit.Create(Self);
Edit.Parent := Self;
Edit.OnExit := InplaceEditorExit;
FInplaceEditor := Edit;
FInplaceEditor.SetFocus;
Result := 1;
end;Защо това се проявява най-силно при бърза Tab навигация?
FocusNextFormField, добавен във v3.226.0 за Tab и Shift+Tab навигация, извиква BeginEditFormField за следващото поле след CommitInplaceEditor. Всеки Tab натиск повтаря detach-then-touch последователността, докато старият editor все още е фокусиран
Точният момент на OnExit зависи от focus и window-handle състоянието и може да се промени под debugger или при repaint. Затова guard трябва да е коректен по конструкция чрез ранно зануляване, а не да разчита на случайния ред на събитията
Същият модел важи за всяка editing surface, която поставя жив VCL control върху rendered content. Нулирайте reference преди операция, която може да предизвика exit event, и не извиквайте Free от път, който може да се изпълнява вътре в dispatch-а Свързана статия Свързана статия
In-place form field editing, Tab навигацията и reentrancy-safe commit пътят са част от интерактивния viewer control на PDFlibPas за Delphi и C++Builder Свързана статия