Drücken Sie Tab in einem PDF-Formular, das Ihr Code gebaut hat, und der Cursor landet zwei Felder von der richtigen Stelle entfernt, überspringt die zweite Spalte ganz oder springt nach dem dritten Feld statt nach dem vierten zurück an den Anfang. Wer in Ihrem Betrachter eine Rechnung ausfüllt, erwartet, dass die Tastatur das Formular so durchläuft, wie sie jedes je benutzte Webformular durchläuft. Tut sie das nicht, greift die Person zur Maus, sucht das nächste Feld und beschließt stillschweigend, dass Ihr Werkzeug unfertig ist. Vorhersagbare Feldnavigation ist der Unterschied zwischen einem Dateneingabe-Betrachter, den die Leute tolerieren, und einem, dem sie vertrauen, und sie ist fast ausschließlich eine Frage der richtigen Fokus-API statt vorgetäuschter Tastatureingaben über simulierte Klicks
Die folgenden Beispiele verwenden die PDFium Component, eine PDFium-basierte VCL/LCL-Komponente für Delphi, C++Builder und Lazarus. Navigation ist eines von drei Dingen, die ein Formularbetrachter richtig machen muss; die anderen beiden, das Formular korrekt zu öffnen und ausgefüllte Werte so zu speichern, dass sie tatsächlich sichtbar werden, sind die Stellen, an denen sich die meisten Überraschungen verstecken, daher werden unten alle drei behandelt
Ein Formular öffnen: FormFill, FormType und die XFA-Frage
Feldzugriff setzt voraus, dass das Form-Fill-Subsystem, gesteuert über die Eigenschaft FormFill, aktiviert ist, bevor das Dokument geöffnet wird. Ist es aktiv, sagt Ihnen FormType, mit welcher Art Formular Sie es zu tun haben, und die Antwort verändert den Funktionsumfang, den Sie versprechen können:
Pdf.FileName := FormPath;
Pdf.FormFill := True; // vor Active aktivieren; für jeden Feldzugriff erforderlich
Pdf.Active := True;
case Pdf.FormType of
ftNone:
DisableFormPanel('This document has no interactive form');
ftAcroForm:
BuildFieldList; // volle Feldnavigation und Bearbeitung verfügbar
ftXfaFull:
ShowXfaNotice; // XFA rendert aus seinem eigenen XML-Template;
// Feldbearbeitung als eingeschränkt behandeln
end;
Aus dieser Verzweigung folgen zwei praktische Hinweise. AcroForm ist das Standard-Formularmodell nach ISO 32000, und darauf zielt jede API hier ab. XFA-Dokumente betten ihre eigene XML-Formulararchitektur ein, sodass das Versprechen vollständiger XFA-Bearbeitung an einen Kunden nach einer kurzen AcroForm-Demo eine Zusage ist, die Sie bereuen werden. Der zweite Hinweis betrifft Nebenwirkungen: FormFill auf True zu setzen initialisiert auch das Dokument-JavaScript. In einem Dateneingabe-Betrachter ist das genau richtig, denn Berechnungsskripte halten eine laufende Summe aktuell, während jemand tippt. In einem Vorschaufenster für Dateien unbekannter Herkunft ist es genau falsch. Der Artikel zur sicheren PDF-Vorschau behandelt die FormFill := False-Seite dieses Kompromisses
Tab-Navigation, die dort landet, wo Benutzer es erwarten
Zurück zum Tastaturproblem vom Anfang. Die Versuchung ist, Tab vorzutäuschen, indem man einen Mausklick auf das Rechteck des nächsten Widgets synthetisiert, was in dem Moment bricht, in dem ein Feld aus dem sichtbaren Bereich gescrollt ist oder zwei Widgets sich überlappen. Die Fokus-API bewegt stattdessen den formulareigenen Fokus direkt, ohne Geometrie-Rätselraten. Fünf Aufrufe decken das ab: FocusFormField per Index, FocusNextFormField und FocusPreviousFormField zum Weiterschalten, FocusedFormFieldIndex, um abzulesen, wo Sie sind, und ClearFormFieldFocus, um den Fokus ganz aufzugeben
procedure TFormViewer.HandleTabKey(Shift: TShiftState);
begin
if ssShift in Shift then
PdfView.FocusPreviousFormField
else
PdfView.FocusNextFormField;
UpdateFieldStatus; // z. B. "Feld 4 von 17: InvoiceDate"
end;
Das eine Verhalten, über das die Leute stolpern, ist der Umbruch. Die Navigation arbeitet die Tab-Reihenfolge der aktuellen Seite ab und läuft darin im Kreis: Schalten Sie über das letzte Feld hinaus, sind Sie wieder beim ersten. Beide Schaltfunktionen geben den neuen Feldindex zurück, oder -1, wenn die Seite gar keine Felder enthält. Dieses Kreisen gilt pro Seite, nicht pro Dokument, was bedeutet, dass der Wechsel zur nächsten Seite Ihre Aufgabe ist, nicht die der Bibliothek. Vergleichen Sie den zurückgegebenen Index mit dem, von dem Sie gestartet sind, erkennen Sie den Umbruch und erhöhen Sie PageNumber selbst, wenn das Formular als eine durchgehende Sequenz gelesen werden soll. Lassen Sie diese Prüfung weg, hält ein zweiseitiges Formular den Cursor stillschweigend auf Seite eins gefangen, was seine eigene Spielart der Beschwerde über die kaputte Tab-Taste ist
Nützlich wird die Navigation, sobald der Rest der UI darauf reagiert. Das Ereignis OnFormFieldEnter feuert, wenn der Fokus ankommt, und auf dem Viewer meldet OnFormFieldFocusChange den neuen Feldindex, sodass ein Seitenpanel mit dem Schritt halten kann, was die Tastatur gerade ausgewählt hat. Brauchen Sie die umgekehrte Zuordnung, von einer Bildschirmposition zu einem Feld, erledigt die indizierte Eigenschaft FormFieldAt den Treffertest für Tooltip-Vorschauen und Klick-zum-Bearbeiten-Panels. In all dem steckt ein stiller Gewinn für die Barrierefreiheit: Weil der Fokus der dokumenteigenen Feldreihenfolge folgt, ist der Pfad, den Sie für die Tab-Taste verdrahten, derselbe Pfad, den ein Screenreader ansagt, ohne zusätzliche Arbeit
Feldnamen statt roher Indexnummern anzuzeigen braucht eine weitere Eigenschaft. FormFieldInfo[] liefert pro Index einen TPdfFormFieldInfo-Record, der Feldname, Typ, Schriftgröße, Ankreuzzustand, Exportwert und Gruppenzugehörigkeit trägt, also das, was eine Navigationsliste anzeigen sollte („Feld 4 von 17: InvoiceDate“ statt „4“). Radiogruppen sind der Fall, der eine eigene Testdatei verdient. Mehrere Widgets können sich einen einzigen Feldnamen teilen, sodass eine naiv aus Widgets zusammengestellte Liste dieselbe Gruppe mehrfach zeigt und jeden verwirrt, der sie liest
Warum ausgefüllte Werte leer erscheinen und welcher Aufruf das behebt
Die andere Beschwerde, die Support-Warteschlangen füllt, ist alarmierender als eine widerspenstige Tab-Taste: Ein Formular wird programmgesteuert ausgefüllt, der Kunde öffnet es in Acrobat, und jedes Feld sieht leer aus. Klickt man in ein Feld, springt sein Wert ins Bild. Die Daten sind die ganze Zeit in der Datei. Was fehlt, ist das Bild der Daten, und den Grund lohnt es sich einmal zu verstehen, weil er eine ganze Familie von Fehlern erklärt
Ein AcroForm-Textfeld speichert seinen Wert im /V-Eintrag des Feld-Dictionarys (ISO 32000-1 §12.7.3.3). Was ein Betrachter tatsächlich zeichnet, ist etwas Getrenntes: der Appearance-Stream des Widgets unter /AP (§12.5.5), ein kleiner vorgerenderter Inhaltsschnipsel. Schreiben Sie /V und lassen Sie /AP unangetastet, driften die beiden auseinander. Der Wert ist da; seine gerenderte Fassung ist veraltet oder fehlt. Acrobat baut das Erscheinungsbild eines Feldes zufällig neu auf, wenn es den Fokus erhält, und das ist die gesamte Erklärung für Werte, die nur beim Klick erscheinen. Das alte NeedAppearances-Flag, das Betrachter bat, Erscheinungsbilder für Sie neu zu erzeugen, hat nie einheitlich funktioniert, ist in PDF 2.0 veraltet, und Druckserver und Thumbnail-Generatoren ignorieren es vollständig. Sie zeichnen /AP und sonst nichts, und ist /AP leer, drucken sie ein leeres Kästchen
Das Zuweisen eines Werts über FormField[i] schreibt nur /V. Deshalb ist das Ausfüllen eines Formulars eine dreistufige Sequenz, und der Schritt, den Teams weglassen, ist der mittlere:
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]; // schreibt nur /V
// Die /AP-Appearance-Streams neu aufbauen; ohne das wirkt das Formular
// in Acrobat leer, bis jedes Feld angeklickt wird
Pdf.GenerateFormAppearances;
Pdf.SaveAs(OutputPath);
end;
GenerateFormAppearances ist die ganze Abhilfe. Es baut den Appearance-Stream jedes Widgets aus den aktuellen Werten, Schriften und der Ausrichtung (Quadding) neu auf, sodass ein Betrachter, der nie ein Fokusereignis ausführt, ein Druckserver oder ein Thumbnailer, den ausgefüllten Zustand trotzdem zeichnet. Rufen Sie es einmal nach dem Stapel von Zuweisungen auf, nicht einmal pro Feld. Die Erzeugung der Erscheinungsbilder leistet echte Layoutarbeit, und Aufrufe pro Feld vervielfachen das über ein großes Formular hinweg für nichts
Das Neuerzeugen der Erscheinungsbilder ist auch der Moment, in dem sich Schriften und Ausrichtung geltend machen, und das ist die Quelle einer Überraschung zweiter Ordnung. Der neue Stream setzt jeden Wert innerhalb des Widget-Rechtecks mit Schrift, Größe und Quadding des Feldes. Ein Wert, der in Ihrem Testformular bequem sitzt, kann in der Kopie eines Kunden, in der dasselbe Feld schmaler ist, abgeschnitten oder verkleinert werden. Automatisch dimensionierte Felder (Schriftgröße null) schrumpfen den Text passend; Felder fester Größe schneiden ihn einfach ab. Beides ist zulässig, und der einzige ehrliche Weg zu wissen, was ein bestimmtes Formular tut, ist, sich die neu erzeugte Ausgabe anzusehen statt den String, den Sie geschrieben haben. Meldet jemand am Rand eines Kästchens abgeschnittenen Text, ist das fast immer der Grund
Behandeln Sie die Verifikation als Teil des Abschlusses der Arbeit, nicht als Nachgedanken. Öffnen Sie die gespeicherte Datei in Acrobat und bestätigen Sie, dass die Werte sichtbar sind, bevor Sie irgendein Feld berühren. Drucken Sie sie dann aus einem anderen Betrachter, einem, der Formularlogik vollständig ignoriert, als PDF oder als Bild, und bestätigen Sie, dass die Werte auch diesen Weg überstehen. Zusammen fangen diese beiden Prüfungen jede Variante der Drift zwischen /V und /AP ab
Feldkonfigurationen, die die Demo bestehen und im Einsatz scheitern
Saubere Demo-Formulare verbergen eine Reihe von Randfällen, die Kundendateien nicht verbergen. Vier davon machen die meisten Meldungen der Sorte „bei mir hat es funktioniert“ aus
- Exportwerte von Checkboxen. Der „an“-Zustand ist nicht immer
Yes. Einem Formular steht es frei, seinen eigenen Exportwert zu definieren, und wer den falschen String schreibt, lässt das Kästchen optisch unmarkiert, während der Code überzeugt ist, es gesetzt zu haben. Lesen Sie den Exportwert ausFormFieldInfo[], statt einen anzunehmen - Radiogruppen mit gemeinsamem Namen. Ein Feld, mehrere Widgets. Der Wert, den Sie zuweisen, entscheidet, welches Widget als ausgewählt gilt, sodass UI-Code, der annimmt, ein Name entspreche einem Rechteck, den Fokusring am Ende auf den falschen Button zeichnet
- Berechnete Felder. Summen, die vom Dokument-JavaScript gepflegt werden, aktualisieren sich als Reaktion auf Feldereignisse. Ein programmgesteuertes Ausfüllen, das diese Ereignisse umgeht, muss entweder die Neuberechnung auslösen oder die berechneten Felder direkt überschreiben. Ein Formular, in dem Positionen und Summe nicht übereinstimmen, ist schlimmer als jede der beiden Abhilfen
- Verborgene Pflichtfelder. Bedingte Formulare verbergen Felder, die weiterhin als erforderlich markiert sind. Entscheiden Sie vorab, ob Ihre Validierung die Sichtbarkeit oder das rohe Required-Flag berücksichtigt, und halten Sie diese Entscheidung dort fest, wo der Support sie findet
Eine Unterscheidung lohnt es sich zu klären, bevor sie Sie beißt: Erscheinungsbilder zu erzeugen ist kein Flattening. GenerateFormAppearances macht Werte überall sichtbar und lässt die Felder bearbeitbar. Flattening backt das Erscheinungsbild in statischen Seiteninhalt ein und entfernt die Interaktivität endgültig, was für eine Archivkopie richtig und für ein Formular, das die nächste Person noch ausfüllen muss, falsch ist. Meldet FormType ftXfaFull statt ftAcroForm, greift ohnehin nichts von der hier gezeigten Bearbeitungsoberfläche sauber, da das Dokument aus seinem eigenen XML-Template rendert; erkennen Sie diesen Fall und sagen Sie es dem Benutzer, statt ihn die Grenze selbst entdecken zu lassen
Das hier gezeigte Form-Fill-Subsystem, die Fokusnavigation und die Erzeugung der Erscheinungsbilder sind Teil der PDFium Component für Delphi, C++Builder und Lazarus/FPC. Wenn Ihr Betrachter neben Formulardaten auch Reviewer-Markup verarbeitet, behandelt der Artikel zum Annotations-Review dieses benachbarte Modell