为了账号安全,请及时绑定邮箱和手机立即绑定

如何使用箭头按钮在 TableView 的编辑模式下遍历单元格

如何使用箭头按钮在 TableView 的编辑模式下遍历单元格

喵喵时光机 2022-09-01 17:43:26
我想使用箭头/enter键遍历 中的单元格,但是,如果我尝试在我的自定义EditCell类中实现它,它似乎不起作用。有没有办法做到这一点?我尝试过在 上使用侦听器,但它实际上并没有在实际单元格中启动焦点。TableViewTextField这是我的代码:测试人员.javapackage 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);    }}
查看完整描述

2 回答

?
动漫人物

TA贡献1815条经验 获得超10个赞

行选择模式

尽管我的评论,但看起来您并不需要为此启用单元格选择。从CheckBoxTableCell的实现中汲取灵感,您的自定义应该采用某种形式的回调来获取模型属性;它还可能需要一个 StringConverter,允许您使用不仅仅包含 s 的 。下面是一个示例:TableCellTableCellString

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());

        }

    }


}

这个例子并不详尽,并且做出了不能保证的假设,但它只是一个例子,所以我把任何调整留给你。其中一个改进可能是以某种方式包含文本格式化程序。也就是说,我相信它提供了您正在寻找的基本功能。


要使用此单元格,您只需将 每个 的 设置。没有必要设置,这样做实际上可能是有害的,这取决于如何被调用。基本上,它看起来像这样:cellFactoryTableColumncellValueFactoryupdateItem


TableView<YourModel> table = ...;


TableColumn<YourModel, String> column = new TableColumn<>("Column");

column.setCellFactory(CustomTableCell.forTableColumn(i -> table.getItems().get(i).someProperty()));

table.getColumns().add(column);

单元格选择模式

但是,您尝试实现的此行为本身似乎基于单元格,因此启用单元格选择可能更好。这允许自定义人员将其行为基于选择而不是焦点,并将箭头键处理留给 .下面是上述示例的略微修改版本:TableCellTableView


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. 上的集不能定义提取器。由于某种原因,这会导致表在您键入 时选择下一个右侧单元格。ObservableListTableViewTextField

  3. 仅使用 JavaFX 12 测试了这两种方法。


查看完整回答
反对 回复 2022-09-01
?
叮当猫咪

TA贡献1776条经验 获得超12个赞

多亏了Slaw,才想通了。


首先启用单元格选择,table.getSelectionModel().setCellSelectionEnabled(true);


然后在 EditableTextCell.java类中:


        this.focusedProperty().addListener((ObservableValue<? extends Boolean> o, Boolean oldValue, Boolean newValue) ->

        {

            if (newValue)

            {

                textField.requestFocus();

            }


        });


        textField.focusedProperty().addListener((ObservableValue<? extends Boolean> o, Boolean oldValue, Boolean newValue) ->

        {

            if (newValue)

            {

                getTableView().getFocusModel().focus(getTableRow().getIndex(), getTableColumn());

            }

        }



        textField.setOnKeyPressed((KeyEvent ke) ->

        {

            switch (ke.getCode())

            {

                case DOWN:

                    getTableView().getFocusModel().focusBelowCell();

                    ke.consume();

                    break;

                case ENTER:

                    getTableView().getFocusModel().focusBelowCell();

                    ke.consume();

                    break;

                case UP:

                    getTableView().getFocusModel().focusAboveCell();

                    ke.consume();

                    break;

                case RIGHT:

                    getTableView().getFocusModel().focusRightCell();

                    ke.consume();

                    break;

                case LEFT:

                    getTableView().getFocusModel().focusLeftCell();

                    ke.consume();

                    break;

                default:

                    break;

            }

        });


查看完整回答
反对 回复 2022-09-01
  • 2 回答
  • 0 关注
  • 66 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信