如何在TableView中使用箭头按钮在编辑模式下遍历单元格 [英] How to use arrow buttons to traverse cells in edit mode in TableView

查看:39
本文介绍了如何在TableView中使用箭头按钮在编辑模式下遍历单元格的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想使用箭头/输入键来遍历TableView中的单元格,但是,如果我尝试在自定义EditCell类中实现它,则似乎无法正常工作.有没有办法做到这一点?我在TextField上尝试了一个侦听器,但实际上并没有在实际单元格中开始聚焦.

I'd like to use the arrow/enter keys to traverse the cells in TableView, however, if I try to implement it in my custom EditCell class, it doesn't seem to work. Is there a way to make this happen? I've tried a listener on the TextField but it doesn't actually start the focus in the actual cell.

这是我的代码:

Tester.java

package tester;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;

public class Tester extends Application
{

    @Override
    public void start(Stage primaryStage)
    {

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

        Callback<TableColumn<LineItem, String>, TableCell<LineItem, String>> textFactoryEditable = (TableColumn<LineItem, String> p) -> new EditableTextCell();

        TableColumn<LineItem, String> column1 = new TableColumn<>("Test1");
        column1.setCellValueFactory(cellData -> cellData.getValue().getString1Property());
        column1.setEditable(true);
        column1.setCellFactory(textFactoryEditable);

        table.getColumns().add(column1);

        TableColumn<LineItem, String> column2 = new TableColumn<>("Test2");
        column2.setCellValueFactory(cellData -> cellData.getValue().getString2Property());
        column2.setEditable(true);
        column2.setCellFactory(textFactoryEditable);

        table.getColumns().add(column2);

        table.getItems().add(new LineItem());
        table.getItems().add(new LineItem());
        table.getItems().add(new LineItem());

        table.setPrefWidth(500);

        HBox root = new HBox();
        root.getChildren().addAll(table);

        Scene scene = new Scene(root, 500, 500);

        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args)
    {
        launch(args);
    }

}

LineItem.java

package tester;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class LineItem
{

    private final StringProperty string1;
    private final StringProperty string2;

    public LineItem()
    {
        this.string1 = new SimpleStringProperty();
        this.string2 = new SimpleStringProperty();
    }

    public final StringProperty getString1Property()
    {
        return this.string1;
    }

    public final StringProperty getString2Property()
    {
        return this.string2;
    }
}

EditableTextCell.java

package tester;

import java.util.Objects;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WritableValue;
import javafx.geometry.Pos;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;

public class EditableTextCell<E> extends TableCell<E, String>
{

    private final TextField textField;
    private boolean updating = false;

    public EditableTextCell()
    {
        textField = new TextField();
        textField.setAlignment(Pos.CENTER_RIGHT);


        textField.textProperty().addListener((ObservableValue<? extends String> o, String oldValue, String newValue) ->
        {

            if (!updating)
            {
                ((WritableValue<String>) getTableColumn().getCellObservableValue((E) getTableRow().getItem())).setValue(newValue);
                getTableView().scrollTo(getTableRow().getIndex());
                getTableView().scrollToColumn(getTableColumn());
            }

        });

        textField.setOnKeyPressed((KeyEvent ke) ->
        {
            switch (ke.getCode())
            {
                case DOWN:
                    getTableView().getFocusModel().focusBelowCell();
                    break;
                case UP:
                    getTableView().getFocusModel().focusAboveCell();
                    break;
                case RIGHT:
                    getTableView().getFocusModel().focusRightCell();
                    break;
                case LEFT:
                    getTableView().getFocusModel().focusLeftCell();
                    break;
                default:
                    break;
            }
        });

    }

    @Override
    protected void updateItem(String item, boolean empty)
    {
        super.updateItem(item, empty);
        if (empty)
        {
            setGraphic(null);
        } else
        {
            setGraphic(textField);
            if (!Objects.equals(textField.getText(), item))
            {
                // prevent own updates from moving the cursor
                updating = true;
                textField.setText(item);
                updating = false;

            }
        }
    }
}

推荐答案

行选择模式

尽管 CheckBoxTableCell ,您的自定义TableCell应该采取某种形式的回调来获取模型属性;它也可能需要 StringConverter ,您可以将TableCell与不止String一起使用.这是一个示例:

Row Selection Mode

Despite my comment, it doesn't look like you need to enable cell selection for this. Taking inspiration from the implementation of CheckBoxTableCell, your custom TableCell should take some form of callback to get the model property; it can also require a StringConverter, allowing you to use the TableCell with more than just Strings. Here's an example:

import java.util.Objects;
import java.util.function.IntFunction;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView.TableViewFocusModel;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.util.Callback;
import javafx.util.StringConverter;
import javafx.util.converter.DefaultStringConverter;

public class CustomTableCell<S, T> extends TableCell<S, T> {

    public static <S> Callback<TableColumn<S, String>, TableCell<S, String>> forTableColumn(
            IntFunction<Property<String>> extractor) {
        return forTableColumn(extractor, new DefaultStringConverter());
    }

    public static <S, T> Callback<TableColumn<S, T>, TableCell<S, T>> forTableColumn(
            IntFunction<Property<T>> extractor, StringConverter<T> converter) {
        Objects.requireNonNull(extractor);
        Objects.requireNonNull(converter);
        return column -> new CustomTableCell<>(extractor, converter);
    }

    private final ObjectProperty<IntFunction<Property<T>>> extractor = new SimpleObjectProperty<>(this, "extractor");
    public final void setExtractor(IntFunction<Property<T>> callback) { extractor.set(callback); }
    public final IntFunction<Property<T>> getExtractor() { return extractor.get(); }
    public final ObjectProperty<IntFunction<Property<T>>> extractorProperty() { return extractor; }

    private final ObjectProperty<StringConverter<T>> converter = new SimpleObjectProperty<>(this, "converter");
    public final void setConverter(StringConverter<T> converter) { this.converter.set(converter); }
    public final StringConverter<T> getConverter() { return converter.get(); }
    public final ObjectProperty<StringConverter<T>> converterProperty() { return converter; }

    private Property<T> property;
    private TextField textField;

    public CustomTableCell(IntFunction<Property<T>> extractor, StringConverter<T> converter) {
        setExtractor(extractor);
        setConverter(converter);

        // Assumes this TableCell will never become part of a different TableView
        // after the first one. Also assumes the focus model of the TableView will
        // never change. These are not great assumptions (especially the latter),
        // but this is only an example.
        tableViewProperty().addListener((obs, oldTable, newTable) ->
                newTable.getFocusModel().focusedCellProperty().addListener((obs2, oldPos, newPos) -> {
                    if (getIndex() == newPos.getRow() && getTableColumn() == newPos.getTableColumn()) {
                        textField.requestFocus();
                    }
                })
        );
    }

    @Override
    protected void updateItem(T item, boolean empty) {
        super.updateItem(item, empty);
        if (empty) {
            setText(null);
            setGraphic(null);
            cleanUpProperty();
        } else {
            initializeTextField();
            cleanUpProperty();

            property = getExtractor().apply(getIndex());
            Bindings.bindBidirectional(textField.textProperty(), property, getConverter());

            setGraphic(textField);
            if (getTableView().getFocusModel().isFocused(getIndex(), getTableColumn())) {
                textField.requestFocus();
            }
        }
    }

    private void cleanUpProperty() {
        if (property != null) {
            Bindings.unbindBidirectional(textField.textProperty(), property);
            property = null;
        }
    }

    private void initializeTextField() {
        if (textField == null) {
            textField = new TextField();
            textField.addEventFilter(KeyEvent.KEY_PRESSED, this::processArrowKeys);
            textField.focusedProperty().addListener((observable, wasFocused, isFocused) -> {
                if (isFocused) {
                    getTableView().getFocusModel().focus(getIndex(), getTableColumn());
                }
            });
        }
    }

    private void processArrowKeys(KeyEvent event) {
        if (event.getCode().isArrowKey()) {
            event.consume();

            TableViewFocusModel<S> model = getTableView().getFocusModel();
            switch (event.getCode()) {
                case UP:
                    model.focusAboveCell();
                    break;
                case RIGHT:
                    model.focusRightCell();
                    break;
                case DOWN:
                    model.focusBelowCell();
                    break;
                case LEFT:
                    model.focusLeftCell();
                    break;
                default:
                    throw new AssertionError(event.getCode().name());
            }
            getTableView().scrollTo(model.getFocusedCell().getRow());
            getTableView().scrollToColumnIndex(model.getFocusedCell().getColumn());
        }
    }

}

该示例并非详尽无遗,并做出了无法保证的假设,但这只是一个示例,因此我需要您做任何调整.这样的改进之一可能是包括 TextFormatter 不知何故.就是说,我相信它提供了您正在寻找的基本功能.

The example is not exhaustive and makes assumptions that are not guaranteed, but it's only an example and so I leave any tweaks up to you. One such improvement might be to include a TextFormatter somehow. That said, I believe it provides the basic functionality you're looking for.

要使用此单元格,只需设置每个TableColumncellFactory.不必设置cellValueFactory,这实际上可能是有害的,具体取决于调用updateItem的方式.基本上,它看起来像:

To use this cell, you would only set the cellFactory of each TableColumn. It is not necessary to set the cellValueFactory and doing so might actually be detrimental, depending on how updateItem gets called. Basically, it'd look something like:

TableView<YourModel> table = ...;

TableColumn<YourModel, String> column = new TableColumn<>("Column");
column.setCellFactory(CustomTableCell.forTableColumn(i -> table.getItems().get(i).someProperty()));
table.getColumns().add(column);


单元格选择模式

您尝试实现的这种行为本质上是基于单元格的,因此启用单元格选择可能更好.这允许自定义TableCell将其行为基于选择而不是焦点,并将箭头键处理留给TableView.这是上面示例的稍作修改的版本:


Cell Selection Mode

This behavior you're attempting to implement seems inherently cell based, however, and as such it's probably better to enable cell selection. This allows the custom TableCell to base it's behavior on selection, rather than focus, and leaves the arrow key handling to the TableView. Here's a slightly modified version of the above example:

import java.util.Objects;
import java.util.function.IntFunction;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.EventDispatcher;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.util.Callback;
import javafx.util.StringConverter;
import javafx.util.converter.DefaultStringConverter;

public class CustomTableCell<S, T> extends TableCell<S, T> {

    /* 
     * -- CODE OMITTED --
     *
     * The factory methods (forTableColumn) and properties (extractor
     * and converter) have been omitted for brevity. They are defined
     * and used exactly the same way as in the previous example.
     */

    private Property<T> property;
    private TextField textField;

    public CustomTableCell(IntFunction<Property<T>> extractor, StringConverter<T> converter) {
        setExtractor(extractor);
        setConverter(converter);
    }

    @Override
    public void updateSelected(boolean selected) {
        super.updateSelected(selected);
        if (selected && !isEmpty()) {
            textField.requestFocus();
        }
    }

    @Override
    protected void updateItem(T item, boolean empty) {
        super.updateItem(item, empty);
        if (empty) {
            setText(null);
            setGraphic(null);
            clearProperty();
        } else {
            initializeTextField();
            clearProperty();

            property = getExtractor().apply(getIndex());
            Bindings.bindBidirectional(textField.textProperty(), property, getConverter());

            setGraphic(textField);
            if (isSelected()) {
                textField.requestFocus();
            }
        }
    }

    private void clearProperty() {
        if (property != null) {
            Bindings.unbindBidirectional(textField.textProperty(), property);
            textField.setText(null);
            property = null;
        }
    }

    private void initializeTextField() {
        if (textField == null) {
            textField = new TextField();
            textField.focusedProperty().addListener((observable, wasFocused, isFocused) -> {
                if (isFocused && !isSelected()) {
                    getTableView().getSelectionModel().clearAndSelect(getIndex(), getTableColumn());
                }
            });

            /*
             * TableView has key handlers that will select cells based on arrow keys being
             * pressed, scrolling to them if necessary. I find this mechanism looks cleaner
             * because, unlike TableView#scrollTo, it doesn't cause the cell to jump to the
             * top of the TableView.
             *
             * The way this works is by bypassing the TextField if, and only if, the event
             * is a KEY_PRESSED event and the pressed key is an arrow key. This lets the
             * event bubble up back to the TableView and let it do what it needs to. All
             * other key events are given to the TextField for normal processing.
             *
             * NOTE: The behavior being relied upon here is added by the default TableViewSkin
             *       and its corresponding TableViewBehavior. This may not work if a custom
             *       TableViewSkin skin is used.
             */
            EventDispatcher oldDispatcher = textField.getEventDispatcher();
            textField.setEventDispatcher((event, tail) -> {
                if (event.getEventType() == KeyEvent.KEY_PRESSED
                        && ((KeyEvent) event).getCode().isArrowKey()) {
                    return event;
                } else {
                    return oldDispatcher.dispatchEvent(event, tail);
                }
            });
        }
    }

}


注释

  1. 使用SelectionMode.MULTIPLE(并实际上选择多个行/单元格)时,两种方法都无法很好地工作.
  2. TableView上设置的ObservableList不能具有
  1. Neither approach works well when using SelectionMode.MULTIPLE (and actually selecting multiple rows/cells).
  2. The ObservableList set on the TableView cannot have an extractor defined. For some reason this causes the table to select the next right cell when you type into the TextField.
  3. Only tested both approaches with JavaFX 12.

这篇关于如何在TableView中使用箭头按钮在编辑模式下遍历单元格的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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