I am trying to draw random circles with random x/y centers, but the result of my code is only one circle at the center of the stage (window).
I use Task class to update my UI every 1 second.
This is my code:
package javafxupdateui;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class JavaFXUpdateUI extends Application {
private Stage window;
private StackPane layout;
private Scene scene;
#Override
public void start(Stage primaryStage) {
window = primaryStage;
window.setTitle("JavaFX - Update UI");
layout = new StackPane();
scene = new Scene(layout, 500, 500);
window.setScene(scene);
window.show();
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
}
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
while (true) {
Platform.runLater(new Runnable() {
#Override
public void run() {
drawCircles();
}
});
Thread.sleep(1000);
}
}
};
public void drawCircles() {
Circle circle;
float x = (float)(Math.random()*501);
float y = (float)(Math.random()*501);
circle = new Circle(x, y, 25, Color.RED);
layout.getChildren().add(circle);
scene.setRoot(layout);
window.setScene(scene);
}
public static void main(String[] args) {
launch(args);
}
}
The result of the above code is:
Result GUI
What is going wrong
StackPane is a layout pane, it centers everything by default. As you want to manually place the circles at random locations, you don't want to use a pane which manages the layout for you.
How to fix it
Use a Pane or a Group instead of StackPane. Neither Pane nor Group manage the layout of items for you, so children you add to them at specific locations will remain at those locations.
Aside
You might wish to use a Timeline for your periodic updates rather than a Task with runLater (though the later will still work OK, with a Timeline you don't have to deal with additional complexities of concurrent code).
Related
In Javafx, I am trying to create a pane where I can add points through a mouse click event. When you click on the pane a circle should appear at your mouse position. The circles are being created, as I am tracking them in the console, but they are not showing in the graphics.
I did a similar program to this that auto drew an image that resized with the stage/window, I am using all the same techniques but that project didn't include event handling.
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.paint.Color;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
public class ClickToShape extends Application {
private ClickPane clickPane = new ClickPane();
#Override
public void start(Stage primaryStage) throws Exception {
Pane clickPane = new ClickPane();
clickPane.setOnMouseClicked(new ClickHandler());
// create the scene
Scene clickScene = new Scene(clickPane, 500, 500);
// set up the window/stage
primaryStage.setTitle("Click To Draw");
primaryStage.setScene(clickScene); // add the scene to the stage
primaryStage.show(); // fire it off
}
public static void main(String[] args) {
launch(args);
}
class ClickHandler implements EventHandler<MouseEvent> {
#Override
public void handle(MouseEvent e) {
System.out.println("MouseEvent occured");
clickPane.addPoint(e.getX(), e.getY());
}
}
}
class ClickPane extends Pane{
private ArrayList<Circle> points = new ArrayList<Circle>();
private Color color1 = Color.BLACK;
public void addPoint(double x, double y) {
System.out.println("A new point function ran");
Circle newPoint = new Circle (x, y, 300, color1 );
System.out.println(newPoint.toString());
points.add(newPoint);
getChildren().clear();
getChildren().add(newPoint);
}
}
There are no error messages.
the problem is that you instantiated two ClickPane objects, one outside the start method, and another inside the start method, you added the second one to the scene but used the first one to add points, and that's why points aren't showing in your scene
what you can do about this is delete the first line in your start method, so the application will be using the same instance to fire events as to add to the scene, the code would look like this
import java.util.ArrayList;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.paint.Color;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
public class ClickToShape extends Application {
private ClickPane clickPane = new ClickPane();
#Override
public void start(Stage primaryStage) throws Exception {
clickPane.setOnMouseClicked(new ClickHandler());
// create the scene
Scene clickScene = new Scene(clickPane, 500, 500);
// set up the window/stage
primaryStage.setTitle("Click To Draw");
primaryStage.setScene(clickScene); // add the scene to the stage
primaryStage.show(); // fire it off
}
public static void main(String[] args) {
launch(args);
}
class ClickHandler implements EventHandler<MouseEvent> {
#Override
public void handle(MouseEvent e) {
System.out.println("MouseEvent occured");
clickPane.addPoint(e.getX(), e.getY());
}
}
}
class ClickPane extends Pane{
private ArrayList<Circle> points = new ArrayList<Circle>();
private Color color1 = Color.BLACK;
public void addPoint(double x, double y) {
System.out.println("A new point function ran");
Circle newPoint = new Circle (x, y, 10, color1 );
System.out.println(newPoint.toString());
points.add(newPoint);
getChildren().setAll(newPoint);
}
}
I have undecorated non-fullscreen window, which I like to move outside screen boundaries when mouse leaves it's area, but do so smoothly. I found some JavaFX functionality to do so - Timeline, but KeyValue for that Timeline doesn't supports stage.xProperty - because this property is readonlyProperty. Is there way to move my window smoothly using JavaFX functions?
You can setup proxy properties that you manipulate via KeyValues in a Timeline. A listener on the proxy can modify the actual stage location.
import javafx.animation.*;
import javafx.application.*;
import javafx.beans.property.*;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.TextAlignment;
import javafx.stage.*;
import javafx.util.Duration;
public class StageSwiper extends Application {
private static final int W = 350;
private static final Duration DURATION = Duration.seconds(0.5);
#Override
public void start(Stage stage) throws Exception {
Label instructions = new Label(
"Window will slide off-screen when the mouse exits it.\n" +
"Click the window to close the application."
);
instructions.setTextAlignment(TextAlignment.CENTER);
final StackPane root = new StackPane(instructions);
root.setStyle("-fx-background-color: null;");
DoubleProperty stageX = new SimpleDoubleProperty();
stageX.addListener((observable, oldValue, newValue) -> {
if (newValue != null && newValue.doubleValue() != Double.NaN) {
stage.setX(newValue.doubleValue());
}
});
final Timeline slideLeft = new Timeline(
new KeyFrame(
DURATION,
new KeyValue(
stageX,
-W,
Interpolator.EASE_BOTH
)
),
new KeyFrame(
DURATION.multiply(2)
)
);
slideLeft.setOnFinished(event -> {
slideLeft.jumpTo(Duration.ZERO);
stage.centerOnScreen();
stageX.setValue(stage.getX());
});
root.setOnMouseClicked(event -> Platform.exit());
root.setOnMouseExited(event -> slideLeft.play());
stage.setScene(new Scene(root, W, 100, Color.BURLYWOOD));
stage.initStyle(StageStyle.UNDECORATED);
stage.show();
stage.centerOnScreen();
stageX.set(stage.getX());
}
public static void main(String[] args) {
launch(args);
}
}
I'm trying to find a way to update the Categories of a JavaFX CategoryAxis(). I made an observable list of the categories and they also do update in the plot() function. However, if I try to add a new item to the series, I get a java.lang.IllegalStateException. Although I know, that not a state is causing the error, moreover the dynamic adding seems to be the problem. Below I attached my Code.
package application;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.StackedBarChart;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
final XYChart.Series series1 = new XYChart.Series();
final XYChart.Series series2 = new XYChart.Series();
ObservableList<XYChart.Data> xyList1 = FXCollections.observableArrayList();
ObservableList<XYChart.Data> xyList2 = FXCollections.observableArrayList();
ObservableList<String> myXaxis = FXCollections.observableArrayList();
int i;
#Override public void start(Stage stage) {
stage.setTitle("Line Chart Sample");
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Month");
final StackedBarChart<String,Number> lineChart =
new StackedBarChart<String,Number>(xAxis,yAxis);
lineChart.setTitle("Woohoo, 2010");
lineChart.setAnimated(false);
series1.setName("Test 1");
series2.setName("test 2");
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
plot();
}
}, 0, 1000);
Scene scene = new Scene(lineChart,800,600);
xAxis.setCategories(myXaxis);
XYChart.Series XYSeries1 = new XYChart.Series(xyList1);
XYChart.Series XYSeries2 = new XYChart.Series(xyList2);
lineChart.getData().addAll(XYSeries1,XYSeries2);
i = 0;
stage.setScene(scene);
stage.show();
}
public void plot() {
SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
Date date = new Date();
date.setTime(date.getTime() + i++ * 11111);
myXaxis.add(dateFormat.format(date));
System.out.println(myXaxis);
// with the line below uncommented the application breaks. Without the x-axis is updated as intended.
//xyList1.add(new XYChart.Data(dateFormat.format(date), Math.random()*10));
}
public static void main(String[] args) {
launch(args);
}
}
Issue - Transitive Modification of Scene Graph Nodes off of the JavaFX Application Thread
Don't modify scene nodes (or even observable lists of data on which scene nodes depend), off of the main JavaFX application thread (it's illegal - as the IllegalStateException you received states).
A Timer thread does not run things on the JavaFX application thread.
Potential Fixes
There are a few ways to fix this:
Continue using a Timer, but surround the plot() call in the timer with Platform.runLater.
Use the JavaFX animation framework (the Timeline), which always runs all of it's code on the JavaFX application thread.
Of the two options, I think I'd prefer the second, but either will work.
Timer style solution
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
Platform.runLater(new Runnable() {
#Override public void run() {
plot();
}
});
}
}, 0, 1000);
Timeline style solution
Timeline timeline = new Timeline(
new KeyFrame(
Duration.ZERO,
new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent actionEvent) {
plot();
}
}
),
new KeyFrame(
Duration.seconds(1)
)
);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
Alternative Service based solution
I suggest that you read up on concurrency in JavaFX.
Another alternative would be to use Task or Service in conjunction with a ScheduledExecutorService and updates running via Platform.runLater(). However this kind of solution, while sophisticated and flexible, is more complicated than the problem warrants as you described it and the simple Timeline or Timer based solutions should be preferred. The more complicated Service based solution is appropriate if each pulse results in the execution of a time consuming algorithm or a lot of network or file based I/O.
I am trying to create multiple stages using different classes, whereby I can have another window being brought up by a click of a button, but this window should be in a different class.
I used to do this in Java where I would create an object of the class in the buttons action and use the name of the object to set the new JFrame visible, but modal to the main JFrame. I tried the same in JavaFX but it failed to work.
I have two different classes and both are in different stages, but I just can't use one stage to display the other stages. I only know to use one class whereby I would create another stage in the action handler method, but this makes the code very long and too complicated.
P.S. what I am trying to accomplish is not multiple screens in the same window. but different windows (stages), and I prefer not to use FXML files, but java files using netbeans.
Any help would be greatly appreciated.
So you want each class to be a sub-class of a Stage. I'll give you two Stages and how to interact with each other.
public class FirstStage extends Stage{
Button openOther = new Button("Open other Stage");
HBox x = new HBox();
FirstStage(){
x.getChildren().add(openOther);
this.setScene(new Scene(x, 300, 300));
this.show();
openOther.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
new SecondStage();
}//end action
});
}
}
For the second Stage,
public class SecondStage extends Stage {
Label x = new Label("Second stage");
VBox y = new VBox();
SecondStage(){
y.getChildren().add(x);
this.setScene(new Scene(y, 300, 300));
this.show();
}
}
And call from main the first stage:
#Override
public void start(Stage primaryStage){
new FirstClass();
}
// My version of the 1st answer above.
package edu.dtcc.michael_javafx_2;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class HelloApplication extends Application {
public void start(Stage primaryStage) {
new FirstStage();
}
}
class FirstStage extends Stage{
Button openOther = new Button("Open other Stage");
HBox x = new HBox();
FirstStage(){
x.getChildren().add(openOther);
this.setScene(new Scene(x, 300, 300));
this.show();
openOther.setOnAction(t -> new SecondStage());
}
}
class SecondStage extends Stage {
Label x = new Label("Second stage");
VBox y = new VBox();
SecondStage(){
y.getChildren().add(x);
this.setScene(new Scene(y, 300, 300));
this.show();
}
}
Thank you. This has been a tough topic for me after weeks of FX work.
To execute:
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
new FirstStage();
}
public static void main(String[] args) {
launch(args);
}
}
We are creating some graphical applications to the our project. We are using Draw2d and GEF technologies in our project. We have to draw one rectangle in the draw area. The functionality is as follows.
Use click the rectangle button from the toolbar.
Changes cursor to Cross curson symbol.
When the user clicks the area and drag the mouse, it shows the rectangle based on the dragging of the mouse.
Till now it is working fine. Now the issue is, we have zoom in and zoom out functionality.
When the use zoom in and draw the rectangle, it is not coming in the desired position.
It is going below parts of the area.Then user has to scroll and see the rectangle. This problem happens only when we use zoom in and zoom out.
How to ressolve this issue? Please see my code below.
package draw2dview;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.FreeformLayout;
import org.eclipse.draw2d.FreeformViewport;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.LightweightSystem;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseListener;
import org.eclipse.draw2d.RectangleFigure;
import org.eclipse.draw2d.ScalableFigure;
import org.eclipse.draw2d.ScalableFreeformLayeredPane;
import org.eclipse.draw2d.ToolbarLayout;
import org.eclipse.draw2d.XYLayout;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.EditDomain;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartFactory;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.editparts.ScalableFreeformRootEditPart;
import org.eclipse.gef.editparts.ScalableRootEditPart;
import org.eclipse.gef.editparts.ZoomManager;
import org.eclipse.gef.ui.actions.ZoomInAction;
import org.eclipse.gef.ui.actions.ZoomOutAction;
import org.eclipse.gef.ui.parts.ScrollingGraphicalViewer;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.part.ViewPart;
public class View extends ViewPart implements org.eclipse.swt.events.MouseListener {
public static final String ID = "draw2dview.view";
private Action drawAction;
private ScalableFreeformLayeredPane root;
ScalableRootEditPart editPart = null ;
private XYLayout layout;
private ZoomManager zoomManager ;
EditDomain editDomain = new DefaultEditDomain(null);
GraphicalViewer graphicalViewer = new ScrollingGraphicalViewer();
ScalableFreeformRootEditPart rootEditPart = new ScalableFreeformRootEditPart();
private FigureCanvas createContents(Composite parent){
root = new ScalableFreeformLayeredPane();
zoomManager = new ZoomManager(root,new FreeformViewport());
root.setFont(parent.getFont());
//layout = new XYLayout();
layout= new FreeformLayout();
root.setLayoutManager(layout);
FigureCanvas figureCanvas = new FigureCanvas(parent,SWT.DOUBLE_BUFFERED);
figureCanvas.addMouseListener(this);
figureCanvas.setBackground(ColorConstants.white);
LightweightSystem lws = new LightweightSystem(figureCanvas);
lws.setContents(root);
return figureCanvas ;
}
private IFigure createPersonFigure() {
RectangleFigure rectangleFigure = new RectangleFigure();
rectangleFigure.setBackgroundColor(ColorConstants.blue);
rectangleFigure.setLayoutManager(new ToolbarLayout());
rectangleFigure.setPreferredSize(100, 100);
return rectangleFigure ;
}
/**
* This is a callback that will allow us to create the viewer and initialize
* it.
*/
public void createPartControl(Composite parent) {
/* graphicalViewer.createControl(parent);
editDomain.addViewer(graphicalViewer);
graphicalViewer.setRootEditPart(rootEditPart);*/
createContents(parent);
createAction();
contributeToActionBars();
}
private void contributeToActionBars() {
IActionBars bars = getViewSite().getActionBars();
addToToolBar(bars.getToolBarManager());
}
private void addToToolBar(IToolBarManager toolBarManager2){
toolBarManager2.add(drawAction);
toolBarManager2.add(new ZoomInAction(zoomManager));
toolBarManager2.add(new ZoomOutAction(zoomManager));
}
private void createAction() {
drawAction = new Action() {
public void run() {
System.out.println("execued..");
}
};
drawAction.setText("Draw");
drawAction.setImageDescriptor(Activator.getImageDescriptor("icons/alt_window_16.gif"));
}
/**
* Passing the focus request to the viewer's control.
*/
public void setFocus() {
// viewer.getControl().setFocus();
}
#Override
public void mouseDoubleClick(org.eclipse.swt.events.MouseEvent e) {
}
#Override
public void mouseDown(org.eclipse.swt.events.MouseEvent e) {
System.out.println("inside..Mousedeown:: "+e.x+","+e.y);
IFigure personFigure = createPersonFigure();
root.add(personFigure);
layout.setConstraint(personFigure, new Rectangle(new Point(e.x,e.y),personFigure.getPreferredSize()));
//layout.setConstraint(personFigure, new Rectangle(new Point(e.x,e.y),personFigure.getPreferredSize()));
}
#Override
public void mouseUp(org.eclipse.swt.events.MouseEvent e) {
}
}
You will need to scale your mouse event coordinates according to the zoom level your zoom manager is currently using. The mouse events are absolute pixels, but your ZoomManger is causing a scale factor to be applied to your figure's coordinates. I think you will also need to take into account your ViewPort's client area.
First, when you constuct your ZoomManager pass in the ViewPort from your FigureCanvas:
zoomManager = new ZoomManager(root, figureCanvas.getViewPort());
Then try something like:
double scaleFactor = zoomManager.getZoom();
Rectangle r = figureCanvas.getViewport().getClientArea();
layout.setConstraint(personFigure, new Rectangle(new Point((e.x + r.x) * scaleFactor,(e.y + r.y) * scaleFactor),personFigure.getPreferredSize()));
This may need tweaked to get it right, let me know....
You have to translate from absolute coordinates obtained from the mouse event to relative coordinates to the person figure's parent:
#Override
public void mouseDown(org.eclipse.swt.events.MouseEvent e) {
System.out.println("inside..Mousedeown:: "+e.x+","+e.y);
IFigure personFigure = createPersonFigure();
root.add(personFigure);
Point p = new PrecisionPoint(e.x,e.y);
personFigure.translateToRelative(p);
layout.setConstraint(personFigure, new Rectangle(p,personFigure.getPreferredSize()));
}
For more information look at draw2d help