javafxcontrollerselectionmodel

JavaFX TabPane selection wont work properly


I have two *.fxml forms with controllers. First is Window, second - ProductPane.

Simplified Window.fxml is:

<BorderPane prefWidth="650.0" prefHeight="450.0" fx:controller="package.WindowController">
    <center>
        <TabPane fx:id="tabsPane">
            <tabs>
                <Tab fx:id="productsTab" text="Products"/>
                <Tab fx:id="warehouseTab" text="Warehouse"/>
                <Tab fx:id="saleTab" text="Sale"/>
            </tabs>
        </TabPane>
    </center>
</BorderPane>

Controller for Window.fxml:

public class WindowController {
    @FXML
    private TabPane tabsPane;

    @FXML
    private Tab productsTab;

    @FXML
    void initialize() {
        sout("Main Window initialization...");

        tabsPane.getSelectionModel().selectedIndexProperty().addListener((e, o, n) -> {
            sout("Changed to " + n);
        });

        tabsPane.getSelectionModel().selectedItemProperty().addListener((e, o, n) -> {
            sout("New item: " + n);
            // Load ProductPane content:
            if(n == productsTab) {
                try {

                    Parent p = FXMLLoader.load(getClass().getResource("productPane.fxml"));
                    n.setContent(p);

                } catch(IOException ex) {
                    ex.printStackTrace();
                }
            }
        });

        sout("Select first item...");
        tabsPane.getSelectionModel().selectFirst();

        // This methods also don't work
        // tabsPane.getSelectionModel().clearAndSelect();
        // tabsPane.getSelectionModel().select(productTab);
        // tabsPane.getSelectionModel().select(0);
    }
}

The problem is: when I load Window.fxml in main() and launch it appearse window with empty first tab.

Debug output:

Main Window initialization...
Select first item...

But ProductPane not loaded and listener do not call. If I switch between tabs in Window, listeners are triggered and Products tab loaded properly.

What's the problem?


Solution

  • You added a ChangeListener to the tab pane's selection model, which of course gets notified when the selection changes. By default, the first tab is selected, so at the time the change listener is added, the first tab is already selected. This means when you call selectFirst(), the selection doesn't change (because you're asking to select the tab that is already selected), so the listener isn't notified.

    The solution is a little ugly: you just need to directly load your products tab content if the products tab is selected at the time you add the listener. I would factor that code into a separate method to avoid too much repetition:

    @FXML
    void initialize() {
        System.out.println("Main Window initialization...");
    
        tabsPane.getSelectionModel().selectedIndexProperty().addListener((e, o, n) -> {
            System.out.println("Changed to " + n);
        });
    
        tabsPane.getSelectionModel().selectedItemProperty().addListener((e, o, n) -> {
            System.out.println("New item: " + n);
            // Load ProductPane content:
            if(n == productsTab) {
                loadProductsTab();
            }
        });
    
        if (tabPane.getSelectionModel().getSelectedItem() == productsTab) {
            loadProductsTab();
        }
    }
    
    private void loadProductsTab() {
        try {
    
            Parent p = FXMLLoader.load(getClass().getResource("productPane.fxml"));
            productsTab.setContent(p);
    
        } catch(IOException ex) {
            ex.printStackTrace();
        }
    }
    

    If you find you need this kind of functionality a lot, you might be interested in the ReactFX framework, which (I think) has built-in functionality that handles these kinds of cases.