使用通用对象类型的 TableColumn 的 CellValueFactory?

CellValueFactory of a TableColumn using a generic Object type?

错误:类型不匹配:无法从 SimpleStringProperty 转换为 ObservableValue<Object>

我正在尝试创建一个 TreeTableView,其中包含一个管理各种数据类型的列。这意味着每一行可以使用三种左右数据类型中的一种(StringintStringPropertyObjectProperty< LocalDate >

鉴于我的数据类型是 "Object",那么 setCellValueFactory( cellDataFeatures -> { return ...; }) 需要一个 ObservableValue< Object >。我不知所措地试图从 CellValueFactory 回调的属性中获取所需的 ObservableValue。 :(

This post 建议使用 ReadOnlyStringWrapper 但我想保留可编辑的值(在大多数情况下)。我还找到了有关 #asObject() 方法的建议,该方法在 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  |
         +-------------------------+------------+ 
        */

注意 1: 尚未处理 setCellFactory(...)

Note2: 就目前而言,我意识到这个例子是不切实际的。作为所需功能的示例,它完全独立于我的项目。

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

错误:

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版本:

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)

对于 TreeTableColumn<S,T>cellValueFactory 的类型为 Callback<CellDataFeatures<S,T>, ObservableValue<S,T>>,它本质上是一个采用 CellDataFeatures<S,T> 和 return 实例的函数ObservableValue<S,T>.

你的colNameTreeTableColumn<Pair<Object, Object>, String>,所以SPair<Object, Object>TString。所以 colNamecellValueFactory 是一个接受 CellDataFeatures<Pair<Object, Object>> 实例(你称之为 cellDataFeatures)和 returning ObservableValue<String>.[= 的函数49=]

您的实施存在多个问题。第一个是如果声明为 Objectitem 不是 StringStringProperty 的实例,那么代码实际上永远不会到达 [=32] =] 语句。所以这甚至不是一个有效的 lambda 表达式。

第二个问题是如果item是一个String实例(第一个if语句),你return一个String,它不是一个 ObservableValue<String>,所以你 return 输入错误的类型。

所以至少可以编译的实现是

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

同样,对于 colData,您的代码路径永远不会到达 return 语句,因此您没有定义有效的 lambda 表达式。在这种情况下,colData 是一个 TreeTableColumn<Pair<Object, Object>, Object>,因此 TObject,并且 return 类型必须是 ObservableValue<Object>。您的各种 if 块尝试 return StringIntegerPropertyStringProperty(或待定的东西),其中 none 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 ){
            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 ;
    });