JavaFX 自定义控件<T> : Is this possible?

标签 java generics custom-controls javafx-8 scenebuilder

我想在 JavaFX 中创建一个简单的可重用自定义控件,它只不过是 ComboBox头上有一个标签,可以设置文本。

我希望它可以在 JavaFX Scene Builder 中使用。

我还希望它能够采用单个通用参数 <T>能够尽可能模仿标准的行为 ComboBox这是可用的。

我遇到的问题是,当我尝试将控制 Controller 设置为Controller<T>时在 SceneBuilder 中,我收到一条错误消息:Controller<T> is invalid for Controller class .

当您调用 FXMLLoader.load() 时,这是有道理的(在设置根、类加载器和位置之后),(我可以找到)没有办法告诉加载器“哦,这是一个自定义控件。”

这是我的控件代码:

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*/
}

这是 Controller 类:

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 类本身无法使用通用参数创建(即:public class Controller<T>{...} = 不好) 这有点烦人,但我想这是有道理的。

那么如何将通用参数应用于自定义控件 Controller 内的方法,并使控件本身(而不是 Controller )成为通用的:就像这样?

控制:

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

Controller :

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);
    }
}

这行得通吗?这是否更符合正确的轨道?再说一次,我的愿望只不过是一个带有标签的组合框,告诉用户它的全部内容。

最佳答案

这对我有用,当我将库导入到 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>

Controller :

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);
    }
}

关于JavaFX 自定义控件<T> : Is this possible?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26206632/

相关文章:

java - R2dbc 无法使用 tcp 处理 h2 数据库

java - 视觉对象编辑器

c# - 自定义用户控件在 WinForm 表单上托管时会自动调整大小

ios - 如何创建像 HBO GO iPad 应用程序那样的多向无限/循环 ScrollView

android - 具有不同状态图像的自定义 switchcompat

java - 如何使用 EL 将字段绑定(bind)到重复的数据源?

java - 将 Java 转换为 Objective-c(Android 到 iOS)

c# - 避免使用 if-else 或 switch-case 语句来进行决定

java - Wicket 中的 Form<Void> (或一般使用 Void 类型)

java - 没有参数的参数化类?