Verschieden Scenen ansprechen mit dem Scene Builder und JavaFX (Eclipse)

DaveDarell

Mitglied
Hallo,

ich versuche mich aktuell daran, eine mehrstufige GUI mit dem Scene Builder zu bauen und habe aktuell ein Problem. Ich habe drei Scenen (Scene1.fxml, Scene2.fxml und Scene3.fxml) und drei Controller (Controller1, Controller2und Controller3). Folgendes soll möglich sein:

  • Scene 1 soll der Start sein
  • Von Scene 1 soll man auf Scene 2 und Scene 3 kommen
  • Von Scene 2 auf Scene 1 oder Scene 3
  • Von Scene 3 auf Scene 1

Meine Main.java sieht wie folgt aus:

Java:
public class Main extends Application {

    @Override
    public void start(Stage stage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("Scene1.fxml"));
        
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }

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

Die Controllerklassen sind eigentlich (bisher) alle gleich aufgebaut, als Beispiel hier mal Controller1:

Java:
package application;

import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;

import javafx.event.ActionEvent;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Controller1 implements Initializable {



    public void switchToScene2(ActionEvent event) throws IOException {

        
        
        Parent tableView = FXMLLoader.load(getClass().getClassLoader().getResource("Scene2.fxml"));
        Scene tableViewscene = new Scene(tableView);
        Stage window = (Stage)((Node)event.getSource()).getScene().getWindow();
        window.setScene(tableViewscene);
        window.show();
    }

    public void switchToScene3(ActionEvent event) throws IOException {

        Parent tableView = FXMLLoader.load(getClass().getClassLoader().getResource("Scene3.fxml"));
        Scene tableViewscene = new Scene(tableView);
        Stage window = (Stage)((Node)event.getSource()).getScene().getWindow();
        window.setScene(tableViewscene);
        window.show();
        
        
    }

    @Override
    public void initialize(URL arg0, ResourceBundle arg1) {
        // TODO Auto-generated method stub
        
    }
}



Ich weiß nicht wo genau da der Fehler liegt, ich habe schon verschieden Sachen ausprobiert in den Controllerklassen, alles bisher ohne Erfolg, es kommt eigentlich immer eine Fehlermeldung (Hänge mal eine Textdatei damit im Anhang an.)

Ich würde mich freuen wenn mir hier von euch jemand einen Tipp geben kann, wo genau mein Fehler liegt 🙂

Vorab schon mal vielen Dank!
 

Anhänge

Die fxml Datei wird nicht gefunden. Wo liegen die fxml Dateien? In welchem Namespace sind die Klassen? Gross-/Kleinschreibung bei den Namen beachtet?

Das getResource bei der Klasse schaut im Verzeichnis nach, das dem Namespace entspricht. Daher müsste Scene3.fxml im Verzeichnis application sein. Bei der Klasse Main ist kein package mit angegeben...
 
Die fxml Datei wird nicht gefunden. Wo liegen die fxml Dateien? In welchem Namespace sind die Klassen? Gross-/Kleinschreibung bei den Namen beachtet?

Das getResource bei der Klasse schaut im Verzeichnis nach, das dem Namespace entspricht. Daher müsste Scene3.fxml im Verzeichnis application sein. Bei der Klasse Main ist kein package mit angegeben...

Also es liegt alles unter "application" ab und die Groß und Kleinschreibung sitzt eigentlich auch. Soll ich da mal ein extra Package machen für die FXML Files? Bzw. das ggf. aus "application" raus schieben und entsprechend anpassen?

Bei der Main wird "package application;" angegeben, habe ich leider nur vergessen in den Code einzufügen, das war mein Fehler...

1633638266733.png
 
Also es liegt alles unter "application" ab und die Groß und Kleinschreibung sitzt eigentlich auch. Soll ich da mal ein extra Package machen für die FXML Files? Bzw. das ggf. aus "application" raus schieben und entsprechend anpassen?

Bei der Main wird "package application;" angegeben, habe ich leider nur vergessen in den Code einzufügen, das war mein Fehler...

Anhang anzeigen 16379
Noch ein Nachtrag zum Namespace:

Java:
xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1"

Das ist bei allen drei fxml-Dateien identisch.
 
Ach, wollte gerade schreiben, dass es doch alles richtig aussieht aber vor dem Posten habe ich noch einmal genau geschaut. Ist aber auch leicht zu übersehen:
FXMLLoader.load(getClass().getResource("Scene1.fxml")); (aus Main)
FXMLLoader.load(getClass().getClassLoader().getResource("Scene3.fxml")); (aus dem Controller)

Schmeiss das getClassLoader() bitte raus.

Der Unterschied ist: Bei dem getResource auf der Klasse stehst Du im "Ordner", der dem namespace der Klasse entspricht.
Bei dem getResource des ClassLoaders stehst Du im Root.

Das erste wäre also ein "/application/Scene1.fxml" und das zweite ein "/Scene2.fxml". (Wenn man es absolut und nicht relativ angibt!)
 
Ach, wollte gerade schreiben, dass es doch alles richtig aussieht aber vor dem Posten habe ich noch einmal genau geschaut. Ist aber auch leicht zu übersehen:
FXMLLoader.load(getClass().getResource("Scene1.fxml")); (aus Main)
FXMLLoader.load(getClass().getClassLoader().getResource("Scene3.fxml")); (aus dem Controller)

Schmeiss das getClassLoader() bitte raus.

Der Unterschied ist: Bei dem getResource auf der Klasse stehst Du im "Ordner", der dem namespace der Klasse entspricht.
Bei dem getResource des ClassLoaders stehst Du im Root.

Das erste wäre also ein "/application/Scene1.fxml" und das zweite ein "/Scene2.fxml". (Wenn man es absolut und nicht relativ angibt!)
Das ist es, dass ist die Lösung, tausend Dank an dich!

Nochmal zu meinem Verständnis, wenn ich dann quasi im Root stehe, muss ich noch den Pfad /application/ angeben? Falls ich das so richtig verstanden habe, würde dann beim Controller ein "/application/Scene2.fxml" klappen?
 
Ja, du kannst es generell (also egal ob auf der class oder dem clasloader aufgerufen) als absoluten Pfad (führender /) angeben.

Durchaus üblich ist aber der Weg, wie du es in der main gemacht hast. Hat den Vorteil (bei eclipse Projekten), dass die fxml schön bei ihren Klassen liegen.

Wenn man später Maven oder Gradle nutzt, dann weicht man eher davon ab, denn die Ressourcen will man nicht in so langen Pfaden haben wie de/kneitzel/MeineTolleApp/ui/hauptfenster/ sondern da nehme ich dann nur /ui/hauptfenster/. Das sind aber Dinge, die jeder so handhaben kann, wie er möchte.
 
Ja, du kannst es generell (also egal ob auf der class oder dem clasloader aufgerufen) als absoluten Pfad (führender /) angeben.

Durchaus üblich ist aber der Weg, wie du es in der main gemacht hast. Hat den Vorteil (bei eclipse Projekten), dass die fxml schön bei ihren Klassen liegen.

Wenn man später Maven oder Gradle nutzt, dann weicht man eher davon ab, denn die Ressourcen will man nicht in so langen Pfaden haben wie de/kneitzel/MeineTolleApp/ui/hauptfenster/ sondern da nehme ich dann nur /ui/hauptfenster/. Das sind aber Dinge, die jeder so handhaben kann, wie er möchte.
Ah perfekt, dann habe ich wieder was neues gelernt. Nochmal vielen Dank und schon mal ein schönes Wochenende! 🙂
 
Guten Abend,

ich hätte nochmal eine Frage - wie kann ich in dem obenstehenden Szenario verschiedene ResourceBundles einbinden, damit bei bestimmten Einstellungen die Sprache geändert wird und die Labels / Buttons etc. innerhalb der Scenes aus dem ResourceBundle geladen werden?

Folgenden Code habe ich in der Main

Java:
public void start(Stage stage) throws Exception {
String locale = new String("de_DE");
FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("Scene1.fxml"));
        loader.setResources(ResourceBundle.getBundle("bundles.language_"+ locale));
        //
        
        Parent root = loader.load();

        Scene scene = new Scene(root);

        scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
        stage.setScene(scene);
        stage.show();
    
}

und das steht bspw. im Scene2 Controller:

Java:
    public void switchBack(ActionEvent event) throws IOException {
    
       String locale = new String("de_DE");
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("Scene1.fxml"));
        loader.setResources(ResourceBundle.getBundle("bundles.language_"+ locale));
        
        //Parent tableView = FXMLLoader.load(getClass().getResource("Login.fxml"));
        Parent root = loader.load();
        Scene tableViewscene = new Scene(root);
        Stage window = (Stage) ((Node) event.getSource()).getScene().getWindow();
        tableViewscene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
        window.setScene(tableViewscene);
        window.show();
    }

Irgendwie bekomme ich das im Scene2Controller nicht zum laufen, meine Vermutung ist, dass es mit dem window zusammenhängt.
Hat mir jemand eine Idee bzw. ein Tipp was ich hier falsch mache?
 
Du erstellst das ResourceBundle auch falsch.

Ein ResourceBundle lädt mehrere properties Dateien. Du hast also eine default Datei und dann noch spezifische für andere Sprachen.
Und dann lädst Du das per ResourceBundle.getBundle("bundles.laguage", locale);
Und locale ist dann auch kein String sondern vom Typ Locale.

Generell ist ein new String("...") extrem dubios. Wozu erstellst Du eine neue String Instanz und willst nicht die String Instanz des Literals verwenden?

Bezüglich ResourceBundle findet sich auch viel im Netz, z.B.
 
Also ich habe zwei Files, 1x language_de_properties und language_en_properties.
Wenn ich dich da jetzt richtig verstanden habe, dann ist hier eins davon mein default (in dem Fall de) und das en-Bundle lade ich dann über
"ResourceBundle.getBundle("bundles.laguage", locale);" rein?

Ja das mit dem String wurde uns so beigebracht, aber generell kann ich das ja auch String s = "de_DE" nennen, das meinst du doch, oder?

Muss dazu sagen, dass ich ein absoluter Neuling bin bei JavaFX und den ResourceBundles erst Recht
 
Ich hoffe du meintest jeweils mit .properties (also kein _).
Leg noch ein language.properties an. Das ist das default File. Wenn Du z.B. kein Locale angibst, dann würde er immer mit dem Locale mit dem Java gestartet wurde, es versuchen.

Sprich: Bei einer Einstellung de_de würde er erst nach einem language_de_de.properties schauen. Wenn die Datei nicht gefunden wird, dann wird nach language_de.properties geschaut. Wird diese Datei auch nicht gefunden, dann wird language.properties genommen.

Wenn dann ein key aufgelöst wird, dann wird die Suchreihenfolge auch beibehalten. Wenn also der key in language_de_de.properties nicht vorhanden ist, dann wird in den anderen Dateien auch geschaut.

(Ich muss gestehen, dass ich dies bei Java selbst so im Detail auch noch nicht ausprobiert habe. Ich habe da vor allem Erfahrungen im .Net Bereich und habe da einiges übertragen. Es kann also durchaus gewisse Abweichungen geben. Das was ich bisher aber alles gelesen habe und so wie es sich bei mir verhalten hat, müsste es so korrekt sein! Aber ich will es halt sagen - würde normalerweise selbst prüfen und dann auch Links zur Quellen schicken, aber ich bin da im Augenblick etwas zu lustlos zu, sorry)


Was den String angeht: ja genau. Das new String("...") kenne ich eigentlich nur als Demo, um zu zeigen, dass man Strings nicht mit == vergleicht. Es gibt für Strings eine interne Liste. Auf die Liste kommen z.B. alle Literale, die irgendwo auftauchen. Dadurch gibt es Literale nur einmal.
Das kannst Du auch etwas nachspielen - String.intern() wäre hier die Methode, die aufzurufen ist.

Bei der Codezeile, die Du da hast, hast Du den String zwei Mal im Speicher. Einmal wurde das Literal natürlich mit der Klasse / dem Code geladen. Und dann hast Du eine neue String Instanz erzeugt, die den gleichen Inhalt hat. ==> Doppelter Speicherverbrauch.
Jetzt kommt noch ein ganz wichtiger Punkt hinzu: Strings sind immutable - unveränderbar. Du kannst einen String nicht verändern. Daher macht es keinen Sinn, diesen doppelt zu haben.


Wenn man nun zu Deinem Code zurück geht, dann kannst Du die Zeile aber fast so lassen. Du musst dann String nur durch Locale ersetzen. Und dann beim Resource Bundle den _ wegnehmen und statt dem + ein , nutzen um die Locale als zweiten Parameter mit zu geben.
 
Ja also das ist alles mit .properties abgespeichert, dass habe ich vergessen.
Ok und kann ich theoretisch auch das language_de als default setzten? Sprich das dieses File automatisch geladen wird und nur wenn ein Button geklickt wird, dass language_en.properties geladen wird und die Bezeichnungen von Englisch auf Deutsch geswitcht werden?
Nenne ich das dann einfach in language.properties, damit es als default erkannt wird?

Der Code würde dann wie folgt aussehen

Java:
    public void switchBack(ActionEvent event) throws IOException {
        Locale locale = new Locale("de");
        
        
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("Scene1.fxml"));
        loader.setResources(ResourceBundle.getBundle("bundles.language_", locale));

        // Parent tableView = FXMLLoader.load(getClass().getResource("Login.fxml"));
        Parent root = loader.load();
        Scene tableViewscene = new Scene(root);
        Stage window = (Stage) ((Node) event.getSource()).getScene().getWindow();
        tableViewscene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
        window.setScene(tableViewscene);
        window.show();
    }

Und danke für die Erklärung mit dem String, dass war mir vorher noch nicht so klar, werde ich auf jeden Fall berücksichtigen!
 
Bei dem getBundle Aufruf muss das _ Zeichen am Ende noch weg. Und wenn de der Default sein soll, dann kannst du das File einfach entsprechend umbenennen.

Und es heisst dann einfach bundles.language.properties (die Datei)
 
Bei dem getBundle Aufruf muss das _ Zeichen am Ende noch weg. Und wenn de der Default sein soll, dann kannst du das File einfach entsprechend umbenennen.

Und es heisst dann einfach bundles.language.properties (die Datei)
das "_" am Ende kommt daher, dass die Dateien ja in dem Ordner Bundle liegen und mit "language_de.properties" abespeichert sind, deshalb brauche ich das (zumindest nach meinem Verständnis)

Nochmal zu meinem Verständnis, dann gebe ich in jedem Controller diese Locale an und lade die dann mit
"loader.setResources(ResourceBundle.getBundle("bundles.language", locale));"

Weil falls ja, dann habe ich es glaub jetzt begriffen und auf den ersten Blick scheint es auch zu funktionieren.
Schonmal vielen Dank und noch einen schönen Abend!
 
das "_" am Ende kommt daher, dass die Dateien ja in dem Ordner Bundle liegen und mit "language_de.properties" abespeichert sind, deshalb brauche ich das (zumindest nach meinem Verständnis)
Daher schreibe ich das doch immer wieder: Das ist falsch!

Du hast ein ResourceBundle - das besteht aus mehreren .properties Dateien.

Die Basis Datei hat dann irgend einen Namen wie z.B. "Name" und heißt dann Name.properties.
Dann gibt es für diverse Locale spezielle Dateien - die bekommen dann mit "_" abgetrennt, die Locale in Kleinbuchstaben angehängt. Also z.B. Name_de.properties.

Der Name des Bundles ist aber dann "Name" und wird dann auch genau so angegeben.

Konkretes Beispiel, damit Du einfach siehst, was ich meine:
Kleine Testklasse:
Java:
package example;

import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

public class InternationalizingExample {
    public static void main(String[] args) {
        printMessages(ResourceBundle.getBundle("example/messages", Locale.GERMAN));
        printMessages(ResourceBundle.getBundle("example/messages", Locale.ENGLISH));
        printMessages(ResourceBundle.getBundle("example/messages", new Locale("en_US")));
        printMessages(ResourceBundle.getBundle("example/messages", Locale.CHINESE));
    }

    public static void printMessages(ResourceBundle bundle) {
        System.out.println(bundle.getString("farbe"));
        System.out.println(bundle.getString("nur_en"));
        System.out.println(bundle.getString("nur_basis"));
        try {
            System.out.println(bundle.getString("gibt es nicht"));
        } catch (MissingResourceException ex) {
            System.out.println("Eintrag nicht gefunden!");
        }
    }
}

Nun habe ich ein ResourceBundle - imNamespace example mit Namen messages. Also finden sich die folgenden Dateien bei der Java Klasse:
[CODE lang="java" title="messages.properties"]farbe = Farbe
nur_en = nur in en
nur_basis = nur in Basis[/CODE]

[CODE lang="java" title="messages_en.properties"]farbe = colour
nur_en = only in en[/CODE]

[CODE lang="java" title="messages_en_us.properties"]farbe = color[/CODE]

Was siehst Du hier nun? Ich rufe für jede Lokale immer alle Keys auf sowie einen nicht existierenden.

Auswahl der verwendeten Ressource Bundle
GERMAN und CHINESE haben keine spezifische Datei -> Basis wird genommen.
ENGLISH hat eine spezifische Datei: _en wird genommen, dann die Basis
en_us hat eine spezifische Datei: _en_us wird genommen, dann _en und dann basis.

Was bedeutet dies konkret für ein paar Fälle?

Nehmen wir Farbe: Bei en_us wird dies zu color. Bei en wird dies das die BE Variante: colour

Wenn wir "nur_en" anschauen bei en_us: Das findet sich nicht in en_us, also wird in en geschaut und von dort genommen.

=> Die Suchkette wird, wie von mir schon beschrieben, abgearbeitet.
 
Guten Morgen und vielen Dank! Anhand des Beispiels hab ich es endlich begriffen, dass ich den "_" zum abspeichern brauche, aber nicht zum eigentlichen Aufruf der Datei.

Nochmals Danke, du hast mir echt weiter geholfen hier! 🙂
 

Neue Themen


Zurück
Oben