How to handle an event from another class in JavaFX? - java

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.

Related

JavaFX Update Issue?

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

Share objects in classes Java(FX)

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

JavaFX software design

In a JavaFX application, javafx.application.Application must be subclassed, and the inherited launch() method, although it's public, must be called from within this derived class, otherwise an exception is thrown. The launch() method then uses reflection to instantiate the derived class, making it difficult to set values for the class members without losing them when launching. All that appears totally unusual to me, and I was wondering why starting a JavaFX application is so complicated, if that kind of software design (design pattern?) has a name, or if it's just bad design?
EDIT:
To be more specific, I want to use the observer pattern, so my java application gets notified when a document was loaded, like this:
public class MyDocumentLoader extends Application
{
private ChangeListener<Worker.State> changeListener;
public void setChangeListener(ChangeListener<Worker.State> changeListener)
{
this.changeListener = changeListener;
}
...
public void loadDocument(String url)
{
webEngine.getLoadWorker().stateProperty().addListener(changeListener);
webEngine.load(url);
}
...
}
I need the callback member in several methods, and ideally I can have more than one instances of the class that loads documents, so I can set different ChangeListeners for different URLs.
My guess is that this design was motivated by the (vast) number of Swing applications that were incorrectly written, with the "primary" JFrames being instantiated and shown on the wrong thread (i.e. not on the AWT event dispatch thread). My guess is that so many Swing applications were incorrectly written that they had to defensively code the framework against the incorrect usage, and that they wanted to avoid this scenario with JavaFX.
Forcing (well, almost forcing, there are hack-arounds) an FX Application to start this way makes it much harder to write an application incorrectly in a similar way. The launch method (and the equivalent Oracle JVM startup process if you have an Application subclass without a main method and a call to launch) does quite a bit of boilerplate work: it starts the FX toolkit, instantiates the Application subclass and calls its init() method, then on the FX Application Thread it instantiates the primary Stage and passes it to the Application subclass's start(...) method. This then ensures everything is running on the correct thread.
You should basically consider the start(...) method in a JavaFX application as the replacement for the main(...) method in a "traditional" Java application, with the understanding it is invoked on the FX Application Thread.
My recommendation is that the Application subclass should be as minimal as possible; it should just delegate to something else to actually create the UI, and then should just place it in the primary stage and show it. Include a main method that does nothing other than call launch(...) as a fallback for non-JavaFX-aware JVMs. You should only have one instance of one Application subclass present in any JVM. This way your Application subclass has no class members to set, and so the issues you describe simply don't arise.
If you use FXML, this is actually fairly natural: the start(...) method essentially just delegates to the FXML-controller pair to do the real work. If you don't use FXML, create a separate class to do the actual layout, etc, and delegate to it. See this related question which gets at the same kind of idea.
Note also that your statement
the inherited launch() method, although it's public, must be called
from within this derived class
is not entirely accurate, as there is an overloaded form of the launch(...) method in which you can specify the application subclass. So, if you really need, you can just create a stub for starting the FX toolkit:
public class FXStarter extends Application {
#Override
public void start(Stage primaryStage) {
// no-op
}
}
Now you can do:
public class MyRegularApplication {
public static void main(String[] args) {
// start FX toolkit:
new Thread(() -> Application.launch(FXStarter.class)).start();
// other stuff here...
}
}
Note that launch does not return until the FX toolkit shuts down, so it is imperative to put this call in another thread. This potentially creates race conditions, where you may try to do something needing the FX toolkit before launch(...) has actually initialized it, so you should probably guard against that:
public class FXStarter extends Application {
private static final CountDownLatch latch = new CountDownLatch(1);
public static void awaitFXToolkit() throws InterruptedException {
latch.await();
}
#Override
public void init() {
latch.countDown();
}
#Override
public void start(Stage primaryStage) {
// no-op
}
}
and then
public class MyRegularApplication {
public static void main(String[] args) throws InterruptedException {
// start FX toolkit:
new Thread(() -> Application.launch(FXStarter.class)).start();
FXStarter.awaitFXToolkit();
// other stuff here...
}
}
SSCCE (I just used inner classes for everything so this is convenient to run, but in real life these would be standalone classes):
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class BackgroundProcessDrivenApp {
public static void main(String[] args) throws InterruptedException {
Platform.setImplicitExit(false);
new Thread(() -> Application.launch(FXStarter.class)).start();
FXStarter.awaitFXToolkit();
new MockProcessor().doStuff() ;
}
public static class FXStarter extends Application {
private static final CountDownLatch latch = new CountDownLatch(1);
#Override
public void init() {
latch.countDown();
}
public static void awaitFXToolkit() throws InterruptedException {
latch.await();
}
#Override
public void start(Stage primaryStage) { }
}
public static class MockProcessor {
private final int numEvents = 10 ;
public void doStuff() {
Random rng = new Random();
try {
for (int event = 1 ; event <= numEvents; event++) {
// just sleep to mimic waiting for background service...
Thread.sleep(rng.nextInt(5000) + 5000);
String message = "Event " + event + " occurred" ;
Platform.runLater(() -> new Messager(message).showMessageInNewWindow());
}
} catch (InterruptedException exc) {
Thread.currentThread().interrupt();
} finally {
Platform.setImplicitExit(true);
}
}
}
public static class Messager {
private final String message ;
public Messager(String message) {
this.message = message ;
}
public void showMessageInNewWindow() {
Stage stage = new Stage();
Label label = new Label(message);
Button button = new Button("OK");
button.setOnAction(e -> stage.hide());
VBox root = new VBox(10, label, button);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 350, 120);
stage.setScene(scene);
stage.setAlwaysOnTop(true);
stage.show();
}
}
}
JavaFX supports a great number of deployment and packaging strategies, ref. https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/toc.html, and having a standardized lifecycle entry- and exit-point simplifies supporting all these strategies.
If you are struggling to initialize your main application class, due to it being instanciated by the JavaFX launcher, your best option is to use the Application.init() and Application.stop() methods, as James_D points out.

JavaFX - Why does JavaFx create a primary stage instead of allowing me to do it?

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.

How can I place #Action methods in a separate class from a Swing component?

When developing Swing applications, I've typically defined a delegate interface for each UI component for action callbacks. For example, if there is a class, MyDialog, which contains a button, MyButton, then the ActionListener for MyButton will call MyDialog.Delegate.OnMyButtonClick(Event e). The UI component then becomes "dumb" and requires a controller to handle events as well as update the component itself.
I thought that by using the Swing Application Framework's #Actions, I could get around creating delegate interfaces and implementations by defining #Action methods in implementation classes, letting the ApplicationContext figure out what to call. Apparently, that is not the case, as I don't see any clear way of adding those classes into the ApplicationContext, nor do I see any examples out there of doing such a thing.
Has anyone managed to use SAF in this manner so that there is a clean separation between UI and UI action code?
I've discovered a good way to keep the UI separate from the behavior using #Actions.
First, create a UI Component, say a JPanel with a button and then give it a public method that can be used to set the action of the Button:
class CustomJPanel extends JPanel {
private JButton myButton;
public CustomJPanel() {
initializeComponents();
}
public void initializeComponents() {
myButton = new JButton();
}
public void setButtonAction(javax.swing.Action action)
{
myButton.setAction(action);
}
}
Next, create an Action class that will provide the logic for that button:
class CustomJPanelActions {
#Action
public void doSomething()
{
JOptionPane.showMessageDialog(null,"You pressed me!");
}
}
Finally, setup the application controller and during setup, assign the appropriate action to the appropriate UI:
class MyApp extends SingleFrameApplication {
private JFrame mainFrame;
private JLabel label;
#Override
protected void startup() {
getMainFrame().setTitle("BasicSingleFrameApp");
CustomJPanel panel = new CustomJPanel();
panel.setButtonAction(getContext().getActionMap(new CustomJPanelActions()).get("doSomething");
show(panel);
}
public static void main(String[] args) {
Application.launch(BasicFrameworkApp.class, args);
}
}
In this way, the UI is logically separated from the control (i.e. Action) and can be tested on its own. The controller can make any decisions it needs to in order to determine what Action set to use and which specific action to assign to the UI controls. That is, one can create a Test Action Set and a Live Action Set, etc.
This method of using SAF has worked rather well for me.
The SAF javadoc has some information on how to do this sort of thing in the doc for ActionManager#getActionMap

Categories