Zwei Dokumente gleichzeitig geöffnet, dieselbe Seitenzahl, jedes in seinem eigenen scrollbaren Bereich: Das ist der Kern eines Vergleichs-Viewers. Die PDFium Component bietet dies durch ein einfaches Objektmodell, bei dem TPdf die Datei und TPdfView die Anzeige besitzt. Ein Dokument, ein TPdf, ein TPdfView. Wenn Sie drei Bereiche möchten, haben Sie drei Paare. Die schwierigen Teile sind nicht die API-Aufrufe; es ist die Layoutberechnung, wenn sich die Fenstergröße ändert, und die Seiten-Synchronisierungslogik, wenn Sie entscheiden, welche Ansicht welcher folgen soll
Formular-Layout
Das VCL-Formular enthält drei TScrollBox-Container nebeneinander, jeder mit einem TPdfView darin, der an alClient ausgerichtet ist, sodass er die Box ausfüllt. Zwei TSplitter-Komponenten sitzen zwischen den Boxen, damit der Benutzer die Spaltenbreiten zur Laufzeit anpassen kann. Eine Symbolleiste über den Bereichen enthält die Öffnen-Schaltflächen, Zoom-Steuerelemente und den Umschalter zwischen Zwei-Ansichten- und Drei-Ansichten-Modus
Der Drei-Ansichten-Modus ist ein boolescher Wert, den das Formular intern verfolgt. Wenn er umschaltet, berechnen Sie die Breiten neu und blenden die dritte Spalte ein oder aus. Der einfachste Ansatz besteht darin, alle Align-Eigenschaften zu löschen, die Splitter auszublenden und dann absolute Positionen festzulegen:
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;
Das Setzen von Align := alNone für alle drei Boxen vor der Integer-Arithmetik vermeidet, dass die VCL-Einschränkungs-Engine gegen Ihre Zuweisungen ankämpft. Stellen Sie die Sichtbarkeit der Splitter nach der Positionierung wieder her, wenn Sie im Zwei-Ansichten-Modus eine Größenänderung durch Ziehen wünschen
Die Höhe jedes Scroll-Bereichs ist der Clientbereich abzüglich der Höhe des Symbolleisten-Panels. Da die Symbolleiste oben mit alTop angedockt ist, ergibt ClientHeight - PanelButtons.Height den nutzbaren vertikalen Platz. Weisen Sie diesen allen drei Boxen innerhalb desselben UpdateLayout-Aufrufs zu, damit es nie ein Frame gibt, in dem eine Box höher als die anderen ist und ein Layout-Flackern verursacht
Öffnen eines Dokuments
Jedes Panel-Paar benötigt sein eigenes Öffnungsverfahren. Das Muster ist kurz: Deaktivieren Sie die Komponente, legen Sie den Dateinamen fest, versuchen Sie sie zu aktivieren, fangen Sie EPdfError ab, wenn die Datei ein Passwort erfordert. Beachten Sie, dass TPdfView.Active das Rendering steuert, aber TPdf.Active die Datei tatsächlich öffnet; sie sind unabhängig voneinander. Das Setzen von PdfView.Active := True, wenn das verknüpfte TPdf noch nicht aktiv ist, ist harmlos, zeigt aber nichts an
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 := '';
try
PdfComponent.Active := True;
except
on E: EPdfError do
begin
if InputQuery('Password', 'Enter document password:', Password) then
begin
PdfComponent.Password := Password;
PdfComponent.Active := True;
end
else
raise;
end;
end;
if PdfComponent.Active then
begin
PdfViewComponent.PageNumber := 1;
SetActivePdfView(PdfViewComponent);
end;
end;
Überprüfen Sie nach der Zuweisung immer PdfComponent.Active; eine beschädigte Datei oder ein falsches Passwort führt dazu, dass das Laden stillschweigend fehlschlägt, ohne im Standardpfad eine Ausnahme auszulösen. Das explizite Setzen von PdfViewComponent.PageNumber := 1 nach einem erfolgreichen Öffnen vermeidet eine veraltete Seitenzahl aus dem vorherigen Dokument
Der obige Code zur Passwortbehandlung löst bei jedem anderen Fehler als der bekannten Passwortmeldung eine Ausnahme aus. Das ist beabsichtigt: Sie möchten, dass beschädigte oder nicht unterstützte Dateien sofort sichtbar werden, anstatt als stiller leerer Bereich geschluckt zu werden. Ein Benutzer, der nichts sieht, hat keine Ahnung, ob die Datei geladen wurde und einfach leer ist, oder ob die Komponente sie abgelehnt hat. Das Auslösen einer Ausnahme hält den Fehler sichtbar
Verfolgung des aktiven Bereichs
Wenn der Benutzer in einen Bereich klickt, wird dieser Bereich aktiv. Das Formular verfolgt ein privates FActivePdfView: TPdfView-Feld. Die visuelle Rückmeldung ist eine Farbänderung des Rahmens auf der enthaltenen TScrollBox: Setzen Sie ihn für den aktiven Bereich auf clHighlight und für die anderen auf clWindow. Verknüpfen Sie dies mit jedem TPdfView.OnClick und mit dem Öffnungsverfahren, damit der Fokus dem Dokument folgt, das Sie gerade geöffnet haben
Einige Operationen gelten für alle sichtbaren Bereiche und nicht nur für den aktiven. Ein boolescher Wert FAllViewsMode auf dem Formular steuert diesen Zweig. Wenn er wahr ist, breiten sich Zoomänderungen und die Seitennavigation auf jeden Bereich aus, der ein aktives Dokument enthält:
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;
Synchronisierte Seitennavigation
Synchronisierte Navigation ist optional, aber nützlich für Workflows zur Dokumentenrevision, bei denen beide Dateien denselben Seitenbereich abdecken. Die Logik gehört in einen Ereignishandler, der ausgelöst wird, nachdem der Benutzer durch eine Ansicht navigiert hat. Wenn eine Quellansicht ihre PageNumber ändert, gibt der Handler diese Nummer an die anderen Ansichten weiter, vorbehaltlich einer Einschränkung: Die Zielansicht muss mindestens so viele Seiten haben, andernfalls wird übersprungen
Die PageNumber in TPdfView und in TPdf sind unabhängig voneinander. TPdf.PageNumber verfolgt, welche Seite die Dokumentkomponente als aktuell betrachtet; TPdfView.PageNumber verfolgt, was auf dem Bildschirm angezeigt wird. Für Navigationszwecke benötigen Sie die Eigenschaft der Ansicht, nicht die Eigenschaft des Dokuments
Ein Kontrollkästchen mit der Bezeichnung "Seiten synchronisieren" gibt dem Benutzer die Kontrolle. Wenn es nicht aktiviert ist, navigiert jeder Bereich unabhängig, und der Handler wird sofort beendet. Diese Unabhängigkeit ist wichtig für Anwendungsfälle, bei denen die beiden Dokumente eine unterschiedliche Seitenanzahl haben oder bei denen der Benutzer die entsprechende Passage in einer Übersetzung finden möchte, die auf einer anderen Seite beginnt. Eine ständige Synchronisierung würde die Verwendung des Tools schwieriger machen als ein einfaches Desktop-Arrangement mit zwei Fenstern
Eine Sache, auf die Sie achten sollten: Das programmgesteuerte Setzen von PdfView.PageNumber innerhalb des Synchronisierungs-Handlers löst selbst das Änderungsereignis in dieser Ansicht aus. Schützen Sie sich vor unendlicher Rekursion mit einem booleschen Flag, das Sie vor der Zuweisung setzen und unmittelbar danach löschen. Das Flag gilt pro Formular, nicht pro Ansicht, da alle drei Ansichten denselben Handler teilen
Zoom pro Bereich
Jedes TPdfView hat seine eigene Zoom-Eigenschaft, einen Double-Wert in Prozent, bei dem Zoom := 100 die tatsächliche Größe (100%) bedeutet. Durch das Setzen wird jeder aktive FitMode überschrieben. Für eine Schaltfläche "An Breite anpassen" im aktiven Bereich lesen Sie den passenden Zoom von PdfView.PageWidthZoom[PdfView.PageNumber] und weisen Sie ihn zu. Verwenden Sie für die Anpassung an die Seite PageZoom[PageNumber]. Beides sind Array-Eigenschaften, die nach einer 1-basierten Seitenzahl indiziert sind. Schützen Sie sich also vor einer Seitenzahl von null, bevor Sie darauf zugreifen
Wenn Sie die aktuelle Seite in ein Bild exportieren, lesen Sie die Drehung aus der Ansicht, rufen Sie aber RenderPage auf der TPdf-Komponente auf, nicht in der Ansicht. Die Bitmap-Form von TPdf.RenderPage erfordert explizite Pixelabmessungen plus einen TRotation-Wert und einen TRenderOptions-Satz. Die Funktionsvariante gibt eine dem Aufrufer gehörende TBitmap zurück, die Sie nach dem Speichern selbst freigeben:
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;
Der 2-fache Multiplikator für Breite und Höhe liefert eine schärfere Ausgabe für Dokumente mit feinem Text. Das try/finally um die Bitmap-Freigabe ist nicht optional; ein Abbrechen des TSaveDialog trifft immer noch den finally-Block, und Sie möchten, dass die Bitmap unabhängig davon freigegeben wird, was der Benutzer getan hat
DLL-Anforderungen
Die PDFium Component umschließt die native pdfium-Bibliothek. Ein 32-Bit-Hostprozess benötigt pdfium32.dll; ein 64-Bit-Host benötigt pdfium64.dll. Varianten mit der V8 JavaScript-Engine fügen das Suffix v8 hinzu und wiegen etwa 23-27 MB im Vergleich zu den 5-6 MB der Standard-Builds. Für einen Vergleichs-Viewer, der das Ausfüllen von Formularen deaktiviert (Pdf.FormFill := False), ist der Standard-Non-V8-Build ausreichend und hält die Verteilung kleiner
Platzieren Sie die DLL im selben Verzeichnis wie die ausführbare Datei oder in einem beliebigen Verzeichnis im System-PATH. Die Komponente lädt sie bei Bedarf, wenn das erste TPdf aktiviert wird, sodass eine fehlende DLL an diesem Punkt sichtbar wird und nicht beim Anwendungsstart. Wenn Sie ein Installationsprogramm ausliefern, besteht der zuverlässigste Ansatz darin, die DLL während der Installation in den Anwendungsordner zu kopieren, anstatt sich auf ein Systemverzeichnis zu verlassen, das ein Administrator möglicherweise später bereinigt
Die V8-Builds sind vor allem dann nützlich, wenn Sie mit PDF-JavaScript-Aktionen interagieren müssen, beispielsweise um Berechnungsfelder auszulösen oder Handler zu übermitteln. Ein passiver Vergleichs-Viewer hat keinen Grund, JavaScript auszuführen; das Setzen von Pdf.FormFill := False vor Active := True überspringt die Formularausfüllumgebung vollständig, was auch bedeutet, dass keine JS-Engine initialisiert wird, selbst wenn der Standard-Build verwendet wird. Dies ist die korrekte Standardeinstellung für einen schreibgeschützten Viewer, unabhängig davon, welche DLL-Variante Sie ausliefern
Weitere Details zur PDFium Component-Komponente und ihrer vollständigen API finden Sie auf der Produktseite der Delphi PDFium Component