在TableView中为CheckBox单元格实现选项卡功能 [英] Implementing tab functionality for CheckBox cells in TableView

查看:153
本文介绍了在TableView中为CheckBox单元格实现选项卡功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我创建了一个TableView,其中每个单元格都包含一个TextField或一个CheckBox.在TableView中,您应该能够使用TAB和SHIFT + TAB在单元格之间左右导航,以及使用UP和DOWN键在单元格之间向上和向下导航.

I've created a TableView where each cell contains a TextField or a CheckBox. In the TableView you're supposed to be able to navigate left and right between cells using TAB and SHIFT+TAB, and up and down between cells using the UP and DOWN keys.

当文本字段单元格被聚焦时,此功能非常理想.但是当一个复选框单元格被聚焦时,选项卡的功能性表现得很奇怪.您可以沿与您所选择的单元格相反的方向进行制表,但不能切换制表符的方向.

This works perfectly when a text field cell is focused. But when a check box cell is focused, the tab funcationality behaves strange. You can tab in the opposite direction of the cell you tabbed from, but you can't switch tab direction.

因此,例如,如果仅使用TAB键将选项卡制表到复选框单元格,则SHIFT + TAB将不起作用.但是,如果您使用TAB键跳至下一个单元格,然后使用SHIFT + TAB退回到TAB(假设下一个单元格是文本字段单元格),则TAB将无法正常工作.

So for instance if you tabbed to the check box cell using only the TAB key, then SHIFT+TAB wont work. But if you tab to the next cell using the TAB key, and then TAB back using SHIFT+TAB (assuming the next cell is a text field cell), then TAB wont work.

我尝试使用Platform.runLater()在UI线程上运行任何代码处理焦点,而没有任何明显的区别.我所知道的是TAB KeyEvent已被正确捕获,但是复选框单元格和复选框始终始终不会失去焦点.我尝试例如通过执行手动删除其焦点getParent().requestFocus(),但这只会导致父对象被聚焦,而不是下一个单元格被聚焦.令我感到奇怪的是,当您在与所来自单元格相反的方向上进行制表时,将执行相同的代码,并且可以正常工作.

I've tried running any code handling focus on the UI thread using Platform.runLater(), without any noteable difference. All I know is that the TAB KeyEvent is properly catched, but the check box cell and the check box never loses focus in the first place anyway. I've tried for instance removing its focus manually by doing e.g. getParent().requestFocus() but that just results in the parent being focused instead of the next cell. What makes it strange is that the same code is executed and working properly when you tab in the opposite direction of the cell you came from.

这是此问题的MCVE.可悲的是,它并没有真正达到缩写的"M":

Here's a MCVE on the issue. Sadly it does not really live up to the "M" of the abbreviation:

import java.util.List;

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;

public class AlwaysEditableTable extends Application {
    public void start(Stage stage) {

        TableView<ObservableList<StringProperty>> table = new TableView<>();
        table.setEditable(true);
        table.getSelectionModel().setCellSelectionEnabled(true);
        table.setPrefWidth(510);

        // Dummy columns
        ObservableList<String> columns = FXCollections.observableArrayList("Column1", "Column2", "Column3", "Column4",
                "Column5");

        // Dummy data
        ObservableList<StringProperty> row1 = FXCollections.observableArrayList(new SimpleStringProperty("Cell1"),
                new SimpleStringProperty("Cell2"), new SimpleStringProperty("0"), new SimpleStringProperty("Cell4"),
                new SimpleStringProperty("0"));
        ObservableList<StringProperty> row2 = FXCollections.observableArrayList(new SimpleStringProperty("Cell1"),
                new SimpleStringProperty("Cell2"), new SimpleStringProperty("1"), new SimpleStringProperty("Cell4"),
                new SimpleStringProperty("0"));
        ObservableList<StringProperty> row3 = FXCollections.observableArrayList(new SimpleStringProperty("Cell1"),
                new SimpleStringProperty("Cell2"), new SimpleStringProperty("1"), new SimpleStringProperty("Cell4"),
                new SimpleStringProperty("0"));
        ObservableList<ObservableList<StringProperty>> data = FXCollections.observableArrayList(row1, row2, row3);

        for (int i = 0; i < columns.size(); i++) {
            final int j = i;
            TableColumn<ObservableList<StringProperty>, String> col = new TableColumn<>(columns.get(i));
            col.setCellValueFactory(param -> param.getValue().get(j));
            col.setPrefWidth(100);

            if (i == 2 || i == 4) {
                col.setCellFactory(e -> new CheckBoxCell(j));
            } else {
                col.setCellFactory(e -> new AlwaysEditingCell(j));
            }

            table.getColumns().add(col);
        }

        table.setItems(data);

        Scene scene = new Scene(table);
        stage.setScene(scene);
        stage.show();
    }

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

    /**
     * A cell that contains a text field that is always shown.
     */
    public static class AlwaysEditingCell extends TableCell<ObservableList<StringProperty>, String> {

        private final TextField textField;

        public AlwaysEditingCell(int columnIndex) {

            textField = new TextField();

            this.emptyProperty().addListener((obs, wasEmpty, isNowEmpty) -> {
                if (isNowEmpty) {
                    setGraphic(null);
                } else {
                    setGraphic(textField);
                }
            });

            // The index is not changed until tableData is instantiated, so this
            // ensure the we wont get a NullPointerException when we do the
            // binding.
            this.indexProperty().addListener((obs, oldValue, newValue) -> {

                ObservableList<ObservableList<StringProperty>> tableData = getTableView().getItems();
                int oldIndex = oldValue.intValue();
                if (oldIndex >= 0 && oldIndex < tableData.size()) {
                    textField.textProperty().unbindBidirectional(tableData.get(oldIndex).get(columnIndex));
                }
                int newIndex = newValue.intValue();
                if (newIndex >= 0 && newIndex < tableData.size()) {
                    textField.textProperty().bindBidirectional(tableData.get(newIndex).get(columnIndex));
                    setGraphic(textField);
                } else {
                    setGraphic(null);
                }

            });

            // Every time the cell is focused, the focused is passed down to the
            // text field and all of the text in the textfield is selected.
            this.focusedProperty().addListener((obs, oldValue, newValue) -> {
                if (newValue) {
                    textField.requestFocus();
                    textField.selectAll();
                    System.out.println("Cell focused!");
                }
            });

            // Switches focus to the cell below if ENTER or the DOWN arrow key
            // is pressed, and to the cell above if the UP arrow key is pressed.
            // Works like a charm. We don't have to add any functionality to the
            // TAB key in these cells because the default tab behavior in
            // JavaFX works here.
            this.addEventFilter(KeyEvent.KEY_RELEASED, e -> {
                if (e.getCode().equals(KeyCode.UP)) {
                    getTableView().getFocusModel().focus(getIndex() - 1, this.getTableColumn());
                    e.consume();
                } else if (e.getCode().equals(KeyCode.DOWN)) {
                    getTableView().getFocusModel().focus(getIndex() + 1, this.getTableColumn());
                    e.consume();
                } else if (e.getCode().equals(KeyCode.ENTER)) {
                    getTableView().getFocusModel().focus(getIndex() + 1, this.getTableColumn());
                    e.consume();
                }
            });
        }
    }

    /**
     * A cell containing a checkbox. The checkbox represent the underlying value
     * in the cell. If the cell value is 0, the checkbox is unchecked. Checking
     * or unchecking the checkbox will change the underlying value.
     */
    public static class CheckBoxCell extends TableCell<ObservableList<StringProperty>, String> {
        private final CheckBox box;

        public CheckBoxCell(int columnIndex) {

            this.box = new CheckBox();

            this.emptyProperty().addListener((obs, wasEmpty, isNowEmpty) -> {
                if (isNowEmpty) {
                    setGraphic(null);
                } else {
                    setGraphic(box);
                }
            });

            this.indexProperty().addListener((obs, oldValue, newValue) -> {
                // System.out.println("Row: " + getIndex() + ", Column: " +
                // columnIndex + ". Old index: " + oldValue
                // + ". New Index: " + newValue);

                ObservableList<ObservableList<StringProperty>> tableData = getTableView().getItems();
                int newIndex = newValue.intValue();
                if (newIndex >= 0 && newIndex < tableData.size()) {
                    // If active value is "1", the check box will be set to
                    // selected.
                    box.setSelected(tableData.get(getIndex()).get(columnIndex).equals("1"));

                    // We add a listener to the selected property. This will
                    // allow us to execute code every time the check box is
                    // selected or deselected.
                    box.selectedProperty().addListener((observable, oldVal, newVal) -> {
                        if (newVal) {
                            // If newValue is true the checkBox is selected, and
                            // we set the corresponding cell value to "1".
                            tableData.get(getIndex()).get(columnIndex).set("1");
                        } else {
                            // Otherwise we set it to "0".
                            tableData.get(getIndex()).get(columnIndex).set("0");
                        }
                    });

                    setGraphic(box);
                } else {
                    setGraphic(null);
                }

            });

            // If I listen to KEY_RELEASED instead, pressing tab next to a
            // checkbox will make the focus jump past the checkbox cell. This is
            // probably because the default TAB functionality is invoked on key
            // pressed, which switches the focus to the check box cell, and then
            // upon release this EventFilter catches it and switches focus
            // again.
            this.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
                if (e.getCode().equals(KeyCode.UP)) {
                    System.out.println("UP key pressed in checkbox");
                    getTableView().getFocusModel().focus(getIndex() - 1, this.getTableColumn());
                    e.consume();
                } else if (e.getCode().equals(KeyCode.DOWN)) {
                    System.out.println("DOWN key pressed in checkbox");
                    getTableView().getFocusModel().focus(getIndex() + 1, this.getTableColumn());
                    e.consume();
                } else if (e.getCode().equals(KeyCode.TAB)) {
                    System.out.println("Checkbox TAB pressed!");
                    TableColumn<ObservableList<StringProperty>, ?> nextColumn = getNextColumn(!e.isShiftDown());
                    if (nextColumn != null) {
                        getTableView().getFocusModel().focus(getIndex(), nextColumn);
                    }
                    e.consume();

                    // ENTER key will set the check box to selected if it is
                    // unselected and vice versa.
                } else if (e.getCode().equals(KeyCode.ENTER)) {
                    box.setSelected(!box.isSelected());
                    e.consume();
                }
            });

            // Tracking the focused property of the check box for debug
            // purposes.
            box.focusedProperty().addListener((obs, oldValue, newValue) ->

            {
                if (newValue) {
                    System.out.println("Box focused on index " + getIndex());
                } else {
                    System.out.println("Box unfocused on index " + getIndex());
                }
            });

            // Tracking the focused property of the check box for debug
            // purposes.
            this.focusedProperty().addListener((obs, oldValue, newValue) ->

            {
                if (newValue) {
                    System.out.println("Box cell focused on index " + getIndex());
                    box.requestFocus();
                } else {
                    System.out.println("Box cell unfocused on index " + getIndex());
                }
            });
        }

        /**
         * Gets the column to the right or to the left of the current column
         * depending no the value of forward.
         * 
         * @param forward
         *            If true, the column to the right of the current column
         *            will be returned. If false, the column to the left of the
         *            current column will be returned.
         */
        private TableColumn<ObservableList<StringProperty>, ?> getNextColumn(boolean forward) {
            List<TableColumn<ObservableList<StringProperty>, ?>> columns = getTableView().getColumns();
            // If there's less than two columns in the table view we return null
            // since there can be no column to the right or left of this
            // column.
            if (columns.size() < 2) {
                return null;
            }

            // We get the index of the current column and then we get the next
            // or previous index depending on forward.
            int currentIndex = columns.indexOf(getTableColumn());
            int nextIndex = currentIndex;
            if (forward) {
                nextIndex++;
                if (nextIndex > columns.size() - 1) {
                    nextIndex = 0;
                }
            } else {
                nextIndex--;
                if (nextIndex < 0) {
                    nextIndex = columns.size() - 1;
                }
            }

            // We return the column on the next index.
            return columns.get(nextIndex);
        }
    }
}

推荐答案

TableView源代码中进行了一些挖掘之后,我发现了问题所在.这是focus(int row, TableColumn<S, ?> column)方法的源代码:

After some digging in the TableView source code I found the issue. Here's the source code for the focus(int row, TableColumn<S, ?> column) method:

@Override public void focus(int row, TableColumn<S,?> column) {
            if (row < 0 || row >= getItemCount()) {
                setFocusedCell(EMPTY_CELL);
            } else {
                TablePosition<S,?> oldFocusCell = getFocusedCell();
                TablePosition<S,?> newFocusCell = new TablePosition<>(tableView, row, column);
                setFocusedCell(newFocusCell);

                if (newFocusCell.equals(oldFocusCell)) {
                    // manually update the focus properties to ensure consistency
                    setFocusedIndex(row);
                    setFocusedItem(getModelItem(row));
                }
            }
        }

newFocusCelloldFocusCell进行比较时会出现此问题.当跳到复选框单元格时,由于某种原因,该单元格将不会设置为焦点单元格.因此,由getFocusedCell()返回的focusedCell属性将是我们在复选框单元格之前聚焦的单元格.因此,当我们然后再次尝试将先前的单元格聚焦时,newFocusCell.equals(oldFocusCell)将返回true,并且通过执行以下操作将焦点再次设置为当前聚焦的单元格:

The issue arises when newFocusCell is compared to oldFocusCell. When tabbing to a checkbox cell the cell would for some reason not get set as the focused cell. Hence the focusedCell property returned by getFocusedCell() will be the cell we focused before the check box cell. So when we then try to focus that previous cell again, newFocusCell.equals(oldFocusCell) will return true, and the focus will be set to the currently focused cell again by doing:

setFocusedIndex(row);     
setFocusedItem(getModelItem(row));`

所以我要做的是确保要聚焦时该单元格不是focusedCell属性的值.我通过手动将焦点设置到整个表来解决此问题,然后尝试从复选框单元格切换焦点:

So what I had to do was make sure that the cell isn't be the value of the focusedCell property when we want to focus it. I solved this by setting the focus manually to the whole table before trying to switch the focus from the check box cell:

table.requestFocus();

这篇关于在TableView中为CheckBox单元格实现选项卡功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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