Druk op Tab in een PDF-formulier dat uw code bouwde, en de cursor landt twee velden verder dan waar hij hoorde, of slaat de tweede kolom helemaal over, of springt na het derde veld terug naar de top in plaats van naar het vierde. Degene die een factuur invult in uw viewer verwacht dat het toetsenbord het formulier doorloopt zoals het elk webformulier doorloopt dat ze ooit gebruikten. Doet hij dat niet, dan grijpen ze naar de muis, zoeken naar het volgende vakje en besluiten geruisloos dat uw gereedschap onaf is. Voorspelbare veldvolgorde is het verschil tussen een data-invoerviewer die mensen tolereren en een die ze vertrouwen, en het is vrijwel geheel een kwestie van de juiste focus-API gebruiken in plaats van toetsenbordinvoer te faken met gesimuleerde klikken
De voorbeelden hieronder gebruiken PDFium Component, een op PDFium-gebaseerde VCL/LCL-component voor Delphi, C++Builder en Lazarus. Navigatie is één van drie dingen die een formulier-viewer goed moet krijgen; de andere twee, het formulier correct openen en ingevulde waarden opslaan zodat ze werkelijk verschijnen, zijn waar de meeste verrassingen zich verbergen, dus alle drie worden hieronder behandeld
Een formulier openen: FormFill, FormType en de XFA-vraag
Veldtoegang vereist het form-fill-subsysteem, bestuurd door de eigenschap FormFill, om ingeschakeld te zijn voordat het document wordt geopend. Eenmaal actief vertelt FormType u wat voor formulier u tegenover zich hebt, en het antwoord verandert de featureset die u kunt beloven:
Pdf.FileName := FormPath;
Pdf.FormFill := True; // aanzetten vóór Active; verplicht voor elke veldtoegang
Pdf.Active := True;
case Pdf.FormType of
ftNone:
DisableFormPanel('This document has no interactive form');
ftAcroForm:
BuildFieldList; // volledige veldnavigatie en -bewerking beschikbaar
ftXfaFull:
ShowXfaNotice; // XFA rendert vanuit zijn eigen XML-template;
// beschouw veldbewerking als beperkt
end;
Twee praktische opmerkingen volgen uit die switch. AcroForm is het standaard ISO 32000-formuliermodel en het is waar elke API hierop richt. XFA-documenten sluiten hun eigen XML-formulierarchitectuur in, dus een klant volledige XFA-bewerking beloven na een snelle AcroForm-demo is een toezegging waar u spijt van krijgt. De tweede opmerking gaat over bijwerkingen: FormFill op True zetten initialiseert ook document-JavaScript. In een data-invoerviewer is dat precies goed, want berekeningsscripts zijn wat een lopend totaal actueel houden terwijl iemand typt. In een voorbeeldvenster voor bestanden van onbekende herkomst is het precies verkeerd. Het artikel over veilig PDF-voorbeeld behandelt de FormFill := False-kant van die afweging
Tab-toets-volgorde die landt waar gebruikers verwachten
Terug naar het toetsenbordprobleem van boven. De verleiding is om Tab te faken door een muisklik te synthetiseren op de rechthoek van de volgende widget, wat breekt zodra een veld uit het scherm is gescrold of twee widgets overlappen. De focus-API verplaatst de eigen focus van het formulier direct, zonder giswerk met geometrie. Vijf aanroepen dekken het: FocusFormField op index, FocusNextFormField en FocusPreviousFormField voor stappen, FocusedFormFieldIndex om te lezen waar u bent, en ClearFormFieldFocus om focus helemaal te laten vallen
procedure TFormViewer.HandleTabKey(Shift: TShiftState);
begin
if ssShift in Shift then
PdfView.FocusPreviousFormField
else
PdfView.FocusNextFormField;
UpdateFieldStatus; // e.g. "Field 4 of 17: InvoiceDate"
end;
Het ene stuk gedrag dat mensen laat struikelen is de wrap. Volgorde werkt door de tab-volgorde van de huidige pagina en loopt daarbinnen: stap voorbij het laatste veld en u bent terug bij het eerste. Beide stapfuncties retourneren de nieuwe veldindex, of -1 wanneer de pagina helemaal geen velden bevat. Dat lussen is per pagina, niet per document, wat betekent dat oversteken naar de volgende pagina uw taak is, niet die van de bibliotheek. Vergelijk de geretourneerde index met degene waarmee u begon, merk wanneer hij gewrapt is, en schuif PageNumber zelf vooruit als het formulier als één doorlopende reeks moet lezen. Sla die controle over en een formulier van twee pagina's vangt de cursor geruisloos op pagina één, wat zijn eigen variant van de kapotte-Tab-klacht is
Volgorde wordt nuttig zodra de rest van de UI erop reageert. De OnFormFieldEnter-event vuurt wanneer focus aankomt, en op de viewer rapporteert OnFormFieldFocusChange de nieuwe veldindex, zodat een zijpaneel in de pas blijft met wat het toetsenbord zojuist selecteerde. Wanneer u de omgekeerde afbeelding nodig hebt, van een schermpositie naar een veld, doet de geïndexeerde eigenschap FormFieldAt de hit-testing voor tooltip-voorbeelden en klik-om-te-bewerken-panelen. Er zit een stil toegankelijkheidsvoordeel in dit alles: omdat focus de eigen veldvolgorde van het document volgt, is het pad dat u voor de Tab-toets bekabelt hetzelfde pad dat een schermlezer aankondigt, zonder extra werk
Veldnamen tonen in plaats van ruwe indexcijfers kost één extra eigenschap. FormFieldInfo[] retourneert een TPdfFormFieldInfo-record per index, met de veldnaam, het type, de lettergrootte, de aangevinkte staat, de exportwaarde en het groepslidmaatschap, wat een navigatielijst zou moeten tonen ("Field 4 of 17: InvoiceDate" eerder dan "4"). Radiogroepen zijn het geval dat een toegewijd testbestand verdient. Meerdere widgets kunnen één veldnaam delen, dus een lijst naïef uit widgets samengesteld toont dezelfde groep meerdere keren en verwart iedereen die hem leest
Waarom ingevulde waarden leeg uitkomen, en de aanroep die het oplost
De andere klacht die support-wachtrijen vult is verontrustender dan een misdragende Tab-toets: een formulier wordt programmatisch ingevuld, de klant opent het in Acrobat, en elk veld oogt leeg. Klik in een veld en zijn waarde klikt in beeld. De data staat de hele tijd in het bestand. Wat ontbreekt is de afbeelding van de data, en de reden is de moeite waard om één keer te begrijpen want hij verklaart een hele familie van bugs
Een AcroForm-tekstveld slaat zijn waarde op in de /V-entry van het veldwoordenboek (ISO 32000-1 §12.7.3.3). Wat een viewer werkelijk verft is iets afzonderlijks: de appearance-stream van de widget onder /AP (§12.5.5), een klein vooraf gerenderd inhoudsfragmentje. Schrijf /V en laat /AP met rust, en de twee drijven uit elkaar. De waarde is er; de gerenderde versie ervan is verouderd of afwezig. Acrobat toevallig herbouwt de appearance van een veld wanneer het focus krijgt, wat de hele verklaring is voor waarden die alleen bij klik verschijnen. De oude NeedAppearances-vlag, die viewers vroeg om appearances voor u te regenereren, werkte nooit uniform en is in PDF 2.0 afgeschaft, en print-servers en miniatuurgeneratoren negeren hem volledig. Ze verven /AP en niets anders, dus als /AP leeg is printen ze een leeg vakje
Waarden toewijzen via FormField[i] schrijft alleen /V. Daarom is een formulier vullen een reeks van drie stappen, en de stap die teams laten vallen is de middelste:
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]; // writes /V only
// Bouw de /AP appearance streams opnieuw op; zonder dit ziet het
// formulier er in Acrobat leeg uit tot je elk veld aanklikt
Pdf.GenerateFormAppearances;
Pdf.SaveAs(OutputPath);
end;
GenerateFormAppearances is de hele oplossing. Hij herbouwt elke widget z'n appearance-stream vanuit de huidige waarden, fonts en quadding, zodat een viewer die nooit een focus-event draait, een print-server of een miniatuurgenerator, de ingevulde staat toch verft. Roep hem één keer na de reeks toewijzingen, niet één keer per veld. Appearance-generatie doet echt layout-werk, en aanroepen per veld vermenigvuldigen dat over een groot formulier voor niets
Appearances regenereren is ook het moment waarop fonts en uitlijning zich laten gelden, wat de bron is van een verrassing van de tweede orde. De nieuwe stream legt elke waarde uit binnen de widget-rechthoek met het font, de grootte en de quadding van het veld. Een waarde die comfortabel in uw testformulier zit kan afknippen of krimpen in een exemplaar van een klant waar hetzelfde veld smaller is. Velden met automatische grootte (lettergrootte nul) krimpen de tekst om te passen; velden met vaste grootte knippen hem gewoon af. Beide zijn legaal, en de enige eerlijke manier om te weten welke een gegeven formulier doet is kijken naar de geregenereerde uitvoer eerder dan de string die u schreef. Wanneer iemand tekst meldt die aan de rand van een vakje is afgesneden, is dit vrijwel altijd de reden
Behandel verificatie als onderdeel van het afronden van het werk, niet als een bijzaak. Open het opgeslagen bestand in Acrobat en bevestig dat de waarden zichtbaar zijn voordat u een veld aanraakt. Print het dan naar PDF of naar een afbeelding vanuit een andere viewer, een die formulierlogica helemaal negeert, en bevestig dat de waarden dat pad ook overleven. Samen vangen die twee controles elke variant van de /V-tegen-/AP-drift
Veldconfiguraties die de demo passeren en in het veld falen
Schone demo-formulieren verbergen een verzameling edge-cases die klantbestanden niet verbergen. Vier ervan verklaren de meeste "it worked on my machine"-rapporten
- Checkbox-exportwaarden. De "aan"-staat is niet altijd
Yes. Een formulier is vrij om zijn eigen exportwaarde te definiëren, en de verkeerde string schrijven laat het vakje visueel uitgeschakeld terwijl uw code ervan overtuigd is dat hij het instelde. Lees de exportwaarde uitFormFieldInfo[]in plaats van er één aan te nemen - Radiogroepen met gedeelde naam. Eén veld, meerdere widgets. De waarde die u toewijst beslist welke widget als geselecteerd leest, dus UI-code die aanneemt dat één naam naar één rechthoek wijst uiteindelijk de focusring op de verkeerde knop tekent
- Berekende velden. Totalen onderhouden door document-JavaScript werken bij in reactie op veld-events. Een programmatische invulling die die events omzeilt moet óf herberekening triggeren óf de berekende velden direct overschrijven. Een formulier waar de regelitems en het totaal het oneens zijn is erger dan beide fixes
- Verborgen verplichte velden. Conditionele formulieren verbergen velden die nog steeds als verplicht zijn gemarkeerd. Besluit vooraf of uw validatie zichtbaarheid respecteert of de ruwe verplicht-vlag, en schrijf die beslissing ergens op waar support hem kan vinden
Eén onderscheid is de moeite waard om te settelen voordat het u bijt: appearances genereren is niet flattenen. GenerateFormAppearances maakt waarden overal zichtbaar terwijl het de velden bewerkbaar laat. Flattenen bakelt de appearance in statische pagina-inhoud en stroopt de interactiviteit voor altijd, wat juist is voor een archiefexemplaar en verkeerd voor een formulier dat de volgende persoon nog moet invullen. Als FormType ftXfaFull rapporteert eerder dan ftAcroForm, is geen van het bewerkingsoppervlak hier sowieso schoon van toepassing, aangezien het document vanuit zijn eigen XML-sjabloon rendert; detecteer dat geval en vertel het de gebruiker, in plaats van hen de limiet zelf te laten vinden
Het form-fill-subsysteem, focusvolgorde en appearance-generatie die hier worden getoond zijn onderdeel van PDFium Component voor Delphi, C++Builder en Lazarus/FPC. Als uw viewer ook review-markup naast formulierdata afhandelt, behandelt het artikel over annotatie-review dat aangrenzende model