Két dokumentum nyílik meg egyszerre, ugyanazon az oldalszámon, mindegyik a maga görgethető paneljében: ez egy összehasonlító megjelenítő lényege. A PDFium Component ezt egyszerű objektummodellen keresztül adja, ahol a TPdf birtokolja a fájlt, a TPdfView pedig a megjelenítést. Egy dokumentum, egy TPdf, egy TPdfView. Ha három panelt akarunk, három párunk lesz. A nehéz részek nem az API-hívások; hanem az elrendezés számtana az ablak átméretezésekor, és az oldal-összehangolás logikája, amikor eldöntjük, melyik nézet kövesse melyiket
Az űrlap elrendezése
A VCL űrlap három TScrollBox tárolót tart egymás mellett, mindegyikben egy TPdfView vezérlővel, amely alClient igazítással kitölti a dobozt. A dobozok között két TSplitter komponens ül, hogy a felhasználó futásidőben állíthassa az oszlopszélességeket. A panelek fölötti eszköztár viszi a megnyitógombokat, a nagyítási vezérlőket és a két- illetve háromnézetes kapcsolót
A háromnézetes mód logikai érték, amelyet az űrlap belül tart nyilván. Amikor átbillen, újraszámoljuk a szélességeket, és megjelenítjük vagy elrejtjük a harmadik oszlopot. A legegyszerűbb megközelítés az, ha töröljük az összes Align tulajdonságot, elrejtjük az elválasztókat, majd abszolút pozíciókat állítunk be:
procedure TFormMain.UpdateLayout;
var
TotalWidth: Integer;
begin
TotalWidth := ClientWidth;
if ThreeViewMode then
begin
ScrollBox3.Visible := True;
ScrollBox1.Left := 0;
ScrollBox1.Width := TotalWidth div 3;
ScrollBox2.Left := ScrollBox1.Width;
ScrollBox2.Width := TotalWidth div 3;
ScrollBox3.Left := ScrollBox2.Left + ScrollBox2.Width;
ScrollBox3.Width := TotalWidth - ScrollBox3.Left;
// Ugyanazt a (ClientHeight - eszköztármagasság) értéket adjuk mindhárom Height tulajdonságnak
end
else
begin
ScrollBox3.Visible := False;
ScrollBox1.Left := 0;
ScrollBox1.Width := TotalWidth div 2;
ScrollBox2.Left := ScrollBox1.Width;
ScrollBox2.Width := TotalWidth - ScrollBox2.Left;
end;
end;
Ha az egészszámos számtan előtt mindhárom dobozon beállítjuk az Align := alNone értéket, azzal elkerüljük, hogy a VCL kényszermotorja az értékadásainkkal küzdjön. A pozicionálás után állítsuk vissza az elválasztók láthatóságát, ha kétnézetes módban húzással átméretezhető oszlopokat szeretnénk
Az egyes görgetődobozok magassága a kliensterület mínusz az eszköztárpanel magassága. Mivel az eszköztár alTop igazítással a tetejére van dokkolva, a ClientHeight - PanelButtons.Height adja a használható függőleges helyet. Ezt ugyanazon az UpdateLayout híváson belül rendeljük mindhárom dobozhoz, hogy soha ne legyen olyan képkocka, amelyben az egyik doboz magasabb a többinél, és elrendezésvillanást okoz
Dokumentum megnyitása
Minden panelpárnak saját megnyitó eljárás kell. A minta rövid: kapcsoljuk ki a komponenst, állítsuk be a fájlnevet, kapcsoljuk be, majd ellenőrizzük az Active tulajdonságot; ha False maradt, kérjünk jelszót, és próbáljuk újra. Jegyezzük meg, hogy a megjelenítést a TPdfView.Active vezérli, a fájlt viszont a TPdf.Active nyitja meg; a kettő független. A PdfView.Active := True beállítása akkor, amikor a hozzá kötött TPdf még nem aktív, ártalmatlan, de semmit nem jelenít meg
procedure TFormMain.OpenPdfFile(PdfComponent: TPdf;
PdfViewComponent: TPdfView);
var
Password: string;
begin
if not OpenDialog.Execute then
Exit;
PdfComponent.Active := False;
PdfComponent.FileName := OpenDialog.FileName;
PdfComponent.Password := '';
PdfComponent.Active := True;
// A betöltési hibák némák: az Active False marad, kivétel nélkül.
if not PdfComponent.Active then
begin
// Nagy valószínűséggel jelszóval védett fájl; adjunk egy újrapróbálkozást.
if InputQuery('Password', 'Enter document password:', Password) then
begin
PdfComponent.Password := Password;
PdfComponent.Active := True;
end;
end;
if not PdfComponent.Active then
begin
ShowMessage('Could not open ' + OpenDialog.FileName +
' (damaged file or wrong password)');
Exit;
end;
PdfViewComponent.PageNumber := 1;
SetActivePdfView(PdfViewComponent);
end;
Az értékadás után mindig ellenőrizzük a PdfComponent.Active tulajdonságot; sérült fájl vagy rossz jelszó esetén a betöltés némán bukik el, az alapértelmezett úton kivétel kiváltása nélkül. Ha sikeres megnyitás után kifejezetten beállítjuk a PdfViewComponent.PageNumber := 1 értéket, azzal elkerüljük, hogy az előző dokumentum elavult oldalszáma maradjon érvényben
A végén álló üzenetablak szándékos: azt akarjuk, hogy a sérült vagy nem támogatott fájlok azonnal felszínre kerüljenek, ne pedig csendes üres panelként nyelődjenek el. Az a felhasználó, aki semmit nem lát, nem tudja, hogy a fájl betöltődött-e és egyszerűen üres, vagy a komponens utasította el. A hiba jelentése láthatóan tartja a problémát
Az aktív panel nyilvántartása
Amikor a felhasználó egy panelen belülre kattint, az a panel válik aktívvá. Az űrlap egy privát FActivePdfView: TPdfView mezőt tart nyilván. A vizuális visszajelzés a befoglaló TScrollBox keretszínének változása: az aktívnál clHighlight, a többinél clWindow. Kössük ezt minden TPdfView.OnClick eseményhez és a megnyitó eljáráshoz is, hogy a fókusz kövesse az imént megnyitott dokumentumot
Néhány művelet nem csak az aktív, hanem minden látható panelre vonatkozik. Ezt az ágat az űrlapon lévő FAllViewsMode logikai érték vezérli. Ha igaz, a nagyítási változások és az oldalléptetés minden olyan panelre kiterjednek, amelyben aktív dokumentum van:
procedure TFormMain.ApplyZoomToAll(NewZoom: Double);
begin
if PdfView1.Active then PdfView1.Zoom := NewZoom;
if PdfView2.Active then PdfView2.Zoom := NewZoom;
if ThreeViewMode and PdfView3.Active then PdfView3.Zoom := NewZoom;
end;
Összehangolt oldalléptetés
Az összehangolt léptetés választható, de hasznos olyan dokumentumverziózási munkafolyamatokban, ahol mindkét fájl ugyanazt az oldaltartományt fedi le. A logika abba az eseménykezelőbe tartozik, amely azután fut le, hogy a felhasználó az egyik nézetben lépett. Amikor egy forrásnézet megváltoztatja a PageNumber értékét, a kezelő továbbadja ezt a számot a többi nézetnek, egyetlen feltétellel: a célnézetnek legalább ennyi oldalt kell tartalmaznia, különben kihagyjuk
A TPdfView és a TPdf PageNumber tulajdonsága független. A TPdf.PageNumber azt tartja nyilván, melyik oldalt tekinti aktuálisnak a dokumentumkomponens; a TPdfView.PageNumber azt, mi látszik a képernyőn. Léptetéshez a nézet tulajdonsága kell, nem a dokumentumé
Egy „Oldalak összehangolása” feliratú jelölőnégyzet adja a felhasználónak az irányítást. Ha nincs bejelölve, minden panel önállóan lépked, és a kezelő azonnal kilép. Ez a függetlenség fontos azoknál a felhasználási eseteknél, ahol a két dokumentum oldalszáma eltér, vagy ahol a felhasználó egy fordításban keresi a megfelelő szakaszt, amely más oldalon kezdődik. Ha mindig kikényszerítenénk az összehangolást, az eszköz nehezebben lenne használható, mint két egyszerű ablak az asztalon
Egy dologra figyeljünk: ha a PdfView.PageNumber értéket programból állítjuk be az összehangoló kezelőn belül, az maga is kiváltja az adott nézet változáseseményét. Védekezzünk a végtelen rekurzió ellen egy logikai jelzővel, amelyet az értékadás előtt beállítunk, és közvetlenül utána törlünk. A jelző űrlaponkénti, nem nézetenkénti, mert mind a három nézet ugyanazon a kezelőn osztozik
Panelenkénti nagyítás
Minden TPdfView saját Zoom tulajdonságot hordoz, egy Double értéket százalékban, ahol a Zoom := 100 a tényleges méretet (100%) jelenti. A beállítása felülír minden aktív FitMode értéket. Az aktív panelen működő szélességhez igazító gombhoz olvassuk ki az igazítási nagyítást a PdfView.PageWidthZoom[PdfView.PageNumber] tulajdonságból, és adjuk értékül. Oldalhoz igazításnál használjuk a PageZoom[PageNumber] tulajdonságot. Mindkettő 1-alapú oldalszámmal indexelt tömbtulajdonság, ezért elérés előtt védekezzünk a nulla oldalszám ellen
Amikor az aktuális oldalt képbe exportáljuk, az elforgatást a nézetből olvassuk ki, a RenderPage hívást viszont a TPdf komponensen adjuk ki, ne a nézeten. A TPdf.RenderPage bitképes alakja kifejezett képpontméreteket, egy TRotation értéket és egy TRenderOptions halmazt vesz át. A függvényváltozat a hívó tulajdonába kerülő TBitmap objektumot ad vissza, amelyet mentés után magunknak kell felszabadítanunk:
procedure TFormMain.SaveActiveViewAsImage;
var
Pdf: TPdf;
Bmp: TBitmap;
Jpeg: TJpegImage;
begin
if not Assigned(FActivePdfView) or not FActivePdfView.Active then
Exit;
Pdf := FActivePdfView.Pdf;
Pdf.PageNumber := FActivePdfView.PageNumber;
Bmp := Pdf.RenderPage(
0, 0,
Round(Pdf.PageWidth * 2),
Round(Pdf.PageHeight * 2),
FActivePdfView.Rotation, [], clWhite);
try
if SavePictureDialog.Execute then
begin
Jpeg := TJpegImage.Create;
try
Jpeg.Assign(Bmp);
Jpeg.CompressionQuality := 90;
Jpeg.SaveToFile(SavePictureDialog.FileName);
finally
Jpeg.Free;
end;
end;
finally
Bmp.Free;
end;
end;
A szélességre és magasságra alkalmazott 2x szorzó élesebb kimenetet ad az apró betűs dokumentumoknál. A bitkép felszabadítása köré tett try/finally nem elhagyható; egy TSaveDialog megszakítása is eljut a finally blokkig, és azt akarjuk, hogy a bitkép felszabaduljon, bármit tett is a felhasználó
DLL-követelmények
A PDFium Component a natív pdfium könyvtárat burkolja. Egy 32 bites gazdafolyamathoz pdfium32.dll kell; egy 64 biteshez pdfium64.dll. A V8 JavaScript-motort tartalmazó változatok a v8 utótagot kapják, és nagyjából 23-27 MB méretűek a szokásos buildek 5-6 MB méretéhez képest. Egy olyan összehasonlító megjelenítőhöz, amely kikapcsolja az űrlapkitöltést (Pdf.FormFill := False), a szokásos, V8 nélküli build elegendő, és kisebben tartja a terjesztést
Tegyük a DLL-t a végrehajtható állománnyal azonos könyvtárba, vagy a rendszer PATH változójában szereplő bármely könyvtárba. A komponens igény szerint tölti be, amikor az első TPdf aktiválódik, ezért a hiányzó DLL ott derül ki, nem az alkalmazás indulásakor. Ha telepítőt szállítunk, a legmegbízhatóbb megoldás az, ha a DLL-t telepítéskor az alkalmazás mappájába másoljuk, ahelyett hogy olyan rendszerkönyvtárra hagyatkoznánk, amelyet egy rendszergazda később kitakaríthat
A V8 buildek elsősorban akkor hasznosak, ha PDF-beli JavaScript-műveletekkel kell kapcsolatba lépnünk, például számított mezők vagy beküldési kezelők kiváltásához. Egy passzív összehasonlító megjelenítőnek nincs oka JavaScriptet futtatni; ha az Active := True előtt beállítjuk a Pdf.FormFill := False értéket, azzal teljesen kihagyjuk az űrlapkitöltési környezetet, ami azt is jelenti, hogy JS-motor akkor sem inicializálódik, ha a szokásos buildet használjuk. Ez a helyes alapértelmezés egy csak olvasható megjelenítőnél, függetlenül attól, melyik DLL-változatot szállítjuk
A PDFium Component eszközről és teljes API-járól további részletekért látogassuk meg a Delphi PDFium Component termékoldalát