Explicitly positioning nodes in JavaFX - java

When I have clicked a button, it changes its position.
But when I move the mouse, the button comes back to the center of the scene, why?
I have the following code:
public class HolaMundo extends Application {
Button btn;
Scene scene;
#Override
public void start(Stage primaryStage) {
btn = new Button();
btn.setText("Hola Mundo");
StackPane root = new StackPane();
root.getChildren().add(btn);
scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
scene.setOnMouseMoved(new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent t) {
btn.setText(String.valueOf(t.getX() ));
}
});
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
btn.setLayoutX(Math.random() * (300 - btn.getWidth()));
btn.setLayoutY(Math.random() * (250 - btn.getHeight()));
}
});
}
public static void main(String[] args) {
launch(args);
}
}

Suggested approach
For your particular code snippet, perhaps using a Pane instead of a StackPane is the best approach.
Why your code doesn't do what you want
If you are going to manually set layout values, don't use a layout pane (such as a StackPane) which automatically sets layout values for you. If you use a layout pane, then the layout values you explicitly set will be overridden automatically the next time layout pane performs a layout pass (e.g. it is resized or some its content or location in the scene graph becomes dirty).
Options for explicitly laying out nodes
If you want to explicitly layout items in JavaFX do one of the following:
Subclass Region and override layoutChildren.
Place your content in a Pane if you need the container to be styled with CSS or implement resizing functions.
Place your content in a Group if you don't need the container to be styled with CSS or implement resizing functions.
Related advice on node positioning
If you want to have automatically laid out components using the predefined layout managers but you want to adjust or temporarily modify the locations of some of the components from their default positions, you can adjust the translateX/Y values rather than layoutX/Y values. According to the definition of translateX:
The node's final translation will be computed as layoutX + translateX, where layoutX establishes the node's stable position and translateX optionally makes dynamic adjustments to that position.
This variable can be used to alter the location of a node without disturbing its layoutBounds, which makes it useful for animating a node's location.
This means that the layout manager can compute a child's default position using layoutX, and you can adjust the position from the default using translateX.

Not having deeply investigated the current case, I see a difference when I use an AnchorPane instead of the StackPane to place the Button on.
By changing the label text of the Button by the mouseMoved-Event the Pane is rendered (layout is requested). With a StackPane placing all it's children in the center of itself the Button's position is reset to the center of the Pane. When you have a look into the layoutChildren method of StackPane you'll see a call to resizeRelocate. So layoutX and layoutY are reset and the button moves back to the center position (or whatever you set the alignment of the StackPane to).
So I think this is a correct behavior of the StackPane and I recommend to use another Pane, e.g. AnchorPane.

Related

JavaFX setting root of scene causes root to change size

I have found that when I set the root of a scene (as advised - instead of simply setting the scene), the root pane is resized and does not fit in the stage properly, even though I supply the same dimensions as the stage. Screenshots can be seen in the links below.
If I use the setScene function, it works as expected and the root pane is the correct size, but there is a flash of a white screen between scene changes.
Here are examples from my project:
// resizes root pane - too small for stage
public void handle(MouseEvent event) {
if (event.getSource() instanceof Button) {
Button sourceButton = (Button)event.getSource();
Stage primaryStage = (Stage ((Node)event.getSource()).getScene().getWindow();
if (sourceButton.getId().equals("back-button")) {
primaryStage.getScene().setRoot(view.getWelcomePane());
}
}
}
public Pane getWelcomePane() {
return new BorderPane(bpWelcomeMain); // bpWelcomeMain is the border pane that contains the page components
}
Or:
// produces 'flashing' between scene transition (but root pane is correct size)
public void handle(MouseEvent event) {
if (event.getSource() instanceof Button) {
Button sourceButton = (Button)event.getSource();
Stage primaryStage = (Stage ((Node)event.getSource()).getScene().getWindow();
if (sourceButton.getId().equals("back-button")) {
primaryStage.setScene(view.getWelcomeScene());
}
}
}
public Pane getWelcomeSceme() {
return new Scene(bpWelcomeMain, 1500, 750); // bpWelcomeMain is the border pane that contains the page components
}
I have tried sizeToScene() as well as other things mentioned on other posts, but I am yet to discover a solution. The function setRoot() seems to work on one of my pages but not the others, even though they are all the same dimensions and the code for switching between the root panes is the same. I don't understand why setRoot() works for one but doesn't work for the other pages. I also don't understand why setScene() produces the correctly sized root pane but setRoot() does not.
So, I would like to use the setRoot() function without the root pane resizing, or setScene() without the flash between scene transitions (it ruins the fluidity of the UI). Is there a way?
This is the result of setScene() (although it looks good because of the flashing between scene changes I feel like an epilepsy warning may be needed haha)
This is the result of setRoot()
Any help would be greatly appreciated.

When is updated a Label's widthProperty?

I use a Pane to print some Label by adding them to its children list. For some reason, I need to bind a label width, but when I do something like :
public class LabelTestApp extends Application
{
private Pane root = new Pane();
private Label myLabel = new Label("test");
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage stage)
{
root.getChildren.add(myLabel);
System.out.println(myLabel.widthProperty());
stage.setScene(new Scene(root, 500, 500);
stage.show();
}
}
It seems like the Label doesn't update its width. When I print it with :
System.out.println(myLabel.widthProperty());
I get :
ReadOnlyDoubleProperty [bean : Label#xxxxxxxx[styleClass = label]'test', name:width, value: 0.0]
So I suppose that the width of a Label is calculated during Layout, and since a Pane doesn't have any Layout manager, it is never calculated, am I right ?
If so, how am I supposed to get this width ?
If not, the problem is somewhere else, and I can't figure out where.
For informations : I need to use a Pane to draw some Shape, and control their placement. The purpose of the Label is to print some informations on the shapes. I need the width of the Label because they are some Line on the shapes, and I don't want them to cross the Label.
Don't hesitate to ask for further informations.
JavaFX only lays out components which are visible.
You can see this if you try something like this:
final Label myLabel = new Label("test");
final Pane myPane = new Pane(myLabel);
System.out.println(myLabel.getWidth());
stage.setScene(new Scene(myPane, 640, 480));
stage.show();
System.out.println(myLabel.getWidth());
which prints
0.0
19.5
As you can see here, the label's size is not set until stage.show() is called. You can also override Pane to see layoutChildren() is called for the first time at this point.
As a sidenote, if you want to find out the width of some text without showing it in a Stage, you can make a Text node (instead of a label) and get its preferred width, e.g.
new Text("test").prefWidth(0)
Hope this helps!

WebView size in JavaFX Stage

I want to change the size of the Stage and WebView of a JavaFx application.
I can change the windows size but the browser size does not increases from 800px so the display of the html page does not fit all the windows.
It looks like this (note the light grey zone on the right):
This is some code of the page:
public class EfadosApp extends Application {
private Scene scene;
#Override public void start(Stage stage) {
scene = new Scene(new Browser(), 1000,500);
stage.setScene(scene);
stage.show();
}
}
class Browser extends Region {
final WebView browser = new WebView();
final WebEngine webEngine = browser.getEngine();
public Browser() {
webEngine.load("www.oracle.com");
getChildren().add(browser);
}
}
You can simply fix this with extending for example StackPane rather than Region:
class Browser extends StackPane {
...
}
The difference is that Region resizes the children to their preferred size :
By default a Region inherits the layout behavior of its superclass,
Parent, which means that it will resize any resizable child nodes to
their preferred size, but will not reposition them. If an application
needs more specific layout behavior, then it should use one of the
Region subclasses: StackPane, HBox, VBox, TilePane, FlowPane,
BorderPane, GridPane, or AnchorPane.
While StackPane tries to resize its children to fit the content area:
The stackpane will attempt to resize each child to fill its content
area. If the child could not be sized to fill the stackpane (either
because it was not resizable or its max size prevented it) then it
will be aligned within the area using the alignment property, which
defaults to Pos.CENTER.
This of course also means, if you use Region but either you set the preferred size of the WebView to "something big" like
browser.setPrefSize(5000, 5000);
or you bind the heightProperty and widthProperty of the WebView to the corresponding properties of the Stage,
Browser browser = new Browser();
browser.browser.prefHeightProperty().bind(stage.heightProperty());
browser.browser.prefWidthProperty().bind(stage.widthProperty());
scene = new Scene(browser, 1000, 500);
it will also work as you expect.

JavaFX - Adding a ScrollPane popup which closes when user clicks out of it

My title is badly worded because my problem is very hard to describe, so I drew an image for it:
I have an ImageView object which represents a pile of cards (not poker cards but just used them as an example). When this image view is clicked, I need a window to popup that features a ScrollPane and shows them all the card objects that are in the linked list. When the user clicks anywhere outside of the window (and later on, any right mouse button click) the scrollpane window needs to close.
Ways that I have already tried:
Scene with APPLICATION_MODAL. Then did Scene.showAndWait(). I didn't like this method because it made another application on the user's taskbar. It also felt clunky and just bad.
Changed my root pane to a StackPane, then added this Scrollpane to the stackpane when the user clicked on the deck. This for some reason was really slow and seemed really obtrusive. It was also annoying because my alternate class needed to have access to the root pane (since when it closes, it needs to go to the root StackPane and call .remove() on itself).
Are there any other better ways to accomplish this? My application is going to have many of these piles and so this framework needs to work very well.
I would still propose to open a new Stage with some restrictions to solve your issues with this approach.
You can use the initOwner property of the Stage to have another Stage as owner, therefore no other icon will appear on the taskbar.
You can use the initStyle property with TRANSPARENT or UNDECORATED StageStlye, this will ensure that only the content is visible.
And in the end you can use the focusedProperty to check whether the Stage lost focus to close it.
Example
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
Button b = new Button("Open deck");
b.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Stage popupStage = new Stage();
popupStage.initOwner(primaryStage);
popupStage.initStyle(StageStyle.UNDECORATED);
Scene sc = new Scene(new ScrollPane(), 300, 300);
popupStage.setScene(sc);
popupStage.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue,
Boolean newValue) {
if(!newValue)
popupStage.close();
}
});
popupStage.show();
}
});
root.setCenter(b);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
It is also possible of course to not open it in a new Stage, but draw a ScrollPane inside the current Stage which overlaps the content of the Stage using for example an AnchorPane or Group as root, but the first solution has the advantage that you are not bound to the dimensions of main Stage (the popup can have any size that you want).
You can achieve this with a low level system hook that catches the mouse events.
http://kra.lc/blog/2016/02/java-global-system-hook/ or https://github.com/kwhat/jnativehook/releases
I hope that is what you needed, otherwise i got your question wrong.

what is the difference between stackpane and root in javafx?

I was practicing for javafx for doing pie chart. Following are the codes for developing pie chart. If i do with the Group and with the StackPane,I find no difference in the output.I have commented the Group part.Just wandering the difference between the two.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.chart.PieChart.Data;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class ChartApp1 extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
PieChart pieChart = new PieChart();
//Group p=new Group();
pieChart.setData(getChartData());
primaryStage.setTitle("PieChart");
StackPane root = new StackPane();
root.getChildren().add(pieChart);
//p.getChildren().add(pieChart);
primaryStage.setScene(new Scene(root, 400, 250));
primaryStage.show();
}
private ObservableList<PieChart.Data> getChartData() {
ObservableList<PieChart.Data> answer = FXCollections.observableArrayList();
answer.addAll(new PieChart.Data("java", 17.56),
new PieChart.Data("C", 17.06),
new PieChart.Data("C++", 8.25),
new PieChart.Data("C#", 8.20),
new PieChart.Data("ObjectiveC", 6.8),
new PieChart.Data("PHP", 6.0),
new PieChart.Data("(Visual)Basic", 4.76),
new PieChart.Data("Other", 31.37));
return answer;
}
}
According to the official documentation,
StackPane lays out its children in a back-to-front stack. The z-order of the children is defined by the order of the children list
with the 0th child being the bottom and last child on top. If a border
and/or padding have been set, the children will be layed out within
those insets.
The stackpane will attempt to resize each child to fill its
content area. If the child could not be sized to fill the stackpane
(either because it was not resizable or its max size prevented it)
then it will be aligned within the area using the alignment
property, which defaults to Pos.CENTER
While the official documentation for the Group class states that
A Group node contains an ObservableList of children that are
rendered in order whenever this node is rendered. A Group will take on the collective bounds of its children and is not directly
resizable.
Any transform, effect, or state applied to a Group will be applied to
all children of that group. Such transforms and effects will NOT be
included in this Group's layout bounds, however if transforms and
effects are set directly on children of this Group, those will be
included in this Group's layout bounds.
By default, a Group will "auto-size" its managed resizable children to
their preferred sizes during the layout pass to ensure that Regions
and Controls are sized properly as their state changes. If an
application needs to disable this auto-sizing behavior, then it should
set autoSizeChildren to false and understand that if the preferred
size of the children change, they will not automatically resize.
StackPane is a container object; it is used to lay out nodes in a specific manner (back-to-front, according to z-order). Functionally, StackPane is a layout manager. The term root or root node refers to the top-most node in the scene graph of a JavaFX application. The root node is passed to the constructor of the Scene; it is the only node that does not have a parent. Group is a container too. It is not a layout manager; it places its children in absolute coordinates. StackPane and Group can be root nodes.
You would see a difference between Group and StackPane if you wanted to place your chart at a specific location in the application's client area. Placing a chart at x=10 and y=10 would work with Group but would not work with StackPane.

Categories

Resources