blackgreen
blackgreen

Reputation: 45162

JavaFX - bind properties of corresponding TableRows in different TableViews

I have two TableViews in the same scene that are closely related. I want to set up a listener such that when the user hovers a certain row in one table, the row with the same index in the other table is "hovered" as well.

I'm trying to solve this with a custom row factory tableView.setRowFactory(...). Inside the factory call(...) method I can toggle a CSS pseudo-class (.myclass:hover) on the target row, like:

row.hoverProperty().addListener((obs, o, n) -> {
        myOtherTable.[get row here].pseudoClassStateChanged(PseudoClass.getPseudoClass("hover"), true);
});

As you can see in my factory method I have a reference to the second TableView object, myOtherTable. I guess I have to get hold of its TableRow objects to go ahead and set the pseudo class, but I can't figure out how.
Maybe is there a better way to do this?

Upvotes: 1

Views: 975

Answers (2)

James_D
James_D

Reputation: 209684

Create a single property representing the index of the hovered row, and a PseudoClass:

IntegerProperty hoveredRowIndex = new SimpleIntegerProperty(-1);
PseudoClass appearHovered = PseudoClass.getPseudoClass("appear-hovered");

Now create a row factory that creates table rows that observe this value and their own index:

Callback<TableView<T>, TableCell<T>> rowFactory = tv -> {
    TableRow<T> row = new TableRow<T>() {
        private BooleanBinding shouldAppearHovered = Bindings.createBooleanBinding(
                () -> getIndex() != -1 && getIndex() == hoveredRowIndex.get(), indexProperty(),
                hoveredRowIndex);

        {

            shouldAppearHovered.addListener(
                    (obs, wasHovered, isNowHovered) -> pseudoClassStateChanged(appearHovered, isNowHovered));

            hoverProperty().addListener((obs, wasHovered, isNowHovered) -> {
                if (isNowHovered) {
                    hoveredRowIndex.set(getIndex());
                } else {
                    hoveredRowIndex.set(-1);
                }
            });
        }
    };
    return row;
};

(Replace T with the actual type of the table.)

And now use the row factory for both tables. You can use the CSS selector

.table-row-cell:appear-hovered {
    /* ... */
}

to style the rows that should appear to be hovered, or use

.table-row-cell:appear-hovered .table-cell {
    /* ... */
}

to style individual cells in that row.

Here's a SSCCE:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class ConnectedHoverTables extends Application {

    private IntegerProperty hoveredRowIndex = new SimpleIntegerProperty(-1);
    private PseudoClass appearHovered = PseudoClass.getPseudoClass("appear-hovered");

    @Override
    public void start(Stage primaryStage) {
        HBox root = new HBox(10, createTable(), createTable());
        root.setPadding(new Insets(20));
        Scene scene = new Scene(root);
        scene.getStylesheets().add("style.css");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private TableView<Item> createTable() {
        TableView<Item> table = new TableView<>();

        table.setRowFactory(tv -> {
            TableRow<Item> row = new TableRow<Item>() {
                private BooleanBinding shouldAppearHovered = Bindings.createBooleanBinding(
                        () -> getIndex() != -1 && getIndex() == hoveredRowIndex.get(), indexProperty(),
                        hoveredRowIndex);

                {

                    shouldAppearHovered.addListener(
                            (obs, wasHovered, isNowHovered) -> pseudoClassStateChanged(appearHovered, isNowHovered));

                    hoverProperty().addListener((obs, wasHovered, isNowHovered) -> {
                        if (isNowHovered) {
                            hoveredRowIndex.set(getIndex());
                        } else {
                            hoveredRowIndex.set(-1);
                        }
                    });
                }
            };
            return row;
        });

        table.setOnMouseClicked(e -> System.gc());

        table.getColumns().add(column("Item", Item::nameProperty));
        table.getColumns().add(column("Value", Item::valueProperty));
        table.getItems().setAll(createData());
        return table;
    }

    private List<Item> createData() {
        Random rng = new Random();
        List<Item> items = new ArrayList<>();
        for (int i = 1; i <= 100; i++) {
            Item item = new Item("Item " + i, rng.nextInt(1000));
            items.add(item);
        }
        return items;
    }

    private <S, T> TableColumn<S, T> column(String title, Function<S, ObservableValue<T>> property) {
        TableColumn<S, T> col = new TableColumn<>(title);
        col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
        return col;
    }

    public static class Item {

        private final StringProperty name = new SimpleStringProperty();
        private final IntegerProperty value = new SimpleIntegerProperty();

        public Item(String name, int value) {
            setName(name);
            setValue(value);
        }

        public final StringProperty nameProperty() {
            return this.name;
        }

        public final String getName() {
            return this.nameProperty().get();
        }

        public final void setName(final String name) {
            this.nameProperty().set(name);
        }

        public final IntegerProperty valueProperty() {
            return this.value;
        }

        public final int getValue() {
            return this.valueProperty().get();
        }

        public final void setValue(final int value) {
            this.valueProperty().set(value);
        }

    }

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

Upvotes: 2

Yoann Haffner
Yoann Haffner

Reputation: 53

If I remember correctly, you can't access directly a row of a TableView. The only way to get the row index is to access the attribute indexProperty when you define the CellFactory.

I advise you to create rather a personalized extending TableRow or TableCell object where you can stock an id or something like that...

Upvotes: 0

Related Questions