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
Related
Im working on a small JavaFX project. In one of my scenes, I want to dynamically add and remove a custom component I implemented, which extends from TitledPane, to respectively from an Accordion. This works all well and good, but after removing the pane from the Accordion the damn thing won't resize immidately, but only after I click somewhere on the gui. I prepared the following GIF to visualize the problem to you.
Can someone tell me why the accordion only resizes after I clicked somewhere on the gui interface and not immidiately? I mean auto resizing doesn't seem to be a problem, it just won't trigger...can someone tell me why that is the case? Maybe this is obvious, but I am not very familiar with JavaFX, so I am really stuck here. I also observed a similar behavior with other component, so maybe I am missing something fundamentally here.
UPDATE
Ok I created a minimal example for you to reproduce my problem. You can clone the repository on GitHub javafx-demo and try it out yourself. Doing this I noticed, that the Accordion resizes only if I click on it and not when I click anywhere else on the gui.
UPDATE 1
I simplified the example further. You can find the example in the GitHub repository above or see the code below:
App
public class App extends Application {
#Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource("parentView.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
ParentController
public class ParentController {
#FXML
private Accordion accordion;
public void onAddAction() {
var itemControl = new ItemControl();
EventHandler<ActionEvent> removeEventHandler = event -> {
accordion.getPanes().remove(itemControl);
};
itemControl.setOnRemoveProperty(removeEventHandler);
accordion.getPanes().add(itemControl);
}
}
Parent View
<StackPane xmlns="http://javafx.com/javafx/16"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.ParentController">
<Group StackPane.alignment="CENTER">
<VBox>
<Accordion fx:id="accordion"/>
<Button onAction="#onAddAction" text="Add"/>
</VBox>
</Group>
</StackPane>
ItemControl
public class ItemControl extends TitledPane {
private final UUID id = UUID.randomUUID();
private final ObjectProperty<EventHandler<ActionEvent>> onRemoveProperty = new SimpleObjectProperty<>();
#FXML
private Button removeButton;
public ItemControl() {
FXMLLoader fxmlLoader = new FXMLLoader(ItemControl.class.getResource("itemControl.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
}
#FXML
public void initialize() {
removeButton.onActionProperty().bind(onRemoveProperty);
}
public void setOnRemoveProperty(EventHandler<ActionEvent> onRemoveProperty) {
this.onRemoveProperty.set(onRemoveProperty);
}
// equals and hashCode omitted for brevity (id instance variable is used as identifier)
}
ItemControl FXML
<fx:root type="javafx.scene.control.TitledPane" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
<VBox>
<Button fx:id="removeButton" text="Remove"/>
</VBox>
</fx:root>
The behavior is a bug in AccordionSkin. The technical reason is that it keeps internal references to the current and previous expanded pane - both used in calculating the min/pref height - which are not updated correctly on removing the expanded pane. A fix would be to null those references if the panes are no longer part of the accordion, f.i. from the skin's listener to the panes list.
There is no clean way to work around this because all involved fields/methods are private - if we are allowed to go dirty, though, we can hack around the bug with reflection.
The basics:
extend AccordionSkin and let our accordion use the extended version
in the skin, override both computeMin/Pref/Height to check/fix the references before returning super
check: the panes should be contained in the accordion's panes
fix: if not, set the reference to null
Notes:
the reflective access to internal fields requires that the package is opened at runtime
the usual beware: tweaking/relying on implementation internals is highly version dependent and might/will break eventually
FXUtils is my local utility class for reflection, you have to replace it with your own implementation
The code:
public class SimpleLayoutAccordionOnRemove extends Application {
/**
* AccordionSkin that hacks the broken layout after remove of expanded pane.
*/
public static class HackedAccordionSkin extends AccordionSkin {
public HackedAccordionSkin(Accordion control) {
super(control);
}
#Override
protected double computeMinHeight(double width, double topInset, double rightInset,
double bottomInset, double leftInset) {
checkPaneFields();
return super.computeMinHeight(width, topInset, rightInset, bottomInset, leftInset);
}
#Override
protected double computePrefHeight(double width, double topInset, double rightInset,
double bottomInset, double leftInset) {
checkPaneFields();
return super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
}
private void checkPaneFields() {
checkPaneField("previousPane");
checkPaneField("expandedPane");
}
/**
* Check if the pane referenced by the field with the given name is contained
* in the accordion's panes and sets it to null if not.
*/
private void checkPaneField(String fieldName) {
TitledPane prev = (TitledPane) FXUtils.invokeGetFieldValue(AccordionSkin.class, this, fieldName);
if (!getSkinnable().getPanes().contains(prev)) {
FXUtils.invokeSetFieldValue(AccordionSkin.class, this, fieldName, null);
}
}
}
private Parent createContent() {
Accordion accordion = new Accordion() {
#Override
protected Skin<?> createDefaultSkin() {
return new HackedAccordionSkin(this);
}
};
Button add = new Button("add");
add.setOnAction(e -> {
addTitledPane(accordion);
});
VBox accBox = new VBox(accordion, add);
StackPane content = new StackPane(new Group(accBox));
return content;
}
int count;
private void addTitledPane(Accordion accordion) {
TitledPane pane = new TitledPane();
pane.setText("Pane " + count++);
Button remove = new Button("remove");
remove.setOnAction(e -> {
accordion.getPanes().remove(pane);
});
VBox box = new VBox(remove);
pane.setContent(box);
accordion.getPanes().add(pane);
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent(), 600, 400));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
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.
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.
I am new to Java and JavaFX all together, so please be patient with me if I am asking the wrong question here.
I am trying to add preloader Scene to an application I built: I was able to add the preloader using the code below. It's the default preloader and it looks very basic. So, here is my question. 1) how to add percentage progress status instead of progressbar. 2) Is it possible to load this progress bar on top of an FXML scene
Preloader
public class JavaFXPreloader3 extends Preloader {
ProgressBar bar;
Stage stage;
private Scene createPreloaderScene() throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("FXML.fxml"));
ProgressIndicator pi = new ProgressIndicator();
BorderPane p = new BorderPane();
p.setCenter(pi);
return new Scene(p, 300, 150);
}
#Override
public void start(Stage stage) throws Exception {
this.stage = stage;
stage.setScene(createPreloaderScene());
stage.show();
}
#Override
public void handleStateChangeNotification(StateChangeNotification scn) {
if (scn.getType() == StateChangeNotification.Type.BEFORE_START) {
stage.hide();
}
}
#Override
public void handleProgressNotification(ProgressNotification pn) {
bar.setProgress(pn.getProgress());
}
}
How do I return root scene instead of the BorderPane p including the Progress Indicator:
ProgressIndicator pi = new ProgressIndicator();
BorderPane p = new BorderPane();
p.setCenter(pi);
return new Scene(p, 300, 150);
This is a excellent tutorial for creating a PreLoader.
https://docs.oracle.com/javafx/2/deployment/preloaders.htm
I'm trying to implement a game on JavaFX. Moreover, I'm dealing with an FXML file so I have a main class and controller class. My question is how can I reach the objects of the main class from the controller class. To be more clear I will share a simple code.
This is main class:
public class JavaFXApplication1 extends Application {
#Override
public void start(Stage primaryStage) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("Risk3.fxml"));
// Main Pane
BorderPane borderPane = new BorderPane();
borderPane.setCenter(root);
// Main scene
Scene scene = new Scene(borderPane);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
So for example I want to reach root or borderPane from controller class which is:
public class SampleController implements Initializable {
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
Should I make root and borderPane global and static or is there any another way to reach them ?.
The root panel can simply reached from the FXML controller using
#FXML tag like any component.
<BorderPane xmlns:fx="http://javafx.com/fxml" fx:id="root">
...
</BorderPane>