לחץ Tab בטופס PDF שהקוד שלך בנה, והסמן נוחת שני שדות הרחק ממקום שהיה אמור להיות, או מדלג לחלוטין על העמודה השנייה, או קופץ חזרה לראש לאחר השדה השלישי במקום הרביעי. האדם שממלא חשבונית במציג שלך מצפה שהמקלדת תנווט את הטופס כפי שהיא מנווטת כל טופס אינטרנט שהם אי פעם השתמשו בו. כשזה לא קורה, הם מגיעים לעכבר, צדים את התיבה הבאה, ובשקט מחליטים שהכלי שלך לא גמור. מעבר שדות צפוי הוא ההבדל בין מציג הזנת נתונים שאנשים סובלים לבין כזה שאנשים סומכים עליו, והוא כמעט לחלוטין עניין של שימוש ב-API הנכון לפוקוס במקום לזייף קלט מקלדת עם לחיצות מדומות
הדוגמאות שלהלן משתמשות ב-PDFium Component, רכיב VCL/LCL מבוסס PDFium עבור Delphi, C++Builder ו-Lazarus. ניווט הוא אחד משלושה דברים שמציג טופס חייב לקבל נכון; השניים האחרים, פתיחת הטופס כהלכה ושמירת ערכים ממולאים כך שיופיעו בפועל, הם המקום שבו רוב ההפתעות מסתתרות, לכן כל שלושתם מכוסים להלן
פתיחת טופס: FormFill, FormType ושאלת XFA
גישה לשדות מחייבת שמערכת ה-form-fill, הנשלטת על ידי מאפיין FormFill, תהיה מופעלת לפני פתיחת המסמך. לאחר ההפעלה, FormType אומר לך באיזה סוג טופס אתה מתמודד, והתשובה משנה את קבוצת התכונות שאתה יכול להבטיח:
Pdf.FileName := FormPath;
Pdf.FormFill := True; // enable before Active; required for any field access
Pdf.Active := True;
case Pdf.FormType of
ftNone:
DisableFormPanel('This document has no interactive form');
ftAcroForm:
BuildFieldList; // full field navigation and editing available
ftXfaFull:
ShowXfaNotice; // XFA renders from its own XML template;
// treat field editing as limited
end;
שתי הערות מעשיות נובעות מה-switch הזה. AcroForm הוא מודל הטופס הסטנדרטי של ISO 32000, וזה מה שכל ממשק ה-API כאן מכוון אליו. מסמכי XFA מטמיעים ארכיטקטורת טופס XML משלהם, כך שהבטחת עריכת XFA מלאה ללקוח לאחר הדגמה מהירה של AcroForm היא התחייבות שתתחרט עליה. ההערה השנייה עוסקת בתופעות לוואי: הגדרת FormFill ל-True גם מאתחלת JavaScript במסמך. במציג הזנת נתונים זה בדיוק נכון, כי סקריפטי חישוב הם מה ששומר על סך עדכני בזמן שמישהו מקליד. בחלון תצוגה מקדימה לקבצים ממקור לא ידוע זה בדיוק לא נכון. מאמר תצוגה מקדימה מאובטחת של PDF מכסה את הצד FormFill := False של הפשרה הזו
מעבר Tab שנוחת היכן שהמשתמשים מצפים
חזרה לבעיית המקלדת מההתחלה. הפיתוי הוא לזייף Tab על ידי סינתוז לחיצת עכבר על מלבן הווידג'ט הבא, שנשבר ברגע שהשדה גלול מחוץ למסך או ששני וידג'טים חופפים. ממשק ה-API של פוקוס מעביר את הפוקוס של הטופס עצמו ישירות במקום זאת, ללא ניחוש גאומטרי. חמש קריאות מכסות: FocusFormField לפי אינדקס, FocusNextFormField ו-FocusPreviousFormField לצעידה, FocusedFormFieldIndex לקריית המיקום הנוכחי, ו-ClearFormFieldFocus לשחרור הפוקוס לחלוטין
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;
החלק אחד של ההתנהגות שמבלבל אנשים הוא ה-wrap. המעבר פועל דרך סדר ה-tab של הדף הנוכחי וחוזר בתוכו: צעד מעבר לשדה האחרון ואתה חזור בראשון. שתי פונקציות הצעידה מחזירות את אינדקס השדה החדש, או -1 כשבדף אין שדות כלל. הלולאה הזו היא לכל דף, לא לכל המסמך, מה שאומר שמעבר לדף הבא הוא העבודה שלך, לא של הספרייה. השווה את האינדקס המוחזר לזה שהתחלת ממנו, שים לב כשהוא עשה wrap, ועדכן את PageNumber בעצמך אם הטופס אמור להיקרא כרצף רציף אחד. דלג על הבדיקה הזו וטופס של שני דפים בשקט כולא את הסמן בדף ראשון, שזה טעם משלו של תלונת ה-Tab השבור
המעבר הופך שימושי ברגע שממשק המשתמש הנותר מגיב אליו. האירוע OnFormFieldEnter מופעל כשהפוקוס מגיע, ובמציג OnFormFieldFocusChange מדווח על אינדקס השדה החדש, כדי שפאנל צדדי יוכל להישאר בסנכרון עם כל מה שהמקלדת רק בחרה. כשאתה צריך את המיפוי ההפוך, ממיקום מסך לשדה, מאפיין FormFieldAt המאונדקס עושה את בדיקת הפגיעה עבור תצוגות tooltip ופאנלי לחיצה-לעריכה. יש תשלום נגישות שקט בכל זה: מכיוון שהפוקוס עוקב אחרי סדר השדות של המסמך עצמו, הנתיב שאתה מחווט למקש Tab הוא אותו הנתיב שקורא מסך מכריז, ללא עבודה נוספת
הצגת שמות שדות במקום מספרי אינדקס גולמיים לוקחת מאפיין נוסף. FormFieldInfo[] מחזיר רשומה TPdfFormFieldInfo לכל אינדקס, הנושאת את שם השדה, הסוג, גודל הגופן, מצב הסימון, ערך הייצוא וחברות הקבוצה, שזה מה שרשימת ניווט אמורה להציג ("שדה 4 מתוך 17: InvoiceDate" ולא "4"). קבוצות רדיו הן המקרה הראוי לקובץ בדיקה ייעודי. מספר וידג'טים יכולים לחלוק שם שדה יחיד, כך שרשימה שנבנית בתמימות מוידג'טים מציגה את אותה קבוצה מספר פעמים ומבלבלת את כולם שקוראים אותה
מדוע ערכים ממולאים יוצאים ריקים, והקריאה שמתקנת
התלונה האחרת שממלאת תורי תמיכה מבהילה יותר ממקש Tab שמתנהג רע: טופס מתמולא באופן תכנותי, הלקוח פותח אותו ב-Acrobat, וכל שדה נראה ריק. לחץ לתוך שדה וערכו מופיע לפתע. הנתונים בקובץ כל הזמן. מה חסר הוא התמונה של הנתונים, והסיבה שווה להבין פעם אחת כי היא מסבירה משפחה שלמה של באגים
שדה טקסט של AcroForm מאחסן את ערכו בערך /V של מילון השדה (ISO 32000-1 §12.7.3.3). מה שמציג אכן מצייר הוא דבר נפרד: זרם המראה של הווידג'ט תחת /AP (§12.5.5), קטע קטן של תוכן מוגדר מראש. כתוב /V והשאר את /AP לבד, ושניהם מתרחקים זה מזה. הערך שם; גרסת העיבוד שלו מיושנת או נעדרת. Acrobat בונה מחדש את מראה השדה כשהוא מקבל פוקוס, שזה ההסבר המלא לערכים שמופיעים רק בלחיצה. הדגל הישן NeedAppearances, שביקש ממציגים לייצר מחדש מראות בשבילך, מעולם לא עבד באופן אחיד והוצא משימוש ב-PDF 2.0, ושרתי הדפסה ומחוללי תמונות ממוזערות מתעלמים ממנו לחלוטין. הם מציירים /AP ולא יותר מזה, כך שאם /AP ריק הם מדפיסים תיבה ריקה
הקצאת ערך דרך FormField[i] כותבת /V בלבד. לכן מילוי טופס הוא רצף של שלושה שלבים, והשלב שצוותים מוותרים עליו הוא האמצעי:
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
// Rebuild the /AP appearance streams; without this the form
// looks blank in Acrobat until each field is clicked
Pdf.GenerateFormAppearances;
Pdf.SaveAs(OutputPath);
end;
GenerateFormAppearances הוא התיקון המלא. הוא בונה מחדש את זרם המראה של כל וידג'ט מהערכים, הגופנים והיישור הנוכחיים, כדי שמציג שלעולם לא מריץ אירוע פוקוס, שרת הדפסה או ממצמצן, יצייר את המצב הממולא ממילא. קרא לו פעם אחת לאחר קבוצת ההקצאות, לא פעם אחת לכל שדה. יצירת מראה עושה עבודת פריסה אמיתית, וקריאות לכל שדה מכפילות אותה על פני טופס גדול ללא שום תועלת
יצירת מחדש של מראות היא גם הרגע שגופנים ויישור מביעים את עצמם, שהוא מקור הפתעה מסדר שני. הזרם החדש מפרס כל ערך בתוך מלבן הווידג'ט תוך שימוש בגופן, גודל ויישור של השדה. ערך שיושב בנוחות בטופס הבדיקה שלך עשוי לחתוך או להתכווץ בעותק של לקוח שבו אותו שדה צר יותר. שדות בגודל אוטומטי (גודל גופן אפס) מכווצים את הטקסט להתאמה; שדות בגודל קבוע פשוט חותכים אותו. שניהם חוקיים, והדרך הכנה היחידה לדעת מה טופס נתון עושה היא להסתכל על הפלט שנוצר מחדש ולא על המחרוזת שכתבת. כשמישהו מדווח על טקסט שחתוך בקצה התיבה, זו כמעט תמיד הסיבה
התייחס לאימות כחלק מסיום העבודה, לא כמחשבה שלאחר מעשה. פתח את הקובץ השמור ב-Acrobat ואשר שהערכים גלויים לפני שתגע בשדה כלשהו. ואז הדפס אותו ל-PDF או לתמונה ממציג אחר, כזה שמתעלם לחלוטין מלוגיקת טופס, ואשר שהערכים שורדים את הנתיב הזה גם כן. ביניהם, שתי הבדיקות הללו תופסות כל וריאנט של סחף /V מול /AP
תצורות שדה שעוברות את ההדגמה ונכשלות בשטח
טפסי הדגמה נקיים מסתירים קבוצה של מקרי קצה שקבצי לקוחות לא מסתירים. ארבעה מהם מסבירים את רוב הדיווחים "עבד אצלי"
- ערכי ייצוא של תיבת סימון. המצב "on" הוא לא תמיד
Yes. טופס חופשי להגדיר ערך ייצוא משלו, וכתיבת המחרוזת הלא נכונה משאירה את התיבה לא מסומנת חזותית בזמן שהקוד שלך בטוח שהגדיר אותה. קרא את ערך הייצוא מ-FormFieldInfo[]במקום להניח אחד - קבוצות רדיו עם שם משותף. שדה אחד, מספר וידג'טים. הערך שאתה מקצה מחליט איזה וידג'ט נקרא כנבחר, כך שקוד ממשק משתמש שמניח שם אחד ממפה למלבן אחד מסיים בציור טבעת הפוקוס על הכפתור הלא נכון
- שדות מחושבים. סיכומים שנשמרים על ידי JavaScript של המסמך מתעדכנים בתגובה לאירועי שדה. מילוי תכנותי שעוקף אירועים אלה חייב לטרגר מחדש חישוב או לדרוס את השדות המחושבים ישירות. טופס שבו פריטי השורות והסיכום לא מסכימים גרוע יותר מכל תיקון
- שדות חובה מוסתרים. טפסים מותנים מסתירים שדות שעדיין מסומנים כחובה. החלט מראש אם האימות שלך מכבד את הנראות או את דגל חובה הגולמי, ואז כתוב את ההחלטה הזו איפשהו שהתמיכה יכולה למצוא
הבחנה אחת שווה להסדיר לפני שתנשוך: יצירת מראות אינה שיטוח. GenerateFormAppearances הופך ערכים גלויים בכל מקום תוך השארת השדות ניתנים לעריכה. שיטוח אופה את המראה לתוכן דף סטטי ומפשיט את האינטראקטיביות לצמיתות, שזה נכון לעותק ארכיוני ולא נכון לטופס שהאדם הבא עדיין צריך למלא. אם FormType מדווח ftXfaFull במקום ftAcroForm, אף אחד ממשטח העריכה כאן לא חל בצורה נקייה ממילא, כי המסמך מעבד מתבנית ה-XML שלו; זהה את המקרה הזה ואמור למשתמש, במקום לתת לו למצוא את הגבול בעצמו
מערכת ה-form-fill, מעבר פוקוס ויצירת מראות המוצגים כאן הם חלק מ-PDFium Component עבור Delphi, C++Builder ו-Lazarus/FPC. אם המציג שלך גם מטפל בסימוני סוקר לצד נתוני טופס, מאמר סקירת ההערות מכסה את המודל הסמוך הזה