I tried to load an FXML and set the controller with Java code (not with the FXML tag). I have different fields in the FXML. I tried to load (for example textfields, buttons...).
Here is the example:
Tab tab = new Tab();
tab.setText("TesetTabAdd");
tabpane.getTabs().add(tab);
FXMLLoader loader = new FXMLLoader(getClass().getResource("tab.fxml"));
TabController tabCont = new TabController();
tabCont.setName("Sandro");
loader.setController(tabCont);
try {
tab.setContent((Node)loader.load(getClass().getResource("tab.fxml")));
} catch (IOException ex) {
System.out.println(ex);
}
As you can see I create a new Tab, set the text for it, add it to the tabpane, load the fxml and then I create a new controller and set it as controller for the FXML. After this, I tried to set a value in the fxml before initialize so that I can use it in my controller to update a textfield or button.
Here is my controller, I tried to set:
public class TabController implements Initializable {
#FXML private TextField name;
private final StringProperty nameProp = new SimpleStringProperty();
public String getNameProp() {
return nameProp.get();
}
public void setNameProp(String value) {
nameProp.set(value);
}
public StringProperty namePropProperty() {
return nameProp;
}
public void setName(String name){
nameProp.setValue(name);
}
public String getName(){
return nameProp.get();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
name.textProperty().bind(nameProp);
}
}
I tried it with binding, but it doesn't work.
I edit my createTab() method a littel bit. Here i set the controller and then use the setName method. But i the textfield dont display anything. The System.out.println(tabCont.getName()); method prints out "Sandro"!!!
public void createTab(){
try{
Tab tab = new Tab();
tab.setText("TesetTabAdd");
tabpane.getTabs().add(tab);
FXMLLoader loader = new FXMLLoader();
TabController tabCont = new TabController();
loader.setController(tabCont);
tabCont.setName("Sandro");
tab.setContent((Node)loader.load(getClass().getResource("tab.fxml")));
System.out.println(tabCont.getName());
} catch (IOException ex) {
System.out.println(ex);
}
}
1) the FXML file is not loaded until the load() method is invoked.
(as per your comment "... load the fxml and then I create a new controller ..."). So just initiating the FXMLLoader will not load the given fxml file.
2) You are invoking a wrong load method. You should use the load method of instantiated FXMLLoader. But you used static load method of the FXMLLoader class. This static version will ignore the controller class set through setController(). Try:
FXMLLoader loader = new FXMLLoader(getClass().getResource("tab.fxml"));
TabController tabCont = new TabController();
tabCont.setName("Sandro");
loader.setController(tabCont);
try {
tab.setContent((Node) loader.load());
} catch (IOException ex) {
System.out.println(ex);
}
Related
I have looked into using ControllerFactory a lot, to allow this code to be instantiated from a database, and have cross-controller compatibility. But with my original setup different from others I found online, I found it extremely hard to follow along, and use what would fit into my program from theirs. Any advice on where to start?
Current Controller creation -
// get Main Class package name to get correct files path
String pathRef = mainRef.getClass().getPackage().getName();
// set FXRouter current route reference
currentRoute = route;
// create correct file path. "/" doesn't affect any OS
String scenePath = "/" + pathRef + "/" + route.scenePath;
// Creates controller for route
Controller_Factory cf = new Controller_Factory();
Object controller = cf.CreateController(route.scenePath);
FXMLLoader loader = new FXMLLoader(controller.getClass().getResource(scenePath));
loader.setController(controller);
Parent root = loader.load();
// set window title from route settings or default setting
window.setTitle(route.windowTitle);
// set new route scene
window.setScene(new Scene(root, route.sceneWidth, route.sceneHeight));
// show the window
window.show();
}
Controller Example-
public class BuyController extends Controller {
#FXML
public Button CloseAppButton;
#FXML public Button SwitchToProfileButton;
#FXML public Button SwitchToSellButton;
#FXML public Button SwitchToBuyButton;
#FXML public Button SwitchToMainButton;
#FXML public TextField BuyText;
String AmountBought;
public void initialize (URL location, ResourceBundle resources){
CloseAppButton.setPrefHeight(30);
CloseAppButton.setPrefWidth(56);
SwitchToBuyButton.setPrefHeight(30);
SwitchToBuyButton.setPrefWidth(56);
SwitchToMainButton.setPrefHeight(30);
SwitchToMainButton.setPrefWidth(56);
SwitchToSellButton.setPrefHeight(30);
SwitchToSellButton.setPrefWidth(56);
SwitchToProfileButton.setPrefHeight(30);
SwitchToProfileButton.setPrefWidth(56);
}
public void OnBuyButton (ActionEvent event) {
AmountBought = BuyText.getText();
System.out.println("You have bought " + AmountBought + " of crypto");
BuyText.clear();
}
#Override
public void initilize(URL url, ResourceBundle rb) {
}
}
Current Controller_Factory-
public class Controller_Factory {
private static final Controller_Factory instance = new Controller_Factory();
public static Controller_Factory getInstance() {
return instance;
}
public Object CreateController (String routeScenePath) throws IllegalArgumentException, IOException {
Object controller = null;
switch (routeScenePath) {
case "Buy.fxml":
controller = new BuyController();
break;
case "Error.fxml":
controller = new ErrorController();
break;
case "Home.fxml":
controller = new HomeController();
break;
case "Profile.fxml":
controller = new ProfileController();
break;
case "Sell.fxml":
controller = new SellController();
break;
default:
}
System.out.println(routeScenePath);
return controller;
}
}
How would I pass this info with the said controller? (This is not real code I have, but an example of configuration JSON I want to pass with the controller.)
"HomePage": {
"ValidPages": [
"BuyPage",
"SellPage"
],
"InternalID": "HP"
},
"BuyPage": {
"ValidPages": [
"HomePage"
],
"InternalID": "BP",
"Cryptos": [
"BTC",
"LTC"
]
The controller factory is simply a Callback<Class<?>, Object> whose call(Class<?> type) function takes the class defined in the fx:controller attribute in the FXML file and returns the object to be used as the controller. This is invoked by the FXMLLoader at the time the FXML is loaded.
I think your question is asking if you can use a controller factory to automatically populate controllers with data that's stored in JSON, which will be read at runtime.
You can do something like this:
public class NavigationInfo {
private final Map<String, PageNavigationInfo> pageInfoPerPage ;
public NavigationInfo(Map<String, PageNavigationInfo pageInfoPerPage) {
this.pageInfoPerPage = pageInfoPerPage;
}
public PageNavigationInfo getInfoForPage(String page) {
return pageInfoPerPage.get(page);
}
}
public class PageNavigationInfo {
private final String internalID ;
private final List<String> validPages ;
private final List<String> cryptos ;
// .... etc
}
public class NavigationControllerFactory implements Callback<Class<?>, Object> {
private final NavigationInfo navigationInfo ;
public NavigationControllerFactory() {
// read and parse JSON and create NavigationInfo instance
}
#Override
public Object call(Class<?> type) {
try {
for (Constructor<?> c : type.getConstructors()) {
if (c.getParameterCount() == 1 && c.getParameterTypes()[0].equals(NavigationInfo.class)) {
return c.newInstance(navigationInfo);
}
}
// no suitable constructor, just use default constructor as fallabck
return type.getConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Now just define the fx:controller attribute in each FXML in the usual way. E.g. for Buy.fxml do
<BorderPane ... fx:controller="com.yourcompany.yourproject.BuyController">
<!-- ... -->
</BorderPane>
Then
public class BuyController {
private final PageNavigationInfo navInfo ;
public BuyController(NavigationInfo navigationInfo) {
this.navInfo = navigationInfo.getInfoForPage("BuyPage");
}
#FXML
private void initialize() {
// do whatever you need with navInfo
}
}
I am working on a project where there's a lot of windows being opened and closed and would like to create a static class which only takes in a few parameters and then does the rest.
The problem is that "controller" will need to be different types of declaration, depending on what controller is needed. For instance; FXMLControllerAdd or FXMLControllerHome.
I tried to pass the type to the method with a parameter. That did not work, neither did using var as declaration (it's coded in Java11) because then i got a "cannot find symbol"-error for initData() on the next line.
public static void nySide(Class c, String controllerPath, Dataset dataset, String tittel, Window window) {
try {
FXMLLoader loader = new FXMLLoader(c.getResource(controllerPath));
Parent root = (Parent) loader.load();
//THIS IS WHERE TO PROBLEM IS
FXMLControllerAdd controller = loader.getController();
controller.initData(dataset);
//This line gets the Stage information
Stage st = new Stage();
st.setTitle(tittel);
st.setScene(new Scene(root));
st.show();
Stage stage = (Stage) window;
stage.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Also; does it exists another way which requires less parameters?
I figured it out thanks to Slaw. Making an interface E.g (FXMLInitData) and implementing that in every FXMLController.java and declaring controller as that interface did the trick.
Interface:
public interface FXMLInitData {
public void initData(Dataset dataset);
}
Method:
public static void nySide(Class c, String controllerPath, Dataset dataset, String tittel, Window window){
try {
FXMLLoader loader = new FXMLLoader(c.getResource(controllerPath));
Parent root = (Parent) loader.load();
FXMLInitData controller = loader.getController();
controller.initData(dataset);
//This line gets the Stage information
Stage st = new Stage();
st.setTitle(tittel);
st.setScene(new Scene(root));
st.show();
Stage stage = (Stage) window;
stage.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Class:
public class FXMLControllerHome implements Initializable, FXMLInitData{
#Override
public void initData(Dataset dataset){
}
}
Try letting every controller extend or implement a parent Controller-class. Make the parent Controller a parameter, and pass the child-controller as a parameter when calling the method instead of String controllerPath.
I'm currently working on a 'small' project with JavaFX. I used the SceneBuilder to create the first sketch of my GUI. it still needs some adjustment and styling but I wanted to see if it's working so far.
I have 2 hyperlinks on the GUI, if the user clicks one of them the default system-browser should open with a specific URL.
So far I got this:
Main.java:
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws MalformedURLException, IOException {
DataBean dataBean= new DataBean(primaryStage);
Controller controller = new Controller(dataBean);
controller.show();
}
public static void main(String[] args) {
launch(args);
}
}
DataBean.java:
public class DataBean {
private Stage primaryStage;
public DataBean(Stage stage) {
primaryStage = stage;
}
public Stage getPrimaryStage() {
return primaryStage;
}
}
TestautomatView.java:
public class TestautomatView implements Initializable {
#FXML
private ComboBox<String> environmentCombo;
#FXML
private Hyperlink crhl;
#FXML
private Hyperlink help;
#Override
public void initialize(URL location, ResourceBundle resources) {
}
private Scene scene;
private BorderPane root;
public TestautomatView() throws MalformedURLException, IOException {
root = FXMLLoader.load(new URL(TestautomatView.class.getResource("Sample.fxml").toExternalForm()));
scene = new Scene(root);
}
public void show(Stage stage) {
stage.setTitle("CrossReport Testautomat");
stage.setScene(scene);
stage.show();
}
public ComboBox<String> getEnvironmentCombo() {
return environmentCombo;
}
public Hyperlink getCrhl() {
return crhl;
}
public Hyperlink getHelp() {
return help;
}
public Scene getScene() {
return scene;
}
}
In my controller I want to set the ActionHandler to the hyperlinks but it's not working because the getters in my view return null.
public class Controller {
private DataBean dataBean;
private TestautomatView view;
public Controller(DataBean databean) throws MalformedURLException, IOException {
this.dataBean = databean;
this.view = new TestautomatView();
setActionHandlers();
}
public void show() throws MalformedURLException, IOException {
view.show(dataBean.getPrimaryStage());
}
private void setActionHandlers() {
// setHyperlink(view.getCrhl(), "www.example.com");
// setHyperlink(view.getHelp(), "www.example2.com");
}
private void setHyperlink(Hyperlink hl, String uri) {
hl.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
//TODO - Open Default Browser
}
});
}
}
When I start my application, I can see the GUI but when I want to add the ActionHandlers I get a NullPointerException.
In the ´Sample.fxml´ file the hyperlinks are children of a HBox
<Hyperlink fx:id="crhl" text="Report" />
<Hyperlink fx:id="help" text="Help" />
But it's not just the hyperlinks even the ComboBox is null when I inspect my app in the debugger.
Where is my mistake?
The problem is that you are creating your controller manually by using new TestautomatView(). It must be created by FXMLLoader for annotations to work. You must also set fx:controller attribute in Sample.fxml to your controller (TestautomatView) fully qualified class name.
Example code:
FXMLLoader fl = new FXMLLoader(new URL(TestautomatView.class.getResource("Sample.fxml").toExternalForm()));
root = fl.load();
TestautomatView controller = fl.getController();
PS: You should rename your TestautomatView to TestautomatController. FXML file is your "view".
As pointed out in another answer, the issue is that you create an instance of TestautomatView "by hand". The default behavior of the FXMLLoader is to create an instance of the controller class specified in the FXML file, and use that instance as the controller. Consequently, you have two instances of TestautomatView: the one you created (and have a reference to), and the one that was created by the FXMLLoader. It is the second one that has the #FXML-annotated fields initialized.
You can change this default behavior by creating an FXMLLoader instance, and setting the controller on it directly. E.g. consider doing:
public class TestautomatView implements Initializable {
#FXML
private ComboBox<String> environmentCombo;
#FXML
private Hyperlink crhl;
#FXML
private Hyperlink help;
#Override
public void initialize(URL location, ResourceBundle resources) {
}
private Scene scene;
private BorderPane root;
public TestautomatView() throws MalformedURLException, IOException {
FXMLLoader loader = new FXMLLoader(TestautomatView.class.getResource("Sample.fxml"));
loader.setController(this);
root = loader.load();
scene = new Scene(root);
}
// etc...
}
Since you are directly setting the controller, you need to remove the fx:controller attribute from the Sample.fxml file for this to work.
You may also be interested in this pattern, which is quite similar (though not exactly the same) as what you are trying to do here.
New to JAVAFX so this maybe a simple fix, but I have controllers in my application setup using FXML files. I reference the controller to use via the FXML file and to load the file i use the following code in my Application class
private void replaceScene(String resource) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource(resource));
Pane screen = (Pane) loader.load();
Scene scene = new Scene(screen);
scene.getStylesheets().addAll(getClass().getResource("/css/application.css").toExternalForm());
stage.setScene(scene);
stage.sizeToScene();
IControlledScreen controller = (IControlledScreen) loader.getController();
controller.setApp(this);
} catch (Exception e) {
System.out.println("Cannot load resource " + resource);
System.out.println(e.getMessage());
}
}
And here is a basic controller
public class MyController implements IControlledScreen {
MyApplication app;
public void setApp(MyApplication application) {
app = application;
}
#FXML
public Button btnStart;
// Initialises the controller class.
#FXML
protected void initialize() {
btnStart.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
// code here
}
});
}
}
I have also got an interface called IControlledScreen to set the reference to the application
public interface IControlledScreen {
// ALlows us a reference to the application
public void setApp(MyApplication app);
}
Now this all works fine, until i try to access the app variable during the initialize event. So changing the above controller to this now breaks, because app = NULL.
public class MyController implements IControlledScreen {
MyApplication app;
public void setApp(MyApplication application) {
app = application;
}
#FXML
public Button btnStart;
// Initialises the controller class.
#FXML
protected void initialize() {
// HERE app = NULL
app.GetSomeProperty = "";
}
}
How can i get round this?
Well I think you have to change your design.
The initialize method is called during FXMLLoader.load()
So the call stack would be something like
..replaceScene
..loader.load
....MyController.initialize()
..loader.getController
..controller.setApp(app)
If you really have to access the application from inside your controller you would need to make it a singleton.
I just can't figure out how to add data to static ComboBox in JavaFX 2.2. Whatever I try to do ComboBox is empty. Here is the code:
#FXML private MenuItem menuItemNewTile;
#FXML private static ComboBox<Tile> comboBoxTileList;
#FXML
private void menuItemNewTileSetOnAction(ActionEvent event) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource(TILE_WINDOW_URL));
Scene scene = new Scene(root);
Stage tileStage = new Stage();
tileStage.setScene(scene);
tileStage.show();
}
#FXML
private void comboBoxTileListSetOnAction(ActionEvent event) {
}
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
comboBoxTileList = new ComboBox<>();
}
public static void refreshTileList(Tile tile) {
comboBoxTileList.getItems().add(tile);
}
If ComboBox is private, and I add item in initialize method it's working, but with static ComboBox I tried million things and still no progress.
Solution
Don't use static and #FXML together.
Rework your design so that the static keyword is no longer required for the comboBoxTileList and use an instance variable instead.
Additional Issue
An #FXML member such as comboBoxTileList should never be set to a new value, so you should not have comboBoxTileList = new ComboBox<>();
Answer to additional questions
I use another window to create new Tile object and from controller class of that window i call refreshTileList method. How to do that without using static?
There are numerous ways of writing your code so that you don't need a static reference to controller members. Here is a sample based on a solution from: Passing Parameters JavaFX FXML. You will need to modify the example to fit your exact case, it's just presented to demonstrate a possible pattern that you could use.
You can construct a new controller in code, passing any parameters you want from your caller into the controller constructor. Once you have constructed a controller, you can set it on an FXMLLoader instance before you invoke the load() instance method.
To set a controller on a loader (in JavaFX 2.x) you CANNOT also define a fx:controller attribute in your fxml file.
class ComboController {
#FXML private static ComboBox<Tile> comboBoxTileList;
public void refreshTileList(Tile tile) {
comboBoxTileList.getItems().add(tile);
}
}
class AnotherController {
#FXML private Button createTile;
#FXML private Button newCombo;
#FXML private StackPane mainPane;
private comboController;
#FXML private void createTile(ActionEvent event) {
if (comboController == null) {
return;
}
comboController.refreshTileList(
new Tile()
);
}
#FXML private void newCombo(ActionEvent event) {
try {
comboController = new ComboController();
FXMLLoader loader = new FXMLLoader(
getClass().getResource(
"combo.fxml"
)
);
loader.setController(comboController);
Pane comboPane = (Pane) loader.load();
mainPane.getChildren().setAll(comboPane);
} catch (IOException e) {
// handle exception.
}
}
}