ashwor11
ashwor11

Reputation: 57

How to change positions of node in a GridPane

I have an 4x4 gridpane object consists of Tiles, extends ImageView, and i want to create method for changing places of connected Tiles by mouse drag and drop. I've figured out how to take first element which drag started but I couldn't get the referances of the ImageView which in drag dropped.

Tile Class

public class Tile extends ImageView {
    protected int xCoordinate;
    protected int yCoordinate;
    protected boolean isMoveable;
    
    protected ArrayList<Point2D> points = new ArrayList<>();
    

    public Tile(int xCoordinate, int yCoordinate) {
        this.xCoordinate = xCoordinate;
        this.yCoordinate = yCoordinate;
        super.setFitHeight(125);
        super.setFitWidth(125);
    }}

GridPane codes

GridPane gridPane = new GridPane();
for (Tile tile : tiles){
    gridPane.add(tile,tile.getXCoordinate(),tile.getYCoordinate());
}
StackPane centerPane = new StackPane();
centerPane.setStyle("-fx-background-color: white;");
centerPane.getChildren().add(gridPane);
centerPane.setPadding(new Insets(0,50,0,50));

I have tried this but I don't know how to get referance of connected Tile

        gridPane.setOnMouseDragged(e->{
        System.out.println(e.getTarget());

        gridPane.setOnMouseReleased(e1->{
            System.out.println(e1.getTarget());
        });
    });

I have created the codes for changing places but I should get the referance of the connected Tile when mouse released.

Upvotes: 2

Views: 993

Answers (1)

jewelsea
jewelsea

Reputation: 159416

Oracle provides an excellent tutorial on using drag and drop in JavaFX.

You probably want to make use of the Dragboard, which is a special kind of Clipboard.

Points to note

  1. You may not actually need to move the tiles that you have created. They are ImageViews.

  2. You can place the image associated with the source in the dragboard and change the image in the target view when it is dropped.

  3. You can set the dragView on the dragboard for visual feedback of the drag operation.

  4. You can use a custom content type for the dragboard rather than an image, this is explained in the linked Oracle tutorial.

    private static final DataFormat customFormat =
       new DataFormat("helloworld.custom");
    

    When putting a custom data onto a dragboard, specify the data type. Note that the data must be serializable.

    When reading the data from the dragboard, a proper casting is needed.

Potential Approaches

There are two ways you can handle the tile display.

  1. Tile view and model.

Create a separate tile view and tile model interface.

When the tiles change, don't change the view, only change the model instance backing the view. The view observes its model for changes and automatically updates itself. New nodes are not created and existing nodes are not moved.

That is the approach in the example below. The view is an ImageView and the model is an Image.

  1. Encapsulate view and model together.

In this case, you place the information about the model in the view.

When a node is placed in the grid, you record it's grid position, for example by member values in the node or by setting user data on the node.

When a node is dragged to a new position, you query the source node for its position, then you swap the source and target nodes in the grid using:

GridPane.setConstraints(node, columnIndex, rowIndex)

This is essentially the approach you propose in your question.

I do not provide an implementation for this second potential approach.

Example

The images are images of the grid before and after manually dragging the tiles to reorder them.

before after

The example is not going to be exactly what you want, it is purely provided as an example and you will need to adapt it if you wish to make use of some of the concepts in it.

The ImageViewFactory is just to create test images, you can ignore that portion.

Dragging specific stuff is in the DragUtil class.

Code uses Java 18 and newer Java language features, so to compile it you will need to enable the appropriate language level.

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.Label;
import javafx.scene.effect.*;
import javafx.scene.image.*;
import javafx.scene.input.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class TileDragApp extends Application {
    private static final String TEXT = "abcdefghijklmnop";

    private static final int GRID_SIZE = 4;
    private static final int TILE_SIZE = 60;

    @Override
    public void start(Stage stage) {
        ImageViewFactory imageViewFactory = new ImageViewFactory();
        ImageView[] imageViews = imageViewFactory.makeImageViews(
                TEXT, 
                TILE_SIZE
        );

        DragUtil dragUtil = new DragUtil();

        GridPane grid = new GridPane();
        for (int i = 0; i < TEXT.length(); i++) {
            dragUtil.makeDraggable(imageViews[i], imageViews);
            grid.add(imageViews[i], i % GRID_SIZE, i / GRID_SIZE);
        }
        grid.setGridLinesVisible(true);
        grid.setPadding(new Insets(20));

        stage.setScene(new Scene(grid));
        stage.setResizable(false);
        stage.show();
    }

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

class ImageViewFactory {
    private static final String CSS =
            """
            data:text/css,
            """ +
            // language=CSS
            """
            .root {
                -fx-background-color: azure;
            }
            
            .label {
                -fx-font-size: 40px;
                -fx-text-fill: navy;
            }
            """;

    public ImageView[] makeImageViews(String text, int tileSize) {
        List<Character> chars =
                text.chars()
                        .mapToObj(
                                c -> (char) c
                        ).collect(
                                Collectors.toList()
                        );

        Collections.shuffle(chars);

        return chars.stream()
                .map(
                        c -> makeImageView(c, tileSize)
                ).toArray(
                        ImageView[]::new
                );
    }

    private ImageView makeImageView(char c, int tileSize) {
        Label label = new Label(Character.toString(c));

        StackPane layout = new StackPane(label);
        layout.setPrefSize(tileSize, tileSize);

        Scene scene = new Scene(layout);
        scene.getStylesheets().add(CSS);

        SnapshotParameters snapshotParameters = new SnapshotParameters();
        snapshotParameters.setFill(Color.AZURE);

        Image image = layout.snapshot(snapshotParameters,null);

        return new ImageView(image);
    }
}

class DragUtil {
    public void makeDraggable(ImageView imageView, Node[] acceptedSources) {
        Effect highlight = createHighlightEffect(imageView);

        imageView.setOnDragDetected(e -> {
            Dragboard db = imageView.startDragAndDrop(TransferMode.MOVE);

            ClipboardContent content = new ClipboardContent();
            content.putImage(imageView.getImage());
            db.setContent(content);
            db.setDragView(makeSmaller(imageView.getImage()));

            e.consume();
        });

        imageView.setOnDragOver(e -> {
            if (e.getGestureSource() != imageView
                    && Arrays.stream(acceptedSources)
                             .anyMatch(source -> source == e.getGestureSource())
                    && e.getDragboard().hasImage()
            ) {
                e.acceptTransferModes(TransferMode.MOVE);
                imageView.setEffect(highlight);
                e.consume();
            }
        });

        imageView.setOnDragExited(e -> {
            imageView.setEffect(null);

            e.consume();
        });

        imageView.setOnDragDropped(e -> {
            Dragboard db = e.getDragboard();
            boolean success = false;

            if (db.hasImage() && e.getGestureSource() instanceof ImageView source) {
                source.setImage(imageView.getImage());
                imageView.setImage(db.getImage());

                success = true;
            }

            e.setDropCompleted(success);

            e.consume();
        });
    }

    private Image makeSmaller(Image image) {
        ImageView resizeView = new ImageView(image);
        resizeView.setFitHeight(image.getHeight() * 3 / 4);
        resizeView.setFitWidth(image.getWidth() * 3 / 4);

        SnapshotParameters snapshotParameters = new SnapshotParameters();

        return resizeView.snapshot(snapshotParameters, null);
    }

    private Effect createHighlightEffect(Node n) {
        ColorAdjust monochrome = new ColorAdjust();
        monochrome.setSaturation(-1.0);

        return new Blend(
                BlendMode.MULTIPLY,
                monochrome,
                new ColorInput(
                        0,
                        0,
                        n.getLayoutBounds().getWidth(),
                        n.getLayoutBounds().getHeight(),
                        Color.PALEGREEN
                )
        );
    }
}

Upvotes: 1

Related Questions