Я понимаю, что в названии есть почти одинаковый вопрос. Вопрос, похоже, не имеет отношения к моим конкретным вопросам.
Я использую построитель сценариев JavaFX для создания моего пользовательского интерфейса (включая 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);
}
}
Линия FXML, которая определяет 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)
Примечание. Это действительно полностью описано в разделе "Передача параметров 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
-адрес ресурса FXMLLoader
конструктор FXMLLoader
или вызвать setLocation(...)
на FXMLLoader
. Затем, чтобы загрузить файл FXML, просто вызовите метод load()
без аргументов на FXMLLoader
. Как только это будет завершено, вы сможете получить доступ к контроллеру, вызвав getController()
в экземпляре FXMLLoader
.
Другие проблемы в вашем коде
Вы не можете обновить пользовательский интерфейс из фонового потока: вы должны обновить его из приложения JavaFX Application Thread. Как бы то ни было (если вы исправите проблему NullPointerException
), ваш код будет генерировать IllegalArgumentException
в Java 8. В JavaFX 2.2 и ранее вам придется жить с возможностью появления ошибок в будущем. Вы можете запланировать код, который будет выполняться в потоке приложения FX, путем упаковки этого кода при вызове Platform.runLater()
.
Не совсем так, но плохой дизайн - это то, что вы подвергаете элементы пользовательского интерфейса (TextArea
) вне пары FXML-контроллеров. Это становится реальной проблемой, когда ваш босс приходит в ваш офис и говорит, что он больше не хочет сообщений, отображаемых в 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);
}
}
connection
илиconnection.reader
?