toFront() throwing UnsupportedOperationException - java

I want to create a StackPane where, whatever is added, a node of my choice is always at the front (rather than having to be careful about the order I add things in, or remembering to call toFront() on that particular node whenever anything is added.)
In order to do this, I simply place a listener on the child list of the relevant StackPane object, so that whenever anything changes, it calls toFront() on the relevant node, for example:
public class Test extends Application {
#Override
public void start(Stage stage) {
StackPane root = new StackPane();
final Rectangle r1 = new Rectangle(50, 50);
root.getChildren().add(r1);
root.getChildren().addListener(new ListChangeListener<Node>() {
#Override
public void onChanged(ListChangeListener.Change<? extends Node> change) {
try {
while(change.next()) {
if(change.wasAdded()) {
r1.toFront();
}
}
}
catch(Exception ex) {
ex.printStackTrace();
}
}
});
root.getChildren().add(new Rectangle(50, 50));
stage.setScene(new Scene(root));
stage.show();
}
}
In Java 7, this works just fine. However, in JFX8 (latest build downloaded just now), it fails with the following:
java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableList.add(Collections.java:1374)
at javafx.collections.ListChangeBuilder.nextRemove(ListChangeBuilder.java:208)
at javafx.collections.ObservableListBase.nextRemove(ObservableListBase.java:150)
at javafx.collections.ModifiableObservableListBase.remove(ModifiableObservableListBase.java:181)
at com.sun.javafx.collections.VetoableListDecorator.remove(VetoableListDecorator.java:284)
at com.sun.javafx.collections.VetoableListDecorator.remove(VetoableListDecorator.java:209)
at javafx.scene.Parent.impl_toFront(Parent.java:624)
at javafx.scene.Node.toFront(Node.java:1713)
at test.Test$1.onChanged(Test.java:34)
at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:315)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:72)
at com.sun.javafx.collections.VetoableListDecorator$1.onChanged(VetoableListDecorator.java:77)
at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:315)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:72)
at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at javafx.collections.ModifiableObservableListBase.add(ModifiableObservableListBase.java:155)
at java.util.AbstractList.add(AbstractList.java:108)
at com.sun.javafx.collections.VetoableListDecorator.add(VetoableListDecorator.java:200)
at test.Test.start(Test.java:41)
at com.sun.javafx.application.LauncherImpl$8.run(LauncherImpl.java:837)
at com.sun.javafx.application.PlatformImpl$7.run(PlatformImpl.java:331)
at com.sun.javafx.application.PlatformImpl$6$1.run(PlatformImpl.java:297)
at com.sun.javafx.application.PlatformImpl$6$1.run(PlatformImpl.java:294)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl$6.run(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.access$300(WinApplication.java:39)
at com.sun.glass.ui.win.WinApplication$4$1.run(WinApplication.java:112)
at java.lang.Thread.run(Thread.java:744)
And yes, test.Test$1.onChanged(Test.java:34) does indeed refer to r1.toFront();.
Is this to be considered a bug, or am I breaking some rule I'm unaware of by trying to achieve things this way? I did wonder whether the list was still being changed while the onChanged() method was executing, and toFront() would also change the list contents, hence the exception - but the Javadoc to onChanged() clearly says:
Called after a change has been made to an ObservableList.
(Bolding is mine.)
EDIT: At this point I'm more certain that it's a bug, so the related bug report is here.

It seems that you are not allowed to modify a (JavaFX) list inside an event handler that is currently handling another (previous) modification event of the same list. Although this seems reasonable it is not self-evident, so there should be a more obvious exception in that case.
Unfortunately non-speeking exceptions are very common in JavaFX.
Fortunately the solution/workaround is pretty easy: Call your modifying code (here: r1.toFront) by Platform.runLater(), it will delay your modification to happen after the originating event:
root.getChildren().addListener(new ListChangeListener<Node>() {
#Override
public void onChanged(ListChangeListener.Change<? extends Node> change) {
Platform.runLater(new Runnable() {
public void run() { r1.toFront(); }
});
}
});
Sidenote: toFront does nothing, if the component is already at front. This prevents infinite loops. Nevertheless, as this is not explicitely mentioned in the documentation, you might not rely on that.

Related

How to handle an event from another class in JavaFX?

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.

Issue with JavaFX second scene/stage creation

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();
}
}

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

NullPointerException while fast updating TextField

I'm working on an app, that searches some files in the directed folder and prints them in TableView<myFile> foundFilesList, that is stored in the Main class. myFile just extends File a bit. The searching is done using service in background thread, that puts found data to ObservableList<myFile> filesOfUser.
I want to display current amount of find files in TextField foundFilesAmount in the same view, where TableView with files is located -- ResultsView
To do that, I added a ListChangeListener for foundFilesList to ResultsView controller, that uses method setText to print current size of filesOfUser. It looks like:
Main.filesOfUser.addListener(new ListChangeListener<myFile>() {
#Override
public void onChanged(Change<? extends myFile> c) {
while (c.next()){
if (c.wasAdded())
setCounter(c.getAddedSize());
}
}
});
void setCounter (int number) contains only
int currValue = Integer.valueOf(foundFilesAmount.getText());
foundFilesAmount.setText(String.valueOf(currValue + number));
And now what the problem is. Textfield with current amount of find files is updated very fast, and from one moment it stops doing it. In the console I see lots of repeated NullPointerException's from JavaFX Application Thread. Its' contents:
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at com.sun.javafx.text.PrismTextLayout.getRuns(PrismTextLayout.java:236)
at javafx.scene.text.Text.getRuns(Text.java:317)
at javafx.scene.text.Text.updatePGText(Text.java:1465)
at javafx.scene.text.Text.impl_updatePeer(Text.java:1500)
at javafx.scene.Node.impl_syncPeer(Node.java:503)
at javafx.scene.Scene$ScenePulseListener.synchronizeSceneNodes(Scene.java:2290)
at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2419)
at com.sun.javafx.tk.Toolkit.lambda$runPulse$30(Toolkit.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:354)
at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:381)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:510)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:490)
at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$404(QuantumToolkit.java:319)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
at java.lang.Thread.run(Thread.java:745)
I tried to set sort of delay before using setText, like updating value in foundFilesAmount only after 5'th, 10'th, 15'th update, etc. But if the search works longer, exceptions are still thrown.
Is there a correct method to show current amount of found files, that contains real amount and doesn't cause so much exceptions?
Thanks in advance.
The correct way is not doing the updates of the UI on a thread other than the application thread. This can otherwise lead to issues with rendering/layouting.
You could use a Task to do the updates to the updates however:
Task<ObservableList<myFile>> task = new Task<
Task<ObservableList<myFile>>() {
#Override
protected ObservableList<myFile> call() throws Exception {
ObservableList<myFile> files = FXCollections.observableArrayList();
while (...) {
...
files.add(...);
updateMessage(Integer.toString(files.size()));
...
}
return files;
}
};
task.setOnSucceeded(evt -> tableView.setItems(task.getValue()));
Thread t = new Thread(task);
textField.textProperty().bind(task.messageProperty());
t.setDaemon(true);
t.start();
try something like this:
Platform.runLater(new Runnable() {
#Override
public void run() {
// update your JavaFX controls here
}
});

Categories