Twee documenten tegelijk geopend, op hetzelfde paginanummer, elk in een eigen schuifbaar paneel: dat is de kern van een vergelijkingsviewer. De PDFium-component levert dit via een eenvoudig objectmodel waarbij TPdf de eigenaar is van het bestand en TPdfView de eigenaar van de weergave. Één document, één TPdf, één TPdfView. Wilt u drie panelen, dan heeft u drie paren. Het moeilijke deel ligt niet bij de API-aanroepen; het zit in het rekenwerk voor de lay-out wanneer het venster van formaat verandert en in de logica voor paginasynchronisatie wanneer u beslist welke weergave de andere moet volgen
Formulierlay-out
Het VCL-formulier bevat drie TScrollBox containers naast elkaar, elk met een TPdfView erin, uitgelijnd op alClient zodat deze de box vult. Twee TSplitter-componenten bevinden zich tussen de boxen zodat de gebruiker de kolombreedtes tijdens runtime kan aanpassen. Een werkbalk boven de panelen bevat de knoppen voor openen, zoomregelaars, en de schakelaar voor de twee-/drie-weergaven-modus
De drie-weergaven-modus is een boolean die het formulier intern bijhoudt. Wanneer deze omslaat, berekent u de breedtes opnieuw en toont of verbergt u de derde kolom. De eenvoudigste benadering is om alle Align-eigenschappen te wissen, de splitters te verbergen en vervolgens absolute posities in te stellen:
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;
// Apply the same (ClientHeight - toolbar height) to all three Height values
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;
Het instellen van Align := alNone op alle drie de boxen voorafgaand aan het berekenen met gehele getallen (integer arithmetic) voorkomt dat de constraint-engine van de VCL botst met uw toewijzingen. Herstel de zichtbaarheid van de splitter na het positioneren als u wilt kunnen slepen om het formaat aan te passen (drag-to-resize) in de twee-weergaven-modus
De hoogte van elke scrollbox is het clientgebied minus de hoogte van het werkbalkpaneel. Omdat de werkbalk aan de bovenkant is vastgezet (docked) met alTop, geeft ClientHeight - PanelButtons.Height u de bruikbare verticale ruimte. Wijs dit toe aan alle drie de boxen binnen dezelfde aanroep van UpdateLayout, zodat er nooit een frame is waarbij de ene box hoger is dan de andere en een flikkering in de lay-out veroorzaakt
Een document openen
Elk panelenpaar heeft zijn eigen open-procedure nodig. Het patroon is kort: deactiveer de component, stel de bestandsnaam in, activeer en controleer vervolgens Active; als het op False bleef staan, vraag dan om een wachtwoord en probeer het opnieuw. Merk op dat TPdfView.Active bepaalt wat er gerenderd wordt, maar TPdf.Active is wat daadwerkelijk het bestand opent; ze zijn onafhankelijk van elkaar. Het instellen van PdfView.Active := True wanneer de gekoppelde TPdf nog niet actief is, is onschadelijk maar toont niets
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;
// Load failures are silent: Active stays False instead of raising.
if not PdfComponent.Active then
begin
// Most likely a password-protected file; give the user one retry.
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;
Controleer altijd PdfComponent.Active na de toewijzing; een beschadigd bestand of een verkeerd wachtwoord zorgt ervoor dat het laden stilletjes mislukt zonder een uitzondering op te werpen in het standaardpad. Het expliciet instellen van PdfViewComponent.PageNumber := 1 na een succesvolle opening voorkomt een verouderd paginanummer van het vorige document
De berichtdialoog aan het einde is opzettelijk: u wilt dat corrupte of niet-ondersteunde bestanden onmiddellijk aan de oppervlakte komen in plaats van te worden ingeslikt als een stilstaand leeg paneel. Een gebruiker die niets ziet, heeft geen idee of het bestand wel is geladen en simpelweg leeg is, of dat de component het heeft geweigerd. Het melden van de fout houdt de fout zichtbaar
Bijhouden van het actieve paneel
Wanneer de gebruiker binnen een paneel klikt, wordt dat paneel actief. Het formulier houdt een privéveld FActivePdfView: TPdfView bij. De visuele feedback is een verandering van de randkleur op de bevattende TScrollBox: stel deze in op clHighlight voor de actieve en clWindow voor de overige. Koppel dit aan elke TPdfView.OnClick en aan de open-procedure zodat de focus het document volgt dat u zojuist heeft geopend
Sommige bewerkingen zijn van toepassing op alle zichtbare panelen in plaats van alleen op de actieve. Een boolean FAllViewsMode op het formulier stuurt die vertakking aan. Als het true is, waaieren zoomwijzigingen en paginanavigatie uit naar elk paneel met een actief document:
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;
Gesynchroniseerde paginanavigatie
Gesynchroniseerde navigatie is optioneel, maar nuttig voor workflows bij documentrevisie waarbij beide bestanden hetzelfde paginabereik beslaan. De logica hoort thuis in een eventhandler die afvuurt nadat de gebruiker in één weergave heeft genavigeerd. Wanneer een bronweergave zijn PageNumber verandert, propageert de handler dat nummer naar de andere weergaven, behoudens één bewaker: de doelweergave moet minstens zoveel pagina's hebben, anders moet worden overgeslagen
Het PageNumber op TPdfView en op TPdf zijn onafhankelijk. TPdf.PageNumber houdt bij welke pagina de documentcomponent als huidig beschouwt; TPdfView.PageNumber houdt bij wat er op het scherm wordt weergegeven. Voor navigatiedoeleinden wilt u de weergave-eigenschap gebruiken, niet de documenteigenschap
Een selectievakje met een label als "Pagina's synchroniseren" geeft de gebruiker de controle. Als het is uitgevinkt, navigeert elk paneel onafhankelijk en wordt de handler onmiddellijk afgesloten. Die onafhankelijkheid is belangrijk voor gebruiksscenario's waarbij de twee documenten verschillende pagina-aantallen hebben, of waarbij de gebruiker de equivalente passage wil vinden in een vertaling die op een andere pagina begint. Als u de synchronisatie altijd forceert, zou de tool lastiger te gebruiken zijn dan een eenvoudige opstelling met twee vensters op het bureaublad
Eén ding om in de gaten te houden: het programmatisch instellen van PdfView.PageNumber binnen de synchronisatie-handler zal zelf de change event in die weergave triggeren. Bescherm uzelf tegen oneindige recursie met een boolean-vlag die u voorafgaand aan de toewijzing instelt en direct erna weer wist. De vlag is per-formulier, niet per-weergave, omdat alle drie de weergaven dezelfde handler delen
Zoom per paneel
Elke TPdfView draagt zijn eigen Zoom-eigenschap mee, een Double in procenten waarbij Zoom := 100 ware grootte (100%) betekent. Het instellen ervan heft elke actieve FitMode op. Voor een aan-breedte-aanpassen-knop (fit-to-width) op het actieve paneel leest u de inpassingszoom (fit zoom) uit PdfView.PageWidthZoom[PdfView.PageNumber] en wijst u deze toe. Voor aan-pagina-aanpassen (fit-to-page) gebruikt u PageZoom[PageNumber]. Beide zijn array-eigenschappen die zijn geïndexeerd met een op 1 gebaseerd paginanummer, dus zorg dat u wegblijft van een paginanummer nul voordat u ze raadpleegt
Wanneer u de huidige pagina naar een afbeelding exporteert, leest u de rotatie uit de weergave, maar roept u RenderPage aan op de TPdf-component, niet de weergave. De bitmap-vorm van TPdf.RenderPage neemt expliciete pixeldimensies aan plus een TRotation-waarde en een TRenderOptions-set. De functievariant retourneert een TBitmap, eigendom van de aanroeper, die u na het opslaan zelf vrijgeeft:
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;
De 2x vermenigvuldiger op breedte en hoogte levert scherpere uitvoer op voor documenten met fijne tekst. De try/finally rond de vrije bitmap is niet optioneel; als TSaveDialog wordt geannuleerd, wordt het finally-blok alsnog geraakt en u wilt dat de bitmap wordt vrijgegeven, ongeacht wat de gebruiker heeft gedaan
DLL-vereisten
De PDFium-component vormt een omhulsel rond de native pdfium-bibliotheek. Een 32-bits hostproces heeft pdfium32.dll nodig; een 64-bits host heeft pdfium64.dll nodig. Varianten met de V8 JavaScript-engine voegen het achtervoegsel v8 toe en wegen ruwweg 23-27 MB tegenover de 5-6 MB van standaard builds. Voor een vergelijkingsviewer die het invullen van formulieren uitschakelt (Pdf.FormFill := False), is de standaard non-V8-build voldoende en houdt het de distributie kleiner
Plaats het DLL-bestand in dezelfde map als het uitvoerbare bestand, of in een willekeurige map op de systeem-PATH. De component laadt het on-demand wanneer de eerste TPdf wordt geactiveerd, dus een ontbrekend DLL-bestand komt pas op dat moment aan de oppervlakte en niet al bij het opstarten van de applicatie. Als u een installatieprogramma meelevert, is de meest betrouwbare benadering om het DLL-bestand tijdens de installatie naar de applicatiemap te kopiëren in plaats van te vertrouwen op een systeemmap die door een beheerder later misschien wordt opgeruimd
De V8-builds zijn voornamelijk bruikbaar wanneer u moet interageren met PDF JavaScript-acties, bijvoorbeeld om berekeningsvelden of indieningshandlers te activeren. Een passieve vergelijkingsviewer heeft geen reden om JavaScript uit te voeren; door Pdf.FormFill := False in te stellen vóór Active := True wordt de form-fill-omgeving volledig overgeslagen, wat ook betekent dat er geen JS-engine wordt geïnitialiseerd, zelfs niet als de standaardbuild wordt gebruikt. Dat is de juiste standaardinstelling voor een read-only viewer, ongeacht de DLL-variant die u meelevert
Ga voor verdere details over de PDFium Component en zijn volledige API naar de productpagina van de Delphi PDFium Component