I have a grid of 100 javafx labels in a program using a gui I made in scenebuilder. I'm trying to set it up so whenever you click on a label, the label turns the color green.
I have the labels in a two dimensional array list. I'm using a nested for loop to traverse the list, and then then using the setOnMouseClicked function to set the mouse event, instead of creating a function for all 100 labels.
However, I cannot access the for loop control variables in the EventHandler method.
for (int i = 0; i < 10; i )
{
for (int j = 0; j < 10; j )
{
labels.get(i).get(j).setOnMouseClicked(new EventHandler<MouseEvent>(){
@Override
public void handle(MouseEvent event) {
labels.get(i).get(j).setTextFill(Color.GREEN);
}
});
}
}
Local variable i defined in an enclosing scope must be final or effectively final
The error only pops up for 'i' in ecllipse but I assume its affecting both.
What is a different way around this problem? I don't have to use a loop to set the mouse events up, that was just my attempt at solving the problem of setting up 100 labels to be clicked on.
CodePudding user response:
This is really a duplicate of:
-
import javafx.application.Application; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.layout.TilePane; import javafx.scene.layout.VBox; import javafx.stage.Stage; import java.util.ArrayList; import java.util.List; import static javafx.scene.paint.Color.GREEN; public class MultiLabelApplication extends Application { private static final int N_ROWS = 5; private static final int N_COLS = 5; @Override public void start(Stage stage) { Label lastClicked = new Label("No Selection"); lastClicked.setPadding(new Insets(10)); TilePane grid = new TilePane(5, 5); grid.setPrefRows(N_ROWS); grid.setPrefColumns(N_COLS); grid.setMinSize(TilePane.USE_PREF_SIZE, TilePane.USE_PREF_SIZE); grid.setMaxSize(TilePane.USE_PREF_SIZE, TilePane.USE_PREF_SIZE); List<List<Label>> labels = new ArrayList<>(new ArrayList<>()); for (int i = 0; i < N_ROWS; i ) { final int row = i; labels.add(new ArrayList<>()); for (int j = 0; j < N_COLS; j ) { final int col = j; final Label label = new Label(row "," col); label.setPadding(new Insets(10)); label.setOnMouseClicked(event -> { label.setTextFill(GREEN); lastClicked.setText( "Last clicked row: " row ", col: " col ); }); labels.get(row).add(label); grid.getChildren().add(label); } } VBox layout = new VBox(10, lastClicked, grid); layout.setPadding(new Insets(10)); Scene scene = new Scene(layout); scene.getStylesheets().add(CSS); stage.setScene(scene); stage.setResizable(false); stage.show(); } public static void main(String[] args) { launch(); } public static final String CSS = "data:text/css," // language=CSS """ .root { -fx-font-size: 16px; -fx-background-color: cornsilk; } .label { -fx-background-color: palegreen; -fx-text-fill: darkgreen; } TilePane .label { -fx-background-color: lightblue; -fx-text-fill: navy; } """; }
CodePudding user response:
Using @jewelsea's answer, I want to show you another way to do this without using
final
. I don't know if there are any drawbacks or pitfalls.You can use the
MouseEvent
to determine which node was pressed by callingevent.getSource()
. See the altered code below.import javafx.application.Application; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.layout.TilePane; import javafx.scene.layout.VBox; import javafx.stage.Stage; import java.util.ArrayList; import java.util.List; import static javafx.scene.paint.Color.GREEN; public class App extends Application { private static final int N_ROWS = 5; private static final int N_COLS = 5; @Override public void start(Stage stage) { Label lastClicked = new Label("No Selection"); lastClicked.setStyle("-fx-background-color: palegreen; -fx-text-fill: darkgreen; -fx-font-size: 16px;"); lastClicked.setPadding(new Insets(10)); TilePane grid = new TilePane(5, 5); grid.setPrefRows(N_ROWS); grid.setPrefColumns(N_COLS); grid.setMinSize(TilePane.USE_PREF_SIZE, TilePane.USE_PREF_SIZE); grid.setMaxSize(TilePane.USE_PREF_SIZE, TilePane.USE_PREF_SIZE); grid.setStyle("-fx-background-color: cornsilk;"); List<List<Label>> labels = new ArrayList<>(new ArrayList<>()); for (int i = 0; i < N_ROWS; i ) { final int row = i; labels.add(new ArrayList<>()); for (int col = 0; col < N_COLS; col ) { Label label = new Label(row "," col); label.setPadding(new Insets(10)); label.setStyle("-fx-background-color: lightblue; -fx-text-fill: navy; -fx-font-size: 16px;"); label.setOnMouseClicked(event -> { Label tempLabel = ((Label)event.getSource()); tempLabel.setTextFill(GREEN); lastClicked.setText("Last clicked row: " tempLabel.getText()); }); labels.get(row).add(label); grid.getChildren().add(label); } } VBox layout = new VBox(10, lastClicked, grid); layout.setPadding(new Insets(10)); stage.setScene(new Scene(layout)); stage.setResizable(false); stage.show(); } public static void main(String[] args) { launch(); } }
CodePudding user response:
When you instantiate each EventHandler class, the loop variables
i
andj
exist. However after the two loops have finished creating the listeners, the two variable are gone.What you need to do is pass into the listener the coordinates (i and j).
for (int i = 0; i < 10; i ) { for (int j = 0; j < 10; j ) { labels.get(i).get(j).setOnMouseClicked(new EventHandler<MouseEvent>(i, j){ private int i; private int j; public EventHandler<MouseEvent>(int iIn, int jIn ) { i = iIn; j = jIn; } @Override public void handle(MouseEvent event) { labels.get(i).get(j).setTextFill(Color.GREEN); } }); } }
Um, I didn't actually test this, but I have passed in variables to listener classes before, though the classes were nested rather than inline.