JavaFX Otwieranie kolejnych okien

0

Cześć,

Siedzę nad tym tematem już dobrych kilka dni i nie potrafię zrozumieć tego mechanizmu. Otwarcie pierwszego okna w aplikacji działa bez zarzutu, ale otwarcie kolejnego zwraca błąd NullPointerException.

Plik main

package Main;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Main extends Application{

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

	@Override
	public void start(Stage primaryStage) throws Exception {

			FXMLLoader loader = new FXMLLoader(this.getClass().getResource("/main.fxml"));
			StackPane stackPane = loader.load();
			Scene scena = new Scene(stackPane);
			primaryStage.setScene(scena);
			primaryStage.show();	
	}}

Main Controller

 package Main;

import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.layout.StackPane;

public class mainController implements Initializable {
	
	@FXML private StackPane stackPane;

	@Override
	public void initialize(URL location, ResourceBundle resources) {
		
		loader("/first.fxml");
		
	}
	
	public void loader(String fxml){
		
		FXMLLoader loader = new FXMLLoader(this.getClass().getResource(fxml));
		StackPane stackpane = null;
		try {
			stackpane = loader.load();
		} catch (IOException e) {
			e.printStackTrace();
		}
		setScreen(stackpane);
	}
	
	public void setScreen(StackPane stackpane) {
		stackPane.getChildren().clear();
		stackPane.getChildren().add(stackpane);
	}}

pierwsze okno

 package firstWindow;

import java.net.URL;
import java.util.ResourceBundle;
import Main.mainController;
import javafx.fxml.Initializable;

public class firstWindowController implements Initializable{
	
	private mainController stackPane;
	
	public void setStackPane(mainController stackPane) {
		this.stackPane = stackPane;
	}

	@Override
	public void initialize(URL location, ResourceBundle resources) {
		
		stackPane.loader("/secund.fxml");
		
	}}

fxmly są totalnie puste, mają tylko StackPane z nadanym fx i podpięty kontroler. Pierwsze okno ładuje się bez problemu, ale drugie już zwraca nulla. Mógłby mi ktoś coś doradzić ?

1

Wstawiłeś sobie referencję w firstWindowController do obiektu klasy mainController, a zainicjalizować obiektem tą referencję ma Ci Duch Święty? :D

Dodatkowo całkowicie mylisz to który obiekt czym powinien zarządzać.
Poprawnie ma być:

  1. FxmlLoader tworzy drzewo kontrolek na podstawie pliku .fxml
  2. FxmlLoader gdy wykryje że w pliku .fxml jest odniesienie do controllera to go utworzy.
  3. FxmlLoader gdy wykryje w kontrolerze pola z adnotacją @FXML postara się wypełnić jes obiektami z widoku.
  4. FxmlLoader gdy wykryje że w kontrolerze jest metoda o nazwie initialize() to ją wywoła.

To FxmlLoader tworzy wszystko. Nie rozumiesz tego i myślisz że controllery są odpowiedzialne za tworzenie.

PS: Natychmiast zmień nazwy tych klas na zgodne z konwencją Javy!!! :P

0

Dzięki za sugestię i cieszę się, że udało mi się Cię rozbawić ;-) A teraz mógłbyś mi jeszcze napisać jak ten obiekt zainicjować bo próbowałem na wszystkie znane mi sposoby i nadal jest null ;/ Wybacz po prostu jeszcze tego nie chwytam.

1

Czekam aż nazwiesz te klasy poprawnie.
Dlaczego pole stackPane ustawiasz ręcznie jakimś seterem i dodatkowo mówisz FxmlLoaderowi żeby to wypełnił za pomocą adnotacji @FXML? Zdecyduj się.
Controllery to nie klasy, które mają wczytywać .fxml. Więc wykorzystywanie FxmlLoadera w nich jest bez sensu.

Popraw kod zgodnie z powyższym. NPE który Ci leci jest nie problemem, a symptomem idiotycznej struktury klas, które nie wiadomo co ma utworzyć.

Nie naprawiaj NPE, napraw strukturę klas!

0

Wzorowałem się na kursie z YT, gdzie właśnie używane są loadery w Controllerach. Zrobiłem nowy projekt,

public class Main extends Application {

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

	@Override
	public void start(Stage primaryStage) throws Exception {

		FXMLLoader loader = new FXMLLoader(this.getClass().getResource("/fxml/Main.fxml"));
		StackPane stackPane = loader.load();
		Scene scene = new Scene(stackPane, 400, 400);
		primaryStage.setScene(scene);
		primaryStage.setTitle("Okno Aplikacji");
		primaryStage.show();
		
	}}

MainController:

public class MainController {
	
	@FXML private StackPane mainStackPane;
	
	@FXML public void initialize() {
		loadMenuScreen();
	}

	public void loadMenuScreen() {
		FXMLLoader loader = new FXMLLoader(this.getClass().getResource("/fxml/FirstWin.fxml"));
		Pane pane = null;
		try {
			pane = loader.load();
		} catch (IOException e) {
			e.printStackTrace();
		}
		setScreen(pane);
	}

	public void setScreen(Pane pane) {
		mainStackPane.getChildren().clear();
		mainStackPane.getChildren().add(pane);
	}}

Pierwsze okno:

public class FirstController {
	
	private MainController mainController;
	
	@FXML public void goSecond(){
		
		FXMLLoader loader = new FXMLLoader(this.getClass().getResource("/fxml/SecondWin.fxml"));
		Pane pane = null;
		try {
			pane = loader.load();
		} catch (IOException e) {
			e.printStackTrace();
		}
		mainController.setScreen(pane);
	
	}}

Błąd java.lang.NullPointerException at klasy.FirstController.goSecond(FirstController.java:22)
22 to oczywiście: mainController.setScreen(pane);

1

Chyba sam nie wiesz co robi twój program. Powiem CI:

  1. Po uruchomieniu Twój program tworzy FxmlLoader.
  2. FxmlLoader tworzy drzewo kontrolek na podstawie Main.fxml
  3. W Main.fxml jest odniesienie do MainController, więc FxmlLoader tworzy instancję tej klasy.
  4. FxmlLoader wykrywa adnotację @FXML na polu MainController::mainStackPane i wstrzykuje utworzoną kontrolkę.
  5. FxmlLoader wykrywa metodę MainController::initialize i ją wywołuje
  6. Metoda MainController::initialize wywołuje MainController::loadMenuScreen
  7. Metoda MainController::loadMenuScreen Tworzy drugi FxmlLoader nazwijmy go FxmlLoader2.
  8. FxmlLoader2 wczytuje FirstWin.fxml
  9. W FirstWin.fxml jest odniesienie do FirstController, więc FxmlLoader2 tworzy instancję tej klasy.
  10. Jakiś button prawdopodobnie wywołuje metodę FirstController::goSecond
  11. Metoda FirstController::goSecond tworzy FxmlLoader nazwijmy go FxmlLoader3
  12. FxmlLoader3 wczytuje SecondWin.fxml
  13. Metoda FirstController::goSecond próbuje wstawić drzewo kontrolek z pliku SecondWin.fxml do StackPane z MainController, ale nic nie ustawiło referencji do tego obiektu i jest ona null.

Ponowię moje pytanie:

nie100sowny napisał(a):

Wstawiłeś sobie referencję w firstWindowController do obiektu klasy mainController, a zainicjalizować obiektem tą referencję ma Ci Duch Święty? :D

Pole mainController w klasie FirstController nie jest przez nic inicjalizowane wartością. Ma się wydarzyć cud czy przeorganizujesz wreszcie swój kod?

0

Ja naprawdę staram się zainicjować to, ale najprawdopodobniej robię to źle ponieważ ciągle otrzymuje null np mainController = new MainController(); przed mainController.setScreen(pane);
Mógłbym dostać wskazówkę jak zainicjować to pole ?

1

Przy obecnej strukturze kodu coś tak brzydkiego może zadziałać:

  public void loadMenuScreen() {
        FXMLLoader loader = new FXMLLoader(this.getClass().getResource("/fxml/FirstWin.fxml"));
        FirstController ctrl = loader.getController();
        ctrl.setMainController(this);

        // dalsze smrodki

Polecam podpiąć jakiś framework do Dependency Injection. Robi się to podając do FxmlLoadera tzw. ControllerFactory. Czyli klasę która pozwala nam dostarczać instancje kontrolerów. I można w ten sposób podpiąć Guice lub Spring DI.

PS: Pamiętaj o plusikach

0

To już niestety też sprawdzałem. Sugeruję się tym oto kursem: www.youtube.com/watch?v=G2MVPrmrDtw. Reszta Twojej sugestii niestety jest dla mnie jeszcze nie zrozumiała, ale to pewnie już wywnioskowałeś po poziomie mojej wiedzy.
Także walczę dalej, choć sugerując się Twoją opinią warto chyba sprawdzić inną metodę zarządzania oknami.

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