fx:controller="" in .FXML - javafx-2

is it possible to add two controllers (fx:controller="") in one FXML file ?
I could managed to add only one as fx:controller=""
See the code
<BorderPane id="BorderPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="596.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml" fx:controller="demoapp.ClientArea">

You can't set more than one controller within an FXML file using (fx:controller=""), instead consider injecting the controller manually, basically there are two ways :
Using setController method without mention the controller inside the FXML file :
FXMLLoader loader = new FXMLLoader();
URL location = getClass().getClassLoader().getResource("fxml/ClientArea.fxml");
loader.setLocation(location);
loader.setController(new ClientArea());
// loader.setController(new Undecorator());
loader.load();
More appropriately, use setControllerFactory method :
first, make sure that both of the controllers ClientArea and Undecorator implement an interface (Icontroller, containing the event Handler methods) mentioned in the FXML file (fx:controller="IController"), then choose the controller when loading your View from the FXML file:
FXMLLoader loader= new FXMLLoader();
URL location = getClass().getClassLoader().getResource("fxml/ClientArea.fxml");
loader.setLocation(location);
loader.setControllerFactory(new Callback<Class<?>, Object>() {
public Object call(Class<?> p) {
return new ClientArea();
// return new Undecorator();
}
});
loader.load();

If fits in your code, your Undecorator.java could extend from ClientArea.java. So, any method (or FXML method/control) could be used from its parent: ClientArea.java. Using JavaFX SceneBuilder, won't show you the package.ClientArea in the controller selection, but at runtime it will work.

Related

Connected styles in Scene Builder is not found in runtime

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.

Getting Null Pointer Exception while removing a layer in Javafx

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.

Adding JavaFX2 Controls dynamically

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.

javafx - updating values in fxml object which was dynamically loaded on button click

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.

Quick way to create JSF custom component

I know of two ways of creating custom JSF components:
1. Native JSF way: creating JSF component class, tag, etc.
2. Facelets way: defining component in a xhtml file and then creating appropriate decrption in facelets taglib.
Currently I work on a project in which introducing facelets is unfortunately out of the question. On the other hand, creating custom components the standard JSF way seems like a pain in the ass.
Is there maybe a third party library that allows creating custom components in the way similar to facelets but doesn't entail the need of using non-standard renderer?
You can do a limited amount of templating using (for example) jsp:include and f:subview.
Alternatively, you can extend a UIComponent overriding selected methods and then provide it via an existing tag and a managed bean using the binding attribute. This still requires a reasonably detailed understanding of component development (and the consequences of this choice), but could cut down the number of files/volume of code significantly.
This approach is a bit of a hack, but might be OK for short-term stuff. You wouldn't do it for component libraries you want to distribute or components requiring long term maintenance.
The new component:
public class QuickComponent extends HtmlOutputText {
#Override public void encodeAll(FacesContext context) throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.writeText("I'm not really a HtmlOutputText", null);
for (UIComponent kid : getChildren()) {
if (kid instanceof UIParameter) {
UIParameter param = (UIParameter) kid;
writer.startElement("br", this);
writer.endElement("br");
writer.writeText(param.getName() + "=" + param.getValue(), null);
}
}
}
}
The bean providing an instance:
/**Request-scope managed bean defined in faces-config.xml*/
public class QuickComponentProviderBean {
private QuickComponent quick;
public void setQuick(QuickComponent quick) {
this.quick = quick;
}
public QuickComponent getQuick() {
if (quick == null) {
quick = new QuickComponent();
}
return quick;
}
}
Note: don't reuse a single bean property for multiple tags in your views, or they'll reference the same object instance.
Adding the new component to the view:
<h:outputText binding="#{quickComponentProviderBean.quick}">
<f:param name="Hello" value="World" />
</h:outputText>
Note: the attributes that can be defined have not changed. They're fixed by the TLD.

Resources