1

Error: Type mismatch: cannot convert from SimpleStringProperty to ObservableValue<Object>.

I am trying to create a TreeTableView with a column that manages various data types. This means that each row could use one of three or so types of data ( String, int, StringProperty, or ObjectProperty< LocalDate >.

Given my data-type is "Object" then setCellValueFactory( cellDataFeatures -> { return ...; }) expects an ObservableValue< Object >. I am at a loss trying to get the required ObservableValue out of the Properties for the CellValueFactory callback. :(

This post suggests using a ReadOnlyStringWrapper but I would like to keep to the value editable (in most cases). I've also found suggestions for the #asObject() method which is not available in a StringProperty.

        /*-------------------------+------------+ 
         | columnPeople            | columnData |
         +-------------------------+------------+ 
         |                         |            |
         | + Bob Rozz ============ | 33 ======= |
         |   + SKILLS              |            |
         |     + testskill 1       | 0.7        |
         |                         | 3/14/2017  |
         |     + testskill 2       |            |
         |                         | 0.9        |
         |                         | 3/11/2017  |
         |                         |            |
         | + Bob Dilly =========== | 34 ======= |
         |   + SKILLS              |            |
         |     + testskill 1       | 0.6        |
         |                         | 3/10/2017  |
         |     + testskill 2       |            |
         |                         | 0.5        |
         |                         | 3/17/2017  |
         +-------------------------+------------+ 
        */

Note1: Not tackling the setCellFactory(...) yet.

Note2: As-presented, I realize this example is unpractical. It is completely separate from my project as an example of the desired functionality.

package testGenericTableTreeColumn;

import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleFloatProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.DatePicker;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.TextFieldTreeTableCell;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Pair;
import javafx.util.converter.DefaultStringConverter;



public class testGenericTableTreeColumn extends javafx.application.Application {

    class Skill {

        private StringProperty        name = new SimpleStringProperty( "" );
        public  final String          getName(){ return this.name.get();         }
        public  final void            setName( String v ){ name.set(v); return;  }
        public  StringProperty        nameProperty(){ return this.name;          }

        private SimpleFloatProperty   value = new SimpleFloatProperty( 0f );
        public  final String          getValue(){ return this.name.get();        }
        public  final void            setValue( String v ){ name.set(v); return; }
        public  StringProperty        valueProperty(){ return this.name;         }

        private SimpleObjectProperty< LocalDate > date = new SimpleObjectProperty< LocalDate >( LocalDate.now() );//LocalDate date  = LocalDate.now();
        public  final String          getDate(){ return this.name.get();         }
        public  final void            setDate( String v ){ name.set(v); return;  }
        public  StringProperty        dateProperty(){ return this.name;          }

        public Skill( String name ){
            java.util.Random r = new java.util.Random();
            this.name  .set( name );                        //this.name  = name;
            this.value .set( 100 * ( r.nextFloat() ));      //this.value = 100 * ( r.nextFloat() );
            this.date  .get().minusWeeks( r.nextInt(10) );  //date.minusWeeks( r.nextInt(10) );
            return;
        }
    }
    class Person {

        private StringProperty  name = new SimpleStringProperty( "" );
        public final String     getName()          { return this.name.get(); }
        public final void       setName( String v ){ name.set(v); return;    }
        public StringProperty   nameProperty()     { return this.name;       }

        private IntegerProperty age = new SimpleIntegerProperty( 0 );
        public final int        getAge()           { return this.age.get();  }
        public final void       setAge( int v )    { age.set(v); return;     }
        public IntegerProperty  ageProperty()      { return this.age;        }

        public  List< Skill >   skills = new ArrayList<>();

        public Person( String name, int age ){
            this.name .set( name );
            this.age  .set( age  );
            skills.add( new Skill( "testskill 1" ));
            skills.add( new Skill( "testskill 2" ));
            return;
        }
    }

    public TreeTableView< Pair< Object, Object >> table = new TreeTableView<>();

    @Override public void start( Stage primaryStage ){

        TreeItem< Pair< Object, Object >> itemRoot = new TreeItem<>( new Pair<>( "PEOPLE", null ));
        this.table.setRoot( itemRoot );

        Person person1 = new Person( "Bob Rozz"  ,33 );
        Person person2 = new Person( "Bob Dilly" ,34 );

        List< Person > people = new ArrayList<>();
        people.add( person1 );
        people.add( person2 );

        /*-------------------------+------------+ 
         | columnPeople            | columnData |
         +-------------------------+------------+ 
         |                         |            |
         | + Bob Rozz ============ | 33 ======= |
         |   + SKILLS              |            |
         |     + testskill 1       | 0.7        |
         |                         | 3/14/2017  |
         |     + testskill 2       |            |
         |                         | 0.9        |
         |                         | 3/11/2017  |
         |                         |            |
         | + Bob Dilly =========== | 34 ======= |
         |   + SKILLS              |            |
         |     + testskill 1       | 0.6        |
         |                         | 3/10/2017  |
         |     + testskill 2       |            |
         |                         | 0.5        |
         |                         | 3/17/2017  |
         +-------------------------+------------+ 
        */

        for ( Person person : people ){

            TreeItem< Pair< Object, Object >> treeItemPerson = new TreeItem<>( new Pair< Object, Object >( person.nameProperty(), person.ageProperty() ));
            TreeItem< Pair< Object, Object >> treeItemSkills = new TreeItem<>( new Pair< Object, Object >( "Skills"             , null ));

            itemRoot       .getChildren().add( treeItemPerson );
            treeItemPerson .getChildren().add( treeItemSkills );

            for ( Skill skill : person.skills ){

                TreeItem< Pair< Object, Object >> treeItemSkillName  = new TreeItem<>( new Pair< Object, Object >( null, skill.nameProperty  () ));
                TreeItem< Pair< Object, Object >> treeItemSkillValue = new TreeItem<>( new Pair< Object, Object >( null, skill.valueProperty () ));
                TreeItem< Pair< Object, Object >> treeItemSkillDate  = new TreeItem<>( new Pair< Object, Object >( null, skill.dateProperty  () ));

                treeItemSkills    .getChildren().add( treeItemSkillName  );
                treeItemSkillName .getChildren().add( treeItemSkillDate  );
                treeItemSkillName .getChildren().add( treeItemSkillValue );
            }
        }

        TreeTableColumn< Pair< Object, Object>, String > colName = new TreeTableColumn<>("People"); colName.setMinWidth(100);
        colName.setCellValueFactory( cellDataFeatures -> {

            // Could be a String, StringProperty, or ObjectProperty< LocalDate >
            Object item   = cellDataFeatures.getValue().getValue().getKey();

            //String
            if ( item instanceof String ){
                /* ERROR */ return ( String ) item; //ERROR: Type mismatch: cannot convert from SimpleStringProperty to ObservableValue< Object >
            }

            //StringProperty
            if ( item instanceof StringProperty ){
                /* ERROR */ return (( StringProperty ) item ); //ERROR: Type mismatch: cannot convert from SimpleStringProperty to ObservableValue< Object >
            }
        });

        TreeTableColumn< Pair< Object, Object>, Object > colData = new TreeTableColumn<>("Skills"); colData.setMinWidth(200);
        colData.setCellValueFactory( cellDataFeatures -> {

            //itemKey : Could be a String, IntegerProperty, StringProperty, or ObjectProperty< LocalDate >
            Object item = cellDataFeatures.getValue().getValue().getValue();

            //String
            if ( item instanceof String ){
                /* ERROR */ return (String) item; //ERROR: Type mismatch: cannot convert from SimpleStringProperty to ObservableValue< Object >
            }

            //IntegerProperty
            if ( item instanceof IntegerProperty ){
                /* ERROR */ return (( IntegerProperty ) item ); //ERROR: Type mismatch: cannot convert from SimpleIntegerProperty to ObservableValue< Object >
            }

            //StringProperty
            if ( item instanceof StringProperty ){
                /* ERROR */ return (( StringProperty ) item ); //ERROR: Type mismatch: cannot convert from SimpleStringProperty to ObservableValue< Object >
            }

            //ObjectProperty< LocalDate >
            if ( item instanceof ObjectProperty< ? >){
                Object value = (( ObjectProperty<?> ) item ).getBean();
                if ( value instanceof LocalDate ){
                    //@TODO LocalDate cell
                }
            }
        });

        /*
        //colData.setCellFactory( new Callback< TreeTableColumn< Person, Object >, TreeTableCell< Person, Object >>());
        colData.setCellFactory( column -> {

            TreeTableCell< Object, Object > cell = new TreeTableCell< Object, Object >(){
                @Override protected void updateItem( Object newValue, boolean empty ){

                    this.setEditable( false );

                    super.updateItem( newValue, empty );
                    if ( empty || newValue == null ){
                        setText    ( null );
                        setGraphic ( null );
                        return;
                    }

                    if ( newValue instanceof String ){
                        return;
                    }

                    this.setEditable( true );

                    if ( newValue instanceof LocalDate ){
                        return;
                    }

                    return;
                }   // updateItem( ... );
            };
        });
        // */

        // Type safety: A generic array of Table... is created for a varargs
        // parameter
        // -> @SuppressWarnings("unchecked") to start method!
        table.getColumns().addAll( colName, colData );

        // Output in console the selected table view's cell value/class to check
        // that the data type is correct.
        //  SystemOutTreeTableViewSelectedCell.set(tableView);
        /*
        // To check that table view is correctly refreshed on data changed..
        final Button agePlusOneButton = new Button("Age +1");
        agePlusOneButton.setOnAction((ActionEvent e) -> {
            Person<?> person = tableView.getSelectionModel().getSelectedItem();
            try {
                person.setAge(person.getAge() + 1);
            } catch (NullPointerException npe ){
                //
            }
        });
        */

        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll( table );

        Scene scene = new Scene(new Group());
        ((Group) scene.getRoot()).getChildren().addAll(vbox);

        primaryStage.setWidth(600);
        primaryStage.setHeight(750);

        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

Errors:

Description Resource    Path    Location    Type
Type mismatch: cannot convert from IntegerProperty to ObservableValue<Object>   testGenericTableTreeColumn.java /testGenericTableTreeColumn/src/testGenericTableTreeColumn  line 300    Java Problem
Type mismatch: cannot convert from String to ObservableValue<Object>    testGenericTableTreeColumn.java /testGenericTableTreeColumn/src/testGenericTableTreeColumn  line 295    Java Problem
Type mismatch: cannot convert from String to ObservableValue<String>    testGenericTableTreeColumn.java /testGenericTableTreeColumn/src/testGenericTableTreeColumn  line 278    Java Problem
Type mismatch: cannot convert from StringProperty to ObservableValue<Object>    testGenericTableTreeColumn.java /testGenericTableTreeColumn/src/testGenericTableTreeColumn  line 305    Java Problem

Java Version:

java version "1.8.0_121" Java(TM)
SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
Community
  • 1
  • 1
Kevin J.
  • 87
  • 2
  • 11
  • 1
    Some of these don't generate the compile errors you claim they do, e.g. in `colName` the only error can be fixed by casting `item` to a `String` in the first `if` clause. (The second cast should really be to `StringProperty`, not `SimpleStringProperty`, but it will compile as it stands.) In `colData` you can just return a `new SimpleObjectProperty<>(...)` and pass in the appropriate value: `item.getValue()` if `item` is some kind of property, or just `item` otherwise. – James_D Mar 21 '17 at 18:19
  • Well it wont compile for me. It cant even find the main class because it has errors. java version "1.8.0_121" Java(TM) SE Runtime Environment (build 1.8.0_121-b13) Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode) – Kevin J. Mar 22 '17 at 12:11
  • I tried to make the changes you suggested and updated my first post to reflect that. I also posted the errors I'm getting at the end. Thanks. – Kevin J. Mar 22 '17 at 12:37
  • 1
    Well, under the same JDK, I don't get those same compile errors. However, the fundamental errors you need to fix are that the lambda expressions in your `cellValueFactory`s must return a value under every possible code path. For example, in the factory for `colName`, if `item` is neither a `String` nor a `StringProperty`, you never reach a `return` statement. Furthermore, you need to return the correct type (an `ObservableValue` in the `colName` factory): you cannot return a `String` as you do in the first `if`: do `return new SimpleStringProperty((String)item);`. – James_D Mar 22 '17 at 12:50
  • Probably since the code structure is broken (i.e. you don't reach `return` statements in every code path), the compiler is confused enough that it can't generate the correct error messages in the other cases. – James_D Mar 22 '17 at 12:51
  • Wait: When I cast the returns of `colName.setCellFactory(...)` to StringProperty, it is fine. However this doesnt work for `colData.setCellFactory(...)`. Hmmmmm. Looking into this further.... – Kevin J. Mar 22 '17 at 13:27
  • No, I'm not on windows. It's not a bug anywhere. Your code is not valid, and you're getting compile errors: that is normal, expected, and correct behavior. Different compilers (e.g. using different IDEs) will report errors differently (particularly when there are multiple confounding errors, as in this case). That said, I simply do not believe you in some of the claims. E.g. for the first return statement in the factory for `colName` I get "Type mismatch: cannot convert from String to ObservableValue" in my version of eclipse, not the error you claim it gives. – James_D Mar 22 '17 at 13:27
  • Well, of course it doesn't work for `colData`: `colData` has a different type. Have you read my answer? – James_D Mar 22 '17 at 13:28
  • Yes, I'm not very good at generics. I got that. My original view was that these are all of type "Object" so thats finally out the window. -- Trying to follow your suggestions : So now, only in `colData.setCellValueFactory(...)` am I'm needing to return a type `Object`. You suggested I use `SimpleObjectProperty<>` and I see that does indeed work. – Kevin J. Mar 22 '17 at 14:16
  • 1
    "I am still confused as to where it gets the type from". The compiler just looks at the compile-time type of the value you are returning. The compile-time type of `(String) item` is `String` (basically by definition of what a downcast is). The compile-time type of `(StringProperty) item` is `StringProperty`. Note that `StringProperty` is a subtype of `ObservableValue`, but not of `ObservableValue`. – James_D Mar 22 '17 at 14:20
  • Ha-ha! Thanks again. =) – Kevin J. Mar 22 '17 at 14:33

1 Answers1

2

For a TreeTableColumn<S,T>, the cellValueFactory has type Callback<CellDataFeatures<S,T>, ObservableValue<S,T>>, which is essentially a function taking an instance of CellDataFeatures<S,T> and returning an ObservableValue<S,T>.

Your colName is a TreeTableColumn<Pair<Object, Object>, String>, so S is Pair<Object, Object> and T is String. So the cellValueFactory for colName is a function taking a CellDataFeatures<Pair<Object, Object>> instance (which you call cellDataFeatures) and returning an ObservableValue<String>.

There are multiple problems with your implementation. The first is that if item, which is declared as an Object, is not an instance of either String or StringProperty, then the code never actually reaches a return statement. So this isn't even a valid lambda expression.

The second problem is that if item is a String instance (first if statement), you return a String, which is not an ObservableValue<String>, so you are returning the wrong type.

So an implementation that will at least compile is

    TreeTableColumn< Pair< Object, Object>, String > colName = new TreeTableColumn<>("People"); colName.setMinWidth(100);
    colName.setCellValueFactory( cellDataFeatures -> {

        // Could be a String, StringProperty, or ObjectProperty< LocalDate >
        Object item   = cellDataFeatures.getValue().getValue().getKey();

        //String
        if ( item instanceof String ){
            return new SimpleStringProperty(( String ) item); 
        }

        //StringProperty
        if ( item instanceof StringProperty ){
            return (( StringProperty ) item ); 
        }

        // must return something: you probably don't want to return null though, so you should fix this as needed.
        return null ;
    });

Similarly, for colData, you have code paths that never reach a return statement, so you have not defined a valid lambda expression. In this case, colData is a TreeTableColumn<Pair<Object, Object>, Object>, so T is Object, and the return type must be ObservableValue<Object>. Your various if blocks attempt to return String, IntegerProperty, StringProperty, (or something to be determined), and none of those are instances of ObservableValue<Object>.

An implementation that actually compiles is

    TreeTableColumn< Pair< Object, Object>, Object > colData = new TreeTableColumn<>("Skills"); colData.setMinWidth(200);
    colData.setCellValueFactory( cellDataFeatures -> {

        //itemKey : Could be a String, IntegerProperty, StringProperty, or ObjectProperty< LocalDate >
        Object item = cellDataFeatures.getValue().getValue().getValue();

        //String
        if ( item instanceof String ){
            return new SimpleObjectProperty<>( item ); 
        }

        //IntegerProperty
        if ( item instanceof IntegerProperty ){
            return new SimpleObjectProperty<>(((IntegerProperty) item).getValue()); 
        }

        //StringProperty
        if ( item instanceof StringProperty ){
            return new SimpleObjectProperty<>(((IntegerProperty) item).getValue()); 
        }

        //ObjectProperty< LocalDate >
        if ( item instanceof ObjectProperty< ? >){
            Object value = (( ObjectProperty<?> ) item ).getBean();
            if ( value instanceof LocalDate ){
                //@TODO LocalDate cell
            }
        }

        // TODO return something appropriate here
        return null ;
    });
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thank you James! I am still confused as to where it gets the type from? I would have expected something like `SimpleObjectProperty< Object >` which doesn't work. Why not? – Kevin J. Mar 22 '17 at 14:20
  • I'll also note that, while the compiler asks for an instance of an 'ObservableValue' (an abstract class), the class is is inherited in 'SimpleObjectProperty' very-far down its inherited hierarchy. – Kevin J. Mar 22 '17 at 14:30
  • 1
    `ObservableValue` is an interface, not a class. (So there isn't such a well-defined notion of the depth in the hierarchy anyway.) – James_D Mar 22 '17 at 14:32
  • James answered in the above post: "The compiler just looks at the compile-time type of the value you are returning. The compile-time type of (String) item is String (basically by definition of what a downcast is). The compile-time type of (StringProperty) item is StringProperty. Note that StringProperty is a subtype of ObservableValue, but not of ObservableValue. – James_D " – Kevin J. Mar 22 '17 at 14:35