In my JavaFX application I have to load many fxml files (200+) in the same time. I have decided to load them in background Task just like in https://stackoverflow.com/a/34878843 answear. Everything works fine (load time was acceptable) until JDK update. Newest version of JDK lengthened load time 3-4 times.
I have checked previous JDK releases and that problem appears from the JDK 8u92.
To test that issue I created new simple JavaFX FXML Application in Netbeans 8.1 and use only generated classes and fxml. Creating view from code works fine.
Application class:
public class FXMLLoaderTest extends Application {
private static Executor ex = Executors.newCachedThreadPool();
//private static Executor ex = Executors.newFixedThreadPool(400);
//private static Executor ex = Executors.newSingleThreadExecutor();
#Override
public void start(Stage stage) throws Exception {
VBox box = new VBox();
ScrollPane root = new ScrollPane(box);
Button b = new Button("GENERATE");
b.setOnAction(e -> {
IntStream.range(0, 1000).forEach(i -> {
Task<Parent> task = new Task<Parent>() {
#Override
protected Parent call() throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
Parent root = null;
try {
root = loader.load();
} catch (IOException ex) {
Logger.getLogger(Loader.class.getName()).log(Level.SEVERE, null, ex);
}
// StackPane root= new StackPane();
// Button click = new Button("Click");
// root.setPrefSize(300, 300);
// root.getChildren().add(click);
return root;
}
};
task.setOnSucceeded(ev -> {
final Parent parent = task.getValue();
box.getChildren().add(parent);
});
task.setOnFailed(ev -> task.getException().printStackTrace());
ex.execute(task);
});
});
box.getChildren().add(b);
Scene scene = new Scene(root, 400, 500);
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
FXMLDocument.fxml
<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxmlloader.FXMLDocumentController">
<children>
<Button layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="button" />
<Label layoutX="126" layoutY="120" minHeight="16" minWidth="69" fx:id="label" />
</children>
</AnchorPane>
FXMLDocumentController.java
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
}
}
I have tested this on several computers and result was always the same. On JDK 8u91 fxml files load fast. I have checked release note of 8u92 and I haven't found any changes in FXMLLoader class.
Has anybody encounter this issue? Mayby I am doing something wrong then please correct me.
Related
I'm using a loading screen in my application while a service is running a task in another thread.
After the service is done, I would like to close the loading screen.
But instead of calling something like window.hide() every time, I would like to have a binding between the service state and the visibility of the window.
Service runs --> loading screen visible
Service runs not --> loading screen invisible
The service has properties like onRunningProperty() or runningProperty() and the window has onShownProperty() or showingProperty() but I didn't manage to bind them.
How can I bind the visibility of the loading screen with the running state of a service, so that the loading screen is automatically shown, when the service runs and hidden, when the service is done?
Example:
HelloApplication.java
public class HelloApplication extends Application {
#Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 320, 240);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
HelloController.java
public class HelloController {
#FXML
private Label welcomeText;
private final Service<Void> service = new Service<>() {
#Override
protected Task<Void> createTask() {
return new Task<>() {
#Override
protected Void call() throws Exception {
Thread.sleep(3000);
return null;
}
};
}
};
public HelloController() {
service.setOnSucceeded(e -> {
// now I want to hide the loading screen
WaitController.waitController.waitLabel.getScene().getWindow().hide();
});
}
#FXML
protected void onHelloButtonClick() {
welcomeText.setText("Welcome to JavaFX Application!");
service.restart();
try {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getResource("wait.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 630, 400);
Stage stage = new Stage();
stage.setTitle("New Window");
stage.setScene(scene);
stage.show();
WaitController.waitController = fxmlLoader.getController();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
WaitController.java
public class WaitController {
#FXML
Label waitLabel;
public static WaitController waitController;
}
wait.fxml
<AnchorPane prefHeight="291.0" prefWidth="428.0" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.test.WaitController">
<children>
<Label fx:id="waitLabel" layoutX="161.0" layoutY="100.0" prefHeight="92.0" prefWidth="105.0" text="Wait..." textAlignment="CENTER">
<font>
<Font size="18.0" />
</font>
</Label>
</children>
</AnchorPane>
hello-view.fxml
<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml"
fx:controller="com.example.test.HelloController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
</padding>
<Label fx:id="welcomeText"/>
<Button text="Hello!" onAction="#onHelloButtonClick"/>
</VBox>
You won't be able to use bindings for this. There are no writable properties of Window that control whether or not it's showing. There is, of course, the showing property, but it is read-only. In other words, there's no appropriate property of Window that you can bind to the service's running property.
What you can do, however, is listen to the service's running property and call show() / hide() on the window instance as appropriate. For example:
import javafx.application.Application;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.Window;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
var service = new ServiceImpl();
setupWindowForService(primaryStage, service);
var button = new Button("Start service");
button.disableProperty().bind(service.runningProperty());
button.setOnAction(e -> {
e.consume();
service.restart();
});
primaryStage.setScene(new Scene(new StackPane(button), 600, 400));
primaryStage.show();
}
private void setupWindowForService(Window owner, Service<?> service) {
var window = new Stage();
window.initOwner(owner);
window.initModality(Modality.WINDOW_MODAL);
window.setTitle("Service Window");
window.setScene(new Scene(new StackPane(new Label("Service running...")), 300, 150));
window.setOnCloseRequest(e -> {
if (service.isRunning()) {
// prevents user from closing the window while service is
// running. Perhaps it would make more sense to cancel the
// service?
e.consume();
}
});
// the code that shows and hides the window based on the service's state
service.runningProperty().addListener((obs, wasRunning, isRunning) -> {
if (isRunning) {
window.show();
} else {
window.hide();
}
});
}
private static class ServiceImpl extends Service<Void> {
#Override protected Task<Void> createTask() {
return new Task<>() {
#Override protected Void call() throws Exception {
int max = 3_000;
for (int i = 0; i < max; i++) {
Thread.sleep(1L);
}
return null;
}
};
}
}
}
I'm just a complete beginner when it comes to programming or java.
So for the start my plan was to create a window useing JavaFX(combined with scene builder) where I do have a button that leads me to another window where i do have a combobox. I googled for hours now to find a way to fill that combobox with choices but all the solutions i found don't work for me. Thats why I think I made some mistakes here and I hope you can somehow help me. Or at list give me a hint what I should learn/read to get to the solution myself.
So to start with, here's my main.java code where I build my first stage.
main.java:
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
Parent root= FXMLLoader.load(getClass().getResource("Scene-Hauptmenu.fxml"));
primaryStage.setTitle("Fishbase");
primaryStage.sizeToScene();
primaryStage.setResizable(false);
primaryStage.setScene(new Scene(root));
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
In my "Scene-Hauptmenu.fxml" all that matters is the button that leads me to my second window:
Scene-Hauptmenu.fxml:
<Button id="btn_gefangen" fx:id="btn_gefangen" mnemonicParsing="false" onAction="#gefangen" text="Ich habe Fische gefangen!" GridPane.rowIndex="1" />
So far everything works fine and I can switch to my second window without a problem. But I think my main problem lies within my controller class so here it is.
MyController.java:
public class MyController implements Initializable{
private Node node;
private Stage stage;
private Scene scene;
private FXMLLoader fxmlLoader;
private Parent root;
#FXML
private Button btn_gefangen;
#FXML
private ComboBox<String> chobo_fisch;
#FXML
private Button btn_gefangen_zurueck;
#Override
public void initialize(URL location, ResourceBundle resources) {
}
public void gefangen(ActionEvent event) throws IOException{
node = (Node) event.getSource();
stage = (Stage) node.getScene().getWindow();
scene = stage.getScene();
fxmlLoader = new FXMLLoader (getClass().getResource("gefangen.fxml"));
root = (Parent) fxmlLoader.load();
scene.setRoot(root);
stage.sizeToScene();
stage.setTitle("Fische eintragen");
}
public void gefangen_zurueck(ActionEvent event) throws IOException{
node = (Node) event.getSource();
stage = (Stage) node.getScene().getWindow();
scene = stage.getScene();
fxmlLoader = new FXMLLoader (getClass().getResource("Scene-Hauptmenu.fxml"));
root = (Parent) fxmlLoader.load();
scene.setRoot(root);
stage.sizeToScene();
stage.setTitle("Fishbase");
}
}
So the button "btn_gefangen" leads me to that other window where i do have the combobox with the fx:id "chobo_fisch".
gefangen.fxml:
<ComboBox fx:id="chobo_Fisch" prefWidth="150.0"/>
So I googled for hours but I still didnt find any solution to fill the combobox with choices that works with my code. What did I do wrong? Can anyone help me here?
Best regards
Jannik
I found three variants, depending on your setup:
1st variant
// Weekdays
String week_days[] =
{ "Monday", "Tuesday", "Wednesday",
"Thrusday", "Friday" };
// Create a combo box
ComboBox combo_box = new ComboBox(FXCollections.observableArrayList(week_days));
(Soure: https://www.geeksforgeeks.org/javafx-combobox-with-examples/)
2nd variant
final ComboBox emailComboBox = new ComboBox();
emailComboBox.getItems().addAll(
"jacob.smith#example.com",
"isabella.johnson#example.com",
"ethan.williams#example.com",
"emma.jones#example.com",
"michael.brown#example.com"
);
Source: (https://docs.oracle.com/javafx/2/ui_controls/combo-box.htm)
3rd variant (for FXML)
<ComboBox fx:id="someName">
<items>
<FXCollections fx:factory="observableArrayList">
<String fx:value="1"/>
<String fx:value="2"/>
<String fx:value="3"/>
<String fx:value="4"/>
</FXCollections>
</items>
<value>
<String fx:value="1"/>
</value>
</ComboBox>
Edit
As mentioned by fabian you should make sure to include the FXML imports:
<?import javafx.collections.FXCollections?>
<?import java.lang.String?>
The second one may not be needed.
I'm new to those stuff but I think this is how it should look or at least close too if I understood what you wanted.
Example below:
ComboBox<String> stuff = new ComboBox<>();
stuff.getItems().addAll("1","2","5","10");
Note: I'm new to stackoverflow.
Try this:
ObservableList<String> items = FXCollections.observableArrayList();
items.add("a");
items.add("b");
chobo_fisch.getItems().addAll(items);
Your combobox must be filled with items (in your case String):
List<String> list = new ArrayList<String>();
list.add("Item 1");
list.add("Item 2");
chobo_fisch.setItems(FXCollections.observableArrayList(list));
If you use a combobox of a more complex object, you could use a cellfactory to choose the value that is displayed :
chobo_fisch.setCellFactory(obj -> new ChoboFischListCell());
chobo_fisch.setButtonCell(new ChoboFischListCell());
where ChoboFischListCell is a class that extends ListCell and where you implement which field of your object should be displayed.
I want to set my SplitPane divider to a "specific" starting position so, that takes into consideration the components of the window.
There is a fix starter sized TableView, but the window size can be different. So I would like to set the divider position at start so, that the table is fully visible, and right next to it stands the divider.
I have the following code so far in the public void initialize():
Controller:
#FXML
SplitPane splitPane;
#FXML
TreeTableView treeTable;
public void initialize() {
getStage().addEventHandler(WindowEvent.WINDOW_SHOWN, new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
double tableWidth = treeTable.getWidth();
double stageWidth = getStage().getWidth();
splitPane.setDividerPositions(tableWidth / stageWidth);
}
});
}
FXML:
<SplitPane fx:id="splitPane">
<items>
<TreeTableView fx:id="treeTable" prefWidth="280">
<!-- table -->
</TreeTableView>
<AnchorPane fx:id="anchorPane">
<!-- anchor pane -->
</AnchorPane>
</items>
</SplitPane>
But it's not working, beacuse the Stage is null at this moment.
So the problem is everything isn't loaded yet so to fix this you can call it at the end of your Main start function as below
Main:
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
FXMLLoader loader = new FXMLLoader(getClass().getResource("/Sample.fxml"));
Scene scene = new Scene(loader.load());
primaryStage.setScene(scene);
Controller controller = loader.getController();
controller.setStartingPosition(primaryStage);
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}
Controller:
public class Controller {
public SplitPane splitPane;
public TreeTableView treeTable;
public AnchorPane anchorPane;
public void setStartingPosition(Stage stage){
stage.addEventHandler(WindowEvent.WINDOW_SHOWN, new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
double tableWidth = treeTable.getWidth();
double stageWidth = stage.getWidth();
splitPane.setDividerPositions(tableWidth / stageWidth);
}
});
}
}
I cannot tell if this works as I do not have the "other components" of which you speak so let me know if this works it looks the same both ways for me
I am developing a small, fake, Mail Client in JavaFXML.
It offers a Listview with messages written on a txt file, a TextArea which prints selected message and some buttons.
Here's an Image of the Main View: https://ibb.co/iKN2rm
I already took care of "New Message" Button, which is launching a new FXML View and works well.
Here's an Image of the "New_Message" View: https://ibb.co/hQf5Bm
Now I'm trying to implement the "Reply" button, which should launch the same View as before (New Message) but set on all three TextFields strings taken from the Main View, such as Message's text, recipient and Message Argument.
Below the New Message Button Handler:
private void handle_new(ActionEvent event) {
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/Client/Resources/new_utente1.fxml"));
Parent root1 = (Parent) fxmlLoader.load();
Stage stage = new Stage();
stage.setTitle("New Message");
stage.setScene(new Scene(root1));
stage.show();
} catch (Exception ecc) {
System.out.println("ERROR: " + ecc.getMessage());
}
}
I tried to implement the handle_reply method, but I'm not able to add parameters because FXML file won't find the method if I do so.
Below a small part of the FXML file:
<TextArea fx:id = "testo" editable="false" layoutX="283.0" layoutY="53.0" prefHeight="450.0" prefWidth="490.0" promptText="Select a message and it will be displayed here..." />
<Button id="nuovo" layoutX="46.0" layoutY="521.0" onAction = "#handle_new" mnemonicParsing="false" prefHeight="25.0" prefWidth="173.0" text="New Message" />
<Button id="reply" layoutX="283.0" layoutY="521.0" onAction = "#handle_reply" mnemonicParsing="false" prefHeight="25.0" prefWidth="132.0" text="Reply" />
My question is: How do I implement the "handle_reply" method as described before?
Thank you
Create a Controller class
then connect this class to your view
private void handle_new(ActionEvent event) {
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/Client/Resources/new_utente1.fxml"));
Parent root1 = (Parent) fxmlLoader.load();
CController ctr = fxmlLoader.getController();
ctr.setLabelText("asdsad");
Stage stage = new Stage();
stage.setTitle("New Message");
stage.setScene(new Scene(root1));
stage.show();
} catch (Exception ecc) {
System.out.println("ERROR: " + ecc.getMessage());
}
}
and in your Controllers you'll do
public class CController implements Initializable {
#FXML TextArea testo;
#Override
public void initialize(URL location, ResourceBundle resources) {}
public void setLabelText(String text){testo.setText(text);}
}
Unfortunately it's not possible to use handlers with parameters. You should use different handler in each case.
Had this problem some time ago too. As a solution you could perform small refactoring to minimize code duplicating.
And if you know javascript you can play with this instead of using java handlers: https://docs.oracle.com/javase/8/javafx/api/javafx/fxml/doc-files/introduction_to_fxml.html#scripting
Edit: also you can check this answer: https://stackoverflow.com/a/37902780/5572007 (pretty the same)
I've got the following classes:
Main:
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
BorderPane root = FXMLLoader.load(getClass().getResource("../view/PersonOverview.fxml"));
AnchorPane view2 = FXMLLoader.load(getClass().getResource("../view/view2.fxml"));
root.setLeft(view2);
primaryStage.setScene(new Scene(root, 1000, 600));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
TreeController:
public class TreeController implements Initializable {
//Set icon for folder
Node folderIcon = new ImageView(new Image(this.getClass().getResourceAsStream("../icon/icon.jpg")));
//Set root
TreeItem<String> root;
#FXML TreeView<String> tree;
//Set other Items
private TreeItem<String> item1 = new TreeItem<String>("item1", folderIcon);
private TreeItem<String> item2 = new TreeItem<String>("item2", folderIcon);
private TreeItem<String> item3 = new TreeItem<String>("item3", folderIcon);
private TreeItem<String> item4 = new TreeItem<String>("item4", folderIcon);
private TreeItem<String> item5 = new TreeItem<String>("item5", folderIcon);
//Add Children to root
private void makeChildren() {
root.getChildren().add(item1);
root.getChildren().add(item2);
root.getChildren().add(item3);
root.getChildren().add(item4);
root.getChildren().add(item5);
}
#Override
public void initialize(URL location, ResourceBundle resources) {
root = new TreeItem<String>("root", folderIcon);
makeChildren();
root.setExpanded(true);
tree.setRoot(root);
}
}
And of course my view2 fxml file:
<AnchorPane
maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity"
prefHeight="400.0" prefWidth="354.0"
xmlns="http://javafx.com/javafx/8.0.40"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="control.TreeController">
<children>
<TreeView
layoutX="69.0" layoutY="118.0"
prefHeight="400.0" prefWidth="354.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
Now the problem I have is that it will throw a Nullpointer Exception at tree.setRoot(root);
And a ConstructLoad Exception in my Main at:
AnchorPane view2 = FXMLLoader.load(getClass().getResource("../view/view2.fxml"));
I'm still learning this stuff but I was told that when using FXML, you don't need to initialize TreeViews using "new" as the #FXML annotation will already take care of this with tree.setRoot(root).
Sorry for such a noobish question but I've been googling for the past 2 hours and haven't gotten any wiser.
I'm still learning this stuff but I was told that when using FXML, you don't need to initialize TreeViews using "new" as the #FXML annotation will already take care of this with tree.setRoot(root).
You guessed right, but in order for JavaFX to inject your Treeview (= make the "new" for you) you need to declare something like:
<Treeview fx:id="tree" />
in view2.fxml.
With the fx:id attribute setted to the same name as your Treeview variable in Java code.