I'm quite new to java and javafx and have a problem which i could not solve.
I need to dynamically add new custom controlls to a javafx scene. Further i need interaction between the main control and the added controls.
I found already some useful information in the web but could not put it together.
So i build a little example for explanation:
main class:
public class Test_TwoController extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("Fxml1.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The main fxml:
<AnchorPane id="fxml1_anchorpane_id" fx:id="fxml1_anchorpane" prefHeight="206.0" prefWidth="406.0" xmlns:fx="http://javafx.com/fxml" fx:controller="test_twocontroller.Fxml1Controller">
<children>
<HBox id="fxml1_hbox_id" fx:id="fxml1_hbox" prefHeight="200.0" prefWidth="400.0">
<children>
<Button id="fxml1_button_id" fx:id="fxml1_button" mnemonicParsing="false" onAction="#button_action" prefHeight="200.0" prefWidth="200.0" text="Button" />
</children>
</HBox>
</children>
</AnchorPane>
and its controller:
public class Fxml1Controller implements Initializable {
#FXML HBox hbox;
#FXML Button button;
#Override
public void initialize(URL url, ResourceBundle rb) { }
public void button_action(ActionEvent event) throws IOException {
// 1. add an instance of Fxml2 to hbox
// 2. change to tab2 in new Fxml2
// or
// notify Fxml2Controller to change to tab2 in Fxml2
}
}
And now the control to dynamically add:
Its fxml:
<AnchorPane id="fxml2_anchorpane_id" fx:id="fxml2_anchorpane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="200.0" prefWidth="200.0" xmlns:fx="http://javafx.com/fxml" fx:controller="test_twocontroller.Fxml2Controller">
<children>
<TabPane id="fxml2_tabpane_id" fx:id="fxml2_tabpane" prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE">
<tabs>
<Tab id="fxml2_tab1_id" fx:id="fxml2_tab1" text="tab1">
<content>
<AnchorPane id="Content" minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0" />
</content>
</Tab>
<Tab id="fxml2_tab2_id" fx:id="fxml2_tab2" onSelectionChanged="#onSelectionChanged" text="tab2">
<content>
<AnchorPane id="Content" minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0" />
</content>
</Tab>
</tabs>
</TabPane>
</children>
</AnchorPane>
and the controler:
public class Fxml2Controller {
#FXML TabPane tabpane;
#FXML Tab tab1;
#FXML Tab tab2;
public Fxml2Controller() throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("Fxml2.fxml"));
Scene scene = new Scene(root);
Stage stage = new Stage();
stage.setScene(scene);
}
public void onSelectionChanged(Event e) throws IOException {
FXMLLoader loader = new FXMLLoader();
// how can i get the current Fxml1 anchorpane instance?
AnchorPane root = (AnchorPane) loader.load(getClass().getResource("Fxml1.fxml").openStream());
Button b = (Button)root.lookup("#fxml1_button_id");
b.setText("New Button Text"); // dont change the buttons text!!!
}
}
The usage is: A fxml2 should be added to the hbox of fxml1. Then after a button click in fxml1 the tabs of fxml2 should change.
You may have a look at that image http://s13.postimage.org/uyrmgylo7/two_controlls.png
So my questions are:
how can i add one or more of the fxml2 controller into the hbox of fxml1?
how can i access one control from another or communicate between controlls? See onSelectionChanged() method in Fxml2Controller for detail.
Thank you in advance,
solarisx
You seem to have mixed quite a few concepts together which are distinct. First of all, a Stage can be understood as a window on the screen. It has a Scene object which holds the actual SceneGraph. In your example, you are creating a new Stage and a new Scene that get filled which the content of your second fxml-file. This means that, if working, a second window will pop up containing your stuff. I don't think that this is what you want to achieve.
Moreover, when the FXMLLoader reads a file, it looks for the class that is specified as its controller and constructs an instance of it via reflection. This means that when you call the load method in the constructor of the controller of the fxml-file you are loading with it, you are causing an infinite loop.
The last thing to understand is that the object that load() returns is an arbitrary node which can be put into the SceneGraph of your application just like any other node.
So to make your concept work, you should make the following:
Move the loading-code which is currently in the constructor of your second controller to the button_Action method of your first controller.
Throw away the new-stage-new-scene code in the button_action and take the Node returned by the FXMLLoader and add it to the children of the HBox.
For your second question, you can get the controller instance if you actually create an instance of an FXMLLoader instead of calling the static method, and use the load() method in it. After calling load() you can retrieve the controller and root object of the fxml-file via getController() and getRoot(). You can then use them just like any arbitrary object in your logic.
Related
I create Pane in Scene Builder for Java 8. My .css files store in /rescouces/css/app.css. I connect stylesheet in Scene Builder and all ok. But after i start my app i get exception with error:
Caused by: javafx.fxml.LoadException: Invalid resource: /../style/app.css not found on the classpath.
How to fix this? I need every time rename path to css in .fxml?
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="912.0" prefWidth="1368.0" styleClass="app" stylesheets="#/style/app.css" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.mypod.tablet.controller.MainController">
<children>
<AnchorPane fx:id="contentPane" layoutX="248.0" layoutY="138.0" stylesheets="#/style/content.css" AnchorPane.bottomAnchor="94.0" AnchorPane.leftAnchor="250.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="115.0">
<styleClass>
<String fx:value="block" />
<String fx:value="content-block" />
</styleClass>
</AnchorPane>
</children>
</AnchorPane>
Load fxml:
FXMLLoader loader = new FXMLLoader();
this.primaryStage.setScene(new Scene(loader.load(Util.getResource("/fxml/main.fxml"))));
The problem
In order to reproduce the issue, and based on the comments for the question, this is required:
Main class
#Override
public void start(Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader();
Parent root = loader.load(MainApp.class.getClassLoader().getResourceAsStream("fxml/scene.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
FXML under src/main/resources/fxml/scene.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" stylesheets="#../styles/styles.css" prefHeight="200" prefWidth="320" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Button fx:id="button" text="Click Me!" />
</children>
</AnchorPane>
CSS under src/main/resources/styles/styles.css
.button {
-fx-font-size: 2em;
}
The project runs, but you get this error printed:
null/../styles/styles.css
com.sun.javafx.css.StyleManager loadStylesheetUnPrivileged
WARNING: Resource "../styles/styles.css" not found.
While manually editing the FXML file and removing the parent dots:
stylesheets="#/styles/styles.css"
seems to solve the issue and runs fine without warning, this prevents Scene Builder from finding the css file, so it shouldn't be done.
The solution
Using getResourceAsStream to retrieve the FXML file is not recommended, just use getResource().
This works:
FXMLLoader loader = new FXMLLoader();
Parent root = loader.load(MainApp.class.getClassLoader().getResource("fxml/scene.fxml"));
Using the FXMLLoader empty constructor is not the recommended way, instead use the the static load method.
This works:
Parent root = FXMLLoader.load(MainApp.class.getClassLoader().getResource("fxml/scene.fxml"));
Finally, there is no need for class loader. The classloader is stream based, and it doesn't know about the class you retrieve it from and tries to locate from the package "fxml/scene.fxml". On the other hand, Class.getResource() is URL based and it looks up the resource relative to the class, so you need to set the path to the root of the project "/fxml/scene.fxml".
This is how it should be called:
Parent root = FXMLLoader.load(MainApp.class.getResource("/fxml/scene.fxml"));
Or in case you need the loader (for instance to retrieve the controller), this is also the recommended way:
FXMLLoader loader = new FXMLLoader(MainApp.class.getResource("/fxml/scene.fxml"));
// YourController controller = (YourController) loader.getController();
Parent root = loader.load();
This post is worth reading.
I have three layers
Root layout, home, content (rootLayout.fxml, home.fxml and content.fxml)
FXMLLoader loader = new FXMLLoader();
loader.setLocation(GenerateReport.class
.getResource("/skin/rootLayout.fxml"));
rootLayout = (AnchorPane) loader.load();
Scene scene = new Scene(rootLayout);
FXMLLoader loader = new FXMLLoader();
loader.setLocation(GenerateReport.class
.getResource("/skin/home.fxml"));
AnchorPane homeLayout = (AnchorPane) loader.load();
rootLayout.getChildren().addAll(homeLayout);
.
.
.
rootLayout.getChildren().addAll(contentLayout);
like this I am adding content layout. In rootLayout.fxml i have a home button. My requiredment is if a user clicks home button
then i want content layout to be removed and home layout to be visible.
content.fxml
<AnchorPane id="myContent" ... fx:controller="com.ReportController">
rootLayout.fxml
<AnchorPane id="AnchorPane" .. fx:controller="com.ReportController">
<children>
<Button fx:id="home" onAction="#homeBtn" .../>
</children>
</AnchorPane>
In my Controller (In all the fxml file i am pointing to the same controller) class i created
#FXML
private Button home;
#FXML
private AnchorPane myContent;
#FXML
protected void homeBtn(ActionEvent event) throws Exception {
System.out.println("click! homeBtn");
myContent.getChildren().clear();
}
The problem i am facing is i am getting NullPointerException. i.e. myContent.getChildren() is returning null. Could anyone help me in resolving this issue.
You're getting a Null Pointer Exception because Javafx doesn't associate your myContent with the AnchorPane stored in the fxml, and does not attach a reference to that object.
Nodes in fxml files are given their name identifiers by using fx:id. Note, for example, that your #FXML private Button home is marked in the fxml as <Button fx:id="home"...>.
In this case, your #FXML AnchorPane myContent is marked in fxml as <AnchorPane id="myContent"...>, not <AnchorPane fx:id="myContent"...>.
id="" is used only in CSS.
I started working with JavaFX just today and already need some advise. I load the applicaton.fxml (created with Oracle SceneBuiler) using the FXMLLoader in the start(Stage ...) method of the MainApplication (which has an ApplicationController specified in my application.fxml file).
<AnchorPane id="AnchorPane" disable="false" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="800.0" styleClass="theme" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="app.AppController">
...more code here...
<ComboBox id="cmb_locations" fx:id="cmb_locations">
<items>
<FXCollections fx:factory="observableArrayList">
<String fx:value="Item 1" />
<String fx:value="Item 2" />
<String fx:value="Item 3" />
</FXCollections>
</items>
</ComboBox>
Now, I have a ComboBox in the applicaton.fxml, which has three items (the default items). What I need is to populate that ComboBox during the startup with my own values. Does anyone know how to achieve that and where to put the relevant code snippets (app.AppController or something similar)? Thanks in advance.
You have some controller for you fxml file. There you have access to your ComboBox. You could put this code to setup list of elements (probably in initialize() method):
If you don't really want to edit your fxml file you can just clear the list first with cmb_locations.getItems().clear(); before you setup new list.
public class ApplicationController implements Initializable {
#FXML
ComboBox cmb_locations;
...
#Override
public void initialize(URL url, ResourceBundle rb) {
...
List<String> list = new ArrayList<String>();
list.add("Item A");
list.add("Item B");
list.add("Item C");
ObservableList obList = FXCollections.observableList(list);
cmb_locations.getItems().clear();
cmb_locations.setItems(obList);
...
}
}
Start by removing the default values on the FXML "Item 1" "Item 2" ...
just to have
<FXCollections fx:factory="observableArrayList">
</FXCollections>
and on your controller if you want retrieve your combobox you have to inject it by doing
#FXML
ComboBox cmb_locations
public void initialize(URL url, ResourceBundle resource) {
//here populate your combobox
}
In your controller, you implement the Initializable interface.
Then in initialize method, you just add your code to load your combo box.
I am creating a simple Weather Application
Here goes the details of my question :-
I have a main Controller (GeoWeatherMain.java). When Appl is run, this class(GeoWeatherMain.class) gets loaded which loads an fxml file.
Below is code for it :-
Parent root = FXMLLoader.load(getClass().getResource("GeoWeatherMainUI.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
Now, GeoWeatherMainUI.fxml has BorderPane object implementation. It consists of a button(on the left pane) which onclick loads another fxml file inside center pane. The skeleton of GeoWeatherMainUI.fxml looks like below:-
<BorderPane fx:id="MainBody" prefHeight="736.0" prefWidth="1140.0" xmlns:fx="http://javafx.com/fxml" fx:controller="geoweather.GeoWeatherUIActionHandlers">
.
.
<Button layoutX="91.0" layoutY="67.0" mnemonicParsing="false" onAction="#getCurrAndForecastWeatherCond" text="Get Weather Details" />
.
.
<BorderPane>
Now GeoWeatherUIActionHandlers.java is another controller which takes care of different button action events.Below is its complete code
public class GeoWeatherUIActionHandlers implements Initializable{
#FXML
BorderPane MainBody;
#FXML
Label LocName;/*LocName is fx id for a label*/
#FXML
private void getCurrAndForecastWeatherCond(ActionEvent event){
try{
Pane centerPane = FXMLLoader.load(getClass().getResource("WeatherDetails.fxml"));
MainBody.setCenter(centerPane);
/*LocName.setText("xyz");*/
}catch (IOException ex){
TextArea excep = new TextArea(ex.toString());
MainBody.setCenter(excep);
}catch(Exception e){
TextArea excep = new TextArea("Inside Exception : \n" + e.toString());
MainBody.setCenter(excep);
}
}
#Override
public void initialize(URL url, ResourceBundle rb){}
}
Now, if I want to update the loaded WeatherDetails.fxml file with new values, how to do that? I tried as in the above commented code.(LocName.setText("xyz")). But, it didn't work (giving NullPointerException).
I went through complete documentation of javafx # docs.oracle.com. No luck. Here also I didn't get the answer. Please guide.
If the LocName is located inside WeatherDetails.fxml then it is excepted behavior that the LocName will be null. Because #FXML Label LocName; is defined in GeoWeatherUIActionHandlers.java which is a controller of GeoWeatherMainUI.fxml. Move LocName from WeatherDetails to GeoWeatherMainUI FXML file and see where you are still getting the error or not.
If your aim is to set the text of the label that is inside WeatherDetails.fxml, then do this work
in the controller of WeatherDetails similar to current GeoWeatherUIActionHandlers, or
in GeoWeatherUIActionHandlers, after loading the WeatherDetails.fxml
get the label by id (not fx:id) from centerPane with ((Label)centerPane.lookup("#myLabel")).setText("xyz"), or
get the controller of WeatherDetails and call getLocName().setText("xyz"). Assuming getter method exists in a controller class.
I have created a tabPane. Under each tab, I have included () an fxml to show the actual session user, with this code :
home-tab.fxml has this line :
<fx:include fx:id="topTab" source="../top-tab.fxml"/>
top-tab.fxml :
<AnchorPane maxHeight="20.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml" fx:controller="wuendo.client.TopTabController">
<children>
<HBox id="hbox_top" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
<Label id="label_session" prefHeight="20.0" text="SESSION : " />
<Label fx:id="sessionLabel" prefHeight="20.0" text="" />
</HBox>
</children>
</AnchorPane>
TopTabController.java :
#FXML public Label sessionLabel;
HomeTabController.java :
#FXML private TopTabController topTabController;
#Override
public void initialize(URL url, ResourceBundle rb) {
URL location = getClass().getResource("../top-tab.fxml");
FXMLLoader fxmlLoader = new FXMLLoader(location);
AnchorPane root = null;
try {
root = (AnchorPane) fxmlLoader.load();
} catch (IOException ex) {
Logger.getLogger(HomeTabController.class.getName()).log(Level.SEVERE, null, ex);
}
topTabController = (TopTabController) fxmlLoader.getController();
Label txt = (Label) root.lookup("#sessionLabel");
txt.setText("blabla");
System.out.println("sessionLabel= " + topTabController.sessionLabel.getText());
}
When I execute this, the console prints "blabla", but the label is not modified in the program (gui)
What do I have to do to see the value updated ?
Thank you all
The FXMLLoader is already created the TopTabController while the home-tab.fxml is being loaded. And it is the rendered one in the scene. However you are creating/loading another instance of TopTabController which is not added to the any scene. And you are changing the text of the label in the second one. The correct approach is to modify the already loaded 1st instance and not to load other one:
#Override
public void initialize(URL url, ResourceBundle rb) {
topTabController.sessionLabel.setText("Real blabla");
System.out.println("sessionLabel= " + topTabController.sessionLabel.getText());
}
Side note, The link you provided in the comment was useful.