Search code examples
javajavafxopenjfx

PropertyValueFactory error on proper getter from model class


Im trying to populate sample javafx TableView from fxml file.

this is my controller method :

public class TestController implements Initializable {


       @FXML private TableView<user> tableView;
        @FXML private TableColumn<user, String> UserId;
        @FXML private TableColumn<user, String> UserName;


        public void initialize(URL location, ResourceBundle resources) {
            UserId.setCellValueFactory(new PropertyValueFactory<user, String>("userId"));
            UserName.setCellValueFactory(new PropertyValueFactory<user, String>("userName"));


            tableView.getItems().setAll(parseUserList());
        }
        private List<user> parseUserList(){

            List<user> l_u = new ArrayList<user>();

            user u = new user();
            u.setUserId(1);
            u.setUserName("test1");
            l_u.add(u);

            u.setUserId(2);
            u.setUserName("test2");
            l_u.add(u);

            u.setUserId(3);
            u.setUserName("test3");
            l_u.add(u);


            return l_u;
        }

}

and the fxml file :

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="369.0" prefWidth="505.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="viewmodel.TestController ">

   <center>
      <TableView fx:id="tableView" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
        <columns>
          <TableColumn prefWidth="75.0" text="UserId" fx:id="UserId"/>
          <TableColumn prefWidth="75.0" text="UserName" fx:id="UserName"/>
        </columns>
      </TableView>
   </center>
</BorderPane>

and finally my model :

package model;

public class user {
    private int userId;

    public int getUserId() { return this.userId; }
    public void setUserId(int userId) { this.userId = userId; }

    private String userName;

    public String getUserName() { return this.userName; }
    public void setUserName(String userName) { this.userName = userName; }
}

now when i try to populate it gives me this error :

Jun 28, 2019 12:17:35 PM javafx.scene.control.cell.PropertyValueFactory getCellDataReflectively
WARNING: Can not retrieve property 'userId' in PropertyValueFactory: javafx.scene.control.cell.PropertyValueFactory@638db851 with provided class type: class model.user
java.lang.RuntimeException: java.lang.IllegalAccessException: module javafx.base cannot access class model.user (in module JavaFXTest) because module JavaFXTest does not open model to javafx.base
    at javafx.base/com.sun.javafx.property.PropertyReference.get(PropertyReference.java:176)

Some articles on SO mentioned that ,the getter and setter property must be Started with Uppercase after get word but that didn't fix the problem.


Solution

  • this is a very good example for "why you should use Callback instead of PropertyValueFactory. At the moment I don't see any reason to use PVF instead of Callback.

    Here you can see one disadvantage which is you don't really see that you did a coding mistake before you run the app. Since PVF works with reflection, it doesn't find the appropriate fields if you don't declare them correctly. The PVF expects Property-es as you can see in the exception a PropertyRefference is needed and none of the fields delcared in your user class are Propertyes

    It can be used this way but you have to rewrite the user class like:

    public class User { // use java naming conventions 
        private IntegerProperty userId;
    
        public int getUserId() { return this.userId.get(); }
        public void setUserId(int userId) { this.userId.set(userId); }
        public IntegerProperty userIdProperty() { return this.userId; }
    
        private StringProperty userName;
    
        public String getUserName() { return this.userName.get(); }
        public void setUserName(String userName) { this.userName.set(userName); }
        public StringProperty userNameProperty() {return this.userName; }
    
        public User(int userId,String userName){
             this.userId = new SimpleIntegerProperty(userId);
             this.userName = new SimpleStringProperty(userName);
        }
    }
    

    Now since the fields are properties the PropertyValueFactory would find them but I don't recommend using it, because as you can see it can lead to problems you din't even notice before you run it.

    So instead of :

    UserId.setCellValueFactory(new PropertyValueFactory<user, String>("userId"));
    UserName.setCellValueFactory(new PropertyValueFactory<user, String>("userName"));
    

    use:

    // use java naming conventions (`userId` instead of `UserId`)
    userId.setCellValueFactory(data -> data.getValue().userIdProperty().asString()); 
    // same here user naming convention
    userName.setCellValueFactory(data -> data.getValue().userNameProperty());
    

    I have wrote as comments a few time, but I will mention it here again to use java naming conventions. Like User instead of user and userId instead of UserId and so on...