java - 在具有合并列的 TableView 中进行选择

标签 java javafx javafx-8

在下面的简化代码中,我对第一行进行了硬编码,以跨越 TableView 的所有三列。

除了选择之外,它似乎运作良好。如果我选择了第一行并按右键,则选择将转到宽度为零的单元格,因此似乎消失了。同样,如果选择第二行的中间单元格并按下向上键,则选择内容将转到同一隐藏单元格。

是否有办法告诉选择模型应该忽略某些单元格?或者我更有可能必须从头开始编写一个选择模型?

import com.sun.javafx.scene.control.skin.TableRowSkin;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class TableSkinTest extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        BorderPane pane = new BorderPane();
        Scene scene = new Scene(pane, 500, 500);
        stage.setScene(scene);

        ObservableList<Person> data = FXCollections.observableArrayList(
                new Person("This should span columns because it is long", "", ""),
                new Person("Isabella", "Johnson", "079155882"),
                new Person("Ethan", "Williams", "079155883"),
                new Person("Emma", "Jones", "079155884"),
                new Person("Michael", "Brown", "079155885")
        );

        TableView<Person> table = new TableView<>();

        table.getSelectionModel().setCellSelectionEnabled(true);
        table.setRowFactory(param -> new TableRow<Person>() {
            @Override
            protected Skin<?> createDefaultSkin() {
                return new TableRowSkin<TableSkinTest.Person>(this) {
                    @Override
                    protected void layoutChildren(double x, double y, double w, double h) {
                        checkState();
                        if (cellsMap.isEmpty()) return;
                        ObservableList<? extends TableColumnBase> visibleLeafColumns = getVisibleLeafColumns();
                        if (visibleLeafColumns.isEmpty()) {
                            super.layoutChildren(x, y, w, h);
                            return;
                        }
                        TableRow<TableSkinTest.Person> control = getSkinnable();
                        // layout the individual column cells
                        double width;
                        double height;

                        final double verticalPadding = snappedTopInset() + snappedBottomInset();
                        final double horizontalPadding = snappedLeftInset() + snappedRightInset();
                        final double controlHeight = control.getHeight();

                        int index = control.getIndex();

                        for (int column = 0, max = cells.size(); column < max; column++) {
                            TableCell<TableSkinTest.Person, ?> tableCell = cells.get(column);
                            width = snapSize(tableCell.prefWidth(-1)) - snapSize(horizontalPadding);
                            height = Math.max(controlHeight, tableCell.prefHeight(-1));
                            height = snapSize(height) - snapSize(verticalPadding);
                            if (index == 0 && column > 0) {
                                width = 0; // Hard code for simplification
                            } else if (index == 0) {
                                double width1 = snapSize(cells.get(1).getTableColumn().getWidth());
                                double width2 = snapSize(cells.get(2).getTableColumn().getWidth());
                                width += width1;
                                width += width2;
                            }
                            tableCell.resize(width, height);
                            tableCell.relocate(x, snappedTopInset());
                            x += width;
                        }
                    }
                };
            }
        });

        TableColumn<Person,String> firstNameCol = new TableColumn<>("First Name");
        TableColumn<Person,String> lastNameCol = new TableColumn<>("Last Name");
        TableColumn<Person,String> numberCol = new TableColumn<>("Number");
        firstNameCol.setCellValueFactory(new PropertyValueFactory<>("firstName"));
        lastNameCol.setCellValueFactory(new PropertyValueFactory<>("lastName"));
        numberCol.setCellValueFactory(new PropertyValueFactory<>("number"));

        table.getColumns().addAll(firstNameCol, lastNameCol, numberCol);
        table.getColumns().forEach(col -> col.setPrefWidth(100));

        table.setItems(data);

        pane.setCenter(table);

        stage.show();
    }

    public static class Person {
        private final SimpleStringProperty firstName;
        private final SimpleStringProperty lastName;
        private final SimpleStringProperty number;
        private Person(String fName, String lName, String nNumber) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.number = new SimpleStringProperty(nNumber);
        }
        public String getFirstName() {return firstName.get();}
        public void setFirstName(String fName) {firstName.set(fName);}
        public String getLastName() {return lastName.get();}
        public void setLastName(String fName) {lastName.set(fName);}
        public String getNumber() {return number.get();}
        public void setNumber(String number) {this.number.set(number);}
    }

    public static void main(String[] args) {
        Application.launch(args);
    }
}

最佳答案

Is there anyway of telling the selection model that certain cells should be ignored?

不,选择模型没有不可/可选择的概念。

Or is it more likely I have to write a selection model from scratch?

严格来说,自定义 TableViewSelectionModel 不能从头开始实现 - 它扩展了 MultipleSelectionModelBase,它是包私有(private)的(TableViewArrayListSelectionModel 也是 TableView 中的静态类)。最接近“从头开始”的方法是基本上 c&p TableViewArrayListSelectionModel 并根据需要实现忽略单元格。需要对隐藏的 super 方法/字段进行脏的、反射性的访问...

IMO,没有很好的解决方案可以满足您的要求(除了从头开始编写 TableView 之外)。可能的脏操作(与您的皮肤布局宽度为 0 的跨度单元格类似)可能是在选择任何跨度单元格时保持跨度单元格被选中。仍然存在轻微的可用性问题,因为左/右没有明显的变化 - 但至少选择不会消失。

粗略版本(PlainTableCell 是 TableColumn 中的 c&p 默认值):

public static class SpanTableCell<S, T> extends PlainTableCell<S, T> {

    private ListChangeListener<TablePosition> selectedListener = c -> {
        while (c.next()) {
            if (c.wasAdded() || c.wasRemoved()) {
                updateSelection();
            }
        }
    };

    private WeakListChangeListener weakSelectedListener 
            = new WeakListChangeListener<>(selectedListener);

    private ChangeListener<TableView> tableViewListener = (t, old, value) -> {
        if (old != null && old.getSelectionModel() != null) {
            old.getSelectionModel().getSelectedIndices().removeListener(weakSelectedListener);
        }
        if (value != null && value.getSelectionModel() != null) {
            value.getSelectionModel().getSelectedIndices().addListener(weakSelectedListener);
        }
        updateSelection();
    };

    private WeakChangeListener weakTableViewListener = new WeakChangeListener(tableViewListener);

    public SpanTableCell() {
        tableViewProperty().addListener(weakTableViewListener);
    }

    private void updateSelection() {
        // super will handle (hard-coded not span condition for simplicity)
        if (!isInCellSelectionMode() || getIndex() != 0) return;
        // TableViewSelectionModel doesn't support row selection in
        // cellSelectionMode
        TableViewSelectionModel<S> selectionModel = getTableView()
                .getSelectionModel();
        boolean rowSelected = false; 
        ObservableList<TableColumn<S, ?>> columns = getTableView()
                .getVisibleLeafColumns();
        for (TableColumn<S, ?> tableColumn : columns) {
            rowSelected = rowSelected
                    || selectionModel.isSelected(getIndex(), tableColumn);
        }
        boolean finalSelected = rowSelected;
        Platform.runLater(() -> {
            // need to defer, super gets into the way
            updateSelected(finalSelected);
        });
    }

    private boolean isInCellSelectionMode() {
        TableView<S> tableView = getTableView();
        if (tableView == null) return false;
        TableSelectionModel<S> sm = tableView.getSelectionModel();
        return sm != null && sm.isCellSelectionEnabled();
    }

}

关于java - 在具有合并列的 TableView 中进行选择,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26677385/

相关文章:

java - Tomcat 7 上的 RESTEasy CDI 部署异常

java - 从其他线程访问 while 循环 bool 是线程安全的吗?

java - 如何将数字传递给 TextField JavaFX?

javafx-2 - 如何在 JavaFX Canvas 上绘制旋转的图像?

javafx-8 - 如何忽略 javafx 中的 60fps 限制?

java - 如何在servlet中获取图像数组?

java - 带有 Scene Builder 的 JavaFX 中的 MVC 模式

JavaFX SwingWorker 等效?

java - 了解 getStyleClass().add() 和几行代码

java - 在Java游戏中实现声音