How can I pass parameters to a secondary window in javafx? Is there a way to communicate with the corresponding controller?
For example:
The user chooses a customer from a TableView and a new window is opened, showing the customer's info.
Stage newStage = new Stage();
try
{
AnchorPane page = (AnchorPane) FXMLLoader.load(HectorGestion.class.getResource(fxmlResource));
Scene scene = new Scene(page);
newStage.setScene(scene);
newStage.setTitle(windowTitle);
newStage.setResizable(isResizable);
if(showRightAway)
{
newStage.show();
}
}
newStage would be the new window. The problem is, I can't find a way to tell the controller where to look for the customer's info (by passing the id as parameter).
Any ideas?
Using MVC
Most of this answer focuses on a direct call to pass a parameter from a calling class to the controller.
If instead, you want to decouple the caller and controller and use a more general architecture involving a model class with settable and listenable properties to achieve inter-controller communication, see the following basic overview:
Applying MVC With JavaFx
Recommended Approach
This answer enumerates different mechanisms for passing parameters to FXML controllers.
For small applications I highly recommend passing parameters directly from the caller to the controller - it's simple, straightforward and requires no extra frameworks.
For larger, more complicated applications, it would be worthwhile investigating if you want to use Dependency Injection or Event Bus mechanisms within your application.
Passing Parameters Directly From the Caller to the Controller
Pass custom data to an FXML controller by retrieving the controller from the FXML loader instance and calling a method on the controller to initialize it with the required data values.
Something like the following code:
public Stage showCustomerDialog(Customer customer) {
FXMLLoader loader = new FXMLLoader(
getClass().getResource(
"customerDialog.fxml"
)
);
Stage stage = new Stage(StageStyle.DECORATED);
stage.setScene(
new Scene(loader.load())
);
CustomerDialogController controller = loader.getController();
controller.initData(customer);
stage.show();
return stage;
}
...
class CustomerDialogController {
#FXML private Label customerName;
void initialize() {}
void initData(Customer customer) {
customerName.setText(customer.getName());
}
}
A new FXMLLoader is constructed as shown in the sample code i.e. new FXMLLoader(location). The location is a URL and you can generate such a URL from an FXML resource by:
new FXMLLoader(getClass().getResource("sample.fxml"));
Be careful NOT to use a static load function on the FXMLLoader, or you will not be able to get your controller from your loader instance.
FXMLLoader instances themselves never know anything about domain objects. You do not directly pass application specific domain objects into the FXMLLoader constructor, instead you:
Construct an FXMLLoader based upon fxml markup at a specified location
Get a controller from the FXMLLoader instance.
Invoke methods on the retrieved controller to provide the controller with references to the domain objects.
This blog (by another writer) provides an alternate, but similar, example.
Setting a Controller on the FXMLLoader
CustomerDialogController dialogController =
new CustomerDialogController(param1, param2);
FXMLLoader loader = new FXMLLoader(
getClass().getResource(
"customerDialog.fxml"
)
);
loader.setController(dialogController);
Pane mainPane = loader.load();
You can construct a new controller in code, passing any parameters you want from your caller into the controller constructor. Once you have constructed a controller, you can set it on an FXMLLoader instance before you invoke the load() instance method.
To set a controller on a loader (in JavaFX 2.x) you CANNOT also define a fx:controller attribute in your fxml file.
Due to the limitation on the fx:controller definition in FXML, I personally prefer getting the controller from the FXMLLoader rather than setting the controller into the FXMLLoader.
Having the Controller Retrieve Parameters from an External Static Method
This method is exemplified by Sergey's answer to Javafx 2.0 How-to Application.getParameters() in a Controller.java file.
Use Dependency Injection
FXMLLoader supports dependency injection systems like Guice, Spring or Java EE CDI by allowing you to set a custom controller factory on the FXMLLoader. This provides a callback that you can use to create the controller instance with dependent values injected by the respective dependency injection system.
An example of JavaFX application and controller dependency injection with Spring is provided in the answer to:
Adding Spring Dependency Injection in JavaFX (JPA Repo, Service)
A really nice, clean dependency injection approach is exemplified by the afterburner.fx framework with a sample air-hacks application that uses it. afterburner.fx relies on JEE6 javax.inject to perform the dependency injection.
Use an Event Bus
Greg Brown, the original FXML specification creator and implementor, often suggests considering use of an event bus, such as the Guava EventBus, for communication between FXML instantiated controllers and other application logic.
The EventBus is a simple but powerful publish/subscribe API with annotations that allows POJOs to communicate with each other anywhere in a JVM without having to refer to each other.
Follow-up Q&A
on first method, why do you return Stage? The method can be void as well because you already giving the command show(); just before return stage;. How do you plan usage by returning the Stage
It is a functional solution to a problem. A stage is returned from the showCustomerDialog function so that a reference to it can be stored by an external class which may wish to do something, such as hide the stage based on a button click in the main window, at a later time. An alternate, object-oriented solution could encapsulate the functionality and stage reference inside a CustomerDialog object or have a CustomerDialog extend Stage. A full example for an object-oriented interface to a custom dialog encapsulating FXML, controller and model data is beyond the scope of this answer, but may make a worthwhile blog post for anybody inclined to create one.
Additional information supplied by StackOverflow user named #dzim
Example for Spring Boot Dependency Injection
The question of how to do it "The Spring Boot Way", there was a discussion about JavaFX 2, which I anserwered in the attached permalink.
The approach is still valid and tested in March 2016, on Spring Boot v1.3.3.RELEASE:
https://stackoverflow.com/a/36310391/1281217
Sometimes, you might want to pass results back to the caller, in which case you can check out the answer to the related question:
JavaFX FXML Parameter passing from Controller A to B and back
I realize this is a very old post and has some great answers already,
but I wanted to make a simple MCVE to demonstrate one such approach and allow new coders a way to quickly see the concept in action.
In this example, we will use 5 files:
Main.java - Simply used to start the application and call the first controller.
Controller1.java - The controller for the first FXML layout.
Controller2.java - The controller for the second FXML layout.
Layout1.fxml - The FXML layout for the first scene.
Layout2.fxml - The FXML layout for the second scene.
All files are listed in their entirety at the bottom of this post.
The Goal: To demonstrate passing values from Controller1 to Controller2 and vice versa.
The Program Flow:
The first scene contains a TextField, a Button, and a Label. When the Button is clicked, the second window is loaded and displayed, including the text entered in the TextField.
Within the second scene, there is also a TextField, a Button, and a Label. The Label will display the text entered in the TextField on the first scene.
Upon entering text in the second scene's TextField and clicking its Button, the first scene's Label is updated to show the entered text.
This is a very simple demonstration and could surely stand for some improvement, but should make the concept very clear.
The code itself is also commented with some details of what is happening and how.
THE CODE
Main.java:
import javafx.application.Application;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Create the first controller, which loads Layout1.fxml within its own constructor
Controller1 controller1 = new Controller1();
// Show the new stage
controller1.showStage();
}
}
Controller1.java:
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import java.io.IOException;
public class Controller1 {
// Holds this controller's Stage
private final Stage thisStage;
// Define the nodes from the Layout1.fxml file. This allows them to be referenced within the controller
#FXML
private TextField txtToSecondController;
#FXML
private Button btnOpenLayout2;
#FXML
private Label lblFromController2;
public Controller1() {
// Create the new stage
thisStage = new Stage();
// Load the FXML file
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout1.fxml"));
// Set this class as the controller
loader.setController(this);
// Load the scene
thisStage.setScene(new Scene(loader.load()));
// Setup the window/stage
thisStage.setTitle("Passing Controllers Example - Layout1");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Show the stage that was loaded in the constructor
*/
public void showStage() {
thisStage.showAndWait();
}
/**
* The initialize() method allows you set setup your scene, adding actions, configuring nodes, etc.
*/
#FXML
private void initialize() {
// Add an action for the "Open Layout2" button
btnOpenLayout2.setOnAction(event -> openLayout2());
}
/**
* Performs the action of loading and showing Layout2
*/
private void openLayout2() {
// Create the second controller, which loads its own FXML file. We pass a reference to this controller
// using the keyword [this]; that allows the second controller to access the methods contained in here.
Controller2 controller2 = new Controller2(this);
// Show the new stage/window
controller2.showStage();
}
/**
* Returns the text entered into txtToSecondController. This allows other controllers/classes to view that data.
*/
public String getEnteredText() {
return txtToSecondController.getText();
}
/**
* Allows other controllers to set the text of this layout's Label
*/
public void setTextFromController2(String text) {
lblFromController2.setText(text);
}
}
Controller2.java:
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import java.io.IOException;
public class Controller2 {
// Holds this controller's Stage
private Stage thisStage;
// Will hold a reference to the first controller, allowing us to access the methods found there.
private final Controller1 controller1;
// Add references to the controls in Layout2.fxml
#FXML
private Label lblFromController1;
#FXML
private TextField txtToFirstController;
#FXML
private Button btnSetLayout1Text;
public Controller2(Controller1 controller1) {
// We received the first controller, now let's make it usable throughout this controller.
this.controller1 = controller1;
// Create the new stage
thisStage = new Stage();
// Load the FXML file
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout2.fxml"));
// Set this class as the controller
loader.setController(this);
// Load the scene
thisStage.setScene(new Scene(loader.load()));
// Setup the window/stage
thisStage.setTitle("Passing Controllers Example - Layout2");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Show the stage that was loaded in the constructor
*/
public void showStage() {
thisStage.showAndWait();
}
#FXML
private void initialize() {
// Set the label to whatever the text entered on Layout1 is
lblFromController1.setText(controller1.getEnteredText());
// Set the action for the button
btnSetLayout1Text.setOnAction(event -> setTextOnLayout1());
}
/**
* Calls the "setTextFromController2()" method on the first controller to update its Label
*/
private void setTextOnLayout1() {
controller1.setTextFromController2(txtToFirstController.getText());
}
}
Layout1.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1">
<VBox alignment="CENTER" spacing="10.0">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding>
<Label style="-fx-font-weight: bold;" text="This is Layout1!"/>
<HBox alignment="CENTER_LEFT" spacing="10.0">
<Label text="Enter Text:"/>
<TextField fx:id="txtToSecondController"/>
<Button fx:id="btnOpenLayout2" mnemonicParsing="false" text="Open Layout2"/>
</HBox>
<VBox alignment="CENTER">
<Label text="Text From Controller2:"/>
<Label fx:id="lblFromController2" text="Nothing Yet!"/>
</VBox>
</VBox>
</AnchorPane>
Layout2.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1">
<VBox alignment="CENTER" spacing="10.0">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding>
<Label style="-fx-font-weight: bold;" text="Welcome to Layout 2!"/>
<VBox alignment="CENTER">
<Label text="Text From Controller1:"/>
<Label fx:id="lblFromController1" text="Nothing Yet!"/>
</VBox>
<HBox alignment="CENTER_LEFT" spacing="10.0">
<Label text="Enter Text:"/>
<TextField fx:id="txtToFirstController"/>
<Button fx:id="btnSetLayout1Text" mnemonicParsing="false" text="Set Text on Layout1"/>
</HBox>
</VBox>
</AnchorPane>
Here is an example for passing parameters to a fxml document through namespace.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx/null" xmlns:fx="http://javafx.com/fxml/1">
<BorderPane>
<center>
<Label text="$labelText"/>
</center>
</BorderPane>
</VBox>
Define value External Text for namespace variable labelText:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class NamespaceParameterExampleApplication extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws IOException {
final FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("namespace-parameter-example.fxml"));
fxmlLoader.getNamespace()
.put("labelText", "External Text");
final Parent root = fxmlLoader.load();
primaryStage.setTitle("Namespace Parameter Example");
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
}
}
javafx.scene.Node class has a pair of methods
setUserData(Object)
and
Object getUserData()
Which you could use to add your info to the Node.
So, you can call page.setUserData(info);
And controller can check, if info is set. Also, you could use ObjectProperty for back-forward data transfering, if needed.
Observe a documentation here:
http://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html
Before the phrase "In the first version, the handleButtonAction() is tagged with #FXML to allow markup defined in the controller's document to invoke it. In the second example, the button field is annotated to allow the loader to set its value. The initialize() method is similarly annotated."
So, you need to associate a controller with a node, and set a user data to the node.
This WORKS ..
Remember first time you print the passing value you will get null,
You can use it after your windows loaded , same for everything you want to code for any other component.
First Controller
try {
Stage st = new Stage();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/inty360/free/form/MainOnline.fxml"));
Parent sceneMain = loader.load();
MainOnlineController controller = loader.<MainOnlineController>getController();
controller.initVariable(99L);
Scene scene = new Scene(sceneMain);
st.setScene(scene);
st.setMaximized(true);
st.setTitle("My App");
st.show();
} catch (IOException ex) {
Logger.getLogger(LoginController.class.getName()).log(Level.SEVERE, null, ex);
}
Another Controller
public void initVariable(Long id_usuario){
this.id_usuario = id_usuario;
label_usuario_nombre.setText(id_usuario.toString());
}
You have to create one Context Class.
public class Context {
private final static Context instance = new Context();
public static Context getInstance() {
return instance;
}
private Connection con;
public void setConnection(Connection con)
{
this.con=con;
}
public Connection getConnection() {
return con;
}
private TabRoughController tabRough;
public void setTabRough(TabRoughController tabRough) {
this.tabRough=tabRough;
}
public TabRoughController getTabRough() {
return tabRough;
}
}
You have to just set instance of controller in initialization using
Context.getInstance().setTabRough(this);
and you can use it from your whole application just using
TabRoughController cont=Context.getInstance().getTabRough();
Now you can pass parameter to any controller from whole application.
Yes you can.
You need to add in the first controller:
YourController controller = loader.getController();
controller.setclient(client);
Then in the second one declare a client, then at the bottom of your controller:
public void setclien(Client c) {
this.client = c;
}
Here is an example for using a controller injected by Guice.
/**
* Loads a FXML file and injects its controller from the given Guice {#code Provider}
*/
public abstract class GuiceFxmlLoader {
public GuiceFxmlLoader(Stage stage, Provider<?> provider) {
mStage = Objects.requireNonNull(stage);
mProvider = Objects.requireNonNull(provider);
}
/**
* #return the FXML file name
*/
public abstract String getFileName();
/**
* Load FXML, set its controller with given {#code Provider}, and add it to {#code Stage}.
*/
public void loadView() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource(getFileName()));
loader.setControllerFactory(p -> mProvider.get());
Node view = loader.load();
setViewInStage(view);
}
catch (IOException ex) {
LOGGER.error("Failed to load FXML: " + getFileName(), ex);
}
}
private void setViewInStage(Node view) {
BorderPane pane = (BorderPane)mStage.getScene().getRoot();
pane.setCenter(view);
}
private static final Logger LOGGER = Logger.getLogger(GuiceFxmlLoader.class);
private final Stage mStage;
private final Provider<?> mProvider;
}
Here is a concrete implementation of the loader:
public class ConcreteViewLoader extends GuiceFxmlLoader {
#Inject
public ConcreteViewLoader(Stage stage, Provider<MyController> provider) {
super(stage, provider);
}
#Override
public String getFileName() {
return "my_view.fxml";
}
}
Note this example loads the view into the center of a BoarderPane that is the root of the Scene in the Stage. This is irrelevant to the example (implementation detail of my specific use case) but decided to leave it in as some may find it useful.
You can decide to use a public observable list to store public data, or just create a public setter method to store data and retrieve from the corresponding controller
Why answer a 6 year old question ?
One the most fundamental concepts working with any programming language is how to navigate from one (window, form or page) to another. Also while doing this navigation the developer often wants to pass data from one (window, form or page) and display or use the data passed
While most of the answers here provide good to excellent examples how to accomplish this we thought we would kick it up a notch or two or three
We said three because we will navigate between three (window, form or page) and use the concept of static variables to pass data around the (window, form or page)
We will also include some decision making code while we navigate
public class Start extends Application {
#Override
public void start(Stage stage) throws Exception {
// This is MAIN Class which runs first
Parent root = FXMLLoader.load(getClass().getResource("start.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setResizable(false);// This sets the value for all stages
stage.setTitle("Start Page");
stage.show();
stage.sizeToScene();
}
public static void main(String[] args) {
launch(args);
}
}
Start Controller
public class startController implements Initializable {
#FXML Pane startPane,pageonePane;
#FXML Button btnPageOne;
#FXML TextField txtStartValue;
public Stage stage;
public static int intSETonStartController;
String strSETonStartController;
#FXML
private void toPageOne() throws IOException{
strSETonStartController = txtStartValue.getText().trim();
// yourString != null && yourString.trim().length() > 0
// int L = testText.length();
// if(L == 0){
// System.out.println("LENGTH IS "+L);
// return;
// }
/* if (testText.matches("[1-2]") && !testText.matches("^\\s*$"))
Second Match is regex for White Space NOT TESTED !
*/
String testText = txtStartValue.getText().trim();
// NOTICE IF YOU REMOVE THE * CHARACTER FROM "[1-2]*"
// NO NEED TO CHECK LENGTH it also permited 12 or 11 as valid entry
// =================================================================
if (testText.matches("[1-2]")) {
intSETonStartController = Integer.parseInt(strSETonStartController);
}else{
txtStartValue.setText("Enter 1 OR 2");
return;
}
System.out.println("You Entered = "+intSETonStartController);
stage = (Stage)startPane.getScene().getWindow();// pane you are ON
pageonePane = FXMLLoader.load(getClass().getResource("pageone.fxml"));// pane you are GOING TO
Scene scene = new Scene(pageonePane);// pane you are GOING TO
stage.setScene(scene);
stage.setTitle("Page One");
stage.show();
stage.sizeToScene();
stage.centerOnScreen();
}
private void doGET(){
// Why this testing ?
// strSENTbackFROMPageoneController is null because it is set on Pageone
// =====================================================================
txtStartValue.setText(strSENTbackFROMPageoneController);
if(intSETonStartController == 1){
txtStartValue.setText(str);
}
System.out.println("== doGET WAS RUN ==");
if(txtStartValue.getText() == null){
txtStartValue.setText("");
}
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// This Method runs every time startController is LOADED
doGET();
}
}
Page One Controller
public class PageoneController implements Initializable {
#FXML Pane startPane,pageonePane,pagetwoPane;
#FXML Button btnOne,btnTwo;
#FXML TextField txtPageOneValue;
public static String strSENTbackFROMPageoneController;
public Stage stage;
#FXML
private void onBTNONE() throws IOException{
stage = (Stage)pageonePane.getScene().getWindow();// pane you are ON
pagetwoPane = FXMLLoader.load(getClass().getResource("pagetwo.fxml"));// pane you are GOING TO
Scene scene = new Scene(pagetwoPane);// pane you are GOING TO
stage.setScene(scene);
stage.setTitle("Page Two");
stage.show();
stage.sizeToScene();
stage.centerOnScreen();
}
#FXML
private void onBTNTWO() throws IOException{
if(intSETonStartController == 2){
Alert alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle("Alert");
alert.setHeaderText("YES to change Text Sent Back");
alert.setResizable(false);
alert.setContentText("Select YES to send 'Alert YES Pressed' Text Back\n"
+ "\nSelect CANCEL send no Text Back\r");// NOTE this is a Carriage return\r
ButtonType buttonTypeYes = new ButtonType("YES");
ButtonType buttonTypeCancel = new ButtonType("CANCEL", ButtonData.CANCEL_CLOSE);
alert.getButtonTypes().setAll(buttonTypeYes, buttonTypeCancel);
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == buttonTypeYes){
txtPageOneValue.setText("Alert YES Pressed");
} else {
System.out.println("canceled");
txtPageOneValue.setText("");
onBack();// Optional
}
}
}
#FXML
private void onBack() throws IOException{
strSENTbackFROMPageoneController = txtPageOneValue.getText();
System.out.println("Text Returned = "+strSENTbackFROMPageoneController);
stage = (Stage)pageonePane.getScene().getWindow();
startPane = FXMLLoader.load(getClass().getResource("start.fxml"));
Scene scene = new Scene(startPane);
stage.setScene(scene);
stage.setTitle("Start Page");
stage.show();
stage.sizeToScene();
stage.centerOnScreen();
}
private void doTEST(){
String fromSTART = String.valueOf(intSETonStartController);
txtPageOneValue.setText("SENT "+fromSTART);
if(intSETonStartController == 1){
btnOne.setVisible(true);
btnTwo.setVisible(false);
System.out.println("INTEGER Value Entered = "+intSETonStartController);
}else{
btnOne.setVisible(false);
btnTwo.setVisible(true);
System.out.println("INTEGER Value Entered = "+intSETonStartController);
}
}
#Override
public void initialize(URL url, ResourceBundle rb) {
doTEST();
}
}
Page Two Controller
public class PagetwoController implements Initializable {
#FXML Pane startPane,pagetwoPane;
public Stage stage;
public static String str;
#FXML
private void toStart() throws IOException{
str = "You ON Page Two";
stage = (Stage)pagetwoPane.getScene().getWindow();// pane you are ON
startPane = FXMLLoader.load(getClass().getResource("start.fxml"));// pane you are GOING TO
Scene scene = new Scene(startPane);// pane you are GOING TO
stage.setScene(scene);
stage.setTitle("Start Page");
stage.show();
stage.sizeToScene();
stage.centerOnScreen();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
}
Below are all the FXML files
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane id="AnchorPane" fx:id="pagetwoPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="atwopage.PagetwoController">
<children>
<Button layoutX="227.0" layoutY="62.0" mnemonicParsing="false" onAction="#toStart" text="To Start Page">
<font>
<Font name="System Bold" size="18.0" />
</font>
</Button>
</children>
</AnchorPane>
<?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.text.Font?>
<AnchorPane id="AnchorPane" fx:id="startPane" prefHeight="200.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="atwopage.startController">
<children>
<Label focusTraversable="false" layoutX="115.0" layoutY="47.0" text="This is the Start Pane">
<font>
<Font size="18.0" />
</font>
</Label>
<Button fx:id="btnPageOne" focusTraversable="false" layoutX="137.0" layoutY="100.0" mnemonicParsing="false" onAction="#toPageOne" text="To Page One">
<font>
<Font size="18.0" />
</font>
</Button>
<Label focusTraversable="false" layoutX="26.0" layoutY="150.0" text="Enter 1 OR 2">
<font>
<Font size="18.0" />
</font>
</Label>
<TextField fx:id="txtStartValue" layoutX="137.0" layoutY="148.0" prefHeight="28.0" prefWidth="150.0" />
</children>
</AnchorPane>
<?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.text.Font?>
<AnchorPane id="AnchorPane" fx:id="pageonePane" prefHeight="200.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="atwopage.PageoneController">
<children>
<Label focusTraversable="false" layoutX="111.0" layoutY="35.0" text="This is Page One Pane">
<font>
<Font size="18.0" />
</font>
</Label>
<Button focusTraversable="false" layoutX="167.0" layoutY="97.0" mnemonicParsing="false" onAction="#onBack" text="BACK">
<font>
<Font size="18.0" />
</font></Button>
<Button fx:id="btnOne" focusTraversable="false" layoutX="19.0" layoutY="97.0" mnemonicParsing="false" onAction="#onBTNONE" text="Button One" visible="false">
<font>
<Font size="18.0" />
</font>
</Button>
<Button fx:id="btnTwo" focusTraversable="false" layoutX="267.0" layoutY="97.0" mnemonicParsing="false" onAction="#onBTNTWO" text="Button Two">
<font>
<Font size="18.0" />
</font>
</Button>
<Label focusTraversable="false" layoutX="19.0" layoutY="152.0" text="Send Anything BACK">
<font>
<Font size="18.0" />
</font>
</Label>
<TextField fx:id="txtPageOneValue" layoutX="195.0" layoutY="150.0" prefHeight="28.0" prefWidth="150.0" />
</children>
</AnchorPane>
Related
I generate a simple app from Gluon Start with Glisten Afterburner (v2.1.0). This generates three classes of which two are Main and AppViewManager using glisten.afterburner AppView and AppViewRegistry. I add an additional View to the generated code ("Other") and I add methods to AppViewManager to get the Presenter associated with a View.
The generated Main class looks like the following to which I have added the static calls to get Presenters: getMainPresenter() and getOtherPresenter():
public class Main extends Application {
private final AppManager appManager = AppManager.initialize(this::postInit);
#Override
public void init() {
AppViewManager.registerViewsAndDrawer();
}
#Override
public void start(Stage stage) {
appManager.start(stage);
}
private void postInit(Scene scene) {
:
// Get Main Presenter
AppViewManager.getMainPresenter();
// Get Other Presenter
AppViewManager.getOtherPresenter();
}
:
}
AppViewManager looks like the following where I added the additional View ("Other") and added methods to get Presenters associated with Views. In registering Views, I added some output to see what is going on:
public class AppViewManager {
private static final AppViewRegistry REGISTRY = new AppViewRegistry();
public static final AppView MAIN_VIEW = view("Home", MainPresenter.class, MaterialDesignIcon.HOME, SHOW_IN_DRAWER, HOME_VIEW, SKIP_VIEW_STACK);
public static final AppView OTHER_VIEW = view("Other", OtherPresenter.class, MaterialDesignIcon.NOTE, SHOW_IN_DRAWER, SKIP_VIEW_STACK);
private static AppView view(String title, Class<?> presenterClass, MaterialDesignIcon menuIcon, AppView.Flag... flags ) {
return REGISTRY.createView(name(presenterClass), title, presenterClass, menuIcon, flags);
}
private static String name(Class<?> presenterClass) {
return presenterClass.getSimpleName().toUpperCase(Locale.ROOT).replace("PRESENTER", "");
}
public static void registerViewsAndDrawer()
{
for (AppView view : REGISTRY.getViews())
{
// What's in the REGISTRY?
System.out.println("==> View: " +view.getId()+ ", Presenter: " +view.getPresenterClass().getCanonicalName());
// Not sure what the following does, the javadoc is blank!
view.registerView();
}
:
}
/**
* Get MainPresenter.
* See: .../AppViewRegistry.html#getPresenter(com.gluonhq.charm.glisten.afterburner.AppView)
*/
public static void getMainPresenter()
{
MainPresenter mp = (MainPresenter)MAIN_VIEW.getPresenter().get();
System.out.println("..Presenter..> " +mp.toString());
}
public static void getOtherPresenter()
{
OtherPresenter op = (OtherPresenter)OTHER_VIEW.getPresenter().get();
System.out.println("..OtherPresenter..> " +op.toString());
}
}
OtherPresenter is a simple declaration:
public class OtherPresenter {
public void initialize() {}
}
The OtherPresenter FXML is:
<View fx:id="other" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.runtheworld.sample.OtherPresenter">
<center>
<VBox styleClass="box">
<children>
<Label text="Other!" />
</children>
</VBox>
</center>
</View>
What's in the REGISTRY output is as expected (except I would have expected 'HOME_VIEW' rather than 'home'):
==> View: home, Presenter: com.runtheworld.sample.MainPresenter
==> View: OTHER_VIEW, Presenter: com.runtheworld.sample.OtherPresenter
But no matter where I make the getOtherPresenter() call, it returns: "Caused by: java.util.NoSuchElementException: No value present". The getMainPresenter() call, however, works. In fact, the app functions, showing OtherPresenter (displaying "Other!") but still throwing "No value".
Why does Main work but not Other, is there some setup/init I have missed?
Here's all the output:
==> View: home, Presenter: com.runtheworld.sample.MainPresenter
==> View: OTHER_VIEW, Presenter: com.runtheworld.sample.OtherPresenter
Apr 03, 2022 11:24:16 AM com.gluonhq.attach.util.Platform <clinit>
INFO: [Gluon Attach] System Property javafx.platform is not defined. Platform will be set to Platform.DESKTOP
..MainPresenter..> com.runtheworld.sample.MainPresenter#686bbb76
java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.get(Optional.java:143)
at com.runtheworld.sample.AppViewManager.getOtherPresenter(AppViewManager.java:54)
at com.runtheworld.sample.Main.postInit(Main.java:41)
at com.gluonhq.charm.glisten.application.AppManager.continueInit(AppManager.java:328)
at com.gluonhq.charm.glisten.application.AppManager.start(AppManager.java:288)
at com.runtheworld.sample.Main.start(Main.java:24)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:847)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:484)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:457)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:456)
at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at javafx.graphics/com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.gtk.GtkApplication.lambda$runLoop$11(GtkApplication.java:290)
at java.base/java.lang.Thread.run(Thread.java:833)
EDIT:
I decided to eschew afterburner and simply built a View as follows:
public class OtherView {
public static final String OTHER_VIEW = "OTHER_VIEW";
public AbstractMap.SimpleEntry<String,AbstractMap.SimpleEntry<View,Presenter>> getViewController() {
try {
FXMLLoader f = new FXMLLoader(OtherView.class.getResource("/path/to/other.fxml"), ResourceBundle.getBundle("path.to.other"));
View view = f.load();
OtherPresenter controller = f.getController();
return new AbstractMap.SimpleEntry(OTHER_VIEW,new AbstractMap.SimpleEntry(view,controller));
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
The View and Controller are defined in the FXML and this ties the View name to the View and its associated Controller. getViewController() is called from Main.init().
I'm developing the JavaFX application who displays a TableView and outputs a PDF file by using the JavaFX 8 PrintJob and Printer APIs.
The JDK version is 1.8.0.
I expected that all texts in the Scene are output as text data into the PDF file.
However, the result is that the texts are rendered as images not text data.
The requirement of my system is to store all texts as text data so that the user can select and copy them via PDF reader software (such as Adobe Reader).
The question is how I should modify the TabeView property in order to print the texts as text.
I don't want to put the Labels onto each cell in the TableView, but if there is no way to achieve my goal, I'll take this unsmart solution.
For the investigation purpose, I just created the small application as shown below.
The application includes two buttons ([Reload CSS] and [Print] button), one label and one TableView.
[Reload CSS] button: It has been set for the convenience for the test. The tester does not need to re-start application after modifying the CSS file. Please just click this button when you check the behavior after CSS file is changed.
[Print] button: When it is clicked, the print dialog will be shown, then please choose one PDF creating software as the printer such as CubePDF.
Only one thing I found is, when the '-fx-opacity' style of the TableView is set with the value smaller than 1.0 (such as 0.99) in CSS file, the label which locates outside the TableView (in my case, it shows "Person List") is output as text data.
The text "Person List" can be selected via the PDF reader software.
However, the whole TableView is rendered as one images.
I would appreciate if some smart solution is provided.
Main class: PrintApp.java
package test;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
/**
* Main class.
*/
public class PrintApp extends Application {
/**
* Start the application.
* #param stage Stage object
* #throws Exception Exception
*/
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("person_list.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
FXML file: person_list.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import java.net.URL?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.Pane?>
<Pane fx:id="rootPane" prefHeight="400.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="test.FxmlController">
<stylesheets>
<URL value="#style.css" />
</stylesheets>
<children>
<!-- Reload the CSS file -->
<!-- * User does not need to re-start the application after the CSS file is modified. -->
<Button layoutX="20.0" layoutY="10.0" onAction="#reload" prefHeight="20.0" prefWidth="80.0" text="Reload CSS" />
<!-- Print the "printBoundsPane" pane -->
<Button layoutX="120.0" layoutY="10.0" onAction="#print" prefHeight="20.0" prefWidth="80.0" text="Print" />
<!-- Target bounds which is printed out. -->
<!-- * When the [Print] button is clicked, the print dialog will be displayed. -->
<!-- Then, the following "Pane" will be printed out. -->
<Pane fx:id="printBoundsPane" layoutX="20.0" layoutY="60.0" prefHeight="300.0" prefWidth="260.0">
<children>
<Label layoutX="10.0" layoutY="10.0" text="Person List" />
<TableView fx:id="table" layoutY="40.0" prefHeight="260.0" prefWidth="260.0" visible="true">
<columns>
<TableColumn fx:id="familyNameColumn" prefWidth="120.0" text="Family Name" />
<TableColumn fx:id="givenNameColumn" prefWidth="120.0" text="Given Name" />
</columns>
</TableView>
</children>
</Pane>
</children>
</Pane>
CSS file: style.css
/**
* Root pane.
*/
#rootPane {
-fx-background-color: white;
}
/**
* Actual bounds which is printed out.
*/
#printBoundsPane {
-fx-border-color: darkgray;
-fx-border-width: 1px;
}
/**
* Whole TableView.
*/
.table-view {
-fx-opacity: 0.99;
}
Controller class: FxmlController.java
package test;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.print.PageLayout;
import javafx.print.PageOrientation;
import javafx.print.Paper;
import javafx.print.Printer;
import javafx.print.PrinterJob;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.Pane;
/**
* FXML Controller class.
*/
public class FxmlController implements Initializable {
#FXML private Pane rootPane;
#FXML private Pane printBoundsPane;
#FXML private TableView<Person> table;
#FXML private TableColumn<Person, String> familyNameColumn;
#FXML private TableColumn<Person, String> givenNameColumn;
/**
* Initialize the stage.
* #param location FXML file location
* #param resources resources
*/
#Override
public void initialize(URL location, ResourceBundle resources) {
// Link the data with a column.
this.familyNameColumn.setCellValueFactory(new PropertyValueFactory("familyName"));
this.givenNameColumn.setCellValueFactory(new PropertyValueFactory("givenName"));
// Add sample records to the TableView
table.getItems().add(new Person("TEST1", "test1"));
table.getItems().add(new Person("TEST2", "test2"));
}
/**
* Event listener for [Reload CSS] button.
* Reload the CSS file.
* #throws java.net.MalformedURLException
*/
public void reload() throws MalformedURLException {
// Get the stylesheet list.
ObservableList<String> styleSheets = this.rootPane.getStylesheets();
// CSS file location.
URL cssLocation = new URL(styleSheets.get(0));
// Reset the CSS file.
rootPane.getStylesheets().set(0, cssLocation.toExternalForm());
}
/**
* Event listener for [Print] button.
* Print out the pane which can be identified as "printBoundsPane".
*/
public void print() {
// Default printer object.
Printer printer = Printer.getDefaultPrinter();
// Print page layout object.
// * Set "LANDSCAPE" as the page orientation for the convenience for the test.
// If the output pdf has the text information, the output file is shown in a PORTRAIT mode.
// If not, it will be shown in a LANDSCAPE mode.
PageLayout layout = printer.createPageLayout(Paper.A4, PageOrientation.LANDSCAPE, Printer.MarginType.DEFAULT);
// Create a printer job.
PrinterJob job = PrinterJob.createPrinterJob();
if (job != null) {
// Set the job name.
job.getJobSettings().setJobName("TestPrint");
if (job.showPrintDialog(this.rootPane.getScene().getWindow())) {
// Print out the specified pane.
job.printPage(layout, this.printBoundsPane);
}
else {
System.out.println("Print canceled.");
}
// Finish the print job.
job.endJob();
}
}
/**
* The class for defining a person.
*/
public static class Person {
private final StringProperty familyName = new SimpleStringProperty();
private final StringProperty givenName = new SimpleStringProperty();
/**
* Constructor.
* #param _familyName Family name of the person.
* #param _givenName Given name of the person.
*/
Person(String _familyName, String _givenName) {
this.familyName.set(_familyName);
this.givenName.set(_givenName);
}
public StringProperty familyNameProperty() {return this.familyName;}
public StringProperty givenNameProperty() {return this.givenName;}
}
}
I've have been trying for hours to get the custom fonts working in my MvvmCross project, specifically the Android platform.
I successfully installed the component and followed the steps mentioned:
https://components.xamarin.com/gettingstarted/calligraphyxamarin
It just doesn't want to work.
I tested it on a activity than inherits from Activity and AppCompatActivity they both work correctly.
Seems like inheriting from MvxAppCompatActivity breaks it? Any solutions this this problem?
Update
I released a nuget package for this (see: MvvmCross.Calligraphy).
Sample project: https://github.com/smstuebe/mvvmcross-calligraphy.
Blog post: http://smstuebe.de/2017/02/12/mvvmcross-calligraphy/
Just download it and modify your setup like:
public class Setup : MvxAndroidSetup
{
protected override MvxAndroidBindingBuilder CreateBindingBuilder()
{
return new CalligraphyMvxAndroidBindingBuilder();
}
}
Yes, because MvvmCross uses a custom layout inflator to bring in the bindings and some other magic stuff. This kicks out the Calligraphy inflator. Unfortunately, I didn't find a way usining the nuget package / xamarin component. You have to create a own binding and make CalligraphyFactory available.
Modified metadata.xml
<attr path="/api/package[#name='uk.co.chrisjenx.calligraphy']/class[#name='CalligraphyFactory']"
name="visibility">public</attr>
Custom factory
public class MyFactory : MvxAndroidViewFactory
{
private CalligraphyFactory _factory;
public MyFactory()
{
_factory = new Calligraphy.CalligraphyFactory(Resource.Attribute.fontPath);
}
public override View CreateView(View parent, string name, Context context, IAttributeSet attrs)
{
var view = base.CreateView(parent, name, context, attrs);
view = _factory.OnViewCreated(view, context, attrs);
return view;
}
}
Custom binding builder
class MyBindingBuilder : MvxAndroidBindingBuilder
{
protected override IMvxAndroidViewFactory CreateAndroidViewFactory()
{
return new MyFactory();
}
}
Setup.cs
public class Setup : MvxAndroidSetup
{
protected override MvxAndroidBindingBuilder CreateBindingBuilder()
{
return new MyBindingBuilder();
}
// ...
}
Activity
You don't need AttachBaseContext. Unfortunately it seems not to work with MvxAppCompatActivity, but with MvxActivity. I'm not sure what causes this issue, yet.
public class FirstView : MvxActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
CalligraphyConfig.InitDefault(new CalligraphyConfig.Builder()
.SetDefaultFontPath("fonts/gtw.ttf")
.SetFontAttrId(Resource.Attribute.fontPath)
.DisablePrivateFactoryInjection()
.Build());
SetContentView(Resource.Layout.FirstView);
}
}
View
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="40dp"
local:MvxBind="Text Hello"
/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="40dp"
local:MvxBind="Text Hello"
fontPath="fonts/gtw.ttf"
/>
</LinearLayout>
Result
Using Calligraphy and MvvmCross together is overkill because MvvmCross uses a custom layout inflator to bring in the bindings you can easily bind custom fonts to TextView typeface by a custom converter.
public class StringToFontConverter : MvxValueConverter<string, Typeface>
{
private static readonly Dictionary<string, Typeface> Cache = new Dictionary<string, Typeface>();
protected override Typeface Convert(string fontName, Type targetType, object parameter, CultureInfo culture) {
try {
if (!fontName.StartsWith(#"fonts/")) fontName = #"fonts/" + fontName;
if (!fontName.EndsWith(".ttf")) fontName += ".ttf";
if (!Cache.ContainsKey(fontName))
Cache[fontName] = Typeface.CreateFromAsset(Application.Context.Assets, fontName);
return Cache[fontName];
} catch {
return Typeface.Default;
}
}
}
And use it in layout:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
local:MvxBind="Typeface StringToFont('Roboto-Light')"/>
Or in styles:
<style name="CustomTextViewStyle" parent="#android:style/Widget.TextView">
<item name="MvxBind">"Typeface StringToFont('Roboto-Regular')"</item>
</style>
Calligraphy library does the same work, override layout inflate process and change TextView typeface, so it's undesirable to add next library and increase apk weight.
How to create 5x identical TabPane with SceneBuilder, without 5x work?
one window contains 5 identical TabPane's with very many and complex GUI components
I would also like to use the SceneBuilder for the design of the window / tab
How can I avoid having to design 5x identical TabPane's with the SceneBuilder, although only the fx: id and handle methods
Is slightly different in the name, eg:
TabPane0: tab0_textField_inputValue
TabPane1: tab1_textField_inputValue
TabPane2: tab2_textField_inputValue
etc.
Solution:
just design a TabPane with the SceneBuilder
but how can you duplicate this "master"-TabPane still 4x, whereby the variable / method names are adapted accordingly automatically?
the duplicate should be automatic because I need it very often (after every GUI development step or
Correction / adjustment of the "master" tab)
Is there perhaps already a tool, which expands the FXML file automatically accordingly?
You usually create a fxml containing only a single TabPane and use this fxml multiple times using <fx:include>:
tabpane.fxml
<TabPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="mapackage.TabPaneController">
...
</TabPane>
Containing layout
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="mapackage.RootController">
<children>
<fx:include fx:id="tabPane1" source="#tabpane.fxml" />
<fx:include fx:id="tabPane2" source="#tabpane.fxml" />
...
</children>
</VBox>
public class RootController {
#FXML
private TabPaneController tabPane1Controller;
#FXML
private TabPaneController tabPane2Controller;
...
}
Of course you should make sure TabPaneController contains the appropriate methods to access the functionality you need to access from RootController...
Solution:
Thanks for your help. My solution looks as follows:
- I use a TabView.fxml multiple times for input
- each tab must be assigned a unique tabId as soon as the user clicks on a tab
- the following code is complete and working
Main.java
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(new AnchorPane());
FXMLLoader loader = new FXMLLoader(getClass().getResource("tabPaneRootView.fxml"));
scene.setRoot(loader.load());
TabPaneRootController controller = loader.getController();
controller.myInit(); //my init-Methode
primaryStage.setScene(scene);
primaryStage.setTitle("I'm Tab x");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
TabPaneRootController.java
public class TabPaneRootController {
#FXML private TabPane tabPane;
//################################# Inject SubController ##################################
//Inject tab controller
#FXML private TabController xxx_tab1_xxxController; //TabPaneRootView.fxml_include_fx:id="xxx_tab_xxx" + "Controller"
#FXML private TabController xxx_tab2_xxxController;
//#########################################################################################
public void myInit() {
tabPane.getSelectionModel().selectedItemProperty().addListener((ObservableValue<? extends Tab> observable,
Tab oldValue, Tab newValue)->{
preparationInitTab(newValue);
});
}
public void preparationInitTab(Tab selectedTab) {
String currentTabId_string = selectedTab.getId();
String[] parts = currentTabId_string.split("_");
int currentTabId = Integer.parseInt(parts[1]);
switch(currentTabId) {
case 1:
xxx_tab1_xxxController.initTab(currentTabId);
break;
case 2:
xxx_tab2_xxxController.initTab(currentTabId);
break;
default:
System.out.println("Warning: Select an unassigned tab='" + currentTabId + "'");
}
}
}
TabController.java
public class TabController {
private int tabId = -9; //is the ID of the currently selected tab
#FXML private TextField tab_textField_currentTabName;
#FXML private TextField tab_textField01_costPosition1;
public void initTab(int currentTabId) {
System.out.println(">TabController::initTab() with currentTabId=" + currentTabId);
this.tabId = currentTabId;
tab_textField_currentTabName.setText(String.valueOf(tabId));
}
//----------------- FXML-Methoden --------------------------
#FXML
private void handleTabTextFieldCostPosition1() {
System.out.println("costPosition1[" + tabId + "]=" + tab_textField01_costPosition1.getText());
}
#FXML
private void handleExit() {
Platform.exit();
}
}
tabPaneRootView.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.myapp.TabPaneRootController">
<children>
<TabPane fx:id="tabPane" tabClosingPolicy="UNAVAILABLE">
<tabs>
<Tab fx:id="tabId_0" text="Tab0">
<content>
<AnchorPane prefHeight="200.0" prefWidth="200.0">
<children>
<Label layoutX="10.0" layoutY="30.0" text="general information ..." />
</children>
</AnchorPane>
</content>
</Tab>
<Tab fx:id="tabId_1" text="Tab1">
<content>
<fx:include source="tabView.fxml" fx:id="xxx_tab1_xxx" />
</content>
</Tab>
<Tab fx:id="tabId_2" text="Tab2">
<content>
<fx:include source="tabView.fxml" fx:id="xxx_tab2_xxx" />
</content>
</Tab>
</tabs>
</TabPane>
</children>
</AnchorPane>
tabView.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="200.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.myapp.TabController">
<children>
<Label layoutX="10.0" layoutY="40.0" text="I'm Tab Index:"> </Label>
<TextField fx:id="tab_textField_currentTabName" editable="false" layoutX="107.0" layoutY="35.0" prefWidth="30.0" />
<Button alignment="CENTER" contentDisplay="CENTER" layoutX="185.0" mnemonicParsing="false" onAction="#handleExit" text="Exit" AnchorPane.bottomAnchor="10.0"> </Button>
<Label layoutX="10.0" layoutY="90.0" text="costPosition1 in € =" />
<TextField fx:id="tab_textField01_costPosition1" alignment="CENTER_RIGHT" layoutX="150.0" layoutY="85.0" onKeyReleased="#handleTabTextFieldCostPosition1" prefWidth="150.0" promptText="input an int value" />
</children>
</AnchorPane>
- Directory structure
- Example started
I am doing a project via JavaFx and I have an image and I want to display some information about that image when I hover above it (like the title tag in html).
Any idea how can I do that ?
Use Tooltip (Tooltip.install()).
Tooltip.install(myImageView, new Tooltip("This is my image."));
The functionality you're looking for is implemented in the Tooltip class and you can add one to a ImageView using Tooltip.install:
#Override
public void start(Stage primaryStage) {
Tooltip tooltip = new Tooltip("stackoverflow logo");
ImageView imageView = new ImageView("https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-logo.png?v=9c558ec15d8a");
imageView.setPickOnBounds(true);
Tooltip.install(imageView, tooltip);
StackPane root = new StackPane(imageView);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
You can easily write a utility class that allows you to install the Tooltip from an fxml file:
package fxml;
import javafx.scene.Node;
import javafx.scene.control.Tooltip;
public final class TooltipUtil {
private TooltipUtil() {}
public static void setTooltip(Node node, Tooltip tooltip) {
Tooltip.install(node, tooltip);
}
// only here for the FXMLLoader
#Deprecated
public static Tooltip getTooltip(Node node) {
throw new UnsupportedOperationException();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.StackPane?>
<?import fxml.TooltipUtil?>
<StackPane xmlns:fx="http://javafx.com/fxml/1">
<children>
<ImageView pickOnBounds="true" >
<image>
<Image url="https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-logo.png?v=9c558ec15d8a"/>
</image>
<TooltipUtil.tooltip>
<Tooltip text="stackoverflow logo" />
</TooltipUtil.tooltip>
</ImageView>
</children>
</StackPane>