我意识到标题中有一个几乎相同的问题。这个问题似乎与我的具体问题无关。
我正在使用JavaFX场景构建器来创建我的UI(包括所讨论的TextArea)。我想要做的就是从服务器获取消息并将其发布到文本区域。通过各种println
陈述,我将问题范围缩小到了这一点。我尝试了各种解决方案(数小时);来到这里是最后的选择。
我能想到的唯一其他可能的原因是多线程会出现问题,但甚至可以开始考虑什么。
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控制器类(仅限相关行):
@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);
}
}
定义chatBox
:
<TextArea id="ChatBox" fx:id="chatBox" focusTraversable="false" mouseTransparent="true" prefHeight="400.0" prefWidth="200.0" promptText="Chat:" wrapText="true" BorderPane.alignment="CENTER">
由此产生的异常:
Exception in thread "Thread-4" java.lang.NullPointerException
at javachat.IncomingReader.run(IncomingReader.java:28)
at java.lang.Thread.run(Thread.java:745)
答案 0 :(得分:4)
注意:Passing Parameters JavaFX FXML完全涵盖了这一点。但是,您发布的代码远没有正确构建以使用那里的方法,您可能需要专门为您的示例查看它。我强烈建议你仔细阅读那篇文章并理解它。
为什么您会看到NullPointerException
:
FXMLDocumentController.controller
指的是FXMLDocumentController
的实例,它与FXMLLoader
为您创建的实例不同。因此,chatBox
中的FXMLDocumentController.controller
字段未由FXMLLoader
初始化。
FXMLLoader
的作用:
当您调用其中一个FXMLLoader.load(...)
方法时,它基本上会执行以下操作:
fx:controller
属性,并且没有通过其他方式设置控制器,则会创建该属性中指定的类的实例fx:id
属性,并且控制器与FXMLLoader
关联,则它使用相应的对象初始化控制器的@FXML
带注释的字段加载FXML后如何访问控制器
要访问控制器,您必须创建FXMLLoader
实例,而不是依赖(邪恶)静态FXMLLoader.load(URL)
方法。您可以将URL
FXML资源传递到FXMLLoader
构造函数,也可以在setLocation(...)
上调用FXMLLoader
。然后,要加载FXML文件,只需在load()
上调用无参数 FXMLLoader
方法。完成后,您可以通过调用getController()
实例上的FXMLLoader
来访问控制器。
您的代码中的其他问题
您无法从后台线程更新UI:您必须从JavaFX应用程序线程更新它。就目前而言(如果您修复了NullPointerException
问题),您的代码将在Java 8中抛出IllegalArgumentException
。在JavaFX 2.2及更早版本中,您将不得不忍受在任意时间出现错误的可能性在将来。您可以通过将该代码包含在Platform.runLater()
的调用中来安排在FX应用程序线程上执行的代码。
不是很糟糕,但是设计不好,就是你在你的FXML控制器对之外暴露了UI元素(TextArea
)。当你的老板进入你的办公室并且告诉你他不想再显示TextArea
中显示的消息时,这就成了一个真正的问题,他希望他们在ListView
中。由于公开了TextArea
,您必须遍历所有代码以查找对它的任何引用。更好的方法是在控制器中定义appendMessage(String)
方法并访问它。您甚至可能希望将所有数据分解到单独的模型类中,并将该类的实例引用到控制器和读取器类,但这超出了此问题的范围。
我不会抱怨你过度使用static
东西......;)。
以下是修复此代码的一种方法的框架:
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.
}
}
}
控制器:
public class FXMLDocumentController {
@FXML
private TextArea chatBox ;
public void appendMessage(String message) {
chatBox.appendText(message);
}
// other stuff as before...
}
应用:
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);
}
}