JavaFX TreeTableView-防止选择某些TreeItems

问题描述 投票:0回答:1

我在这里看了几个问题,但是我似乎找不到任何与禁用TreeTableViews的行选择有关的东西,特别是在JavaFX中。

我遇到的最接近的相关问题都与TreeViews有关:

  1. How to make certain JavaFX TreeView nodes non-selectable?

  2. TreeView - Certain TreeItems are not allowed to be selected

问题2中给出的答案似乎是最有希望的,其中使用了从MultipleSelectionModel扩展的自定义选择模型。但是,TreeTableViews的问题在于它们使用了自定义TreeTableViewSelectionModel,该自定义TreeTableViewSelectionModel本身是从TableSelectionModel扩展而来的。因此,实现一个从TreeTableViewSelectionModel扩展并转发到包装的TreeTableViewSelectionModel的新类,同时过滤掉您不想被选择的行,并不是那么简单。

还有其他方法吗?还是我需要TreeTableViewSelectionModel的自定义子类来完成工作?

javafx treeview javafx-8 treetableview
1个回答
0
投票

我通过查看TreeTableViewArrayListSelectionModel的内部知识拼凑了一个类。由于TreeTableViewSelectionModel类本身具有状态,因此在实现包装器类时,需要注意何时转发到内部selectionModel对象以及何时退回到超类方法之一。

import java.util.Arrays;
import java.util.stream.IntStream;

import javafx.collections.ObservableList;
import javafx.scene.control.*;
import javafx.scene.control.TreeTableView.TreeTableViewSelectionModel;

public class FilteredTreeTableViewSelectionModel<S> extends TreeTableViewSelectionModel<S> {

    private final TreeTableViewSelectionModel<S> selectionModel;
    private final TreeItemSelectionChecker<S> checker;

    public FilteredTreeTableViewSelectionModel(
            TreeTableView<S> treeTableView,
            TreeTableViewSelectionModel<S> selectionModel,
            TreeItemSelectionChecker<S> checker) {
        super(treeTableView);
        this.selectionModel = selectionModel;
        this.checker = checker;
        cellSelectionEnabledProperty().bindBidirectional(selectionModel.cellSelectionEnabledProperty());
        selectionModeProperty().bindBidirectional(selectionModel.selectionModeProperty());
    }

    @Override
    public ObservableList<Integer> getSelectedIndices() {
        return this.selectionModel.getSelectedIndices();
    }

    @Override
    public ObservableList<TreeItem<S>> getSelectedItems() {
        return this.selectionModel.getSelectedItems();
    }

    @Override
    public ObservableList<TreeTablePosition<S, ?>> getSelectedCells() {
        return this.selectionModel.getSelectedCells();
    }

    @Override
    public boolean isSelected(int index) {
        return this.selectionModel.isSelected(index);
    }

    @Override
    public boolean isSelected(int row, TableColumnBase<TreeItem<S>, ?> column) {
        return this.selectionModel.isSelected(row, column);
    }

    @Override
    public boolean isEmpty() {
        return this.selectionModel.isEmpty();
    }

    @Override
    public TreeItem<S> getModelItem(int index) {
        return this.selectionModel.getModelItem(index);
    }

    @Override
    public void focus(int row) {
        this.selectionModel.focus(row);
    }

    @Override
    public int getFocusedIndex() {
        return this.selectionModel.getFocusedIndex();
    }

    private TreeTablePosition<S,?> getFocusedCell() {
        TreeTableView<S> treeTableView = getTreeTableView();
        if (treeTableView.getFocusModel() == null) {
            return new TreeTablePosition<>(treeTableView, -1, null);
        }
        return treeTableView.getFocusModel().getFocusedCell();
    }

    private TreeTableColumn<S,?> getTableColumn(int pos) {
        return getTreeTableView().getVisibleLeafColumn(pos);
    }

    // Gets a table column to the left or right of the current one, given an offset
    private TreeTableColumn<S,?> getTableColumn(TreeTableColumn<S,?> column, int offset) {
        int columnIndex = getTreeTableView().getVisibleLeafIndex(column);
        int newColumnIndex = columnIndex + offset;
        return getTreeTableView().getVisibleLeafColumn(newColumnIndex);
    }

    private int getRowCount() {
        TreeTableView<S> treeTableView = getTreeTableView();
        return treeTableView.getExpandedItemCount();
    }

    @Override
    public void select(int row) {
        TreeTableView<S> treeTableView = getTreeTableView();
        TreeItem<S> treeItem = treeTableView.getTreeItem(row);
        if (this.checker.isSelectable(treeItem)) {
            this.selectionModel.select(row);
        }
    }

    @Override
    public void select(TreeItem<S> treeItem) {
        if (this.checker.isSelectable(treeItem)) {
            this.selectionModel.select(treeItem);
        }
    }

    @Override
    public void selectIndices(int row, int ... rows) {
        // If we have no trailing rows, we forward to normal row-selection.
        if (rows == null || rows.length == 0) {
            select(row);
            return;
        }

        // Filter rows so that we only end up with those rows whose corresponding
        // tree-items are selectable.
        TreeTableView<S> treeTableView = getTreeTableView();
        int[] filteredRows = IntStream.concat(IntStream.of(row), Arrays.stream(rows)).filter(rowToCheck -> {
            TreeItem<S> treeItem = treeTableView.getTreeItem(rowToCheck);
            return checker.isSelectable(treeItem);
        }).toArray();

        // If we have rows left, we proceed to forward to delegate selection-model.
        if (filteredRows.length > 0) {
            int newRow = filteredRows[0];
            int[] newRows = Arrays.copyOfRange(filteredRows, 1, filteredRows.length);
            this.selectionModel.selectIndices(newRow, newRows);
        }
    }

    @Override
    public void selectRange(int start, int end) {
        super.selectRange(start, end);
    }

    @Override
    public void selectRange(int minRow, TableColumnBase<TreeItem<S>, ?> minColumn, int maxRow, TableColumnBase<TreeItem<S>, ?> maxColumn) {
        super.selectRange(minRow, minColumn, maxRow, maxColumn);
    }

    @Override
    public void select(int row, TableColumnBase<TreeItem<S>, ?> column) {
        TreeTableView<S> treeTableView = getTreeTableView();
        TreeItem<S> treeItem = treeTableView.getTreeItem(row);
        if (this.checker.isSelectable(treeItem)) {
            this.selectionModel.select(row, column);
        }
    }

    @Override
    public void clearAndSelect(int row) {
        TreeTableView<S> treeTableView = getTreeTableView();
        TreeItem<S> treeItem = treeTableView.getTreeItem(row);

        // If the specified row is selectable, we forward clear-and-select
        // call to the delegate selection-model.
        if (this.checker.isSelectable(treeItem)) {
            this.selectionModel.clearAndSelect(row);
        }
        // Else, we just do a normal clear-selection call.
        else {
            this.selectionModel.clearSelection();
        }
    }

    @Override
    public void clearAndSelect(int row, TableColumnBase<TreeItem<S>, ?> column) {
        TreeTableView<S> treeTableView = getTreeTableView();
        TreeItem<S> treeItem = treeTableView.getTreeItem(row);

        // If the specified row is selectable, we forward clear-and-select
        // call to the delegate selection-model.
        if (this.checker.isSelectable(treeItem)) {
            this.selectionModel.clearAndSelect(row, column);
        }
        // Else, we just do a normal clear-selection call.
        else {
            this.selectionModel.clearSelection();
        }
    }

    @Override
    public void clearSelection(int index) {
        this.selectionModel.clearSelection(index);
    }

    @Override
    public void clearSelection(int row, TableColumnBase<TreeItem<S>, ?> column) {
        this.selectionModel.clearSelection(row, column);
    }

    @Override
    public void clearSelection() {
        this.selectionModel.clearSelection();
    }

    @Override
    public void selectAll() {
        int rowCount = getRowCount();

        // If we only have a single row to select, we forward to the
        // row-index select-method.
        if (rowCount == 1) {
            select(0);
        }
        // Else, if we have more than one row available, we construct an array
        // of all the indices and forward to the selectIndices-method.
        else if (rowCount > 1) {
            int row = 0;
            int[] rows = IntStream.range(1, rowCount).toArray();
            selectIndices(row, rows);
        }
    }

    @Override
    public void selectFirst() {
        TreeTablePosition<S,?> focusedCell = getFocusedCell();

        if (getSelectionMode() == SelectionMode.SINGLE) {
            // Forward to delegate to ensure selection is cleared quietly.
            this.selectionModel.selectFirst();
        }

        if (getRowCount() > 0) {
            if (isCellSelectionEnabled()) {
                select(0, focusedCell.getTableColumn());
            } else {
                select(0);
            }
        }
    }

    @Override
    public void selectLast() {
        TreeTablePosition<S,?> focusedCell = getFocusedCell();

        if (getSelectionMode() == SelectionMode.SINGLE) {
            // Forward to delegate to ensure selection is cleared quietly.
            this.selectionModel.selectLast();
        }

        int numItems = getRowCount();
        if (numItems > 0 && getSelectedIndex() < numItems - 1) {
            if (isCellSelectionEnabled()) {
                select(numItems - 1, focusedCell.getTableColumn());
            } else {
                select(numItems - 1);
            }
        }
    }

    @Override
    public void selectPrevious() {
        if (isCellSelectionEnabled()) {
            // in cell selection mode, we have to wrap around, going from
            // right-to-left, and then wrapping to the end of the previous line
            TreeTablePosition<S,?> pos = getFocusedCell();
            if (pos.getColumn() - 1 >= 0) {
                // go to previous row
                select(pos.getRow(), getTableColumn(pos.getTableColumn(), -1));
            } else if (pos.getRow() < getRowCount() - 1) {
                // wrap to end of previous row
                select(pos.getRow() - 1, getTableColumn(getTreeTableView().getVisibleLeafColumns().size() - 1));
            }
        } else {
            int focusIndex = getFocusedIndex();
            if (focusIndex == -1) {
                select(getRowCount() - 1);
            } else if (focusIndex > 0) {
                select(focusIndex - 1);
            }
        }
    }

    @Override
    public void selectNext() {
        if (isCellSelectionEnabled()) {
            // in cell selection mode, we have to wrap around, going from
            // left-to-right, and then wrapping to the start of the next line
            TreeTablePosition<S,?> pos = getFocusedCell();
            if (pos.getColumn() + 1 < getTreeTableView().getVisibleLeafColumns().size()) {
                // go to next column
                select(pos.getRow(), getTableColumn(pos.getTableColumn(), 1));
            } else if (pos.getRow() < getRowCount() - 1) {
                // wrap to start of next row
                select(pos.getRow() + 1, getTableColumn(0));
            }
        } else {
            int focusIndex = getFocusedIndex();
            if (focusIndex == -1) {
                select(0);
            } else if (focusIndex < getRowCount() -1) {
                select(focusIndex + 1);
            }
        }
    }

    @Override
    public void selectLeftCell() {
        if (!isCellSelectionEnabled()) {
            return;
        }

        TreeTablePosition<S,?> pos = getFocusedCell();
        if (pos.getColumn() - 1 >= 0) {
            select(pos.getRow(), getTableColumn(pos.getTableColumn(), -1));
        }
    }

    @Override
    public void selectRightCell() {
        if (!isCellSelectionEnabled()) {
            return;
        }

        TreeTablePosition<S,?> pos = getFocusedCell();
        if (pos.getColumn() + 1 < getTreeTableView().getVisibleLeafColumns().size()) {
            select(pos.getRow(), getTableColumn(pos.getTableColumn(), 1));
        }
    }

    @Override
    public void selectAboveCell() {
        TreeTablePosition<S,?> pos = getFocusedCell();
        if (pos.getRow() == -1) {
            select(getRowCount() - 1);
        } else if (pos.getRow() > 0) {
            select(pos.getRow() - 1, pos.getTableColumn());
        }
    }

    @Override
    public void selectBelowCell() {
        TreeTablePosition<S,?> pos = getFocusedCell();

        if (pos.getRow() == -1) {
            select(0);
        } else if (pos.getRow() < getRowCount() -1) {
            select(pos.getRow() + 1, pos.getTableColumn());
        }
    }
}

您需要实现此接口才能对TreeItem执行实际的选择过滤:

public interface TreeItemSelectionChecker<S> {

    public boolean isSelectable(TreeItem<S> treeItem);
}

并将其作为FilteredTreeTableViewSelectionModel的构造函数参数之一传递。

要与TreeTableView一起使用,可以执行:

TreeTableViewSelectionModel<S> selectionModel = treeTableView.getSelectionModel();
TreeItemSelectionChecker<S> treeItemChecker = ...custom implementation
FilteredTreeTableViewSelectionModel<S> filteredSelectionModel = new FilteredTreeTableViewSelectionModel(treeTableView, selectionModel, treeItemChecker);
© www.soinside.com 2019 - 2024. All rights reserved.