pdfviewfx wird von 3.1.1 auf 3.3.2 aktualisiert. Version 3.3.1 behebt 'Do not interrupt rendering', wodurch ClosedByInterruptException bei schnellem Seitenwechsel (#27 Folge-Bug) und das Ausbleiben weiterer Renderings ab Seite 3+ (#29 Folge-Bug) nicht mehr auftreten. Das 100-ms-PauseTransition-Workaround fuer den Seitenanfang wird ersetzt durch einen Listener auf die imageProperty des internen ImageView der PDFView-Skin. Der Listener scrollt erst dann zum Seitenanfang, wenn das Rendering tatsaechlich abgeschlossen ist und pendingScrollToTop gesetzt wurde (bei loadSource und Seitenwechsel-Buttons). Dadurch wird der Seitenanfang zuverlaessig angezeigt, unabhaengig von der Renderzeit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+94
-41
@@ -10,21 +10,21 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import com.dlsc.pdfviewfx.PDFView;
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Duration;
|
||||
|
||||
/**
|
||||
* Detailbereich-Komponente zur asynchronen Anzeige von Seiten einer Quelldatei.
|
||||
@@ -99,11 +99,18 @@ public final class PdfPreviewPane {
|
||||
|
||||
/**
|
||||
* Interner ScrollPane der PDFView-Skin. Wird nach der Skin-Installation
|
||||
* per Lookup gesetzt und für den Scroll-Schutz (Bug #27) sowie das
|
||||
* Zurücksetzen auf den Seitenanfang (Bug #29) verwendet.
|
||||
* per Lookup gesetzt und für den Scroll-Schutz verwendet.
|
||||
*/
|
||||
private ScrollPane pdfViewScrollPane = null;
|
||||
|
||||
/**
|
||||
* Signalisiert, dass nach dem nächsten abgeschlossenen Rendering-Vorgang
|
||||
* zum Seitenanfang gescrollt werden soll. Wird gesetzt beim Laden einer
|
||||
* neuen Datei und bei jedem Seitenwechsel; wird vom ImageView-Listener
|
||||
* nach dem Scrollen zurückgesetzt.
|
||||
*/
|
||||
private boolean pendingScrollToTop = false;
|
||||
|
||||
/**
|
||||
* Erstellt die Komponente im deaktivierten Platzhalter-Zustand.
|
||||
*/
|
||||
@@ -115,13 +122,11 @@ public final class PdfPreviewPane {
|
||||
pdfView.setShowToolBar(false);
|
||||
pdfView.setId("pdf-preview-view");
|
||||
|
||||
// Bug #27: Nach Skin-Installation den internen ScrollPane suchen und
|
||||
// dort einen gezielten Filter registrieren, der nur an den Scroll-Grenzen
|
||||
// konsumiert. So wird Mausrad-Seitenwechsel verhindert, ohne das
|
||||
// Inhalts-Scrolling innerhalb einer Seite zu blockieren.
|
||||
// Nach der Skin-Installation den internen ScrollPane suchen und
|
||||
// die Scroll-Filter sowie den Seitenanfang-Listener installieren.
|
||||
pdfView.skinProperty().addListener((obs, oldSkin, newSkin) -> {
|
||||
if (newSkin != null) {
|
||||
Platform.runLater(this::installInternalScrollFilter);
|
||||
Platform.runLater(this::installScrollHandlers);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -188,6 +193,7 @@ public final class PdfPreviewPane {
|
||||
currentSourceFile = sourceFile;
|
||||
currentPage = 0;
|
||||
totalPages = -1;
|
||||
pendingScrollToTop = true;
|
||||
requestLoad(sourceFile);
|
||||
}
|
||||
|
||||
@@ -261,12 +267,11 @@ public final class PdfPreviewPane {
|
||||
return;
|
||||
}
|
||||
int targetPage = currentPage - 1;
|
||||
pendingScrollToTop = true;
|
||||
pdfView.setPage(targetPage - 1);
|
||||
currentPage = targetPage;
|
||||
updatePageLabel();
|
||||
updateNavigationButtons();
|
||||
// Bug #29: Nach dem Rendering-Durchlauf zum Seitenanfang scrollen
|
||||
scrollToTopAfterRender();
|
||||
}
|
||||
|
||||
private void navigateToNextPage() {
|
||||
@@ -274,41 +279,52 @@ public final class PdfPreviewPane {
|
||||
return;
|
||||
}
|
||||
int targetPage = currentPage + 1;
|
||||
pendingScrollToTop = true;
|
||||
pdfView.setPage(targetPage - 1);
|
||||
currentPage = targetPage;
|
||||
updatePageLabel();
|
||||
updateNavigationButtons();
|
||||
// Bug #29: Nach dem Rendering-Durchlauf zum Seitenanfang scrollen
|
||||
scrollToTopAfterRender();
|
||||
}
|
||||
|
||||
// --- Interne Scroll-Hilfsmethoden ----------------------------------------
|
||||
|
||||
/**
|
||||
* Sucht nach der Skin-Installation den internen ScrollPane der PDFView-Skin
|
||||
* und registriert dort einen EventFilter für Bug #27.
|
||||
*
|
||||
* <p>Der Filter konsumiert ScrollEvents ausschließlich dann, wenn kein
|
||||
* überlaufender Seiteninhalt vorhanden ist oder der Scroll-Inhalt an der
|
||||
* Grenze (oben/unten) angekommen ist. Dadurch wird der interne
|
||||
* Seitenwechsel-Handler der Skin blockiert, ohne normales Inhalts-Scrolling
|
||||
* zu unterbinden.
|
||||
* und installiert dort beide Scroll-Handler:
|
||||
* <ol>
|
||||
* <li>Einen EventFilter gegen Mausrad-Seitenwechsel (verhindert, dass das
|
||||
* Mausrad an den Scroll-Grenzen die Seite wechselt).</li>
|
||||
* <li>Einen ImageView-Listener, der nach abgeschlossenem Rendering zum
|
||||
* Seitenanfang scrollt, sofern {@code pendingScrollToTop} gesetzt ist.</li>
|
||||
* </ol>
|
||||
*/
|
||||
private void installInternalScrollFilter() {
|
||||
javafx.scene.Node found = pdfView.lookup(".scroll-pane");
|
||||
private void installScrollHandlers() {
|
||||
Node found = pdfView.lookup(".scroll-pane");
|
||||
if (!(found instanceof ScrollPane sp)) {
|
||||
LOG.warn("PDF-Vorschau: Interner ScrollPane nicht gefunden – Mausrad-Schutz nicht aktiv");
|
||||
LOG.warn("PDF-Vorschau: Interner ScrollPane nicht gefunden – Scroll-Handler nicht aktiv");
|
||||
return;
|
||||
}
|
||||
pdfViewScrollPane = sp;
|
||||
LOG.debug("PDF-Vorschau: Interner ScrollPane gefunden, Mausrad-Schutz wird installiert");
|
||||
LOG.debug("PDF-Vorschau: Interner ScrollPane gefunden, Scroll-Handler werden installiert");
|
||||
|
||||
installMouseWheelPageChangeFilter(sp);
|
||||
installScrollToTopOnRenderComplete(sp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registriert einen EventFilter am internen ScrollPane, der Mausrad-Ereignisse
|
||||
* an den Scroll-Grenzen konsumiert. Dadurch wird verhindert, dass die PDFView-Skin
|
||||
* beim Erreichen des Seitenanfangs oder -endes automatisch die Seite wechselt,
|
||||
* ohne dass der Benutzer die Navigations-Buttons betätigt.
|
||||
*
|
||||
* @param sp der interne ScrollPane der PDFView-Skin
|
||||
*/
|
||||
private void installMouseWheelPageChangeFilter(ScrollPane sp) {
|
||||
sp.addEventFilter(ScrollEvent.SCROLL, event -> {
|
||||
if (event.isInertia()) {
|
||||
return;
|
||||
}
|
||||
// Prüfen ob die Seite überhaupt scrollbaren Inhalt hat
|
||||
javafx.scene.Node content = sp.getContent();
|
||||
Node content = sp.getContent();
|
||||
if (content == null) {
|
||||
event.consume();
|
||||
return;
|
||||
@@ -334,21 +350,59 @@ public final class PdfPreviewPane {
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrollt den internen ScrollPane nach einer kurzen Pause (100 ms) zum
|
||||
* Seitenanfang. Die Verzögerung stellt sicher, dass der PDFView-interne
|
||||
* Rendering-Durchlauf und etwaige nachgelagerte Scroll-Anpassungen der
|
||||
* Skin abgeschlossen sind, bevor die Position zurückgesetzt wird (Bug #29).
|
||||
* Sucht den {@link ImageView} innerhalb des internen ScrollPane und registriert
|
||||
* einen Listener auf dessen {@code imageProperty}. Sobald ein neues Seitenbild
|
||||
* gerendert wurde und {@code pendingScrollToTop} gesetzt ist, scrollt die
|
||||
* Komponente zum Seitenanfang. Der Listener stellt sicher, dass der Skin
|
||||
* seinen eigenen Scroll-Zustand erst abgeschlossen hat, bevor die Position
|
||||
* zurückgesetzt wird.
|
||||
*
|
||||
* @param sp der interne ScrollPane der PDFView-Skin
|
||||
*/
|
||||
private void scrollToTopAfterRender() {
|
||||
if (pdfViewScrollPane == null) {
|
||||
private void installScrollToTopOnRenderComplete(ScrollPane sp) {
|
||||
// CSS anwenden, damit Style-Klassen für den Lookup verfügbar sind
|
||||
pdfView.applyCss();
|
||||
// Suche den ImageView über die bekannte CSS-Klasse des Bild-Wrappers
|
||||
Node wrapperNode = sp.lookup(".image-view-wrapper");
|
||||
ImageView targetImageView = null;
|
||||
if (wrapperNode instanceof javafx.scene.layout.Pane wrapper) {
|
||||
for (Node child : wrapper.getChildren()) {
|
||||
if (child instanceof ImageView iv) {
|
||||
targetImageView = iv;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetImageView == null) {
|
||||
// Fallback: alle ImageViews im ScrollPane durchsuchen (da Thumbnails
|
||||
// deaktiviert sind, gibt es im Hauptbereich genau einen)
|
||||
for (Node n : sp.lookupAll(".image-view")) {
|
||||
if (n instanceof ImageView iv) {
|
||||
targetImageView = iv;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetImageView == null) {
|
||||
LOG.warn("PDF-Vorschau: ImageView nicht gefunden – Scrollen zum Seitenanfang nicht aktiv");
|
||||
return;
|
||||
}
|
||||
PauseTransition pause = new PauseTransition(Duration.millis(100));
|
||||
pause.setOnFinished(e -> {
|
||||
pdfViewScrollPane.setVvalue(0.0);
|
||||
pdfViewScrollPane.setHvalue(0.0);
|
||||
|
||||
final ImageView imageView = targetImageView;
|
||||
imageView.imageProperty().addListener((obs, oldImg, newImg) -> {
|
||||
// Nur scrollen, wenn ein neues Bild geliefert wurde und ein Seitenwechsel
|
||||
// bzw. Neuladung angefordert war
|
||||
if (newImg != null && pendingScrollToTop) {
|
||||
pendingScrollToTop = false;
|
||||
// Ein Platform.runLater() stellt sicher, dass der Skin seinen
|
||||
// internen Scroll-Zustand zuerst abgeschlossen hat
|
||||
Platform.runLater(() -> {
|
||||
sp.setVvalue(0.0);
|
||||
sp.setHvalue(0.0);
|
||||
});
|
||||
}
|
||||
});
|
||||
pause.play();
|
||||
LOG.debug("PDF-Vorschau: ImageView-Listener für Seitenanfang installiert");
|
||||
}
|
||||
|
||||
// --- Asynchrones Laden ---------------------------------------------------
|
||||
@@ -403,13 +457,12 @@ public final class PdfPreviewPane {
|
||||
int pages = (doc != null) ? doc.getNumberOfPages() : 1;
|
||||
totalPages = Math.max(1, pages);
|
||||
currentPage = 1;
|
||||
// PDFView zeigt nach load() bereits Seite 0 (= Seite 1)
|
||||
// PDFView zeigt nach load() bereits Seite 0 (= Seite 1); das Scrollen
|
||||
// zum Seitenanfang übernimmt der ImageView-Listener sobald das Bild vorliegt
|
||||
showContent();
|
||||
updateNavigationButtons();
|
||||
updatePageLabel();
|
||||
// Bug #29: Nach abgeschlossenem Rendering-Durchlauf zum Seitenanfang scrollen
|
||||
scrollToTopAfterRender();
|
||||
LOG.debug("PDF-Vorschau: Rendering erfolgreich – {} Seite(n)", totalPages);
|
||||
LOG.debug("PDF-Vorschau: Rendering angestoßen – {} Seite(n)", totalPages);
|
||||
} catch (Exception e) {
|
||||
String msg = classifyLoadException(e);
|
||||
LOG.warn("PDF-Vorschau: Rendering fehlgeschlagen – {}", msg, e);
|
||||
|
||||
Reference in New Issue
Block a user