我刚启动JavaFx,我有点卡在TableView,它显示非常长的字符串表示每个列,如:
StringProperty [bean: com.plcsim2.PlcSimModel$ErpSheet@2c1ffe7b, name:name, value: big]
IntegerProperty [bean: com.plcsim2.PlcSimModel$ErpSheet@2c1ffe7b, name:sheet_long, value: 5000]
IntegerProperty [bean: com.plcsim2.PlcSimModel$ErpSheet@2c1ffe7b, name:sheet_short, value: 3000]
而我只期待"big", "5000", "3000"显示在单元格中。
这是我的模型:object PlcSimModel {
class ErpSheet {
val name = SimpleStringProperty(this, "name")
val sheet_long = SimpleIntegerProperty(this, "sheet_long")
val sheet_short = SimpleIntegerProperty(this, "sheet_short")
}
val erpSheets = ArrayList<ErpSheet>()
}
fxml:
<VBox alignment="CENTER" prefHeight="562.0" prefWidth="812.0" spacing="20.0"
xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.plcsim2.PlcSimController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
<TableView fx:id="table_1" prefHeight="400.0" prefWidth="200.0">
</TableView>
<Button onAction="#onHelloButtonClick" text="Hello!" />
</VBox>
最后是控制器:
@FXML
private fun onHelloButtonClick() {
val rs = DB.populateSql("select name, sheet_long, sheet_short from erp_sheet")
PlcSimModel.erpSheets.clear()
if (rs != null) {
while (rs.next()) {
val sheet = PlcSimModel.ErpSheet()
sheet.name.set(rs.getString("name"))
sheet.sheet_long.set(rs.getInt("sheet_long"))
sheet.sheet_short.set(rs.getInt("sheet_short"))
PlcSimModel.erpSheets.add(sheet)
}
}
table_1.columns.clear()
val col0 = TableColumn<PlcSimModel.ErpSheet, String>("name")
col0.cellValueFactory = PropertyValueFactory("name")
table_1.columns.add(col0)
val col1 = TableColumn<PlcSimModel.ErpSheet, Int>("sheet_long")
col1.cellValueFactory = PropertyValueFactory("sheet_long")
table_1.columns.add(col1)
val col2 = TableColumn<PlcSimModel.ErpSheet, Int>("sheet_short")
col2.cellValueFactory = PropertyValueFactory("sheet_short")
table_1.columns.add(col2)
table_1.items = FXCollections.observableArrayList(PlcSimModel.erpSheets)
}
似乎控制器是好的,它能够从数据库中获取值,并将行添加到TableView,但为什么TableView显示属性对象的字符串表示,而不是只是显示值?
非常感谢!
JavaFX Properties
当一个类公开JavaFX属性时,它应该遵循以下模式:
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty
public class Foo {
// a field holding the property
private final StringProperty name = new SimpleStringProperty(this, "name");
// a setter method (but ONLY if the property is writable)
public final void setName(String name) {
this.name.set(name);
}
// a getter method
public final String getName() {
return name.get();
}
// a "property getter" method
public final StringProperty nameProperty() {
return name;
}
}
注意属性的名称是name
,以及如何在getter、setter和属性getter方法的名称中使用它。方法名必须遵循该格式。
PropertyValueFactory
类使用反射来获得所需的属性。它依赖于上面描述的方法命名模式。您的ErpSheet
类不遵循上述模式。隐式getter方法(而不是属性getter方法)返回属性对象,而不是属性的值。
芬兰湾的科特林,JavaFX特性h1> otlin不能很好地处理JavaFX属性。您需要创建两个Kotlin属性,一个用于JavaFX属性对象,另一个作为JavaFX属性的值的委托(手动或通过by
关键字)。下面是一个例子:
import javafx.beans.property.IntegerProperty
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty
class Person(name: String = "", age: Int = 0) {
@get:JvmName("nameProperty")
val nameProperty: StringProperty = SimpleStringProperty(this, "name", name)
var name: String
get() = nameProperty.get()
set(value) = nameProperty.set(value)
@get:JvmName("ageProperty")
val ageProperty: IntegerProperty = SimpleIntegerProperty(this, "age", age)
var age: Int
get() = ageProperty.get()
set(value) = ageProperty.set(value)
}
您可以看到,例如,name
Kotlin属性将其getter和setter委托给nameProperty
Kotlin属性。
@get:JvmName("nameProperty")
注释对于Kotlin生成正确的"属性getter"是必需的。方法(JVM字节码)。如果没有该注释,getter将被命名为getNameProperty()
,这与JavaFX属性的模式不匹配。如果你从不打算使用Java中的Kotlin代码,或者使用任何依赖反射的类(例如,PropertyValueFactory
)来获得属性,那么你可以不使用注释。
如果你想使用by
关键字,而不是手动编写getter和setter(例如,var name: String by nameProperty
),请参阅Kotlin文档中的委托属性。您可以为ObservableValue
/WritableValue
(以及ObservableIntegerValue
/WritableIntegerValue
等)编写扩展函数来实现此功能。
可运行示例
下面是一个使用上述Person
类的可运行示例。它还定期增加每个Person
的年龄,以便您可以看到TableView
正在观察模型项。
import javafx.animation.PauseTransition
import javafx.application.Application
import javafx.beans.property.IntegerProperty
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty
import javafx.scene.Scene
import javafx.scene.control.TableColumn
import javafx.scene.control.TableView
import javafx.scene.control.cell.PropertyValueFactory
import javafx.stage.Stage
import javafx.util.Duration
fun main(args: Array<String>) = Application.launch(App::class.java, *args)
class App : Application() {
override fun start(primaryStage: Stage) {
val table = TableView<Person>()
table.columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY
table.items.addAll(
Person("John Doe", 35),
Person("Jane Doe", 42)
)
val nameCol = TableColumn<Person, String>("Name")
nameCol.cellValueFactory = PropertyValueFactory("name")
table.columns += nameCol
val ageCol = TableColumn<Person, Number>("Age")
ageCol.cellValueFactory = PropertyValueFactory("age")
table.columns += ageCol
primaryStage.scene = Scene(table, 600.0, 400.0)
primaryStage.show()
PauseTransition(Duration.seconds(1.0)).apply {
setOnFinished {
println("Incrementing age of each person...")
table.items.forEach { person -> person.age += 1 }
playFromStart()
}
play()
}
}
}
class Person(name: String = "", age: Int = 0) {
@get:JvmName("nameProperty")
val nameProperty: StringProperty = SimpleStringProperty(this, "name", name)
var name: String
get() = nameProperty.get()
set(value) = nameProperty.set(value)
@get:JvmName("ageProperty")
val ageProperty: IntegerProperty = SimpleIntegerProperty(this, "age", age)
var age: Int
get() = ageProperty.get()
set(value) = ageProperty.set(value)
}
避免PropertyValueFactory
综上所述,无论您是用Java还是Kotlin编写应用程序,都应该避免使用PropertyValueFactory
。它是在lambda表达式还不是Java的一部分时添加的,以帮助开发人员避免到处编写冗长的匿名类。然而,它有两个缺点:它依赖于反射,更重要的是,您会丢失编译时验证(例如,属性是否实际存在)。
您应该用lambdas替换PropertyValueFactory
的使用。例如,从上面的代码中替换为:
val nameCol = TableColumn<Person, String>("Name")
nameCol.cellValueFactory = PropertyValueFactory("name")
table.columns += nameCol
val ageCol = TableColumn<Person, Number>("Age")
ageCol.cellValueFactory = PropertyValueFactory("age")
table.columns += ageCol
:
val nameCol = TableColumn<Person, String>("Name")
nameCol.setCellValueFactory { it.value.nameProperty }
table.columns += nameCol
val ageCol = TableColumn<Person, Number>("Age")
ageCol.setCellValueFactory { it.value.ageProperty }
table.columns += ageCol
现在我知道PropertyValueFactory使用反射来查找属性。我认为这是定义到IntegerProperty或StringProperty的关键。因此,只需将模型类更改为以下内容即可解决问题:
class ErpSheet {
var name = ""
var sheet_long = 0
var sheet_short = 0
}
成员变量名是PropertyVaueFactory的关键。