Javafx - how to modify components in fxml file after loading it - javafx

I am a novice to javafx. I created a simple javafx fxml application. I defined a treeView in my fxml file. It is as follows,
FXMLDocument.fxml
<AnchorPane id="AnchorPane" prefHeight="200.0" prefWidth="320.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="scenebuilderfirst.FXMLDocumentController">
<children>
<TreeView id="" fx:id="tree" layoutX="60.0" layoutY="14.0" prefHeight="172.0" prefWidth="200.0" />
</children>
</AnchorPane>
and created the controller class as follows,
FXMLDocumentController.java
public class FXMLDocumentController implements Initializable {
#FXML
private TreeView<?> tree;
#Override
public void initialize(URL url, ResourceBundle rb) {
}
}
my main java file is as follows,
MyApp.java
public class SceneBuilderFirst extends Application {
#Override
public void start(Stage stage) throws Exception {
AnchorPane root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Now I need to add tree items to the TreeView while running the program. So I thought of writing the following peace of code inside the initialize() method of my contorller class,
// creating root item for the tree
TreeItem<String> rootItem = new TreeItem<>("root");
rootItem.setExpanded(true);
//creating childern for the tree
TreeItem<String>item1= new TreeItem<>("content1");
rootItem.getChildren().add(item1);
TreeItem<String>item2= new TreeItem<>("content2");
rootItem.getChildren().add(item2);
//attaching root item to tree
tree = new TreeView<>(rootItem);
But it didn't work.
I need to set the TreeItems while running the program. Not through the fxml file.
So I think I should do the coding in my main java file
I need a way to access my TreeView after loading it via FXMLLoader in my main java file so i can make a TreeView on my own choice and attach it to this TreeView while running the program. Please guide me.

To fix this, replace
tree = new TreeView<>(rootItem);
with
tree.setRoot(rootItem);
For an explanation of why, see this discussion.
You also have to fix your declaration of the TreeView:
#FXML
private TreeView<String> tree;

Related

Update component in javafx

Hy guys, I'm developing an application using javafx and fxml.
What I'm currently trying to do is to update a flowpane, adding components to it when the user performs an action (is not the case but let's take as an example the click of a button).
Let's suppose that the method just has to add buttons with the passed labels to the flowpane.
In the Controller of the fxml that contains the flowpane I have this:
public class CategoryPaneController implements Initializable {
#FXML
private FlowPane categoryContainer;
public void setCategories(String[] labels) throws IOException{
for(String label : labels){
Button button = new Button(label);
categoryContainer.getChildren().add(button);
}
}
}
This method is called in another controller as follows:
public class AddCategoryController implements Initializable {
#FXML
private Pane addCategoryPane;
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
#FXML
private void refreshCategories(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/categoryPane.fxml"));
loader.load();
CategoryPaneController cat = loader.getController();
String[] labels = {"categoria3", "categoria4"};
cat.setCategories(labels);
}
}
The categoryPane.fxml is the following:
<ScrollPane fx:id="storeMainPane" fx:controller="controller.CategoryPaneController">
<content>
<VBox prefHeight="629.0" prefWidth="862.0">
<children>
<Label alignment="TOP_CENTER" contentDisplay="CENTER" prefHeight="68.0" prefWidth="857.0" text="Magazzino" />
<FlowPane fx:id="categoryContainer" prefHeight="549.0" prefWidth="862.0" />
</children>
</VBox>
</content>
</ScrollPane>
And the following is the addCategory.fxml file
<Pane fx:id="addCategoryPane" fx:controller="controller.AddCategoryController">
<children>
<Button onAction="#refreshCategories" text="aggiungi categoria" />
</children>
</Pane>
I've debbugged the code and the method is called in the right way, it adds the buttons to the flowpane but the latter doesn't change.
Thanks in advance. <3
Every time you load a new FXML a new Controller instance and new nodes will be generated.
If you want to use an existing controller and nodes, you need to store a reference to the originally loaded controller and invoke methods on that.
Calling methods on a controller.
Or you can use MVC:
Applying MVC With JavaFx

The value set doesn't get displayed in the Medusa Gauge

I tried adding a Medusa Gauge to my JavaFX project that uses FXML.
I mapped the fx:id correctly in the scene builder and set a value to be displayed.
But unfortunately the value doesn't get displayed and only the default 0.00 is displayed.
Here is my code
Class that contains the main method - GaugeFX.java
public class GaugeFX extends Application {
#Override
public void start(Stage stage) {
try {
Parent root = FXMLLoader.load(getClass().getResource("Demo.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
} catch (IOException ex) {
Logger.getLogger(GaugeFX.class.getName()).log(Level.SEVERE, null, ex);
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
FXML - Demo.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import eu.hansolo.medusa.Gauge?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.102" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gaugeDemoFX.DemoController">
<children>
<Gauge fx:id="gaugeExample" alert="true" alertMessage="Almost full" animated="true" areaTextVisible="true" layoutX="123.0" layoutY="113.0" lcdFont="ELEKTRA" skinType="SLIM" />
</children>
</AnchorPane>
FXML Controller - DemoController.java
public class DemoController implements Initializable {
#FXML
Gauge gaugeExample;
#Override
public void initialize(URL url, ResourceBundle rb) {
GaugeBuilder builder = GaugeBuilder.create();
gaugeExample = builder.decimals(0).maxValue(10000).unit("Questions").build();
gaugeExample.setValue(45);
}
}
I tried looking at the documentation. There the examples were done in hard coding. I noticed that the Gauge's value was set before showing the stage.
https://community.oracle.com/docs/DOC-992746
But according to my knowledge i have done the same even if i used FXML to make the project.
Can someone please tell me where i went wrong that the value i set doesn't get displayed?
You're overwriting value of the field containing the Gauge created in the fxml with a new one in the initialize method. The new one is never added to the scene; you only see the old unmodified one.
If Gauge works the same way JavaFX standard Controls work, you only need to use setters instead of using the builder:
#Override
public void initialize(URL url, ResourceBundle rb) {
gaugeExample.setDecimals(0);
gaugeExample.setMaxValue(10000);
gaugeExample.setUnit("Questions");
gaugeExample.setValue(45);
}
Furthermore it should also be possible to assign the properties from the fxml file:
<Gauge fx:id="gaugeExample"
alert="true"
alertMessage="Almost full"
animated="true"
areaTextVisible="true"
layoutX="123.0"
layoutY="113.0"
lcdFont="ELEKTRA"
skinType="SLIM"
decimals="0"
maxValue="10000"
unit="Questions"
value="45" />

How can a textfield from fxml file be updated by setText in java file?

I am looking to update text in a textfield based on some value. In order to make this sample simpler I have made my program smaller. The problem seems to be when I put top.setText("This is my new Text");
I looked at this:
how to change the text of TextField in java fx 2
but the answer does not seem to make sense. I don't know why you'd initialize a textfield that has already been implemented. Regardless it did not work.
I have also looked at:
NullPointerException (JavaFX Label.setText())
This seems to be the closest to what I think is the issue, but when I did the following I get an error. Just for clarity this is in the JavaFXApplication5 class.
try {
FXMLLoader loader = new FXMLLoader(
getClass().getResource("FXML.fxml")
);
FXMLLoader.setController(this); // non-static method setController(Object)
// can not be referenced from static context ERROR****
Parent root = (Parent) loader.load();
/*Parent root = FXMLLoader.load(getClass().getResource("FXML.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("java code");
stage.show();
*/
}
From reading on the internet I wondered if there was a race condition: http://gotoanswer.stanford.edu/nullpointerexception_in_javafx_initialize_method-8807679/
So I tried:
Platform.runLater(new Runnable() {
#Override public void run() {
top.setText("This is my new Text");
}
});
But that did not work. I know that it can be set in Scene Builder but I need a way to dynamically change it based on values from another class. I can figure out how to do that part if I can just figure out how to set it to begin with. Hopefully this explains enough to get some help.
FXMLController Class:
public class FXMLController implements Initializable {
#FXML private TextField top;
public FXMLController() {
System.out.println("Hi");
top.setText("This is my new Text"); // This breaks the program *********
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
}
FXML.fxml class:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="javafxapplication5.FXMLController">
<children>
<TextField fx:id="top" layoutX="171.0" layoutY="68.0" />
</children>
</AnchorPane>
JavaFXApplication5 class: // main class
public class JavaFXApplication5 extends Application {
#Override
public void start(Stage stage) throws Exception {
try {
Parent root = FXMLLoader.load(getClass().getResource("FXML.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("java code");
stage.show();
}
catch (Exception ex) {
Logger.getLogger(JavaFXApplication5.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static void main(String[] args) {
launch(args);
}
}
You can't use a constructor on your controller class (FXMLController), since it will be initilized by the FXMLLoader.
And you are right, the first link has a wrong answer, since the textfield will be initialized (because of the #FXML annotation) in this process.
So for starters, you can add some text to the textfield inside initialize, as it will be loaded from the beginning by the loader, and top will be already instantiated.
public class FXMLController implements Initializable {
#FXML private TextField top;
#Override
public void initialize(URL url, ResourceBundle rb) {
top.setText("This is my first Text");
}
}
Try this first, with your posted version of JavaFXApplication5, and check that works.
There are many ways to set the content on the field, but if you need to modify the text field from another class, just add a public method for that:
public class FXMLController implements Initializable {
#FXML private TextField top;
#Override
public void initialize(URL url, ResourceBundle rb) {
top.setText("This is my new Text");
}
public void setTopText(String text) {
// set text from another class
top.setText(text);
}
}
As an example, you could get an instance of your controller in your main class, and use it to pass the content to the text field, after the stage is shown. This will override the previous content.
#Override
public void start(Stage stage) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("FXML.fxml"));
Parent root = loader.load();
FXMLController controller = (FXMLController)loader.getController();
Scene scene = new Scene(root);
stage.setTitle("Java code");
stage.setScene(scene);
stage.show();
controller.setTopText("New Text");
}

External jar FXML Load Exception

I try to load a class located in an external jar using classloader. The class "FXMLbase" loads ok, but the error is triggered when the FXMLdocument.fxml try to instantiate the FXMLDocumentController. However when the "FXMLbase" is instantiate throught the JavaFXApplication5.java (located at the external jar) it works fine. Any Ideas?
Class Loader
File file = new File("C:/Users/Os/Dropbox/CODE_OS/JavaFXApplication5/dist/JavaFXApplication5.jar");
URLClassLoader clazzLoader = URLClassLoader.newInstance(new URL[]{file.toURI().toURL()}, this.getClass().getClassLoader());
Class c = clazzLoader.loadClass("javafxapplication5.FXMLbase");
PluginInterface fXMLbase = (PluginInterface) c.newInstance();
Parent loadScreen = fXMLbase.getRoot();
FXMLbase.java -- external jar --
public Parent getRoot() {
Parent root = null;
try {
System.out.println("Class Name:" + getClass().getName());
root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
} catch (IOException ex) {
Logger.getLogger(FXMLbase.class.getName()).log(Level.SEVERE, null, ex);
}
return root;
}
FXMLdocument.fxml -- external jar --
<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="javafxapplication5.FXMLDocumentController">
<children>
<Button fx:id="button" layoutX="126" layoutY="90" onAction="#handleButtonAction" text="Click Me! app5" />
<Label fx:id="label" layoutX="126" layoutY="120" minHeight="16" minWidth="69" />
</children>
FXMLDocumentController.java -- external jar --
public class FXMLDocumentController implements Initializable{
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!");
label.setText("Hello World!");
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
JavaFxApplication5.java -- external jar --
public void start(Stage stage) throws Exception {
FXMLbase fXMLbase=new FXMLbase();
Parent root = fXMLbase.getRoot();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
Error:
ago 28, 2014 2:26:16 PM javafxapplication5.FXMLbase getRoot
SEVERE: null
javafx.fxml.LoadException:
file:/C:/Users/Os/Dropbox/CODE_OS/JavaFXApplication5/dist/JavaFXApplication5.jar!/javafxapplication5/FXMLDocument.fxml:9
....
Caused by: java.lang.ClassNotFoundException: javafxapplication5.FXMLDocumentController
The FXMLLoader at some point has to load the controller class from the value of the fx:controller attribute in the root element of the FXML file. It looks like it is using the system class loader to do this: I think this is because the system class loader finds the FXMLLoader class and loads it, rather than the class loader used to load your FXMLBase class.
The only fix I can find for this is to explicitly set the controller class from the FXMLbase class, instead of specifying it in the FXML. This is a bit unsatisfactory; perhaps there is a better way I am missing.
Updated FXMLbase class:
public Parent getRoot() {
Parent root = null;
try {
System.out.println("Class Name:" + getClass().getName());
FXMLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
loader.setController(new FXMLDocumentController());
root = loader.load();
} catch (IOException ex) {
Logger.getLogger(FXMLbase.class.getName()).log(Level.SEVERE, null, ex);
}
return root;
}
and you need to remove the fx:controller attribute from the FXML file.

Customize ListView in JavaFX with FXML

I want to make a customize list view in javafx. Here I need to bind multiple component in list cell as follow, like
one label, one textfield, one button under one HBox and
two button, one hyperlink, one label in another HBox and
these HBox comes under one VBox and
this VBox comes under the single list cell and
it will repeat and make a list View.
The code is
<ListView fx:id="ListView" layoutX="0" layoutY="30" prefWidth="600" prefHeight="300">
<HBox fx:id="listBox" alignment="CENTER_LEFT">
<padding><Insets top="5" bottom="5" left="5"></Insets> </padding>
<HBox alignment="CENTER_LEFT" prefWidth="170" minWidth="88">
<Label fx:id="surveyName" text="Field A" styleClass="Name"></Label>
</HBox>
<VBox styleClass="Description" prefWidth="155" minWidth="86">
<HBox>
<HBox styleClass="surveyDesIcon" prefWidth="20" prefHeight="16"></HBox>
<Label fx:id="surveyCode" text="PRW3456HJ"></Label>
</HBox>
<HBox>
<HBox styleClass="DateIcon" prefWidth="20" prefHeight="16"></HBox>
<Label fx:id="Date" text="PRW3456HJ"></Label>
</HBox>
</VBox>
<HBox fx:id="Status" prefWidth="160" minWidth="80">
<Label fx:id="StatusLabel" text="Checking Files.."/>
</HBox>
<HBox fx:id="StatusIcon1" prefWidth="50" prefHeight="50" alignment="CENTER">
<Label styleClass="StatusIcon1" prefWidth="24" prefHeight="24" alignment="CENTER"/>
</HBox>
<HBox fx:id="StatusIcon2" prefWidth="50" prefHeight="50" styleClass="StatusIconBox" alignment="CENTER">
<Hyperlink styleClass="StatusIcon2" prefWidth="24" maxHeight="24" alignment="CENTER"/>
</HBox>
</HBox>
</ListView>
I understand your question. There are mainly two ways to set items in a Listview:
1. Create the ObservableList and set the items of the ListView with the ObservableList (listView.setItems(observableList)).
2. Use the setCellFactory() method of the ListView class.
You would prefer to use the setCellFactory() method, because this approach simplies the process as well as it helps to separate out the business logic and the UI (FXML).
Here is a more detailed explaination:
1. Create a new FXML file with the name listview.fxml to contain the ListView, and set the ListViewController class as its controller:
File: listview.fxml:
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.ListView?>
<?import demo.ListViewController?>
<GridPane xmlns:fx="http://javafx.com/fxml" alignment="CENTER">
<ListView fx:id="listView"/>
</GridPane>
2. Create the controller and name it ListViewController.
The controller can load the listview.fxml file and access the listview.
File: ListViewController.java:
package demo;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.util.Callback;
import java.io.IOException;
import java.util.Set;
public class ListViewController
{
#FXML
private ListView listView;
private Set<String> stringSet;
ObservableList observableList = FXCollections.observableArrayList();
public ListViewController()
{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/listview.fxml"));
fxmlLoader.setController(this);
try
{
Parent parent = (Parent)fxmlLoader.load();
Scene scene = new Scene(parent, 400.0 ,500.0);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
public void setListView()
{
stringSet.add("String 1");
stringSet.add("String 2");
stringSet.add("String 3");
stringSet.add("String 4");
observableList.setAll(stringSet);
listView.setItems(observableList);
listView.setCellFactory(new Callback<ListView<String>, javafx.scene.control.ListCell<String>>()
{
#Override
public ListCell<String> call(ListView<String> listView)
{
return new ListViewCell();
}
});
}
}
3. First you need to set the value of the ObservableList. This is very important.
Then, set the items of list using the ObservableList and call the setCellFactory() method on the ListView. In the given example I just take the String values an add them to the String set (the Set<String> stringSet).
4. When the setCellFactory() method is called on the ListView, it will return the ListCell. So for sake of simplicity, I added a class which extends the ListCell, and the setGraphic() method is present for the ListCell() and will set the items of the ListCell.
File: ListViewCell.java:
package demo;
import javafx.scene.control.ListCell;
public class ListViewCell extends ListCell<String>
{
#Override
public void updateItem(String string, boolean empty)
{
super.updateItem(string,empty);
if(string != null)
{
Data data = new Data();
data.setInfo(string);
setGraphic(data.getBox());
}
}
}
5. I just added a class which will load the listCellItem.fxml and return the HBox, which will contain the other components as children.
The HBox is then set to the ListCell.
File: listCellItem.fxml:
<?import demo.Data?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Label?>
<HBox xmlns:fx="http://javafx.com/fxml" fx:id="hBox">
<children>
<Label fx:id="label1"/>
<Label fx:id="label2"/>
</children>
</HBox>
File: Data.java:
package demo;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import java.io.IOException;
public class Data
{
#FXML
private HBox hBox;
#FXML
private Label label1;
#FXML
private Label label2;
public Data()
{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/listCellItem.fxml"));
fxmlLoader.setController(this);
try
{
fxmlLoader.load();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
public void setInfo(String string)
{
label1.setText(string);
label2.setText(string);
}
public HBox getBox()
{
return hBox;
}
}
Using this way, you can use the setCellFactory() method to separate the things that are business logic and FXML.
Hope this is helpful.
The example above by #Anvay needs a couple of tweaks to work. These are simple things to set on-track.
The ListViewController needs to be running on the JavaFX application thread.
You can only call the injected #FXML elements from the JavaFX initialize() method
Need to call setListView()
The stringSet in the example needs to be allocated with a new before calling setListView().
The ListViewController below works with these changes. I changed "stringSet" to a list, "stringList". The controller is pretty much the sample controller provided by Scene Builder 2
public class ListViewController
{
#FXML private ResourceBundle resources;
#FXML private URL location;
#FXML private ListView listView;
private List<String> stringList = new ArrayList<>(5);
private ObservableList observableList = FXCollections.observableArrayList();
public void setListView(){
stringList.add("String 1");
stringList.add("String 2");
stringList.add("String 3");
stringList.add("String 4");
observableList.setAll(stringList);
listView.setItems(observableList);
listView.setCellFactory(
new Callback<ListView<String>, javafx.scene.control.ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> listView) {
return new ListViewCell();
}
});
}
#FXML
void initialize() {
assert listView != null : "fx:id=\"listView\" was not injected: check your FXML file 'CustomList.fxml'.";
setListView();
}
}//ListViewController
The JavaFX platform needs to be started in the main() method from a JavaFX Application. Netbeans conviently provides most of this structure from the Maven JavaFX application template.
public class MainApp extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/fxml/CustomList.fxml"));
Scene scene = new Scene(root);
scene.getStylesheets().add("/styles/Styles.css");
stage.setTitle("CustomList");
stage.setScene(scene);
stage.show();
}
/**
* The main() method is ignored in correctly deployed JavaFX application.
*
* #param args the command line arguments
**/
public static void main(String[] args) {
launch(args);
}
}
The answer by Anvay for some reason didnt work for me, what i had to do to fix it was just some very small tweaks:
remove import data statement from listCellItem.fxml
as the comment below the post states in Data.java put hBox = fmxlLoader.load()
I also had a main class (intellij auto generated).
public class MainMain extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
FXMLLoader fxmlLoader = new
FXMLLoader(getClass().getResource("MainController.fxml"));
try
{
Parent root = fxmlLoader.load();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("Title");
primaryStage.show();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
launch(args);
}
I know this was probably obvious for most of the experts here, but these issues perplexed me for hours while i was debugging.

Resources