Alright, so hello everyone hope you're having a nice day.
So I'm pretty new to Java, however I know most of the basics, so not a superb noob at least; and I'm developing a JavaFX sample application, just for testing.
So I ran into a problem, I have a Main class in which I've created the interface, pretty simple, just a scene with 1 button.
Code for Main:
public class Main extends Application{
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Hi I'm a title");
//Initialize the Button Object
Button button = new Button();
button.setText("Bite me");
//Call the Handlers Class, with the name handlers
Handlers handlers = new Handlers();
/*
Here would go the code to pass the 'button' object to Handlers class
So then I can do whatever I need in there
*/
//The Button event, managed by the 'handle' method in Handlers class, that's why the 'handlers' in the
//parentheses
button.setOnAction(handlers);
//Just the scenery, not relevant
StackPane pane = new StackPane();
pane.getChildren().add(button);
Scene scene = new Scene(pane, 300,250);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Code for Handlers:
public class Handlers implements EventHandler<ActionEvent> {
//Very sad try of a constructor, so as to pass the 'button' object from Main class
//In here, however I have no idea as to how to pass an Object type and how to receive it
public void Handlers(){
}
//The handle method to manage the 'button.setOnAction' event
//This is where I need the 'button' object to compare the source of the event
//to that specific button, so as to prevent that every single button does the same thing
//'button' object from Main class should go ".equals(button)"
#Override
public void handle(ActionEvent event) {
Main main = new Main();
if (event.getSource().equals()){
}
}
}
I'm adding an event to that button, so I've created a Handlers class to handle (dah) all the events on the Main class, the problem rises it's ugly head when I try to pass the 'button' object from Main to Handlers so then I can get it's source, so that every button doesn't do the same thing.
My question is: How can I pass the 'button' object from Main to Handlers? I know that I can use the Constructor, the only problem with that is that I still quite don't grasp the functionality of the Constructor, nor the correct use of the arguments.
I've read these forums for about 1 hour looking for the solution, and I'm pretty sure I've already encountered it, but due to my ignorance, can't understand it.
The clearest example of this would be:
Similar problem
Sorry for the utterly long post, but... I need help :(
Have a nice day :D
There are a couple of things wrong here that I recommend you learn a little more about before trying to fully tackle your application idea. But I can answer your question and give you some links to get your started.
Intro
here is a post about main(), typically you only have one for an application.
Part I
Look into class inheritance. In a JavaFX application, your main class will typically extend Application and your main() method typically calls Application.launch(), made available to your main class because of access control. Under the hood, launch() is calling Application.start(). Because you're inheriting all of the things from Application, you are required to implement (what is probably, but don't quote me on this) the abstract method start() to do all your UI work, see overriding methods and the related overloading. So, in start(), which is pretty much called in every JavaFX application, you then create a button Handler that can be assigned to multiple Buttons on your Stage.
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Hi I'm a title");
EventHandler eventHandler = new BiteHandler("You've been bitten");
Button biteMeButton = new Button();
biteMeButton.setText("Bite me");
Button biteMeAgainButton = new Button();
biteMeAgainButton.setText("Bite me again");
biteMeButton.setOnAction(eventHandler)
biteMeAgainButton.setOnAction(eventHandler);
StackPane pane = new StackPane();
pane.getChildren().add(biteMeButton);
pane.getChildren().add(biteMeAgainButton);
Scene scene = new Scene(pane, 300,250);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Part II
Here is the BiteHandler class. With this construct, check out polymorphism. You could have a BiteHandler, ChewHandler, and LickHandler that can all be stored in a List<EventHandler> due to polymorphism.
With the way EventHandler is setup, essentially the handle() method is what is called when the EventHandler is invoked, a la a button's on click action. So we need to give this thing something to do when the handler is invoked. Since I have no idea how to animate a mouth, I'm just gonna have this thing print to the passed message to the console. Here, I've left a comment about what a constructor is.
public class BiteHandler implements EventHandler<ActionEvent> {
private String message_;
/**
* Think of a constructor as any other method, however, it is special
* in that it is named after the class. Because it is named
* after the class, the compiler knows that the first thing
* that needs to be done when instantiating this class. A
* constructor is "automatic work" that "constructs" your object.
* In this class, when you instantiate it, a message is required.
* this message is stored in a "private class member" to be accessed
* later on when the bite() method is called
*/
public BiteHandler(String message) {
message_ = message;
}
#Override
public void handle(ActionEvent event) {
bite();
}
public void bite() {
System.out.println(message_);
}
}
Finale
On your application, you will have 2 buttons. When you click either button, the phrase "You've been bitten" will print to the console. This is because each button has the same BiteHandler. Pretty fk'ing lack luster for all that work but you've got plenty of homework to do to fully digest this answer
Related
I'm trying to have a button in the Launcher class and make a handling function in another class as below. However, the handling function doesn't seem to work. Nothing printed.
I think the function button.setOnAction(anotherclass) is the cause. On some tutorials, they sai the parameter for setOnAction() is where I put the handling function at. So I put anotherclass there.
I know that I can just make a handling function in the same class or just use lambda. However, I'm trying to see if this way works.
public class Launcher extends Application{
public static Button button;
AnotherClass anotherclass;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage myStage) throws Exception {
button = new Button("Click me");
button.setOnAction(anotherclass);
StackPane layout = new StackPane();
layout.getChildren().add(button);
Scene scene = new Scene(layout, 300, 250);
myStage.setScene(scene);
myStage.show();
}
}
public class AnotherClass implements EventHandler<ActionEvent>{
#Override
public void handle(ActionEvent event) {
if(event.getSource()== Launcher.button) {
System.out.println("print");
}
}
}
Can anyone help me make a handling function in a different class as the button's?
This would be a very strange design to have an entirely different class created for the sole purpose of handling one event, but it can be done. Just not the way you're trying to do it.
If you are set on using this separate AnotherClass to handle it, you just need to pass the class with the new keyword to actually instantiate the class:
button.setOnAction(new AnotherClass());
However, this would be poor design for a couple of reasons:
You are exposing your Button through the public modifier. This is a bad idea and violates the principles of encapsulation.
You would be separating the logic of your application from the UI in a fairly unintuitive manner, making it more challenging to maintain in the future.
My suggestion would be to use an anonymous class and a lambda expression within the setOnAction() method:
button.setOnAction(event -> {
System.out.println("print");
}
This would have the same result as your current implementation of AnotherClass, but is easier to read, easier to maintain, keeps the logic for your buttons right in the same code, and does not require you to expose your Button publicly.
I've been having troubles generating a second stage/scene in my program. I have a first stage/scene that generates at launch, works perfectly with a working button. It is in it's own class and executes at the beginning.
Further along in the program I have a method that I was hoping to launch my next stage from. I had created another class below for the generation of my second stage, and placed the constructor within this method. When the method is executed, a stage is being created, but the scene isn't showing anything, just the stage with the proper title. I've tried adding in simple buttons, labels with text, but nothing shows. Not really sure what could be the cause, but after fiddling for awhile I decided to request some help from you guys. Given my limited knowledge of JavaFX, I was wondering if the first stage is causing problems for the second one? I leave it open and running, should I close it before this second executes? Or is there a better way to create a second stage without creating a whole new class? Is the construction of the secondScene class a possible problem?
Let me know, thanks!
EDIT: I have now added a runtime exception handler in, and it does trigger a runtime exception when this class is created. I'm still a bit new to Java, does this mean the problem lies within the class I've created? Or can it still be something earlier on in the code? I have still not been able to get the simple class to create inside this program effectively, so I'm unable to produce a minimum level of functionality here, but according to other people, the code does work when it stands alone.
public SecondScene() {
Thread t = new Thread(new adminThread());
t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
System.out.println(t + " throws exception: " + e);
}
});
// this will call run() function
t.start();
Text textOutput = new Text(textToDisplay);
BorderPane borderPane = new BorderPane(textOutput);
Scene secondScene = new Scene(borderPane, 400, 600);
// New window (Stage)
Stage secondWindow = new Stage();
secondWindow.setTitle("Second Window");
secondWindow.setScene(secondScene);
secondWindow.show();
}
public void setRunningText(String text){
this.textToDisplay = text;
}
}
class adminThread implements Runnable {
public void run() {
throw new RuntimeException();
}
}
I'm working on a game in JavaFX, and right now I'm trying to create a loading screen, since loading the assets takes some time. I've created a LoadingPane class that displays several progress bars, and I know for sure that it works. However, in the below code, the loading pane will not be visible until after the loadAssets function, even though I'm adding it beforehand. When I run the below code, I get a blank stage for the time it takes for the assets to load, and then a screen with the completed progress bars.
I haven't been able to find anyone with similar issues, or any sort of refresh or update function to force the scene to display the loading pane before continuing with the program.
Note: I've deleted some irrelevant code setting up keyboard input handling.
public class Main extends Application{
Pane root = new Pane();
Scene mainScene = new Scene(root, Constants.WINDOW_WIDTH, Constants.WINDOW_HEIGHT, Color.BLACK);
static LoadingPane loadingPane = new LoadingPane(3);
private static int loadingIndex = 0;
public static void main(String[] args) {
if(Constants.DEBUG_MODE)
System.out.println("WARNING: Game has launched in debug mode!");
launch(args);
}
public static void updateProgress(double percent){
loadingPane.setBarLength(loadingIndex, percent);
}
public static void loadAssets(){
RoomLoader.createRooms();
updateProgress(1.0);
loadingIndex++;
ProjectileLoader.load("imgs/projectiles/");
ProjectileLoader.load(Constants.BATTLE_IMAGES_FILEPATH);
updateProgress(1.0);
loadingIndex++;
BattleLoader.createBattles();
updateProgress(1.0);
loadingIndex++;
}
public static void updateProgress(double percent){
loadingPane.setBarLength(loadingIndex, percent);
}
#Override
public void start(final Stage primaryStage) {
//root.getChildren().add(new javafx.scene.image.ImageView(new Image("imgs/loading.png")));
root.setLayoutX(0);
primaryStage.setScene(mainScene);
primaryStage.show();
primaryStage.toFront();
primaryStage.setTitle("Branch");
primaryStage.setResizable(false);
//primaryStage.getIcons().add(new Image("core/imgs/soul/red.png"));
//This allows the closing of the primaryStage to end the program
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>(){
#Override
public void handle(WindowEvent t) {
System.exit(0);
}
});
root.resize(Constants.WINDOW_WIDTH, Constants.WINDOW_HEIGHT);
primaryStage.getIcons().add(new Image("imgs/icon.png"));
//End GUI setup
//The problem lines
root.getChildren().add(loadingPane);
//refresh root?
loadAssets();
}
}
EDIT: Working Code
For anyone who arrives here with a similar issue, below is the code I used to get this to work:
I replaced this:
//The problem lines
root.getChildren().add(loadingPane);
//refresh root?
loadAssets();
With this:
root.getChildren().add (loadingPane);
Task<Integer> loadingTask = new Task<Integer>() {
#Override protected Integer call() throws Exception {
loadAssets();
return 1;
}
};
loadingTask.setOnSucceeded(new EventHandler<WorkerStateEvent>(){
#Override
public void handle(WorkerStateEvent t){
loadingPane.setVisible(false);
load(); //note: this function sets up the actual game
//updating the GUI, adding game elements, etc
}
});
new Thread(loadingTask).start();
I can't say that this is the best way to go about this, but I can say that it works. Good luck!
You need a separate thread for the update method.
Code runs in a linear fashion, one bit of code runs then the next. With a separate thread, the two “lines” of code can run side by side. The process runs and the GUI updates at the same time.
JavaFX application runs on specific thread called Application Thread, in other to make GUI responsive while doing some expensive operation, like in your case loading assets, you will need to load assets on another Thread that you create yourself or you can use Task which is one of JavaFX classes meant to be used in such use cases.
I suggest you to read about Task in official javadocs
I would like to create a class called EnhancedStage which adds functionality to Stage.
The problem is, javaFx creates the primary stage and passes it into the application, instead
of allowing me to create it. This way I'm not able to use my own class for the primary stage.
I don't understand why the system creates the Stage, and would I be losing anything by not
using that stage and instead constructing another one ?
Thanks
If you don't want to use the stage which is a parameter of the start() method, then don't call show() on that stage, just create your own stage and only call show() on your custom stage. I suggest doing this inside the start method rather than via a runLater call from init. I don't see any significant drawback in such an approach.
As to why a stage is passed in start, MadProgrammer's guess is as good as any: Simplifies life for programmers, removes some boilerplate code from basic apps and limits app startup coding errors. For all those reasons, for most people, I'd generally just advise using the provided stage rather than creating your own.
Suggested approach if you need or desire to use a stage subclass for your primary stage:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class StageTester extends Application {
#Override public void start(final Stage systemStage) throws Exception {
UserStage userStage = new UserStage();
userStage.show();
}
private class UserStage extends Stage {
FlowPane layout = new FlowPane();
UserStage() {
super();
foo();
setScene(new Scene(layout));
}
public void foo() {
layout.getChildren().add(new Label("foo"));
}
}
public static void main(String[] args) { launch(args); }
}
There is nothing stopping you from creating another stage and ignoring the one you get from the start method. There are number of ways to do this:
Extend Stage and add your additional functionality
public class EStage extends javafx.stage.Stage {
... // Do your magic here
}
or Extend Window
public class EStage extends javafx.stage.Window {
}
In either case, to use your new stage, you can just create it in the init method and use it in the start method, ignoring the stage supplied to you
public class ShowCase extends Application {
private Stage mStage;
#Override
public void init() throws Exception {
Platform.runLater(() -> {
mStage = new EStage();
//mStage.impl_setPrimary(true);
}
}
#Override
public void start(Stage primaryStage) {
mStage.setScene(...)
}
}
The JavaFX architecture is based upon a container which manages the JavaFX Event Loop (on the JavaFX application thread) for your application (which is why the entry class for your application must inherit from javafx.application.Application).
Because JavaFX applications are container managed, you gain a few benefits:
the container can inject JavaFX control references into your controller class instances via the FXML loader
the container manages event propagation from the event loop all the way down to the target control and back up to the event loop again without complex event handling on your part
Of course, the event loop requires a top-level object for event propagation and this top-level object is the Stage object, which gets passed to the start() method of your Application object.
I realize there is an nearly identical question in title. The question does not seem to be relevant to my particular issues.
I'm using the JavaFX scene builder to create my UI, (which includes the TextArea in question). All I want to do is take the message I get from the server and post it into the text area. Through various println statements I have narrowed the problem to this. I have tried various solutions (for hours); coming here was a last resort.
The only other possible cause I can think of would be something going wrong with the multithreading, but can even begin to think of what.
public class IncomingReader implements Runnable
{
#Override
public void run()
{
String message;
try
{
while((message = Connection.connection.reader.readLine()) != null)
{
System.out.println("read" + message); //for debug, prints fine
FXMLDocumentController.controller.chatBox
.appendText(message + "\n"); /////////PROBLEM HERE//////
}
}
catch(IOException e)
{
System.out.println("Problem!"); // TODO: Catch better.
}
}
}
FXML controller class (relevant line only):
#FXML protected TextArea chatBox;
public class JavaChat extends Application
{
#Override
public void start(Stage stage) throws Exception {
// Create and set up scene...
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
// Establish connection to server...
Connection.createNewConnection();
// Create new reader thread...
Thread readerThread = new Thread(new IncomingReader());
readerThread.start();
// When all done...
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The line of FXML that defines the chatBox:
<TextArea id="ChatBox" fx:id="chatBox" focusTraversable="false" mouseTransparent="true" prefHeight="400.0" prefWidth="200.0" promptText="Chat:" wrapText="true" BorderPane.alignment="CENTER">
The resulting exception:
Exception in thread "Thread-4" java.lang.NullPointerException
at javachat.IncomingReader.run(IncomingReader.java:28)
at java.lang.Thread.run(Thread.java:745)
Note: this is really covered fully in Passing Parameters JavaFX FXML. However, the code you posted is so far from being structured properly to use the approaches there, that you probably need to see it specifically for your example. I would strongly recommend you read through that post and understand it, though.
Why you are seeing a NullPointerException:
FXMLDocumentController.controller refers to an instance of FXMLDocumentController which is not the same instance that was created for you by the FXMLLoader. Consequently, the chatBox field in FXMLDocumentController.controller was not initialized by the FXMLLoader.
What FXMLLoader does:
When you call one of the FXMLLoader.load(...) methods, it basically does the following:
Parses the FXML file (or stream)
If the root element of the FXML contains an fx:controller attribute, and no controller has been set by other means, it creates an instance of the class specified in that attribute
Creates the object hierarchy described by the FXML. If any of the objects defined in FXML have fx:id attributes, and a controller is associated with the FXMLLoader, it initializes #FXML annotated fields of the controller with the corresponding objects
Associates event handlers with the nodes, where defined
Returns the root object of the FXML object hierarchy
How to access the controller after loading the FXML
To access the controller, you must create an FXMLLoader instance instead of relying on the (evil) static FXMLLoader.load(URL) method. You can either pass the URL of the FXML resource into the FXMLLoader constructor, or call setLocation(...) on the FXMLLoader. Then, to load the FXML file, just call the no-argument load() method on the FXMLLoader. Once that is complete, you can access the controller by calling getController() on the FXMLLoader instance.
Other issues in your code
You cannot update the UI from a background thread: you must update it from the JavaFX Application Thread. As it stands (if you fix your NullPointerException issue), your code would throw an IllegalArgumentException in Java 8. In JavaFX 2.2 and earlier you would have to live with the possibility of bugs showing up at arbitrary times in the future. You can schedule code to be executed on the FX Application Thread by wrapping that code in a call to Platform.runLater().
Not quite so bad, but imo a bad design, is that you are exposing UI elements (the TextArea) outside of your FXML-controller pair. This becomes a real issue when your boss comes into your office and tells you he doesn't want the messages displayed in a TextArea any more, he wants them in a ListView. Since the TextArea is exposed, you have to trawl through all your code looking for any references to it. A better approach is to define a appendMessage(String) method in your controller, and access it. You might even want to factor all the data out into a separate model class, and pass references an instance of that class to both the controller and the reader class, but that is beyond the scope of this question.
I will refrain from complaining about your overuse of static stuff.. ;).
Here's the skeleton of one way to fix this code:
public class IncomingReader implements Runnable
{
private final FXMLDocumentController controller ;
public IncomingReader(FXMLDocumentController controller) {
this.controller = controller ;
}
#Override
public void run()
{
String message;
try
{
while((message = Connection.connection.reader.readLine()) != null)
{
System.out.println("read" + message); //for debug, prints fine
final String msg = message ;
Platform.runLater(new Runnable() {
#Override
public void run() {
controller.appendMessage(msg + "\n");
}
});
}
}
catch(IOException e)
{
System.out.println("Problem!"); // TODO: Catch better.
}
}
}
Controller:
public class FXMLDocumentController {
#FXML
private TextArea chatBox ;
public void appendMessage(String message) {
chatBox.appendText(message);
}
// other stuff as before...
}
Application:
public class JavaChat extends Application
{
#Override
public void start(Stage stage) throws Exception {
// Create and set up scene...
FMXLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
Parent root = loader.load();
FXMLDocumentController controller = (FXMLDocumentController) loader.getController();
Scene scene = new Scene(root);
// Establish connection to server...
Connection.createNewConnection();
// Create new reader thread...
Thread readerThread = new Thread(new IncomingReader(controller));
readerThread.start();
// When all done...
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}