Joel
Joel

Reputation: 6193

Get Object from Collection

I'm building a grid out of rectangles.

I want to click one of the rectangles and its color should change.

However, I dont know how to access the rectangles in Main AFTER they've been created.

enter image description here

Main:

import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;

public class Main extends Application{

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

    public void changeColor(Pane p) {
         p.setOnMouseClicked(me -> {
            double posX = me.getX();
            double posY = me.getY();

            int colX = (int)(posX / 30);
            int colY = (int) (posY / 30);

            ObservableList<Node> children = p.getChildren();
            for( Node d : children) {
                    if(d.getLayoutX() == colX && d.getLayoutY() == colY) {
                        // how can i access my rectangle here?
                        // basically, i want to be able to do .setFill()
                    }
            }
        });
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        Grid g = new Grid(30,30, 30);
        Pane window = g.render();
        Scene scene = new Scene(window, 500, 500);
        primaryStage.setScene(scene);
        primaryStage.show();
        this.changeColor(window);

    }
}

Grid:

import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;


public class Grid {

    Integer width, height, squareSize; 
    Color fill = Color.ALICEBLUE, 
        stroke = Color.BLACK;

    public Grid(int x, int y, int squareSize){
        this.width = x; 
        this.height = y;
        this.squareSize = squareSize;
    }

    public Pane render() {
        Pane p = new Pane();
        Rectangle [][] rect = new Rectangle[this.width][this.height];
        for(int i = 0; i < this.width; i++) {
            for(int j = 0; j < this.height; j++) {
                rect[i][j] = new Rectangle();
                rect[i][j].setX(i * width);
                rect[i][j].setY(j * height);
                rect[i][j].setWidth(this.squareSize);
                rect[i][j].setHeight(this.squareSize);
                rect[i][j].setFill(this.fill);
                rect[i][j].setStroke(this.stroke);              
                p.getChildren().add(rect[i][j]);
            }
        }
        return p;
    }
}

Can someone please help me to figure out how I can access my rectangles again in the main file?

Upvotes: 0

Views: 195

Answers (1)

Slaw
Slaw

Reputation: 46146

Keeping with your current design, you simply need to test if the mouse clicked within a child and if that child is an instance of Rectangle; then you can cast and call setFill. However, I recommend changing the name of changeColor as that name does not represent what that method is doing.

public void installChangeColorHandler(Pane pane) {
    pane.setOnMouseClicked(event -> {
        for (Node child : pane.getChildren()) {
            if (child instanceof Rectangle 
                    && child.contains(child.parentToLocal(event.getX(), event.getY()))) {
                ((Rectangle) child).setFill(/* YOUR COLOR */);
                event.consume();
                break;
            }
        }
    });
}

Since the event handler is added to Pane the x and y mouse coordinates are relative to said Pane. But since your Rectangles are direct children of Pane we can call Node.parentToLocal to transform those coordinates into the Rectangle's space1. We then need to test if the bounds of the Rectangle contain those coordinates using Node.contains; if it does, change the fill.

That said, you may want to modify your code so that you're adding an/the EventHandler directly to the Rectangles. That way you can use Event.getSource(). For instance2:

public Pane render(EventHandler<MouseEvent> onClick) {
    // outer loop...
        // inner loop...
            Rectangle r = new Rectangle();
            // configure r...
            r.setOnMouseClicked(onClick);
        // end inner loop...
    // end outer loop...
}

...

// may want to consume event
Pane window = new Grid(30, 30, 30).render(event ->
        ((Rectangle) event.getSource()).setFill(/* YOUR COLOR */));

1. Even if they weren't direct children you can still transform the coordinates into the local space. For example, you can use Node.sceneToLocal and the scene coordinates provided by the MouseEvent (i.e. getSceneX()/getSceneY()).
2. This is still staying close to your design. You may want to rethink things, however, into a proper MVC (or other) architecture. Applying MVC With JavaFx

Upvotes: 1

Related Questions