FXML StackPane doesn't align children correctl

2019-09-12 04:07发布

I want a Rectangle and a Label to align in the StackPane, however my code doesn't achieve the desired result:

enter image description here

FXML:

<fx:root type="HBox" xmlns:fx="http://javafx.com/fxml">
    <StackPane fx:id="pane">
        <children>
            <Rectangle fx:id="bubble" fill="#ffffff"></Rectangle>
            <Label fx:id="message" style="-fx-border-color:black; -fx-border-width: 1; -fx-border-style: solid;"></Label>
        </children>
    </StackPane>
</fx:root>

Controller:

public class MessageBubble extends HBox implements Initializable {
    @FXML
    private StackPane pane;
    @FXML
    private Label message;
    @FXML
    private Rectangle bubble;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        message.setText("message");
        pane.setAlignment(Pos.CENTER);

        bubble.setArcWidth(15.0);
        bubble.setArcHeight(15.0);

        bubble.setStroke(Color.BLACK);

        message.widthProperty().add(10.0).addListener((observable, oldValue, newValue) -> {
            bubble.setWidth(newValue.doubleValue());
            pane.layout();
        });
        message.heightProperty().add(10.0).addListener((observable, oldValue, newValue) -> {
            bubble.setHeight(newValue.doubleValue());
            pane.layout();
        });
    }

    public MessageBubble() {
        FXMLLoader loader = new FXMLLoader(this.getClass().getResource("MessageBubble.fxml"));
        loader.setRoot(this);
        loader.setController(this);

        try {
            loader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Update

If I do sizing programmatically, it aligns fine:

this.pane.getChildren().addAll(this.bubble, this.message);
bubble.setFill(Color.ALICEBLUE);
bubble.setArcWidth(15.0);
bubble.setArcHeight(15.0);

bubble.setStroke(Color.BLACK);

message.setStyle("-fx-border-color:black; -fx-border-width: 1; -fx-border-style: solid;");

message.setText("message");

message.setPrefWidth(60.0);
message.setPrefHeight(25.0);

bubble.setWidth(70.0);
bubble.setHeight(30.0);

But this way, I need to calculate the size myself.

1条回答
家丑人穷心不美
2楼-- · 2019-09-12 04:50

Basically, if you need a Text or Label with colored backround, the easiest approach to use CSS for a single Label or Text, without any Rectangle objects.

Example to use Label with CSS

Main.java

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            HBox root = new HBox();

            Scene scene = new Scene(root,400,400);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());

            Label message = new Label();
            TextArea textArea = new TextArea();
            textArea.setPrefWidth(200);
            textArea.setText("message");
            message.textProperty().bind(textArea.textProperty());
            message.getStyleClass().add("rounded-background-label");

            root.getChildren().addAll(message, textArea);

            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

application.css

.rounded-background-label {
    -fx-background-color: black, white;
    -fx-background-insets: 0, 1;
    -fx-padding: 10px;
    -fx-background-radius: 12px;
}

The answer from @jewelsea on this question is really detailed:

JavaFX 2: resizable rectangle containing text

But if you want to stay with the Label+Rectangle solution

I fear it is related to this bug: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8137252

As workaround you can request the layout update of the StackPane, as you have tried in the listeners.

The only problem with your code is that you should replace:

pane.layout();

with

Platform.runLater(new Runnable() {
    @Override
    public void run() {
        pane.requestLayout();

    }
});

which will ensure that the layout will happen on the GUI Thread.

Example

LabelRectangleTest.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.shape.*?>

<HBox prefHeight="100.0" prefWidth="200.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="application.LabelRectangleTestController">
  <children>
    <HBox prefHeight="100.0" prefWidth="200.0">
      <children>
        <StackPane fx:id="pane" minHeight="126.0" prefHeight="126.0" prefWidth="200.0" HBox.hgrow="ALWAYS">
          <children>
            <Rectangle fx:id="bubble" arcHeight="15.0" arcWidth="15.0" fill="WHITE" height="27.75" stroke="BLACK" strokeType="INSIDE" width="68.0" />
            <Label fx:id="message" text="Label" />
          </children>
        </StackPane>
        <TextArea fx:id="textArea" prefWidth="200.0" wrapText="true" />
      </children>
    </HBox>
  </children>
</HBox>

LabelRectangleTestController.java

public class LabelRectangleTestController implements Initializable {

    @FXML
    private Label message;
    @FXML
    private Rectangle bubble;
    @FXML
    private StackPane pane;
    @FXML
    private TextArea textArea;

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        textArea.setText("message");
        message.textProperty().bind(textArea.textProperty());

        message.widthProperty().addListener((observable, oldValue, newValue) -> {
            bubble.setWidth(newValue.intValue() + 10);
            Platform.runLater(new Runnable() {
                @Override
                public void run() {
                    pane.requestLayout();
                }
            });
        });
        message.heightProperty().addListener((observable, oldValue, newValue) -> {
            bubble.setHeight(newValue.doubleValue() + 10);
            Platform.runLater(new Runnable() {
                @Override
                public void run() {
                    pane.requestLayout();

                }
            });
        });

    }

}

Main.java

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("LabelRectangleTest.fxml"));
            HBox root = loader.load();
            Scene scene = new Scene(root,400,400);

            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
查看更多
登录 后发表回答