Scala中的JavaFX 2和setCellValueFactory()?

时间:2012-10-07 18:27:35

标签: java scala javafx-2 javafx

我正在尝试遵循此JavaFX 2指南:

http://docs.oracle.com/javafx/2/ui_controls/table-view.htm#CJAGAAEE

我使用Scala而不是Java,对我来说它看起来像这样:

<TableView fx:id="test">
     <columns>
         <TableColumn prefWidth="75.0" text="Message" />
     </columns>
</TableView>

代码:

val c = test.getColumns.get(0) // Get the first column.
c.setCellValueFactory(new PropertyValueFactory[Foo, _]("message")) // Foo is my model with a single SimpleStringProperty called "message".

val observableFoos = FXCollections.observableList(foos)
test.setItems(observableFoos)

我遇到的问题是setCellValueFactory行导致:

  

错误:需要类类型但是   javafx.scene.control.cell.PropertyValueFactory [com.myapp.models.Foo,   _]找到c.setCellValueFactory(new PropertyValueFactoryFoo,_)

我不明白我应该如何使用这种方法。如果我将_替换为String,那么我会得到:

  

错误:类型不匹配;发现:   javafx.scene.control.cell.PropertyValueFactory [com.myapp.models.Foo,字符串]   需要:   javafx.util.Callback [javafx.scene.control.TableColumn.CellDataFeatures [com.myapp.models.Foo,?0],javafx.beans.value.ObservableValue [?0]]   where?0 c.setCellValueFactory(new PropertyValueFactoryFoo,   字符串)

如果我删除setCellValueFactory行,我可以确认一切正常 - 我只是没有看到表格中的任何内容 - 只是按预期显示空行..

2 个答案:

答案 0 :(得分:5)

TL; DR:在java中,很容易在类型参数方面默默地绕过类型安全。 除了执行显式演员之外,Scala不会让你这样做。 此外,使用Table.getColumns检索列意味着我们失去了单元格类型。 解决方案:将您的TableColumn实例转换为正确的类型,或手动实例化该列。

第一个问题在于Table.getColumns的签名:它返回ObservableList[TableColumn[S,_]](请参阅http://docs.oracle.com/javafx/2/api/javafx/scene/control/TableView.html#getColumns())。 因此,c val的输入为TableColumn[Foo,_]。 这意味着您将丢失单元格内容的类型(由第二个类型参数表示)。

这里唯一的解决方案是使用强制转换:

正确键入列(c
val c = test.getColumns.get(0).asInstanceOf[TableColumn[Foo, String]]
c.setCellValueFactory(new PropertyValueFactory[Foo, String]("message"))

这是非常合乎逻辑的:您有一个列列表,其中每列可以包含不同类型的对象。我们将面临同样的困境,scala List包含不同类型的对象,所有这些都是先验未知的:只有一个强制转换(或类型上的匹配,这是一个伪装的强制转换)将让你在编译时返回一个特定的类型时间。

另请注意,在Web上的许多JavaFx示例中,人们不会通过Table.getColumns检索列。相反,他们手动实例化它们,然后立即调用setCellValueFactory。 这样做可以解决您的问题(无需任何演员),因为您将自己指定类型参数:

val c = new TableColumn[Foo, String] // See ma, no cast!
c.setCellValueFactory(new PropertyValueFactory[Foo, String]("message"))

现在,您可以查看一些Java示例,并注意到在实例化TableColumn时它们实际上并未提供任何类型参数:

TableColumn c  = new TableColumn();    
c.setCellValueFactory( new PropertyValueFactory<Foo,String>("name"));

确实它确实编译了。怎么会这样?令人遗憾的事实是,上面的代码并不比执行转换更安全,因为它依赖于java对预先通用的感知类的向后兼容性。 换句话说,因为c仅通过示例键入为TableColumn而不是TableColumn<?, String>,因此java编译器将其视为非泛型类,而不执行任何关于类型参数的类型检查TableColumn。 将此与您明确将c的类型设置为泛型类型(但具有unknwon单元格类型)时所发生的情况进行对比:

TableColumn<Foo, ?> name  = new TableColumn("Name");
name.setCellValueFactory( new PropertyValueFactory<Room,String>("name"));

在这种情况下,编译器会发出类型不匹配错误:

error: method setCellValueFactory in class TableColumn<S,T> cannot be applied to given types;
...

这就像在scala中输入c作为TableColumn [Foo,_]

一样

答案 1 :(得分:1)

我建议尝试使用PropertyValueFactory的长格式:
(对不起,我不熟悉Scala所以用Java写的)

 c.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Foo, String>,
             ObservableValue<String>>() {
     public ObservableValue<String> call(TableColumn.CellDataFeatures<Foo, String> f) {
         // f.getValue() returns the Foo instance for a particular TableView row
         return f.getValue().messageProperty();
         // or
         return new SimpleStringProperty(p.getValue().getMessage());
         // if you omit messageProperty() in Foo model class.
         // However omitting it, causes the property listener could not be attached resulting 
         // unable to refresh (synchronize) tableview row item value when the backing 
         // Foo's message value is changed.
     }
  });

请注意,您的Foo模型必须类似于:

public class Foo {
    private SimpleStringProperty message;

    public SimpleStringProperty messageProperty() {
        return message;
    }

    public String getMessage() {
        return message.get();
    }

    public void setMessage(String message) {
        this.message.set(message);
    }
}