我创建了一个Treeview(javafx),它看起来像:
我现在想要,仅可以选择" Tour" -TreeItems。 但我不知道如何。
我用ChangeListener尝试了它,但我只能用它刷新Tab(TabPane)的内容......刷新工作正常......但是" Delivery" -TreeItems可以是选中:(
码:
public void showTours(List 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);
}
};
我希望你能帮助我。 当你能给我看一个示例代码时,我会非常高兴:)
谢谢
答案 0 :(得分:1)
修改JavaFX中任何控件的选择行为似乎有点痛苦;但“正确”的方法是为树定义自定义选择模型。最简单的方法是包装默认选择模型,并将方法调用委托给它,如果选择索引是针对不应该选择的项目,则否决选择。
在调用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);
}
}
答案 1 :(得分:0)
我修改了James_D发布的选择模型,使其更加通用,以便您可以指定自定义过滤器。实现是:
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)
方法,因为如果过滤器允许,该方法应将基于索引的选择转发到其内部选择模型。我不同意while循环逻辑,因为您将要选择的索引显式传递给此方法,希望它可以选择它。预期的行为应该是,如果过滤器不允许选择,则应忽略选择。我还通过为index == -1
案例添加一个捕获来充实该方法,因为我们需要在发生这种情况时清除选择。
通过检查select(TreeItem)
参数并将其分开处理,null
方法也进行了相当大的更改,因此,如果我们处于单选模式,则需要清除选择,否则调用select(null)
,以便内部选择模型正确处理它。如果确实有一个树项目,我们只需对filter进行检查,然后传递给内部选择模型。
selectIndices(int, int[])
方法的不同之处还在于它应处理indices
数组可以为null或长度为0的情况。如果是这种情况,select(index)
方法应被称为。
我实现的clearAndSelect(int)
方法与其他方法相比有所不同。我在开始时进行边界检查,以查看是否需要立即调用clearSelection()
。否则,我检查是否可以通过过滤器选择索引处的TreeItem
。如果是,我们将转到内部选择模型,否则我们就清除了。我也不同意其他实现中的while循环方法。
James_D实现的selectPrevious()
和selectNext()
方法实际上存在一个错误。如果未选择任何内容,则在调用selectPrevious()
时需要捕捉到最后一个索引。 selectFirst()
则相反,如果未选择任何内容,则需要捕捉到第一个索引。然后,您可以从这些新索引中查找过滤器允许的第一项。您还需要使用焦点索引而不是不是所选索引。如果查看MultipleSelectionModelBase
类以供参考,则会看到此行为。
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
的行为。将其与默认选择模型进行比较,看看您是否对此行为感到满意。