У меня есть таблица с некоторыми пользовательскими ячейками.Я включил выбор ячейки с помощью setCellSelectionEnabled (true).Теперь я хочу выделить всю строку при выборе ячейки.Для этого я использую псевдокласс, используя реализацию из двух ссылок:
1. https://community.oracle.com/message/12308173#12308173
2. Получить выбранную строку из TableView
Решение отлично работает для первых нескольких кликов, но выделение строки останавливается после нескольких щелчков, а затем выбор ячейки происходит без выделения строки.
Снимок экрана 1 - Строка подсвечивается при выборе ячейки - Выбранный фон ячейки темно-синий, а фон строки светло-синий
Снимок экрана 2 - Строка не выделяется при выборе ячейки.Подсветка в строке замораживается. - После случайного нажатия на ячейки в столбце «Дополнительные данные» подсветка в строке замораживается и выделяется только выбранная ячейка.
При отладке я обнаружил, что прослушиватель BooleanBinding (containsSelection.addListener) в Controller не вызывается после замораживания выделения строки.Я пытался использовать клавиатуру, и та же проблема возникает после нескольких выборов ячеек.Иногда для запуска проблемы требуется 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 & Listeners с помощью некоторых операторов печати для мониторинга значений.
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 ;
});