How to change positions of node in a GridPane - java

I have an 4x4 gridpane object consists of Tiles, extends ImageView, and i want to create method for changing places of connected Tiles by mouse drag and drop. I've figured out how to take first element which drag started but I couldn't get the referances of the ImageView which in drag dropped.
Tile Class
public class Tile extends ImageView {
protected int xCoordinate;
protected int yCoordinate;
protected boolean isMoveable;
protected ArrayList<Point2D> points = new ArrayList<>();
public Tile(int xCoordinate, int yCoordinate) {
this.xCoordinate = xCoordinate;
this.yCoordinate = yCoordinate;
super.setFitHeight(125);
super.setFitWidth(125);
}}
GridPane codes
GridPane gridPane = new GridPane();
for (Tile tile : tiles){
gridPane.add(tile,tile.getXCoordinate(),tile.getYCoordinate());
}
StackPane centerPane = new StackPane();
centerPane.setStyle("-fx-background-color: white;");
centerPane.getChildren().add(gridPane);
centerPane.setPadding(new Insets(0,50,0,50));
I have tried this but I don't know how to get referance of connected Tile
gridPane.setOnMouseDragged(e->{
System.out.println(e.getTarget());
gridPane.setOnMouseReleased(e1->{
System.out.println(e1.getTarget());
});
});
I have created the codes for changing places but I should get the referance of the connected Tile when mouse released.

Oracle provides an excellent tutorial on using drag and drop in JavaFX.
You probably want to make use of the Dragboard, which is a special kind of Clipboard.
Points to note
You may not actually need to move the tiles that you have created. They are ImageViews.
You can place the image associated with the source in the dragboard and change the image in the target view when it is dropped.
You can set the dragView on the dragboard for visual feedback of the drag operation.
You can use a custom content type for the dragboard rather than an image, this is explained in the linked Oracle tutorial.
private static final DataFormat customFormat =
new DataFormat("helloworld.custom");
When putting a custom data onto a dragboard, specify the data type. Note that the data must be serializable.
When reading the data from the dragboard, a proper casting is needed.
Potential Approaches
There are two ways you can handle the tile display.
Tile view and model.
Create a separate tile view and tile model interface.
When the tiles change, don't change the view, only change the model instance backing the view. The view observes its model for changes and automatically updates itself. New nodes are not created and existing nodes are not moved.
That is the approach in the example below. The view is an ImageView and the model is an Image.
Encapsulate view and model together.
In this case, you place the information about the model in the view.
When a node is placed in the grid, you record it's grid position, for example by member values in the node or by setting user data on the node.
When a node is dragged to a new position, you query the source node for its position, then you swap the source and target nodes in the grid using:
GridPane.setConstraints(node, columnIndex, rowIndex)
This is essentially the approach you propose in your question.
I do not provide an implementation for this second potential approach.
Example
The images are images of the grid before and after manually dragging the tiles to reorder them.
The example is not going to be exactly what you want, it is purely provided as an example and you will need to adapt it if you wish to make use of some of the concepts in it.
The ImageViewFactory is just to create test images, you can ignore that portion.
Dragging specific stuff is in the DragUtil class.
Code uses Java 18 and newer Java language features, so to compile it you will need to enable the appropriate language level.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.Label;
import javafx.scene.effect.*;
import javafx.scene.image.*;
import javafx.scene.input.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class TileDragApp extends Application {
private static final String TEXT = "abcdefghijklmnop";
private static final int GRID_SIZE = 4;
private static final int TILE_SIZE = 60;
#Override
public void start(Stage stage) {
ImageViewFactory imageViewFactory = new ImageViewFactory();
ImageView[] imageViews = imageViewFactory.makeImageViews(
TEXT,
TILE_SIZE
);
DragUtil dragUtil = new DragUtil();
GridPane grid = new GridPane();
for (int i = 0; i < TEXT.length(); i++) {
dragUtil.makeDraggable(imageViews[i]);
grid.add(imageViews[i], i % GRID_SIZE, i / GRID_SIZE);
}
grid.setGridLinesVisible(true);
grid.setPadding(new Insets(20));
stage.setScene(new Scene(grid));
stage.setResizable(false);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
class ImageViewFactory {
private static final String CSS =
"""
data:text/css,
""" +
// language=CSS
"""
.root {
-fx-background-color: azure;
}
.label {
-fx-font-size: 40px;
-fx-text-fill: navy;
}
""";
public ImageView[] makeImageViews(String text, int tileSize) {
List<Character> chars =
text.chars()
.mapToObj(
c -> (char) c
).collect(
Collectors.toList()
);
Collections.shuffle(chars);
return chars.stream()
.map(
c -> makeImageView(c, tileSize)
).toArray(
ImageView[]::new
);
}
private ImageView makeImageView(char c, int tileSize) {
Label label = new Label(Character.toString(c));
StackPane layout = new StackPane(label);
layout.setPrefSize(tileSize, tileSize);
Scene scene = new Scene(layout);
scene.getStylesheets().add(CSS);
SnapshotParameters snapshotParameters = new SnapshotParameters();
snapshotParameters.setFill(Color.AZURE);
Image image = layout.snapshot(snapshotParameters,null);
return new ImageView(image);
}
}
class DragUtil {
public void makeDraggable(ImageView imageView) {
Effect highlight = createHighlightEffect(imageView);
imageView.setOnDragDetected(e -> {
Dragboard db = imageView.startDragAndDrop(TransferMode.MOVE);
ClipboardContent content = new ClipboardContent();
content.putImage(imageView.getImage());
db.setContent(content);
db.setDragView(makeSmaller(imageView.getImage()));
e.consume();
});
imageView.setOnDragOver(e -> {
if (e.getGestureSource() != imageView
&& e.getDragboard().hasImage()
) {
e.acceptTransferModes(TransferMode.MOVE);
}
imageView.setEffect(highlight);
e.consume();
});
imageView.setOnDragExited(e -> {
imageView.setEffect(null);
e.consume();
});
imageView.setOnDragDropped(e -> {
Dragboard db = e.getDragboard();
boolean success = false;
if (db.hasImage() && e.getGestureSource() instanceof ImageView source) {
source.setImage(imageView.getImage());
imageView.setImage(db.getImage());
success = true;
}
e.setDropCompleted(success);
e.consume();
});
}
private Image makeSmaller(Image image) {
ImageView resizeView = new ImageView(image);
resizeView.setFitHeight(image.getHeight() * 3 / 4);
resizeView.setFitWidth(image.getWidth() * 3 / 4);
SnapshotParameters snapshotParameters = new SnapshotParameters();
return resizeView.snapshot(snapshotParameters, null);
}
private Effect createHighlightEffect(Node n) {
ColorAdjust monochrome = new ColorAdjust();
monochrome.setSaturation(-1.0);
return new Blend(
BlendMode.MULTIPLY,
monochrome,
new ColorInput(
0,
0,
n.getLayoutBounds().getWidth(),
n.getLayoutBounds().getHeight(),
Color.PALEGREEN
)
);
}
}

Related

JavaFX setHgrow / binding property expanding infinitely

This is my code:
public class TestPanel extends ScrollPane {
final int SPACING = 5;
final int ROW_MAX = 6;
public TestPanel(ArrayList<Item> items) {
VBox root = new VBox();
root.setSpacing(SPACING);
HBox row = null;
int count = 0;
for (Item item: items){
if (count == ROW_MAX || row == null){
row = new HBox();
row.setSpacing(SPACING);
root.getChildren().add(row);
count = 0;
}
CustomBox box = new customBox(item);
row.getChildren().add(box);
HBox.setHgrow(box, Priority.ALWAYS);
//box.prefWidthProperty().bind(row.widthProperty()); // Worked for GridPane
count++;
}
setFitToWidth(true);
setContent(root);
}
And this is the custom box node element thing that I'm placing in the V and H Boxes
public class CustomBox extends StackPane {
private Items item;
private Rectangle square;
private int size = 20; // Irrelevent
public CustomBox(NewAirbnbListing item) {
this.item= item;
setPadding(new Insets(5, 5, 5, 5));
square = new Rectangle(size, size, Color.RED);
square.widthProperty().bind(widthProperty());
//square.heightProperty().bind(heightProperty());
minHeightProperty().bind(widthProperty()); // Maintains aspect ratio
getChildren().add(square);
}
The Error: Pink is box, grey is background colour of the StackPane for visual testing.
What I want to do:
What I want: I want the rectangle inside the CustomBox (as well as other components I'll add later) to fill up the size of the StackPane which they sit in and alter their size when the window is resized. I basically want them to mimic the size of that Pane.
Now to try explain whats going on, basically I have a class that's basically a square (There's going to be more stuff later) and I want to fill my "grid" with. When I first did this I used a GridPane to extend my class, I also used the commented out code to bind the properties and it worked "perfectly", it resized how I wanted with no issues, except the fact it lagged like crazy because the items ArrayList contains thousands of items so this will be a very long list. And then later when I was implementing the boxes to store images, that's when the massive lag problems started. My solution to this was to replace the GridPane with the HBox and VBox combo, and it very much fixed the lag issue, it also lets me do things I can't do with the GridPane. However, the problem in the gif I linked is what I'm facing. I've tried every combination of binding properties that I can think of but it still expands like crazy and I just have no idea whats causing it so I really hope someone here can help me out. I don't know tonnes about JavaFX but I'm here to learn, any help is greatly appreciated.
I believe the root cause for your problem lies at: the square width binding with CustomBox width. You might wonder how this is a problem. You are allowing the CustomBox width to be relied on its content (a.k.a square) and your square width is relied on its parent width.. as now these two widths are interdependent to each other.. the width is increased exponentially..
One possible way to fix this, is to calculate the CustomBox size manually based on the ScrollPane viewport width. This way you are controlling the parent width manully and the contents width is handled by binding.
The code in the TestPanel will be:
private DoubleProperty size = new SimpleDoubleProperty();
double padding = 4; // 2px on either side
viewportBoundsProperty().addListener((obs, old, bounds) -> {
size.setValue((bounds.getWidth() - padding - ((ROW_MAX - 1) * SPACING)) / ROW_MAX);
});
CustomBox box = new CustomBox(item);
box.minWidthProperty().bind(size);
A complete working demo is below:
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
public class ScrollPaneContentDemo extends Application {
#Override
public void start(Stage stage) throws Exception {
List<Item> items = new ArrayList<>();
IntStream.range(1, 1000).forEach(i -> items.add(new Item()));
TestPanel root = new TestPanel(items);
Scene scene = new Scene(root, 500, 500);
stage.setScene(scene);
stage.setTitle("ScrollPaneContent Demo");
stage.show();
}
class TestPanel extends ScrollPane {
private final int SPACING = 5;
private final int ROW_MAX = 6;
private DoubleProperty size = new SimpleDoubleProperty();
public TestPanel(List<Item> items) {
final VBox root = new VBox();
root.setSpacing(SPACING);
HBox row = null;
int count = 0;
for (Item item : items) {
if (count == ROW_MAX || row == null) {
row = new HBox();
row.setSpacing(SPACING);
root.getChildren().add(row);
count = 0;
}
CustomBox box = new CustomBox(item);
box.minWidthProperty().bind(size);
row.getChildren().add(box);
HBox.setHgrow(box, Priority.ALWAYS);
count++;
}
setFitToWidth(true);
setContent(root);
double padding = 4;
viewportBoundsProperty().addListener((obs, old, bounds) -> {
size.setValue((bounds.getWidth() - padding - ((ROW_MAX - 1) * SPACING)) / ROW_MAX);
});
}
}
class CustomBox extends StackPane {
private Item item;
private Rectangle square;
private int size = 20;
public CustomBox(Item item) {
setStyle("-fx-background-color:#99999950;");
this.item = item;
setPadding(new Insets(5, 5, 5, 5));
square = new Rectangle(size, size, Color.RED);
square.widthProperty().bind(widthProperty());
square.heightProperty().bind(heightProperty());
maxHeightProperty().bind(minWidthProperty());
maxWidthProperty().bind(minWidthProperty());
minHeightProperty().bind(minWidthProperty());
getChildren().add(square);
}
}
class Item {
}
}

Node.snapshot(null, null) changes size of Scene

I have Scene which is set to the Scene of my primaryStage that - amongst other nodes - contains a VBox with a TableView and some buttons. When I take a snapshot on a row in the table using TableRow.snapshot(null, null), the size of the Scene is changed. The width is changed by about 10 pixels while the height is changed by about 40 - sometimes more than 600 (!) - pixels.
This happens because Node.snapshot(null, null) invokes Scene.doCSSLayoutSyncForSnapshot(Node node) which seems to get the preferred size of all nodes in the size and recalculate the size using that. This somehow returns the wrong values since my nodes only has preferred sizes specified and looks great before this method is invoked. Is there any way to prevent this?
The size change is a problem, but it is also a problem that the primary stage doesn't change size with the Scene that it contains.
I have tried to create an MCVE reproducing the issue, but after a few days of trying to do this, I am still unable to reproduce the problem. The original program contains around 2000 lines of code that I don't want to post here.
Why would Scene.doCSSLayoutSyncForSnapshot(Node node) compromise my layout when it is properly laid out in the first place? Can I somehow make sure that the layout is properly synced before this method is invoked to make sure that it doesn't change anything?
Solved the issue. Had to copy my whole project and then remove parts of the code until the issue disappeared.
Anyway. I basically had three components in my application. A navigation component, a table compontent, and a status bar compontent. It looked like this:
The problem I had was that the width of the status bar and the width and height of the table component was increased whenever I took a snapshot of a row in the table.
Apparently, this was due to the padding of the status bar compontent. It had a right and left padding of 5 pixels, and once I removed the padding, the problem disappeared.
The added 10 pixels in width made the BorderPane that contained all of this expand with the same amount of pixels, and since the table width was bound to the BorderPane width, it increased by the same amount. What I still don't understand though, is why the Stage that contains the BorderPane doesn't adjust to the new width.
The component was properly padded before Scene.doCSSLayoutSyncForSnapshot(Node node) was invoked, so I don't understand why the extra width of ten pixels is added.
Anyhow: Removing the padding from the status bar component and instead padding the components inside the status bar fixed the issue. If someone has a good explanation for this, I'm all ears.
Here's a MCVE where you can reproduce the issue by dragging a row in the table:
import java.io.File;
import java.sql.SQLException;
import java.util.ArrayList;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MCVE extends Application {
private Stage primaryStage;
private BorderPane rootLayout;
private VBox detailsView;
private StatusBar statusBar;
public void start(Stage primaryStage) throws SQLException {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("MCVE");
initRootLayout();
showStatusBar();
showDetailsView();
detailsView.prefWidthProperty().bind(rootLayout.widthProperty());
detailsView.prefHeightProperty().bind(rootLayout.heightProperty());
}
#Override
public void init() throws Exception {
super.init();
}
public void initRootLayout() {
rootLayout = new BorderPane();
primaryStage.setWidth(1000);
primaryStage.setHeight(600);
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
}
public void showStatusBar() {
statusBar = new StatusBar();
rootLayout.setBottom(statusBar);
}
public void showDetailsView() {
detailsView = new VBox();
rootLayout.setCenter(detailsView);
setDetailsView(new Table(this));
detailsView.prefHeightProperty().bind(primaryStage.heightProperty());
detailsView.setMaxHeight(Region.USE_PREF_SIZE);
}
public static void main(String[] args) {
launch(args);
}
public VBox getDetailsView() {
return detailsView;
}
public void setDetailsView(Node content) {
detailsView.getChildren().add(0, content);
}
public StatusBar getStatusBar() {
return statusBar;
}
class StatusBar extends HBox {
public StatusBar() {
setPadding(new Insets(0, 5, 0, 5));
HBox leftBox = new HBox(10);
getChildren().addAll(leftBox);
/**
* CONTROL SIZES
*/
setPrefHeight(28);
setMinHeight(28);
setMaxHeight(28);
// Leftbox takes all the space not occupied by the helpbox.
leftBox.prefWidthProperty().bind(widthProperty());
setStyle("-fx-border-color: black;");
}
}
class Table extends TableView<ObservableList<String>> {
private ObservableList<ObservableList<String>> data;
public Table(MCVE app) {
prefWidthProperty().bind(app.getDetailsView().widthProperty());
prefHeightProperty()
.bind(app.getDetailsView().heightProperty());
widthProperty().addListener((obs, oldValue, newValue) -> {
System.out.println("Table width: " + newValue);
});
setRowFactory(r -> {
TableRow<ObservableList<String>> row = new TableRow<ObservableList<String>>();
row.setOnDragDetected(e -> {
Dragboard db = row.startDragAndDrop(TransferMode.ANY);
db.setDragView(row.snapshot(null, null));
ArrayList<File> files = new ArrayList<File>();
// We create a clipboard and put all of the files that
// was selected into the clipboard.
ClipboardContent filesToCopyClipboard = new ClipboardContent();
filesToCopyClipboard.putFiles(files);
db.setContent(filesToCopyClipboard);
});
row.setOnDragDone(e -> {
e.consume();
});
return row;
});
ObservableList<String> columnNames = FXCollections.observableArrayList("Col1", "col2", "Col3", "Col4");
data = FXCollections.observableArrayList();
for (int i = 0; i < columnNames.size(); i++) {
final int colIndex = i;
TableColumn<ObservableList<String>, String> column = new TableColumn<ObservableList<String>, String>(
columnNames.get(i));
column.setCellValueFactory((param) -> new SimpleStringProperty(param.getValue().get(colIndex).toString()));
getColumns().add(column);
}
// Adds all of the data from the rows the data list.
for (int i = 0; i < 100; i++) {
// Each column from the row is a String in the list.
ObservableList<String> row = FXCollections.observableArrayList();
row.add("Column 1");
row.add("Column 2");
row.add("Column 3");
row.add("Column 4");
// Adds the row to data.
data.add(row);
}
// Adds all of the rows in data to the table.
setItems(data);
}
}
}
This answer talks about it a little bit
Set scene width and height
but after diving into the source code I found that the resizing in snapshot is conditional on the scene never having a size set by one of its constructors.
You can only set a scene's size in its constructors and never again. That makes a little bit of sense, since its otherwise only used to size the window that contains it. It is unfortunate that the snapshot code is not smart enough to use the window's dimensions when set by the user in addition to the scene's possible user settings.
None of this prevents resizing later, so if you depend on taking snapshots, you may want to make a best practice out of using the Scene constructors which take a width and height and sending them something above 0

How to resize an image when resizing the window in JavaFX

I want an image to be resized automatically when the user drags the main window. Is that possible?
I have the following code that sets a window of a certain size. It also loads the image from an external URL.
#Override
public void start(Stage primaryStage) {
MenuBar menuBar=new MenuBar();
Menu menuGame = new Menu("Game");
MenuItem newGame = new MenuItem("New Game F1");
MenuItem exit = new MenuItem("Exit F2");
exit.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
primaryStage.close();
}
});
menuGame.getItems().addAll(newGame,new SeparatorMenuItem(),exit);
menuBar.getMenus().addAll(menuGame);
Image image = new Image("http://docs.oracle.com/javafx/"
+ "javafx/images/javafx-documentation.png");
ImageView imageView = new ImageView();
imageView.setImage(image);
VBox vbox=new VBox();
StackPane root=new StackPane();
root.getChildren().addAll(imageView);
vbox.getChildren().addAll(menuBar,root);
Scene scene= new Scene(vbox,400,400);
primaryStage.setScene(scene);
primaryStage.setMaxHeight(800);
primaryStage.setMinHeight(400);
primaryStage.setMaxWidth(1000);
primaryStage.setMinWidth(800);
primaryStage.setTitle("Minesweeper");
primaryStage.show();
}
Applying the solution to JavaFx image resizing to your sample code and resizing the window results in different image sizes for me.
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.beans.value.*;
import javafx.event.*;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class ImageResizer extends Application {
#Override
public void start(Stage primaryStage) {
MenuBar menuBar=new MenuBar();
Menu menuGame = new Menu("Game");
MenuItem newGame = new MenuItem("New Game F1");
MenuItem exit = new MenuItem("Exit F2");
exit.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
primaryStage.close();
}
});
menuGame.getItems().addAll(newGame,new SeparatorMenuItem(),exit);
menuBar.getMenus().addAll(menuGame);
Image image = new Image("http://docs.oracle.com/javafx/"
+ "javafx/images/javafx-documentation.png");
ImageView imageView = new ImageView();
imageView.setImage(image);
ImageViewPane viewPane = new ImageViewPane(imageView);
VBox vbox=new VBox();
StackPane root=new StackPane();
root.getChildren().addAll(viewPane);
vbox.getChildren().addAll(menuBar,root);
VBox.setVgrow(root, Priority.ALWAYS);
Scene scene= new Scene(vbox,200,200);
primaryStage.setScene(scene);
primaryStage.setMaxHeight(400);
primaryStage.setMinHeight(200);
primaryStage.setMaxWidth(500);
primaryStage.setMinWidth(400);
primaryStage.setTitle("Minesweeper");
primaryStage.show();
}
public static void main(String[] args) { launch(); }
}
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*/
/**
*
* #author akouznet
*/
class ImageViewPane extends Region {
private ObjectProperty<ImageView> imageViewProperty = new SimpleObjectProperty<ImageView>();
public ObjectProperty<ImageView> imageViewProperty() {
return imageViewProperty;
}
public ImageView getImageView() {
return imageViewProperty.get();
}
public void setImageView(ImageView imageView) {
this.imageViewProperty.set(imageView);
}
public ImageViewPane() {
this(new ImageView());
}
#Override
protected void layoutChildren() {
ImageView imageView = imageViewProperty.get();
if (imageView != null) {
imageView.setFitWidth(getWidth());
imageView.setFitHeight(getHeight());
layoutInArea(imageView, 0, 0, getWidth(), getHeight(), 0, HPos.CENTER, VPos.CENTER);
}
super.layoutChildren();
}
public ImageViewPane(ImageView imageView) {
imageViewProperty.addListener(new ChangeListener<ImageView>() {
#Override
public void changed(ObservableValue<? extends ImageView> arg0, ImageView oldIV, ImageView newIV) {
if (oldIV != null) {
getChildren().remove(oldIV);
}
if (newIV != null) {
getChildren().add(newIV);
}
}
});
this.imageViewProperty.set(imageView);
}
}
Alternate approaches and extra information based upon comments
If you have to do all that, then it's a weakness in he JavaFX platform. Ideally I would expect there to have been a scale property on the image that one could set so that it uses the SceneGraph to determine it's size.
The solution presented above is just one answer, there are others possible. The scene graph can be used by binding various properties to the width and height of the parent node or by overriding layoutChildren in the parent node.
Related properties:
There is a scale property which can be applied to any node.
A node also has a transform list to which a Scale may be applied.
ImageView has a fitWidth and fitHeight properties (use of this is demonstrated in other answers).
I prefer the region subclass approach to a scale property or transform based approach because then the image is automatically sized based upon the layout manager. The ImageViewPane defined above is just a one-off definition class which can be reused as much as you want, the actual application code to use an ImageView or ImageViewPane is pretty much equivalent.
Another possible approach is to use a Region subclass (such as a Pane), which has a defined CSS style class or id and then to define the image in CSS as a background. The nice thing about a CSS defined image is that you can then use additional CSS based attributes to define things, like sizing, scaling, positioning and repeats for the image. This background can also be set programmatically if desired rather than via CSS.
Related question:
Resizing images to fit the parent node
To maintain proportional height and width of the image
The following code can be used in the ImageViewPane class provided above:
if (imageView.isPreserveRatio()) {
if (getHeight() > getWidth()) {
imageView.setFitWidth(getWidth());
imageView.setFitHeight(0);
} else {
imageView.setFitWidth(0);
imageView.setFitHeight(getHeight());
}
} else {
imageView.setFitWidth(getWidth());
imageView.setFitHeight(getHeight());
}
I know this is old but someone like me may stumble upon this so here is my solution. I am assuming that root is the root node of your scene. However, this should work if your parent node of the ImageView is any child of Pane.
imv = new ImageView();
root.getChildren().add(imv);
Image image = new Image(Main.class.getResourceAsStream("image.png"))
imv.setImage(image);
imv.fitWidthProperty().bind(center.widthProperty());
imv.fitHeightProperty().bind(center.heightProperty());
Just use a normal pane with a background image, here the css needed.
#titleImage{
-fx-background-image: url("../img/logo.png");
-fx-background-repeat: stretch;
-fx-background-position: center center;
}
Are you looking to resize an actual image, or just to center it?
You can try this: VBox.setVgrow(root, Priority.ALWAYS); and see if that's what you want.
Also, be careful with you image, because it's a png image. I'm a bit skeptic regarding what's resized there (the actual image or the transparent background).

JavaFX effect on background

I'm using this to make a iOS-themed JavaFX2 (Java7) application with a frosted glass effect. The problem is that this code uses its effect on an ImageView. I'd like it to use its effect on whatever's behind the window, like this:
Is there anyway to do that? I'd also like that small drop-shadow effect you see around the above image.
To be clear, I don't want that slider or anything, just the effect of being able to see through the window and having that slight shadow around the edges. I want to use this iOS7-ish effect instead of aero, though.
This might be important: I'm using a modified version of Undecorator.
import javafx.animation.*;
import javafx.application.*;
import javafx.beans.property.*;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.control.Label;
import javafx.scene.effect.*;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.image.*;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
public class FrostyTech extends Application {
private static final double BLUR_AMOUNT = 10;
private static final Effect frostEffect =
new BoxBlur(BLUR_AMOUNT, BLUR_AMOUNT, 3);
private static final ImageView background = new ImageView();
private static final StackPane layout = new StackPane();
#Override public void start(Stage stage) {
layout.getChildren().setAll(background, createContent());
layout.setStyle("-fx-background-color: null");
Scene scene = new Scene(
layout,
200, 300,
Color.TRANSPARENT
);
Platform.setImplicitExit(false);
scene.setOnMouseClicked(event -> {
if (event.getClickCount() == 2) Platform.exit();
});
makeSmoke(stage);
stage.initStyle(StageStyle.TRANSPARENT);
stage.setScene(scene);
stage.show();
background.setImage(copyBackground(stage));
background.setEffect(frostEffect);
makeDraggable(stage, layout);
}
// copy a background node to be frozen over.
private Image copyBackground(Stage stage) {
final int X = (int) stage.getX();
final int Y = (int) stage.getY();
final int W = (int) stage.getWidth();
final int H = (int) stage.getHeight();
try {
java.awt.Robot robot = new java.awt.Robot();
java.awt.image.BufferedImage image = robot.createScreenCapture(new java.awt.Rectangle(X, Y, W, H));
return SwingFXUtils.toFXImage(image, null);
} catch (java.awt.AWTException e) {
System.out.println("The robot of doom strikes!");
e.printStackTrace();
return null;
}
}
// create some content to be displayed on top of the frozen glass panel.
private Label createContent() {
Label label = new Label("Create a new question for drop shadow effects.\n\nDrag to move\n\nDouble click to close");
label.setPadding(new Insets(10));
label.setStyle("-fx-font-size: 15px; -fx-text-fill: green;");
label.setMaxWidth(250);
label.setWrapText(true);
return label;
}
// makes a stage draggable using a given node.
public void makeDraggable(final Stage stage, final Node byNode) {
final Delta dragDelta = new Delta();
byNode.setOnMousePressed(mouseEvent -> {
// record a delta distance for the drag and drop operation.
dragDelta.x = stage.getX() - mouseEvent.getScreenX();
dragDelta.y = stage.getY() - mouseEvent.getScreenY();
byNode.setCursor(Cursor.MOVE);
});
final BooleanProperty inDrag = new SimpleBooleanProperty(false);
byNode.setOnMouseReleased(mouseEvent -> {
byNode.setCursor(Cursor.HAND);
if (inDrag.get()) {
stage.hide();
Timeline pause = new Timeline(new KeyFrame(Duration.millis(50), event -> {
background.setImage(copyBackground(stage));
layout.getChildren().set(
0,
background
);
stage.show();
}));
pause.play();
}
inDrag.set(false);
});
byNode.setOnMouseDragged(mouseEvent -> {
stage.setX(mouseEvent.getScreenX() + dragDelta.x);
stage.setY(mouseEvent.getScreenY() + dragDelta.y);
layout.getChildren().set(
0,
makeSmoke(stage)
);
inDrag.set(true);
});
byNode.setOnMouseEntered(mouseEvent -> {
if (!mouseEvent.isPrimaryButtonDown()) {
byNode.setCursor(Cursor.HAND);
}
});
byNode.setOnMouseExited(mouseEvent -> {
if (!mouseEvent.isPrimaryButtonDown()) {
byNode.setCursor(Cursor.DEFAULT);
}
});
}
private javafx.scene.shape.Rectangle makeSmoke(Stage stage) {
return new javafx.scene.shape.Rectangle(
stage.getWidth(),
stage.getHeight(),
Color.WHITESMOKE.deriveColor(
0, 1, 1, 0.08
)
);
}
/** records relative x and y co-ordinates. */
private static class Delta {
double x, y;
}
public static void main(String[] args) {
launch(args);
}
}
Related Questions
Frosted Glass Effect in JavaFX?
How do I create a JavaFX transparent stage with shadows on only the border?
The visual effect that you want for OS dependent window decoration, can only be achieved through the APIs that OS provides. And thus was eliminated by StageStyle.TRANSPARENT below.
For JavaFX content itself, you can control the visuals of the stage > scene > root pane hierarchy. Stage and scene do not (and not aimed to) support advanced stylings so were eliminated by setting as transparent below.
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
root.setStyle("-fx-background-color: null;");
root.setPadding(new Insets(10));
DoubleProperty doubleProperty = new SimpleDoubleProperty(0);
Region region = new Region();
region.styleProperty().bind(Bindings
.concat("-fx-background-radius:20; -fx-background-color: rgba(56, 176, 209, ")
.concat(doubleProperty)
.concat(");"));
region.setEffect(new DropShadow(10, Color.GREY));
Slider slider = new Slider(0, 1, .3);
doubleProperty.bind(slider.valueProperty());
root.getChildren().addAll(region, slider);
primaryStage.initStyle(StageStyle.TRANSPARENT);
Scene scene = new Scene(root, 300, 250);
scene.setFill(Color.TRANSPARENT);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
However the drop shadow effect does not play well with alpha value of the background color. You can observe it by changing the shadow's color to another contrast one.
Output:
To expand on Jewlsea's answer .. And using the above example with JavaFX ONLY ..
While the classes are not public API, it does avoid the AWT stack completely.
Here is a non public example :
// copy a background node to be frozen over.
private Image copyBackground(Stage stage) {
final int X = (int) stage.getX();
final int Y = (int) stage.getY();
final int W = (int) stage.getWidth();
final int H = (int) stage.getHeight();
final Screen screen = Screen.getPrimary();
try {
Robot rbt = com.sun.glass.ui.Application.GetApplication().createRobot();
Pixels p = rbt.getScreenCapture(
(int)screen.getBounds().getMinX(),
(int)screen.getBounds().getMinY(),
(int)screen.getBounds().getWidth(),
(int)screen.getBounds().getHeight(),
true
);
WritableImage dskTop = new WritableImage((int)screen.getBounds().getWidth(), (int)screen.getBounds().getHeight());
dskTop.getPixelWriter().setPixels(
(int)screen.getBounds().getMinX(),
(int)screen.getBounds().getMinY(),
(int)screen.getBounds().getWidth(),
(int)screen.getBounds().getHeight(),
PixelFormat.getByteBgraPreInstance(),
p.asByteBuffer(),
(int)(screen.getBounds().getWidth() * 4)
);
WritableImage image = new WritableImage(W,H);
image.getPixelWriter().setPixels(0, 0, W, H, dskTop.getPixelReader(), X, Y);
return image;
} catch (Exception e) {
System.out.println("The robot of doom strikes!");
e.printStackTrace();
return null;
}
}
Results with a small dropshadow added:
DropShadow shdw = new DropShadow();
shdw.setBlurType(BlurType.GAUSSIAN);
shdw.setColor(Color.GAINSBORO);
shdw.setRadius(10);
shdw.setSpread(0.12);
shdw.setHeight(10);
shdw.setWidth(10);
layout.setEffect(shdw);
The opacity is a property of Node, which is the parent class in JavaFX for things that show up on the screen. http://docs.oracle.com/javafx/2/api/javafx/scene/Node.html#opacityProperty
So you can just set the opacity on the object that you want to have fade away. You then have to add some sort of way to change the opacity on the desired object. Using the slider from your image is one way, but there are others.
Drop shadows can be done using the DropShadow effect... http://docs.oracle.com/javafx/2/api/javafx/scene/effect/DropShadow.html. I have never used it. This is a little high level but if there are follow up questions in the comments I can help answer them.

ScrollPane is not refreshing its contents

RESUME
Good day StackOverflow community.
I've been trying for some time to develop a program that enables users to put objects in an area, allowing this area to be moved by the mouse. For this type of program, I decided to use a ScrollPane, because the user can add various contents in the area which I call the canvas. For some reason, something strange is happening in my program.
EXPLANATION OF PROGRAM
What I basically did was create a group of objects, and define this group as the ScrollPane content. Within the group, there is a Rectangle object that was added to serve as canvas boundaries. This object has larger dimensions (such as 1500 x 1000, for example), and is used in calculations that prevent nodes from moving beyond its limits. This is just the logical behind the existing large rectangle in my program, but in reality, there is no Node object with the mouse movement. What exists is the random distribution of Shape objects by the rectangle area.
For ScrollPane has its scrollbars moved, I use the setHvalue setVvalue methods. Unfortunately for my purposes, this method does not change the position of the ScrollPane's viewport with pixel values​​, but values ​​that are in a range between 0f and 1f. So I can move the viewport with the mouse, I used a equation known as Rule of 3 (here in my Country, as I know), which we equate values ​​and cross multiply.
For example, say I want to move the viewport of the ScrollPane with the mouse horizontally, and that my canvas area has a width of 2000 pixels. Finding how far (in pixels) the mouse was dragged from one point to another, I need to know how this value represents in a range 0f to 1f. Suppose I have dragged the mouse in 3 pixels, I could find the representation of the 0f to 1f with the following comparison:
2000 px ---- 1f
3 px ---- xf
Multiplying crossed, I'll get the following result:
xf = 3 / 2000
xf = 0.0015
Note: I believe you all know that. I'm not teaching math to anyone,
just want to explain the logic of my problem.
SOURCE CODE
Here is my program class:
import testes.util.TestesUtil;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
public class ScrollTest4 extends Application
{
// #########################################################################################################
// MAIN
// #########################################################################################################
public static void main(String[] args)
{
Application.launch(args);
}
// #########################################################################################################
// INSTÂNCIAS
// #########################################################################################################
// OUTSIDE
private BorderPane root;
private Button but_moreH;
private Button but_lessH;
private Button but_moreV;
private Button but_lessV;
// LOG
private VBox vbox_south;
private Label lab_hValue;
private Label lab_vValue;
private Label lab_viewport;
// INSIDE
private Rectangle rec_canvas;
private ScrollPane scroll;
private Group grp_objects;
// MOUSE
private double mouse_x = 0;
private double mouse_y = 0;
// MISC
private AnimationTimer timer;
// EDITED - 08/02/2014
private boolean moreH = false;
private boolean moreV = false; // Purposely unused.
private boolean lessH = false;
private boolean lessV = false; // Purposely unused.
// #########################################################################################################
// INÍCIO FX
// #########################################################################################################
#Override public void start(Stage estagio) throws Exception
{
this.iniFX();
this.confFX();
this.adFX();
this.evFX();
Scene cenario = new Scene(this.root , 640 , 480);
estagio.setScene(cenario);
estagio.setTitle("Programa JavaFX");
estagio.show();
}
protected void iniFX()
{
// OUTSIDE
this.root = new BorderPane();
this.but_moreH = new Button();
this.but_lessH = new Button();
this.but_moreV = new Button();
this.but_lessV = new Button();
// LOG
this.vbox_south = new VBox();
this.lab_hValue = new Label();
this.lab_vValue = new Label();
this.lab_viewport = new Label();
// INSIDE
this.scroll = new ScrollPane();
this.grp_objects = new Group();
this.rec_canvas = new Rectangle();
// MISC
this.timer = new AnimationTimer()
{
#Override public void handle(long now)
{
// EDITED - 08/02/2014
if(but_moreH.isArmed() || moreH)
{
// scroll.hvalueProperty().set(scroll.hvalueProperty().get() + 0.003f);
scroll.setHvalue(scroll.getHvalue() + 0.003f);
}
// EDITED - 08/02/2014
if(but_lessH.isArmed() || lessH)
{
// scroll.hvalueProperty().set(scroll.hvalueProperty().get() - 0.003f);
scroll.setHvalue(scroll.getHvalue() - 0.003f);
}
if(but_moreV.isArmed())
{
scroll.setVvalue(scroll.getVvalue() + 0.003f);
}
if(but_lessV.isArmed())
{
scroll.setVvalue(scroll.getVvalue() - 0.003f);
}
}
};
this.timer.start();
}
protected void confFX()
{
// OUTSIDE
this.but_moreH.setText("More H");
this.but_moreH.setMaxHeight(Double.MAX_VALUE);
this.but_lessH.setText("Less H");
this.but_lessH.setMaxHeight(Double.MAX_VALUE);
this.but_moreV.setText("More V");
this.but_moreV.setMaxWidth(Double.MAX_VALUE);
this.but_lessV.setText("Less V");
this.but_lessV.setMaxWidth(Double.MAX_VALUE);
// LOG
this.updateHvalue();
this.updateVvalue();
this.updateViewport();
// INSIDE
this.rec_canvas.setWidth(1200);
this.rec_canvas.setHeight(1000);
this.rec_canvas.setFill(Color.INDIANRED);
this.rec_canvas.setStroke(Color.RED);
this.rec_canvas.setStrokeType(StrokeType.INSIDE);
this.rec_canvas.setStrokeWidth(1);
}
protected void adFX()
{
// LOG
this.vbox_south.getChildren().add(this.but_moreV);
this.vbox_south.getChildren().addAll(this.lab_hValue , this.lab_vValue , this.lab_viewport);
// OUTSIDE
this.root.setCenter(this.scroll);
this.root.setTop(this.but_lessV);
this.root.setBottom(this.vbox_south);
this.root.setRight(this.but_moreH);
this.root.setLeft(this.but_lessH);
// INSIDE
this.grp_objects.getChildren().add(this.rec_canvas);
this.scroll.setContent(this.grp_objects);
// MISC
StrokeType[] strokes = {StrokeType.CENTERED , StrokeType.INSIDE , StrokeType.OUTSIDE};
for(int cont = 0 ; cont < 20 ; cont++)
{
Rectangle node = new Rectangle(Math.random() * 100 + 50 , Math.random() * 100 + 50);
node.setFill(TestesUtil.getCorAleatoria(false));
node.setStroke(TestesUtil.getCorAleatoria(false));
node.setStrokeType(strokes[(int) (Math.random() * 2)]);
node.setStrokeWidth(Math.random() * 9 + 1);
node.setRotate(Math.random() * 360);
node.setMouseTransparent(true);
// EDITED - 08/02/2014
TestsUtil.putRandomlyIn(
node ,
rec_canvas.getBoundsInParent().getMinY() ,
rec_canvas.getBoundsInParent().getMinY() + rec_canvas.getBoundsInParent().getHeight() ,
rec_canvas.getBoundsInParent().getMinX() + rec_canvas.getBoundsInParent().getWidth() ,
rec_canvas.getBoundsInParent().getMinX() );
this.grp_objects.getChildren().add(node);
}
}
protected void evFX()
{
// ##########################
// SCROLL PROPERTIES
// ##########################
this.scroll.hvalueProperty().addListener(new ChangeListener<Number>()
{
#Override public void changed(ObservableValue<? extends Number> observable,Number oldValue, Number newValue)
{
updateHvalue();
updateViewport();
}
});
this.scroll.vvalueProperty().addListener(new ChangeListener<Number>()
{
#Override public void changed(ObservableValue<? extends Number> observable,Number oldValue, Number newValue)
{
updateVvalue();
updateViewport();
}
});
this.scroll.setOnKeyPressed(new EventHandler<KeyEvent>()
{
#Override public void handle(KeyEvent e)
{
if(e.getCode() == KeyCode.RIGHT)
{
moreH = true;
}
else if(e.getCode() == KeyCode.LEFT)
{
lessH = true;
}
}
});
this.scroll.setOnKeyReleased(new EventHandler<KeyEvent>()
{
#Override public void handle(KeyEvent e)
{
if(e.getCode() == KeyCode.RIGHT)
{
moreH = false;
}
else if(e.getCode() == KeyCode.LEFT)
{
lessH = false;
}
}
});
// ##########################
// CANVAS
// ##########################
this.rec_canvas.setOnMousePressed(new EventHandler<MouseEvent>()
{
#Override public void handle(MouseEvent e)
{
// The XY distance from the upper left corner of the canvas.
mouse_x = e.getX();
mouse_y = e.getY();
}
});
this.rec_canvas.setOnMouseDragged(new EventHandler<MouseEvent>()
{
#Override public void handle(MouseEvent e)
{
// ##########################
// PIXELS
// ##########################
// The distance between mouse movements (drag events).
double xPixelsMoved = e.getX() - mouse_x;
// double yPixelsMoved = e.getY() - mouse_y;
// ##########################
// TO 1F
// ##########################
double h_of_1f = xPixelsMoved / rec_canvas.getBoundsInParent().getWidth();
double h_of_1f_inverted = h_of_1f * -1;
double currentH = scroll.getHvalue();
scroll.setHvalue(currentH + h_of_1f);
// scroll.hvalueProperty().set(scroll.getHvalue() + h_de_x);
// scroll.vvalueProperty().set(scroll.getVvalue() + v_de_y);
// ##########################
// DEBUG
// ##########################
System.out.printf("xPixelsMoved: %f , h_of_1f: %f , h_of_1f_inverted: %f %n",
xPixelsMoved , h_of_1f , h_of_1f_inverted);
// ##########################
// UPDATE FROM
// EVENT TO EVENT
// ##########################
// Writes last mouse position to update on new motion event.
mouse_x = e.getX();
mouse_y = e.getY();
}
});
}
// #########################################################################################################
// MISC.
// #########################################################################################################
protected void updateViewport()
{
Bounds vport = this.scroll.getViewportBounds();
this.lab_viewport.setText(String.format("Viewport - [X: %f , Y: %f , W: %f , H: %f]",
vport.getMinX() , vport.getMinY() , vport.getWidth() , vport.getHeight() ));
}
protected void updateHvalue()
{
this.lab_hValue.setText("H value: " + this.scroll.getHvalue() );
}
protected void updateVvalue()
{
this.lab_vValue.setText("V value: " + this.scroll.getVvalue() );
}
}
THE PROBLEM
Clicking the mouse button on the canvas area and drag it, you can see that the program moves the ScrollPane viewport horizontally. The program seems to work perfectly (or not). However, something goes wrong at the time when the mouse is dragged sometimes abruptly (...or not!). At certain times the ScrollPane Viewport is not visually updated. This is a strange behavior, because even if viewport is not visually updated, the scrollbars are still updated.
I put other ways to move the ScrollPane viewport horizontally using the same method, and for some reason, only the approach using the mouse makes it happen. I thought this could be solved by making a request for layout using requestLayout, also causing a request to a pulse, but it does not work.
THE TEST OUTPUT
The odd thing is that everything returns to normal when the window of my application is resized. Here's a video that shows what happens to my program:
VIDEO & MIRROR 1
I no longer know what else to do. Can anyone help me with this please?
EDIT (08/02/2014 10:08 AM GMT - 3:00)
The original source code of my application is found written in Portuguese, so you may be seeing something unknown. Basically TestesUtil is a utility class with static methods that define shortcuts to other client classes. I changed the call from my source code shown here previously and am now putting some methods of my class TestesUtil, translated into English as TestsUtil:
public static void putRandomlyIn(Node node , double northPoint , double southPoint , double eastPoint , double westPoint)
{
node.setLayoutX(Math.random() * pontoLeste);
node.setLayoutY(Math.random() * pontoSul);
fixEasternBoundary(node , eastPoint);
fixNorthernBoundary(node , northPoint);
fixWesternBoundary(node , westPoint);
fixSouthernBoundary(node , southPoint);
}
There is no mystery here. This method simply calculates a value from an interval, and defines the LayoutXY properties for the Node argument. Methods "fix ..." just check the boundsInParent bounds of the node compared to the point in the argument, and then adjust the layoutXYproperties from the Node object. Even if I remove the random distribution of objects, the problem still happens. So I'm sure this problem is not being caused by this.
The source code of the original post was changed with the addition of the ability to move the scroll bars with the arrow keys. Even if it is already an existing function of ScrollPane, adding that could reproduce the error seen with the mouse (now with arrows). Some things were also translated into English for better understanding by the community.
Please, I ask for help. I'm getting dizzy not knowing what to do. This type of situation could be happening because of some bug in JavaFX? Ahhrr... Please somebody help me in this. :'(
Thank you for your attention anyway.
EDIT (09/02/2014 10:50 AM GMT - 3:00)
Forgot to mention... My program was initially written and tested using JDK 8 b123. Currently I installed the JDK 8 b128 version and am still getting the same problem. My operating system is Windows 7 64x.
I am almost certain that this is a bug. Are you guys getting the same result as me? Or am I the only one to find this kind of problem? If this is a bug, which procedure should be taken?
Thank you for your attention.
EDIT (10/02/2014 09:45 AM GMT - 3:00)
A bounty was started.
UPDATE
This bug has now been fixed for JavaFX 8u20.
Bug description
This is a bug, that can be easily verified by executing the following code with JavaFx JRE 8:
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
final ScrollPane sp = new ScrollPane();
final Image[] images = new Image[5];
final ImageView[] pics = new ImageView[5];
final VBox vb = new VBox();
final Label fileName = new Label();
final String [] imageNames = new String [] {"fw1.jpg", "fw2.jpg",
"fw3.jpg", "fw4.jpg", "fw5.jpg"};
#Override
public void start(Stage stage) {
VBox box = new VBox();
Scene scene = new Scene(box, 180, 180);
stage.setScene(scene);
stage.setTitle("Scroll Pane");
box.getChildren().addAll(sp, fileName);
VBox.setVgrow(sp, Priority.ALWAYS);
fileName.setLayoutX(30);
fileName.setLayoutY(160);
for (int i = 0; i < 5; i++) {
images[i] = new Image(getClass().getResourceAsStream(imageNames[i]));
pics[i] = new ImageView(images[i]);
pics[i].setFitWidth(100);
pics[i].setPreserveRatio(true);
vb.getChildren().add(pics[i]);
}
sp.setVmax(440);
sp.setPrefSize(115, 150);
sp.setContent(vb);
sp.vvalueProperty().addListener(new ChangeListener<Number>() {
public void changed(ObservableValue<? extends Number> ov,
Number old_val, Number new_val) {
fileName.setText(imageNames[(new_val.intValue() - 1)/100]);
}
});
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This code comes directly from the JavaFX ScrollPane Tutorial.
If one randomly moves the vertical scroll bar with the mouse very rapidly, then at some time the screen will freeze and no longer get updated. Although one is still able to move the scroll bar around, the displayed images will stay fixed. Only if one resizes the frame, the display of the images will be updated and the ScrollPane reverts to its previous state. Note, that this bug will only happen in JRE 8, it is not reproducible in JRE 7.
The only workaround for the problem, that I could find, is adding
sp.snapshot(new SnapshotParameters(), new WritableImage(1, 1));
to the listener:
sp.vvalueProperty().addListener(new ChangeListener<Number>() {
public void changed(ObservableValue<? extends Number> ov,
Number old_val, Number new_val) {
fileName.setText(imageNames[(new_val.intValue() - 1)/100]);
sp.snapshot(new SnapshotParameters(), new WritableImage(1, 1));
}
});
Calling snapshot on the ScrollPane seems to force the update every time the vvalueProperty changes. This seems to be a known workaround for several update problems with JavaFX - see here.

Categories

Resources