在 JavaFX 8 中为 select 全部创建框架类型列 header
Create a framework type column header for select all in JavaFX 8
我想创建一个自定义 TableColumn,它是一个 CheckBox 控件,表示该行是否 selected。我们不想使用标准的 SHIFT 或 CONTROL + CLICK 来处理这个问题,因为用户往往会不经意地点击并失去他们的 selection。
我想做的是:
在这种情况下,该列不应是基础数据模型的一部分,而只是表示 select 编辑或不编辑的内容。它不应该以任何方式依赖于数据模型。如果用户 select 选中了 header 列中的复选框,它应该 select table 中的所有记录(不仅仅是可见的)并且如果它是 deselected,然后它应该删除select所有记录。
有没有办法用这种方式表示 selection 模型?
我遇到的第二个问题是,我的第一次尝试不会在 SceneBuilder 中显示为 selectable,因为(据我所知)TableColumn 不是节点,因此似乎被忽略了导入?
如有任何帮助,我们将不胜感激。
谢谢
可能的解决方案是扩展 TableViewSkin
以添加带有复选框的 TableColumn
,虽然此列未与用户模型一起烘焙,但复选框选择的更改将影响 table的选择模型。
虽然仅通过复选框进行选择可以正常工作,但您无法删除也允许选择行的行为,因此您必须监听这两种情况。
此代码段有效,但尚未通过排序和修改模型进行测试,因此它只是自定义 TableView
.
的开始
CheckTableViewclass
public class CheckTableView<T> extends TableView<T> {
private ObservableList<T> selected;
public CheckTableView() {
this(FXCollections.observableArrayList());
}
public CheckTableView(ObservableList<T> items) {
setItems(items);
setEditable(true);
getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
skinProperty().addListener(new InvalidationListener() {
@Override
public void invalidated(Observable observable) {
selected = ((CheckTableViewSkin) getSkin()).getSelectedRows();
skinProperty().removeListener(this);
}
});
}
@Override
protected Skin<?> createDefaultSkin() {
return new CheckTableViewSkin<>(this);
}
public ObservableList<T> getSelectedRows() {
return selected;
}
}
CheckTableViewSkin class
public class CheckTableViewSkin<T> extends TableViewSkin<T> {
private final TableColumn<T, Boolean> checkColumn;
private final CheckBox headerCheckBox = new CheckBox();
private final List<BooleanProperty> colSelected = new ArrayList<>();
private final ChangeListener<Number> listener = (obs, ov, nv) -> {
if (nv.intValue() != -1) {
Platform.runLater(() -> {
colSelected.get(nv.intValue()).set(!colSelected.get(nv.intValue()).get());
refreshSelection();
});
}
};
private final ChangeListener<Boolean> headerListener = (obs, ov, nv) -> {
Platform.runLater(() -> {
IntStream.range(0, colSelected.size()).forEach(i -> colSelected.get(i).set(nv));
refreshSelection();
});
};
public CheckTableViewSkin(CheckTableView<T> control) {
super(control);
checkColumn = new TableColumn<>();
headerCheckBox.selectedProperty().addListener(headerListener);
checkColumn.setGraphic(headerCheckBox);
// install listeners in checkboxes
IntStream.range(0, control.getItems().size()).forEach(i -> {
final SimpleBooleanProperty simple = new SimpleBooleanProperty();
simple.addListener((obs, ov, nv) -> refreshSelection());
colSelected.add(simple);
});
checkColumn.setCellFactory(CheckBoxTableCell.forTableColumn(colSelected::get));
checkColumn.setPrefWidth(60);
checkColumn.setEditable(true);
checkColumn.setResizable(false);
getColumns().add(0, checkColumn);
getSelectionModel().selectedIndexProperty().addListener(listener);
}
private void refreshSelection() {
// refresh list of selected rows
getSelectionModel().selectedIndexProperty().removeListener(listener);
getSelectionModel().clearSelection();
AtomicInteger count = new AtomicInteger();
IntStream.range(0, colSelected.size()).forEach(i -> {
if (colSelected.get(i).get()) {
getSelectionModel().select(i);
count.getAndIncrement();
}
});
headerCheckBox.selectedProperty().removeListener(headerListener);
headerCheckBox.setSelected(count.get() == colSelected.size());
headerCheckBox.selectedProperty().addListener(headerListener);
// it may flick, but required to show all selected rows focused
getSkinnable().requestFocus();
getSelectionModel().selectedIndexProperty().addListener(listener);
}
public ObservableList<T> getSelectedRows() {
return getSelectionModel().getSelectedItems();
}
}
现在的示例,Application
class:
@Override
public void start(Stage primaryStage) {
TableColumn<Person, String> firstNameColumn = new TableColumn<>("First Name");
firstNameColumn.setCellValueFactory(p -> p.getValue().firstNameProperty());
TableColumn<Person, String> lastNameColumn = new TableColumn<>("Last Name");
lastNameColumn.setCellValueFactory(p -> p.getValue().lastNameProperty());
CheckTableView<Person> tableView = new CheckTableView(FXCollections.observableArrayList(
new Person("Hans", "Muster"), new Person("Ruth", "Mueller"),
new Person("Heinz", "Kurz"), new Person("Cornelia", "Meier"),
new Person("Anna", "Best"), new Person("Stefan", "Meier")
));
tableView.getColumns().addAll(firstNameColumn, lastNameColumn);
Scene scene = new Scene(tableView, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
对于通常的 Person
class:
public class Person {
private final StringProperty firstName;
private final StringProperty lastName;
public Person(String firstName, String lastName) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
}
//getters & setters
}
此自定义控件也适用于 Scene Builder。
我想创建一个自定义 TableColumn,它是一个 CheckBox 控件,表示该行是否 selected。我们不想使用标准的 SHIFT 或 CONTROL + CLICK 来处理这个问题,因为用户往往会不经意地点击并失去他们的 selection。
我想做的是:
在这种情况下,该列不应是基础数据模型的一部分,而只是表示 select 编辑或不编辑的内容。它不应该以任何方式依赖于数据模型。如果用户 select 选中了 header 列中的复选框,它应该 select table 中的所有记录(不仅仅是可见的)并且如果它是 deselected,然后它应该删除select所有记录。
有没有办法用这种方式表示 selection 模型?
我遇到的第二个问题是,我的第一次尝试不会在 SceneBuilder 中显示为 selectable,因为(据我所知)TableColumn 不是节点,因此似乎被忽略了导入?
如有任何帮助,我们将不胜感激。
谢谢
可能的解决方案是扩展 TableViewSkin
以添加带有复选框的 TableColumn
,虽然此列未与用户模型一起烘焙,但复选框选择的更改将影响 table的选择模型。
虽然仅通过复选框进行选择可以正常工作,但您无法删除也允许选择行的行为,因此您必须监听这两种情况。
此代码段有效,但尚未通过排序和修改模型进行测试,因此它只是自定义 TableView
.
CheckTableViewclass
public class CheckTableView<T> extends TableView<T> {
private ObservableList<T> selected;
public CheckTableView() {
this(FXCollections.observableArrayList());
}
public CheckTableView(ObservableList<T> items) {
setItems(items);
setEditable(true);
getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
skinProperty().addListener(new InvalidationListener() {
@Override
public void invalidated(Observable observable) {
selected = ((CheckTableViewSkin) getSkin()).getSelectedRows();
skinProperty().removeListener(this);
}
});
}
@Override
protected Skin<?> createDefaultSkin() {
return new CheckTableViewSkin<>(this);
}
public ObservableList<T> getSelectedRows() {
return selected;
}
}
CheckTableViewSkin class
public class CheckTableViewSkin<T> extends TableViewSkin<T> {
private final TableColumn<T, Boolean> checkColumn;
private final CheckBox headerCheckBox = new CheckBox();
private final List<BooleanProperty> colSelected = new ArrayList<>();
private final ChangeListener<Number> listener = (obs, ov, nv) -> {
if (nv.intValue() != -1) {
Platform.runLater(() -> {
colSelected.get(nv.intValue()).set(!colSelected.get(nv.intValue()).get());
refreshSelection();
});
}
};
private final ChangeListener<Boolean> headerListener = (obs, ov, nv) -> {
Platform.runLater(() -> {
IntStream.range(0, colSelected.size()).forEach(i -> colSelected.get(i).set(nv));
refreshSelection();
});
};
public CheckTableViewSkin(CheckTableView<T> control) {
super(control);
checkColumn = new TableColumn<>();
headerCheckBox.selectedProperty().addListener(headerListener);
checkColumn.setGraphic(headerCheckBox);
// install listeners in checkboxes
IntStream.range(0, control.getItems().size()).forEach(i -> {
final SimpleBooleanProperty simple = new SimpleBooleanProperty();
simple.addListener((obs, ov, nv) -> refreshSelection());
colSelected.add(simple);
});
checkColumn.setCellFactory(CheckBoxTableCell.forTableColumn(colSelected::get));
checkColumn.setPrefWidth(60);
checkColumn.setEditable(true);
checkColumn.setResizable(false);
getColumns().add(0, checkColumn);
getSelectionModel().selectedIndexProperty().addListener(listener);
}
private void refreshSelection() {
// refresh list of selected rows
getSelectionModel().selectedIndexProperty().removeListener(listener);
getSelectionModel().clearSelection();
AtomicInteger count = new AtomicInteger();
IntStream.range(0, colSelected.size()).forEach(i -> {
if (colSelected.get(i).get()) {
getSelectionModel().select(i);
count.getAndIncrement();
}
});
headerCheckBox.selectedProperty().removeListener(headerListener);
headerCheckBox.setSelected(count.get() == colSelected.size());
headerCheckBox.selectedProperty().addListener(headerListener);
// it may flick, but required to show all selected rows focused
getSkinnable().requestFocus();
getSelectionModel().selectedIndexProperty().addListener(listener);
}
public ObservableList<T> getSelectedRows() {
return getSelectionModel().getSelectedItems();
}
}
现在的示例,Application
class:
@Override
public void start(Stage primaryStage) {
TableColumn<Person, String> firstNameColumn = new TableColumn<>("First Name");
firstNameColumn.setCellValueFactory(p -> p.getValue().firstNameProperty());
TableColumn<Person, String> lastNameColumn = new TableColumn<>("Last Name");
lastNameColumn.setCellValueFactory(p -> p.getValue().lastNameProperty());
CheckTableView<Person> tableView = new CheckTableView(FXCollections.observableArrayList(
new Person("Hans", "Muster"), new Person("Ruth", "Mueller"),
new Person("Heinz", "Kurz"), new Person("Cornelia", "Meier"),
new Person("Anna", "Best"), new Person("Stefan", "Meier")
));
tableView.getColumns().addAll(firstNameColumn, lastNameColumn);
Scene scene = new Scene(tableView, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
对于通常的 Person
class:
public class Person {
private final StringProperty firstName;
private final StringProperty lastName;
public Person(String firstName, String lastName) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
}
//getters & setters
}
此自定义控件也适用于 Scene Builder。