我想在JavaFX中创建一个简单的可重用自定义控件,它只不过是一个ComboBox
,其头部有一个可以设置文本的标签。
我希望它可以在JavaFX Scene Builder中使用。
我还希望它能够采用单个通用参数<T>
,以尽可能接近地模拟可用的标准ComboBox
的行为。
我遇到的问题是当我尝试在SceneBuilder中将控件控制器设置为Controller<T>
时,我收到错误消息告诉我:Controller<T> is invalid for Controller class
。
这有意义,因为当你调用FXMLLoader.load()
时(在设置root,classLoader和Location之后),没有办法(我能找到)告诉加载器“哦,这是一个CustomControl。 “
这是我对控件的代码:
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类的实现(真的,真的,真的非常希望)。
有人可以告诉我我错过了什么,或者这是否可能?
有人向我指出一个链接,我可能知道如何做到这一点,但我仍然不是百分之百。对我来说,感觉就像无法使用通用参数创建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,告诉用户它的全部内容。
答案 0 :(得分:1)
这对我有用,当我将这个库导入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);
}
}
答案 1 :(得分:0)
我确信这种结构是不可能的,因为在运行时评估FXML。并且在运行时已经删除了泛型。
但是,可以做的是为控制器分配通用。
FXML实现了模型 - 视图 - 控制器(MVC)设计,该设计遵循以下主题:
What is MVC (Model View Controller)?
您的问题也是以下主题中的问题: