JavaFX 8: Unstable interactive TabPane when adding new Tab

1k views Asked by At

I am making a highly interactive TabPane for viewing contact lists in JavaFX 8. For this I have made my own subclass of Tab, EditableTab, which has functionality for changing the name of the tab by double clicking on the name in the overview. When the user clicks the + sign to create a new contact list, I want the program to create a new tab, select it, then focus the name and select all the text - it is natural to name the contact list at once (similar to when you create a new file in windows).

My problem: This seems to be very unstable. Most of the times, it seems some kind of animation/transition problem arises, and the tab name ends up empty. Here is a screenshot of what usually, but not always, happens when the + button is clicked: screenshot

And here is what I want: screenshot2

Here is the code for my EditableTab:

public class EditableTab extends Tab {

private Label lbl;
private TextField txtField;

public EditableTab(String text, Node content) {
    super();
    setContent(content);
    lbl = new Label(text);
    txtField = new TextField(text);
    txtField.setStyle("-fx-background-color: transparent");
    setGraphic(lbl);
    setupInteractivity();
}

public TextField getTextField() {
    return txtField;
}

private void setupInteractivity() {
    lbl.setOnMouseClicked((mouseEvent) -> {
        if (mouseEvent.getClickCount() == 2) {
            showTextField();
        }
    });

    txtField.setOnAction(event -> setGraphic(lbl));

    txtField.focusedProperty().addListener(
            (observable, oldValue, newValue) -> {
        if (! newValue) {
            lbl.setText(txtField.getText());
            setGraphic(lbl);
        }
    });
}

public void showTextField() {
    txtField.setPrefWidth(lbl.getWidth());
    txtField.setText(lbl.getText());
    setGraphic(txtField);
    txtField.selectAll();
    txtField.requestFocus();
}

}

And here is the code where the functionality is implemented:

private void addNewContactlist() {
    Contactlist newList = new Contactlist();
    newList.setName("New contact list");
    contactlistApp.getContactlistData().add(newList);
    ListView<Person> lv = new ListView<Person>(newList.getContacts());
    setupListView(lv);
    int position = tabPane.getTabs().size() - 1;
    EditableTab tab = createEditableTab("New contact list", lv);
    tabPane.getTabs().add(position, tab);
    tabPane.getSelectionModel().select(tab);
    tab.showTextField();
}

I suspect that the problem comes from some animation/transition timings, but that is really just a guess. I tried wrapping the showTextField() call in a Platform.runLater() with no luck.

Here is a small test app to replicate the issue:

public class TestApp extends Application {

TabPane tabPane = new TabPane();

@Override
public void start(Stage primaryStage) throws Exception {
    Tab addNewContactlistTab = new Tab();
    addNewContactlistTab.setClosable(false);
    Label lbl = new Label("\u2795");

    lbl.setOnMouseClicked(mouseEvent -> {
        if (tabPane.getTabs().size() == 1) {
            addNewTab();
        }
    });

    addNewContactlistTab.setGraphic(lbl);
    tabPane.getTabs().add(addNewContactlistTab);

    addNewContactlistTab.selectedProperty().addListener(
            (observable, oldValue, newValue) -> {
        if (newValue && tabPane.getTabs().size() != 1) {
            addNewTab();
        }
    });

    Scene scene = new Scene(tabPane);
    primaryStage.setScene(scene);
    primaryStage.setWidth(600);
    primaryStage.setHeight(400);
    primaryStage.show();
}

private void addNewTab() {
    int insertionIndex = tabPane.getTabs().size() - 1;
    ListView<String> lv = new ListView<String>();
    EditableTab tab = new EditableTab("Unnamed", lv);
    tabPane.getTabs().add(insertionIndex, tab);
    tabPane.getSelectionModel().select(tab);
    tab.showTextField();
}

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

}

1

There are 1 answers

7
Chris Smith On BEST ANSWER

Here is my code for the RenamableTab class:

import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TextField;

public class RenamableTab extends Tab {
    private final Label     label;
    private final TextField textField;

    public RenamableTab() {
        this("New Tab", null);
    }

    public RenamableTab(String text) {
        this(text, null);
    }

    public RenamableTab(String text, Node content) {
        super();
        label = new Label(text);
        textField = new TextField(text);
        setContent(content);
        textField.setStyle("-fx-background-color: transparent");
        setGraphic(label);
        label.setOnMouseClicked((mouseEvent) -> {
            if (mouseEvent.getClickCount() == 2) {
                rename();
            }
        });
        textField.setOnAction(event -> setGraphic(label));
        textField.focusedProperty().addListener((observable, oldValue, newValue) -> {
            if (!newValue) {
                label.setText(textField.getText());
                setGraphic(label);
            }
        });
    }

    public TextField getTextField() {
        return textField;
    }

    public void rename() {
        //textField.setPrefWidth(label.getWidth());
        //textField.setText(label.getText());
        setGraphic(textField);
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        textField.selectAll();
                        textField.requestFocus();
                    }
                });
            }
        }.start();
    }
}

And here is my code for the FancyTabPane:

import javafx.event.EventHandler;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.input.MouseEvent;

public class FancyTabPane extends TabPane {
    public FancyTabPane() {
        Tab newTabTab = new Tab();
        newTabTab.setClosable(false);
        Label addLabel = new Label("\u2795");
        addLabel.setOnMouseClicked(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent paramT) {
                System.out.println("mouse click");
                addTab();
            }
        });
        /*
         * getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>() {
         * @Override
         * public void changed(ObservableValue<? extends Tab> paramObservableValue, Tab paramT1, Tab
         * paramT2) {
         * System.out.println("model");
         * if (paramT1 == newTabTab) {
         * System.out.println("tab");
         * addTab();
         * }
         * }
         * });
         */
        newTabTab.setGraphic(addLabel);
        getTabs().add(newTabTab);
    }

    public void addTab() {
        RenamableTab newTab = new RenamableTab();
        getTabs().add(getTabs().size() - 1, newTab);
        getSelectionModel().select(newTab);
        newTab.rename();
    }
}

I am having issued with the new tab button when another tab is selected, not sure how you overcame that.