在过去的几天里,我浏览了一些解释“如何使用 TableViews?”的 JavaFX 教程。每个教程都会创建数据模型对象,例如
Person
的 example。只要您只需要像 select Firstname, Lastname from Person
这样的 sql 语句就可以了。
但是,如果您不想为每个选择查询创建数据模型,因为您出于某种原因将数据与其他对象连接起来,该怎么办?例如,
select * from Person join City join Country
。我最近几天了解到的是,如果您创建一个仅包含 List
(代表行)的表格视图,那么处理格式化甚至评估表格视图都会变得很糟糕。
这真的是 javaFX 的事情吗?还是我错过了什么?
为了更清楚,请参阅这个问题。我在阅读问题的答案和评论后第一次发现这个问题,但我对链接问题的答案不满意。
你的问题在这里混淆了两个概念:一方面是
javafx.scene.control.TableView
,另一方面是SQL/ORM。让我们忘记 SQL 查询之类的,我认为您主要关心的是 TableView
。
TableView
必须 填充为 List
。该列表的内部类型必须与TableView的通用类型匹配。因此,例如 TableView<Person>
将填充为 List<Person>
。
除此之外,表示行的对象的实际类型可以是anything。它不需要甚至必须包含数据本身,或者它很可能是,例如,一个
Map<String, Object>
。在这种情况下,您将通过为每列定义 CellValueFactory
将行的键映射到每列,这将返回所选键的条目值。
然后您可以通过为列定义
String
将此值转换为 Node
和/或 CellFactory
。
具有 JavaFX 属性的对象可以更轻松地映射,因为存在预制的
PropertyValueFactory
,仅需要属性名称。但它们并不是唯一的出路。
每行使用
Map<String, Object>
的示例:
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.application.Application;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class App extends Application {
private TableView<Map<String, Object>> tableView;
private TableColumn<Map<String, Object>, String> nameCol;
private TableColumn<Map<String, Object>, Integer> ageCol;
private Stage stage;
@Override
public void start(Stage stage) {
this.stage = stage;
//Insert a TableView into scene
this.tableView = new TableView<>();
this.nameCol = new TableColumn<>("Name");
this.nameCol.setPrefWidth(250);
ageCol = new TableColumn<>("Age");
tableView.getColumns().add(nameCol);
tableView.getColumns().add(ageCol);
nameCol.setCellValueFactory(param -> {
Map<String, Object> v = param.getValue();
return new SimpleStringProperty(v == null ? null : (String) v.get("name"));
});
ageCol.setCellValueFactory(param -> {
Map<String, Object> v = param.getValue();
return new SimpleObjectProperty<Integer>(v == null ? null : (Integer) v.get("age"));
});
ageCol.setCellFactory(param -> {
return new TableCell<>() {
@Override
public void updateItem(Integer item, boolean empty) {
if (empty || item == null)
super.setText("");
else
super.setText(NumberFormat.getIntegerInstance().format(item));
//Could also call setGraphic(Node)
}
};
});
final Scene scene = new Scene(new BorderPane(tableView), 640, 480);
stage.setScene(scene);
stage.setOnShown(event -> stageReady()); //Continue when stage is rendered
stage.show();
}
private void stageReady() {
//Generate data
List<Map<String, Object>> data = new ArrayList<>();
Map<String, Object> obj1 = new HashMap<>();
obj1.put("name", "Name of Object 1");
obj1.put("age", 42);
data.add(obj1);
Map<String, Object> obj2 = new HashMap<>();
obj2.put("name", "OBJECT 2");
obj2.put("age", 53);
data.add(obj2);
//Show data
tableView.getItems().setAll(data);
}
public static void main(String[] args) {
launch();
}
}
ageCol.setCellFactory
仅用于演示。如果我没有设置它,那么 Integer
单元格将被渲染,为单元格 Integer.toString()
属性调用 text
。
如here所示,数据库查询可用于构造
ObservableList<Map>
属性值对。如将数据映射添加到表中所示,MapValueFactory
是“Callback
接口的便捷实现”,就像PropertyValueFactory
一样,“专为在TableColumn
内使用而设计。如here 讨论过,便利是有代价的。
受此相关 example 的启发,下面的变体显示了
TableView
,其中包含由 Map<String,String>
返回的 System.getenv()
中包含的条目。尽管该地图不可修改,但显示了简单的编辑支持。
import java.util.Map;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/**
* https://docs.oracle.com/javase/8/javafx/user-interface-tutorial/table-view.htm
* https://stackoverflow.com/a/18620705/230513
* https://stackoverflow.com/a/69781121/230513
* https://stackoverflow.com/a/9134371/230513
*/
public class TableMapSample extends Application {
private final String columnKey = "Key";
private final String columnValue = "Value";
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
ObservableList<Map.Entry<String, String>> list
= FXCollections.observableArrayList(System.getenv().entrySet());
TableView<Map.Entry<String, String>> table = new TableView<>(list);
table.setEditable(true);
table.getSelectionModel().setCellSelectionEnabled(true);
TableColumn<Map.Entry<String, String>, String> keyColumn = new TableColumn<>(columnKey);
keyColumn.setCellValueFactory((TableColumn.CellDataFeatures<Map.Entry<String, String>, String> p)
-> new SimpleStringProperty(p.getValue().getKey()));
table.getColumns().add(keyColumn);
keyColumn.setCellFactory(TextFieldTableCell.forTableColumn());
TableColumn<Map.Entry<String, String>, String> valueColumn = new TableColumn<>(columnValue);
valueColumn.setCellValueFactory((TableColumn.CellDataFeatures<Map.Entry<String, String>, String> p)
-> new SimpleStringProperty(p.getValue().getValue()));
valueColumn.setCellFactory(TextFieldTableCell.forTableColumn());
valueColumn.setPrefWidth(800);
table.getColumns().add(valueColumn);
stage.setTitle("Table Map.Entry<K,V>");
stage.setScene(new Scene(new StackPane(table)));
stage.show();
}
}