javafx tableview - 突出显示setCellSelectionEnabled上的行(true)

时间:2018-05-22 02:47:33

标签: css javafx tableview pseudo-class custom-cell

我有一些自定义单元格的tableview。我已使用setCellSelectionEnabled(true)启用了单元格选择。现在我想强调选择单元格的整行。为了实现这一点,我使用PseudoClass使用以下2个链接的实现:
1. https://community.oracle.com/message/12308173#12308173
2. Get selected row from TableView

该解决方案适用于最初的几次点击,但只需点击几下即可冻结该行上的突出显示。然后选择单元格而不突出显示该行。

Screenshot 1 - Row is highlighted on selecting the cell - 所选择的细胞背景是深蓝色&行背景为浅蓝色
Screenshot 2 - Row is not highlighted on selecting the cell. Highlight on the row is freezed. - 在“附加数据”列中随机点击单元格后,该行上的突出显示将被冻结。仅突出显示所选单元格。

在调试时,我发现在行上的高亮显示被冻结后,Controller中的BooleanBinding(containsSelection.addListener)上的侦听器未被调用。我尝试过使用键盘和键盘几个单元格选择后发生同样的问题。有时需要10到12次点击才能触发&有一次,我花了大约75次点击来触发这个问题。

以下是代码:

控制器:

package sample;

import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.collections.*;
import javafx.css.PseudoClass;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;

import java.net.URL;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.stream.Collectors;

public class RowHighlightController implements Initializable {
@FXML
private TableView<Person> personTableView;

@FXML
private TableColumn<Person, String> someDataColumn;

@FXML
private TableColumn<Person, String> someMoreDataColumn;

@FXML
private TableColumn<Person, String> additionalDataColumn;

Person person = new Person();

@Override
public void initialize(URL location, ResourceBundle resources) {
    someDataColumn.setCellValueFactory(new PropertyValueFactory<>("someData"));
    someMoreDataColumn.setCellValueFactory(new PropertyValueFactory<>("someMoreData"));
    ObservableList<Person> personObservableList = FXCollections.observableArrayList();
    additionalDataColumn.setCellValueFactory(new PropertyValueFactory<>("additionalData"));
    someDataColumn.setCellFactory(e -> new EditableTextCell());
    someMoreDataColumn.setCellFactory(e -> new EditableTextCell());
    Person initPerson = setUpPersonData();
    Person personData1 = null, personData2 = null, personData3 = null, personData4 = null, personData5 = null, personData6 = null;
    try {
        personData1 = (Person) initPerson.clone();
        personData2 = (Person) initPerson.clone();
        personData3 = (Person) initPerson.clone();
        personData4 = (Person) initPerson.clone();
        personData5 = (Person) initPerson.clone();
        personData6 = (Person) initPerson.clone();
        personData1.setSomeData("1");
        personData1.setSomeMoreData("a");
        personData2.setSomeData("2");
        personData2.setSomeMoreData("b");
        personData3.setSomeData("3");
        personData3.setSomeMoreData("c");
        personData4.setSomeData("4");
        personData4.setSomeMoreData("d");
        personData5.setSomeData("5");
        personData5.setSomeMoreData("e");
        personData6.setSomeData("6");
        personData6.setSomeMoreData("f");
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    personObservableList.addAll(personData1, personData2, personData3, personData4, personData5, personData6);
    personTableView.setItems(personObservableList);
    personTableView.getSelectionModel().setCellSelectionEnabled(true);
    personTableView.getStylesheets().add(getClass().getResource("tableView.css").toExternalForm());
    personTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
    ObservableSet<Integer> rowsWithSelectedCells = FXCollections.observableSet();
    PseudoClass rowContainsSelectedCell = PseudoClass.getPseudoClass("contains-selection");
    personTableView.getSelectionModel().getSelectedCells().addListener((ListChangeListener.Change<? extends TablePosition> c) -> {
        rowsWithSelectedCells.clear();
        Set<Integer> rows = personTableView.getSelectionModel().getSelectedCells().stream().map(pos -> pos.getRow()).collect(Collectors.toSet());
        rowsWithSelectedCells.addAll(rows);
    });

    personTableView.setRowFactory(tv -> {
        TableRow<Person> row = new TableRow<>();
        BooleanBinding containsSelection = Bindings.createBooleanBinding(
                () -> rowsWithSelectedCells.contains(row.getIndex()), rowsWithSelectedCells, row.indexProperty());
        containsSelection.addListener((obs, didContainSelection, nowContainsSelection) ->
                row.pseudoClassStateChanged(rowContainsSelectedCell, nowContainsSelection));
        return row ;
    });
    /* I have tried using below block of code to resolve the row highlight issue. But its behaves the same.
    ObservableMap<Integer, Integer> selectedCellCountByRow = FXCollections.observableHashMap();
    personTableView.getSelectionModel().getSelectedCells().addListener((ListChangeListener.Change<? extends TablePosition> c) -> {
        while (c.next()) {
            if (c.wasAdded()) {
                for (TablePosition<?,?> p : c.getAddedSubList()) {
                    int currentCount = selectedCellCountByRow.getOrDefault(p.getRow(), 0);
                    int newCount = currentCount + 1 ;
                    selectedCellCountByRow.put(new Integer(p.getRow()), newCount);
                    System.out.println("Count now: "+selectedCellCountByRow.get(p.getRow()));
                }
            }
            if (c.wasRemoved()) {
                for (TablePosition<?, ?> p : c.getRemoved()) {
                    int currentCount = selectedCellCountByRow.getOrDefault(p.getRow(), 0);
                    int newCount = currentCount - 1 ;
                    if (newCount <= 0) {
                        selectedCellCountByRow.remove(p.getRow());
                    } else {
                        selectedCellCountByRow.put(p.getRow(), newCount);
                    }
                }
            }
        }
    });
    PseudoClass rowContainsSelectedCell = PseudoClass.getPseudoClass("contains-selection");
    personTableView.setRowFactory(tv -> {
        TableRow<Person> row = new TableRow<>();
        BooleanBinding containsSelection = Bindings.createBooleanBinding(
                () -> selectedCellCountByRow.containsKey(row.getIndex()), selectedCellCountByRow, row.indexProperty());
        containsSelection.addListener((obs, didContainSelection, nowContainsSelection) ->
                row.pseudoClassStateChanged(rowContainsSelectedCell, nowContainsSelection));
        return row ;
    });*/
}

private Person setUpPersonData() {
    try {
        person.setSomeData("This is SomeData");
        person.setSomeMoreData("This is SomeMoreDate");
        person.setAdditionalData("This is AdditionalData");
    } catch (Exception e1) {
        e1.printStackTrace();
    }
    return person;
}
}

CSS:

.table-row-cell:contains-selection{
    -fx-background-color:lightskyblue;
}

人类:

package sample;

import javafx.beans.property.SimpleStringProperty;

public class Person {
private SimpleStringProperty someData, someMoreData, additionalData;

public Person() {
    this.someData = new SimpleStringProperty("");
    this.someMoreData = new SimpleStringProperty("");
    this.additionalData = new SimpleStringProperty("");
}

public Person(String someData, String someMoreData, String additionalData) {
    this.someData = new SimpleStringProperty(someData);
    this.someMoreData = new SimpleStringProperty(someMoreData);
    this.additionalData = new SimpleStringProperty(additionalData);
}

@Override
public Object clone()throws CloneNotSupportedException{
    String someDataCloned = this.someData.getValue();
    String someMoreDataCloned = this.someMoreData.getValue();
    String additionalDataCloned = this.additionalData.getValue();
    Person personCloned = new Person(someDataCloned, someMoreDataCloned, additionalDataCloned);
    return personCloned;
}

public String getSomeData() {
    return someData.get();
}

public SimpleStringProperty someDataProperty() {
    return someData;
}

public void setSomeData(String someData) {
    this.someData.set(someData);
}

public String getSomeMoreData() {
    return someMoreData.get();
}

public SimpleStringProperty someMoreDataProperty() {
    return someMoreData;
}

public void setSomeMoreData(String someMoreData) {
    this.someMoreData.set(someMoreData);
}

public String getAdditionalData() {
    return additionalData.get();
}

public SimpleStringProperty additionalDataProperty() {
    return additionalData;
}

public void setAdditionalData(String additionalData) {
    this.additionalData.set(additionalData);
}
}

可编辑文字单元格:

package sample;
import javafx.beans.value.WritableValue;
import javafx.scene.control.*;
import java.util.Objects;

public class EditableTextCell<Person> extends TableCell<Person, String> {

private final TextField textField;
private boolean updating = false;

public EditableTextCell() {
    textField = new TextField();
    textField.textProperty().addListener((o, oldValue, newValue) -> {
        if (!updating) {
            ((WritableValue<String>) getTableColumn().getCellObservableValue((Person) getTableRow().getItem())).setValue(newValue);
        }
    });
}

@Override
protected void updateItem(String item, boolean empty) {
    super.updateItem(item, empty);
    if (empty) {
        setGraphic(null);
    } else {
        setGraphic(textField);
        if (!Objects.equals(textField.getText(), item)) { // prevent own updates from moving the cursor
            updating = true;
            textField.setText(item);
            updating = false;
        }
    }
}
}

FXML:

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

<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane prefHeight="400.0" prefWidth="1250.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.RowHighlightController">
<children>
    <TableView fx:id="personTableView" layoutY="50.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
        <columns>
            <TableColumn fx:id="someDataColumn" prefWidth="84.0" text="Some Data" />
            <TableColumn fx:id="someMoreDataColumn" minWidth="100.0" prefWidth="100.0" text="Some More Data" />
            <TableColumn fx:id="additionalDataColumn" minWidth="150.0" prefWidth="150.0" text="Additional Data" />
        </columns>
    </TableView>
</children>
</AnchorPane>

EDIT1:

控制器:更新setRowFactory&amp;具有一些打印语句的监听器,用于监控值。

personTableView.getSelectionModel().getSelectedCells().addListener(( 
ListChangeListener.Change<? extends TablePosition> c) -> {
        rowsWithSelectedCells.clear();
        // Output for below statement is always - rowContainsSelectedCell in after rowsWithSelectedCells.clear() []
        System.out.println("rowContainsSelectedCell in after rowsWithSelectedCells.clear() " + rowsWithSelectedCells);
        Set<Integer> rows = personTableView.getSelectionModel().getSelectedCells().stream().map(pos -> pos.getRow()).collect(Collectors.toSet());
        rowsWithSelectedCells.addAll(rows);
        // Example Output for below statement is 'rowContainsSelectedCell in after rowsWithSelectedCells.addAll(rows) [3]'
        // The number 3 is row number selected.
        System.out.println("rowContainsSelectedCell in after rowsWithSelectedCells.addAll(rows) " + rowsWithSelectedCells);
    });

    personTableView.setRowFactory(tv -> {
        TableRow<Person> row = new TableRow<>();
        BooleanBinding containsSelection = Bindings.createBooleanBinding(
                () -> {
                    // below 2 statements are not printed after highlighting on the row freezes
                    System.out.println("rowsWithSelectedCells in setRowFactory " + rowsWithSelectedCells);
                    System.out.println("row.indexProperty() in setRowFactory " + row.indexProperty().getValue());
                    return rowsWithSelectedCells.contains(row.getIndex());
                }, rowsWithSelectedCells, row.indexProperty());
        containsSelection.addListener((obs, didContainSelection, nowContainsSelection) ->
                row.pseudoClassStateChanged(rowContainsSelectedCell, nowContainsSelection));
        return row ;
    });

1 个答案:

答案 0 :(得分:0)

我遇到了同样的问题:使用普通的Java FX绑定框架,我无法使其运行超过几次单击。

但是,如果您使用ReactFX Val代替,它将可靠地工作。这是我的工作代码:

public static final PseudoClass CONTAINS_SELECTION           = PseudoClass.getPseudoClass("contains-selection");

[...]

ObservableSet<Integer> rowsWithSelectedCells = FXCollections.observableSet();
tableView.getSelectionModel().getSelectedIndices().addListener((InvalidationListener) c -> {
    rowsWithSelectedCells.clear();
    Set<Integer> rows = tableView.getSelectionModel().getSelectedCells().stream().map(pos -> pos.getRow()).collect(Collectors.toSet());
    rowsWithSelectedCells.addAll(rows);
});

tableView.setRowFactory(tv -> {
    TableRow row = new TableRow<>();
    Val<Boolean> containsSelectionVal = Val.create(() -> rowsWithSelectedCells.contains(row.getIndex()), rowsWithSelectedCells, row.indexProperty());

    containsSelectionVal.addListener((observable, oldValue, newValue) -> {
        row.pseudoClassStateChanged(CONTAINS_SELECTION, newValue);
    });

    return row;
});