Что не так с этим пользовательским компонентом JavaFX/FXML?

Я учусь писать пользовательские компоненты FXML для использования с JavaFX 8 и Scene Builder.

Я написал файл FXML, показанный ниже, но Scene Builder не открывает его, выдавая мне сообщение "Операция открытия не удалась" из-за исключения:

java.io.IOException: javafx.fxml.LoadException: mycustomcomponent.TicoTeco is not a valid type.
/C:/Users/xxxxx/Documents/NetBeansProjects/MyCustomComponent/src/mycustomcomponent/TicoTeco.fxml:9
    at com.oracle.javafx.scenebuilder.kit.fxom.FXOMLoader.load(FXOMLoader.java:92)
    at com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument.(FXOMDocument.java:80)
    at com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument.(FXOMDocument.java:95)
...

Почему я получаю это исключение?

Вот файл FXML:

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<fx:root type="mycustomcomponent.TicoTeco" prefHeight="93.0" prefWidth="304.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <BorderPane layoutX="61.0" prefHeight="115.0" prefWidth="200.0">
         <left>
            <Button fx:id="tico" mnemonicParsing="false" text="Tico" BorderPane.alignment="CENTER" />
         </left>
         <right>
            <Button fx:id="teco" mnemonicParsing="false" text="Teco" BorderPane.alignment="CENTER" />
         </right>
      </BorderPane>
   </children>
</fx:root>

А вот файлы Java для TicoTeco.java и Main.java:

package mycustomcomponent;

import java.io.IOException;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;

public class TicoTeco extends AnchorPane {

    @FXML
    Button tico;

    @FXML
    Button teco;

    public TicoTeco() throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(TicoTeco.class.getResource("TicoTeco.fxml"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);
        fxmlLoader.load();
    }

    @FXML
    public void initialize() {
        final EventHandler<ActionEvent> onAction = 
                event -> System.out.println("Hi, I'm " + (event.getSource() == tico? "Tico" : "Teco") + "!");
        tico.setOnAction(onAction);
        teco.setOnAction(onAction);
    }
}
package mycustomcomponent;

import java.io.IOException;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws IOException {
        Scene scene = new Scene(new TicoTeco());

        primaryStage.setTitle("Here are Tico and Teco!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

}

person user118967    schedule 17.06.2015    source источник
comment
Можете ли вы попробовать добавить оператор импорта ‹?import mycustomcomponent.*?› в fxml? Ваш код отлично работает в eclipse. Возможно, дело в NetBeans, поскольку он получает fxml из папки src, а не из папки развертывания?   -  person Roland    schedule 17.06.2015
comment
Вы импортировали файл jar, содержащий пользовательский компонент, в sceneBuilder?   -  person griFlo    schedule 17.06.2015
comment
@Роланд. Спасибо. На самом деле оператор импорта не нужен, потому что я использовал полное имя. Код также работает с NetBeans, но не работает с Scene Builder. Для этого атрибут type должен быть AnchorPane, а пользовательский компонент необходимо импортировать в Scene Builder (что странно, поскольку я его разрабатываю).   -  person user118967    schedule 18.06.2015
comment
@griFlo: спасибо. Я этого не делал. Кажется нелогичным импортировать пользовательский компонент в другой для его разработки! :-) Но, увы, так оно и есть.   -  person user118967    schedule 18.06.2015


Ответы (1)


Это немного сложно. Итак, ваш fxml имеет небольшую ошибку:

Ваш пользовательский класс расширяет AnchorPane, так что это должно быть корнем в вашем fxml:

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<fx:root type="AnchorPane" prefHeight="93.0" prefWidth="304.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <BorderPane layoutX="61.0" prefHeight="115.0" prefWidth="200.0">
         <left>
            <Button fx:id="tico" mnemonicParsing="false" text="Tico" BorderPane.alignment="CENTER" />
         </left>
         <right>
            <Button fx:id="teco" mnemonicParsing="false" text="Teco" BorderPane.alignment="CENTER" />
         </right>
      </BorderPane>
   </children>
</fx:root>

После этого вам нужно сделать из него банку, потому что у вас есть fxml и класс java. Это сложная часть в Netbeans, так что продолжайте:

Во-первых: создайте собственный проект Library для компонента, который выглядит следующим образом со скопированными исходными файлами:

введите здесь описание изображения

Второе: удалите скопированный основной файл (где находится основной метод).

Третье: выполните "очистку и сборку" в проекте. Сгенерированный файл .jar будет находиться в подпапке «dist» в каталоге вашего проекта.

Четвертое: откройте Scene Builder и импортируйте файл CustomComponent .jar следующим образом:

введите здесь описание изображения

введите здесь описание изображения

Теперь вы можете использовать компонент по своему усмотрению. Но имейте в виду, что изменения в компоненте не обновляют импортированную банку динамически, вам придется делать все заново.

person aw-think    schedule 17.06.2015
comment
Спасибо. На самом деле FXML работает, даже если тип mycustomcomponent.TicoTeco, но Scene Builder не будет импортировать его таким образом. Означает ли это, что SB импортирует только элементы, полученные из стандартных элементов управления JavaFX, таких как AnchorPane? Это разочаровывает, поскольку означает, что пользовательские компоненты на самом деле не рассматриваются как стандартные. Например, тогда нельзя определить пользовательский компонент из другого пользовательского компонента? - person user118967; 18.06.2015
comment
Итак, это парадигма программирования, вы всегда должны использовать супертипы, где это возможно, или интерфейсы. Таким образом, даже под-подкомпонент является производным от AnchorPane. По-настоящему настоящий компонент выглядит иначе, посмотрите исходники JavaFX, там гораздо больше, чем просто класс AnchorPane. - person aw-think; 18.06.2015