Search code examples
javamultithreadingjavafxstackpanelgridpane

How to swap two GridPane nodes in JavaFX?


I want to emulate the swapping of array elements in a GUI in javafx. I have a grid pane Memory which acts like an array. Row 1 of the grid only has the array. In each row/col I have added a stack pane having image view and label. I want to change the image view of the current node and its next node. Along with the data.

I tried doing it through the loop but the result was not seen. So I used a thread to do the background image swapping but that did not allow swapping of label values. This is the closest I have got without swapping while changing the background.

public void insertFront() {
    System.out.println("InsertList before insert "+ insertList.size()+" : "+insertList);
    if (insertList.size() == 0){
        ObservableList<Node> nodes = insertArr[0].getChildren();
        Label temp = (Label) nodes.get(1);
        insertList.add(random.nextInt(999));
        temp.setText(""+insertList.get(0));
    } else {
        if (insertList.size() < max) {
            new Thread(()->{
                for (int i = insertList.size()-1; i >= 0; i--) {
                    ObservableList<Node> currNodes = insertArr[i].getChildren();
                    ImageView temp = (ImageView) currNodes.get(0);
                    temp.setImage(current);

                    ObservableList<Node> nextNodes = insertArr[i+1].getChildren();
                    ImageView tempNext = (ImageView) nextNodes.get(0);
                    tempNext.setImage(next);
                    Label tempLabel = (Label) currNodes.get(1);
                    Label tempNextLabel = (Label) nextNodes.get(1);

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    temp.setImage(allocated);
                    tempNext.setImage(allocated);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();

            for (int i = insertList.size()-1; i >= 0; i--) {
                ObservableList<Node> currNodes = insertArr[i].getChildren();
                ObservableList<Node> nextNodes = insertArr[i+1].getChildren();

                Label tempLabel = (Label) currNodes.get(1);
                Label tempNextLabel = (Label) nextNodes.get(1);

                tempNextLabel.setText(tempLabel.getText());
                tempLabel.setText("");
            }
            ObservableList<Node> nodes = insertArr[0].getChildren();
            Label temp = (Label) nodes.get(1);
            insertList.add(0, random.nextInt(999));
            temp.setText("" + insertList.get(0));
        } else {
            insertList.removeAll(insertList);
            fillInsert();
            insertFront();
        }
    }
    System.out.println("InsertList after insert "+ (insertList.size())+" : "+insertList);
}

But this is not synced. What I was trying to achieve is at least show the swapping of backgrounds first and then directly update the row. but because the threads are not synced the transition is slow and the grid row is updated.

I want help for at least syncing the threads so the transition is shown first. Then the row is changed.


Solution

  • Not sure if I completely understand, but I think you need a way to swap two nodes in a GridPane.

    Here is a full demo of how that would work.

    GridItem.java

    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.control.Label;
    import javafx.scene.layout.Background;
    import javafx.scene.layout.BackgroundFill;
    import javafx.scene.layout.CornerRadii;
    import javafx.scene.layout.StackPane;
    import javafx.scene.paint.Color;
    
    public class GridItem extends StackPane {
        private String text;
        private Color backgroundColor;
    
        public GridItem(String text, Color color) {
            setText(text);
            setBackgroundColor(color);
            Label textLabel = new Label(text);
            getChildren().add(textLabel);
            StackPane.setAlignment(textLabel, Pos.CENTER);
    
            setBackground(new Background(new BackgroundFill(color, CornerRadii.EMPTY, Insets.EMPTY)));
            setPrefSize(100, 100);
        }
    
        public Color getBackgroundColor() {
            return backgroundColor;
        }
    
        public void setBackgroundColor(Color backgroundColor) {
            this.backgroundColor = backgroundColor;
        }
    
        public String getText() {
            return text;
        }
    
        public void setText(String text) {
            this.text = text;
        }
    }
    

    HelloApplication.java

    import java.util.Random;
    
    import javafx.animation.FadeTransition;
    import javafx.application.Application;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.layout.GridPane;
    import javafx.scene.paint.Color;
    import javafx.stage.Stage;
    import javafx.util.Duration;
    
    public class HelloApplication extends Application {
        @Override
        public void start(Stage stage) {
            GridPane gridPane = new GridPane();
            for (int i = 0; i < 16; i++) {
                gridPane.add(
                        new GridItem("Hello " + i,
                                Color.color(new Random().nextDouble(), new Random().nextDouble(), new Random().nextDouble())), i % 4, i / 4);
            }
    
            Scene scene = new Scene(gridPane, 900, 650);
    
            scene.setOnMouseClicked(e -> swapGridPane(gridPane, 0, 0, 3, 3, true));
            stage.setTitle("Hello!");
            stage.setScene(scene);
            stage.show();
        }
    
        public static void swapGridPane(GridPane gridPane, int i1, int j1, int i2, int j2, boolean animate) {
            Node node1 = getNodeByRowColumnIndex(i1, j1, gridPane);
            Node node2 = getNodeByRowColumnIndex(i2, j2, gridPane);
            gridPane.getChildren().removeAll(node1, node2);
    
            gridPane.add(node1, i2, j2);
            gridPane.add(node2, i1, j1);
    
            if (animate) {
                fadeIn(node1);
                fadeIn(node2);
            }
        }
    
        public static void fadeIn(Node node) {
            FadeTransition fadeTransition = new FadeTransition(Duration.millis(200), node);
            fadeTransition.setFromValue(0);
            fadeTransition.setToValue(1);
            fadeTransition.play();
        }
    
        public static Node getNodeByRowColumnIndex(final int row, final int column, GridPane gridPane) {
            return gridPane.getChildren().stream()
                           .filter(node -> GridPane.getRowIndex(node) == row)
                           .filter(node -> GridPane.getColumnIndex(node) == column)
                           .findFirst().get();
        }
    
        public static void main(String[] args) {
            launch();
        }
    }