Bir Delphi görüntüleyicideki dinamik bir XFA formu sayfa eklediğinde ya da çıkardığında PDFium Component, yeni toplamı v3.126.1'den beri TPdf.PageCount ve TPdf.OnXfaPageCountChanged üzerinden bildirir; çünkü yerel page event, toplam değil eklenen/çıkarılan bir delta taşır. v3.126.1'deki Windows V8 kütüphaneleri ayrıca yer değiştiren field'larla birlikte giriş vuruş alanlarını da taşır ve v3.126.2, bayat sayfa handle'larını layout callback'i döndükten sonra yeniden yükler. Bunu başlatan hata bildirimi bir masraf beyan formuydu: Add Row'ya iki kez tıklayın, form iki sayfaya büyür ve sayfa göstergesi gururla 1 of 1 okur. 2. sayfaya taşınmış bir field'a yazın ve tuş vuruşları görünmez bir yere düşer. Herkesin ilk önce test ettiği sabit uzunluklu örnek formlarda bunların hiçbiri görünmedi; nedenlerini bilmek, bir form görüntüleyici gömüyorsanız değer
Dinamik bir XFA formu yeniden sayfalandığında ne olur?
Dinamik bir XFA formunun sabit bir sayfa listesi yoktur; dolayısıyla sayfa sayısı layout'un bir çıktısıdır ve kullanıcı veri düzenlediği her seferde değişebilir. XFA 3.3, formu bir subform ağacı olarak tanımlar; yinelenen bir subform bir instanceManager tarafından denetlenir ve _Row.addInstance() gibi bir script bir satır daha klonlar. Layout işlemcisi sonra içeriği sayfa alanlarına yeniden akıtır; bu bir sayfa ekleyebilir, bir sayfa düşürebilir ya da var olan field'ları başka bir sayfaya itebilir. ISO 32000-1 §12.7.8 yalnızca XFA paketlerinin PDF içinde nasıl seyahat ettiğini tanımlar; ondan sonra olan her şey XFA motoruna aittir, PDFium Component'te bu da ana sürecin içinde koşan PDFium'un kendi XFA layout'udur. Bir Delphi görüntüleyici bu yüzden sayfa sayısı, sayfa boyutları ve widget konumları canlı durum olan bir belgeyle uğraşır. Ana süreç aksini varsaydığında üç şey ters gider:
- Ana sürecin navigation, kaydırma aralıkları ve sayfa spinner'ları için cache'lediği sayfa sayısı bayatlar; daha kötüsü yanlış sayıyla güncellenir
- Yer değiştiren field'lar kenarlıklarını yeni konumda gösterirken editor ile fare vuruş alanı eski koordinatlarda kalır
- Görüntüleyici, layout'un yerini aldığı bir sayfa handle'ını tutmaya devam eder; dolayısıyla tıklamalar ve boyamalar o formda artık var olmayan bir sayfaya gider
Satır düzenlemelerini kaydetme ve yeniden açma boyunca kalıcı kılmak kendi kuralları olan ayrı bir sorundur; bu yazı, görüntüleyicinin içinde çalışma zamanında olanlarla kalır
Dinamik XFA hangi PDFium runtime'ına gerek duyar?
PDFium Component'te dinamik XFA, yerel kütüphanenin V8/XFA derlemesini gerektirir; ilk belge yüklenmeden PDFium unit'indeki global EnableV8Engine değişkeniyle seçilir. Süreç, herhangi bir TPdf kütüphaneyi ilk yüklediğinde tek bir DLL'ye bağlanır ve sade bir PDFium derlemesi XFA motorunu hiç çalıştıramaz. Bir belge açıldığında TPdf dosyada XFA işaretlerine göz atar ve V8 derlemesine otomatik geçer; ama yalnızca o süreçte henüz sade bir kütüphane yüklenmemişse. Bağlılık yanlış yöne gitmişse TPdf.OnXfaRuntimeMissing bir kez ateşlenir; ana süreç böylece kullanıcıya yeniden başlatmayı söyleyebilir. Bayrağı başlangıçta açıkça set etmek tahmini ortadan kaldırır. XFA olaylarını taşıyan FPDF_FORMFILLINFO callback yapısının da DLL ile eşleşmesi gerekir; arka planı FPDF_FORMFILLINFO sürüm 2 ve XFA callback ABI'si yazısında, form tiplerini görüntüleyici açmadan ayırt etme ise XFA formlarını saptama ve paketlerini okuma yazısında
uses
PDFium;
procedure TClaimForm.FormCreate(Sender: TObject);
begin
// İlk TPdf yerel kütüphaneyi yüklemeden önce karar verin:
// süreç, pdfium.dll'den pdfium.v8.dll'ye sonra geçemez
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;
PageCount iki sayfalık bir form için neden 1 bildirdi?
v3.126.1 öncesinde PDFium Component, yerel page event'in page_count argümanını belge toplamı diye saklıyordu; o argüman ise aslında yeni ile eski sayfa sayıları arasındaki mutlak farktır. PDFium, bir layout geçişi page eklendi ya da page çıkarıldı olay tipiyle bittiğinde FFI_PageEvent yükseltir; içeride önce sakladığı sayfa sayısını günceller, sonra abs(new - old) geçirir. İlk layout'ta eski sayı sıfırdır; dolayısıyla delta toplama eşittir ve üç sayfalık statik bir örnek, beklendiği gibi üç sayfa bildirir. Sabit uzunluklu test formlarının bu hatayı hiç açığa vurmamasının nedeni tam olarak budur. Dinamik bir formun ilk kez bir sayfadan ikiye büyüdüğü anda delta 1'dir ve wrapper, hem TPdf.PageCount'i hem OnXfaPageCountChanged'in NewCount parametresini 1'e set etti. Üç sayfalık bir formdan satır çıkarmak da benzer saçmalığı öbür yönde üretti
Delta'yı önceki değerin üstüne biriktirmek de güvenli bir tamir değildir. Başlatma ile layout callback'lerinin sırası, wrapper'ın önceki sayısına daima taban gibi güvenememesi demektir; böylece koşan bir toplam sapanabilir. v3.126.1'den beri callback, argümanı sayı olarak yok sayar ve belge üzerinde FPDF_GetPageCount çağırır; o da toplamı, yeni tamamlanmış layout'tan okur. Sonra cache'lenmiş sayfa sahnelerini temizler, o toplamı TPdf.PageCount'in ardındaki XFA sayfa sayısı override'u olarak saklar ve ancak ondan sonra OnXfaPageCountChanged yükseltir. Handler'ınız koştuğunda NewCount ile FPdf.PageCount anlaşmıştır
procedure TClaimForm.PdfXfaPageCountChanged(Sender: TObject; NewCount: Integer);
begin
// v3.126.1+: NewCount tamamlanmış layout'un toplamıdır, asla delta değil.
// Bu, PDFium'un layout callback'i içinde koşar: yalnızca ana süreç UI
// durumunu güncelleyin; belgeyi kapatmayın ya da sayfaları buradan yüklemeyin
UpdatePageRange(NewCount);
end;
procedure TClaimForm.PdfViewPageChange(Sender: TObject);
begin
// Ertelenmiş XFA yenilemesi dâhil her sayfa yeniden yüklemesinden sonra ateşlenir
PageSpin.Value := PdfView1.PageNumber;
end;
procedure TClaimForm.UpdatePageRange(Count: Integer);
begin
PageSpin.MinValue := 1;
PageSpin.MaxValue := Count;
PageLabel.Caption := Format('of %d', [Count]);
end;
Olay yalnızca layout'u çalışma zamanında değişen Full XFA formları için ateşlenir. Static XFA ve AcroForm belgeleri onu hiç yükseltmez; dolayısıyla ikisini de işleyen bir görüntüleyici aynı handler'ı atanmış bırakabilir. Atanmamış bırakmak da güvenlidir; TPdf.PageCount'in ardındaki override her hâlde uygulanır ve olay, ana sürecin cache'lediği her şeyi yenileyebilmesi için vardır
Bir field taşındığında giriş kutusu neden eski sayfada kalır?
Kenarlık taşındı, editor taşınmadı; çünkü yerel XFA notifier'ı bir dikdörtgeni kendisiyle karşılaştırdı. Layout, çoktan yüklenmiş bir widget'ın geometrisini değiştirdiğinde PDFium'un yeni dikdörtgeni fark edip widget üzerinde PerformLayout çağırması gerekir; o da metin editor'ünü ve vuruş alanını yeniden konumlandırır. Kontrol, GetWidgetRect()'i RecacheWidgetRect() ile karşılaştırıyordu. İki fonksiyon da aynı üyeye bir const referans döndürür ve recache o üyeyi yerinde üzerine yazar; dolayısıyla karşılaştırma daima iki özdeş değer görüyor ve yüklenmiş widget'lar relayout'larını atlıyordu
Belirti, bir test subform yüksekliğini değiştirip var olan field'ları sonraki sayfaya taşıdığında su yüzüne çıktı. Her iki V8 mimarisinde de field kenarlığı yeni konumunda çizilirken yazılan metin ile fare vuruş alanı önceki Y koordinatında kalıyordu. Açık bir relayout düzeltmedi; sayfayı yeniden yüklemek de düzeltmedi, çünkü widget geometrisinin güncel olduğuna hâlâ inanıyordu. v3.126.1 ile gelen Windows V8 kütüphaneleri, recache'ten önce eski dikdörtgeni değere göre kopyalar ve o kopyayı karşılaştırır; böylece taşınan widget'lar relayout eder ve düzenlenen değer tam kenarlığın olduğu yerde görünür. Bu yerel bir düzeltmedir: DLL'lerle birlikte seyahat eder; dolayısıyla eski bir pdfium.v8.dll tutup Pascal unit'lerini güncellemek yanlış yerdeki vuruş alanlarını yerinde bırakır. Onu süren regresyon kontrolü, önce hayatta kalan bir satırı varsayılan olmayan bir değere düzenler ve sonra o değeri field'ın yeni konumunda ister; yoksa varsayılan değerlerle yeniden kurulan bir satır geçmiş gibi görünürdü
TPdfView sayfaları, PDFium'un altından handle çekmeden nasıl yeniden yükler?
v3.126.2'den beri TPdfView, bir XFA layout değişikliğini izleyen sayfa yeniden yüklemesini, yerel çağrı yığını açılana dek erteler. Page event çoğunlukla PDFium hâlâ girdiyi işlerken ateşlenir: kullanıcı bir Add Row düğmesine tıkladı, tıklama bir script koşturdu, script instance sayısını değiştirdi ve layout aynı yerel çağrının içinde bitti. O anda sayfa handle'ını kapatıp yeniden açmak, çağıranın hâlâ kullandığı bir object'i serbest bırakırdı. v3.126.2 öncesinde görüntüleyici yalnızca kendini geçersiz kılıyordu; dolayısıyla görüntülenen sayfa handle'ı layout öncesi duruma işaret etmeye devam edebiliyordu ve kullanıcı o sayfa kaybolduğunda son sayfadaysa seçili sayfa numarası aralık dışına düşüyordu
Ertelenmiş yenileme birkaç küçük adımda işler ve bunlar ana süreçten gördüğünüz davranışı açıklar:
- Page-event callback'i, görüntülemeyi bekleyen bir XFA layout yenilemesi taşıyor diye işaretler ve özel bir pencere iletisi postalar; ileti gelmeden önceki yinelenen olaylar tek bir yenilemede birleşir
- Henüz pencere handle'ı olmayan bir görünüm bekleyen bayrağını tutar ve iletiyi
CreateWnd'den postalar; belge değiştirmek, görünümü pasifleştirmek ya da yok etmek ise bayrağı temizler - İleti geldiğinde görünüm, metin seçimini, arama vurgusunu ve odaklı-field indeksini temizler; çünkü üçü de eski layout'u gösteriyordu
- Seçili sayfa yeni
PageCount'a kıskandırılır; değişen bir sayfa numarası normal sayfa geçişinden geçer, aksi hâlde geçerli sayfa yeniden yüklenir ve fit modu yeniden uygulanır - Layout hiç sayfa bırakmadıysa görünüm, artık var olmayan bir sayfayı boyamak yerine eski sayfa handle'ını boşaltır
Aynı kısıt kendi kodunuz için de geçerlidir. OnXfaPageCountChanged o yerel layout callback'inin içinde koşar; onu bir bildirim gibi ele alın: etiketleri, spinner aralıklarını ve araç çubuğu durumunu orada güncelleyin; belgeyi kapatmak ya da başkasını açmak gibi daha ağır her şeyi, callback döndükten sonra koşması için postalanan bir iletiyle kuyruğa alın. TPdfView.OnPageChange sonra, görünüm sayfayı gerçekten yeniden yüklediğinde haber verir ve o anda PdfView1.PageNumber okumak size kıskanılmış değeri verir. Tab tuşuyla gezinme ile form görüntüleyicinin açılışta koşturduğu FormType kontrolleri PDFium Component ile PDF form field navigasyonu yazısında
Bir Full XFA field'ına tıklamak neden "Cannot open text page" yükseltir?
Full XFA sayfalarının PDF text page'i yoktur ve v3.126.2 öncesinde görüntüleyicinin varsayılan metin seçimi ile bağlantı saptaması yine de bir tane yüklemeye çalışıyordu. TPdfView.AllowUserTextSelection varsayılanı True'dayken üzerine gelmek, metin katmanından farenin altındaki bir karakteri soruyordu ve bir mouse-up tıklaması sayfa metni üzerinde otomatik bir URL sondası koşturuyordu. Bir Full XFA sayfasında text page açılamaz; dolayısıyla bir field'a sıradan bir tıklama, Cannot open text page exception'unda bitebiliyordu. v3.126.2'den beri iki iç yol da, TPdf.FormType ftXfaFull olduğunda ve XFA runtime kullanılabilirken sonuç yok döndürür; böylece varsayılan ayarlar çalışır ve field girişi kullanılabilir kalır
Full XFA belgeleri için AllowUserTextSelection'i kapatmak yine de makul bir UI seçimidir; seçilecek sayfa metni yoktur ve sürükleme jestleri bir seçim modu başlatmamalıdır. Ama yükseltmenin yerine geçmez: önceki sürümlerde tıklamadaki URL sondası o property'ye bağlı değildi; dolayısıyla bir görüntüleyici, seçim kapalıyken de aynı exception'a çarpabilirdi
procedure TClaimForm.ConfigureViewerForForm;
begin
// FormType açık belgeyi okur; bunu FPdf.Active := True'dan sonra çağırın
if FPdf.XFA and (FPdf.FormType = ftXfaFull) and FPdf.XfaRuntimeAvailable then
begin
// Full XFA sayfalarında PDF metin katmanı yoktur; field'lar düzenlenebilir kalır
PdfView1.AllowUserTextSelection := False;
StatusBar1.SimpleText := Format('Dynamic XFA form, %d page(s)',
[FPdf.PageCount]);
end
else
PdfView1.AllowUserTextSelection := True;
end;
Yazmak, v3.126.2'de kendi tamirini istedi. Yerel XFA metin editor'ü bir karakter aldığında seçimi değiştirmiyor: FORM_OnChar imlece ekliyor ve Backspace tek karakter siliyor; dolayısıyla bir değer seçip üstüne yazmak eski ile yeni metni yan yana üretiyordu. PDFium Component artık tıklamanın bir XFA metin field'ına düştüğünü hatırlıyor ve seçim var olduğu ve belge form-doldurma ya da değiştirme izni verdiği her anda yazılan karakterleri, Backspace ile Delete'i FORM_ReplaceSelection üzerinden yönlendiriyor. Salt okunur bir XFA field'ının değişip değişemeyeceği yine yerel editor'e kalmıştır; dolayısıyla formda salt okunur işaretli bir field, aksi hâlde doldurmaya izin veren bir belgede bile değerini korur. TPdfView.AllowFormEvents'i False'a set etmek de bu klavye yönlendirmesini durdurur; bu da salt okunur bir görüntüleyiciyi salt okunur tutar
Hızlı başvuru: bir Delphi görüntüleyicide dinamik XFA
| Belirti | Neden | Düzeltildiği sürüm |
|---|---|---|
| Form iki sayfaya büyüdükten sonra sayfa sayısı 1 gösterir | Yerel page event, toplam değil eklenen/çıkarılan bir delta geçirir | v3.126.1 (wrapper) |
| Field kenarlığı taşınır, yazılan metin ile vuruş alanı geride kalır | Yüklenen widget, kendisiyle karşılaştırma sonrası relayout'u atladı | v3.126.1 (Windows V8 kütüphaneleri) |
| Görüntüleyici boyar ya da girdiyi layout öncesi sayfa durumuna yönlendirir | Yeniden sayfalamadan sonra sayfa handle'ı yeniden yüklenmedi | v3.126.2 (ertelenmiş yenileme) |
| Bir field'a tıklamak Cannot open text page yükseltir | Metin katmanı olmayan sayfalarda metin seçimi ve URL sondası | v3.126.2 |
| Seçili bir değerin üstüne yazmak değiştirmek yerine ekler | Yerel XFA editor'ü imlece ekler | v3.126.2 |
- Herhangi bir belge yüklenmeden
EnableV8Engine'iTrue'ya set edin ve sade kütüphanenin önce yüklendiği durum içinOnXfaRuntimeMissing'i işleyin - Toplamı
TPdf.PageCount'ten ya daOnXfaPageCountChanged'inNewCountparametresinden okuyun; sayfa sayılarını asla kendiniz toplamayın ya da çıkarmayın OnXfaPageCountChangedhandler'ını hafif tutun; çünkü yerel layout callback'inin içinde koşar- Geçerli sayfa göstergesini
TPdfView.OnPageChange'de eşleyin; ertelenmiş yeniden yükleme sayfa numarasını kıskandırdıktan sonra ateşlenir - v3.126.1 ve sonrası Windows V8 DLL'lerini unit'lerle birlikte dağıtın; widget relayout düzeltmesi yerel kodda yaşar
- Sayfa sayısını gerçekten değiştiren ve düzenlenmiş bir field'ı sayfa kırılması üzerinden taşıyan bir formla test edin; sabit uzunluklu örnekler bu listedeki her hatayı saklar
Dinamik XFA, sayfa sayısını ve field geometrisini canlı değerlere çevirir; bir görüntüleyici yalnızca onları tamamlanmış layout'tan aldığında ve sayfaları güvenli bir anda yeniden yüklediğinde doğru kalır. PDFium Component ikisini de TPdf ile TPdfView içinde halleder; ana sürecin yapması gereken tek şey dinlemektir. Ayrıntılar ve indirmeler Delphi için PDFium Component ürün sayfasında