From 90d95b9ff8127876ff4ee92f1f67071d484e6938 Mon Sep 17 00:00:00 2001 From: Marcus van Elst Date: Tue, 5 May 2026 16:15:44 +0200 Subject: [PATCH] =?UTF-8?q?Zentriere=20PDF-Vorschau=20via=20viewStack-Mind?= =?UTF-8?q?estgr=C3=B6=C3=9Fe=20statt=20Timing-Hacks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Korrekte Ursachenanalyse: Im Zoom-Modus schrumpft der viewStack auf Inhalts-Größe (ImageView). Ist der Inhalt kleiner als der Viewport, positioniert ScrollPane den viewStack links/oben – setHvalue(0.5) ist wirkungslos, weil nichts zu scrollen ist. Alle bisherigen runLater/ ChangeListener/AnimationTimer-Ansätze haben am falschen Hebel gedreht. Korrekter Fix: viewportBoundsProperty-Listener im Konstruktor zwingt viewStack auf mindestens Viewport-Größe. Pos.CENTER zentriert dann die ImageView automatisch, wenn sie kleiner ist; bei größerem Inhalt bleibt die Mindestgröße wirkungslos und der ScrollPane scrollt normal. Ersatzlos entfernt: AnimationTimer-Block in applyZoom (wasInFitMode- Zweig), Folge-Schritt-runLater (else-Zweig), setHvalue(0.5)/setVvalue(0.5) in resetToFitView. Bindings in resetToFitView bleiben unverändert. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../in/gui/batchrun/PdfPreviewPane.java | 48 ++++++------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/PdfPreviewPane.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/PdfPreviewPane.java index c59b4a1..5e3c11b 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/PdfPreviewPane.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/PdfPreviewPane.java @@ -17,7 +17,6 @@ import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException; import org.apache.pdfbox.rendering.ImageType; import org.apache.pdfbox.rendering.PDFRenderer; -import javafx.animation.AnimationTimer; import javafx.application.Platform; import javafx.embed.swing.SwingFXUtils; import javafx.geometry.Bounds; @@ -244,6 +243,15 @@ public final class PdfPreviewPane { viewStack.addEventHandler(MouseEvent.MOUSE_PRESSED, this::onPanMousePressed); viewStack.addEventHandler(MouseEvent.MOUSE_DRAGGED, this::onPanMouseDragged); viewStack.addEventHandler(MouseEvent.MOUSE_RELEASED, this::onPanMouseReleased); + // viewStack ist immer mindestens so groß wie der Viewport. Ist der Inhalt + // (ImageView) kleiner als der Viewport, sorgt diese Mindestgröße zusammen + // mit StackPane.Pos.CENTER dafür, dass die ImageView automatisch zentriert + // wird – ohne manuelle setHvalue/setVvalue-Eingriffe. Ist der Inhalt größer, + // bleibt die Mindestgröße wirkungslos und der ScrollPane scrollt normal. + scrollPane.viewportBoundsProperty().addListener((obs, old, bounds) -> { + viewStack.setMinWidth(bounds.getWidth()); + viewStack.setMinHeight(bounds.getHeight()); + }); prevButton.setId("pdf-preview-prev-button"); prevButton.setOnAction(e -> navigateToPreviousPage()); @@ -726,33 +734,10 @@ public final class PdfPreviewPane { zoomLevel = effective; imageView.setFitWidth(naturalViewportWidth * zoomLevel); imageView.setFitHeight(0); - - if (wasInFitMode) { - // Erster Zoom-Schritt nach Verlassen des Fit-Modus: setFitToWidth(false) - // löst Layout-, CSS- und Property-Reset-Passes innerhalb desselben - // JavaFX-Frames aus. Ein AnimationTimer feuert handle() nach allen - // diesen Passes des aktuellen Frames; das ist der einzige Punkt, - // an dem setHvalue(0.5)/setVvalue(0.5) garantiert nicht mehr von - // einem nachträglichen Reset überschrieben werden. stop() im ersten - // handle()-Aufruf macht den Timer zum Single-Shot. - new AnimationTimer() { - @Override - public void handle(long now) { - stop(); - scrollPane.setHvalue(0.5); - scrollPane.setVvalue(0.5); - } - }.start(); - } else { - // Folge-Schritte: aktuelle Scroll-Position bewahren - double hval = scrollPane.getHvalue(); - double vval = scrollPane.getVvalue(); - Platform.runLater(() -> { - scrollPane.layout(); - scrollPane.setHvalue(hval); - scrollPane.setVvalue(vval); - }); - } + // Keine manuellen setHvalue/setVvalue-Eingriffe nötig: viewStack hat + // dank des viewportBoundsProperty-Listeners im Konstruktor mindestens + // Viewport-Größe, und Pos.CENTER sorgt für automatische Zentrierung, + // wenn der Inhalt kleiner als der Viewport ist. } /** @@ -787,14 +772,11 @@ public final class PdfPreviewPane { // den viewStack auf Viewport-Größe zurückrechnet scrollPane.setFitToWidth(true); scrollPane.setFitToHeight(true); - // 2./3. Bindings und H/V-Reset erst nach abgeschlossenem Layout-Pass, - // damit sie auf die zurückgerechneten Dimensionen wirken und - // Rounding-Reste aus Pan/Zoom die Zentrierung nicht verschieben + // 2. Bindings erst nach abgeschlossenem Layout-Pass, damit sie auf + // die zurückgerechneten viewStack-Dimensionen wirken Platform.runLater(() -> { imageView.fitWidthProperty().bind(viewStack.widthProperty()); imageView.fitHeightProperty().bind(viewStack.heightProperty()); - scrollPane.setHvalue(0.5); - scrollPane.setVvalue(0.5); }); } }