【问题标题】:javafx tableview - highlight row on setCellSelectionEnabled(true)javafx tableview - 突出显示 setCellSelectionEnabled(true)上的行
【发布时间】:2018-10-31 17:07:09
【问题描述】:

我有一个带有一些自定义单元格的表格视图。我使用 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>

编辑1:

控制器:使用一些打印语句更新 setRowFactory 和侦听器以监控值。

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 ;
    });

【问题讨论】:

  • 您说containsSelection 没有触发,所以找出原因:它的依赖项没有更新吗? (rowsWithSelectedCellsrow.indexProperty())。如果他们没有更新,那么找出原因:调试personTableView.getSelectionModel().getSelectedCells().addListener(...) 等。去兔子洞看看你发现了什么。
  • 感谢@xtratic 更新我的信息。在进一步调试中,行高亮冻结后似乎未触发 personTableView.setRowFactory。 personTableView.getSelectionModel().getSelectedCells().addListener(...) 已正确更新并且即使在行上的突出显示冻结后也可以正常工作。在我对原始问题的 EDIT1 中,我展示了带有 cmets 的打印语句。
  • 所以personTableView.getSelectionModel().getSelectedCells().addListener(...) 被触发并运行rowsWithSelectedCells.clear();rowsWithSelectedCells.addAll(rows); 但这不是更新依赖rowsWithSelectedCellsBooleanBinding containsSelection?在rowsWithSelectedCells 上添加一个监听器并查看它何时/是否在更新。弄清楚为什么 containsSelection 在它的依赖项似乎应该更新它时没有更新。

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


【解决方案1】:

我遇到了同样的问题:使用普通的 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;
}); 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-01-27
    • 1970-01-01
    • 2016-11-24
    • 1970-01-01
    • 2019-04-27
    • 2020-03-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多