oznomal
oznomal

Reputation: 449

JavaFX Reusable FXML Snippets

I am implementing a tabbed section where each tab will contain a table view. Within this table view, there is a subset of columns that will be rendered regardless of which tab is selected but some tabs will include additional columns (handled programmatically).

For these reasons, each tab needs to have a separate controller but I was wondering if it was possible for me to reuse the inner contents of the tab FXML in each tab without having to copy and paste the code. I am thinking along the lines of a reusable component that I could define in another FXML file and just include in the tab section.

Specifically, I'd like to have the content section in the code below in a single file and just reference it in each file so I don't have to make changes to multiple files if I want to change something in the future. I think this can possibly be achieved programmatically, but I would prefer an FXML solution if possible.

<Tab xmlns:fx="http://javafx.com/fxml/1"
     xmlns="http://javafx.com/javafx/8"
     fx:controller="com.SpecificController"
     id="specificTab" text="Specific Tab Name">

    <content>
        <JFXTreeTableView fx:id="commonTableView" VBox.vgrow="ALWAYS">
            <columns>
                <JFXTreeTableColumn fx:id="nameColumn" text="Name"/>
                <JFXTreeTableColumn fx:id="positionColumn" text="Position"/>
                <JFXTreeTableColumn fx:id="teamColumn" text="Team"/>
                <JFXTreeTableColumn fx:id="selectColumn" text="Select"/>
            </columns>
        </JFXTreeTableView>
    </content>
</Tab>

Upvotes: 1

Views: 1145

Answers (2)

Eric Chan
Eric Chan

Reputation: 59

you can define a fxml in component base, use the fxroot tag.

Upvotes: 0

Zephyr
Zephyr

Reputation: 10253

I am by no means a JavaFX expert, but I was able to come up with this solution.

This is an MCVE, so you can recreate this sample application completely and run it to see it in action.

This sample uses an AnchorPane for the reusable FXML node, but you could modify it to use a Tab instead. I find reusing the content itself to be a bit simpler, though.

I am using a simple Interface to define our tab controllers; this allows us to write one method that sets the content and the individual controller for each Tab.


Main.java:

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class Main extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        try {
            // Load the main layout
            FXMLLoader loader = new FXMLLoader(getClass().getResource("MainLayout.fxml"));

            // Show the Stage
            primaryStage.setWidth(700);
            primaryStage.setHeight(500);
            primaryStage.setScene(new Scene(loader.load()));
            primaryStage.show();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

FXML Documents:

MainLayout.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/9.0.1"
            xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.reusableTabs.MainController">
    <VBox alignment="TOP_CENTER" prefHeight="400.0" prefWidth="600.0" spacing="10.0" AnchorPane.bottomAnchor="0.0"
          AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
        <padding>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
        </padding>
        <Label style="-fx-font-size: 150%; -fx-font-weight: bold;" text="Reusable Tab Content Sample"/>
        <Separator prefWidth="200.0"/>
        <TabPane fx:id="tabPane" prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE"
                 VBox.vgrow="ALWAYS">
            <Tab fx:id="tab1" text="Tab #1"/>
            <Tab fx:id="tab2" text="Tab #2"/>
            <Tab fx:id="tab3" text="Tab #3"/>
        </TabPane>
    </VBox>
</AnchorPane>

TabContent.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0"
            prefWidth="600.0" xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1">
    <VBox alignment="CENTER" prefHeight="400.0" prefWidth="600.0" spacing="20.0" AnchorPane.bottomAnchor="0.0"
          AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
        <Label text="Look - reusable Tab contents!"/>
        <Button fx:id="btnTestController" mnemonicParsing="false" text="Test Controller"/>
        <padding>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
        </padding>
    </VBox>
</AnchorPane>

Controllers:

MainController.java:

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;

import java.io.IOException;

public class MainController {

    @FXML
    private TabPane tabPane;
    @FXML
    private Tab tab1;
    @FXML
    private Tab tab2;
    @FXML
    private Tab tab3;

    @FXML
    private void initialize() {

        // Our TabPane has 3 Tabs. Let's populate them with content, reusing our TabContent.fxml file
        setTabContent(tab1, new Tab1Controller());
        setTabContent(tab2, new Tab2Controller());
        setTabContent(tab3, new Tab3Controller());

    }

    /**
     * Sets the content of Tab to the TabContent.fxml document
     *
     * @param tab        The Tab whose content we wish to set
     * @param controller The controller for this particular Tab's content
     */
    private void setTabContent(Tab tab, TabController controller) {

        // Load our TabContent.fxml file and set it as the content of the Tab that was passed to this method
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("TabContent.fxml"));

            // Set the controller for this specific tab
            loader.setController(controller);

            // Set the content of the passed Tab to this FXML content
            tab.setContent(loader.load());
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

Tab1Controller:

import javafx.fxml.FXML;
import javafx.scene.control.Button;

public class Tab1Controller implements TabController {

    @FXML
    private Button btnTestController;

    @FXML
    private void initialize() {

        // Set our button to print out which controller is being used
        btnTestController.setOnAction(event -> System.out.println("Hello, from Controller #1!"));

    }
}

Just duplicate this controller two more times for Tab2Controller.java and `Tab3Controller.java


The Result:

screenshot

Clicking on the Test Controller button will print out Hello, from Controller #2!, confirming each Tab's contents are controlled by their own controller.

Upvotes: 2

Related Questions