TreeView - 不允许选择某些 TreeItems
TreeView - Certain TreeItems are not allowed to be selected
我创建了一个 Treeview (javafx),它看起来像:
我现在想要只有 "Tour"-TreeItems 是可选的。
但是我不知道怎么办。
我已经尝试使用 ChangeListener,但我只能用它刷新选项卡 (TabPane) 的内容...刷新工作正常...但是可以选择 "Delivery"-TreeItems :(
代码:
public void showTours(List<Tour> pTours) {
treeViewPane.getSelectionModel().selectedItemProperty().addListener(treeItemChangeListener);
TreeItem tTreeRoot = new TreeItem<>("Root", new ImageView(Icons.getIcon24("truck_blue.png")));
tTreeRoot.setExpanded(true);
treeViewPane.setRoot(tTreeRoot);
for (Tour tTour : pTours) {
TreeItem<Object> tTourItem = new TreeItem<>(tTour);
tTreeRoot.getChildren().add(tTourItem);
if (tTour.getDeliveries() != null) {
for (Delivery tDelivery : tTour.getDeliveries()) {
TreeItem<Object> tDeliveryItem = new TreeItem<>(tDelivery);
tTourItem.getChildren().add(tDeliveryItem);
}
}
}
}
private final ChangeListener<TreeItem> treeItemChangeListener = (observable, oldValue, newValue) -> {
if (newValue != null && newValue.getValue() instanceof Tour){
Tour selectedTour = (Tour) newValue.getValue();
reloadTabContent(selectedTour);
}
};
希望你能帮助我。
如果你能给我看示例代码,我会很高兴:)
谢谢
在 JavaFX 的任何控件中修改 selection 行为似乎有点痛苦;但是 "proper" 方法是为树定义自定义 selection 模型。最简单的方法是包装默认的 selection 模型,并将方法调用委托给它,如果 selection 索引用于不应该的项目,则否决 selection不会 selected.
在调用 select
方法时尽可能 select 是个好主意,否则键盘导航会中断。
这是一个实现:
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.MultipleSelectionModel;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class CustomTreeSelectionModelExample extends Application {
@Override
public void start(Stage primaryStage) {
TreeItem<Object> root = new TreeItem<>("Root");
for (int i = 1 ; i <= 5 ; i++) {
TreeItem<Object> item = new TreeItem<>(new Tour("Tour "+i));
for (int j = 1 ; j <= 5; j++) {
Delivery delivery = new Delivery("Delivery "+j);
item.getChildren().add(new TreeItem<>(delivery));
}
root.getChildren().add(item);
}
TreeView<Object> tree = new TreeView<>();
tree.setSelectionModel(new TourSelectionModel(tree.getSelectionModel(), tree));
tree.setRoot(root);
primaryStage.setScene(new Scene(new BorderPane(tree), 400, 400));
primaryStage.show();
}
public static class TourSelectionModel extends MultipleSelectionModel<TreeItem<Object>> {
private final MultipleSelectionModel<TreeItem<Object>> selectionModel ;
private final TreeView<Object> tree ;
public TourSelectionModel(MultipleSelectionModel<TreeItem<Object>> selectionModel, TreeView<Object> tree) {
this.selectionModel = selectionModel ;
this.tree = tree ;
selectionModeProperty().bindBidirectional(selectionModel.selectionModeProperty());
}
@Override
public ObservableList<Integer> getSelectedIndices() {
return selectionModel.getSelectedIndices() ;
}
@Override
public ObservableList<TreeItem<Object>> getSelectedItems() {
return selectionModel.getSelectedItems() ;
}
@Override
public void selectIndices(int index, int... indices) {
List<Integer> indicesToSelect = Stream.concat(Stream.of(index), IntStream.of(indices).boxed())
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.collect(Collectors.toList());
if (indicesToSelect.isEmpty()) {
return ;
}
selectionModel.selectIndices(indicesToSelect.get(0),
indicesToSelect.stream().skip(1).mapToInt(Integer::intValue).toArray());
}
@Override
public void selectAll() {
List<Integer> indicesToSelect = IntStream.range(0, tree.getExpandedItemCount())
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.boxed()
.collect(Collectors.toList());
if (indicesToSelect.isEmpty()) {
return ;
}
selectionModel.selectIndices(0,
indicesToSelect.stream().skip(1).mapToInt(Integer::intValue).toArray());
}
@Override
public void selectFirst() {
IntStream.range(0, tree.getExpandedItemCount())
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.findFirst()
.ifPresent(selectionModel::select);
}
@Override
public void selectLast() {
IntStream.iterate(tree.getExpandedItemCount() - 1, i -> i - 1)
.limit(tree.getExpandedItemCount())
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.findFirst()
.ifPresent(selectionModel::select);
}
@Override
public void clearAndSelect(int index) {
int toSelect = index ;
int direction = selectionModel.getSelectedIndex() < index ? 1 : -1 ;
while (toSelect >= 0 && toSelect < tree.getExpandedItemCount() && ! (tree.getTreeItem(toSelect).getValue() instanceof Tour)) {
toSelect = toSelect + direction ;
}
if (toSelect >= 0 && toSelect < tree.getExpandedItemCount()) {
selectionModel.clearAndSelect(toSelect);
}
}
@Override
public void select(int index) {
int toSelect = index ;
int direction = selectionModel.getSelectedIndex() < index ? 1 : -1 ;
while (toSelect >= 0 && toSelect < tree.getExpandedItemCount() && ! (tree.getTreeItem(toSelect).getValue() instanceof Tour)) {
toSelect = toSelect + direction ;
}
if (toSelect >= 0 && toSelect < tree.getExpandedItemCount()) {
selectionModel.select(toSelect);
}
}
@Override
public void select(TreeItem<Object> obj) {
if (obj.getValue() instanceof Tour) {
selectionModel.select(obj);
}
}
@Override
public void clearSelection(int index) {
selectionModel.clearSelection(index);
}
@Override
public void clearSelection() {
selectionModel.clearSelection();
}
@Override
public boolean isSelected(int index) {
return selectionModel.isSelected(index);
}
@Override
public boolean isEmpty() {
return selectionModel.isEmpty();
}
@Override
public void selectPrevious() {
int current = selectionModel.getSelectedIndex() ;
if (current > 0) {
IntStream.iterate(current - 1, i -> i - 1).limit(current)
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.findFirst()
.ifPresent(selectionModel::select);
}
}
@Override
public void selectNext() {
int current = selectionModel.getSelectedIndex() ;
if (current < tree.getExpandedItemCount() - 1) {
IntStream.range(current + 1, tree.getExpandedItemCount())
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.findFirst()
.ifPresent(selectionModel::select);
}
}
}
public static class Tour {
private final String name ;
public Tour(String name) {
this.name = name ;
}
public String getName() {
return name ;
}
@Override
public String toString() {
return getName();
}
}
public static class Delivery {
private final String name;
public Delivery(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return getName();
}
}
public static void main(String[] args) {
launch(args);
}
}
我修改了 James_D 发布的 selection-model,使其更通用一些,以便您可以指定自定义过滤器。实现是:
public class FilteredTreeViewSelectionModel<S> extends MultipleSelectionModel<TreeItem<S>> {
private final TreeView<S> treeView;
private final MultipleSelectionModel<TreeItem<S>> selectionModel;
private final TreeItemSelectionFilter<S> filter;
public FilteredTreeViewSelectionModel(
TreeView<S> treeView,
MultipleSelectionModel<TreeItem<S>> selectionModel,
TreeItemSelectionFilter<S> filter) {
this.treeView = treeView;
this.selectionModel = selectionModel;
this.filter = filter;
selectionModeProperty().bindBidirectional(selectionModel.selectionModeProperty());
}
@Override
public ObservableList<Integer> getSelectedIndices() {
return this.selectionModel.getSelectedIndices();
}
@Override
public ObservableList<TreeItem<S>> getSelectedItems() {
return this.selectionModel.getSelectedItems();
}
private int getRowCount() {
return this.treeView.getExpandedItemCount();
}
@Override
public boolean isSelected(int index) {
return this.selectionModel.isSelected(index);
}
@Override
public boolean isEmpty() {
return this.selectionModel.isEmpty();
}
@Override
public void select(int index) {
// If the row is -1, we need to clear the selection.
if (index == -1) {
this.selectionModel.clearSelection();
} else if (index >= 0 && index < getRowCount()) {
// If the tree-item at the specified row-index is selectable, we
// forward select call to the internal selection-model.
TreeItem<S> treeItem = this.treeView.getTreeItem(index);
if (this.filter.isSelectable(treeItem)) {
this.selectionModel.select(index);
}
}
}
@Override
public void select(TreeItem<S> treeItem) {
if (treeItem == null) {
// If the provided tree-item is null, and we are in single-selection
// mode we need to clear the selection.
if (getSelectionMode() == SelectionMode.SINGLE) {
this.selectionModel.clearSelection();
}
// Else, we just forward to the internal selection-model so that
// the selected-index can be set to -1, and the selected-item
// can be set to null.
else {
this.selectionModel.select(null);
}
} else if (this.filter.isSelectable(treeItem)) {
this.selectionModel.select(treeItem);
}
}
@Override
public void selectIndices(int index, int... indices) {
// If we have no trailing rows, we forward to normal row-selection.
if (indices == null || indices.length == 0) {
select(index);
return;
}
// Filter indices so that we only end up with those indices whose
// corresponding tree-items are selectable.
int[] filteredIndices = IntStream.concat(IntStream.of(index), Arrays.stream(indices)).filter(indexToCheck -> {
TreeItem<S> treeItem = treeView.getTreeItem(indexToCheck);
return (treeItem != null) && filter.isSelectable(treeItem);
}).toArray();
// If we have indices left, we proceed to forward to internal selection-model.
if (filteredIndices.length > 0) {
int newIndex = filteredIndices[0];
int[] newIndices = Arrays.copyOfRange(filteredIndices, 1, filteredIndices.length);
this.selectionModel.selectIndices(newIndex, newIndices);
}
}
@Override
public void clearAndSelect(int index) {
// If the index is out-of-bounds we just clear and return.
if (index < 0 || index >= getRowCount()) {
clearSelection();
return;
}
// Get tree-item at index.
TreeItem<S> treeItem = this.treeView.getTreeItem(index);
// If the tree-item at the specified row-index is selectable, we forward
// clear-and-select call to the internal selection-model.
if (this.filter.isSelectable(treeItem)) {
this.selectionModel.clearAndSelect(index);
}
// Else, we just do a normal clear-selection call.
else {
this.selectionModel.clearSelection();
}
}
@Override
public void selectAll() {
int rowCount = getRowCount();
// If we are in single-selection mode, we exit prematurely as
// we cannot select all rows.
if (getSelectionMode() == SelectionMode.SINGLE) {
return;
}
// If we only have a single index to select, we forward to the
// single-index select-method.
if (rowCount == 1) {
select(0);
}
// Else, if we have more than one index available, we construct an array
// of all the indices and forward to the selectIndices-method.
else if (rowCount > 1) {
int index = 0;
int[] indices = IntStream.range(1, rowCount).toArray();
selectIndices(index, indices);
}
}
@Override
public void clearSelection(int index) {
this.selectionModel.clearSelection(index);
}
@Override
public void clearSelection() {
this.selectionModel.clearSelection();
}
@Override
public void selectFirst() {
Optional<TreeItem<S>> firstItem = IntStream.range(0, getRowCount()).
mapToObj(this.treeView::getTreeItem).
filter(this.filter::isSelectable).
findFirst();
firstItem.ifPresent(this.selectionModel::select);
}
@Override
public void selectLast() {
int rowCount = getRowCount();
Optional<TreeItem<S>> lastItem = IntStream.iterate(rowCount - 1, i -> i - 1).
limit(rowCount).
mapToObj(this.treeView::getTreeItem).
filter(this.filter::isSelectable).
findFirst();
lastItem.ifPresent(this.selectionModel::select);
}
private int getFocusedIndex() {
FocusModel<TreeItem<S>> focusModel = this.treeView.getFocusModel();
return (focusModel == null) ? -1 : focusModel.getFocusedIndex();
}
@Override
public void selectPrevious() {
int focusIndex = getFocusedIndex();
// If we have nothing selected, wrap around to the last index.
int startIndex = (focusIndex == -1) ? getRowCount() : focusIndex;
if (startIndex > 0) {
Optional<TreeItem<S>> previousItem = IntStream.iterate(startIndex - 1, i -> i - 1).
limit(startIndex).
mapToObj(this.treeView::getTreeItem).
filter(this.filter::isSelectable).
findFirst();
previousItem.ifPresent(this.selectionModel::select);
}
}
@Override
public void selectNext() {
// If we have nothing selected, starting at -1 will work out correctly
// because we'll search from 0 onwards.
int startIndex = getFocusedIndex();
if (startIndex < getRowCount() - 1) {
Optional<TreeItem<S>> nextItem = IntStream.range(startIndex + 1, getRowCount()).
mapToObj(this.treeView::getTreeItem).
filter(this.filter::isSelectable).
findFirst();
nextItem.ifPresent(this.selectionModel::select);
}
}
}
我更改了 selectIndex(int)
方法,因为如果过滤器允许,此方法应该将基于索引的 selection 转发到其内部 selection 模型。我不同意 while 循环逻辑,因为您明确将要 selected 的索引传递给此方法,希望它可以 select 它。预期的行为应该是它应该忽略 select 如果过滤器不允许它。我还通过为 index == -1
案例添加一个 catch 来充实该方法,因为我们需要在发生这种情况时清除 selection。
select(TreeItem)
方法也发生了很大的变化,检查 null
参数并单独处理它,因此如果我们处于单 selection 模式,我们需要清除 selection,否则我们调用 select(null)
以便内部 selection-model 正确处理它。如果我们确实有一个树项,我们只需检查过滤器并传递给内部 selection-model。
selectIndices(int, int[])
方法的不同之处还在于它应该处理 indices
数组可能为空或长度为 0 的情况。如果是这种情况,select(index)
应该调用方法。
我实现 clearAndSelect(int)
方法的方式与其他方法略有不同。我在开始时进行边界检查,看看我们是否需要立即调用 clearSelection()
。否则,我会通过过滤器检查索引处的 TreeItem
是否 select 可用。如果是我们转发到内部 selection-model,否则我们就清除。我也不同意其他实现中使用的 while 循环方法。
James_D 实现的 selectPrevious()
和 selectNext()
方法实际上存在一个错误。如果什么都没有 selected 你需要在调用 selectPrevious()
时捕捉到最后一个索引。 selectFirst()
的情况正好相反,如果没有 selected,则需要捕捉到第一个索引。然后,您可以根据这些新索引找到过滤器允许的第一个项目。您还需要使用 focus-index 而 not selected-index。如果您查看 MultipleSelectionModelBase
class 以供参考,您可以看到此行为。
TreeItemSelectionFilter
指定为:
public interface TreeItemSelectionFilter<S> {
public boolean isSelectable(TreeItem<S> treeItem);
}
对于您的特定情况,您可以将它们连接在一起:
....
MultipleSelectionModel<TreeItem<Object>> selectionModel = tree.getSelectionModel();
TreeItemSelectionFilter<Object> filter = treeItem -> treeItem.getValue() instanceof Tour;
FilteredTreeViewSelectionModel<Object> filteredSelectionModel = new FilteredTreeViewSelectionModel<>(tree, selectionModel, filter);
tree.setSelectionModel(filteredSelectionModel);
....
我已经上传了示例应用程序 here 的源代码,以便您可以轻松地自己测试 FilteredTreeViewSelectionModel
的行为。将其与默认的 selection-model 进行比较,看看您是否对行为感到满意。
我创建了一个 Treeview (javafx),它看起来像:
我现在想要只有 "Tour"-TreeItems 是可选的。 但是我不知道怎么办。
我已经尝试使用 ChangeListener,但我只能用它刷新选项卡 (TabPane) 的内容...刷新工作正常...但是可以选择 "Delivery"-TreeItems :(
代码:
public void showTours(List<Tour> pTours) {
treeViewPane.getSelectionModel().selectedItemProperty().addListener(treeItemChangeListener);
TreeItem tTreeRoot = new TreeItem<>("Root", new ImageView(Icons.getIcon24("truck_blue.png")));
tTreeRoot.setExpanded(true);
treeViewPane.setRoot(tTreeRoot);
for (Tour tTour : pTours) {
TreeItem<Object> tTourItem = new TreeItem<>(tTour);
tTreeRoot.getChildren().add(tTourItem);
if (tTour.getDeliveries() != null) {
for (Delivery tDelivery : tTour.getDeliveries()) {
TreeItem<Object> tDeliveryItem = new TreeItem<>(tDelivery);
tTourItem.getChildren().add(tDeliveryItem);
}
}
}
}
private final ChangeListener<TreeItem> treeItemChangeListener = (observable, oldValue, newValue) -> {
if (newValue != null && newValue.getValue() instanceof Tour){
Tour selectedTour = (Tour) newValue.getValue();
reloadTabContent(selectedTour);
}
};
希望你能帮助我。 如果你能给我看示例代码,我会很高兴:)
谢谢
在 JavaFX 的任何控件中修改 selection 行为似乎有点痛苦;但是 "proper" 方法是为树定义自定义 selection 模型。最简单的方法是包装默认的 selection 模型,并将方法调用委托给它,如果 selection 索引用于不应该的项目,则否决 selection不会 selected.
在调用 select
方法时尽可能 select 是个好主意,否则键盘导航会中断。
这是一个实现:
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.MultipleSelectionModel;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class CustomTreeSelectionModelExample extends Application {
@Override
public void start(Stage primaryStage) {
TreeItem<Object> root = new TreeItem<>("Root");
for (int i = 1 ; i <= 5 ; i++) {
TreeItem<Object> item = new TreeItem<>(new Tour("Tour "+i));
for (int j = 1 ; j <= 5; j++) {
Delivery delivery = new Delivery("Delivery "+j);
item.getChildren().add(new TreeItem<>(delivery));
}
root.getChildren().add(item);
}
TreeView<Object> tree = new TreeView<>();
tree.setSelectionModel(new TourSelectionModel(tree.getSelectionModel(), tree));
tree.setRoot(root);
primaryStage.setScene(new Scene(new BorderPane(tree), 400, 400));
primaryStage.show();
}
public static class TourSelectionModel extends MultipleSelectionModel<TreeItem<Object>> {
private final MultipleSelectionModel<TreeItem<Object>> selectionModel ;
private final TreeView<Object> tree ;
public TourSelectionModel(MultipleSelectionModel<TreeItem<Object>> selectionModel, TreeView<Object> tree) {
this.selectionModel = selectionModel ;
this.tree = tree ;
selectionModeProperty().bindBidirectional(selectionModel.selectionModeProperty());
}
@Override
public ObservableList<Integer> getSelectedIndices() {
return selectionModel.getSelectedIndices() ;
}
@Override
public ObservableList<TreeItem<Object>> getSelectedItems() {
return selectionModel.getSelectedItems() ;
}
@Override
public void selectIndices(int index, int... indices) {
List<Integer> indicesToSelect = Stream.concat(Stream.of(index), IntStream.of(indices).boxed())
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.collect(Collectors.toList());
if (indicesToSelect.isEmpty()) {
return ;
}
selectionModel.selectIndices(indicesToSelect.get(0),
indicesToSelect.stream().skip(1).mapToInt(Integer::intValue).toArray());
}
@Override
public void selectAll() {
List<Integer> indicesToSelect = IntStream.range(0, tree.getExpandedItemCount())
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.boxed()
.collect(Collectors.toList());
if (indicesToSelect.isEmpty()) {
return ;
}
selectionModel.selectIndices(0,
indicesToSelect.stream().skip(1).mapToInt(Integer::intValue).toArray());
}
@Override
public void selectFirst() {
IntStream.range(0, tree.getExpandedItemCount())
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.findFirst()
.ifPresent(selectionModel::select);
}
@Override
public void selectLast() {
IntStream.iterate(tree.getExpandedItemCount() - 1, i -> i - 1)
.limit(tree.getExpandedItemCount())
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.findFirst()
.ifPresent(selectionModel::select);
}
@Override
public void clearAndSelect(int index) {
int toSelect = index ;
int direction = selectionModel.getSelectedIndex() < index ? 1 : -1 ;
while (toSelect >= 0 && toSelect < tree.getExpandedItemCount() && ! (tree.getTreeItem(toSelect).getValue() instanceof Tour)) {
toSelect = toSelect + direction ;
}
if (toSelect >= 0 && toSelect < tree.getExpandedItemCount()) {
selectionModel.clearAndSelect(toSelect);
}
}
@Override
public void select(int index) {
int toSelect = index ;
int direction = selectionModel.getSelectedIndex() < index ? 1 : -1 ;
while (toSelect >= 0 && toSelect < tree.getExpandedItemCount() && ! (tree.getTreeItem(toSelect).getValue() instanceof Tour)) {
toSelect = toSelect + direction ;
}
if (toSelect >= 0 && toSelect < tree.getExpandedItemCount()) {
selectionModel.select(toSelect);
}
}
@Override
public void select(TreeItem<Object> obj) {
if (obj.getValue() instanceof Tour) {
selectionModel.select(obj);
}
}
@Override
public void clearSelection(int index) {
selectionModel.clearSelection(index);
}
@Override
public void clearSelection() {
selectionModel.clearSelection();
}
@Override
public boolean isSelected(int index) {
return selectionModel.isSelected(index);
}
@Override
public boolean isEmpty() {
return selectionModel.isEmpty();
}
@Override
public void selectPrevious() {
int current = selectionModel.getSelectedIndex() ;
if (current > 0) {
IntStream.iterate(current - 1, i -> i - 1).limit(current)
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.findFirst()
.ifPresent(selectionModel::select);
}
}
@Override
public void selectNext() {
int current = selectionModel.getSelectedIndex() ;
if (current < tree.getExpandedItemCount() - 1) {
IntStream.range(current + 1, tree.getExpandedItemCount())
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.findFirst()
.ifPresent(selectionModel::select);
}
}
}
public static class Tour {
private final String name ;
public Tour(String name) {
this.name = name ;
}
public String getName() {
return name ;
}
@Override
public String toString() {
return getName();
}
}
public static class Delivery {
private final String name;
public Delivery(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return getName();
}
}
public static void main(String[] args) {
launch(args);
}
}
我修改了 James_D 发布的 selection-model,使其更通用一些,以便您可以指定自定义过滤器。实现是:
public class FilteredTreeViewSelectionModel<S> extends MultipleSelectionModel<TreeItem<S>> {
private final TreeView<S> treeView;
private final MultipleSelectionModel<TreeItem<S>> selectionModel;
private final TreeItemSelectionFilter<S> filter;
public FilteredTreeViewSelectionModel(
TreeView<S> treeView,
MultipleSelectionModel<TreeItem<S>> selectionModel,
TreeItemSelectionFilter<S> filter) {
this.treeView = treeView;
this.selectionModel = selectionModel;
this.filter = filter;
selectionModeProperty().bindBidirectional(selectionModel.selectionModeProperty());
}
@Override
public ObservableList<Integer> getSelectedIndices() {
return this.selectionModel.getSelectedIndices();
}
@Override
public ObservableList<TreeItem<S>> getSelectedItems() {
return this.selectionModel.getSelectedItems();
}
private int getRowCount() {
return this.treeView.getExpandedItemCount();
}
@Override
public boolean isSelected(int index) {
return this.selectionModel.isSelected(index);
}
@Override
public boolean isEmpty() {
return this.selectionModel.isEmpty();
}
@Override
public void select(int index) {
// If the row is -1, we need to clear the selection.
if (index == -1) {
this.selectionModel.clearSelection();
} else if (index >= 0 && index < getRowCount()) {
// If the tree-item at the specified row-index is selectable, we
// forward select call to the internal selection-model.
TreeItem<S> treeItem = this.treeView.getTreeItem(index);
if (this.filter.isSelectable(treeItem)) {
this.selectionModel.select(index);
}
}
}
@Override
public void select(TreeItem<S> treeItem) {
if (treeItem == null) {
// If the provided tree-item is null, and we are in single-selection
// mode we need to clear the selection.
if (getSelectionMode() == SelectionMode.SINGLE) {
this.selectionModel.clearSelection();
}
// Else, we just forward to the internal selection-model so that
// the selected-index can be set to -1, and the selected-item
// can be set to null.
else {
this.selectionModel.select(null);
}
} else if (this.filter.isSelectable(treeItem)) {
this.selectionModel.select(treeItem);
}
}
@Override
public void selectIndices(int index, int... indices) {
// If we have no trailing rows, we forward to normal row-selection.
if (indices == null || indices.length == 0) {
select(index);
return;
}
// Filter indices so that we only end up with those indices whose
// corresponding tree-items are selectable.
int[] filteredIndices = IntStream.concat(IntStream.of(index), Arrays.stream(indices)).filter(indexToCheck -> {
TreeItem<S> treeItem = treeView.getTreeItem(indexToCheck);
return (treeItem != null) && filter.isSelectable(treeItem);
}).toArray();
// If we have indices left, we proceed to forward to internal selection-model.
if (filteredIndices.length > 0) {
int newIndex = filteredIndices[0];
int[] newIndices = Arrays.copyOfRange(filteredIndices, 1, filteredIndices.length);
this.selectionModel.selectIndices(newIndex, newIndices);
}
}
@Override
public void clearAndSelect(int index) {
// If the index is out-of-bounds we just clear and return.
if (index < 0 || index >= getRowCount()) {
clearSelection();
return;
}
// Get tree-item at index.
TreeItem<S> treeItem = this.treeView.getTreeItem(index);
// If the tree-item at the specified row-index is selectable, we forward
// clear-and-select call to the internal selection-model.
if (this.filter.isSelectable(treeItem)) {
this.selectionModel.clearAndSelect(index);
}
// Else, we just do a normal clear-selection call.
else {
this.selectionModel.clearSelection();
}
}
@Override
public void selectAll() {
int rowCount = getRowCount();
// If we are in single-selection mode, we exit prematurely as
// we cannot select all rows.
if (getSelectionMode() == SelectionMode.SINGLE) {
return;
}
// If we only have a single index to select, we forward to the
// single-index select-method.
if (rowCount == 1) {
select(0);
}
// Else, if we have more than one index available, we construct an array
// of all the indices and forward to the selectIndices-method.
else if (rowCount > 1) {
int index = 0;
int[] indices = IntStream.range(1, rowCount).toArray();
selectIndices(index, indices);
}
}
@Override
public void clearSelection(int index) {
this.selectionModel.clearSelection(index);
}
@Override
public void clearSelection() {
this.selectionModel.clearSelection();
}
@Override
public void selectFirst() {
Optional<TreeItem<S>> firstItem = IntStream.range(0, getRowCount()).
mapToObj(this.treeView::getTreeItem).
filter(this.filter::isSelectable).
findFirst();
firstItem.ifPresent(this.selectionModel::select);
}
@Override
public void selectLast() {
int rowCount = getRowCount();
Optional<TreeItem<S>> lastItem = IntStream.iterate(rowCount - 1, i -> i - 1).
limit(rowCount).
mapToObj(this.treeView::getTreeItem).
filter(this.filter::isSelectable).
findFirst();
lastItem.ifPresent(this.selectionModel::select);
}
private int getFocusedIndex() {
FocusModel<TreeItem<S>> focusModel = this.treeView.getFocusModel();
return (focusModel == null) ? -1 : focusModel.getFocusedIndex();
}
@Override
public void selectPrevious() {
int focusIndex = getFocusedIndex();
// If we have nothing selected, wrap around to the last index.
int startIndex = (focusIndex == -1) ? getRowCount() : focusIndex;
if (startIndex > 0) {
Optional<TreeItem<S>> previousItem = IntStream.iterate(startIndex - 1, i -> i - 1).
limit(startIndex).
mapToObj(this.treeView::getTreeItem).
filter(this.filter::isSelectable).
findFirst();
previousItem.ifPresent(this.selectionModel::select);
}
}
@Override
public void selectNext() {
// If we have nothing selected, starting at -1 will work out correctly
// because we'll search from 0 onwards.
int startIndex = getFocusedIndex();
if (startIndex < getRowCount() - 1) {
Optional<TreeItem<S>> nextItem = IntStream.range(startIndex + 1, getRowCount()).
mapToObj(this.treeView::getTreeItem).
filter(this.filter::isSelectable).
findFirst();
nextItem.ifPresent(this.selectionModel::select);
}
}
}
我更改了 selectIndex(int)
方法,因为如果过滤器允许,此方法应该将基于索引的 selection 转发到其内部 selection 模型。我不同意 while 循环逻辑,因为您明确将要 selected 的索引传递给此方法,希望它可以 select 它。预期的行为应该是它应该忽略 select 如果过滤器不允许它。我还通过为 index == -1
案例添加一个 catch 来充实该方法,因为我们需要在发生这种情况时清除 selection。
select(TreeItem)
方法也发生了很大的变化,检查 null
参数并单独处理它,因此如果我们处于单 selection 模式,我们需要清除 selection,否则我们调用 select(null)
以便内部 selection-model 正确处理它。如果我们确实有一个树项,我们只需检查过滤器并传递给内部 selection-model。
selectIndices(int, int[])
方法的不同之处还在于它应该处理 indices
数组可能为空或长度为 0 的情况。如果是这种情况,select(index)
应该调用方法。
我实现 clearAndSelect(int)
方法的方式与其他方法略有不同。我在开始时进行边界检查,看看我们是否需要立即调用 clearSelection()
。否则,我会通过过滤器检查索引处的 TreeItem
是否 select 可用。如果是我们转发到内部 selection-model,否则我们就清除。我也不同意其他实现中使用的 while 循环方法。
James_D 实现的 selectPrevious()
和 selectNext()
方法实际上存在一个错误。如果什么都没有 selected 你需要在调用 selectPrevious()
时捕捉到最后一个索引。 selectFirst()
的情况正好相反,如果没有 selected,则需要捕捉到第一个索引。然后,您可以根据这些新索引找到过滤器允许的第一个项目。您还需要使用 focus-index 而 not selected-index。如果您查看 MultipleSelectionModelBase
class 以供参考,您可以看到此行为。
TreeItemSelectionFilter
指定为:
public interface TreeItemSelectionFilter<S> {
public boolean isSelectable(TreeItem<S> treeItem);
}
对于您的特定情况,您可以将它们连接在一起:
....
MultipleSelectionModel<TreeItem<Object>> selectionModel = tree.getSelectionModel();
TreeItemSelectionFilter<Object> filter = treeItem -> treeItem.getValue() instanceof Tour;
FilteredTreeViewSelectionModel<Object> filteredSelectionModel = new FilteredTreeViewSelectionModel<>(tree, selectionModel, filter);
tree.setSelectionModel(filteredSelectionModel);
....
我已经上传了示例应用程序 here 的源代码,以便您可以轻松地自己测试 FilteredTreeViewSelectionModel
的行为。将其与默认的 selection-model 进行比较,看看您是否对行为感到满意。