To dokumenter åbne på én gang, samme sidetal, hver i sit eget rulbare (scrollable) panel: det er kernen i en sammenligningsfremviser (comparison viewer). PDFium-komponenten leverer dette gennem en ligetil objektmodel, hvor TPdf ejer filen, og TPdfView ejer skærmen (the display). Ét dokument, én TPdf, én TPdfView. Vil du have tre paneler, har du tre par. De svære dele er ikke API-kaldene; de er layout-aritmetikken, når vinduet ændrer størrelse (resizes), og side-synkroniseringslogikken (the page-sync logic), når du beslutter, hvilken visning der skal følge hvilken
Formularlayout (Form Layout)
VCL-formularen (The VCL form) rummer tre TScrollBox beholdere side om side, hver med en TPdfView indeni og justeret til alClient, så den fylder kassen. To TSplitter komponenter sidder mellem kasserne, så brugeren kan justere kolonnebredder under kørsel (at runtime). En værktøjslinje (toolbar) over panelerne bærer åbn-knapperne, zoom-kontroller og to-visning / tre-visning skifteknappen (toggle)
Tre-visningstilstand (Three-view mode) er en boolean, formularen sporer internt. Når den skifter (flips), genberegner du bredder og viser eller skjuler den tredje kolonne. Den simpleste tilgang er at rydde (clear) alle Align egenskaber, skjule splitterne, og derefter indstille absolutte positioner:
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;
At sætte Align := alNone på alle tre kasser før heltalsaritmetikken undgår, at VCL's begrænsningsmotor (constraint engine) kæmper imod dine tildelinger (assignments). Gendan splitter-synlighed (splitter visibility) efter positionering, hvis du vil have træk-for-at-ændre-størrelse (drag-to-resize) i to-visningstilstand
Højden på hver rullekasse (scroll box) er klientområdet (the client area) minus værktøjslinjepanelets (the toolbar panel) højde. Fordi værktøjslinjen er docket (docked) øverst med alTop, giver ClientHeight - PanelButtons.Height dig den brugbare vertikale plads. Tildel dette til alle tre kasser inden for det samme UpdateLayout kald, så der aldrig er en frame (ramme), hvor én kasse er højere end de andre og forårsager et layout-flimmer (layout flicker)
Åbning af et dokument
Hvert panel-par behøver sin egen åbne-procedure. Mønsteret er kort: deaktiver komponenten, indstil filnavnet, aktiver, og tjek derefter Active; hvis den forblev False, bed om en adgangskode (prompt for a password) og prøv igen. Bemærk at TPdfView.Active er det, der styrer renderingen, men TPdf.Active er det, der faktisk åbner filen; de er uafhængige. At sætte PdfView.Active := True, når dens linkede TPdf endnu ikke er aktiv, er harmløst, men viser intet
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;
Tjek altid PdfComponent.Active efter tildelingen; en beskadiget fil eller forkert adgangskode får indlæsningen til at fejle i stilhed uden at kaste en undtagelse i standardstien (the default path). At sætte PdfViewComponent.PageNumber := 1 eksplicit efter en succesfuld åbning undgår et forældet (stale) sidetal fra det forrige dokument
Beskeddialogen (The message dialog) i slutningen er bevidst: du ønsker, at korrupte eller ikke-understøttede filer skal bringes frem umiddelbart i stedet for at blive opslugt som et stille tomt panel. En bruger, der ikke ser noget, har ingen anelse om, hvorvidt filen blev indlæst og simpelthen er tom, eller om komponenten afviste den. At rapportere fejlen holder fejlen synlig
Sporing af aktivt panel (Active Panel Tracking)
Når brugeren klikker inde i et panel, bliver det panel aktivt. Formularen sporer et privat FActivePdfView: TPdfView felt. Visuel feedback er en rammefarveændring (border color change) på den indeholdende TScrollBox: sæt den til clHighlight for den aktive og clWindow for de andre. Forbind (Wire) dette til hver TPdfView.OnClick og til åbne-proceduren, så fokus følger det dokument, du lige har åbnet
Nogle operationer gælder for alle synlige paneler frem for kun det aktive. En boolean FAllViewsMode på formularen driver den gren (branch). Når den er sand, spreder (fan out) zoom-ændringer og sidenavigation sig ud til hvert panel, der har et aktivt dokument:
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;
Synkroniseret sidenavigation
Synkroniseret navigation er valgfri, men nyttig for dokumentrevisions-workflows, hvor begge filer dækker det samme sideområde (page range). Logikken hører hjemme i en hændelseshåndtering (event handler), der udløses (fires), efter at brugeren navigerer i én visning. Når en kildevisning (source view) ændrer sit PageNumber, udbreder (propagates) håndteringen det tal til de andre visninger, underlagt én vagt (guard): målvisningen (the target view) skal have mindst så mange sider, ellers springes den over
PageNumber på TPdfView og på TPdf er uafhængige. TPdf.PageNumber sporer, hvilken side dokumentkomponenten betragter som aktuel (current); TPdfView.PageNumber sporer, hvad der vises på skærmen. Til navigationsformål ønsker du visningsegenskaben (the view property), ikke dokumentegenskaben
Et afkrydsningsfelt (A checkbox) mærket noget i retning af "Synkroniser sider" (Sync pages) giver brugeren kontrol. Når det ikke er afkrydset (unchecked), navigerer hvert panel uafhængigt, og håndteringen (the handler) afsluttes umiddelbart. Den uafhængighed er vigtig for brugsscenarier, hvor de to dokumenter har forskellige sideantal, eller hvor brugeren ønsker at finde den tilsvarende passage i en oversættelse, der starter på en anden side. Altid at gennemtvinge (Forcing) synkronisering ville gøre værktøjet sværere at bruge end et simpelt to-vindues skrivebordsarrangement
En ting at være opmærksom på: at sætte PdfView.PageNumber programmatisk (programmatically) inde i synkroniserings-håndteringen (the sync handler) vil i sig selv udløse (trigger) ændringshændelsen (the change event) på den visning. Beskyt (Guard) mod uendelig rekursion (infinite recursion) med et boolean flag, som du sætter før tildelingen og rydder umiddelbart efter. Flaget er pr.-formular (per-form), ikke pr.-visning (per-view), fordi alle tre visninger deler den samme håndtering
Zoom pr. panel
Hver TPdfView bærer sin egen Zoom-egenskab, en Double i procent, hvor Zoom := 100 betyder faktisk størrelse (100%). At sætte den tilsidesætter (overrides) enhver aktiv FitMode. For en tilpas-til-bredde-knap (fit-to-width button) på det aktive panel, læs tilpasnings-zoom (fit zoom) fra PdfView.PageWidthZoom[PdfView.PageNumber] og tildel den. For tilpas-til-side (fit-to-page), brug PageZoom[PageNumber]. Begge er array-egenskaber indekseret efter 1-baseret sidetal, så beskyt mod et sidetal på nul, før du får adgang til dem
Når du eksporterer den aktuelle side til et billede, skal du læse rotationen (the rotation) fra visningen, men kalde RenderPage på TPdf komponenten, ikke visningen. Bitmap-formen (The bitmap form) af TPdf.RenderPage tager eksplicitte pixel-dimensioner (pixel dimensions) plus en TRotation-værdi og et TRenderOptions-sæt. Funktionsvarianten (The function variant) returnerer en kalder-ejet (caller-owned) TBitmap, som du selv frigør efter at have gemt:
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;
2x multiplikatoren (multiplier) på bredde og højde giver skarpere output for dokumenter med fin tekst. try/finally omkring bitmap-frigørelsen er ikke valgfrit; en TSaveDialog annullering rammer stadig finally blokken, og du ønsker bitmappet frigivet (released), uanset hvad brugeren gjorde
DLL-krav (DLL Requirements)
PDFium-komponenten indpakker (wraps) det native pdfium-bibliotek. En 32-bit værtsproces (host process) behøver pdfium32.dll; en 64-bit vært behøver pdfium64.dll. Varianter med V8 JavaScript-motoren (the V8 JavaScript engine) tilføjer suffikset v8 og vejer omtrent 23-27 MB mod de 5-6 MB for standard-builds. For en sammenligningsfremviser, der deaktiverer formularudfyldning (Pdf.FormFill := False), er det standard ikke-V8 build tilstrækkeligt og holder distributionen (the distribution) mindre
Placer DLL'en i den samme mappe som den eksekverbare fil, eller i enhver mappe på systemets PATH. Komponenten indlæser den on-demand, når den første TPdf aktiveres, så en manglende DLL dukker op (surfaces) på det tidspunkt frem for ved applikationsstart. Hvis du udgiver (ship) et installationsprogram (installer), er den mest pålidelige tilgang at kopiere DLL'en ind i applikationsmappen under installationen frem for at stole på en systemmappe, som en administrator (administrator) senere kan rydde op i
V8-builds er primært nyttige, når du har brug for at interagere med PDF JavaScript-handlinger (actions), for eksempel for at udløse beregningsfelter (calculation fields) eller send-håndteringer (submit handlers). En passiv sammenligningsfremviser har ingen grund til at køre JavaScript; at sætte Pdf.FormFill := False før Active := True springer formular-udfyldningsmiljøet fuldstændigt over, hvilket også betyder, at ingen JS-motor initialiseres, selv hvis standard-buildet bruges. Det er den korrekte standard (default) for en skrivebeskyttet (read-only) fremviser uanset hvilken DLL-variant, du udgiver
For yderligere detaljer om PDFium-komponenten og dens fulde API, besøg Delphi PDFium Component produktsiden