JavaFX TreeTableView-防止选择某些TreeItem [英] JavaFX TreeTableView - Prevent selection of certain TreeItems

查看:113
本文介绍了JavaFX TreeTableView-防止选择某些TreeItem的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have looked at a couple of questions here, but I can't seem to find anything related to disabling selection of rows for TreeTableViews in particular in JavaFX.

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

The closest related questions I have come across are all related to TreeViews:

  1. 如何制作某些JavaFX TreeView节点无法选择?

TreeView-某些TreeItem不允许被选中

问题2中给出的答案,其中使用了从MultipleSelectionModel扩展的自定义选择模型最有前途的.但是,TreeTableViews的问题在于它们使用了自定义TreeTableViewSelectionModel,而该TreeTableViewSelectionModel本身是从TableSelectionModel扩展而来的.

The answer given in question 2 where a custom selection-model is used that extends from MultipleSelectionModel seems to be the most promising. However, the problem with TreeTableViews are that they use a custom TreeTableViewSelectionModel that itself extends from TableSelectionModel.

如果您执行的是天真的实现,则只需将调用转发到包装的TreeTableViewSelectionModel并在select()selectAndClear()方法中提供过滤,如下所示:

If you do a naive implementation where you just forward the calls to a wrapped TreeTableViewSelectionModel and provide filtering in the select() and selectAndClear() methods as follows:

import javafx.collections.ObservableList;
import javafx.scene.control.TableColumnBase;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTablePosition;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.TreeTableView.TreeTableViewSelectionModel;

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

    private final TreeTableViewSelectionModel<S> selectionModel;
    private final TreeItemSelectionFilter<S> filter;

    public FilteredTreeTableViewSelectionModel(
            TreeTableView<S> treeTableView,
            TreeTableViewSelectionModel<S> selectionModel,
            TreeItemSelectionFilter<S> filter) {
        super(treeTableView);
        this.selectionModel = selectionModel;
        this.filter = filter;
    }

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

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

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

    @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.filter.isSelectable(treeItem)) {
            this.selectionModel.clearAndSelect(row);
        }
        // Else, we just do a normal clear-selection call.
        else {
            this.selectionModel.clearSelection();
        }
    }

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

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

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

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

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

使用此界面进行过滤的位置:

where you filter with this interface:

public interface TreeItemSelectionFilter<S> {

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

您的TreeTableView的选择将无法正常工作,因为您会看到所选行未正确突出显示,如在此示例中,我选择了第一行:

Your TreeTableView's selection will not work correctly as you'll see that the selected rows are not properly highlighted as in this example where I selected the first row:

我是否缺少某种需要实现的方式,或者是否有另一种方式为TreeTableViews提供选择过滤?

Am I missing something in the way it needs to be implemented, or is there another way to provide selection filtering for TreeTableViews?

推荐答案

我通过查看TreeTableViewArrayListSelectionModel的内部内容来拼凑一个类.由于TreeTableViewSelectionModel类本身具有状态,因此需要重写许多超类方法,以提供实现转发给内部选择模型或转发给内部选择模型的其他方法之一的实现.此类的实现如下:

I pieced together a class by looking at the internals of TreeTableViewArrayListSelectionModel. As the TreeTableViewSelectionModel class itself has state, a lot of the superclass methods needed to be overridden to provide implementations that forwarded either to the internal selection-model or to one of the other methods that itself forwards to the internal selection-model. The implementation of this class is as follows:

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

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

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

    private final TreeTableViewSelectionModel<S> selectionModel;
    private final TreeItemSelectionFilter<S> selectionFilter;

    public FilteredTreeTableViewSelectionModel(
            TreeTableView<S> treeTableView,
            TreeTableViewSelectionModel<S> selectionModel,
            TreeItemSelectionFilter<S> selectionFilter) {
        super(treeTableView);
        this.selectionModel = selectionModel;
        this.selectionFilter = selectionFilter;
        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();
        TreeTableViewFocusModel<S> focusModel = treeTableView.getFocusModel();
        return (focusModel == null) ? 
            new TreeTablePosition<>(treeTableView, -1, null) :
            focusModel.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;
        TreeTableView<S> treeTableView = getTreeTableView();
        return treeTableView.getVisibleLeafColumn(newColumnIndex);
    }

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

    @Override
    public void select(int row) {
        select(row, null);
    }

    @Override
    public void select(int row, TableColumnBase<TreeItem<S>, ?> column) {
        // If the row is -1, we need to clear the selection.
        if (row == -1) {
            this.selectionModel.clearSelection();
        } else if (row >= 0 && row < getRowCount()) {
            // If the tree-item at the specified row-index is selectable, we
            // forward select call to the internal selection-model.
            TreeTableView<S> treeTableView = getTreeTableView();
            TreeItem<S> treeItem = treeTableView.getTreeItem(row);
            if (this.selectionFilter.isSelectable(treeItem)) {
                this.selectionModel.select(row, column);
            }
        }
    }

    @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.selectionFilter.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 (treeItem != null) && selectionFilter.isSelectable(treeItem);
        }).toArray();

        // If we have rows left, we proceed to forward to internal 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 clearAndSelect(int row) {
        clearAndSelect(row, null);
    }

    @Override
    public void clearAndSelect(int row, TableColumnBase<TreeItem<S>, ?> column) {
        // If the row is out-of-bounds we just clear and return.
        if (row < 0 || row >= getRowCount()) {
            clearSelection();
            return;
        }

        TreeTableView<S> treeTableView = getTreeTableView();
        TreeItem<S> treeItem = treeTableView.getTreeItem(row);

        // If the tree-item at the specified row-index is selectable, we forward
        // clear-and-select call to the internal selection-model.
        if (this.selectionFilter.isSelectable(treeItem)) {
            this.selectionModel.clearAndSelect(row, column);
        }
        // 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 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 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 selectFirst() {
        // Find first selectable row in the tree-table by testing each tree-item
        // against our selection-filter.
        TreeTableView<S> treeTableView = getTreeTableView();
        OptionalInt firstRow = IntStream.range(0, getRowCount()).
            filter(row -> this.selectionFilter.isSelectable(treeTableView.getTreeItem(row))).
            findFirst();

        TreeTablePosition<S,?> focusedCell = getFocusedCell();

        // If we managed to find a row, we forward to the appropriate internal
        // selection-model's select-method based on whether cell-seleciton is
        // enabled or not.
        firstRow.ifPresent(row -> {
            if (isCellSelectionEnabled()) {
                this.selectionModel.select(row, focusedCell.getTableColumn());
            } else {
                this.selectionModel.select(row);
            }
        });
    }

    @Override
    public void selectLast() {
        // Find first selectable row (by iterating in reverse) in the tree-table
        // by testing each tree-item against our selection-filter.
        int rowCount = getRowCount();
        TreeTableView<S> treeTableView = getTreeTableView();
        OptionalInt lastRow = IntStream.iterate(rowCount - 1, i -> i - 1).
            limit(rowCount).
            filter(row -> this.selectionFilter.isSelectable(treeTableView.getTreeItem(row))).
            findFirst();

        TreeTablePosition<S,?> focusedCell = getFocusedCell();

        // If we managed to find a row, we forward to the appropriate internal
        // selection-model's select-method based on whether cell-seleciton is
        // enabled or not.
        lastRow.ifPresent(row -> {
            if (isCellSelectionEnabled()) {
                this.selectionModel.select(row, focusedCell.getTableColumn());
            } else {
                this.selectionModel.select(row);
            }
        });
    }

    @Override
    public void selectPrevious() {
        TreeTableView<S> treeTableView = getTreeTableView();

        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 we are not at the first column, we go to the previous column.
            if (pos.getColumn() - 1 >= 0) {
                this.selectionModel.select(pos.getRow(), getTableColumn(pos.getTableColumn(), -1));
            }
            // Else, we wrap to end of previous selectable row.
            else {
                // If we have nothing selected, wrap around to the last index.
                int startIndex = (pos.getRow() == -1) ? getRowCount() : pos.getRow();

                // Find previous selectable row.
                OptionalInt previousRow = IntStream.iterate(startIndex - 1, i -> i - 1).
                limit(startIndex).
                filter(row -> this.selectionFilter.isSelectable(treeTableView.getTreeItem(row))).
                findFirst();

                // Last column index.
                int lastColumnIndex = getTreeTableView().getVisibleLeafColumns().size() - 1;

                // If we have a previous row, forward selection to internal selection-model.
                previousRow.ifPresent(row -> this.selectionModel.select(row, getTableColumn(lastColumnIndex)));
            }
        } else {
            // If we have nothing selected, wrap around to the last index.
            int startIndex = (getFocusedIndex() == -1) ? getRowCount() : getFocusedIndex();
            if (startIndex > 0) {
                OptionalInt previousRow = IntStream.iterate(startIndex - 1, i -> i - 1).
                    limit(startIndex).
                    filter(row -> this.selectionFilter.isSelectable(treeTableView.getTreeItem(row))).
                    findFirst();
                previousRow.ifPresent(this.selectionModel::select);
            }
        }
    }

    @Override
    public void selectNext() {
        TreeTableView<S> treeTableView = getTreeTableView();

        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 we are not at the last column, then go to the next column.
            if (pos.getRow() != -1 && pos.getColumn() + 1 < getTreeTableView().getVisibleLeafColumns().size()) {
                this.selectionModel.select(pos.getRow(), getTableColumn(pos.getTableColumn(), 1));
            }
            // Else, wrap to start of next selectable row.
            else if (pos.getRow() < getRowCount() - 1) {
                // If we have nothing selected, starting at -1 will work out correctly
                // because we'll search from 0 onwards.
                int startIndex = pos.getRow();

                // Find next selectable row.
                OptionalInt nextItem = IntStream.range(startIndex + 1, getRowCount()).
                    filter(row -> this.selectionFilter.isSelectable(treeTableView.getTreeItem(row))).
                    findFirst();

                // If we have a next row, forward selection to internal selection-model.
                nextItem.ifPresent(row -> this.selectionModel.select(row, getTableColumn(0)));
            }
        } else {
            // 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) {
                OptionalInt nextRow = IntStream.range(startIndex + 1, getRowCount()).
                    filter(row -> this.selectionFilter.isSelectable(treeTableView.getTreeItem(row))).
                    findFirst();
                nextRow.ifPresent(this.selectionModel::select);
            }
        }
    }

    @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 we have nothing selected, wrap around to the last row.
        if (pos.getRow() == -1) {
            selectLast();
        } else if (pos.getRow() > 0) {
            TreeTableView<S> treeTableView = getTreeTableView();

            // Find previous selectable row.
            OptionalInt previousRow = IntStream.iterate(pos.getRow() - 1, i -> i - 1).
                limit(pos.getRow()).
                filter(row -> this.selectionFilter.isSelectable(treeTableView.getTreeItem(row))).
                findFirst();

            // If we have a previous row, forward selection to internal selection-model.
            previousRow.ifPresent(row -> this.selectionModel.select(row, pos.getTableColumn()));
        }
    }

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

        // If we have nothing selected, start at the first row.
        if (pos.getRow() == -1) {
            selectFirst();
        } else if (pos.getRow() < getRowCount() -1) {
            TreeTableView<S> treeTableView = getTreeTableView();

            // Find next selectable row.
            OptionalInt nextItem = IntStream.range(pos.getRow() + 1, getRowCount()).
                filter(row -> this.selectionFilter.isSelectable(treeTableView.getTreeItem(row))).
                findFirst();

            // If we have a next row, forward selection to internal selection-model.
            nextItem.ifPresent(row -> this.selectionModel.select(row, pos.getTableColumn()));
        }
    }
}

selectFirst()selectLast()selectNext()selectPrevious()方法是通过从特定方向的索引中搜索满足过滤条件的第一个TreeItem来实现的.然后将相应的行转发到内部选择模型.

The selectFirst(), selectLast(), selectNext() and selectPrevious() methods were implemented by searching for the first TreeItem that satisfied the filter from an index in a specific direction. The corresponding row is then forwarded to the internal selection-model.

selectRange()方法只是通过转发给在MultipleSelectionModel中实际实现的超类副本来实现的(不确定这是否正确),因为TreeTableViewArrayListSelectionModel中的实现需要访问其内部的selectedCellsMap数据,如果我们尝试复制所有功能,就会打开一堆蠕虫.

The selectRange() methods were just implemented by forwarding to their superclass counterparts that is actually implemented in MultipleSelectionModel (not sure if this is correct) as the implementation in TreeTableViewArrayListSelectionModel requires access to its internal selectedCellsMap data-structure that opens up a whole can of worms if we try to copy all of that functionality.

我还将cellSelectionEnabledPropertyselectionModeProperty绑定到内部选择模型,因为这需要反映内部模型在首次创建时所处的状态.这也允许我们更改内部选择模型的这些属性,并且更新将反映在FilteredTreeTableViewSelectionModel中.

I also bind the cellSelectionEnabledProperty and the selectionModeProperty to the internal selection-model as this needs to reflect the state that the internal model was in when first created. This also allows us to change these properties for the internal selection-model and the updates will be reflected in the FilteredTreeTableViewSelectionModel.

要使用FilteredTreeTableViewSelectionModel,您需要实现TreeItemSelectionFilter接口(如问题中所述),并将其与TreeTableView和现有选择模型一起作为FilteredTreeTableViewSelectionModel的构造函数参数之一传递: /p>

To use the FilteredTreeTableViewSelectionModel you need to implement the TreeItemSelectionFilter interface (as given in the question) and pass it as one of the constructor arguments for the FilteredTreeTableViewSelectionModel together with the TreeTableView and existing selection-model:

...
TreeTableViewSelectionModel<S> selectionModel = treeTableView.getSelectionModel();
TreeItemSelectionFilter<S> treeItemFilter = MyCustomSelectionFilter<>();
FilteredTreeTableViewSelectionModel<S> filteredSelectionModel = new FilteredTreeTableViewSelectionModel(treeTableView, selectionModel, treeItemFilter);
treeTableView.setSelectionModel(filteredSelectionModel);
...

我已经在此处上传了示例应用程序的源代码.自己轻松测试FilteredTreeTableViewSelectionModel的行为.将其与默认选择模型进行比较,看看您是否对此行为感到满意.

I've uploaded the source-code of an example application here so that you can easily test the behavior of the FilteredTreeTableViewSelectionModel for yourself. Compare it with the default selection-model and see if you are satisfied with the behavior.

这篇关于JavaFX TreeTableView-防止选择某些TreeItem的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆