JavaFX - bugs bei der ermittlung der breite der nodes

Goekhan

Mitglied
In der Main Klasse instanziere ich lediglich die RoundedMenu Klasse und übergabe eine String[]. Hier passt alles.
Java:
package RoundedNav2;

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;


public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception  {
        try {
            primaryStage.setTitle("Edelherz");
            primaryStage.getIcons().add(new Image("images/Playlist-16.png"));

            Group root = new Group();

            String[] myStrings = new String[] {"Home","Projects","Events","Contact"};
            RoundedMenu roundedMenu = new RoundedMenu(myStrings);

            root.getChildren().add(roundedMenu);
            Scene scene = new Scene(root, 400, 400);
            scene.getStylesheets().add(getClass().getResource("myStyle.css").toExternalForm());
            primaryStage.setScene(scene);

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

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

In der RoundedMenu Klasse habe eine deBug method angelegt, weil ich sonst nicht die breite der nodes ermitteln konnte, bevor sie gezeichnet wurden in der scene. Der MenuItem wird nicht richtig positiniert und bei MouseEnter bewegt sich nur der zuletzt instanzierte MenuItem

Java:
package RoundedNav2;


import java.util.Arrays;
import java.util.List;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.scene.layout.Region;
import javafx.util.Duration;

public class RoundedMenu extends Region{

    private List myList;
    private String str;
    private int listLength;
    private int cnt = 0;
    private double xPos;
    private MenuItem mItem = null;

    public RoundedMenu(String[] myStrings){
        this.myList = Arrays.asList(myStrings);
        //System.out.println(myList.get(2));
        listLength = myList.size();
        deBug();
    }

    public void deBug(){
        if(cnt == 0){
            xPos = 0;
        }else
        {
            xPos += mItem.getWidth();
        }

        if(cnt < listLength){
            mItem = new MenuItem(myList.get(cnt).toString());
            System.out.println("xPos: " + xPos);
            mItem.setLayoutX(xPos);
            getChildren().add(mItem);
            mItem.setOpacity(.5);
            cnt++;
            Timeline timeline = new Timeline(new KeyFrame(Duration.millis(50),ae -> deBug()));
            timeline.play();   
        }
    }
}

Hier im MenuItem Klasse in der ich leider nur über ein deBug die richtige Breite ermitteln konnte um den dazugehörigen Text zu zentrieren.


Java:
package RoundedNav2;

import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.util.Duration;

public class MenuItem extends Region {

    private static Text txt;
    private static Rectangle r;
    private static Group root;
    private static Pane holder;
    private static int startYpos = -100;
    private static int endMovePos = -60;
    private static int textOffsetFromBottom = 5;
    private static int rectangleTextOffset = 15;
    private String menuItemText;



        // ******************** Constructors **************************************
        public MenuItem(String menuItemText) {       


            this.menuItemText = menuItemText;
            txt = new Text(menuItemText);
            txt.getStyleClass().add("roundedNavText");
            txt.applyCss();

            r = new Rectangle();
            r.setWidth(50);
            r.setHeight(150);
            r.setArcHeight(25);
            r.setArcWidth(25);
            r.setFill(Color.web("#668845"));

            holder = new Pane();           
            holder.setLayoutY(startYpos);

            holder.setOnMouseClicked(e->{
                System.out.println("CLICK");;
            });


            holder.setOnMouseEntered(e->{
                moveRec(0.3, endMovePos, holder);
                System.out.println(this);
            });

            holder.setOnMouseExited(e->{
                moveRec(0.3, startYpos, holder);
            });


            holder.getChildren().addAll(r, txt);
            getChildren().add(holder);

            Timeline timeline = new Timeline(new KeyFrame(Duration.millis(1),ae -> deBug()));
            timeline.play();
        }


        public static void deBug(){
            final double tWidth = txt.getLayoutBounds().getWidth();
            System.out.println("tWidth: " + tWidth);
            double tHeight = txt.getBoundsInLocal().getHeight();
            r.setWidth(tWidth + (rectangleTextOffset * 2));
            txt.setLayoutX(r.getWidth()/2-tWidth/2);
            txt.setLayoutY(r.getHeight()-tHeight/2-textOffsetFromBottom);

        }


        public static void moveRec(double d, int kv, Node obj){
            Timeline t = new Timeline();
            Duration duration = Duration.seconds(d);
            KeyValue keyValue = new KeyValue(obj.layoutYProperty(), kv, Interpolator.EASE_OUT);
            t.getKeyFrames().add(new KeyFrame(duration, keyValue));
            t.play();
        }
}

Habt ihr eine andere Alternative als so ein verzögerndes aufrufen einer Methode? Warum wird das zweite MenuItem nicht richtig positioniert? Und wieso reagiert der letzte MenuItem auf Bewegung?
 
Das Problem ist, das zur Zeit der Initialisierung deines RoundedMenus es noch nicht gelayoutet wurde - es hat praktisch keine Grösse. Ich verwenden in den Fällen immer einen Listener auf den LayoutBounds des fraglichen Nodes und reagiere dann darauf (richtig zeichen, aber Achtung: kann mehrfach aufgerufen werden!). Versuch das mal und sag, ob es hilft...
 
Java:
    @FXML private Pane canvasGroup;
    
    private ChangeListener<Bounds> layoutChangeListener = this::handleLayoutChanges;

    @FXML
    protected void initialize() {
    canvasGroup.layoutBoundsProperty().addListener(layoutChangeListener);
    }

    private void handleLayoutChanges(ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) {
        // only when the bounds are existent
        if (newValue == null)
            return;
        // and not below ore equal to zero
        if (newValue.getWidth() <= 0.0 || newValue.getHeight() <= 0.0)
            return;
        if (canvas != null) {
            canvasGroup.getChildren().remove(canvas);
            canvas = null;
        }
        // create a new canvas with the container's size
        canvas = new Canvas(canvasGroup.getWidth(), canvasGroup.getHeight());
        // add it to the container
        canvasGroup.getChildren().add(canvas);
        // trigger to draw the content
        drawCanvasContent();
    }

Hier wird halt ein neues Canvas erstellt (Canvas skaliert leider nicht, daher muss man es und den Inhalt bei Layout-Veränderungen - wenn der Container grösser oder kleiner wird - neu zeichen).
 

Zurück
Oben