Două documente deschise în același timp, același număr de pagină, fiecare în panoul lui derulabil: acesta este miezul unui vizualizator de comparare. PDFium Component îl oferă printr-un model de obiecte direct, în care TPdf deține fișierul, iar TPdfView deține afișarea. Un document, un TPdf, un TPdfView. Vreți trei panouri, aveți trei perechi. Părțile grele nu sunt apelurile de API; sunt aritmetica de aranjare când fereastra se redimensionează și logica de sincronizare a paginilor când decideți ce vizualizare pe cine urmează
Aranjarea formularului
Formularul VCL ține trei containere TScrollBox alăturate, fiecare cu un TPdfView înăuntru, aliniat la alClient ca să umple caseta. Două componente TSplitter stau între casete, ca utilizatorul să poată ajusta lățimile coloanelor la execuție. O bară de instrumente deasupra panourilor poartă butoanele de deschidere, comenzile de zoom și comutatorul între două și trei vizualizări
Modul cu trei vizualizări este un boolean pe care formularul îl urmărește intern. Când se schimbă, recalculați lățimile și afișați sau ascundeți a treia coloană. Cea mai simplă abordare este să goliți toate proprietățile Align, să ascundeți separatoarele, apoi să setați poziții absolute:
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;
// Aplică aceeași valoare (ClientHeight - înălțimea barei) la toate cele trei Height
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;
Setarea lui Align := alNone pe toate cele trei casete înaintea aritmeticii cu întregi împiedică motorul de constrângeri al VCL să se lupte cu atribuirile dumneavoastră. Restabiliți vizibilitatea separatoarelor după poziționare, dacă vreți redimensionare prin tragere în modul cu două vizualizări
Înălțimea fiecărei casete de derulare este aria client minus înălțimea panoului cu bara de instrumente. Pentru că bara este andocată sus, cu alTop, ClientHeight - PanelButtons.Height vă dă spațiul vertical utilizabil. Atribuiți-l tuturor celor trei casete în același apel UpdateLayout, ca să nu existe niciodată un cadru în care o casetă este mai înaltă decât celelalte și provoacă o pâlpâire de aranjare
Deschiderea unui document
Fiecare pereche de panouri are nevoie de propria procedură de deschidere. Tiparul este scurt: dezactivați componenta, setați numele fișierului, activați, apoi verificați Active; dacă a rămas False, cereți o parolă și reîncercați. Rețineți că TPdfView.Active este cel care controlează randarea, dar TPdf.Active este cel care deschide efectiv fișierul; sunt independente. Setarea lui PdfView.Active := True când TPdf-ul legat nu este încă activ este inofensivă, dar nu afișează nimic
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;
// Eșecurile de încărcare sunt tăcute: Active rămâne False în loc să ridice excepție.
if not PdfComponent.Active then
begin
// Cel mai probabil un fișier protejat cu parolă; dă-i utilizatorului o reîncercare.
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;
Verificați întotdeauna PdfComponent.Active după atribuire; un fișier deteriorat sau o parolă greșită fac ca încărcarea să eșueze în tăcere, fără să ridice o excepție pe drumul implicit. Setarea explicită a lui PdfViewComponent.PageNumber := 1 după o deschidere reușită evită un număr de pagină rămas de la documentul anterior
Caseta de mesaj de la final este intenționată: vreți ca fișierele corupte sau nesuportate să iasă imediat la suprafață, nu să fie înghițite ca un panou gol și tăcut. Un utilizator care nu vede nimic nu are cum să știe dacă fișierul s-a încărcat și este pur și simplu gol sau dacă l-a respins componenta. Raportarea eșecului ține eroarea vizibilă
Urmărirea panoului activ
Când utilizatorul dă clic într-un panou, acel panou devine activ. Formularul urmărește un câmp privat FActivePdfView: TPdfView. Feedbackul vizual este o schimbare a culorii de chenar pe TScrollBox-ul care îl conține: setați-o pe clHighlight pentru cel activ și pe clWindow pentru celelalte. Legați asta la fiecare TPdfView.OnClick și la procedura de deschidere, ca focalizarea să urmeze documentul tocmai deschis
Unele operații se aplică tuturor panourilor vizibile, nu doar celui activ. Un boolean FAllViewsMode de pe formular conduce acea ramură. Când este adevărat, schimbările de zoom și navigarea între pagini se răsfrâng asupra fiecărui panou care are un document activ:
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;
Navigare sincronizată între pagini
Navigarea sincronizată este opțională, dar utilă pentru fluxurile de revizuire a documentelor, în care ambele fișiere acoperă același interval de pagini. Logica îi revine unui handler de eveniment care se declanșează după ce utilizatorul navighează într-o vizualizare. Când o vizualizare sursă își schimbă PageNumber, handlerul propagă acel număr celorlalte vizualizări, cu o singură pază: vizualizarea țintă trebuie să aibă cel puțin atâtea pagini, altfel se sare peste ea
Proprietățile PageNumber de pe TPdfView și de pe TPdf sunt independente. TPdf.PageNumber urmărește ce pagină consideră curentă componenta document; TPdfView.PageNumber urmărește ce se afișează pe ecran. Pentru navigare vreți proprietatea vizualizării, nu pe cea a documentului
O casetă de bifat cu o etichetă de tipul „Sync pages” îi dă utilizatorului controlul. Când este debifată, fiecare panou navighează independent, iar handlerul iese imediat. Acea independență este importantă pentru cazurile în care cele două documente au numere de pagini diferite sau în care utilizatorul vrea să găsească pasajul echivalent dintr-o traducere care începe pe altă pagină. Sincronizarea forțată permanent ar face instrumentul mai greu de folosit decât o simplă aranjare cu două ferestre pe ecran
Un lucru de urmărit: setarea programatică a lui PdfView.PageNumber în interiorul handlerului de sincronizare va declanșa ea însăși evenimentul de schimbare pe acea vizualizare. Păziți-vă de recursivitatea infinită cu un indicator boolean pe care îl setați înainte de atribuire și îl ștergeți imediat după. Indicatorul este per formular, nu per vizualizare, pentru că toate cele trei vizualizări împart același handler
Zoom per panou
Fiecare TPdfView poartă propria proprietate Zoom, un Double în procente, unde Zoom := 100 înseamnă dimensiunea reală (100%). Setarea ei suprascrie orice FitMode activ. Pentru un buton de potrivire pe lățime aplicat panoului activ, citiți zoomul de potrivire din PdfView.PageWidthZoom[PdfView.PageNumber] și atribuiți-l. Pentru potrivirea în pagină, folosiți PageZoom[PageNumber]. Amândouă sunt proprietăți de tip tablou indexate după numărul de pagină de la 1, așa că păziți-vă de un număr de pagină zero înainte să le accesați
Când exportați pagina curentă într-o imagine, citiți rotirea din vizualizare, dar apelați RenderPage pe componenta TPdf, nu pe vizualizare. Forma cu imagine bitmap a lui TPdf.RenderPage primește dimensiuni explicite în pixeli, plus o valoare TRotation și un set TRenderOptions. Varianta de tip funcție returnează un TBitmap deținut de apelant, pe care îl eliberați singur după salvare:
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;
Multiplicatorul de 2x pe lățime și înălțime dă un rezultat mai clar pentru documentele cu text fin. Blocul try/finally din jurul eliberării imaginii nu este opțional; o anulare a lui TSaveDialog tot ajunge în blocul finally, iar dumneavoastră vreți ca imaginea să fie eliberată indiferent ce a făcut utilizatorul
Cerințe de DLL
PDFium Component împachetează biblioteca nativă pdfium. Un proces gazdă pe 32 de biți are nevoie de pdfium32.dll; unul pe 64 de biți are nevoie de pdfium64.dll. Variantele cu motorul JavaScript V8 adaugă sufixul v8 și cântăresc cam 23-27 MB, față de compilările standard de 5-6 MB. Pentru un vizualizator de comparare care dezactivează completarea formularelor (Pdf.FormFill := False), compilarea standard, fără V8, este suficientă și menține distribuția mai mică
Puneți DLL-ul în același director cu executabilul sau în orice director aflat pe PATH-ul sistemului. Componenta îl încarcă la cerere, când primul TPdf este activat, așa că un DLL lipsă iese la suprafață în acel moment, nu la pornirea aplicației. Dacă livrați un instalator, cea mai sigură abordare este să copiați DLL-ul în folderul aplicației în timpul instalării, în loc să vă bazați pe un director de sistem pe care un administrator îl poate curăța mai târziu
Compilările cu V8 sunt utile în principal când trebuie să interacționați cu acțiuni JavaScript din PDF, de exemplu pentru a declanșa câmpuri de calcul sau handlere de trimitere. Un vizualizator pasiv de comparare nu are niciun motiv să ruleze JavaScript; setarea lui Pdf.FormFill := False înainte de Active := True sare complet peste mediul de completare a formularelor, ceea ce înseamnă și că niciun motor JS nu este inițializat, chiar dacă se folosește compilarea standard. Aceasta este valoarea implicită corectă pentru un vizualizator doar-citire, indiferent ce variantă de DLL livrați
Pentru mai multe detalii despre PDFium Component și despre API-ul lui complet, vizitați pagina de produs Delphi PDFium Component