Når et dynamisk XFA-skjema i en Delphi-viser legger til eller fjerner sider, rapporterer PDFium Component det nye totaltallet gjennom TPdf.PageCount og TPdf.OnXfaPageCountChanged siden v3.126.1, fordi den native sidehendelsen bærer en lagt til/fjernet-differanse snarere enn et totaltall. Windows V8-bibliotekene i v3.126.1 flytter også inntastingsområder med flyttede felt, og v3.126.2 laster foreldede sidehåndtak på nytt etter at layout-callbacken returnerer. Feilrapporten som startet dette, var et utleggskjema: klikk Legg til rad to ganger, skjemaet vokser til to sider, og sideindikatoren leser stolt 1 av 1. Skriv i et felt som har flyttet til side 2, og tastetrykkene lander et sted usynlig. Ingen av det viste seg med skjemaene med fast lengde som alle tester med først, og grunnene er verdt å kjenne hvis du bygger inn en skjemaviser
Hva skjer når et dynamisk XFA-skjema repaginerer?
Et dynamisk XFA-skjema har ingen fast sideliste, så sideantallet er en utdata av layout og kan endres hver gang brukeren redigerer data. XFA 3.3 beskriver skjemaet som et tre av subforms; en repeterende subform styres av en instanceManager, og et skript som _Row.addInstance() kloner én rad til. Layout-prosessoren flyter så innholdet inn i sideområdene igjen, noe som kan legge til en side, droppe en side eller skyve eksisterende felt over på en annen side. ISO 32000-1 §12.7.8 definerer bare hvordan XFA-pakkene kjører inne i PDF-en; alt som skjer etterpå tilhører XFA-motoren, som i PDFium Component er PDFiums egen XFA-layout som kjører i vertsprosessen. En Delphi-viser har derfor å gjøre med et dokument hvis sideantall, sidestørrelser og widgetposisjoner alle er levende tilstand. Tre ting går galt når verten antar noe annet:
- Sideantallet verten cacher for navigering, rulleområder og side-spinner blir foreldet, eller verre, oppdateres med feil tall
- Felt som flytter seg, viser kanten sin i den nye posisjonen mens editoren og musetreffområdet blir på de gamle koordinatene
- Viseren beholder et sidehåndtak som layouten har erstattet, så klikk og tegninger går til en side som ikke lenger finnes i det skjemaet
Å lagre radredigeringer over lagring og gjenåpning er et separat problem med egne regler; denne artikkelen holder seg til det som skjer under kjøring inne i viseren
Hvilket PDFium-kjøretid trenger dynamisk XFA?
Dynamisk XFA i PDFium Component krever V8/XFA-bygget av det native biblioteket, valgt av den globale EnableV8Engine-variabelen i PDFium-enheten før det første dokumentet lastes. Prosessen forplikter seg til én DLL første gang en TPdf laster biblioteket, og et vanlig PDFium-bygg kan ikke kjøre XFA-motoren i det hele tatt. Når et dokument åpnes, kikker TPdf i filen etter XFA-markører og bytter automatisk til V8-bygget, men bare hvis intet vanlig bibliotek er lastet ennå i den prosessen. Når forpliktelsen allerede har gått feil vei, fyrer TPdf.OnXfaRuntimeMissing én gang slik at verten kan be brukeren om å starte på nytt. Å sette flagget eksplisitt ved oppstart fjerner gjettingen. FPDF_FORMFILLINFO-callbackstrukturen som bærer XFA-hendelsene, må også matche DLL-en; bakgrunnen finner du i FPDF_FORMFILLINFO versjon 2 og XFA-callback-ABI-en, og deteksjon av XFA-skjemaer og lesing av pakkene deres dekker å skille skjematyper fra hverandre før du åpner en viser
uses
PDFium;
procedure TClaimForm.FormCreate(Sender: TObject);
begin
// Avgjør før den første TPdf laster det native biblioteket:
// prosessen kan ikke bytte fra pdfium.dll til pdfium.v8.dll senere
EnableV8Engine := True;
FPdf := TPdf.Create(nil);
FPdf.OnXfaRuntimeMissing := PdfXfaRuntimeMissing;
FPdf.OnXfaPageCountChanged := PdfXfaPageCountChanged;
FPdf.FileName := 'C:\Forms\expense-claim.pdf';
FPdf.Active := True;
PdfView1.Pdf := FPdf;
PdfView1.OnPageChange := PdfViewPageChange;
PdfView1.Active := True;
UpdatePageRange(FPdf.PageCount);
end;
procedure TClaimForm.PdfXfaRuntimeMissing(Sender: TObject);
begin
StatusBar1.SimpleText :=
'This XFA form needs the V8 runtime; restart the application to enable it';
end;
Hvorfor rapporterte PageCount 1 for et topagers skjema?
Før v3.126.1 lagret PDFium Component page_count-argumentet til den native sidehendelsen som dokumenttotalen, og det argumentet er faktisk den absolutte differansen mellom det nye og gamle sideantallet. PDFium løfter FFI_PageEvent etter at et layout-pass er ferdig, med en hendelsestype av side lagt til eller side fjernet; internt oppdaterer det sitt lagrede sideantall først og sender så abs(new - old). Ved første layout er det gamle antallet null, så differansen er lik totalen, og en tretagers statisk prøve rapporterer tre sider som forventet. Det er nøyaktig derfor testskjemaer med fast lengde aldri avslørte buggen. Første gang et dynamisk skjema vokser fra én side til to, er differansen 1, og wrapperen satte både TPdf.PageCount og NewCount-parameteren til OnXfaPageCountChanged til 1. Å fjerne en rad fra et tretagers skjema ga samme slags tull i motsatt retning
Å akkumulere differansen oppå den forrige verdien er heller ikke en trygg reparasjon. Rekkefølgen av initialiserings- og layout-callbacks betyr at wrapperen ikke alltid kan stole på sitt tidligere antall som utgangspunkt, så en løpende sum kan drive. Siden v3.126.1 ignorerer callbacken argumentet som et antall og kaller FPDF_GetPageCount på dokumentet, som leser totalen fra layouten som nettopp er fullført. Den tømmer så de bufrede sidescenene, lagrer det totaltallet som XFA-sideantall-overstyringen bak TPdf.PageCount, og fyrer først etter det OnXfaPageCountChanged. Når handleren din kjører, er NewCount og FPdf.PageCount enige
procedure TClaimForm.PdfXfaPageCountChanged(Sender: TObject; NewCount: Integer);
begin
// v3.126.1+: NewCount er den fullførte layoutens total, aldri en differanse.
// Dette kjører inne i PDFiums layout-callback: oppdater bare vertens UI-tilstand,
// ikke lukk dokumentet eller last sider på nytt herfra
UpdatePageRange(NewCount);
end;
procedure TClaimForm.PdfViewPageChange(Sender: TObject);
begin
// Fyrer etter hver sidelasting på nytt, inkludert den utsatte XFA-oppfriskningen
PageSpin.Value := PdfView1.PageNumber;
end;
procedure TClaimForm.UpdatePageRange(Count: Integer);
begin
PageSpin.MinValue := 1;
PageSpin.MaxValue := Count;
PageLabel.Caption := Format('of %d', [Count]);
end;
Hendelsen fyres bare for Full XFA-skjemaer hvis layout endres under kjøring. Statisk XFA og AcroForm-dokumenter løfter den aldri, så en viser som håndterer begge, kan la samme handler stå tilordnet. Å la den stå uten tilordning er også trygt; overstyringen bak TPdf.PageCount brukes uansett, og hendelsen finnes slik at verten kan oppfriske det den har cachet
Hvorfor blir inntastingsboksen på den gamle siden når et felt flytter seg?
Kanten flyttet seg og editoren ikke, fordi den native XFA-notifieren sammenlignet et rektangel med seg selv. Når layout endrer geometrien til en widget som allerede er lastet, skal PDFium legge merke til det nye rektangelet og kalle PerformLayout på widgeten, som flytter teksteditoren og treffområdet dens. Sjekken sammenlignet GetWidgetRect() med RecacheWidgetRect(). Begge funksjonene returnerer en const-referanse til samme medlem, og recachen overskriver det medlemmet på stedet, så sammenligningen så alltid to identiske verdier, og lastede widgets hoppet over sin relayout
Symptomet kom til syne da en test endret høyden til en subform slik at eksisterende felt krysset over på neste side. På begge V8-arkitekturene ble feltkanten tegnet i sin nye posisjon mens den inn-tastede teksten og musetreffområdet ble på den forrige Y-koordinaten. En eksplisitt relayout fikset det ikke, og det gjorde ikke en sidelasting på nytt heller, fordi widgeten fortsatt trodde geometrien sin var aktuell. Windows V8-bibliotekene som følger med v3.126.1, kopierer det gamle rektangelet etter verdi før recaching og sammenligner den kopien, så flyttede widgets relayout-er og den redigerte verdien dukker opp nøyaktig der kanten er. Dette er en native fiks: den reiser med DLL-ene, så å oppdatere Pascal-enhetene mens du beholder en eldre pdfium.v8.dll etterlater de feilplasserte treffområdene som de er. Regresjonssjekken som drev den, redigerer en overlevende rad til en ikke-standardverdi først og krever så den verdien på feltets nye plassering, fordi en rad gjenoppbygd med standardverdier ellers ville sett ut som en bestått test
Hvordan laster TPdfView sider på nytt uten å trekke et håndtak bort under PDFium?
Siden v3.126.2 utsetter TPdfView sidelastingen på nytt som følger en XFA-layout-endring til den native anropsstakken har foldet seg ut. Sidehendelsen fyres vanligvis mens PDFium fortsatt behandler inndata: brukeren klikket en Legg til rad-knapp, klikket kjørte et skript, skriptet endret instansantallet, og layouten ble ferdig inne i samme native kall. Å lukke og gjenåpne sidehåndtaket i det øyeblikket ville frigjort et objekt kalleren fortsatt bruker. Før v3.126.2 ugyldiggjorde viseren bare seg selv, så det viste sidehåndtaket kunne fortsette å peke på tilstand før layout, og hvis brukeren hadde vært på siste side da den forsvant, var det valgte sidetallet utenfor området
Den utsatte oppfriskningen jobber i noen små steg, og de forklarer atferden du ser fra verten:
- Sidehendelses-callbacken markerer visningen som har en ventende XFA-layout-oppfriskning og poster en privat vindusmelding; gjentatte hendelser før meldingen ankommer, slås sammen til én oppfriskning
- En viser uten vindushåndtak ennå beholder det ventende flagget og poster meldingen fra
CreateWnd, mens å bytte dokumenter, deaktivere viseren eller ødelegge den tømmer flagget - Når meldingen ankommer, tømmer viseren tekstmarkeringen, søkeuthevingen og indeksen til feltet i fokus, fordi alle tre refererte til den gamle layouten
- Den valgte siden klemmes til det nye
PageCount-antallet; et endret sidetall går gjennom det normale sidebyttet, ellers lastes den nåværende siden på nytt, og passform-modusen brukes igjen - Legger layouten igjen ingen sider i det hele tatt, losser viseren sitt gamle sidehåndtak i stedet for å tegne en side som ikke lenger finnes
Samme begrensning gjelder din egen kode. OnXfaPageCountChanged kjører inne i den native layout-callbacken, så behandle den som en varsling: oppdater etiketter, spinner-områder og verktøylinjetilstand der, og sett alt tyngre i kø, som å lukke dokumentet eller åpne et annet, med en postet melding slik at det kjører etter at callbacken returnerer. TPdfView.OnPageChange forteller deg så når viseren faktisk har lastet siden på nytt, og å lese PdfView1.PageNumber på det punktet gir deg den klemte verdien. Tab-tast-navigering og FormType-sjekkene en skjemaviser kjører ved åpning, er dekket i PDF-skjemafelt-navigering med PDFium Component
Hvorfor gir klikk i et Full XFA-felt «Cannot open text page»?
Full XFA-sider har ingen PDF-tekstside, og før v3.126.2 prøvde viserens standard tekstmarkering og koblingsdeteksjon å laste én likevel. Med TPdfView.AllowUserTextSelection på standarden True spurte å sveve tekstlaget etter et tegn under musen, og et muse-ned-klikk kjørte en automatisk URL-sonde over sideteksten. På en Full XFA-side kan tekst siden ikke åpnes, så et vanlig klikk inn i et felt kunne ende i et Cannot open text page-unntak. Siden v3.126.2 returnerer begge interne stiene intet resultat når TPdf.FormType er ftXfaFull og XFA-kjøretiden er tilgjengelig, så standardinnstillingene virker og felteinndata forblir tilgjengelige
Å slå av AllowUserTextSelection for Full XFA-dokumenter er fortsatt et fornuftig UI-valg, for det finnes ingen sidetekst å markere, og dra-bevegelser bør ikke starte en markeringsmodus. Det er ikke en erstatning for å oppgradere, derimot: på tidligere versjoner avhang URL-sonden ved klikk ikke av den egenskapen, så en viser kunne treffe samme unntak med markering avslått
procedure TClaimForm.ConfigureViewerForForm;
begin
// FormType leser det åpne dokumentet, så kall dette etter FPdf.Active := True
if FPdf.XFA and (FPdf.FormType = ftXfaFull) and FPdf.XfaRuntimeAvailable then
begin
// Intet PDF-tekstlag finnes på Full XFA-sider; feltene forblir redigerbare
PdfView1.AllowUserTextSelection := False;
StatusBar1.SimpleText := Format('Dynamic XFA form, %d page(s)',
[FPdf.PageCount]);
end
else
PdfView1.AllowUserTextSelection := True;
end;
Skriving trengte sin egen reparasjon i v3.126.2. Den native XFA-teksteditoren erstatter ikke en markering når den mottar et tegn: FORM_OnChar setter inn ved tekstpekeren, og Backspace sletter ett enkelt tegn, så å markere en verdi og skrive over den ga gammel og ny tekst side om side. PDFium Component husker nå at klikket landet på et XFA-tekstfelt og ruter inn-tastede tegn, Backspace og Delete gjennom FORM_ReplaceSelection når en markering finnes og dokumentet gir fyll-skjema- eller endringstillatelse. Om et skrivebeskyttet XFA-felt kan endres, avgjøres fortsatt av den native editoren, så et felt merket skrivebeskyttet i skjemaet beholder verdien sin selv i et dokument som ellers tillater utfylling. Å sette TPdfView.AllowFormEvents til False stopper også denne tastatur-ruteren, noe som holder en skrivebeskyttet viser skrivebeskyttet
Hurtigreferanse: Dynamisk XFA i en Delphi-viser
| Symptom | Årsak | Rettet i |
|---|---|---|
| Sideantallet viser 1 etter at skjemaet vokser til to sider | Den native sidehendelsen sender en lagt til/fjernet-differanse, ikke et totaltall | v3.126.1 (wrapper) |
| Feltkanten flytter seg, inn-tastede tekst og treffområde blir igjen | Lastet widget hoppet over relayout etter en selvsammenligning | v3.126.1 (Windows V8-biblioteker) |
| Viseren tegner eller ruter inndata til tilstand før layout | Sidehåndtaket ikke lastet på nytt etter repaginering | v3.126.2 (utsatt oppfriskning) |
| Klikk i et felt reiser Cannot open text page | Tekstmarkering og URL-sonde på sider uten tekstlag | v3.126.2 |
| Å skrive over en markert verdi legger til i stedet for å erstatte | Den native XFA-editoren setter inn ved tekstpekeren | v3.126.2 |
- Sett
EnableV8EnginetilTruefør noe dokument lastes, og håndterOnXfaRuntimeMissingfor tilfellet der det vanlige biblioteket ble lastet først - Les totalen fra
TPdf.PageCountellerNewCount-parameteren tilOnXfaPageCountChanged; aldri legg til eller trekk fra sideantall selv - Hold
OnXfaPageCountChanged-handleren lett, for den kjører inne i den native layout-callbacken - Synk den gjeldende sideindikatoren i
TPdfView.OnPageChange, som fyres etter at den utsatte sidelastingen har klemt sidetallet - Distribuer v3.126.1 eller nyere Windows V8-DLL-er sammen med enhetene; widget relayout-fiksen bor i native kode
- Test med et skjema som faktisk endrer sideantallet sitt og flytter et redigert felt over et sideskifte, fordi prøver med fast lengde skjuler hver bug på denne listen
Dynamisk XFA gjør sideantall og feltgeometri til levende verdier, og en viser forblir riktig bare når den henter dem fra den fullførte layouten og laster sider på nytt i et trygt øyeblikk. PDFium Component håndterer begge inne i TPdf og TPdfView, så verten trenger bare å lytte. Detaljer og nedlastinger finner du på produktsiden for PDFium Component for Delphi