简体   繁体   中英

How can I implement another class into my JavaFX GUI?

I've been working on a small adventure game as a final project for my AP Java class for a while now, and I've been trying to implement the classes that I made for the JavaFX GUI that I had built with Scene Builder .

But for some reason, the GUI refuses to recognize my method startAdventure() , in the Adventure class, which basically starts the whole thing. The GUI is launched, but it does not display properly what it is told to display in startAdventure() .

startAdventure() appears not to be called at all until after the GUI is closed, resulting in a null pointer exception as startAdventure() is supposed to manipulate Text Area's and such in the GUI.

I apologize in advance if I am not providing enough information, not understanding suggestions, etc. I am a little challenged when it comes to coding, and I am completely new to posting stuff on this site.

Here is how the beginning of startAdventure() looks in my Adventure class (text and inputText are properly initialized variables from my AdventureGUI.fxml file, which was made using Scene Builder ):

public void startAdventure()
{
    //welcome user
    text.setText("Welcome to the land of euphoria!" + "\n");
    //gets users name
    text.appendText("What's your name?" + "\n");
    p.playerName = inputText.getText();
    text.appendText("Welcome, " + p.playerName + "\n");

    //sets start location
    p.setNewLoc("START");

And here is how my main class looks:

public class RunAdventure extends Application
{

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

    Parent root = FXMLLoader.load(getClass().getResource("AdventureGUI.fxml"));
    primaryStage.setScene(new Scene(root, 600, 600));
    primaryStage.setTitle("Adventure");
    primaryStage.show();
}

public static void main(String[] args)
{
    Adventure game = new Adventure();
    launch(args);
    game.startAdventure();
}
} //end class

If it were working properly, the GUI would launch and inside the Text Area, it would display text beginning with "Welcome to the land of euphoria!" .

However, in its present state, the GUI is launched and has no text at all in the Text Area.

Once again, new to everything here, so let me know right away if you think there is something else you need me to show to assist the suggestion process.

Edit: When I attempted to change the order of where game.startAdventure() was, by putting it right before primaryStage.show() in the start method, a long list of exceptions occurred. Here is the change I made:

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

        Parent root = FXMLLoader.load(getClass().getResource("AdventureGUI.fxml"));
        primaryStage.setScene(new Scene(root, 600, 600));
        primaryStage.setTitle("Adventure");
        Adventure game = new Adventure();
        game.startAdventure();
        primaryStage.show();
    }

And here are the exceptions that occurred:

Exception in Application start method
Exception in thread "main" java.lang.RuntimeException: Exception in Application start method
    at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:894)
    at com.sun.javafx.application.LauncherImpl.access$000(LauncherImpl.java:56)
    at com.sun.javafx.application.LauncherImpl$1.run(LauncherImpl.java:158)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException
    at Classwork.FinalExamProject.Adventure.startAdventure(Adventure.java:102)
    at Classwork.FinalExamProject.RunAdventure.start(RunAdventure.java:24)
    at com.sun.javafx.application.LauncherImpl$8.run(LauncherImpl.java:837)
    at com.sun.javafx.application.PlatformImpl$7.run(PlatformImpl.java:335)
    at com.sun.javafx.application.PlatformImpl$6$1.run(PlatformImpl.java:301)
    at com.sun.javafx.application.PlatformImpl$6$1.run(PlatformImpl.java:298)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl$6.run(PlatformImpl.java:298)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)

I should also mention that the Adventure class does also serve as a controller

EDIT (5/20): Alright, I have gotten most everything working in my program, but I am now running into a new issue. In my adventure, I have a function that allows the player to battle various monsters if they are in a given location. My problem is that once I get to this location in my program, the program stops responding. After doing some debug, I figured out that it was a while loop I had that does everything in the doBattle() method while player.isAlive and enemy.isAlive. The only problem is, once I remove the while loop, the program just jumps right to after either the monster is dead or the player is dead, whereas it's supposed to give you the choice on whether to attack the monster or not repeatedly until either entities are dead. I know I'm cramming way too much stuff into this question, but I don't know where else to put it, and so far I have gotten overwhelming support from everyone here. I cannot thank you people enough, but I have no idea how to solve this on my own. Here is the code of the doBattle() method:

public void doBattle(Monster enemy)
    {
        //boolean fled = false;
        //Greets player into battle by telling what monster they are fighting
        text.appendText("\n" + "A wild " + enemy.getName() + " has appeared!" + "\n" );
        mobImagePane.setImage(enemy.getImage());

        //System.out.print("THIS RAN"); //debug


        while ( p.getHealth() > 0 && enemy.getHealth() > 0 ) //while enemy and player are alive
        {
            //Prompts user to attack or run
            text.appendText("Attack " + enemy.getName() + "? (Y/N) " + "\n");
            inputText.setOnAction(event ->
            {
                String fightChoice = inputText.getText();
                fightChoice = fightChoice.toUpperCase();
                //String fightChoice = this.choice;


                if (fightChoice.equals("Y"))//if they want to fight
                {
                    //Player turn
                    enemy.setHealth(enemy.getHealth() - p.getDamage()); //Sets the monsters health as their current health minus the players damage
                    text.appendText("You attack " + enemy.getName() + " for " + p.getDamage() + " damage!" + "\n" + "Enemy health is " + enemy.getHealth() + "\n");

                    //Monster turn
                    p.setHealth(p.getHealth() - enemy.getDamage()); //Sets the players health as their current health minus the monsters damage
                    text.appendText("The " + enemy.getName() + " hit you for " + enemy.getDamage() + " damage!" + "\n" + "Your health is " + p.getHealth() + "\n"); //prints how much damage the monster does to the player
                    if (p.health < 20.0) {
                        text.appendText("Your health is low, you should return home and restore health!" + "\n");
                    }

                    //checks if the player or monster is dead
                    this.checkLife();
                    enemy.checkLife();

                } else {
                    if (fightChoice.equals("N")) // if they don't want to fight
                    {
                        mobImagePane.setImage(null);
                        text.appendText("You fled from the fight!" + "\n");
                        this.setNewLoc("TOWN"); // brings you back to town
                        fled = true;
                        //JOptionPane.showMessageDialog(null, "You are now in " + this.currentLoc.getName() + "\n" + this.currentLoc.getDescription());
                        //String move2 = JOptionPane.showInputDialog(null,"Which way do you want to go? (N, E, S, W)");
                        //makeMove(move2);
                        //break;
                    } else // they don't make any sense
                    {
                        text.appendText("Unrecognized command" + "\n");
                    }
                }

                //}

                //when someone dies
                if (!fled) {
                    if (p.alive) // if you are still standing
                    {
                        //print results (money earned, health remaining)
                        mobImagePane.setImage(null);
                        p.wallet += enemy.getLoot();
                        playerInfo.setText(p.getPlayerName() + "\n" + "Health: " + p.getHealth() + "\n" + "Wallet: " + p.getWallet() + "\n");
                        text.setText("You shrekt the " + enemy.getName() + "\n" + "You got $" + enemy.getLoot() + " for winning!" + "\n" + "You now have $" + p.wallet + "\nYour health is " + p.getHealth() + "\n");
                    } else //if you died
                    {
                        mobImagePane.setImage(null);
                        text.setText("You have been shrekt by the " + enemy.getName() + "\n" + "GAME OVER" + "\n");
                        text.appendText("\nPlay again? (Y/N)" + "\n");
                        inputText.setOnAction(event2 -> {
                            String answer = inputText.getText();
                            answer.toUpperCase();
                            //String answer = this.choice;

                            if (answer.equals("Y")) //if they want to play again
                            {
                                text.appendText("Alright! Let's go!" + "\n");
                                this.reset();
                            } else //if they want to quit
                            {
                                text.appendText("Wow. What a skrub, okay bye." + "\n");
                                System.out.close();
                            }
                        });

                    }
                }
            });
        }
    } //end doBattle

(Some of) the things that are going wrong and how to fix them

As coded, your startAdventure() method isn't being called until after the JavaFX system has been shutdown, which is obviously not what you want.

The launch method javadoc states:

The launch method does not return until the application has exited, either via a call to Platform.exit or all of the application windows have been closed.

You are invoking startAdventure() at the wrong time, you should invoke it in your application's start() method rather than after the launch() method call.

game.startAdventure();
primaryStage.show();

Additionally, the Java launcher creates an instance of your application, so you don't need to create one explicitly. Read up on the JavaFX application lifecycle to understand this process. So your main method should be:

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

Some speculation on my part

Additionally, your creation of a new Adventure() is a bit suspect, though it is difficult to say for sure without further code supplied. Adventure might be a controller, so perhaps you don't need to create a new Adventure object at all, but can instead get the controller from the loader as outlined in:

And, if it is a controller, then the code to start the adventure could occur in the initialize() method of the controller, which will be automatically invoked by the FXMLLoader.

Eventually as the application grows, it may be beneficial to create an MVC style approach, where your Adventure class is just model data and logic which is passed into an AdventureView which is a controller that wires the model and the FXML view together. Though discussion of detailed techniques on how to accomplish that is outside the scope of this answer, you might get some ideas from the passing parameters answer linked earlier.

Answers to additional follow-up questions

text and inputText are properly initialized variables from my AdventureGUI.fxml file, which was made using Scene Builder

Variables in your instance of the Adventure class will not be initialized by the FXMLLoader if you create your adventure class using new , instead the FXMLLoader will create it's own instance of Adventure and initialize that, and that will be separate from the instance you created. As the text field is not initialized for your instance, you end up with a NullPointerException .

Also, text and inputText were declared in the Adventure class like this: '@FXML private TextArea text; @FXML private TextArea inputText;'

Yes, they need to be created by the FXMLLoader, which won't occur if you just write new Adventure() .

So to fix all this, I need to create an initialize() method in my controller class? How would I go about doing that?

Option A (let the FXMLLoader implicitly initialize your Adventure)

Rename startAdventure() to initialize() .

public void start(Stage primaryStage) throws Exception
{
    Parent root = FXMLLoader.load(getClass().getResource("AdventureGUI.fxml"));
    primaryStage.setScene(new Scene(root, 600, 600));
    primaryStage.setTitle("Adventure");
    primaryStage.show();
}

Option B (explicitly initialize your Adventure)

public void start(Stage primaryStage) throws Exception
{
    FXMLLoader loader = new FXMLLoader(
        getClass().getResource("AdventureGUI.fxml")
    );
    Parent root = loader.load();
    Adventure adventure = loader.<Adventure>getController();
    adventure.startAdventure();
    primaryStage.setScene(new Scene(root, 600, 600));
    primaryStage.setTitle("Adventure");
    primaryStage.show();
}

Sample Application

示例应用

adventure/AdventureApp.java

package adventure;

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

public class AdventureApp extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        FXMLLoader loader = new FXMLLoader(
                getClass().getResource(
                        "AdventureGUI.fxml"
                )
        );
        Parent root = loader.load();

        stage.setScene(new Scene(root));
        stage.setTitle("Adventure");
        stage.show();
    }

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

adventure/Adventure.java

package adventure;

import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;

public class Adventure {
    @FXML
    private TextArea text;

    @FXML
    private TextField inputText;

    private Player player = new Player();

    public void initialize() {
        text.setText("Welcome to the land of euphoria!\n");
        text.appendText("What's your name?\n");

        Platform.runLater(() -> inputText.requestFocus());
        inputText.setOnAction(event -> {
            player.setName(inputText.getText());
            text.appendText("Welcome, " + player.getName() + "\n");
        });

        player.setLocation("START");
    }
}

adventure/Player.java

package adventure;

public class Player {
    private String name;
    private String location;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }
}

adventure/Adventure.java

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="150.0" prefWidth="200.0" spacing="10.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="adventure.Adventure">
   <children>
      <TextArea fx:id="text" editable="false" prefHeight="200.0" prefWidth="200.0" wrapText="true" VBox.vgrow="ALWAYS" />
      <TextField fx:id="inputText" minHeight="-Infinity" />
   </children>
   <padding>
      <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
   </padding>
</VBox>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM