Search code examples
javagenericsjavafxtableviewtreetableview

CellValueFactory of a TableColumn using a generic Object type?


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)

Solution

  • 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 ;
        });