LibGDX TextButton do not respond to mouse clicks - button

The text button draws normally (no errors or warnings) but it doesn't respond to mouse clicks. The situation is the same on Desktop and Android build. I've read and followed every topic about it with no results. What am I doing wrong?
Maybe I need to set something in stage object?
Here's my MenuScreen class code:
public class MenuScreen implements Screen {
private OrthographicCamera camera;
private BitmapFont font;
private TextureAtlas menuGraphics;
Stage stage;
TextButton button;
TextButtonStyle buttonStyle;
Skin skin;
public MenuScreen(Game game)
{
//create satage object
stage = new Stage();
//create font object
font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"),false);
font.setColor(Color.RED);
//load buttons texture atlas and create Skin object
menuGraphics = new TextureAtlas( Gdx.files.internal( "buttons/buttons.pack" ) );
skin = new Skin();
skin.addRegions(menuGraphics);
// Store the default libgdx font under the name "defaultFont".
skin.add("defaultFont",font);
//Set button style
buttonStyle = new TextButtonStyle();
buttonStyle.up = skin.newDrawable("yellow");
buttonStyle.down = skin.newDrawable("orange");
buttonStyle.checked = skin.newDrawable("orange");
buttonStyle.over = skin.newDrawable("orange");
buttonStyle.font = skin.getFont("defaultFont");
skin.add("default", buttonStyle);
//assign button style
button=new TextButton("PLAY",buttonStyle);
button.setPosition(200, 200);
button.setSize(200,200);
//add button to stage
stage.addActor(button);
//add click listener to the button
button.addListener(new ClickListener() {
public void clicked(InputEvent event, float x, float y) {
button.setText("Starting new game");
Gdx.app.log("MenuScreen", "clicked button");
}
});
Gdx.app.log("MenuScreen", "create");
}
#Override
public void resize(int width, int height )
{
float aspectRatio = (float) width / (float) height;
camera = new OrthographicCamera(640, 360);
camera.translate(320,180);
camera.update();
stage.setViewport(new FillViewport(640, 360, camera));
stage.getViewport().setCamera(camera);
Gdx.app.log( "MenuScreen", "Resizing screen to: " + width + " x " + height );
}
#Override
public void show() {
Gdx.input.setInputProcessor(stage);
float w = Gdx.graphics.getWidth();
float h = Gdx.graphics.getHeight();
resize((int)w,(int)h);
Gdx.app.log( "MenuScreen", "Show screen code" );
}
#Override
public void render(float delta)
{
Gdx.gl.glClearColor( 0f, 1f, 0f, 1f );
Gdx.gl.glClear( GL20.GL_COLOR_BUFFER_BIT );
stage.act( delta );
// draw the actors
stage.draw();
}
#Override
public void dispose()
{
}
#Override
public void pause() {
}
#Override
public void resume() {
}
#Override
public void hide() {
}
}

I Finally solved it!
Additional line in resize function did the trick:
stage.getViewport().update(width, height);
I realized that the problem was caused by setting the Camera and Viewport in the resize function. When I removed that code (an adjusted the button position to be visible with standard stage viewport) the button started working.
But I wanted to use camera and viewport. Reading about stage resizing in LibGDX documantation (https://github.com/libgdx/libgdx/wiki/Scene2d) I found out that after setting camera, the viewport needs to be updated, otherwise the actor boundries are not recalculated. So I added the viewport update line in resize function and it started working!
The new resize function looks like that:
#Override
public void resize(int width, int height )
{
float aspectRatio = (float) width / (float) height;
camera = new OrthographicCamera(640, 360);
camera.translate(320,180);
camera.update();
stage.setViewport(new FillViewport(640, 360, camera));
stage.getViewport().setCamera(camera);
stage.getViewport().update(width, height);
Gdx.app.log( "MenuScreen", "Resizing screen to: " + width + " x " + height );
}

add implements ApplicationListener to your class
public MenuScreen implements ApplicationListener
hope this helps.

Related

JavaFX - Get Actual Computed-Size of HBOX

I have a simple HBOX control which uses USE_COMPUTED_SIZE in Pref-Height, hence the size is all calculated and adjusted by the controls inside, which are a couple VBOX.
The issue comes when I try to add a new Pane as a children to the HBOX and draw a vertical line from top to bottom of the HBOX, so I write my line:
int startX = 5;
int startY = 0;
int endX = 5;
Line line = new Line(startX,startY,endX,hbox.getHeight());
Here, I need the hbox.getHeight(), but surprise: it is =-1, because it is using USE_COMPUTED_SIZE. So, how can I get the real (computed) value of hbox.getHeight()?
Not tested, but I think you can do something like:
public class HBoxWithLine extends HBox {
// Example of configurable property:
private final DoubleProperty lineOffset = new SimpleDoubleProperty(5);
public DoubleProperty lineOffsetProperty() {
return lineOffset ;
}
public final double getLineOffset() {
return lineOffsetProperty().get();
}
public final void setLineOffset(double lineOffset) {
lineOffsetProperty().set(lineOffset);
}
private final Line line = new Line();
public HBoxWithLine() {
getChildren().add(line);
// request layout when offset is invalidated:
lineOffset.addListener(obs -> requestLayout());
}
#Override
protected void layoutChildren() {
line.setStartX(getLineOffset());
line.setEndX(getLineOffset());
line.setStartY(0);
line.setEndY(getHeight());
super.layoutChildren();
}
}
Now you can just create a HBoxWithLine and add (additional) child nodes to it, set it's pref width and height to either fixed values, or Region.USE_COMPUTED_SIZE, etc., and it should just work.

Fitting rotated ImageView into Application Window / Scene

In JavaFX I am trying to show an rotated ImageView in an Application Window.
Therefore I have put it into a stackPane to have it always centered and I have bound the widths/heights of the ImageView and the stackPane to the scene's width/height to view it just as large as possible.
This works fine as soon as the Image is not rotated.
As soon as I rotate the Image by 90° using stackPane.setRotate(90) (and exchange binding for width/height) then the stackPane is no longer bound to the upper left corner of the Application Window (or scene).
What can I do to place the rotated image correctly?
In the example code [any key] will toggle the rotation 90°/0° so the location problem of the rotated image becomes visible:
public class RotationTest extends Application {
boolean rotated = false;
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Rotation test");
Group root = new Group();
Scene scene = new Scene(root, 1024,768);
//a stackPane is used to center the image
StackPane stackPane = new StackPane();
stackPane.setStyle("-fx-background-color: black;");
stackPane.prefHeightProperty().bind(scene.heightProperty());
stackPane.prefWidthProperty().bind(scene.widthProperty());
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
//toggle rotate 90° / no rotation
rotated = !rotated;
stackPane.prefHeightProperty().unbind();
stackPane.prefWidthProperty().unbind();
if (rotated){
stackPane.setRotate(90);
//rotation: exchange width and height for binding to scene
stackPane.prefWidthProperty().bind(scene.heightProperty());
stackPane.prefHeightProperty().bind(scene.widthProperty());
}else{
stackPane.setRotate(0);
//no rotation: height is height and width is width
stackPane.prefHeightProperty().bind(scene.heightProperty());
stackPane.prefWidthProperty().bind(scene.widthProperty());
}
}
});
final ImageView imageView = new ImageView("file:D:/test.jpg");
imageView.setPreserveRatio(true);
imageView.fitWidthProperty().bind(stackPane.prefWidthProperty());
imageView.fitHeightProperty().bind(stackPane.prefHeightProperty());
stackPane.getChildren().add(imageView);
root.getChildren().add(stackPane);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Results:
Without rotation the stackPane (black) fits the window perfectly and the image has the correct size even if the window is resized with the mouse.
After pressing [any key] the stackPane is rotated. The stackPane (black) seems to have the correct width/height and also the image seems to be correctly rotated. But the stackPane is no longer in the upper left corner??? It moves around when the window is resized with the mouse???
Why not simply leave the Group and the preferred sizes out of the equation?
The root is automatically resized to fit the scene and you can use it's width/height properties to bind the fitWidth and fitHeight properties:
private static void setRotated(boolean rotated, ImageView targetNode, Pane parent) {
double angle;
if (rotated) {
angle = 90;
targetNode.fitWidthProperty().bind(parent.heightProperty());
targetNode.fitHeightProperty().bind(parent.widthProperty());
} else {
angle = 0;
targetNode.fitWidthProperty().bind(parent.widthProperty());
targetNode.fitHeightProperty().bind(parent.heightProperty());
}
targetNode.setRotate(angle);
}
#Override
public void start(Stage primaryStage) {
Image image = new Image("file:D:/test.jpg");
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
StackPane root = new StackPane(imageView);
root.setStyle("-fx-background-color: black;");
// initialize unrotated
setRotated(false, imageView, root);
Scene scene = new Scene(root, 1024, 768);
scene.setOnKeyPressed(evt -> {
// toggle between 0° and 90° rotation
setRotated(imageView.getRotate() == 0, imageView, root);
});
primaryStage.setScene(scene);
primaryStage.show();
}
Note that this may not result in correct layout, if placed in some other layout, since the size constraints may be calculated wrong.
You could implement your own region though to fix this:
public class CenteredImage extends Region {
private final BooleanProperty rotated = new SimpleBooleanProperty();
private final ImageView imageView = new ImageView();
public CenteredImage() {
// make sure layout gets invalidated when the image changes
InvalidationListener listener = o -> requestLayout();
imageProperty().addListener(listener);
rotated.addListener((o, oldValue, newValue) -> {
imageView.setRotate(newValue ? 90 : 0);
requestLayout();
});
getChildren().add(imageView);
imageView.setPreserveRatio(true);
}
public final BooleanProperty rotatedProperty() {
return rotated;
}
public final void setRotated(boolean value) {
this.rotated.set(value);
}
public boolean isRotated() {
return rotated.get();
}
public final void setImage(Image value) {
imageView.setImage(value);
}
public final Image getImage() {
return imageView.getImage();
}
public final ObjectProperty<Image> imageProperty() {
return imageView.imageProperty();
}
#Override
protected double computeMinWidth(double height) {
return 0;
}
#Override
protected double computeMinHeight(double width) {
return 0;
}
#Override
protected double computePrefWidth(double height) {
Image image = getImage();
Insets insets = getInsets();
double add = 0;
if (image != null && height > 0) {
height -= insets.getBottom() + insets.getTop();
add = isRotated()
? height / image.getWidth() * image.getHeight()
: height / image.getHeight() * image.getWidth();
}
return insets.getLeft() + insets.getRight() + add;
}
#Override
protected double computePrefHeight(double width) {
Image image = getImage();
Insets insets = getInsets();
double add = 0;
if (image != null && width > 0) {
width -= insets.getLeft() + insets.getRight();
add = isRotated()
? width / image.getHeight() * image.getWidth()
: width / image.getWidth() * image.getHeight();
}
return insets.getTop() + insets.getBottom() + add;
}
#Override
protected double computeMaxWidth(double height) {
return Double.MAX_VALUE;
}
#Override
protected double computeMaxHeight(double width) {
return Double.MAX_VALUE;
}
#Override
protected void layoutChildren() {
Insets insets = getInsets();
double left = insets.getLeft();
double top = insets.getTop();
double availableWidth = getWidth() - left - insets.getRight();
double availableHeight = getHeight() - top - insets.getBottom();
// set fit sizes
if (isRotated()) {
imageView.setFitWidth(availableHeight);
imageView.setFitHeight(availableWidth);
} else {
imageView.setFitWidth(availableWidth);
imageView.setFitHeight(availableHeight);
}
// place image
layoutInArea(imageView, left, top, availableWidth, availableHeight, 0, null, false,
false, HPos.CENTER, VPos.CENTER);
}
}
#Override
public void start(Stage primaryStage) {
Image image = new Image("file:D:/test.jpg");
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
CenteredImage imageArea = new CenteredImage();
imageArea.setImage(image);
imageArea.setStyle("-fx-background-color: black;");
imageArea.setPrefWidth(300);
SplitPane splitPane = new SplitPane(new Region(), imageArea);
SplitPane.setResizableWithParent(imageArea, true);
Scene scene = new Scene(splitPane, 1024, 768);
scene.setOnKeyPressed(evt -> {
// toggle between 0° and 90° rotation
imageArea.setRotated(!imageArea.isRotated());
});
primaryStage.setScene(scene);
primaryStage.show();
}
I found a solution :-) Fabian's approach inspired me (thank you!!) And my old friend Pit helped me with debugging (also thank you!!)
It seems that the layout location algorithm of JavaFX has a problem when resize() is applied to rotated Panes (or even Nodes - I have not tried):
Following Fabian's idea I debugged into the layoutChildren() method of class Pane. I found that the relocation after setRotate() is correct and keeps the center of the child pane as expected. But as soon as resize() is called (which is done because of fitting the rotated child pane again into its father and additionally always when the window is resized by the user) the origin calculation goes wrong:
The picture above depicts a sequence of setRotate(90), resize() and relocate() in green and the same for setRotate(270) in blue. A little blue/green circle depicts the corresponding origin together with its coordinates in the 1024x786 example.
Analysis
It seems that for calculation the position of the Pane resize() does not use the height and width from BoundsInParent-Property (see JavaFX-Docu of Node) but from getWidth() and getHeight() which seem to reflect BoundsInLocal. As a consequence, for rotations of 90° or 270° height and width seem to be interchanged. Therefore the error in the calculation for the new origin is just the half of the difference between width and height (delta=(width-height)/2) when resize() tries to center the child pane again after the resizing.
Solution
A relocation(delta,-delta) needs to be applied after resizing for Panes with rotation=90 or 270 degrees.
The structure of my implementation follows Fabian's basic idea: I have build a layouter RotatablePaneLayouter:Region that just overwrites the layoutChildren() method. In its constructor it gets a Pane (in my example a StackPane) which can contain any number of children (in my example an ImageView) and that can be rotated.
LayoutChildren() then just executes resize() and relocate() for the child pane to fit it completely into the RotateablePaneLayouter respecting the orientation of the child pane.
The Layouter Helper (RotateablePaneLayouter:Region)
public class RotatablePaneLayouter extends Region {
private Pane child;
public RotatablePaneLayouter(Pane child) {
getChildren().add(child);
this.child = child;
// make sure layout gets invalidated when the child orientation changes
child.rotateProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
requestLayout();
}
});
}
#Override
protected void layoutChildren() {
// set fit sizes:
//resize child to fit into RotatablePane and correct movement caused by resizing if necessary
if ((child.getRotate() == 90)||(child.getRotate() == 270)) {
//vertical
child.resize( getHeight(), getWidth() ); //exchange width and height
// and relocate to correct movement caused by resizing
double delta = (getWidth() - getHeight()) / 2;
child.relocate(delta,-delta);
} else {
//horizontal
child.resize( getWidth(), getHeight() ); //keep width and height
//with 0° or 180° resize does no movement to be corrected
child.relocate(0,0);
}
}
}
To use it: Place the Pane to be rotated into the Layouter first instead of placing the Pane directly.
Here the code for the example's main program. You can use the space bar to rotate the child pane by 90, 180, 270 and again 0 degrees. You can also resize the window with the mouse. The layouter always manages to place the rotated pane correctly.
Expample for using the Layouter
public class RotationTest extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
//image in a StackPane to be rotated
final ImageView imageView = new ImageView("file:D:/Test_org.jpg");
imageView.setPreserveRatio(true);
StackPane stackPane = new StackPane(imageView); //a stackPane is used to center the image
stackPane.setStyle("-fx-background-color: black;");
imageView.fitWidthProperty().bind(stackPane.widthProperty());
imageView.fitHeightProperty().bind(stackPane.heightProperty());
//container for layouting rotated Panes
RotatablePaneLayouter root = new RotatablePaneLayouter(stackPane);
root.setStyle("-fx-background-color: blue;");
Scene scene = new Scene(root, 1024,768);
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.SPACE) {
//rotate additionally 90°
stackPane.setRotate((stackPane.getRotate() + 90) % 360);
}
}
});
primaryStage.setTitle("Rotation test");
primaryStage.setScene(scene);
primaryStage.show();
}
}
For me this seems like a workaround of a javaFX bug in resize().

How make scrollbar appear in Window with Panel with undefined size?

In the example bellow - window with Panel grows till it reaches browser window limits.
After that Panel content becomes scrollable but no scrollbar appear.
If I set Panel to fixed size scrollbar appear as per documentation.
Can someone suggest Vaadin pattern to implement scrollbars when Window or Panel size reaches browser window limit?
public class DemoUI extends UI {
#Override
protected void init(VaadinRequest vaadinRequest) {
VerticalLayout layout = new VerticalLayout();
layout.setSizeFull();
Button demo1 = new Button("Question");
demo1.addClickListener(new Button.ClickListener() {
#Override
public void buttonClick(Button.ClickEvent clickEvent) {
Window window = new Window();
window.setClosable(true);
window.center();
window.setResizable(true);
window.setModal(false);
window.setContent(new UndefWindow(window));
addWindow(window);
}
});
layout.addComponent(demo1);
setContent(layout);
}
}
public class UndefWindow extends VerticalLayout {
private Window window;
public UndefWindow(Window window){
this.window = window;
Panel panel = new Panel();
VerticalLayout layout = new VerticalLayout();
Button add10 =new Button("Add 10");
add10.addClickListener(new Button.ClickListener() {
#Override
public void buttonClick(Button.ClickEvent clickEvent) {
for (int i=0; i <10; i++){
layout.addComponent(new Label("Label: "+i));
}
window.center();
}
});
layout.addComponent(add10);
panel.setContent(layout);
addComponent(panel);
setExpandRatio(panel, 1.0f);
}
}
EDIT: I was able to achieve this with SizeReporter addon - code bellow
SizeReporter
public class UndefWindow extends VerticalLayout {
private Window window;
VerticalLayout root;
int rootHeight;
public UndefWindow(Window window, VerticalLayout root){
this.window = window;
this.root = root;
Panel panel = new Panel();
// panel.setHeight("400px");
SizeReporter sizeReporterRoot = new SizeReporter(root);
sizeReporterRoot.addResizeListener(new ComponentResizeListener() {
#Override
public void sizeChanged(ComponentResizeEvent event) {
System.out.println("Root size: " + event.getWidth() + " x " + event.getHeight());
rootHeight = event.getHeight();
}
});
SizeReporter sizeReporter = new SizeReporter(panel);
sizeReporter.addResizeListener(new ComponentResizeListener() {
#Override
public void sizeChanged(ComponentResizeEvent event) {
System.out.println("Panel size: " + event.getWidth() + " x " + event.getHeight());
if(event.getHeight()>rootHeight){
window.setHeight(rootHeight-60, Unit.PIXELS);
//window.center();
panel.setHeight(rootHeight-60, Unit.PIXELS);
}else
window.center();
}
});
VerticalLayout layout = new VerticalLayout();
Button add10 =new Button("Add 10");
add10.addClickListener(new Button.ClickListener() {
#Override
public void buttonClick(Button.ClickEvent clickEvent) {
for (int i=0; i <10; i++){
layout.addComponent(new Label("Label: "+i));
}
}
});
addComponent(add10);
panel.setContent(layout);
addComponent(panel);
}
}
You have a bit overly complicated thinking in your Window content. In Vaadin, Window itself extends Panel, so you do not need additional Panel there at all. So I stripped it away. Also in order to layouts to work properly, you want to have your VerticalLayout height undefined, i.e. to grow until browser window size is met. In order to Panel scrolling to work in that scenario, there needs to be some content of defined height, in this case it is the Label, so I set defined height to Label. Then adding labels, will add size of the VerticalLayout, and once browser height is exceeded, Panel gets scroll bars.
public class MyUI extends UI {
#Override
protected void init(VaadinRequest vaadinRequest) {
VerticalLayout layout = new VerticalLayout();
layout.setSizeFull();
Button demo1 = new Button("Question");
demo1.addClickListener(new Button.ClickListener() {
#Override
public void buttonClick(Button.ClickEvent clickEvent) {
Window window = new Window("Window");
window.setClosable(true);
window.center();
window.setResizable(true);
window.setModal(false);
window.setContent(new UndefWindow(window));
addWindow(window);
}
});
layout.addComponent(demo1);
setContent(layout);
}
public class UndefWindow extends VerticalLayout {
private Window window;
public UndefWindow(Window window){
this.window = window;
Button add10 =new Button("Add 10");
add10.addClickListener(new Button.ClickListener() {
#Override
public void buttonClick(Button.ClickEvent clickEvent) {
for (int i=0; i <10; i++){
Label label = new Label("Label: "+i);
label.setHeight("38px");
addComponent(label);
}
window.center();
}
});
addComponent(add10);
}
}
}

How can I center the virtual keyboard on bottom before it is shown - javafx

How can I center the keyboard on the bottom of the screen?
I just have the sizes of the keyboard/PopupWindow after the board is already shown. Before calling show() every function returns 0.0 for the asked width. I could set the position correctly if I just knew the width before.
The keyboard size might change later, that's why I can't do it with a set size.
I am using the fx-onscreen-keyboard
My little service:
public class KeyboardService {
private double screenWidth = Screen.getPrimary().getVisualBounds().getWidth();
private double screenHight = Screen.getPrimary().getVisualBounds().getHeight();
private double keyboardPosX = 0.0;
private double keyboardPosY = 0.0;
private KeyBoardPopup keyboardPopup;
public KeyboardService() {
keyboardPopup = KeyBoardPopupBuilder.create().initLocale(Locale.GERMAN).build();
keyboardPopup.setAutoHide(true);
keyboardPopup.setConsumeAutoHidingEvents(false);
keyboardPopup.getKeyBoard().setScale(2.5);
keyboardPopup.getKeyBoard().setLayer(DefaultLayer.DEFAULT);
keyboardPopup.getKeyBoard().setOnKeyboardCloseButton((e) -> {
keyboardPopup.hide();
});
}
public void showKeyboard(Node node){
keyboardPosX = (screenWidth - keyboardPopup.getWidth())/2;
//keyboardPosX = (screenWidth - keyboardPopup.getKeyBoard().getWidth())/2;
keyboardPosY = screenHight;
keyboardPopup.show(node, keyboardPosX, keyboardPosY);
}}
The width of the keyboard is defined when the popup that holds it is laid out, what happens right after you call show.
The easy way to do this is by listening to the widthProperty of the KeyBoardPopup, to get the new value, and then moving the window of the popup accordingly.
This will do:
public KeyboardService() {
keyboardPopup = KeyBoardPopupBuilder.create().initLocale(Locale.GERMAN).build();
keyboardPopup.setAutoHide(true);
keyboardPopup.setConsumeAutoHidingEvents(false);
keyboardPopup.getKeyBoard().setScale(2.5);
keyboardPopup.getKeyBoard().setLayer(DefaultLayer.DEFAULT);
keyboardPopup.getKeyBoard().setOnKeyboardCloseButton((e) -> {
keyboardPopup.hide();
});
// listen to width changes and center
keyboardPopup.widthProperty().addListener((obs, ov, nv) -> {
keyboardPosX = (screenWidth - nv.doubleValue()) / 2d;
keyboardPopup.getScene().getWindow().setX(keyboardPosX);
});
}
public void showKeyboard(Node node) {
keyboardPosX = (screenWidth - keyboardPopup.getWidth())/2;
keyboardPosY = screenHeight;
keyboardPopup.show(node, keyboardPosX, keyboardPosY);
}

JavaFX not positioning the node in the parent node

I have a problem with centering my dialog boxes (that are basically AnchorPanes that have caption in them) in their parent node (that is also AnchorPane, but I beleive this is irrelevant).
Code goes as follows:
public void showDialog(final Dialog dialog) {
dialogStack.add(dialog);
dialogCanvas.toFront();
dialog.toFront();
dialogCanvas.setVisible(true);
dialog.setVisible(true);
getChildren().add(dialog);
dialog.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if(newValue.doubleValue() > 0) {
centerDialog(dialog);
}
}
});
}
public void centerDialog(final Dialog dialog) {
double width = getWidth();
double height = getHeight();
dialog.setLayoutX(width / 2 - dialog.getWidth() / 2);
dialog.setLayoutY(height / 2 - dialog.getHeight() / 2);
}
I use the widthProperty change listener because you can only center the node when it gets rendered and only then it gets the width/height set.
When I debug the code at point when I have set the dialog's layout x/y the values are calculated fine but the dialog node at the end gets positioned at top/left corner (X:0/Y:0).
Why???
Can I somehow trigger the redraw of a parent node so that the element gets positioned righty? What am I doing wrong?
Kind regards,
Stjepan
Your options:
Continue using AnchorPane as a parent but center the child by using anchor constraints as
AnchorPane.setTopAnchor(dialog, height / 2 - dialog.getHeight() / 2);
AnchorPane.setLeftAnchor(dialog, width / 2 - dialog.getWidth() / 2);
on every change of width and height values of parent pane. You need add event listener for both width and height properties if you want the child pane to stay centered on window resizing.
Use Pane instead of AnchorPane as parent and center the child pane by setLayoutX() / setLayoutY(), on every change of parent's width and height values (again using event listeners). The Pane, in opposite to AnchorPane, will not layout its children other than resizing them to their preferred sizes.
Use StackPane instead of AnchorPane as parent and no need to center the child pane since StackPane will do it for you automatically.
Use the ControlsFX Dialogs.
A demo code for the first option:
public class AnchorDemo extends Application {
private final AnchorPane anchorPaneParent = new AnchorPane();
private final AnchorPane anchorPaneChild = new AnchorPane(new Label("TEXT TEXT TEXT TEXT TEXT"));
#Override
public void start(Stage primaryStage) {
anchorPaneParent.setStyle("-fx-border-color:red");
anchorPaneChild.setStyle("-fx-border-color:green");
anchorPaneParent.getChildren().add(anchorPaneChild);
anchorPaneParent.layoutBoundsProperty().addListener(new ChangeListener<Bounds>() {
#Override
public void changed(ObservableValue<? extends Bounds> arg0, Bounds oldBound, Bounds newBound) {
System.out.println("oldBound = " + oldBound);
System.out.println("newBound = " + newBound);
System.out.println("");
if (oldBound.getHeight() != newBound.getHeight() || oldBound.getWidth() != newBound.getWidth()) {
updateChildAnchorConstraint();
}
}
});
Scene scene = new Scene(anchorPaneParent, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
updateChildAnchorConstraint();
}
private void updateChildAnchorConstraint() {
AnchorPane.setTopAnchor(anchorPaneChild, anchorPaneParent.getHeight() / 2 - anchorPaneChild.getHeight() / 2);
AnchorPane.setLeftAnchor(anchorPaneChild, anchorPaneParent.getWidth() / 2 - anchorPaneChild.getWidth() / 2);
}
public static void main(String[] args) {
launch(args);
}
}
The problem was that this was all done in the single thread and the information about layout was not yet known... But using the Platform.runLater(...) I was able to solve the problem.
At the end it is pretty elegant when you know how to solve it...
public void showDialog(final Dialog dialog) {
dialogStack.add(dialog);
dialogCanvas.toFront();
dialog.toFront();
dialogCanvas.setVisible(true);
dialog.setVisible(true);
getChildren().add(dialog);
dialog.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if(newValue.doubleValue() > 0) {
centerDialog(dialog);
}
}
});
}
public void centerDialog(final Dialog dialog) {
Platform.runLater(new Runnable() {
#Override
public void run() {
double width = getWidth();
double height = getHeight();
dialog.setLayoutX(width / 2 - dialog.getWidth() / 2);
dialog.setLayoutY(height / 2 - dialog.getHeight() / 2);
}
});
}

Resources