JavaFX - Как исправить 'fx: контроллер может быть применен только к корневому элементу' - PullRequest
0 голосов
/ 26 декабря 2018

Я создал простое приложение без полей с JavaFX.Теперь я создал ярлык, который должен заменить отсутствующую кнопку «Закрыть».На самом деле, моя попытка не работает, потому что я не могу взаимодействовать с FXMLController.

Я уже пытался добавить FXMLController в мой файл * .fxml.Я попытался вставить контроллер в VBox и AnchorPane, но оба они не работают, и я не получаю его.

LabelCloseTest.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>

<VBox prefHeight="720.0" prefWidth="1025.0" 
    xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1">
      <children>
        <AnchorPane maxHeight="-1.0" maxWidth="-1.0" prefHeight="-1.0" prefWidth="-1.0" style="-fx-background-color: #303136;" VBox.vgrow="ALWAYS" fx:controller="com.mycompany.testing.FXMLDocumentController">
         <children>
            <Label fx:id="labelTest" layoutX="1007.0" layoutY="5.0" prefHeight="10.0" prefWidth="5.0" text="X" textFill="WHITE">
               <font>
                  <Font name="System Bold" size="13.0" />
               </font>
            </Label>
         </children>
    </AnchorPane>
  </children>
</VBox>

FXMLDocumentController.java

package com.mycompany.testing;

import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;

public class FXMLDocumentController {

@FXML
private Label labelTest;

@FXML
public void closeLabelPressed() {

       labelTest.setOnMouseClicked(new EventHandler<MouseEvent>() {

        public void handle(MouseEvent event) {
            System.exit(0);

             }
        });
    }
}

LabelTest.java

package com.mycompany.testing;


import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

public class LabelTest extends Application {

    public static void main (String[] args) {

        launch(args);

    }

    @Override
    public void start(Stage primaryStage) throws Exception {


       primaryStage.initStyle(StageStyle.UNDECORATED);
       FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/LabelCloseTest.fxml"));
       Parent root = loader.load();

       FXMLDocumentController fdc = new FXMLDocumentController();

       fdc.closeLabelPressed();


       Scene scene = new Scene(root);

       primaryStage.setScene(scene);
       primaryStage.show();
    }

}

Stacktrace

Exception in Application start method
Exception in thread "main" java.lang.RuntimeException: Exception in Application start method
    at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:900)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: javafx.fxml.LoadException: fx:controller can only be applied to root element.
/D:/Workspace/SocketClient/target/classes/fxml/LabelCloseTest.fxml:10

    at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2621)
    at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:917)
    at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:980)
    at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:227)
    at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:752)
    at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2722)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2552)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2466)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2435)
    at com.mycompany.testing.LabelTest.start(LabelTest.java:25)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:846)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:455)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
    ... 1 more

Я ожидаю, что приложение запустится, и когда я нажму метку «X», приложение закроется.

Быстрое напоминание: я новичок в JavaFX, поэтому я также был бы благодарен за каждую рекомендацию учебника по JavaFX.

1 Ответ

0 голосов
/ 26 декабря 2018

Я пытался вставить контроллер в VBox и AnchorPane, но оба они не работают, и я действительно не понимаю.

Вы упоминаете, пытаясь поставить fx:controllerатрибут как для элемента VBox, так и для элемента AnchorPane.Код, который вы показываете, является вашей попыткой добавить его к элементу AnchorPane.Запуск вашего кода на моем компьютере в результате LoadException вы наблюдаете.Эта проблема решается путем помещения атрибута fx:controller в элемент VBox (корневой элемент ):

<VBox prefHeight="720.0" prefWidth="1025.0" 
      xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" 
      fx:controller="com.mycompany.testing.FXMLDocumentController">
    <!--- Rest of FXML file omitted for brevity -->

Примечание : Изначально я заметил другую проблему, когда вы не закрывали элементы <VBox> или <children>.Тем не менее, эти закрывающие теги были там все время;они просто не рендерится в вашем вопросе, , как указал Фабиан Я забыл проверить это.


Однако , это приводит к другому исключению.

// Beginning of stack trace omitted for brevity...
Caused by: java.lang.NullPointerException
        at com.mycompany.testing.FXMLDocumentController.closeLabelPressed(FXMLDocumentController.java:16)
        at com.mycompany.testing.LabelTest.start(LabelTest.java:28)
        at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:846)
        at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:455)
        at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
        at java.base/java.security.AccessController.doPrivileged(Native Method)
        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)
        ... 1 more

Этопроблема в конечном итоге вызвана этим кодом в LabelTest#start:

FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/LabelCloseTest.fxml"));
Parent root = loader.load();

FXMLDocumentController fdc = new FXMLDocumentController(); // the real problem

fdc.closeLabelPressed(); // accesses the labelTest field which is null

Поскольку вы используете fx:controller, FXMLLoader создаст свой собственный экземпляр FXMLDocumentController.Именно в этот случай вводятся аннотированные поля @FXML.С другой стороны, вы создаете свой собственный экземпляр, в который в не будут введены поля.Вы должны использовать экземпляр FXMLDocumentController, созданный FXMLLoader.

FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/LabelCloseTest.fxml"));
Parent root = loader.load();

// gets instance from FXMLLoader, must be called after load()
FXMLDocumentController fdc = loader.getController(); 

fdc.closeLabelPressed();

Однако , в то время как ваш код (с исправлениями до сих пор) будет делать то, что вам нужноне правильный способ сделать это.Метод closeLabelPressed выглядит так, как будто вы намереваетесь использовать его как обработчик событий, поскольку вы пометили его @FXML.Однако вы не связываете этот метод с файлом FXML, а вместо этого метод добавляет EventHandler к Label.Настройте правильно, вам вообще не следует звонить closeLabelPressed из LabelTest.

Первое, что вы должны сделать, это связать метод с Label через файл FXML.

<!-- previous omitted for brevity -->

<Label fx:id="labelTest" layoutX="1007.0" layoutY="5.0" prefHeight="10.0" prefWidth="5.0" text="X"
       textFill="WHITE" onMousePressed="#closeLabelPressed">

<!-- rest omitted for brevity -->

Обратите внимание на атрибут onMousePressed .

Второе, что нужно сделать, это изменить метод closeLabelPressed в FXMLDocumentController.

package com.mycompany.testing;

import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;


public class FXMLDocumentController {

    @FXML
    private Label labelTest;

    @FXML
    private void closeLabelPressed(MouseEvent event) {
        Platform.exit();
    }
}

Теперь метод вызывается при нажатии Label.Внутренне, FXMLLoader устанавливает свойство onMousePressed (из labelTest) в EventHandler, которое вызывает closeLabelPressed.Я также изменил использование System.exit(0) на Platform.exit().Последнее позволит корректно завершить работу среды JavaFX (что позволит вам выполнить очистку в таких местах, как Application#stop), в то время как первое немедленно завершит работу JVM (и не вернется нормально). Примечание: впрыск Label может больше не понадобиться .

Третье, что нужно сделать, это удалить вызов closeLabelPressed в методе LabelTest.start.

После этих изменений ваш код должен работать должным образом.


Что касается запроса учебных пособий (что не по теме переполнения стека, см. Справочный центр ), которое вы, вероятно, можетенайти много с помощью поиска Google.Официальные источники см. В разделах JavaFX: Начало работы с JavaFX (последнее обновление для JavaFX 8) и Введение в FXML .Здесь также есть много вопросов , связанных с FXML, здесь о переполнении стека.

...