JavaFX CustomControl ‹T›: Возможно ли это?

Я хочу создать простой настраиваемый элемент управления многократного использования в JavaFX, который представляет собой не что иное, как ComboBox с меткой над головой, в которой может быть установлен текст.

Я бы хотел, чтобы его можно было использовать в JavaFX Scene Builder.

Я также хотел бы, чтобы он мог использовать один общий параметр <T>, чтобы иметь возможность как можно точнее имитировать поведение доступного стандартного ComboBox.

Проблема, с которой я сталкиваюсь, заключается в том, что когда я пытаюсь установить Controls Controller на Controller<T> в SceneBuilder, я получаю сообщение об ошибке: Controller<T> is invalid for Controller class.

Это имеет смысл, поскольку когда вы вызываете FXMLLoader.load() (после установки root, classLoader и Location), нет способа (который я могу найти) сказать загрузчику: «О, это CustomControl».

Это код, который у меня есть для Control:

public class LabeledComboBox<T> extends VBox {
    private final LCBController<T> Controller;
    public LabeledComboBox(){
        this.Controller = this.Load();
    }

    private LCBController Load(){
        final FXMLLoader loader = new FXMLLoader();
        loader.setRoot(this);
        loader.setClassLoader(this.getClass().getClassLoader());
        loader.setLocation(this.getClass().getResource("LabeledComboBox.fxml"));
        try{
            final Object root = loader.load();
            assert root == this;
        } catch (IOException ex){
            throw new IllegalStateException(ex);
        }

        final LCBController ctrlr = loader.getController();
        assert ctrlr != null;
        return ctrlr;
    }
    /*Methods*/
}

Это класс контроллера:

public class LCBController<T> implements Initializable {
    //<editor-fold defaultstate="collapsed" desc="Variables">
    @FXML private ResourceBundle resources;

    @FXML private URL location;

    @FXML private Label lbl; // Value injected by FXMLLoader

    @FXML private ComboBox<T> cbx; // Value injected by FXMLLoader
    //</editor-fold>
    //<editor-fold defaultstate="collapsed" desc="Initialization">
    @Override public void initialize(URL fxmlFileLocation, ResourceBundle resources) {
        this.location = fxmlFileLocation;
        this.resources = resources;
    //<editor-fold defaultstate="collapsed" desc="Assertions" defaultstate="collapsed">
        assert lbl != null : "fx:id=\"lbl\" was not injected: check your FXML file 'LabeledComboBox.fxml'.";
        assert cbx != null : "fx:id=\"cbx\" was not injected: check your FXML file 'LabeledComboBox.fxml'.";
    //</editor-fold>
    }
    //</editor-fold>
    /*Methods*/
}

Ясно, что есть кое-что, чего мне здесь не хватает. Я действительно надеюсь, что это возможно без необходимости придумывать свою собственную реализацию класса FXMLLoader (ДЕЙСТВИТЕЛЬНО, ДЕЙСТВИТЕЛЬНО, ДЕЙСТВИТЕЛЬНО надеясь).

Может ли кто-нибудь сказать мне, что мне не хватает, или возможно ли это?

РЕДАКТИРОВАТЬ 1:

После того, как кто-то указал мне на ссылку, я могу понять, как это сделать, но я все еще не на сто процентов. Мне кажется, что сам класс Controller не может быть создан с помощью универсального параметра (I.E .: public class Controller<T>{...} = No Good) Это немного раздражает, но, думаю, имеет смысл.

Тогда как насчет применения общих параметров к методам внутри настраиваемого контроллера управления и превращения самого элемента управления (а не контроллера) в универсальный: вот так?

Контроль:

public class LabeledComboBox<T> extends VBox {...}

Контроллер:

public class LCBController implements Initializable {
    /*Stuff...*/

    /**
     * Set the ComboBox selected value.
     * @param <T> 
     * @param Value
     */
    public <T> void setValue(T Value){
        this.cbx.setValue(Value);
    }

    /**
     * Adds a single item of type T to the ComboBox.
     * @param <T> ComboBox Type
     * @param Item
     */
    public <T> void Add(T Item){
        this.cbx.getItems().add(Item);
    }

    /**
     * Adds a list of items of type T to the ComboBox.
     * @param <T> ComboBox Type
     * @param Items
     */
    public <T> void Add(ObservableList<T> Items){
        this.cbx.getItems().addAll(Items);
    }

    /**
     * Removes an item of type T from the ComboBox.
     * @param <T> ComboBox Type
     * @param Item
     * @return True if successful(?)
     */
    public <T> boolean Remove(T Item){
        return this.cbx.getItems().remove(Item);
    }
}

Это сработает? Это больше в правильном направлении? Опять же, мое желание - не что иное, как ComboBox с меткой на нем, чтобы рассказать пользователям, что это такое.


person Will    schedule 05.10.2014    source источник
comment
Я думаю, вы сможете это сделать, если установите контроллер на FXMLLoader в коде Java вместо использования атрибута fx:controller в FXML.   -  person James_D    schedule 06.10.2014
comment
На самом деле, похоже, отлично работает определение контроллера в FXML ...   -  person James_D    schedule 06.10.2014


Ответы (2)


У меня это сработало, и когда я импортировал библиотеку в SceneBuilder, она работала нормально:

(Очень простой) FXML:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ComboBox?>

<fx:root xmlns:fx="http://javafx.com/fxml/1" type="VBox"
    fx:controller="application.LabeledComboBoxController">
    <Label fx:id="label" />
    <ComboBox fx:id="comboBox" />
</fx:root>

Контроллер:

package application;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.SingleSelectionModel;

public class LabeledComboBoxController<T> {
    @FXML
    private Label label ;
    @FXML
    private ComboBox<T> comboBox ;

    public void setText(String text) {
        label.setText(text);
    }
    public String getText() {
        return label.getText();
    }
    public StringProperty textProperty() {
        return label.textProperty();
    }

    public ObservableList<T> getItems() {
        return comboBox.getItems();
    }
    public void setItems(ObservableList<T> items) {
        comboBox.setItems(items);
    }

    public boolean isWrapText() {
        return label.isWrapText();
    }

    public void setWrapText(boolean wrapText) {
        label.setWrapText(wrapText);
    }

    public BooleanProperty wrapTextProperty() {
        return label.wrapTextProperty();
    }

    public SingleSelectionModel<T> getSelectionModel() {
        return comboBox.getSelectionModel();
    }
}

Контроль:

package application;

import java.util.logging.Level;
import java.util.logging.Logger;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.layout.VBox;

public class LabeledComboBox<T> extends VBox {

    private final LabeledComboBoxController<T> controller ;

    public LabeledComboBox(ObservableList<T> items, String text) {
        controller = load();
        if (controller != null) {
            setText(text);
            setItems(items);
        }
    }

    public LabeledComboBox(ObservableList<T> items) {
        this(items, "");
    }

    public LabeledComboBox(String text) {
        this(FXCollections.observableArrayList(), text);
    }

    public LabeledComboBox() {
        this("");
    }

    private LabeledComboBoxController<T> load() {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource(
                    "LabeledComboBox.fxml"));
            loader.setRoot(this);
            loader.load();
            return loader.getController() ;
        } catch (Exception exc) {
            Logger.getLogger("LabeledComboBox").log(Level.SEVERE,
                    "Exception occurred instantiating LabeledComboBox", exc);
            return null ;
        }
    }

    // Expose properties, but just delegate to controller to manage them 
    // (by delegating in turn to the underlying controls):

    public void setText(String text) {
        controller.setText(text);
    }
    public String getText() {
        return controller.getText();
    }
    public StringProperty textProperty() {
        return controller.textProperty();
    }

    public boolean isWrapText() {
        return controller.isWrapText();
    }

    public void setWrapText(boolean wrapText) {
        controller.setWrapText(wrapText);
    }

    public BooleanProperty wrapTextProperty() {
        return controller.wrapTextProperty();
    }

    public ObservableList<T> getItems() {
        return controller.getItems();
    }
    public void setItems(ObservableList<T> items) {
        controller.setItems(items);
    }
    public SingleSelectionModel<T> getSelectionModel() {
        return controller.getSelectionModel();
    }
}

Код теста:

package application;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;


public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            BorderPane root = new BorderPane();
            Scene scene = new Scene(root,400,400);
            LabeledComboBox<String> comboBox = new LabeledComboBox<String>(
                    FXCollections.observableArrayList("One", "Two", "Three"), "Test");
            root.setTop(comboBox);
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
person James_D    schedule 06.10.2014
comment
Таким образом, вы МОЖЕТЕ установить его в FXML, SceneBuilder будет ошибочно кричать на вас. Хорошо знать. Спасибо за помощь. - person Will; 06.10.2014
comment
AFAIK, это очень необычно! Обычно с конструкциями fx: root (что я очень рекомендую) элемент управления также становится контроллером. У вас не два отдельных класса, а только один. - person Puce; 07.10.2014
comment
Да, согласен. И это показывает, что в коде слишком много разводки. Я просто хотел показать, что вы можете ссылаться на контроллер с параметризацией типа из FXML в контексте, в котором OP поставил вопрос. - person James_D; 07.10.2014

Я уверен, что такая конструкция невозможна, поскольку FXML оценивается во время выполнения. И дженерики уже удаляются во время выполнения.

Но что можно сделать, так это назначить универсальный контроллер контроллеру.

FXML реализует дизайн Model-View-Controller (MVC), который рассматривается в следующей теме:

Что такое MVC (контроллер представления модели)?


Ваш вопрос также является проблемой в следующей теме:

Установка общего типа TableView из FXML

person sxleixer    schedule 05.10.2014
comment
Хорошо, тогда я понимаю, что я бы установил общий тип не в контроллере, а в самом объекте (первый бит кода правильный, второй - нет). Проблема в том, что у меня есть несколько методов в контроллере, которые непосредственно в ComboBox (а сам ComboBox объявлен как ComboBox ‹T› (то же самое, что и в Controller). Итак, как мне передать ‹T› объекта вплоть до контроллера? Или мне даже не нужно об этом беспокоиться? - person Will; 06.10.2014
comment
Вы можете подумать не с той стороны! Чего вы на самом деле хотите достичь, поступая таким образом? - person sxleixer; 06.10.2014
comment
Я хочу ComboBox с меткой. Просто как тот. Я хочу, чтобы он как можно точнее имитировал настоящий combobox, только с меткой, которая может сообщить пользователю, что находится в ComboBox. Нравится [Имя пользователя] ‹- Ярлык там [ComboBox ‹String›]‹ --- ComboBox со списком имен пользователей Чтобы я мог создать настраиваемый элемент управления с этим ярлыком и ComboBox ‹String› Более понятно? - person Will; 06.10.2014
comment
Если вы хотите сделать это по-своему, а не так, как того требует JavaFX. Вам нужно будет использовать java.reflect и заставить JavaFX делать это. На самом деле вы работаете против JavaFX вместо того, чтобы работать вместе. Такое ощущение, что сделать это так, как тебе удобно, невозможно. - person sxleixer; 06.10.2014
comment
Я смог понять это. Спасибо, в любом случае. - person Will; 06.10.2014
comment
Это просто сработало для меня (хотя я не пытался заставить SceneBuilder понять это). Это вообще не нуждалось в размышлениях. Дайте мне знать, если вам кажется, что вы делаете что-то сложное, и я опубликую свое решение. - person James_D; 06.10.2014
comment
Ничего сложного. Я действительно хочу, чтобы его распознал Scene Builder. Я просто убрал ‹T› из Контроллера (на данный момент), а позже я попробую то, что вы предложили, определив Контроллер в коде, а не в FXML. - person Will; 06.10.2014