Teknisk artikkel

PDF-skjemafeltnavigering i Delphi (PDFium-komponenten)

Trykk Tab i et PDF-skjema koden din bygde, og markøren havner to felt unna der den skulle, eller hopper over den andre kolonnen helt, eller hopper tilbake til toppen etter det tredje feltet i stedet for det fjerde. Personen som fyller ut en faktura i fremviseren din, forventer at tastaturet vandrer gjennom skjemaet på samme måte som det vandrer gjennom hvert eneste nettskjema de noensinne har brukt. Når det ikke gjør det, griper de til musen, jakter på neste boks, og bestemmer stille at verktøyet ditt er uferdig. Forutsigbar feltnavigering er forskjellen mellom en dataregistreringsfremviser folk tolererer, og én de stoler på, og det handler nesten utelukkende om å bruke riktig fokus-API i stedet for å simulere tastaturinndata med falske klikk

Eksemplene under bruker PDFium Component, en PDFium-basert VCL/LCL-komponent for Delphi, C++Builder og Lazarus. Navigering er én av tre ting en skjemafremviser må få riktig; de to andre, å åpne skjemaet korrekt og å lagre utfylte verdier slik at de faktisk vises, er der de fleste overraskelsene gjemmer seg, så alle tre dekkes under

Å åpne et skjema: FormFill, FormType og XFA-spørsmålet

Felttilgang krever at delsystemet for skjemautfylling, styrt av FormFill-egenskapen, er aktivert før dokumentet åpnes. Når det er aktivt, forteller FormType deg hva slags skjema du står overfor, og svaret endrer funksjonssettet du kan love:

Diagram over FormFill-oppsett og FormType-deteksjonsgrener i en Delphi PDFium Component-viser, som splitter ftNone-, ftAcroForm- og ftXfaFull-håndtering
FormType forgrener seg så snart FormFill er aktivert, og hver grein lover et annet funksjonssett
Pdf.FileName := FormPath;
Pdf.FormFill := True;   // aktiver før Active; påkrevd for all felttilgang
Pdf.Active := True;

case Pdf.FormType of
  ftNone:
    DisableFormPanel('This document has no interactive form');
  ftAcroForm:
    BuildFieldList;     // full feltnavigering og -redigering tilgjengelig
  ftXfaFull:
    ShowXfaNotice;      // XFA rendres fra sin egen XML-mal;
                        // behandle feltredigering som begrenset
end;

To praktiske notater følger av det valget. AcroForm er den standard ISO 32000-skjemamodellen, og det er det alle API-ene her retter seg mot. XFA-dokumenter bygger inn sin egen XML-skjemaarkitektur, så å love en kunde full XFA-redigering etter en rask AcroForm-demo er en forpliktelse du vil angre på. Det andre notatet handler om sideeffekter: å sette FormFill til True initialiserer også dokument-JavaScript. I en dataregistreringsfremviser er det nøyaktig riktig, fordi beregningsskript er det som holder en løpende sum oppdatert mens noen skriver. I et forhåndsvisningsvindu for filer av ukjent opprinnelse er det nøyaktig feil. Artikkelen om sikker PDF-forhåndsvisning dekker FormFill := False-siden av den avveiningen

Tab-tastnavigering som lander der brukerne forventer

Tilbake til tastaturproblemet fra toppen. Fristelsen er å simulere Tab ved å syntetisere et museklikk på det neste kontrollelementets rektangel, noe som bryter sammen i det øyeblikket et felt er rullet utenfor skjermen eller to kontrollelementer overlapper. Fokus-API-et flytter i stedet skjemaets eget fokus direkte, uten noen geometrigjetning. Fem kall dekker det: FocusFormField etter indeks, FocusNextFormField og FocusPreviousFormField for steg, FocusedFormFieldIndex for å lese hvor du er, og ClearFormFieldFocus for å slippe fokus helt

Diagram over Tab-tast fokusgjennomgang i en Delphi PDFium Component-viser der FocusNextFormField vikler innenfor én sides tab-rekkefølge, og fem fokus-API-er dekker tastaturnavigasjon
Traversering løkker innenfor én sides tab-rekkefølge, så å krysse til neste side forblir fremviserens jobb
procedure TFormViewer.HandleTabKey(Shift: TShiftState);
begin
  if ssShift in Shift then
    PdfView.FocusPreviousFormField
  else
    PdfView.FocusNextFormField;
  UpdateFieldStatus;  // f.eks. "Felt 4 av 17: InvoiceDate"
end;

Den ene atferden som snubler folk opp, er innpakningen. Navigering fungerer gjennom den gjeldende sidens tab-rekkefølge og løper rundt i den: gå forbi det siste feltet, og du er tilbake ved det første. Begge stegfunksjonene returnerer den nye feltindeksen, eller -1 når siden ikke har noen felt i det hele tatt. Den runddannelsen er per side, ikke per dokument, noe som betyr at å krysse over til neste side er din jobb, ikke bibliotekets. Sammenlign den returnerte indeksen mot den du startet fra, merk deg når den har rundet rundt, og fremskyv PageNumber selv hvis skjemaet er ment å leses som én sammenhengende sekvens. Hopp over den sjekken, og et toskjema-skjema fanger markøren stille på side én, noe som er sin egen variant av klagen om ødelagt Tab

Navigering blir nyttig når resten av UI-et reagerer på den. OnFormFieldEnter-hendelsen avfyres når fokus ankommer, og på fremviseren rapporterer OnFormFieldFocusChange den nye feltindeksen, slik at et sidepanel kan holde tritt med hva tastaturet nettopp valgte. Når du trenger den omvendte kartleggingen, fra en skjermposisjon til et felt, gjør den indekserte egenskapen FormFieldAt treffdeteksjonen for verktøytips-forhåndsvisninger og klikk-for-å-redigere-paneler. Det finnes en stille tilgjengelighetsgevinst i alt dette: fordi fokuset følger dokumentets egen feltrekkefølge, er stien du kobler opp for Tab-tasten, den samme stien en skjermleser kunngjør, uten noe ekstra arbeid

Å vise feltnavn i stedet for rå indeksnumre krever én egenskap til. FormFieldInfo[] returnerer en TPdfFormFieldInfo-post per indeks, som bærer feltnavnet, typen, skriftstørrelsen, den avkryssede tilstanden, eksportverdien og gruppemedlemskap, noe som er det en navigeringsliste bør vise ("Felt 4 av 17: InvoiceDate" fremfor "4"). Radioknapp-grupper er tilfellet som fortjener en dedikert testfil. Flere kontrollelementer kan dele ett enkelt feltnavn, så en liste satt sammen naivt fra kontrollelementer viser den samme gruppen flere ganger og forvirrer alle som leser den

Hvorfor utfylte verdier kommer ut tomme, og kallet som fikser det

Den andre klagen som fyller support-køer, er mer alarmerende enn en Tab-tast som oppfører seg feil: et skjema fylles ut programmatisk, kunden åpner det i Acrobat, og hvert felt ser tomt ut. Klikk inn i et felt, og verdien dukker opp i synsfeltet. Dataene har ligget i filen hele tiden. Det som mangler, er bildet av dataene, og grunnen er verdt å forstå én gang fordi den forklarer en hel familie av feil

Et AcroForm-tekstfelt lagrer verdien sin i /V-oppføringen i feltordboken (ISO 32000-1 §12.7.3.3). Det en fremviser faktisk maler, er noe separat: kontrollelementets visningsstrøm under /AP (§12.5.5), et lite ferdigrendret utdrag av innhold. Skriv /V og la /AP være i fred, og de to driver fra hverandre. Verdien er der; den rendrede versjonen av den er foreldet eller fraværende. Acrobat bygger tilfeldigvis om et felts visning når det får fokus, noe som er hele forklaringen på verdier som bare vises ved klikk. Det gamle NeedAppearances-flagget, som ba fremvisere regenerere visninger for deg, fungerte aldri ensartet og er avviklet i PDF 2.0, og utskriftsservere og miniatyrbilde-generatorer ignorerer det fullstendig. De maler /AP og ingenting annet, så hvis /AP er tomt, skriver de ut en tom boks

Å tilordne en verdi gjennom FormField[i] skriver bare /V. Det er derfor å fylle ut et skjema er en trestegs sekvens, og steget team dropper, er det midterste:

Diagram over /V-verdien mot /AP-utseendedriften i AcroForm-felt, og den tretrinns Delphi fyllsekvensen bygget rundt GenerateFormAppearances
Å tildele verdier skriver bare /V, og mellomsteget er det som maler det utskriftsservere faktisk rendrer
procedure TFormViewer.FillAndSave(const Values: array of WString;
  const OutputPath: string);
var
  i: Integer;
begin
  for i := 0 to Pdf.FormFieldCount - 1 do
    Pdf.FormField[i] := Values[i];   // skriver bare /V

  // Bygg /AP-visningsstrømmene på nytt; uten dette ser skjemaet
  // tomt ut i Acrobat helt til hvert felt klikkes på
  Pdf.GenerateFormAppearances;

  Pdf.SaveAs(OutputPath);
end;

GenerateFormAppearances er hele løsningen. Den bygger om hvert kontrollelements visningsstrøm fra de gjeldende verdiene, skriftene og teksttilpasningen, slik at en fremviser som aldri kjører en fokushendelse, en utskriftsserver eller en miniatyrbildegenerator, likevel maler den utfylte tilstanden. Kall den én gang etter batchen med tilordninger, ikke én gang per felt. Visningsgenerering gjør reelt layoutarbeid, og kall per felt multipliserer det over et stort skjema helt uten grunn

Å regenerere visninger er også øyeblikket hvor skrifter og justering gjør seg gjeldende, noe som er kilden til en overraskelse av annen orden. Den nye strømmen legger hver verdi ut inne i kontrollelementets rektangel ved bruk av feltets skrift, størrelse og teksttilpasning. En verdi som sitter komfortabelt i testskjemaet ditt, kan klippes eller krympes i en kundes kopi hvor det samme feltet er smalere. Autostørrelsesfelt (skriftstørrelse null) krymper teksten for å få den til å passe; faststørrelsesfelt klipper den bare. Begge er lovlige, og den eneste ærlige måten å vite hvilken et gitt skjema gjør, er å se på det regenererte resultatet fremfor strengen du skrev. Når noen rapporterer tekst avkuttet ved kanten av en boks, er dette nesten alltid grunnen

Behandle verifisering som en del av å fullføre arbeidet, ikke en ettertanke. Åpne den lagrede filen i Acrobat og bekreft at verdiene er synlige før du rører noe felt. Skriv den så ut til PDF eller til et bilde fra en annen fremviser, én som ignorerer skjemalogikk helt, og bekreft at verdiene overlever den stien også. Sammen fanger disse to kontrollene hver eneste variant av /V-versus-/AP-driften

Feltkonfigurasjoner som består demoen og feiler i felten

Rene demoskjemaer skjuler et sett med kantede tilfeller kundefiler ikke gjør. Fire av dem står for de fleste "det fungerte på min maskin"-rapportene

  • Eksportverdier for avkrysningsbokser. "På"-tilstanden er ikke alltid Yes. Et skjema står fritt til å definere sin egen eksportverdi, og å skrive feil streng lar boksen forbli visuelt ukrysset mens koden din er overbevist om at den satte den. Les eksportverdien fra FormFieldInfo[] fremfor å anta én
  • Radioknapp-grupper med delt navn. Ett felt, flere kontrollelementer. Verdien du tilordner, avgjør hvilket kontrollelement som leses som valgt, så UI-kode som antar at ett navn kartlegges til ett rektangel, ender opp med å tegne fokusringen på feil knapp
  • Beregnede felt. Totaler vedlikeholdt av dokument-JavaScript oppdateres som svar på felthendelser. En programmatisk utfylling som omgår de hendelsene, må enten utløse omregning eller overskrive de beregnede feltene direkte. Et skjema hvor varelinjene og totalen er uenige, er verre enn begge løsningene
  • Skjulte påkrevde felt. Betingede skjemaer skjuler felt som fortsatt er flagget som påkrevd. Bestem på forhånd om valideringen din respekterer synlighet eller det rå påkrevd-flagget, og skriv så ned den beslutningen et sted support kan finne den

Én distinksjon er verdt å avklare før den biter deg: å generere visninger er ikke flating. GenerateFormAppearances gjør verdier synlige overalt mens feltene forblir redigerbare. Flating baker visningen inn i statisk sideinnhold og fjerner interaktiviteten for godt, noe som er riktig for en arkivkopi og feil for et skjema neste person fortsatt må fylle ut. Hvis FormType rapporterer ftXfaFull fremfor ftAcroForm, gjelder ingenting av redigeringsoverflaten her rent uansett, siden dokumentet rendres fra sin egen XML-mal; oppdag det tilfellet og fortell brukeren, i stedet for å la dem finne begrensningen på egen hånd

Delsystemet for skjemautfylling, fokusnavigering og visningsgenerering vist her, er en del av PDFium Component for Delphi, C++Builder og Lazarus/FPC. Hvis fremviseren din også håndterer gjennomgangslesermarkering ved siden av skjemadata, dekker artikkelen om annotasjonsgjennomgang den tilstøtende modellen