#Override
public void start(Stage stage) throws Exception {
BorderPane root = FXMLLoader.load(getClass().getClassLoader().getResource("mainView.fxml"));
When i run this it doesnt show the injected views
Im building a new application with JavaFX for the main page im using 3 views build by 3 fxml files
each view has its controller. for the main page i want to inject the three fxml files in a mainView.fxml via fx:include the mainView.fxml has also a controller how can i do that?
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.text.Font?>
<fx:root alignment="CENTER_LEFT" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="93.0" prefWidth="600.0" style="-fx-min-width: 800; -fx-min-height: 100; -fx-spacing: 30;" type="javafx.scene.layout.HBox" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label fx:id="title" alignment="CENTER" prefHeight="91.0" prefWidth="179.0" style="-fx-label-padding: 20; -fx-line-spacing: 20;" text="News" textFill="#00a4f2">
<font>
<Font name="Arial Black" size="36.0" />
</font>
<opaqueInsets>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</opaqueInsets></Label>
<Button fx:id="refresh" alignment="CENTER" contentDisplay="CENTER" mnemonicParsing="false" style="-fx-alignment: CENTER; -fx-background-color: #66a6ff;" text="Button" textAlignment="CENTER">
<opaqueInsets>
<Insets bottom="30.0" left="30.0" right="30.0" top="30.0" />
</opaqueInsets></Button>
<Button fx:id="stat" alignment="CENTER" mnemonicParsing="false" style="-fx-background-color: #feada6;" text="Button">
<opaqueInsets>
<Insets bottom="30.0" left="30.0" right="30.0" top="30.0" />
</opaqueInsets></Button>
</children>
</fx:root>
This is per example the topView.fxml
package ch.bfh.spacenews;
import java.io.IOException;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
public class TopBarController extends HBox {
#FXML
public Label title;
#FXML
public Button refresh;
#FXML
public Button stat;
TopBarController(){
FXMLLoader load = new FXMLLoader(getClass().getClassLoader().getResource("topView.fxml"));
load.setRoot(this);
load.setController(this);
try {
System.out.println("TopBarController");
load.load();
}catch(IOException e) {
e.printStackTrace();
}
}
}
This is the corresponding controller of the topView.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<BorderPane fx:controller="ch.bfh.spacenews.mainController" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
<top>
<fx:include fx:id="topBar" source="topView.fxml"/>
</top>
<center>
<fx:include fx:id="article" source="sample.fxml"/>
</center>
<right>
<fx:include fx:id="seacrh" source="searchView.fxml"/>
</right>
</BorderPane>
This is where i want to inject the topView.fxml
package ch.bfh.spacenews;
import javafx.fxml.FXML;
public class mainController {
#FXML
TopBarController topBarController;
#FXML
ArticleController articleController;
#FXML
SearchController searchController;
#FXML
public void initialize() {
}
}
And this is the Controller of the mainView.fxml where i want to inject the topView.fxml
In the FXML custom component pattern the controller classes also serve as the wrapper for the view, by subclassing an appropriate Node subclass. This means you can just instantiate them directly in the FXML via the FXMLLoader. For example, the <TopBarController> element instructs the FXMLLoader to instantiate the TopBarController by calling its no-argument constructor. That constructor, as per your code, loads topView.fxml, etc. There is no need to use <fx:include>, which is an instruction for the FXMLLoader to load another FXML file, since you already have code in your TopBarController to do that.
So your main view FXML file should look like:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<?import ch.bfh.spacenews.TopBarController?>
<!-- other imports... -->
<BorderPane fx:controller="ch.bfh.spacenews.MainController" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
<top>
<TopBarController fx:id="topBar" />
</top>
<center>
<ArticleController fx:id="article" />
</center>
<right>
<SearchController fx:id="search" />
</right>
</BorderPane>
And the corresponding controller is
package ch.bfh.spacenews;
import javafx.fxml.FXML;
public class MainController {
#FXML
TopBarController topBar;
#FXML
ArticleController article;
#FXML
SearchController search;
#FXML
public void initialize() {
}
}
Note that it's really critical to follow standard naming conventions here. The FXMLLoader explicitly relies on the case of an element to determine if it's referring to a class name or a property name.
Main class : Launch.java
public class Launch extends Application {
#Override
public void start(Stage stage) throws Exception {
Controller.show(stage);
}
public static void main(String[] args) {
launch(args);
}
}
You can add view from controller :
public class Controller {
public BorderPane mainPane; // fx:id of your pane
public static Stage current;
public static void show(Stage stage) {
this.current = stage;
Scene scene = null;
BorderPane pane = null;
try {
scene = new Scene(FXMLLoader.load(getClass().getResource("mainView.fxml")));
pane = (BorderPane) scene.lookup("#mainPane");
// Add .fxml view to mainView
pane.setCenter(FXMLLoader.load(getClass().getResource("sample.fxml")));
pane.setTop(FXMLLoader.load(getClass().getResource("topView.fxml")));
pane.setRight(FXMLLoader.load(getClass().getResource("searchView.fxml")));
} catch (IOException e) {
e.printStackTrace();
}
stage.setTitle("MainView");
stage.setScene(scene);
stage.show();
}
}
Related
I have a button which display a menu containing only one custom menu item. This menu item contains a textfield and a button. Here is my code:
helloApplication.java:
package com.example.demo;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
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:
package com.example.demo;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class HelloController {
#FXML
private Label welcomeText;
#FXML
protected void onHelloButtonClick() {
welcomeText.setText("Welcome to JavaFX Application!");
}
}
hello-view.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CustomMenuItem?>
<?import javafx.scene.control.MenuButton?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/18" fx:controller="com.example.demo.HelloController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
<children>
<MenuButton mnemonicParsing="false" text="MenuButton">
<items>
<CustomMenuItem hideOnClick="false" mnemonicParsing="false" text="Unspecified Action">
<content>
<HBox prefHeight="100.0" prefWidth="200.0">
<children>
<TextField />
<Button mnemonicParsing="false" text="Button" />
</children>
</HBox>
</content>
</CustomMenuItem>
</items>
</MenuButton>
</children>
</VBox>
This menu I want this menu item to be unselectable, just a pane poping to display the textfield. It is already not hidden when clicked. The problem is that when i enter some text in the textfield, and move the mouse cursor, the focus in the text field is lost because the menu item takes the focus:
How can I prevent the menu item to react and let the text field keep the focus ? Is a custom menu item a good solution if I want only one menu item containing components ?
if i run the Main Class my JavaFX is not opening. A Java symbol appears in my menubar but no window appears. I don't get any error in the console. I am on a Mac machine and I am using eclipse, together with e(fx)clipes and Gluon Scene Builder. The code is actually 1 by 1 written down from a tutorial. In the tutorial the run configuration JRE is set to JavaSE-1.8. - however, I tested every available JRE, without any success. What do I need to adjust?
Main Class:
package application;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
public class Main extends Application {
private Stage primaryStage;
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
}
public void mainWindow() {
try {
FXMLLoader loader = new FXMLLoader(Main.class.getResource("MainWindow.fxml"));
AnchorPane pane = loader.load();
primaryStage.setMinHeight(400.00);
primaryStage.setMinWidth(500.00);
MainWindowController mainWindowController = loader.getController();
mainWindowController.setMain(this);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Controller class:
package application;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
public class MainWindowController {
public Main main;
#FXML private Label label;
#FXML private TextField field;
#FXML private Button clear;
#FXML private Button changeText;
public void setMain(Main main) {
// TODO Auto-generated method stub
this.main = main;
}
#FXML
public void handleChangeText() {
String text = field.getText();
label.setText(text);
}
#FXML
public void handleClear() {
field.clear();
}
}
FXML file:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<AnchorPane prefHeight="400.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainWindowController">
<children>
<VBox alignment="CENTER" layoutY="83.0" spacing="30.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label fx:id="label" text="Label">
<font>
<Font size="24.0" />
</font></Label>
<HBox alignment="CENTER">
<children>
<TextField fx:id="field" prefWidth="220.0" />
</children>
</HBox>
<HBox alignment="CENTER" spacing="20.0">
<children>
<Button fx:id="changeText" mnemonicParsing="false" onAction="#handleChangeText" text="Change Text" />
<Button fx:id="clear" mnemonicParsing="false" onAction="#handleClear" text="Clear" />
</children>
</HBox>
</children>
</VBox>
</children>
</AnchorPane>
You didn't do anything in you start method. All you did was set your primaryStage variable, you should call your mainWindow method after that.
I created this custom component:
public class IconButton extends Button {
#FXML private ImageView imageView;
private IconButtonState state;
private String fullIconUrl;
private String outlineIconUrl;
public IconButton(#NamedArg("fullIconUrl") String fullIconUrl,
#NamedArg("outlineIconUrl") String outlineIconUrl) {
URL url = getClass().getResource(View.ICON_BUTTON.getFileName());
FXMLLoader loader = new FXMLLoader(url);
loader.setRoot(this);
loader.setController(this);
state = IconButtonState.NOT_INITIALIZED;
this.fullIconUrl = fullIconUrl;
this.outlineIconUrl = outlineIconUrl;
try {
loader.load();
} catch (IOException exception) {
exception.printStackTrace();
throw new RuntimeException(exception);
}
}
#FXML
public void initialize() {
this.state = IconButtonState.ACTIVE;
String url = buildUrl(fullIconUrl);
Image image = new Image(url);
imageView.setImage(image);
}
}
<!-- icon-button.fxml -->
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<fx:root mnemonicParsing="false" prefHeight="65.0" prefWidth="98.0" style="-fx-background-color: transparent;" type="Button" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
<graphic>
<ImageView fx:id="imageView" fitHeight="64.0" fitWidth="48.0" pickOnBounds="true" preserveRatio="true">
</ImageView>
</graphic>
</fx:root>
Then, I instantiated my IconButton component in another fxml file like this:
<!-- home.fxml -->
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.layout.*?>
<?import agill.deshopp.components.*?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="768.0" prefWidth="1024.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
<top>
<HBox styleClass="app-bar" stylesheets="#../css/home.css">
<padding>
<Insets bottom="15.0" top="15.0" />
</padding>
<children>
<IconButton fullIconUrl="form-full" outlineIconUrl="form-outline"></IconButton>
<IconButton fullIconUrl="message-full" outlineIconUrl="message-outline"></IconButton>
<IconButton fullIconUrl="chart-full" outlineIconUrl="chart-outline"></IconButton>
<Region HBox.hgrow="ALWAYS"></Region>
<IconButton fullIconUrl="settings-full" outlineIconUrl="settings-outline"></IconButton>
</children>
</HBox>
</top>
<center>
<Pane BorderPane.alignment="CENTER" />
</center>
</BorderPane>
The code runs fine and the screen renders as expected. However, I can't open the file in SceneBuilder. It prompts me with this exception:
java.lang.RuntimeException: Cannot create instance of agill.deshopp.components.IconButton with given set of properties: [fullIconUrl, outlineIconUrl]
javafx.fxml.LoadException:
/home/allan/IdeaProjects/california/src/main/resources/agill/deshopp/fxml/home.fxml:14
How do I fix this?
The code posted in the question is not mre and can not be invoked.
However the following code is an mre. It runs with no exceptions.
Fxml files can be edited using ScreenBuilder.
Modify it to your needs to find out what's wrong in the code posted in the question.
Note that form-full throws exception so I used form_full and that the fxml assignment should include $ sign: fullIconUrl="$form_full".
package fx_tests.test;
import java.io.IOException;
import java.net.URL;
import javafx.beans.NamedArg;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
public class IconButton extends Button {
#FXML private ImageView imageView;
private final String fullIconUrl;
public IconButton(#NamedArg("fullIconUrl") String fullIconUrl) {
this.fullIconUrl = fullIconUrl;
URL url = getClass().getResource("icon-button.fxml");
FXMLLoader loader = new FXMLLoader(url);
loader.setRoot(this);
loader.setController(this);
try {
loader.load();
} catch (IOException exception) {
exception.printStackTrace();
}
}
#FXML
public void initialize() {
Image image = new Image(fullIconUrl);
imageView.setImage(image);
}
}
Home.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.layout.*?>
<?import fx_tests.test.*?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="168.0"
prefWidth="124.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
<top>
<HBox>
<padding>
<Insets bottom="15.0" top="15.0" />
</padding>
<children>
<IconButton fullIconUrl="$form_full"></IconButton>
</children>
</HBox>
</top>
<center>
<Pane BorderPane.alignment="CENTER" />
</center>
</BorderPane>
icon-button.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<fx:root mnemonicParsing="false" prefHeight="65.0" prefWidth="98.0" style="-fx-background-color: transparent;"
type="Button" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
<graphic>
<ImageView fx:id="imageView" fitHeight="64.0" fitWidth="48.0" pickOnBounds="true" preserveRatio="true">
</ImageView>
</graphic>
</fx:root>
Test with:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
private static final String IMAGE = "https://www.shareicon.net/data/128x128/2015/03/28/14104_animal_256x256.png";
#Override
public void start(Stage currentStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Home.fxml"));
loader.getNamespace().put("form_full", IMAGE);
Parent root=loader.load();
currentStage.setScene(new Scene(root));
currentStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
When you create a custom component in JavaFX, SceneBuilder need to know the definition of this component so it can load it. I have a few custom components of my own and had to export them as a jar, and import them into SceneBuilder. Here is an answer that gives instructions on how to do this:
Adding a custom component to SceneBuilder 2.0
One catch. SceneBuilder only supports up to Java 11. So your custom component must be built and exported to a jar with this version. I had problems with Java 14 and had to backport my specific JavaFX components to Java 11. Good luck!
In my application I have declared a custom component like this:
#DefaultProperty("todoItems")
public class TodoItemsVBox extends VBox {
private ObservableList<TodoItem> todoItems;
// Setter/Getter omitted
}
and now somewhere in the fxml I want to use the TodoItemsVBox component like this:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<BorderPane prefHeight="600" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.todolist.controller.TodoListController"
stylesheets="#../css/app.css">
<top>
<HBox spacing="10.0">
<TextField fx:id="input" layoutX="35.0" layoutY="64.0" prefWidth="431.0" promptText="Enter todo task" HBox.hgrow="ALWAYS" onAction="#addTask"/>
<Button layoutX="216.0" layoutY="107.0" mnemonicParsing="false" onAction="#addTask" prefHeight="27.0" prefWidth="70.0" text="Add" HBox.hgrow="ALWAYS" />
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</HBox>
</top>
<center>
<ScrollPane fitToHeight="true" fitToWidth="true" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<TodoItemsVBox fx:id="todoItemsVBox" todoItems="${todoTasks}"/>
</ScrollPane>
</center>
... so as we can see the fxml has it's controller TodoListController
public class TodoListController implements {
private final ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList(/*Fill in the collection somehow - for now doesn't matter*/);
#FXML
private TodoItemsVBox todoItemsVBox;
// Setter/Getter omitted
}
So, here what I want to do: pass todoTasks into TodoItemsVBox defined in the FXML via such construction: todoItems="${todoTasks}" ---- unfortunately this doesn't work as I expected, because fxml files load before controllers initialised so todoTasks is always null. I also tried #NamedArg with one arg constructor in TodoItemsVBox - it even fails with exception: "Cannot bind to untyped object.”.
Could some one suggest a solution how to pass a collection of objects, defined in a controller, into a custom component via it's parameters?
There are two issues with the code as you have it:
For FXML expression binding, you need to expose properties from your class, not just the values themselves. This applies to ObservableLists as well as regular values. So your TodoItemsVBox class needs to expose a ListProperty todoItemsProperty()
FXML expression bindings (i.e. ${todoTasks}) reference the FXMLLoader's namespace, not the controller. The controller is automatically injected into the namespace (with key "controller"), so, given that the task list is stored in your controller (which is not necessarily a good idea) you can use ${controller.todoTasks} here.
Here's a minimal, complete version of your app which works.
A basic TodoItem.java:
public class TodoItem {
private final String name ;
public TodoItem(String name) {
this.name = name ;
}
public String getName() {
return name ;
}
}
A TodoItemsVBox that exposes the list as a property:
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
public class TodoItemsVBox extends VBox {
private ListProperty<TodoItem> todoItems = new SimpleListProperty<>();
public TodoItemsVBox() {
// not efficient but works for demo:
todoItems.addListener((Change<? extends TodoItem> c) -> rebuildView());
}
private void rebuildView() {
getChildren().clear();
todoItems.stream()
.map(TodoItem::getName)
.map(Label::new)
.forEach(getChildren()::add);
}
public ListProperty<TodoItem> todoItemsProperty() {
return todoItems ;
}
public ObservableList<TodoItem> getTodoItems() {
return todoItemsProperty().get() ;
}
}
A simple controller:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
public class TodoListController {
private ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList();
// not actually needed...
#FXML
private TodoItemsVBox todoItemsVBox;
#FXML
private TextField input ;
public ObservableList<TodoItem> getTodoTasks() {
return todoTasks;
}
#FXML
private void addTask() {
todoTasks.add(new TodoItem(input.getText()));
}
}
The FXML file (TodoList.fxml):
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import org.jamesd.examples.TodoItemsVBox ?>
<BorderPane prefHeight="600" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.todolist.controller.TodoListController"
>
<top>
<HBox spacing="10.0">
<TextField fx:id="input" promptText="Enter todo task" HBox.hgrow="ALWAYS" onAction="#addTask"/>
<Button onAction="#addTask" text="Add" HBox.hgrow="ALWAYS" />
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</HBox>
</top>
<center>
<ScrollPane fitToWidth="true" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<TodoItemsVBox fx:id="todoItemsVBox" todoItems="${controller.todoTasks}"/>
</ScrollPane>
</center>
</BorderPane>
And finally the application class:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class TodoApp extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("TodoList.fxml"));
Scene scene = new Scene(loader.load());
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
Really, a controller is no place to be storing data; you should have a separate model class to do that, which is shared between the controller and view. This is reasonably straightforward to do here; you just need to do a little more work with the FXMLLoader (namely putting the model in the namespace, and manually creating and setting the controller).
For example:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class TodoModel {
private ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList();
public ObservableList<TodoItem> getTodoTasks() {
return todoTasks;
}
}
Then your controller becomes:
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
public class TodoListController {
// not actually needed...
#FXML
private TodoItemsVBox todoItemsVBox;
#FXML
private TextField input ;
private TodoModel model ;
public TodoModel getModel() {
return model;
}
public void setModel(TodoModel model) {
this.model = model;
}
#FXML
private void addTask() {
model.getTodoTasks().add(new TodoItem(input.getText()));
}
}
Modify the FXML to use
<TodoItemsVBox fx:id="todoItemsVBox" todoItems="${model.todoTasks}"/>
And finally assemble the application with
public void start(Stage primaryStage) throws Exception {
TodoModel model = new TodoModel();
FXMLLoader loader = new FXMLLoader(getClass().getResource("TodoList.fxml"));
loader.getNamespace().put("model", model);
Scene scene = new Scene(loader.load());
TodoListController controller = loader.getController();
controller.setModel(model);
primaryStage.setScene(scene);
primaryStage.show();
}
The advantage to this approach is that your data are now separated from the UI (both view and controller), which becomes essential if you want to access the same data in another part of the UI (which would use another FXML and another controller).
Currently i have TabPane with 3 active tabs. I have to manually switch between them, which isn't ideal. What i would like to do is replace the TabPane all together and have one scene inside the stage, which would then switch to next scene (From Tab1, to Tab2, to Tab3) upon press of a button.
It is important to maintain the set label text functionality.
Tab1
Tab2
Main.java
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("../view/Main.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
MainController.java
package controller;
import javafx.fxml.FXML;
import controller.tab.Tab1Controller;
import controller.tab.Tab2Controller;
import controller.tab.Tab3Controller;
public class MainController {
#FXML Tab1Controller tab1Controller;
#FXML Tab2Controller tab2Controller;
#FXML Tab3Controller tab3Controller;
public void initialize() {
tab1Controller.init(this);
tab2Controller.init(this);
tab3Controller.init(this);
}
public void setTab2LabelText(String text) {
tab3Controller.lbl3.setText(text);
tab2Controller.lbl2.setText(text);
}
}
Tab1Controller.java
package controller.tab;
import controller.MainController;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import java.io.IOException;
public class Tab1Controller {
private MainController main;
#FXML public Label lbl1;
#FXML private Button btn1Send;
#FXML private void btn1SendClicked(ActionEvent event) throws IOException {
main.setTab2LabelText("abc");
}
public void init(MainController mainController) {
main = mainController;
}
}
Tab2Controller.java
package controller.tab;
import controller.MainController;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class Tab2Controller {
private MainController main;
#FXML public Label lbl2;
public void init(MainController mainController) {
main = mainController;
}
}
Tab3Controller.java
package controller.tab;
import controller.MainController;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class Tab3Controller {
private MainController main;
#FXML public Label lbl3;
public void init(MainController mainController) {
main = mainController;
}
}
Main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="432.0" prefWidth="443.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.MainController">
<children>
<TabPane prefHeight="299.0" prefWidth="309.0" tabClosingPolicy="UNAVAILABLE" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<tabs>
<Tab closable="false" text="Tab 1">
<content>
<fx:include fx:id="tab1" source="tab/Tab1.fxml" />
</content></Tab>
<Tab closable="false" text="Tab 2">
<content>
<fx:include fx:id="tab2" source="tab/Tab2.fxml" />
</content></Tab>
<Tab closable="false" text="Tab 3">
<content>
<fx:include fx:id="tab3" source="tab/Tab3.fxml" />
</content></Tab>
</tabs>
</TabPane>
</children>
</AnchorPane>
Tab2.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="206.0" prefWidth="226.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.tab.Tab1Controller">
<children>
<Button fx:id="btn1Send" layoutX="42.0" layoutY="74.0" mnemonicParsing="false" onAction="#btn1SendClicked" prefHeight="58.0" prefWidth="142.0" text="Send to Tab2 & Tab3" />
</children>
</AnchorPane>
Tab2.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="206.0" prefWidth="226.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.tab.Tab2Controller">
<children>
<Label fx:id="lbl2" alignment="CENTER" layoutX="37.0" layoutY="46.0" prefHeight="17.0" prefWidth="152.0" text="Default Tab2 text" />
</children>
</AnchorPane>
Tab3.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="206.0" prefWidth="226.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.tab.Tab3Controller">
<children>
<Label fx:id="lbl3" alignment="CENTER" layoutX="37.0" layoutY="46.0" prefHeight="17.0" prefWidth="152.0" text="Default Tab3 text" />
</children>
</AnchorPane>
Here is a example
fxml
create 3 panes, with its own button and label
<?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="380.0" prefWidth="387.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ask.FXMLDocumentController">
<children>
<Pane fx:id="p3" prefHeight="380.0" prefWidth="387.0" visible="false">
<children>
<Label layoutX="184.0" layoutY="181.0" text="p3" />
<Button fx:id="p3previous" layoutX="152.0" layoutY="225.0" mnemonicParsing="false" text="previous" />
</children>
</Pane>
<Pane fx:id="p2" prefHeight="380.0" prefWidth="387.0" visible="false">
<children>
<Label layoutX="184.0" layoutY="181.0" text="p2" />
<Button fx:id="p2previous" layoutX="78.0" layoutY="255.0" mnemonicParsing="false" text="previous" />
<Button fx:id="p2next" layoutX="239.0" layoutY="255.0" mnemonicParsing="false" text="next" />
</children>
</Pane>
<Pane fx:id="p1" prefHeight="380.0" prefWidth="387.0">
<children>
<Button fx:id="p1next" layoutX="167.0" layoutY="210.0" mnemonicParsing="false" text="next" />
<Label layoutX="184.0" layoutY="181.0" text="p1" />
</children>
</Pane>
</children>
</AnchorPane>
controller
add button action event, use setVisible(boolean) to control which pane should show.
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
public class FXMLDocumentController implements Initializable {
#FXML Pane p1;
#FXML Pane p2;
#FXML Pane p3;
#FXML Button p1next;
#FXML Button p2next;
#FXML Button p2previous;
#FXML Button p3previous;
public void initialize(URL url, ResourceBundle rb)
{
p1next.setOnAction(e->{ p1.setVisible(false); p2.setVisible(true); });
p2next.setOnAction(e->{ p2.setVisible(false); p3.setVisible(true); });
p2previous.setOnAction(e->{ p2.setVisible(false); p1.setVisible(true); });
p3previous.setOnAction(e->{ p3.setVisible(false); p2.setVisible(true); });
}
}
You do not need to add all the content you create in a fxml file to the scene. the <fx:define> tag can be used to create Node that are not part of the object scene (yet). Use a suitable Parent that allows you to proper display the content.
Example:
<StackPane fx:id="container" prefHeight="432.0" prefWidth="443.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.MainController">
<children>
<fx:include fx:id="tab1" source="tab/Tab1.fxml" />
<fx:define>
<fx:include fx:id="tab2" source="tab/Tab2.fxml" />
<fx:include fx:id="tab3" source="tab/Tab3.fxml" />
</fx:define>
</children>
</StackPane>
public class MainController {
#FXML private Tab1Controller tab1Controller;
#FXML private Tab2Controller tab2Controller;
#FXML private Tab3Controller tab3Controller;
#FXML private Node tab1;
#FXML private Node tab2;
#FXML private Node tab3;
#FXML private StackPane container;
public void initialize() {
tab1Controller.init(this);
tab2Controller.init(this);
tab3Controller.init(this);
}
public void setTab2LabelText(String text) {
tab3Controller.lbl3.setText(text);
tab2Controller.lbl2.setText(text);
}
public void toTab2() {
container.getChildren().setAll(tab2);
}
}