I want to create a simple reusable custom control in JavaFX that is nothing more than a ComboBox
with a label over its head that can have the text set.
I would like for it to be usable in JavaFX Scene Builder.
I would also like for it to be able to take a single Generic Parameter <T>
to be able to as closely as possible emulate the behavior of the standard ComboBox
that is available.
The problem which I am encountering is that when I attempt to set the Controls Controller to Controller<T>
in SceneBuilder, I get an error telling me: Controller<T> is invalid for Controller class
.
This makes sense as when you call FXMLLoader.load()
(after setting the root, classLoader, and Location), there is no way (that I can find) to tell the loader "Oh, and this is a CustomControl."
This is the code I have for the 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*/
}
This is the Controller class:
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*/
}
Clearly there is something that I am missing here. I am really hoping this is possible without having to come up with my own implementation of the FXMLLoader Class (REALLY, REALLY, REALLY REALLY hoping).
Can someone please tell me what I am missing, or if this is even possible?
EDIT 1:
After someone pointed me to a link I may have an idea of how to do this but I'm still not one hundred percent. To me it feels like the Controller class itself can not be created with a generic parameter (I.E.: public class Controller<T>{...}
= No Good)
That's kind of annoying but I guess makes sense.
Then what about applying Generic parameters to the Methods inside the custom control controller, and making the control itself (not the controller) a generic: like so?
Control:
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);
}
}
Would that work? Is that more along the right track? Again, my desire is nothing more than a ComboBox with a Label on it to tell users what its all about.