用于编辑表的 javafx 绑定代码片段对我不起作用

javafx binding for edit tables Code snippet doesn't work for me

我使用此代码片段完成了 table 的编辑提交。 UITableView - Better Editing through Binding?

我的问题是我得到一个

Caused by: java.lang.ClassCastException: javafx.beans.property.ReadOnlyObjectWrapper cannot be cast to javafx.beans.property.SimpleStringProperty

在这一行中:

     SimpleStringProperty sp = (SimpleStringProperty)ov;

我不知道我能做些什么。 我的数据仅使用 SimpleStringProperty 值 class.

完整代码如下:

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TextArea;
import javafx.util.Callback;

import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class Supermain extends Application {



    @Override
    public void start(Stage primaryStage) {

        final TableView<myTextRow> table = new TableView<>();
        table.setEditable(true);
        table.setStyle("-fx-text-wrap: true;");

        //Table columns
        TableColumn<myTextRow, String> clmID = new TableColumn<>("ID");
        clmID.setMinWidth(160);
        clmID.setCellValueFactory(new PropertyValueFactory<>("ID"));


        TableColumn<myTextRow, String> clmtext = new TableColumn<>("Text");
        clmtext.setMinWidth(160);
        clmtext.setCellValueFactory(new PropertyValueFactory<>("text"));
        clmtext.setCellFactory(new TextFieldCellFactory());

        //Add data
        final ObservableList<myTextRow> data = FXCollections.observableArrayList(
                new myTextRow(5, "Lorem"),
                new myTextRow(2, "Ipsum")
        );
        table.setItems(data);
        table.getColumns().addAll(clmID, clmtext);

        HBox hBox = new HBox();
        hBox.setSpacing(5.0);
        hBox.setPadding(new Insets(5, 5, 5, 5));

        Button btn = new Button();
        btn.setText("Get Data");
        btn.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                for (myTextRow data1 : data) {
                    System.out.println("data:"+data1.getText());
                }
            }
        });

        hBox.getChildren().add(btn);


        BorderPane pane = new BorderPane();
        pane.setTop(hBox);
        pane.setCenter(table);
        primaryStage.setScene(new Scene(pane, 640, 480));
        primaryStage.show();

    }

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

    public static class TextFieldCellFactory
            implements Callback<TableColumn<myTextRow, String>, TableCell<myTextRow, String>> {


        @Override
        public TableCell<myTextRow, String> call(TableColumn<myTextRow, String> param) {
            TextFieldCell textFieldCell = new TextFieldCell();
            return textFieldCell;
        }

        public static class TextFieldCell extends TableCell<myTextRow, String> {

            private TextArea textField;
            private StringProperty boundToCurrently = null;


            public TextFieldCell() {
                String strCss;
                // Padding in Text field cell is not wanted - we want the Textfield itself to "be"
                // The cell.  Though, this is aesthetic only.  to each his own.  comment out
                // to revert back.  
                strCss = "-fx-padding: 0;";

                this.setStyle(strCss);

                textField = new TextArea();
                textField.setWrapText(true);
                textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);



                //textField.setPrefHeight(real_lines_height(textField.getText(),this.getWidth(),30,23));
                // 
                // Default style pulled from caspian.css. Used to play around with the inset background colors
                // ---trying to produce a text box without borders
                strCss = ""
                        + //"-fx-background-color: -fx-shadow-highlight-color, -fx-text-box-border, -fx-control-inner-background;" +
                        "-fx-background-color: -fx-control-inner-background;"
                        + //"-fx-background-insets: 0, 1, 2;" +
                        "-fx-background-insets: 0;"
                        + //"-fx-background-radius: 3, 2, 2;" +
                        "-fx-background-radius: 0;"
                        + // "-fx-padding: 3 5 3 5;" +   /*Play with this value to center the text depending on cell height??*/
                        "-fx-padding: 0 0 0 0;"
                        + /*Play with this value to center the text depending on cell height??*/ //"-fx-padding: 0 0 0 0;" +
                        "-fx-prompt-text-fill: derive(-fx-control-inner-background,-30%);"
                        + "-fx-cursor: text;"
                        + "";

                // Focused and hover states should be set in the CSS.  This is just a test
                // to see what happens when we set the style in code
                textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
                    @Override
                    public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                        TextArea tf = (TextArea) getGraphic();
                        // System.out.println(".changed() index : "+  get_ID_from_table_index(getIndex()));

                        String strStyleGotFocus = "-fx-background-color: blue, -fx-text-box-border, -fx-control-inner-background;"
                                + "-fx-background-insets: -0.4, 1, 2;"
                                + "-fx-background-radius: 3.4, 2, 2;";
                        String strStyleLostFocus
                                = //"-fx-background-color: -fx-shadow-highlight-color, -fx-text-box-border, -fx-control-inner-background;" +
                                "-fx-background-color: -fx-control-inner-background;"
                                + //"-fx-background-insets: 0, 1, 2;" +
                                "-fx-background-insets: 0;"
                                + //"-fx-background-radius: 3, 2, 2;" +
                                "-fx-background-radius: 0;"
                                + //"-fx-padding: 3 5 3 5;" +   /**/
                                "-fx-padding: 0 0 0 0;"
                                + /**/ //"-fx-padding: 0 0 0 0;" +
                                "-fx-prompt-text-fill: derive(-fx-control-inner-background,-30%);"
                                + "-fx-cursor: text;"
                                + "";
                        if (newValue.booleanValue()) {
                            tf.setStyle(strStyleGotFocus);
                        } else {
                            tf.setStyle(strStyleLostFocus);
                        }

                        if(!newValue)
                        {                                                                          


                            System.out.println("EDITABLE???? "+isEditing());
                           System.out.println("TEXT:::: "+textField.getText());
                        //    commitEdit(textField.getText());

                        }

                    }
                });
                textField.hoverProperty().addListener(new ChangeListener<Boolean>() {

                    public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {

                        TextArea tf = (TextArea) getGraphic();
                        String strStyleGotHover = "-fx-background-color: derive(blue,90%), -fx-text-box-border, derive(-fx-control-inner-background, 10%);"
                                + "-fx-background-insets: 1, 2.8, 3.8;"
                                + "-fx-background-radius: 3.4, 2, 2;";
                        String strStyleLostHover
                                = //"-fx-background-color: -fx-shadow-highlight-color, -fx-text-box-border, -fx-control-inner-background;" +
                                "-fx-background-color: -fx-control-inner-background;"
                                + //"-fx-background-insets: 0, 1, 2;" +
                                "-fx-background-insets: 0;"
                                + //"-fx-background-radius: 3, 2, 2;" +
                                "-fx-background-radius: 0;"
                                + //"-fx-padding: 3 5 3 5;" +   /**/
                                "-fx-padding: 0 0 0 0;"
                                + "-fx-prompt-text-fill: derive(-fx-control-inner-background,-30%);"
                                + "-fx-cursor: text;"
                                + "";
                        String strStyleHasFocus = "-fx-background-color: blue, -fx-text-box-border, -fx-control-inner-background;"
                                + "-fx-background-insets: -0.4, 1, 2;"
                                + "-fx-background-radius: 3.4, 2, 2;";

                        if (newValue.booleanValue()) {
                            tf.setStyle(strStyleGotHover);



                        } else if (!tf.focusedProperty().get()) {
                            tf.setStyle(strStyleLostHover);
                        } else {
                            tf.setStyle(strStyleHasFocus);
                        }

                    }
                }); 

                textField.textProperty().addListener(e -> {
                    double height = 25;
                    textField.setPrefHeight(height);
                    textField.setMaxHeight(height);


                    //System.out.println("textfield Parent: "+textField.getParent().toString()); 
                });
                textField.setStyle(strCss);
                this.setGraphic(textField);
            }

            @Override
    protected void updateItem(String item, boolean empty) {
      super.updateItem(item, empty);        
      if(!empty) {
        // Show the Text Field
        this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);

        // Retrieve the actual String Property that should be bound to the TextField
        // If the TextField is currently bound to a different StringProperty
        // Unbind the old property and rebind to the new one
       ObservableValue<String> ov = getTableColumn().getCellObservableValue(getIndex());
        SimpleStringProperty sp = (SimpleStringProperty)ov;

        if(this.boundToCurrently==null) {
            this.boundToCurrently = sp;
            this.textField.textProperty().bindBidirectional(sp);
        }
        else {
            if(this.boundToCurrently != sp) {
              this.textField.textProperty().unbindBidirectional(this.boundToCurrently);
              this.boundToCurrently = sp;
              this.textField.textProperty().bindBidirectional(this.boundToCurrently);
            }
        }
        System.out.println("item=" + item + " ObservableValue<String>=" + ov.getValue());
        //this.textField.setText(item);  // No longer need this!!!
      }
      else {
        this.setContentDisplay(ContentDisplay.TEXT_ONLY);
      }
    }

        }


    }

    public class myTextRow {

        private final SimpleIntegerProperty ID;

        private final SimpleStringProperty text;

        public myTextRow(int ID, String text) {

            this.ID = new SimpleIntegerProperty(ID);
            this.text = new SimpleStringProperty(text);

        }

        public void setID(int id) {
            this.ID.set(id);
        }

        public void setText(String text) {
            this.text.set(text);
        }

        public int getID() {
            return ID.get();
        }

        public String getText() {
            return text.get();
        }
    }
}

您的模型 class 缺少 "property accessors"。因此,属性本身不能被 PropertyValueFactory 使用。如 PropertyValueFactory documentation 中所述:

An example of how to use this class is:

TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name"); 
firstNameCol.setCellValueFactory(new PropertyValueFactory<Person,String>("firstName"));   

In this example, the "firstName" string is used as a reference to an assumed firstNameProperty() method in the Person class type (which is the class type of the TableView items list). Additionally, this method must return a Property instance. If a method meeting these requirements is found, then the TableCell is populated with this ObservableValue. In addition, the TableView will automatically add an observer to the returned value, such that any changes fired will be observed by the TableView, resulting in the cell immediately updating.

If no method matching this pattern exists, there is fall-through support for attempting to call get() or is() (that is, getFirstName() or isFirstName() in the example above). If a method matching this pattern exists, the value returned from this method is wrapped in a ReadOnlyObjectWrapper and returned to the TableCell.

最后一段准确描述了您的情况,因为您的模型 class 中没有定义 textProperty()iDProperty() 方法。因此 PropertyValueFactory 为你创建了一个 ReadOnlyObjectWrapper 并且 returns 它,而不是返回实际的 属性 实例。

另请注意,您的 id 列类型有误。 属性 是 IntegerProperty,它是 Property<Number>,而不是 Property<String>。因此,table 列需要是 TableColumn<myTextRow, Number>。这将使使用 table 单元格实现变得相当棘手,因为您需要在文本字段中的字符串和作为 id.

值的整数之间进行转换

不过一般来说,在JavaFX Property pattern之后写class如下:

public class myTextRow {

    private final IntegerProperty id;

    private final StringProperty text;

    public myTextRow(int ID, String text) {

        this.id = new SimpleIntegerProperty(ID);
        this.text = new SimpleStringProperty(text);

    }

    public void setId(int id) {
        this.id.set(id);
    }

    public void setText(String text) {
        this.text.set(text);
    }

    public int getId() {
        return id.get();
    }

    public String getText() {
        return text.get();
    }

    public StringProperty textProperty() {
        return text;
    }

    public IntegerProperty idProperty() {
        return id ;
    }
}

然后您可以将单元格值工厂定义为

TableColumn<myTextRow, Number> clmID = new TableColumn<>("ID");
clmID.setMinWidth(160);
clmID.setCellValueFactory(new PropertyValueFactory<>("id"));


TableColumn<myTextRow, String> clmtext = new TableColumn<>("Text");
clmtext.setMinWidth(160);
clmtext.setCellValueFactory(new PropertyValueFactory<>("text"));

或者,使用 lambda 表达式(这使得代码类型安全并允许编译器检查是否存在正确的方法):

TableColumn<myTextRow, Number> clmID = new TableColumn<>("ID");
clmID.setMinWidth(160);
clmID.setCellValueFactory(cellData -> cellData.getValue().idProperty());


TableColumn<myTextRow, String> clmtext = new TableColumn<>("Text");
clmtext.setMinWidth(160);
clmtext.setCellValueFactory(cellData -> cellData.getValue().textProperty());