Ridimensionamento corretto di JavaFX

Voglio ridimensionare tutti i nodes in un riquadro su un evento di scorrimento.

Quello che ho provato finora:

  1. Quando eseguo scaleX o scalaY, rispettivamente, il bordo delle scale del riquadro (visto quando si imposta lo stile Pane -fx-border-color: black; ). Quindi non tutti gli eventi inizieranno se non provengo dai bordi del riquadro, quindi ho bisogno di tutto. inserisci la descrizione dell'immagine qui

  2. Il prossimo passo ho cercato di scalare ogni nodo e si è rivelato davvero pessimo, qualcosa del genere – (le linee si estendevano attraverso i punti). O se si scorre nell’altro lato, sarebbe meno inserisci la descrizione dell'immagine qui

  3. Un altro metodo che ho provato è stato quello di scalare i punti del Nodo. È meglio, ma non mi piace. Sembra point.setScaleX(point.getScaleX()+scaleX) e y e altri nodes in modo appropriato.

Ho creato un’app di esempio per dimostrare un approccio per l’esecuzione del ridimensionamento di un nodo in una finestra su un evento di scorrimento (ad esempio, scorrere verso l’interno e verso l’esterno ruotando la rotellina del mouse).

La logica chiave dell’esempio per ridimensionare un gruppo posizionato all’interno di uno StackPane:

 final double SCALE_DELTA = 1.1; final StackPane zoomPane = new StackPane(); zoomPane.getChildren().add(group); zoomPane.setOnScroll(new EventHandler() { @Override public void handle(ScrollEvent event) { event.consume(); if (event.getDeltaY() == 0) { return; } double scaleFactor = (event.getDeltaY() > 0) ? SCALE_DELTA : 1/SCALE_DELTA; group.setScaleX(group.getScaleX() * scaleFactor); group.setScaleY(group.getScaleY() * scaleFactor); } }); 

Il gestore di eventi di scorrimento è impostato sullo StackPane che si chiude, che è un riquadro ridimensionabile, quindi si espande per riempire qualsiasi spazio vuoto, mantenendo il contenuto ingrandito centrato nel riquadro. Se si sposta la rotellina del mouse in qualsiasi punto all’interno dello StackPane, si ingrandirà o ridurrà il gruppo di nodes racchiuso.

zoomyzoomyin

 import javafx.application.Application; import javafx.beans.value.*; import javafx.event.*; import javafx.geometry.Bounds; import javafx.scene.*; import javafx.scene.control.*; import javafx.scene.image.*; import javafx.scene.input.*; import javafx.scene.layout.*; import javafx.scene.paint.Color; import javafx.scene.shape.*; import javafx.stage.Stage; public class GraphicsScalingApp extends Application { public static void main(String[] args) { launch(args); } @Override public void start(final Stage stage) { final Group group = new Group( createStar(), createCurve() ); Parent zoomPane = createZoomPane(group); VBox layout = new VBox(); layout.getChildren().setAll( createMenuBar(stage, group), zoomPane ); VBox.setVgrow(zoomPane, Priority.ALWAYS); Scene scene = new Scene( layout ); stage.setTitle("Zoomy"); stage.getIcons().setAll(new Image(APP_ICON)); stage.setScene(scene); stage.show(); } private Parent createZoomPane(final Group group) { final double SCALE_DELTA = 1.1; final StackPane zoomPane = new StackPane(); zoomPane.getChildren().add(group); zoomPane.setOnScroll(new EventHandler() { @Override public void handle(ScrollEvent event) { event.consume(); if (event.getDeltaY() == 0) { return; } double scaleFactor = (event.getDeltaY() > 0) ? SCALE_DELTA : 1/SCALE_DELTA; group.setScaleX(group.getScaleX() * scaleFactor); group.setScaleY(group.getScaleY() * scaleFactor); } }); zoomPane.layoutBoundsProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, Bounds oldBounds, Bounds bounds) { zoomPane.setClip(new Rectangle(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight())); } }); return zoomPane; } private SVGPath createCurve() { SVGPath ellipticalArc = new SVGPath(); ellipticalArc.setContent( "M10,150 A15 15 180 0 1 70 140 A15 25 180 0 0 130 130 A15 55 180 0 1 190 120" ); ellipticalArc.setStroke(Color.LIGHTGREEN); ellipticalArc.setStrokeWidth(4); ellipticalArc.setFill(null); return ellipticalArc; } private SVGPath createStar() { SVGPath star = new SVGPath(); star.setContent( "M100,10 L100,10 40,180 190,60 10,60 160,180 z" ); star.setStrokeLineJoin(StrokeLineJoin.ROUND); star.setStroke(Color.BLUE); star.setFill(Color.DARKBLUE); star.setStrokeWidth(4); return star; } private MenuBar createMenuBar(final Stage stage, final Group group) { Menu fileMenu = new Menu("_File"); MenuItem exitMenuItem = new MenuItem("E_xit"); exitMenuItem.setGraphic(new ImageView(new Image(CLOSE_ICON))); exitMenuItem.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { stage.close(); } }); fileMenu.getItems().setAll( exitMenuItem ); Menu zoomMenu = new Menu("_Zoom"); MenuItem zoomResetMenuItem = new MenuItem("Zoom _Reset"); zoomResetMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.ESCAPE)); zoomResetMenuItem.setGraphic(new ImageView(new Image(ZOOM_RESET_ICON))); zoomResetMenuItem.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { group.setScaleX(1); group.setScaleY(1); } }); MenuItem zoomInMenuItem = new MenuItem("Zoom _In"); zoomInMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.I)); zoomInMenuItem.setGraphic(new ImageView(new Image(ZOOM_IN_ICON))); zoomInMenuItem.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { group.setScaleX(group.getScaleX() * 1.5); group.setScaleY(group.getScaleY() * 1.5); } }); MenuItem zoomOutMenuItem = new MenuItem("Zoom _Out"); zoomOutMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.O)); zoomOutMenuItem.setGraphic(new ImageView(new Image(ZOOM_OUT_ICON))); zoomOutMenuItem.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { group.setScaleX(group.getScaleX() * 1/1.5); group.setScaleY(group.getScaleY() * 1/1.5); } }); zoomMenu.getItems().setAll( zoomResetMenuItem, zoomInMenuItem, zoomOutMenuItem ); MenuBar menuBar = new MenuBar(); menuBar.getMenus().setAll( fileMenu, zoomMenu ); return menuBar; } // icons source from: http://www.iconarchive.com/show/soft-scraps-icons-by-deleket.html // icon license: CC Attribution-Noncommercial-No Derivate 3.0 =? http://creativecommons.org/licenses/by-nc-nd/3.0/ // icon Commercial usage: Allowed (Author Approval required -> Visit artist website for details). public static final String APP_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/128/Zoom-icon.png"; public static final String ZOOM_RESET_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/24/Zoom-icon.png"; public static final String ZOOM_OUT_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/24/Zoom-Out-icon.png"; public static final String ZOOM_IN_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/24/Zoom-In-icon.png"; public static final String CLOSE_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/24/Button-Close-icon.png"; } 

Aggiornamento per un nodo ingrandito in un ScrollPane

L’implementazione di cui sopra funziona bene, ma è utile essere in grado di posizionare il nodo zoom all’interno di un riquadro di scorrimento, in modo che quando si esegue lo zoom ingrandendo il nodo ingrandito rispetto al viewport disponibile, è comunque ansible eseguire una panoramica nodo ingrandito all’interno del riquadro di scorrimento per visualizzare le parti del nodo.

Ho trovato difficile ottenere il comportamento dello zoom in un riquadro di scorrimento, quindi ho chiesto aiuto su una discussione del forum Oracle JavaFX .

L’utente del forum Oracle JavaFX James_D ha trovato la seguente soluzione che risolve abbastanza bene lo zoom all’interno di un problema ScrollPane.

I suoi commenti e il codice erano i seguenti:

Un paio di modifiche minori prima: ho avvolto lo StackPane in un gruppo in modo che ScrollPane fosse a conoscenza delle modifiche alle trasformazioni, come da ScrollPane Javadocs. E poi ho legato la dimensione minima dello StackPane alle dimensioni del viewport (mantenendo il contenuto centrato quando è più piccolo del viewport).

Inizialmente pensavo di dover usare una trasformazione di scala per ingrandire il centro visualizzato (cioè il punto sul contenuto che si trova al centro della finestra). Ma ho scoperto che dovevo ancora correggere la posizione di scorrimento in seguito per mantenere lo stesso centro visualizzato, quindi l’ho abbandonato e ho ripristinato l’utilizzo di setScaleX () e setScaleY ().

Il trucco è di fissare la posizione di scorrimento dopo il ridimensionamento. Ho calcolato l’offset di scorrimento in coordinate locali del contenuto della scroll e poi ho calcolato i nuovi valori di scroll necessari dopo la scala. Questo è stato un po ‘complicato. L’osservazione di base è che (hValue-hMin) / (hMax-hMin) = x / (contentWidth – viewportWidth), dove x è l’offset orizzontale del bordo sinistro della finestra dal bordo sinistro del contenuto. Quindi hai centerX = x + viewportWidth / 2.

Dopo il ridimensionamento, la coordinata x del vecchio centroX è ora centerX * scaleFactor. Quindi dobbiamo solo impostare il nuovo hValue per renderlo il nuovo centro. C’è un po ‘di algebra per capirlo.

Dopo di ciò, il panning con il trascinamento è stato abbastanza facile :).

Una richiesta di funzionalità corrispondente per aggiungere API di alto livello per supportare la funzionalità di ingrandimento e ridimensionamento in un ScrollPane è Aggiungi funzionalità di scalaCalcello a ScrollPane . Vota o commenta la richiesta di funzionalità se desideri vederla implementata.

 import javafx.application.Application; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.*; import javafx.event.*; import javafx.geometry.Bounds; import javafx.geometry.Point2D; import javafx.scene.*; import javafx.scene.control.*; import javafx.scene.image.*; import javafx.scene.input.*; import javafx.scene.layout.*; import javafx.scene.paint.Color; import javafx.scene.shape.*; import javafx.stage.Stage; public class GraphicsScalingApp extends Application { public static void main(String[] args) { launch(args); } @Override public void start(final Stage stage) { final Group group = new Group(createStar(), createCurve()); Parent zoomPane = createZoomPane(group); VBox layout = new VBox(); layout.getChildren().setAll(createMenuBar(stage, group), zoomPane); VBox.setVgrow(zoomPane, Priority.ALWAYS); Scene scene = new Scene(layout); stage.setTitle("Zoomy"); stage.getIcons().setAll(new Image(APP_ICON)); stage.setScene(scene); stage.show(); } private Parent createZoomPane(final Group group) { final double SCALE_DELTA = 1.1; final StackPane zoomPane = new StackPane(); zoomPane.getChildren().add(group); final ScrollPane scroller = new ScrollPane(); final Group scrollContent = new Group(zoomPane); scroller.setContent(scrollContent); scroller.viewportBoundsProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, Bounds oldValue, Bounds newValue) { zoomPane.setMinSize(newValue.getWidth(), newValue.getHeight()); } }); scroller.setPrefViewportWidth(256); scroller.setPrefViewportHeight(256); zoomPane.setOnScroll(new EventHandler() { @Override public void handle(ScrollEvent event) { event.consume(); if (event.getDeltaY() == 0) { return; } double scaleFactor = (event.getDeltaY() > 0) ? SCALE_DELTA : 1 / SCALE_DELTA; // amount of scrolling in each direction in scrollContent coordinate // units Point2D scrollOffset = figureScrollOffset(scrollContent, scroller); group.setScaleX(group.getScaleX() * scaleFactor); group.setScaleY(group.getScaleY() * scaleFactor); // move viewport so that old center remains in the center after the // scaling repositionScroller(scrollContent, scroller, scaleFactor, scrollOffset); } }); // Panning via drag.... final ObjectProperty lastMouseCoordinates = new SimpleObjectProperty(); scrollContent.setOnMousePressed(new EventHandler() { @Override public void handle(MouseEvent event) { lastMouseCoordinates.set(new Point2D(event.getX(), event.getY())); } }); scrollContent.setOnMouseDragged(new EventHandler() { @Override public void handle(MouseEvent event) { double deltaX = event.getX() - lastMouseCoordinates.get().getX(); double extraWidth = scrollContent.getLayoutBounds().getWidth() - scroller.getViewportBounds().getWidth(); double deltaH = deltaX * (scroller.getHmax() - scroller.getHmin()) / extraWidth; double desiredH = scroller.getHvalue() - deltaH; scroller.setHvalue(Math.max(0, Math.min(scroller.getHmax(), desiredH))); double deltaY = event.getY() - lastMouseCoordinates.get().getY(); double extraHeight = scrollContent.getLayoutBounds().getHeight() - scroller.getViewportBounds().getHeight(); double deltaV = deltaY * (scroller.getHmax() - scroller.getHmin()) / extraHeight; double desiredV = scroller.getVvalue() - deltaV; scroller.setVvalue(Math.max(0, Math.min(scroller.getVmax(), desiredV))); } }); return scroller; } private Point2D figureScrollOffset(Node scrollContent, ScrollPane scroller) { double extraWidth = scrollContent.getLayoutBounds().getWidth() - scroller.getViewportBounds().getWidth(); double hScrollProportion = (scroller.getHvalue() - scroller.getHmin()) / (scroller.getHmax() - scroller.getHmin()); double scrollXOffset = hScrollProportion * Math.max(0, extraWidth); double extraHeight = scrollContent.getLayoutBounds().getHeight() - scroller.getViewportBounds().getHeight(); double vScrollProportion = (scroller.getVvalue() - scroller.getVmin()) / (scroller.getVmax() - scroller.getVmin()); double scrollYOffset = vScrollProportion * Math.max(0, extraHeight); return new Point2D(scrollXOffset, scrollYOffset); } private void repositionScroller(Node scrollContent, ScrollPane scroller, double scaleFactor, Point2D scrollOffset) { double scrollXOffset = scrollOffset.getX(); double scrollYOffset = scrollOffset.getY(); double extraWidth = scrollContent.getLayoutBounds().getWidth() - scroller.getViewportBounds().getWidth(); if (extraWidth > 0) { double halfWidth = scroller.getViewportBounds().getWidth() / 2 ; double newScrollXOffset = (scaleFactor - 1) * halfWidth + scaleFactor * scrollXOffset; scroller.setHvalue(scroller.getHmin() + newScrollXOffset * (scroller.getHmax() - scroller.getHmin()) / extraWidth); } else { scroller.setHvalue(scroller.getHmin()); } double extraHeight = scrollContent.getLayoutBounds().getHeight() - scroller.getViewportBounds().getHeight(); if (extraHeight > 0) { double halfHeight = scroller.getViewportBounds().getHeight() / 2 ; double newScrollYOffset = (scaleFactor - 1) * halfHeight + scaleFactor * scrollYOffset; scroller.setVvalue(scroller.getVmin() + newScrollYOffset * (scroller.getVmax() - scroller.getVmin()) / extraHeight); } else { scroller.setHvalue(scroller.getHmin()); } } private SVGPath createCurve() { SVGPath ellipticalArc = new SVGPath(); ellipticalArc.setContent("M10,150 A15 15 180 0 1 70 140 A15 25 180 0 0 130 130 A15 55 180 0 1 190 120"); ellipticalArc.setStroke(Color.LIGHTGREEN); ellipticalArc.setStrokeWidth(4); ellipticalArc.setFill(null); return ellipticalArc; } private SVGPath createStar() { SVGPath star = new SVGPath(); star.setContent("M100,10 L100,10 40,180 190,60 10,60 160,180 z"); star.setStrokeLineJoin(StrokeLineJoin.ROUND); star.setStroke(Color.BLUE); star.setFill(Color.DARKBLUE); star.setStrokeWidth(4); return star; } private MenuBar createMenuBar(final Stage stage, final Group group) { Menu fileMenu = new Menu("_File"); MenuItem exitMenuItem = new MenuItem("E_xit"); exitMenuItem.setGraphic(new ImageView(new Image(CLOSE_ICON))); exitMenuItem.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { stage.close(); } }); fileMenu.getItems().setAll(exitMenuItem); Menu zoomMenu = new Menu("_Zoom"); MenuItem zoomResetMenuItem = new MenuItem("Zoom _Reset"); zoomResetMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.ESCAPE)); zoomResetMenuItem.setGraphic(new ImageView(new Image(ZOOM_RESET_ICON))); zoomResetMenuItem.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { group.setScaleX(1); group.setScaleY(1); } }); MenuItem zoomInMenuItem = new MenuItem("Zoom _In"); zoomInMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.I)); zoomInMenuItem.setGraphic(new ImageView(new Image(ZOOM_IN_ICON))); zoomInMenuItem.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { group.setScaleX(group.getScaleX() * 1.5); group.setScaleY(group.getScaleY() * 1.5); } }); MenuItem zoomOutMenuItem = new MenuItem("Zoom _Out"); zoomOutMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.O)); zoomOutMenuItem.setGraphic(new ImageView(new Image(ZOOM_OUT_ICON))); zoomOutMenuItem.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { group.setScaleX(group.getScaleX() * 1 / 1.5); group.setScaleY(group.getScaleY() * 1 / 1.5); } }); zoomMenu.getItems().setAll(zoomResetMenuItem, zoomInMenuItem, zoomOutMenuItem); MenuBar menuBar = new MenuBar(); menuBar.getMenus().setAll(fileMenu, zoomMenu); return menuBar; } // icons source from: // http://www.iconarchive.com/show/soft-scraps-icons-by-deleket.html // icon license: CC Attribution-Noncommercial-No Derivate 3.0 =? // http://creativecommons.org/licenses/by-nc-nd/3.0/ // icon Commercial usage: Allowed (Author Approval required -> Visit artist // website for details). public static final String APP_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/128/Zoom-icon.png"; public static final String ZOOM_RESET_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/24/Zoom-icon.png"; public static final String ZOOM_OUT_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/24/Zoom-Out-icon.png"; public static final String ZOOM_IN_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/24/Zoom-In-icon.png"; public static final String CLOSE_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/24/Button-Close-icon.png"; } 

La risposta di jewelsea presenta un problema, se la dimensione del contenuto originale nello zoomPane è già maggiore di Visualizza porta. Quindi il seguente codice non funzionerà. zoomPane.setMinSize (newValue.getWidth (), newValue.getHeight ());

Il risultato è quando rimpiccioliamo, il contenuto non è più centrato.

Per risolvere questo problema, è necessario creare un altro StackPane tra ZoomPane e ScrollPane.

  // Create a zoom pane for zoom in/out final StackPane zoomPane = new StackPane(); zoomPane.getChildren().add(group); final Group zoomContent = new Group(zoomPane); // Create a pane for holding the content, when the content is smaller than the view port, // it will stay the view port size, make sure the content is centered final StackPane canvasPane = new StackPane(); canvasPane.getChildren().add(zoomContent); final Group scrollContent = new Group(canvasPane); // Scroll pane for scrolling scroller = new ScrollPane(); scroller.setContent(scrollContent); 

E nel listener viewerBoundsProperty, cambia zoomPane su canvasPane

 // Set the minimum canvas size canvasPane.setMinSize(newValue.getWidth(), newValue.getHeight()); 

JavaFx è troppo complicato per lo zoom in / out. Per ottenere lo stesso effetto, WPF è molto più facile.