JavaFX GridPane i NullPointerException

0

Witam!
Piszę gierkę Memory w JavaFX. Mam klasę GeneratorKafli, w której generowane są obiekty typu Kafel i wrzucane w losowej kolejności do odpowiedniej wielkości tablicy dwuwymiarowej a ta przekazywana jest do Kontrolera. W kontrolerze metoda odpowiadająca za wyświetlanie odpowiedniej wielkości planszy (np 4x4) pobiera z komórki [i][j] przekazanej powyżej tablicy jedno z pól obiektu - ścieżkę obrazka i wrzuca dany obrazek w ImageView a ten dalej w gridPane. I na tym ostatnim a dokładnie w linijce gridPane.add(iv[i][j],i,j); dostaję NullPointerException. Z debuggera wiem, że ten gridPane jest tutaj null'em, ale nie rozumiem dlaczego. Proszę o pomoc

Controller.java



public class SampleController {
	
   private Stage stage;

	public void setStage(Stage stage){
		this.stage=stage;
		}
	@FXML
	private AnchorPane ap;
	@FXML
	public TilePane tilePane;
	@FXML
	public GridPane gridPane;

 GeneratorKafli generator = new GeneratorKafli();
	 Stage nowa_gra_single = new Stage();
Stage gra_single = new Stage();

public void Gra4x4() throws IOException{
		
		ImageView iv00 = new ImageView();
		 ImageView iv01 = new ImageView();
		 ImageView iv02 = new ImageView();
		 ImageView iv03 = new ImageView();
		 ImageView iv04 = new ImageView();
		 ImageView iv05 = new ImageView();
		 ImageView iv06 = new ImageView();
		 ImageView iv07 = new ImageView();
		 ImageView iv08 = new ImageView();
		 ImageView iv09 = new ImageView();
		 ImageView iv10 = new ImageView();
		 ImageView iv11 = new ImageView();
		 ImageView iv12 = new ImageView();
		 ImageView iv13 = new ImageView();
		 ImageView iv14 = new ImageView();
		 ImageView iv15 = new ImageView();
		 ImageView [][] iv = {
				 { iv00, iv01, iv02, iv03},
				 { iv04, iv05, iv06, iv07},
				 { iv08, iv09, iv10, iv11},
				 { iv12, iv13, iv14, iv15}
		 };
		 generator.GenerujKafle4x4();
		 Kafel kafle[][] = new Kafel[4][4];
		 kafle = generator.PokazKafle();
		//System.out.print("Testowo: " + kafle[0][0].getNazwa() + " " +kafle[1][0].getNazwa() + " " + kafle[3][0].getNazwa());
		 
	      System.out.println();
	      
		 for(int i=0; i<4; i++) {
			
			 for(int j=0; j<4; j++) {
				 
				 Image image = new Image(kafle[i][j].getImage());
			      System.out.print(i+","+j+" ...." + kafle[i][j].getImage() +"\n" );
              
				 iv[i][j].setImage(image);
				 gridPane.add(iv[i][j],i,j);
             
			 }
		      System.out.println();  	
		 }
		
	}
	 @FXML
	private void PlaySingle(){
		 
		   Parent root;
	        try {
	            root = FXMLLoader.load(getClass().getResource("Sample.fxml"));
	            gra_single.setTitle("MEMORY karciane");
	            gra_single.setScene(new Scene(root, 1500, 800));
	            
	            nowa_gra_single.close();
	            gra_single.show();
	            Gra4x4();    
	        }
   catch (IOException e) {
	            e.printStackTrace();
	        }	
}
}
0

Może w FXML'u w nazwie gridPane'a masz literówkę.

0
MrMadMatt napisał(a):

Może w FXML'u w nazwie gridPane'a masz literówkę.

Nie, nie mam literówki w FXML'u.


   <TilePane fx:id="tilePane" layoutX="1.0" layoutY="1.0" prefHeight="800.0" prefWidth="1400.0">
         <children>
            <GridPane fx:id="gridPane" gridLinesVisible="true" prefHeight="800.0" prefWidth="1400.0">
              <!--<columnConstraints>
                <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="20.0" />
                <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="20.0" />
                  <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="20.0" />
                  <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
              </columnConstraints>
              <rowConstraints>
                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                  <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
              </rowConstraints>-->
            </GridPane>
         </children>
      </TilePane>
0

Z debuggera wiem, że ten gridPane jest tutaj null'em, ale nie rozumiem dlaczego.

Który gridPane jest nullem? Bo operujesz na instancji kontrolera odpiętej od widoku. Załadowanie widoku nie powoduje automagicznego zbindowania elementów widoku z kontrolerem, aby adnotacje @FXML zadziałały musisz instancję kontrolera wyciągnąć z loadera i dopiero na nim operować.

0
dymul napisał(a):

Z debuggera wiem, że ten gridPane jest tutaj null'em, ale nie rozumiem dlaczego.

Który gridPane jest nullem? Bo operujesz na instancji kontrolera odpiętej od widoku. Załadowanie widoku nie powoduje automagicznego zbindowania elementów widoku z kontrolerem, aby adnotacje @FXML zadziałały musisz instancję kontrolera wyciągnąć z loadera i dopiero na nim operować.

Próbowałam zrobić coś takiego, ale dalej nie działa. Bardzo proszę o wyjaśnienie jak zrobić to poprawnie. Piszę dopiero drugą aplikację w JavaFX(w celach naukowych), więc jeszcze nie wszystko ogarniam.

 @FXML
	private void PlaySingle(){
		 
		   Parent root;
	        try {
	        	FXMLLoader loader = new FXMLLoader(getClass().getResource("Sample.fxml"));
	        	root = loader.load();
	        	SampleController controller = loader.getController();
	        	 controller.setStage(gra_single);
	
	            gra_single.setTitle("MEMORY karciane");
	            gra_single.setScene(new Scene(root, 1500, 800));
	            
	            nowa_gra_single.close();
	            gra_single.show();
	            Gra4x4();  
	        }
	        catch (IOException e) {
	            e.printStackTrace();
	        }	 
	 }
0

Jeżeli w celach naukowych to słabo wróżę tej nauce :D, jeżeli w celach dydaktycznych to spoko.
Dobrze kombinujesz tylko na końcu zamiast wywoływać Gra4x4() "na sobie" wywołaj ją na wyciągniętym kontrolerze:

 @FXML
    private void PlaySingle(){
 
           Parent root;
            try {
                FXMLLoader loader = new FXMLLoader(getClass().getResource("Sample.fxml"));
                root = loader.load();
                SampleController controller = loader.getController();
                 controller.setStage(gra_single);
 
                gra_single.setTitle("MEMORY karciane");
                gra_single.setScene(new Scene(root, 1500, 800));
 
                nowa_gra_single.close();
                gra_single.show();
                controller.Gra4x4();  
            }
            catch (IOException e) {
                e.printStackTrace();
            }    
     }

Przemyśl design tego wszystkiego bo coś czuję, że masz lekkie piekło w tym projekcie, zamykasz/pokazujesz jakieś Stage, nie wiadomo o co tam chodzi.
Patent na porządek jest prosty, jak startujesz pierwszę scenę w programie to masz mniej więcej taki pattern:

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

	@Override
	public void start(Stage primaryStage) throws Exception {
		this.primaryStage.setTitle("Title");
		initRootLayout(primaryStage);
		
	}
	
	private void initRootLayout(Stage primaryStage) {
		try {
			FXMLLoader loader = new FXMLLoader();
			loader.setResources(ResourceBundle.getBundle("i18nstrings"));
			loader.setLocation(App.class.getResource("gui/view/LoginScreen.fxml"));
			primaryStage.setScene(new Scene(loader.load()));
			primaryStage.show();
			
		} catch (IOException e) {
			App.logger.error("Cannot load LoginScreen.fxml", e);
		} catch (Exception e) {
			App.logger.error("Exception while loading content pane", e);
		}

	}

W powyższym przykładzie loader pod spodem utworzy sobie instancję kontrolera podpiętego do tego widoku, w tym przypadku LoginScreenController i wywoła na nim metodę z adnotacją @FXML i sygnaturą void initialize() o ile taka będzie dostępna. W tym przypadku nie miałem konieczności przekazywania żadnych dodatkowych wartości do kontrolera więc jest łatwo. Jeżeli jest konieczne pokazanie nowego widoku w reakcji np na naciśnięcie przycisku to robię to tak:


	private void loadScreen(String screen) {
		Stage primaryStage = (Stage)login.getScene().getWindow();
		try {
			FXMLLoader loader = new FXMLLoader();
			loader.setResources(ResourceBundle.getBundle("i18nstrings"));
			loader.setLocation(App.class.getResource(screen));
			primaryStage.setScene(new Scene(loader.load()));
			primaryStage.getScene().getStylesheets().add("gui/view/style.css");
			primaryStage.show();
			
		} catch (IOException e) {
			App.logger.error("Cannot load screen", e);
		} catch (Exception e) {
			App.logger.error("Exception while loading content pane", e);
		}

	}

Natomiast, jeżeli potrzebuję przekazać jakieś dodatkowe parametry to trzeba inaczej. Ponieważ nie tworzysz sam instancji kontrolera a robi to za Ciebie loader to dla każdej zależnności kontrolera trzeba zrobić setter i po wyciągnięci instancji z loadera po prostu to ustawić, np jak robisz jakiś customowy dialog:


	public static void showErrorDialog(Stage parentStage, String info) {
		Platform.runLater(() -> {
			try {
				FXMLLoader loader = new FXMLLoader();
				loader.setLocation(App.class
						.getResource("gui/view/ErrorDialog.fxml"));
				AnchorPane pane = (AnchorPane) loader.load();

				Stage dialogStage = new Stage();
				dialogStage.initModality(Modality.WINDOW_MODAL);
				dialogStage.initOwner(parentStage);
				Scene scene = new Scene(pane);
				dialogStage.setScene(scene);
				ErrorDialogController controller = loader
						.getController();
				controller.setDialogStage(dialogStage);
				controller.setInfo(info);
				dialogStage.setResizable(false);
				dialogStage.showAndWait();
			} catch (IOException e) {
				App.logger.error("Exception when loading ErrorDialog.fxml", e);
			}
		});

	}

1 użytkowników online, w tym zalogowanych: 0, gości: 1