How to force Java FX scene refresh? - java

I have an Java FX scene with a start button and several rectangles which represent the tiles of a map. I also have drawn a sphere which represents my explorer (it has to explore the map), but I am having difficulties with running the animation.
In my OnMouseClicked handler for the start button, I start an algorithm for exploring the map which changes the position of the sphere and the colors of the tiles which have been visited. The problem is that the scene won't update itself while the algorithm is running, so I only get to see how the final scene will look like (after the algorithm has stopped running). How can I force a scene update so I can see all the color changes sequentially?
Later edit:
import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Test extends Application {
private static final double boxOuterSize = 50;
private static final double boxInnerSize = 48;
private static final double boxCornerRadius = 20;
private Stage applicationStage;
private Scene applicationScene;
private static double sceneWidth = 1024;
private static double sceneHeight = 800;
private static HBox container = new HBox();
private static Group root = new Group();
private Rectangle[] rectangles = new Rectangle[10];
#Override
public void start(Stage mainStage) throws Exception {
applicationStage = mainStage;
container.setSpacing(10);
container.setPadding(new Insets(10, 10, 10, 10));
try {
applicationScene = new Scene(container, sceneWidth, sceneHeight);
applicationScene.addEventHandler(EventType.ROOT,(EventHandler<? super Event>)this);
applicationScene.setFill(Color.WHITE);
} catch (Exception exception) {
System.out.println ("exception : "+exception.getMessage());
}
applicationStage.setTitle("HurtLockerRobot - Tema 3 IA");
applicationStage.getIcons().add(new Image("icon.png"));
applicationStage.setScene(applicationScene);
for(int i=0; i<10; i++) {
Rectangle r = new Rectangle();
r.setFill(Color.BLUE);
r.setX(i * boxOuterSize);
r.setY(0);
r.setWidth(boxInnerSize);
r.setHeight(boxInnerSize);
r.setArcHeight(boxCornerRadius);
r.setArcWidth(boxCornerRadius);
r.setSmooth(true);
rectangles[i] = r;
root.getChildren().add(rectangles[i]);
}
container.getChildren().add(root);
Button startButton = new Button("Start");
startButton.setOnMouseClicked(new EventHandler<Event>() {
#Override
public void handle(Event arg0) {
for(int i=0; i<10; i++) {
rectangles[i].setFill(Color.RED);
// TODO: some kind of scene refresh here
}
}
});
container.getChildren().add(startButton);
applicationStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Initially all the rectangles are blue. The behavior I want to obtain here is to see the rectangles changing colors sequentially. The problem is that I only get to see the end result (all the rectangles change their color at the same time).

This is an old question and it caught my eye since this is a very general issue faced by people new to JavaFX.
The problem that OP is facing is because he updates all the rectangles at once, without waiting.
OP can wait by either creating a new Thread, put the thread on sleep for an estimated seconds for every iteration of the loop and then update the color of the rectangle on JavaFX application thread by using Platform.runLater.
#Override
public void handle(Event arg0) {
new Thread(() -> {
for(int i=0; i<10; i++) {
try {
Thread.sleep(1000); // Wait for 1 sec before updating the color
} catch (InterruptedException e) {
e.printStackTrace();
}
int finalI = i;
Platform.runLater(() -> rectangles[finalI].setFill(Color.RED));// Update on JavaFX Application Thread
}
}).start();
The above snippet is more of a traditional way of doing things. If we want to use the "JavaFX" ways of doing things, we can achieve the same by using an Animation.
Below is a code snippet which will wait for x-seconds before changing the color of the rectangle. It doesn't need any extra thread since the wait is handled by PauseTransition applied for each rectangle.
startButton.setOnMouseClicked(new EventHandler<Event>() {
#Override
public void handle(Event arg0) {
for(int i=0; i<10; i++) {
PauseTransition pauseTransition = new PauseTransition(Duration.seconds(i));
int finalI = i;
pauseTransition.setOnFinished(event -> rectangles[finalI].setFill(Color.RED));
pauseTransition.play();
}
}
});
It creates a PauseTransition for each rectangle and depending on its index in the array rectangles, it waits for the same number of seconds before updating the color.

This is because of :
exception : Test cannot be cast to javafx.event.EventHandler
Well, I have no idea how Class cast exception came up.
Otherwise, to delay, you can use Thread.sleep().
UPDATE:
Its good to use AnimationTimer to create an animation, you don't need to refresh anything.
Here, I have done a short EG to show color rect using FillTransition.
CODE:
import javafx.animation.FillTransition;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class NewFXMain1 extends Application {
private static final double boxOuterSize = 50;
private static final double boxInnerSize = 48;
private static final double boxCornerRadius = 20;
private Rectangle rectangles = new Rectangle();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("rect");
Button btn = new Button();
StackPane root = new StackPane();
Rectangle r = new Rectangle();
r.setFill(Color.BLUE);
r.setX(2 * boxOuterSize);
r.setY(0);
r.setWidth(boxInnerSize);
r.setHeight(boxInnerSize);
r.setArcHeight(boxCornerRadius);
r.setArcWidth(boxCornerRadius);
r.setSmooth(true);
r.localToScene(boxOuterSize, boxOuterSize);
rectangles = r;
root.getChildren().add(rectangles);
btn.setText("display");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
FillTransition ft = new FillTransition(Duration.millis(3000), rectangles, Color.RED, Color.BLUE);
ft.setCycleCount(4);
ft.setAutoReverse(true);
ft.play();
}
});
root.getChildren().add(btn);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
}
}

Related

Javafx - Update progress indicator in UI from java class

The following are the changes I made in fxml
Changes in the java file , here my code :
private ProgressIndicator pi;
void handlebuildButtonAction(ActionEvent event) throws IOException, GeneralSecurityException {
if ((entServer.isSelected()==true || compasServer.isSelected()==true)) {
if(!fileList.isEmpty()){
ProgressIndicator pi = new ProgressIndicator();
pi.setProgress(10);
}
}
The progress indicator is not updated when I run the application. I'm not sure how to sync the changes to UI. Assist me on this. Thanks in advance.
output
For example: if you set 0.1 - progress will be 10%, 0.2 - 20% and so on, so when you set the progress => 1 you will always have "done".
Here, this an example with a button, when you click the button, your progress indicator will be updated(one click + 10%):
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.*;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.stage.Stage;
public class Test extends Application {
private ProgressIndicator pi;
private double counter = 0;
public void start(Stage stage)
{
ProgressIndicator pi = new ProgressIndicator();
Button button = new Button("Press");
TilePane root = new TilePane();
// action event
EventHandler<ActionEvent> event = new EventHandler<ActionEvent>() {
public void handle(ActionEvent e)
{
counter += 0.1;
pi.setProgress(counter);
}
};
button.setOnAction(event);
root.getChildren().add(button);
root.getChildren().add(pi);
// create a scene
Scene scene = new Scene(root, 200, 200);
// set the scene
stage.setScene(scene);
stage.show();
}
public static void main(String args[])
{
// launch the application
launch(args);
}
}
Just change this code for your case:
EventHandler<ActionEvent> event = new EventHandler<ActionEvent>() {
public void handle(ActionEvent e)
{
if ((entServer.isSelected()==true || compasServer.isSelected()==true)) {
if (!fileList.isEmpty()) {
counter += 0.1;
pi.setProgress(counter);
}
}
}
};
Hope that helps you!

JavaFX - Undo drawing on a scaled Canvas

I'm developing a simple image editing functionality as a part of a larger JavaFX application, but I'm having some trouble to work out the undo/zoom and draw requirements together.
My requirements are the following:
The user should be able to:
Draw freehand on the image
Zoom in and out the image
Undo the changes
If the canvas is bigger than the window, it should have scroll-bars.
How I implemented these requirements:
The Drawing is done by starting a line when the mouse is pressed on the canvas, stroking it when it is dragged and closing the path when the button is released.
The Zoom works by scaling the canvas to a higher or lower value.
The Undo method takes a snapshot of the current state of the canvas when the mouse is pressed (before any change is made) and push it to a Stack of Images. When I need to undo some change I pop the last image of the Stack and draw it on the canvas, replacing the current image by the last one.
To have scroll-bars I just place the Canvas inside a Group and a ScrollPane.
Everything works fine, except when I try to draw on a scaled canvas. Due to the way I implemented the Undo functionality, I have to scale it back to 1, take a snapshot of the Node then scale it back to the size it was before. When this happens and the user is dragging the mouse the image position changes below the mouse pointer, causing it to draw a line that shouldn't be there.
Normal (unscaled canvas):
Bug (scaled canvas)
I tried the following approaches to solve the problem:
Don't re-scale to take the snapshot - Doesn't cause the unwanted line, but I end up with different image sizes in the stack, if it's smaller (zoomed out) when the snapshot was taken I now have a lower resolution of the image that I can't scale up without losing quality.
Tweak the logic and put the pushUndo call to the mouseReleased event - It almost worked, but when the user scrolled to a place and it's drawing there, the re-scaling causes the image to scroll back to the top-left;
Tried to search an way to "clone" or serialize the canvas and store the object state in the Stack - Didn't found anything I was able to adapt, and JavaFX doesn't support serialization of its objects.
I think the problem can be solved either by reworking the undo functionality as it doesn't need to re-scale the canvas to copy its state or by changing the way I zoom the canvas without scaling it, but I'm out of ideas on how to implement either of those options.
Below is the functional code example to reproduce the problem:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Stack;
public class Main extends Application {
Stack<Image> undoStack;
Canvas canvas;
double canvasScale;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
canvasScale = 1.0;
undoStack = new Stack<>();
BorderPane borderPane = new BorderPane();
HBox hbox = new HBox(4);
Button btnUndo = new Button("Undo");
btnUndo.setOnAction(actionEvent -> undo());
Button btnIncreaseZoom = new Button("Increase Zoom");
btnIncreaseZoom.setOnAction(actionEvent -> increaseZoom());
Button btnDecreaseZoom = new Button("Decrease Zoom");
btnDecreaseZoom.setOnAction(actionEvent -> decreaseZoom());
hbox.getChildren().addAll(btnUndo, btnIncreaseZoom, btnDecreaseZoom);
ScrollPane scrollPane = new ScrollPane();
Group group = new Group();
canvas = new Canvas();
canvas.setWidth(400);
canvas.setHeight(300);
group.getChildren().add(canvas);
scrollPane.setContent(group);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setLineWidth(2.0);
gc.setStroke(Color.RED);
canvas.setOnMousePressed(mouseEvent -> {
pushUndo();
gc.beginPath();
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
});
canvas.setOnMouseDragged(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
});
canvas.setOnMouseReleased(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
gc.closePath();
});
borderPane.setTop(hbox);
borderPane.setCenter(scrollPane);
Scene scene = new Scene(borderPane, 800, 600);
stage.setScene(scene);
stage.show();
}
private void increaseZoom() {
canvasScale += 0.1;
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvasScale);
}
private void decreaseZoom () {
canvasScale -= 0.1;
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvasScale);
}
private void pushUndo() {
// Restore the canvas scale to 1 so I can get the original scale image
canvas.setScaleX(1);
canvas.setScaleY(1);
// Get the image with the snapshot method and store it on the undo stack
Image snapshot = canvas.snapshot(null, null);
undoStack.push(snapshot);
// Set the canvas scale to the value it was before the method
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvasScale);
}
private void undo() {
if (!undoStack.empty()) {
Image undoImage = undoStack.pop();
canvas.getGraphicsContext2D().drawImage(undoImage, 0, 0);
}
}
}
Consider drawing Shape objects, in this case Path objects, and apply scale to them:
import java.util.Stack;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
public class Main extends Application {
private Path path;
private Stack<Path> undoStack;
private Group group;
private double scale = 1;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
undoStack = new Stack<>();
Button btnUndo = new Button("Undo");
btnUndo.setOnAction(actionEvent -> undo());
Button btnIncreaseZoom = new Button("Increase Zoom");
btnIncreaseZoom.setOnAction(actionEvent -> increaseZoom());
Button btnDecreaseZoom = new Button("Decrease Zoom");
btnDecreaseZoom.setOnAction(actionEvent -> decreaseZoom());
HBox hbox = new HBox(4, btnUndo, btnIncreaseZoom, btnDecreaseZoom);
group = new Group();
BorderPane root = new BorderPane(new Pane(group), hbox, null,null, null);
Scene scene = new Scene(root, 300, 400);
root.setOnMousePressed(mouseEvent -> newPath(mouseEvent.getX(), mouseEvent.getY()));
root.setOnMouseDragged(mouseEvent -> addToPath(mouseEvent.getX(), mouseEvent.getY()));
primaryStage.setScene(scene);
primaryStage.show();
}
private void newPath(double x, double y) {
path = new Path();
path.setStrokeWidth(1);
path.setStroke(Color.BLACK);
path.getElements().add(new MoveTo(x,y));
group.getChildren().add(path);
undoStack.add(path);
}
private void addToPath(double x, double y) {
path.getElements().add(new LineTo(x, y));
}
private void increaseZoom() {
scale += 0.1;
reScale();
}
private void decreaseZoom () {
scale -= 0.1;
reScale();
}
private void reScale(){
for(Path path : undoStack){
path.setScaleX(scale);
path.setScaleY(scale);
}
}
private void undo() {
if(! undoStack.isEmpty()){
Node node = undoStack.pop();
group.getChildren().remove(node);
}
}
}
I solved the problem by extending the Canvas component and adding a second canvas in the extended class to act as a copy of the main canvas.
Every time I made a change in the canvas I do the same change in this "carbon" canvas. When I need to re-scale the canvas to get the snapshot (the root of my problem) I just re-scale the "carbon" canvas back to 1 and get my snapshot from it. This doesn't cause the drag of the mouse in the main canvas, as it remains scaled during this process. Probably this isn't the optimal solution, but it works.
Below is the code for reference, to anyone who may have a similar problem in the future.
ExtendedCanvas.java
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import java.util.Stack;
public class ExtendedCanvas extends Canvas {
private final double ZOOM_SCALE = 0.1;
private final double MAX_ZOOM_SCALE = 3.0;
private final double MIN_ZOOM_SCALE = 0.2;
private double currentScale;
private final Stack<Image> undoStack;
private final Stack<Image> redoStack;
private final Canvas carbonCanvas;
private final GraphicsContext gc;
private final GraphicsContext carbonGc;
public ExtendedCanvas(double width, double height){
super(width, height);
carbonCanvas = new Canvas(width, height);
undoStack = new Stack<>();
redoStack = new Stack<>();
currentScale = 1.0;
gc = this.getGraphicsContext2D();
carbonGc = carbonCanvas.getGraphicsContext2D();
setEventHandlers();
}
private void setEventHandlers() {
this.setOnMousePressed(mouseEvent -> {
pushUndo();
gc.beginPath();
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
carbonGc.beginPath();
carbonGc.lineTo(mouseEvent.getX(), mouseEvent.getY());
});
this.setOnMouseDragged(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
carbonGc.lineTo(mouseEvent.getX(), mouseEvent.getY());
carbonGc.stroke();
});
this.setOnMouseReleased(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
gc.closePath();
carbonGc.lineTo(mouseEvent.getX(), mouseEvent.getY());
carbonGc.stroke();
carbonGc.closePath();
});
}
public void zoomIn() {
if (currentScale < MAX_ZOOM_SCALE ) {
currentScale += ZOOM_SCALE;
setScale(currentScale);
}
}
public void zoomOut() {
if (currentScale > MIN_ZOOM_SCALE) {
currentScale -= ZOOM_SCALE;
setScale(currentScale);
}
}
public void zoomNormal() {
currentScale = 1.0;
setScale(currentScale);
}
private void setScale(double value) {
this.setScaleX(value);
this.setScaleY(value);
carbonCanvas.setScaleX(value);
carbonCanvas.setScaleY(value);
}
private void pushUndo() {
redoStack.clear();
undoStack.push(getSnapshot());
}
private Image getSnapshot(){
carbonCanvas.setScaleX(1);
carbonCanvas.setScaleY(1);
Image snapshot = carbonCanvas.snapshot(null, null);
carbonCanvas.setScaleX(currentScale);
carbonCanvas.setScaleY(currentScale);
return snapshot;
}
public void undo() {
if (hasUndo()) {
Image redo = getSnapshot();
redoStack.push(redo);
Image undoImage = undoStack.pop();
gc.drawImage(undoImage, 0, 0);
carbonGc.drawImage(undoImage, 0, 0);
}
}
public void redo() {
if (hasRedo()) {
Image undo = getSnapshot();
undoStack.push(undo);
Image redoImage = redoStack.pop();
gc.drawImage(redoImage, 0, 0);
carbonGc.drawImage(redoImage, 0, 0);
}
}
public boolean hasUndo() {
return !undoStack.isEmpty();
}
public boolean hasRedo() {
return !redoStack.isEmpty();
}
}
Main.java
package com.felipepaschoal;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Main extends Application {
ExtendedCanvas extendedCanvas;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
BorderPane borderPane = new BorderPane();
HBox hbox = new HBox(4);
Button btnUndo = new Button("Undo");
btnUndo.setOnAction(actionEvent -> extendedCanvas.undo());
Button btnRedo = new Button("Redo");
btnRedo.setOnAction(actionEvent -> extendedCanvas.redo());
Button btnDecreaseZoom = new Button("-");
btnDecreaseZoom.setOnAction(actionEvent -> extendedCanvas.zoomOut());
Button btnResetZoom = new Button("Reset");
btnResetZoom.setOnAction(event -> extendedCanvas.zoomNormal());
Button btnIncreaseZoom = new Button("+");
btnIncreaseZoom.setOnAction(actionEvent -> extendedCanvas.zoomIn());
hbox.getChildren().addAll(
btnUndo,
btnRedo,
btnDecreaseZoom,
btnResetZoom,
btnIncreaseZoom
);
ScrollPane scrollPane = new ScrollPane();
Group group = new Group();
extendedCanvas = new ExtendedCanvas(300,200);
group.getChildren().add(extendedCanvas);
scrollPane.setContent(group);
borderPane.setTop(hbox);
borderPane.setCenter(scrollPane);
Scene scene = new Scene(borderPane, 600, 400);
stage.setScene(scene);
stage.show();
}
}

Synchronize scrollbars of two JavaFx WebViews

I'm using two WebViews to display two versions of HTML formatted text for comparison. The two display the same amount of text (same number of lines and corresponding lines have always the same length).
When the displayed text exceeds the size of the node, the WebView gets scroll bars. Of course I want these scroll bars to scroll synchronously so that always the corresponding text is displayed.
In order to supply a minimal, complete and verifiable example, I trimmed the code down to this:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.GridPane;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class SynchronizedWebViewsTest extends Application {
protected class DifferencePanel extends GridPane {
private WebView actualPane;
private WebView expectedPane;
public DifferencePanel() {
setPadding(new Insets(20, 20, 20, 20));
actualPane = new WebView();
expectedPane = new WebView();
setResultPanes();
addRow(0, actualPane, expectedPane);
}
public void setHtml(WebView webView) {
Platform.runLater(() -> {
webView.getEngine().loadContent(createHtml());
});
}
public void synchronizeScrolls() {
final ScrollBar actualScrollBarV = (ScrollBar)actualPane.lookup(".scroll-bar:vertical");
final ScrollBar expectedScrollBarV = (ScrollBar)expectedPane.lookup(".scroll-bar:vertical");
actualScrollBarV.valueProperty().bindBidirectional(expectedScrollBarV.valueProperty());
final ScrollBar actualScrollBarH = (ScrollBar)actualPane.lookup(".scroll-bar:horizontal");
final ScrollBar expectedScrollBarH = (ScrollBar)expectedPane.lookup(".scroll-bar:horizontal");
actualScrollBarH.valueProperty().bindBidirectional(expectedScrollBarH.valueProperty());
}
private String createHtml() {
StringBuilder sb = new StringBuilder(1000000);
for (int i = 0; i < 100; i++) {
sb.append(String.format("<nobr>%03d %2$s%2$s%2$s%2$s%2$s%2$s%2$s%2$s</nobr><br/>\n",
Integer.valueOf(i), "Lorem ipsum dolor sit amet "));
}
return sb.toString();
}
private void setResultPanes() {
setHtml(actualPane);
setHtml(expectedPane);
}
} // ---------------------------- end of DifferencePanel ----------------------------
public static void main(String[] args){
launch(args);
}
#Override
public void start(Stage dummy) throws Exception {
Stage stage = new Stage();
stage.setTitle(this.getClass().getSimpleName());
DifferencePanel differencePanel = new DifferencePanel();
Scene scene = new Scene(differencePanel);
stage.setScene(scene);
differencePanel.synchronizeScrolls();
stage.showAndWait();
}
}
I tried using adding a listener:
actualScrollBarV.onScrollFinishedProperty().addListener(event -> {
System.out.println(event);
});
But the listener is never invoked.
I'm using Java version 1.8.0_92, but with version 9.0.4 I get the same result.
Can anybody tell me, what I'm missing here?
I would post a comment, but sadly I did not have enough reputation.
Did you tried the following solution? Create listeners on value changed event, instead of binding. Synchronizing two scroll bars JavaFX
I could not get the ScrollBar approach working. It turned out that the listeners were actually invoked (breakpoints in lambdas are not always working?). Setting the scroll bar value of the other WebView did not get it inclined change the scroll bar or the view port. :-(
There is something strange going on with events in WebView; that might be because there is a native library involved...
However, the approach using the event handler of WebView works. The event handler of each WebView simply mirrors all events to the other WebView, using a synchronizing field Boolean scrolling to avoid recursion.
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.Event;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class SynchronizedWebViewsTest extends Application {
protected class DifferencePanel extends GridPane {
private Boolean scrolling = Boolean.FALSE;
private WebView actualPane;
private WebView expectedPane;
public DifferencePanel() {
setPadding(new Insets(20, 20, 20, 20));
actualPane = new WebView();
expectedPane = new WebView();
setResultPanes();
addRow(0, actualPane, expectedPane);
}
public void setHtml(WebView webView) {
Platform.runLater(() -> {
webView.getEngine().loadContent(createHtml());
});
}
public void synchronizeScrolls() {
wireViews(actualPane, expectedPane);
wireViews(expectedPane, actualPane);
}
private void wireViews(WebView webView, WebView otherWebView) {
webView.addEventHandler(Event.ANY, event -> {
if (!scrolling.booleanValue()) {
synchronized (scrolling) {
scrolling = Boolean.TRUE;
if (event instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) event;
Point2D origin = webView.localToScreen(0, 0);
Point2D otherOrigin = otherWebView.localToScreen(0, 0);
double offsetX = otherOrigin.getX() - origin.getX();
double offsetY = otherOrigin.getY() - origin.getY();
double x = mouseEvent.getX();
double y = mouseEvent.getY();
double screenX = mouseEvent.getScreenX() + offsetX;
double screenY = mouseEvent.getScreenY() + offsetY;
MouseButton button = mouseEvent.getButton();
int clickCount = mouseEvent.getClickCount();
boolean shiftDown = mouseEvent.isShiftDown();
boolean controlDown = mouseEvent.isControlDown();
boolean altDown = mouseEvent.isAltDown();
boolean metaDown = mouseEvent.isMetaDown();
boolean primaryButtonDown = mouseEvent.isPrimaryButtonDown();
boolean middleButtonDown = mouseEvent.isMiddleButtonDown();
boolean secondaryButtonDown = mouseEvent.isSecondaryButtonDown();
boolean synthesized = mouseEvent.isSynthesized();
boolean popupTrigger = mouseEvent.isPopupTrigger();
boolean stillSincePress = mouseEvent.isStillSincePress();
MouseEvent otherMouseEvent =
new MouseEvent(otherWebView, otherWebView, mouseEvent.getEventType(), x, y, screenX,
screenY, button, clickCount, shiftDown, controlDown, altDown, metaDown,
primaryButtonDown, middleButtonDown, secondaryButtonDown, synthesized,
popupTrigger, stillSincePress, null);
otherWebView.fireEvent(otherMouseEvent);
}
else {
otherWebView.fireEvent(event.copyFor(otherWebView, otherWebView));
}
scrolling = Boolean.FALSE;
}
}
});
}
private String createHtml() {
StringBuilder sb = new StringBuilder(1000000);
for (int i = 0; i < 100; i++) {
sb.append(String.format("<nobr>%03d %2$s%2$s%2$s%2$s%2$s%2$s%2$s%2$s</nobr><br/>\n",
Integer.valueOf(i), "Lorem ipsum dolor sit amet "));
}
return sb.toString();
}
private void setResultPanes() {
setHtml(actualPane);
setHtml(expectedPane);
}
}
public static void main(String[] args){
launch(args);
}
#Override
public void start(Stage dummy) throws Exception {
Stage stage = new Stage();
stage.setTitle(this.getClass().getSimpleName());
DifferencePanel differencePanel = new DifferencePanel();
Scene scene = new Scene(differencePanel);
stage.setScene(scene);
differencePanel.synchronizeScrolls();
stage.showAndWait();
}
}
This works for all input methods I'm interested in:
Keyboard: PageUp, PageDown, all 4 arrow keys, "space bar" (same as PageDown) and shift-"space bar" (same as PageUp), Home and End
Mouse wheel: RollDown and RollUp as well as shift-RollUp (scroll left) and shift-RollDown (scroll right)
Using the mouse to click or to drag the scroll bar.
Using the mouse to select text outside of the current view port.
Mirroring the mouse events has the added benefit that text gets selected in both WebViews.

How do I insert all my shapes into an array?

I am Trying to build a cinema booker, where I have to select the seats. I was thinking about make placing all my rectangles in an array so i could use it for later, when clicking on a seat it should check if left and right seats are booked. Here the array index should help me. However I cant figure out how to get to this stage. (See picture.)
Take this scenario:
You click on a rectangle (representing a seat). It changes color only it is not Red colored. So Seats[][].checkNeighbourColor or something like that.See picture
package sample;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import java.awt.*;
import java.util.ArrayList;
public class Main extends Application {
private int seats = 12;
private int rows = 8;
private static Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); //gets screen resolution data
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("");
Group root = new Group();
Scene scene = new Scene(root, screenSize.getWidth()/4, screenSize.getHeight()/3, Color.WHITE);
for (int i = 0; i<= seats; i++)
{
Rectangle r = new Rectangle();
r.setFill(Color.GREEN);
r.setX(scene.getWidth()/5+i*30);
r.setY(scene.getHeight()/5);
r.setWidth(screenSize.getHeight()/80);
r.setHeight(screenSize.getHeight()/80);
root.getChildren().add(r);
for (int q = 0; q<=rows; q++)
{
Rectangle s = new Rectangle();
s.setFill(Color.GREEN);
s.setX(scene.getWidth()/5+i*30);
s.setY(scene.getHeight()/5+q*30);
s.setWidth(screenSize.getHeight()/80);
s.setHeight(screenSize.getHeight()/80);
root.getChildren().add(s);
s.setOnMouseClicked(event ->{
s.setFill(Color.BLACK);
});
}
}
primaryStage.setScene(scene);
primaryStage.show();
}
}
It's not entirely clear to me what you're asking, but can't you just do
private Rectangle[][] rectangles = new Rectangle[seats][rows];
and then just do
rectangles[i][q] = s ;
For your listener you can do
final int row = i ;
final int seat = q ;
s.setOnMouseClicked(event -> {
// check rectangles[row-1][seat] and rectangles[row+1][seat] as needed,
// checking for range of row first
});
Aside: don't mix AWT and JavaFX. Use the Screen API to get the dimension of the physical screen(s) in JavaFX.
Okay I found my mistake. It was a simple ArrayOutOfBound (The statement in my for-loops was wrong), but thank you all for your help :)

I am trying to connect circles with each other with a line from the its centers [duplicate]

This question already has an answer here:
JavaFX: How to connect two Nodes by a Line?
(1 answer)
Closed 7 years ago.
Trying to connect circles together in the picture with a line from the center of the circle to the other circle. The line should be shown will the mouse is being dragged.And when I release the mouse than it should not show any line at all.
This is the code.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
public class Projekt extends Application {
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
//Inserting Circles
for(int i=0;i<10;i++)
{
for(int j=0;j<10;j++)
{
Circle c = new Circle(i*60+10,j*60+10,10);
c.setFill(Color.WHITE);
c.setStroke(Color.BLACK);
c.setStrokeWidth(2);
c.setStrokeType(StrokeType.OUTSIDE);
c.setOnMouseDragged((MouseEvent e)->{
Line line = new Line(c.getCenterX(),c.getCenterY(),e.getX(),e.getY());
});
pane.getChildren().add(c);
}
}
Scene scene = new Scene(pane,600,600);
primaryStage.setScene(scene);
primaryStage.setTitle("Hello World!");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
There are several ways to do this.
1-st of all you need to add your line to the pane. Because there is line creation in your code, but nothing adds this line to the pane. And you probably don't want to create new line each time, so just create some line (maybe even static one) and change coords with line.setStartX(x); line.setStartY(y); and line.setEndX(e.getX()); line.setEndY(e.getY()); on each mouse press and mouse drag events.
I think you will not be able to do this with just 1 setOnMouseDragged listener, you have to use at least some other events to set your line visible and invisible.
Here is starter code for you to play with:
public class Main extends Application {
private Line line = new Line();
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
line.setVisible(false);
pane.getChildren().add(line);
//Inserting Circles
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
double x = i * 60 + 10;
double y = j * 60 + 10;
Circle c = new Circle(x, y, 10);
c.setFill(Color.WHITE);
c.setStroke(Color.BLACK);
c.setStrokeWidth(2);
c.setStrokeType(StrokeType.OUTSIDE);
c.setOnMousePressed((MouseEvent e) -> {
line.setStartX(x);
line.setStartY(y);
});
c.setOnMouseReleased((MouseEvent e) -> {
line.setVisible(false);
});
c.setOnMouseDragged((MouseEvent e) -> {
line.setEndX(e.getX());
line.setEndY(e.getY());
line.setVisible(true);
});
pane.getChildren().add(c);
}
}

Categories

Resources