Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to load content to JavaFX tabs dynamically?

Tags:

fxml

javafx-2

I have a GUI, made using JavaFX with FXML.

This GUI has a lot of components and not all of them are needed at one moment of time.

For example, imagine a GUI that receives from its server part a list of cities. Each city is described on its own tab (and described with a lot of nodes). The set of cities contains 30 elements.

When the GUI is launched, it asks the server for a list of the cities. The server returns a random "sub-set" of cities (so, it can be Moscow + Riga + New York or St.Petersburg + Tokyo, or only Amsterdam, or all 30 cities in one set).

So. I have no need to have all 30 tabs in my node tree (I suppose they'll just "eat" memory and nothing more).

I want to manage the amount of tabs I have at each moment on my GUI.

The first simple solution I have is the following:

  1. Create an FXML file which contains the components for all cities
  2. During the initialization in the controller class, remove tabs, which aren't needed.

There are to problems I have with this solution. First, I don't know whether tabPane.getTabs().remove(index) really removes the tab and all its content from the nodes tree. Second, all unneeded tabs will be initializated before they will be removed, so they'll use memory and resources anyway, and my GUI can be a slower than it has to be.

The second solution I have is:

  1. Make a lot of FXMLs. One for all cities, one for each city and a one for each combination of cities.

But there will be way to many FXMLs, so this solution is also not useful.

The solution I dream for:

  1. Create an FXML file for each city and one for the main app with tabs.
  2. Load FXML city file content into a tab dynamically when needed.

So, if someone has any ideas on this task, or knows the solution, please help my with it...

like image 825
Victoria Agafonova Avatar asked Aug 27 '12 17:08

Victoria Agafonova


1 Answers

Ok, if I understood you correctly, here is my suggestion Victoria;
Suppose the main app FXML contains TabPane somewhere in it:

// other controls
<TabPane fx:id="tabPane" id="tabPane">
   <tabs>
   </tabs>
</TabPane>
// other controls

In main controller:

// TabPane in fxml
@FXML
private TabPane tabPane;

// The FXMLLoader
private FXMLLoader fXMLLoader = new FXMLLoader();

// City list fetched from server
private String[] cityList = {"Moscow", "Stambul", "New York", "Bishkek"};

// OPTIONAL : Map for "city name - city fxml controller" pairs
private Map<String, Object> cityControllerMap = new HashMap<String, Object>();

// Belows are in init method

// Add only tabs dynamically but not their content
for (String city : cityList) {
    tabPane.getTabs().add(new Tab(city));
}

// It is important to call it before adding ChangeListener to the tabPane to avoid NPE and
// to be able fire the manual selection event below. Otherwise the 1st tab will be selected
// with empty content.
tabPane.getSelectionModel().clearSelection();

// Add Tab ChangeListener
tabPane.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>() {
    @Override
    public void changed(ObservableValue<? extends Tab> observable, Tab oldValue, Tab newValue) {
        System.out.println("Tab selected: " + newValue.getText());
        if (newValue.getContent() == null) {
            try {
                // Loading content on demand
                Parent root = (Parent) fXMLLoader.load(this.getClass().getResource(newValue.getText() + ".fxml").openStream());
                newValue.setContent(root);

                // OPTIONAL : Store the controller if needed
                cityControllerMap.put(newValue.getText(), fXMLLoader.getController());

            } catch (IOException ex) {
                ex.printStackTrace();
            }
        } else {
            // Content is already loaded. Update it if necessary.
            Parent root = (Parent) newValue.getContent();
            // Optionally get the controller from Map and manipulate the content
            // via its controller.
        }
    }
});
// By default, select 1st tab and load its content.
tabPane.getSelectionModel().selectFirst();

If you decide to store the controllers, you may define a controller for every city fxml or define only one controller class for all of them and set it by like fXMLLoader.setController(new CommonCityController()); before loading city fxml file.

HTH.

like image 196
Uluk Biy Avatar answered Nov 23 '22 13:11

Uluk Biy