DbSchema
DbSchema

Reputation: 383

JavaFx TableView mouse-over effect

Swing has a feature to enlarge tablecells on mouse over. For example, a cell without mouse-over ( see second row ):

enter image description here

shows on mouse-over like this:

enter image description here

Is it possible to implement something similar in JavaFx TableView TableCell?

Update: This code is something which may work. What I need is to find out the real string width, something like in this example, but for TableCell: sample


import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;

import java.util.List;

public class ExpandingLabelApp2 extends Application {
    @Override
    public void start(Stage stage) {
        TableView<Lines> tableView = new TableView<>(
                createTestData()
        );

        TableColumn<Lines, String> col1 = new TableColumn<>(
                "Instruction 1"
        );
        col1.setCellValueFactory(p ->
                readOnlyStringProperty(p.getValue().line1)
        );
        col1.setCellFactory(p ->
                new TooltipTableCell<>()
        );
        col1.setMaxWidth(120);

        TableColumn<Lines, String> col2 = new TableColumn<>(
                "Instruction 2"
        );
        col2.setCellValueFactory(p ->
                readOnlyStringProperty(p.getValue().line2)
        );

        tableView.getColumns().setAll(
                List.of(col1, col2)
        );

        tableView.setPrefSize(500, 200);

        stage.setScene(new Scene(tableView));
        stage.show();
    }

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

    private static ReadOnlyStringProperty readOnlyStringProperty(String string) {
        return new ReadOnlyStringWrapper(string).getReadOnlyProperty();
    }

    private static ObservableList<Lines> createTestData() {
        ObservableList<Lines> lines = FXCollections.observableArrayList();
        for (int i = 0; i < TROUBLE.size(); i += 2) {
            String line1 = TROUBLE.get(i);
            String line2 = i+1 < TROUBLE.size() ? TROUBLE.get(i+1) : "";

            lines.add(new Lines(line1, line2));
        }
        return lines;
    }

    private static final List<String> TROUBLE = """
            Double, double toil and trouble;
            Fire burn and caldron bubble.
            Fillet of a fenny snake,
            In the caldron boil and bake;
            Eye of newt and toe of frog,
            Wool of bat and tongue of dog,
            Adder's fork and blind-worm's sting,
            Lizard's leg and howlet's wing,
            For a charm of powerful trouble,
            Like a hell-broth boil and bubble.
            Double, double toil and trouble;
            Fire burn and caldron bubble.
            Cool it with a baboon's blood,
            Then the charm is firm and good.
            """.lines().toList();

    record Lines(String line1, String line2) {}


    public class TooltipTableCell<S> extends TableCell<S, String> {

        public TooltipTableCell() {
            super();
            setOnMouseEntered(ev ->{
                //Node text = lookup(".text");
                //setPrefWidth(text != null ? text.getBoundsInLocal().getHeight() : 1200);
                setManaged(false);
                setPrefWidth( 1200);
            });
            setOnMouseExited(e -> {
                if ( !isManaged()){
                    setManaged( true );
                    setPrefWidth( USE_COMPUTED_SIZE );
                }
            });
        }

        @Override
        protected void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);

            if (item == null || empty) {
                setText(null);
                setTooltip(null);
            } else {
                setText(item);
            }
        }
    }

}

Upvotes: 1

Views: 81

Answers (2)

DbSchema
DbSchema

Reputation: 383

I found a solution for this problem. Is not the perfect one, but close to my request. The solution with the tooltip is also fine, but this has the advantage for not duplicating the text on the screen and being more consistent. Here is the code:


import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.text.Text;
import javafx.stage.Stage;

import java.util.List;

public class ExpandingLabelApp2 extends Application {
    @Override
    public void start(Stage stage) {
        TableView<Lines> tableView = new TableView<>(
                createTestData()
        );

        TableColumn<Lines, String> col1 = new TableColumn<>(
                "Instruction 1"
        );
        col1.setCellValueFactory(p ->
                readOnlyStringProperty(p.getValue().line1)
        );
        col1.setCellFactory(p ->
                new TooltipTableCell<>()
        );

        TableColumn<Lines, String> col2 = new TableColumn<>(
                "Instruction 2"
        );
        col2.setCellValueFactory(p ->
                readOnlyStringProperty(p.getValue().line2)
        );

        tableView.getColumns().setAll(
                List.of(col1, col2)
        );

        tableView.setPrefSize(500, 200);

        stage.setScene(new Scene(tableView));
        stage.show();
    }

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

    private static ReadOnlyStringProperty readOnlyStringProperty(String string) {
        return new ReadOnlyStringWrapper(string).getReadOnlyProperty();
    }

    private static ObservableList<Lines> createTestData() {
        ObservableList<Lines> lines = FXCollections.observableArrayList();
        for (int i = 0; i < TROUBLE.size(); i += 2) {
            String line1 = TROUBLE.get(i);
            String line2 = i+1 < TROUBLE.size() ? TROUBLE.get(i+1) : "";

            lines.add(new Lines(line1, line2));
        }
        return lines;
    }

    private static final List<String> TROUBLE = """
            Double, double toil and trouble;
            Fire burn and caldron bubble.
            Fillet of a fenny snake,
            In the caldron boil and bake;
            Eye of newt and toe of frog,
            Wool of bat and tongue of dog,
            Adder's fork and blind-worm's sting,
            Lizard's leg and howlet's wing,
            For a charm of powerful trouble,
            Like a hell-broth boil and bubble.
            Double, double toil and trouble;
            Fire burn and caldron bubble.
            Cool it with a baboon's blood,
            Then the charm is firm and good.
            """.lines().toList();

    record Lines(String line1, String line2) {}


    public static class TooltipTableCell<S> extends TableCell<S, String> {

        public TooltipTableCell() {
            super();
            setOnMouseEntered(ev ->{
                Text text = new Text(getText());
                text.setFont( getFont() );
                    double textNodeWidth = text.getLayoutBounds().getWidth() + 10;
                    if ( textNodeWidth > getWidth() ) {
                        setManaged(false);
                        setPrefWidth( textNodeWidth );
                    }
            });
            setOnMouseExited(e -> {
                if ( !isManaged()){
                    setManaged( true );
                    setPrefWidth( USE_COMPUTED_SIZE );
                }
            });
        }

        @Override
        protected void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);

            if (item == null || empty) {
                setText(null);
                setTooltip(null);
            } else {
                setText(item);
            }
        }
    }

}

Upvotes: 2

jewelsea
jewelsea

Reputation: 159291

A solution using a ToolTip, it is not exactly what you want, but for a lot of people wanting similar functionality it might be fine.

This solution creates a custom cell factory. In the cell factory, a Tooltip is installed on the cell. The Tooltip shows the full text on hover.

TooltipTableCell.java

import javafx.scene.control.TableCell;
import javafx.scene.control.Tooltip;
import javafx.util.Duration;

public class TooltipTableCell<S> extends TableCell<S, String> {
    private final Tooltip tooltip = new Tooltip();

    public TooltipTableCell() {
        super();
        tooltip.setShowDelay(Duration.ZERO);
    }

    @Override
    protected void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);

        if (item == null || empty) {
            setText(null);
            tooltip.setText(null);
            setTooltip(null);
        } else {
            setText(item);
            tooltip.setText(item);
            setTooltip(tooltip);
        }
    }
}

Potential customizations

There might be some additional customizations you may wish to make such as:

  • Styling the tooltip with CSS.
  • Conditionally showing the tooltip only if the text exceeds a certain width or number of characters.
  • Changing the anchor point location of the tooltip.

I don't provide any potential modifications in this answer.

Example Usage

Mouse over the text in the first column of the table to see a tooltip displaying the full text of the cell.

import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;

import java.util.List;

public class ExpandingLabelApp extends Application {
    @Override
    public void start(Stage stage) {
        TableView<Lines> tableView = new TableView<>(
                createTestData()
        );

        TableColumn<Lines, String> col1 = new TableColumn<>(
                "Instruction 1"
        );
        col1.setCellValueFactory(p ->
                readOnlyStringProperty(p.getValue().line1)
        );
        col1.setCellFactory(p ->
                new TooltipTableCell<>()
        );
        col1.setMaxWidth(120);

        TableColumn<Lines, String> col2 = new TableColumn<>(
                "Instruction 2"
        );
        col2.setCellValueFactory(p ->
                readOnlyStringProperty(p.getValue().line2)
        );

        tableView.getColumns().setAll(
                List.of(col1, col2)
        );

        tableView.setPrefSize(500, 200);

        stage.setScene(new Scene(tableView));
        stage.show();
    }

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

    private static ReadOnlyStringProperty readOnlyStringProperty(String string) {
        return new ReadOnlyStringWrapper(string).getReadOnlyProperty();
    }

    private static ObservableList<Lines> createTestData() {
        ObservableList<Lines> lines = FXCollections.observableArrayList();
        for (int i = 0; i < TROUBLE.size(); i += 2) {
            String line1 = TROUBLE.get(i);
            String line2 = i+1 < TROUBLE.size() ? TROUBLE.get(i+1) : "";

            lines.add(new Lines(line1, line2));
        }
        return lines;
    }

    private static final List<String> TROUBLE = """
            Double, double toil and trouble;
            Fire burn and caldron bubble.
            Fillet of a fenny snake,
            In the caldron boil and bake;
            Eye of newt and toe of frog,
            Wool of bat and tongue of dog,
            Adder's fork and blind-worm's sting,
            Lizard's leg and howlet's wing,
            For a charm of powerful trouble,
            Like a hell-broth boil and bubble.
            Double, double toil and trouble;
            Fire burn and caldron bubble.
            Cool it with a baboon's blood,
            Then the charm is firm and good.
            """.lines().toList();

    record Lines(String line1, String line2) {}
}

Alternate Implementation

Create a custom skin for a label which handles a new popover behavior or overrun style.

The label skin code is in LabeledSkinBase. I think I created a feature request for similar behavior sometime in the past or mentioned it on a developer forum. The controls developer at the time responded that it sounded like a good feature. Still, I don't think it was ever implemented.

This is not a trivial implementation option. It is something I would only recommend for experienced JavaFX developers and I do not attempt to do it here.

Upvotes: 4

Related Questions