0

I have a list items I want to display in a TabPane, and just re-use the same viewModel and Gui, and just switch between which one is "current" at the moment seeing only 1 tab can be seen at a time.

When the 2nd tab is taller than the 1st, and I switch to it, it does not expand and truncates the panel. But, if I switch back to the 1st panel and then back to 2nd, it finally resizes itself properly.

I believe the issue is with the Dialog, not the TabPane; if I do not use a Dialog to display, it resizes correctly (final boolean showDialog = false in TabApps.java below, note this requires line 61 in TabsMainController.java to be commented out, the one ending in //DialogPane).

Screen Shots: Dialog:

Initial Load Switched to 2nd tab 1st time switched back to 1st tab Switched to 2nd tab 2nd time

No dialog:

Initial Load Switched to 2nd tab 1st time

EDIT: I added in some change listeners that print out the height property:

showDialog = true

(initial load)
Root Tab Container Height Changed: old=0.0, new=83.0
Individual Tab Content Height Changed; old=0.0, new=34.0
-----=======Selected Tab Changed=======-----
(select 2nd tab 1st time)
Individual Tab Content Height Changed; old=34.0, new=85.0
-----=======Selected Tab Changed=======-----
(go back to 1st tab)
Individual Tab Content Height Changed; old=85.0, new=34.0
-----=======Selected Tab Changed=======-----
(select 2nd tab 2nd time)
Root Tab Container Height Changed: old=83.0, new=134.0
Individual Tab Content Height Changed; old=34.0, new=85.0

showDialog = false

(initial load)
Root Tab Container Height Changed: old=0.0, new=63.0
Individual Tab Content Height Changed; old=0.0, new=34.0
-----=======Selected Tab Changed=======-----
(select 2nd tab 1st time)
Root Tab Container Height Changed: old=63.0, new=114.0
Individual Tab Content Height Changed; old=34.0, new=85.0

I have written a sample program to show this:

TabsApp.java


import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ButtonBar.ButtonData;
import javafx.stage.Stage;

public class TabsApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        
        Thing thing1 = new Thing("Thing #1", Arrays.asList("attr1"));
        Thing thing2 = new Thing("Thing #2", Arrays.asList("attrA", "attrB", "attrC", "attrD"));
        List<Thing> things = new ArrayList<>();
        things.add(thing1);
        things.add(thing2);
        
        ThingView thingView = new ThingView();
        thingView.show(thing1);
        
        FXMLLoader individualLoader = new FXMLLoader(getClass().getResource("individual_tab.fxml"));
        individualLoader.setController(new IndividualTabController(thingView));
        Node individualTab = individualLoader.load();
        
        FXMLLoader rootLoader = new FXMLLoader(getClass().getResource("tabs_main.fxml"));
        rootLoader.setController(new TabsMainController(things, individualTab, thingView));
        Node tabsRoot = rootLoader.load();
        
        final boolean showDialog = true;
        if (showDialog) {
            ButtonType selectButtonType = new ButtonType("Select", ButtonData.OK_DONE);
            Dialog<ButtonType> dialog = new Dialog<>();
            dialog.setTitle("Select A Service");
            dialog.getDialogPane().setContent(tabsRoot);
            dialog.getDialogPane().getButtonTypes().add(selectButtonType);
            dialog.getDialogPane().lookupButton(selectButtonType).setDisable(false);
            
            Optional<ButtonType> result = dialog.showAndWait();
            if (result.isPresent()) {
                System.out.println(result.get());
            }
        } else {
            Scene scene = new Scene((Parent) tabsRoot);
            primaryStage.setTitle("Tabs Halp");
            primaryStage.setScene(scene);
            primaryStage.show();
        }

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

}

IndividualTabController.java


import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;

public class IndividualTabController {
    
    @FXML
    protected HBox root;
    
    @FXML
    protected Label thingName;
    
    @FXML
    protected VBox thingAttributes;
    
    private final ThingView view;
    
    public IndividualTabController(ThingView view) {
        this.view = view;
    }
    
    @FXML
    public void initialize() {
        thingName.textProperty().bind(view.getName());
        Bindings.bindContentBidirectional(thingAttributes.getChildren(), view.getAttributes());
        
        root.heightProperty().addListener(new ChangeListener<Number>() {

            @Override
            public void changed(ObservableValue<? extends Number> observable,
                    Number oldValue, Number newValue) {
                System.out.println("Individual Tab Content Height Changed; old=" + oldValue.doubleValue() +", new=" + newValue.doubleValue());
                
            }
        });
    }
}

TabsMainController.java

package halp;
import java.util.List;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;

public class TabsMainController {
    
    @FXML
    protected TabPane tabsRoot;
    
    private final List<Thing> things;
    private final Node individualPanel;
    private ThingView view;
    
    public TabsMainController(List<Thing> things, Node individualPanel, ThingView view) {
        this.things = things;
        this.individualPanel = individualPanel;
        this.view = view;
    }
    
    @FXML
    public void initialize() {
        
        for (Thing thing : things) {
            tabsRoot.getTabs().add(new Tab(thing.getName()));
        }
        
        tabsRoot.getSelectionModel().getSelectedItem().setContent(individualPanel);

        tabsRoot.heightProperty().addListener(new ChangeListener<Number>() {

            @Override
            public void changed(ObservableValue<? extends Number> observable,
                    Number oldValue, Number newValue) {
                System.out.println("Root Tab Container Height Changed: old=" + oldValue.doubleValue() +", new=" + newValue.doubleValue());
            }
            
        });
        
        tabsRoot.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>() {

            @Override
            public void changed(ObservableValue<? extends Tab> observable,
                    Tab oldValue, Tab newValue) {
                System.out.println("-----=======Selected Tab Changed=======-----");
                
                if (oldValue == newValue) {
                    return;
                }
                
                int selected = tabsRoot.getSelectionModel().getSelectedIndex();
                view.show(things.get(selected));

                individualPanel.getScene().getWindow().sizeToScene();
                tabsRoot.getScene().getWindow().sizeToScene();
                tabsRoot.getParent().getScene().getWindow().sizeToScene(); //DialogPane
                
                oldValue.contentProperty().setValue(null);
                newValue.setContent(individualPanel);
            }
        });
    }
    
}

you can see here, in the changed method that I try the solution here by calling sizeToScene() on the DialogPane's Scene's Window, but it didn't appear to change the behaviour.

Thing.java

package halp;

import java.util.List;

public class Thing {

    private final String name;
    private final List<String> attributes;
    
    public Thing(String name, List<String> attributes) {
        this.name = name;
        this.attributes = attributes;
    }
    
    public String getName() {
        return name;
    }
    
    public List<String> getAttributes() {
        return attributes;
    }
}

ThingView.java


import java.util.stream.Collectors;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.Label;

public class ThingView {
    private final StringProperty name;
    private final ObservableList<Node> attributes;
    
    public ThingView() {
        name = new SimpleStringProperty();
        attributes = FXCollections.observableArrayList();
    }
    
    public void show(Thing thing) {
        this.name.set(thing.getName());
        this.attributes.setAll(thing.getAttributes().stream().map(attr -> new Label(attr)).collect(Collectors.toList()));
    }
    
    public StringProperty getName() {
        return name;
    }
    
    public ObservableList<Node> getAttributes() {
        return attributes;
    }
}

individual_tab.fxml

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

<?import java.net.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.ToggleGroup?>

<HBox fx:id="root"  xmlns:fx="http://javafx.com/fxml" spacing="10">
    <!-- Overview -->
    <VBox spacing="5">
        <VBox>
            <HBox spacing="5">
                <Label text="Name"/>
                <HBox>
                    <Label fx:id="thingName"/>
                </HBox>
            </HBox>
            <VBox fx:id="thingAttributes">
            </VBox>
        </VBox>
    </VBox>
</HBox>

tabs_main.fxml

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

<?import java.net.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.ToggleGroup?>

<TabPane fx:id="tabsRoot" xmlns:fx="http://javafx.com/fxml">
</TabPane>

I see there is a bug related to TabPane and resizing in open java fx: link

How do I make it resize correctly when a larger tab is open? Is this approach totally stupid and I should have a controller, view model and model for each tab...?

  • beware: in a listener to one property (here: selectedItem), you __must not__ access the value of a sibling property (here: selectedIndex) - that's because there is no guarantee of any update correlation between them, so the second is unspecified at that moment. Didn't check if that's really a problem here .. – kleopatra Mar 30 '22 at 08:58
  • oh geez, I didn't know that - thank you! – user1100069 Mar 30 '22 at 22:08

1 Answers1

0

Well it turns out I have put myself on a fool's errand; according to the comments on this bug report https://bugs.openjdk.java.net/browse/JDK-8094853 resizing the tabpane if a tab's content changes after being added isn't supported. I guess I will go with one Thing/ThingView/Controller per tab content.