英文:
Null Pointer Exception when trying to update JavaFX from anouther class using Platform.runlater()
问题
以下是您提供的内容的翻译部分:
因此,我有三个类:GUIBuilder、ConwayLife 和 Updater。GUIBuilder 类构建一个 GUI,为用户提供一个网格,其维度由用户设置,并且能够选择每个网格元素以确定它是“存活”还是“死亡”。ConwayLife 类是一个算法,用于运行康威生命游戏,运行的代数由用户设置。Updater 类是我放置 Platform.runlater 代码的地方,尽管这是在尝试了几种不同的想法后产生的,以使其工作,所以可能是问题的一部分。
目标是使 Conway 类更新网格以反映当前代的状态
起始状态:
[![GUI 起始状态][2]][2]
结束状态:
[![GUI 结束状态][1]][1]
我一直在阅读 JavaFX 应用程序线程应该基本上不要动它,所以在尝试遵循那种逻辑的同时,当按下“开始模拟”按钮时,创建了一个新线程中的 Conway 类。(也忽略 X 和 Y 文本字段中的内容)
startSimulationButton.setOnAction(new EventHandler<ActionEvent>()
{
public void handle(ActionEvent event)
{
if(validateTextBox(genTextField))
{
int[][] universe = convertTo2DArray(simulationPane);
int generations = Integer.parseInt(genTextField.getText());
Runnable simStart = new ConwayLife(universe, generations, window);
new Thread(simStart).start();
}
else
{
instructionLabel.setText("Didn't put numbers in ya silly billy");
}
}
});
以下是将从 GUIbuiler 对象内部或从 updater 对象中调用以更新网格的类。
public void updateSimulationPane(int[][] universe, int xUpdate, int yUpdate) throws InterruptedException
{
clearSimulationGrid(simulationPane);
int[] flatUniverse = Stream.of(universe).flatMapToInt(IntStream::of).toArray();
for(int counter = 0; counter < (xUpdate*yUpdate); counter++)
{
Rectangle rect = new Rectangle(0, 0, squareDimensions, squareDimensions);
if(flatUniverse[counter] == 1)
{
rect.setFill(Color.DARKGREEN);
}
else
{
rect.setFill(Color.GREY);
}
simulationPane.add(rect, (counter) % xInput, (counter) / xInput);
}
double leftAnchor = (xWindow / 2) - ((squareDimensions * xInput) / 2);
gridAnchorPane.setTopAnchor(simulationPane, 100d);
gridAnchorPane.setLeftAnchor(simulationPane, leftAnchor);
//Thread.sleep(5000);
}
以下是 Conway 类和线程的开头部分,我对使用线程不太自信,所以在这里包含这一点,以防我在这里搞砸了什么。
private final int[][] universe;
private final int generations;
private final GUIBuilder window;
public ConwayLife(int[][] universePassed, int generationsPassed, GUIBuilder windowPassed)
{
universe = universePassed;
generations = generationsPassed;
window = windowPassed;
}
public void run()
{
try
{
getGeneration(universe, generations, window);
}
catch(Exception e)
{
System.out.println("Exception caught");
}
}
public static void getGeneration(int[][] cells, int generations, GUIBuilder window) throws InterruptedException
{
int [][] oldGeneration = cells; //new array to not alter original dataset
int [][] newGeneration = new int[oldGeneration.length + 2][oldGeneration[0].length + 2]; //next generation will always have more columns and rows to simulate infinite universe
int deadCells = 0; //counting the deadcells in the dataset to stop redundant generations
int i = 0;
int j = 0;
int bounds = 2;
boolean boundsChange = false;
下面是代数完成后将更新 GUI 的位置,然后循环应用算法到新的代数。
Updater changer = new Updater(window, trim(oldGeneration), oldGeneration.length, oldGeneration[0].length);
changer.run();
oldGeneration = newGeneration; //make the new generation the old generation to show passing of a generation
newGeneration = new int[oldGeneration.length + bounds][oldGeneration[0].length + bounds]; //em
最后是 Updater 类,其中包含回调到 GUIBuilder 对象的方法调用。
public class Updater implements Runnable
{
private final GUIBuilder window;
private final int[][] unvierse;
private final int xAxis;
private final int yAxis;
public Updater(GUIBuilder passedWindow, int[][] passed2DArray,int passedXAxis,int passedYAxis)
{
this.window = passedWindow;
this.unvierse = passed2DArray;
this.xAxis = passedXAxis;
this.yAxis = passedYAxis;
}
public void run()
{
Platform.runLater(new Runnable()
{
@Override
public void run()
{
try
{
window.updateSimulationPane(unvierse, xAxis, yAxis);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
}
这是您遇到的错误消息:
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at moduleInfo/ConwayGUIOfLife.GUIBuilder.clearSimulationGrid(GUIBuilder.java:174)
at moduleInfo/ConwayGUIOfLife.GUIBuilder.updateSimulationPane(GUIBuilder.java:216)
at moduleInfo/ConwayGUIOfLife.Updater$1.run(Updater.java:29)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:389)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
at java.base/java.lang.Thread.run(Thread.java:835)
编辑 1:这是 clearSimulationPane 方法。很抱歉,只是一行代码,所以以为可能是其他原因造成的问题:
private void clearSimulationGrid(GridPane simulationPane)
{
simulationPane
<details>
<summary>英文:</summary>
So, I have three classes: GUIBuilder, ConwayLife, and Updater. The GUI Builder builds a GUI that gives you a grid with dimensions that the user sets and the ability to select each grid element to pick if it is "alive" or "dead". The ConwayLife class is an algorithm to run Conway's game of life for a user-set number of generations. The Updater class is where I put the Platform.runlater code, although this came about after trying several different ideas to get it to work so may be a part of the problem.
The objective is for the Conway class to update the grid to reflect the current generation's state
Start State:
[![GUI Start State][2]][2]
End State:
[![GUI End State][1]][1]
I've been reading that the JavaFX application thread should be pretty much left alone and so in trying to follow that logic created the Conway class in a new thread when the start simulation button is pressed. (Also ignore what is in the X and Y Textfields)
startSimulationButton.setOnAction(new EventHandler<ActionEvent>()
{
public void handle(ActionEvent event)
{
if(validateTextBox(genTextField))
{
int[][] universe = convertTo2DArray(simulationPane);
int generations = Integer.parseInt(genTextField.getText());
Runnable simStart = new ConwayLife(universe, generations, window);
new Thread(simStart).start();
}
else
{
instructionLabel.setText("Didn't put numbers in ya silly billy");
}
}
});
Here is the class that will be called to update the grid either from within the GUIbuiler object or from an updater object.
public void updateSimulationPane(int[][] universe, int xUpdate, int yUpdate) throws InterruptedException
{
clearSimulationGrid(simulationPane);
int[] flatUniverse = Stream.of(universe).flatMapToInt(IntStream::of).toArray();
for(int counter = 0; counter < (xUpdate*yUpdate); counter++)
{
Rectangle rect = new Rectangle(0, 0, squareDimensions, squareDimensions);
if(flatUniverse[counter] == 1)
{
rect.setFill(Color.DARKGREEN);
}
else
{
rect.setFill(Color.GREY);
}
simulationPane.add(rect, (counter) % xInput, (counter) / xInput);
}
double leftAnchor = (xWindow / 2) - ((squareDimensions * xInput) / 2);
gridAnchorPane.setTopAnchor(simulationPane, 100d);
gridAnchorPane.setLeftAnchor(simulationPane, leftAnchor);
//Thread.sleep(5000);
}
Here is the start of the Conway class and thread, I'm not too confident using threads so I'm including this in case I've messed something up here.
private final int[][] universe;
private final int generations;
private final GUIBuilder window;
public ConwayLife(int[][] universePassed, int generationsPassed, GUIBuilder windowPassed)
{
universe = universePassed;
generations = generationsPassed;
window = windowPassed;
}
public void run()
{
try
{
getGeneration(universe, generations, window);
}
catch(Exception e)
{
System.out.println("Exception caught");
}
}
public static void getGeneration(int[][] cells, int generations, GUIBuilder window) throws InterruptedException
{
int [][] oldGeneration = cells; //new array to not alter original dataset
int [][] newGeneration = new int[oldGeneration.length + 2][oldGeneration[0].length + 2]; //next generation will always have more columns and rows to simulate infinite universe
int deadCells = 0; //counting the deadcells in the dataset to stop redundant generations
int i = 0;
int j = 0;
int bounds = 2;
boolean boundsChange = false;
Next here is where is where the generation has finished, is going to update the GUI then loop to apply the algorithim to the new generation.
Updater changer = new Updater(window, trim(oldGeneration), oldGeneration.length, oldGeneration[0].length);
changer.run();
oldGeneration = newGeneration; //make the new generation the old generation to show passing of a generation
newGeneration = new int[oldGeneration.length + bounds][oldGeneration[0].length + bounds]; //em
}
Finally here is the updater class where the method call back to the GUIBuilder object is located.
public class Updater implements Runnable
{
private final GUIBuilder window;
private final int[][] unvierse;
private final int xAxis;
private final int yAxis;
public Updater(GUIBuilder passedWindow, int[][] passed2DArray,int passedXAxis,int passedYAxis)
{
this.window = passedWindow;
this.unvierse = passed2DArray;
this.xAxis = passedXAxis;
this.yAxis = passedYAxis;
}
public void run()
{
Platform.runLater(new Runnable()
{
@Override
public void run()
{
try
{
window.updateSimulationPane(unvierse, xAxis, yAxis);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
}
This is the error message I get:
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at moduleInfo/ConwayGUIOfLife.GUIBuilder.clearSimulationGrid(GUIBuilder.java:174)
at moduleInfo/ConwayGUIOfLife.GUIBuilder.updateSimulationPane(GUIBuilder.java:216)
at moduleInfo/ConwayGUIOfLife.Updater$1.run(Updater.java:29)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:389)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
at java.base/java.lang.Thread.run(Thread.java:835)
EDIT 1: Here is the clearSimulationPane method. Sorry, it was just a single line so thought it must be something else causing it:
private void clearSimulationGrid(GridPane simulationPane)
{
simulationPane.getChildren().clear();
}
Edit 2: Okay, I see the problem where the GUIBuilder window object has been getting passed between classes by value rather than by reference as I'd been assuming I'm pretty sure but I still don't know how to fix it at this point.
[1]: https://i.stack.imgur.com/v9KkO.png
[2]: https://i.stack.imgur.com/ah5NS.png
</details>
专注分享java语言的经验与见解,让所有开发者获益!
评论