在TableView中键入要编辑

时间:2015-04-12 06:26:28

标签: javafx tableview

问题

我想在输入后立即切换到TableView中的编辑模式。我不想先双击或按下进入每个单元格,这很烦人。

我提出了以下代码。问题是它或多或少是副作用编程,我怀疑是麻烦。当您使用KEY_RELEASED将表格切换到编辑模式时,第一次按键会丢失。

所以你必须使用KEY_PRESSED。这一切似乎现在都运行良好,但偶尔你会得到一个竞争条件,TextField单元格编辑器中的插入符号在键入的文本之前而不是之后。但是当您继续键入时,文本会在现有文本后正确添加。

看起来没问题,但从发展的角度来看,它似乎与竞争条件混乱。

问题

有没有人有一个正确的方法来做"类型编辑"功能?

代码

这是我到目前为止所获得的代码:

public class InlineEditingTableView extends Application {

    private final ObservableList<Data> data =
        FXCollections.observableArrayList(
                new Data(1.,5.),
                new Data(2.,6.),
                new Data(3.,7.),
                new Data(4.,8.)
        );

    private TableView<Data> table;

    @Override
    public void start(Stage stage) {

        // create edtiable table
        table = new TableView<Data>();
        table.setEditable(true);

        // column 1 contains numbers
        TableColumn<Data, Number> number1Col = new TableColumn<>("Number 1");
        number1Col.setMinWidth(100);
        number1Col.setCellValueFactory( cellData -> cellData.getValue().number1Property());
        number1Col.setCellFactory( createNumberCellFactory());
        number1Col.setOnEditCommit(new EventHandler<CellEditEvent<Data, Number>>() {
            @Override
            public void handle(CellEditEvent<Data, Number> t) {
                System.out.println( t);
//                ((Person) t.getTableView().getItems().get(t.getTablePosition().getRow())).setFirstName(t.getNewValue());
            }
        });

        // column 2 contains numbers
        TableColumn<Data, Number> number2Col = new TableColumn<>("Number 2");
        number2Col.setMinWidth(100);
        number2Col.setCellValueFactory( cellData -> cellData.getValue().number2Property());
        number2Col.setCellFactory( createNumberCellFactory());

        // add columns & data to table
        table.setItems(data);
        table.getColumns().addAll( number1Col, number2Col);




        // switch to edit mode on keypress
        // this must be KeyEvent.KEY_PRESSED so that the key gets forwarded to the editing cell; it wouldn't be forwarded on KEY_RELEASED
        table.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent event) {

                if( event.getCode() == KeyCode.ENTER) {
//                  event.consume(); // don't consume the event or else the values won't be updated;
                    return;
                }

                // switch to edit mode on keypress, but only if we aren't already in edit mode
                if( table.getEditingCell() == null) {
                    if( event.getCode().isLetterKey() || event.getCode().isDigitKey()) {  

                        TablePosition focusedCellPosition = table.getFocusModel().getFocusedCell();
                        table.edit(focusedCellPosition.getRow(), focusedCellPosition.getTableColumn());

                    }
                }

            }
        });

        table.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent event) {

                if( event.getCode() == KeyCode.ENTER) {
                    table.getSelectionModel().selectBelowCell();
                }
            }
        });     

        // single cell selection mode
        table.getSelectionModel().setCellSelectionEnabled(true);
        table.getSelectionModel().selectFirst();




        // add nodes to stage
        BorderPane root = new BorderPane();
        root.setCenter(table);

        Scene scene = new Scene( root, 800,600);
        stage.setScene(scene);
        stage.show();
    }

    /**
     * Number cell factory which converts strings to numbers and vice versa.
     * @return
     */
    private Callback<TableColumn<Data, Number>, TableCell<Data, Number>>  createNumberCellFactory() {

        Callback<TableColumn<Data, Number>, TableCell<Data, Number>> factory = TextFieldTableCell.forTableColumn( new StringConverter<Number>() {

            @Override
            public Number fromString(String string) {
                return Double.parseDouble(string);
            }

            @Override
            public String toString(Number object) {
                return object.toString();
            }
        });

        return factory;
    }

    /**
     * Table data container
     */
    public static class Data {

        private final SimpleDoubleProperty number1;
        private final SimpleDoubleProperty number2;

        private Data( Double number1, Double number2) {
            this.number1 = new SimpleDoubleProperty(number1);
            this.number2 = new SimpleDoubleProperty(number2);
        }

        public final DoubleProperty number1Property() {
            return this.number1;
        }

        public final double getNumber1() {
            return this.number1Property().get();
        }

        public final void setNumber1(final double number1) {
            this.number1Property().set(number1);
        }

        public final DoubleProperty number2Property() {
            return this.number2;
        }

        public final double getNumber2() {
            return this.number2Property().get();
        }

        public final void setNumber2(final double number2) {
            this.number2Property().set(number2);
        }


    }

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


} 

2 个答案:

答案 0 :(得分:1)

要在点击一个单元格时立即进行编辑,让我更有意义的是让TextField永久显示在表格中,而不是转换为特殊的编辑模式&#34;并从Label切换到TextField。 (我认为这是因为所有单元格总是在&#34;编辑模式&#34;,我认为这对你想要的行为有意义。)

如果这种UI适用于您的要求,您只需在单元格中呈现文本字段,并将文本字段textProperty双向绑定到模型中的相应属性。这里棘手的部分是获取该属性:您必须从单元格转到表格行,然后转到表格行的项目,然后转到您需要的属性。在任何时候,其中一个可能会改变(可能改为null),所以你必须处理这些可能性。

举出通常的例子:

public class Person {

    // ...

    public StringProperty firstNameProperty() { ... }

    // etc...
}

你可以做到

    TableView<Person> table = new TableView<>();
    TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
    firstNameCol.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
    firstNameCol.setCellFactory(col -> {
        TableCell<Person, String> cell = new TableCell<>();
        TextField textField = new TextField();

        cell.graphicProperty().bind(Bindings.when(cell.emptyProperty())
                .then((Node)null)
                .otherwise(textField));

        ChangeListener<Person> rowItemListener = (obs, oldPerson, newPerson) -> {
            if (oldPerson != null) {
                textField.textProperty().unbindBidirectional(((Person) oldPerson).firstNameProperty());
            }
            if (newPerson != null) {
                textField.textProperty().bindBidirectional(((Person) newPerson).firstNameProperty());
            }
        };
        cell.tableRowProperty().addListener((obs, oldRow, newRow) -> {
            if (oldRow != null) {
                oldRow.itemProperty().removeListener(rowItemListener);
                if (oldRow.getItem() != null) {
                    textField.textProperty().unbindBidirectional(((Person) oldRow.getItem()).firstNameProperty());
                }
            }
            if (newRow != null) {
                newRow.itemProperty().addListener(rowItemListener);
                if (newRow.getItem() != null) {
                    textField.textProperty().bindBidirectional(((Person) newRow.getItem()).firstNameProperty());
                }
            }
        });

        return cell ;
    });

通过使用EasyBind框架可以极大地降低代码复杂性,EasyBind框架提供(以及其他方面)获取属性属性的方法。适当处理null

    TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
    firstNameCol.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
    firstNameCol.setCellFactory(col -> {
        TableCell<Person, String> cell = new TableCell<>();
        TextField textField = new TextField();

        cell.graphicProperty().bind(Bindings.when(cell.emptyProperty())
                .then((Node)null)
                .otherwise(textField));

        textField.textProperty().bindBidirectional(
                EasyBind.monadic(cell.tableRowProperty())
                .selectProperty(TableRow::itemProperty)
                .selectProperty(p -> ((Person)p).firstNameProperty()));

        return cell ;
    });

这是一个完整的示例,其中我将上面的单元工厂代码分解为更通用的方法:

import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

import org.fxmisc.easybind.EasyBind;

public class LiveTableViewCell extends Application {

    @Override
    public void start(Stage primaryStage) {
        TableView<Person> table = new TableView<>();
        table.getItems().addAll(            
            new Person("Jacob", "Smith", "jacob.smith@example.com"),
            new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
            new Person("Ethan", "Williams", "ethan.williams@example.com"),
            new Person("Emma", "Jones", "emma.jones@example.com"),
            new Person("Michael", "Brown", "michael.brown@example.com")
        );

        table.getColumns().addAll(
            createColumn("First Name", Person::firstNameProperty),
            createColumn("Last Name", Person::lastNameProperty),
            createColumn("Email", Person::emailProperty)
        );

        Button button = new Button("Debug");
        button.setOnAction(e -> table.getItems().stream().map(p -> String.format("%s %s %s", p.getFirstName(), p.getLastName(), p.getEmail())).forEach(System.out::println));

        primaryStage.setScene(new Scene(new BorderPane(table, null, null, button, null), 600, 120));
        primaryStage.show();
    }

    private TableColumn<Person, String> createColumn(String title, Function<Person, Property<String>> property) {
        TableColumn<Person, String> col = new TableColumn<>(title);
        col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));

        col.setCellFactory(column -> {
            TableCell<Person, String> cell = new TableCell<>();
            TextField textField = new TextField();

        // Example of maintaining selection behavior when text field gains
        // focus. You can also call getSelectedCells().add(...) on the selection
        // model if you want to maintain multiple selected cells, etc.

        textField.focusedProperty().addListener((obs, wasFocused, isFocused) -> {
            if (isFocused) {
                cell.getTableView().getSelectionModel().select(cell.getIndex(), cell.getTableColumn());
            }
        });

        cell.graphicProperty().bind(Bindings.when(cell.emptyProperty())
                .then((Node)null)
                .otherwise(textField));

            // If not using EasyBind, you need the following commented-out code in place of the next statement:

//          ChangeListener<Person> rowItemListener = (obs, oldPerson, newPerson) -> {
//               if (oldPerson != null) {
//                   textField.textProperty().unbindBidirectional(property.apply((Person)oldPerson));
//               }
//               if (newPerson != null) {
//                   textField.textProperty().bindBidirectional(property.apply((Person)newPerson));
//               }
//            };
//            cell.tableRowProperty().addListener((obs, oldRow, newRow) -> {
//                if (oldRow != null) {
//                    oldRow.itemProperty().removeListener(rowItemListener);
//                    if (oldRow.getItem() != null) {
//                        textField.textProperty().unbindBidirectional(property.apply((Person)oldRow.getItem()));
//                    }
//                }
//                if (newRow != null) {
//                    newRow.itemProperty().addListener(rowItemListener);
//                    if (newRow.getItem() != null) {
//                        textField.textProperty().bindBidirectional(property.apply((Person)newRow.getItem()));
//                    }
//                }
//            });

            textField.textProperty().bindBidirectional(EasyBind.monadic(cell.tableRowProperty())
                    .selectProperty(TableRow::itemProperty)
                    .selectProperty(p -> (property.apply((Person)p))));

            return cell ;
        });
        return col ;
    }

    public static class Person {
        private final StringProperty firstName = new SimpleStringProperty();
        private final StringProperty lastName = new SimpleStringProperty();
        private final StringProperty email = new SimpleStringProperty();

        public Person(String firstName, String lastName, String email) {
            setFirstName(firstName);
            setLastName(lastName);
            setEmail(email);
        }

        public final StringProperty firstNameProperty() {
            return this.firstName;
        }

        public final java.lang.String getFirstName() {
            return this.firstNameProperty().get();
        }

        public final void setFirstName(final java.lang.String firstName) {
            this.firstNameProperty().set(firstName);
        }

        public final StringProperty lastNameProperty() {
            return this.lastName;
        }

        public final java.lang.String getLastName() {
            return this.lastNameProperty().get();
        }

        public final void setLastName(final java.lang.String lastName) {
            this.lastNameProperty().set(lastName);
        }

        public final StringProperty emailProperty() {
            return this.email;
        }

        public final java.lang.String getEmail() {
            return this.emailProperty().get();
        }

        public final void setEmail(final java.lang.String email) {
            this.emailProperty().set(email);
        }


    }

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

(这里令人讨厌的向下倾斜是因为TableCell<S,T>.getTableRow()返回原始TableRow对象,而不是TableRow<S>,原因我从未理解过。)

答案 1 :(得分:0)

我认为您可以通过实现自定义文本字段tablecell来避免它,您可以在进入编辑模式时手动将插入符号放在项目文本的末尾。

另一种方法是在焦点上进入编辑模式:

table.getFocusModel().focusedCellProperty().addListener(
        ( ObservableValue<? extends TablePosition> observable, TablePosition oldValue, TablePosition newValue ) ->
        {
            if ( newValue != null )
            {
                Platform.runLater( () ->
                        {
                            table.edit( newValue.getRow(), newValue.getTableColumn() );
                } );
            }
        }
);