使用 Map<String, Map<String, String>> JavaFX 填充 TableView
Populate TableView with Map<String, Map<String, String>> JavaFX
我的脑子已经烧焦了,我找不到在 JavaFX 中填充 TableView 的正确方法。我的数据图是 Map<String, Map<String, String>>
。第一个键是一个状态名,值是一个映射,键是变量,值是变量值。我需要一个像
这样的 table
| States | x | y | ...
| state 1 | 5 | 6 | ...
等等
编辑:这是我的最后一个解决方案,它只填充一列,其他列由相同的数据填充。这可以在另一个具有值的 foreach 中。
for (TableColumn<ObservableList<String>, ?> column : table.getColumns()) {
TableColumn<ObservableList<String>, String> col = (TableColumn<ObservableList<String>, String>) column;
col.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(someValue));
}
我考虑使用类似这样的解决方案,但它仅按最后一个值填充行:
ObservableList<ObservableList<String>> tableData = FXCollections.observableArrayList();
for (Map<String, String> map : map.values()) {
for (Map.Entry<String, String> entry : map.entrySet()) {
if (Utils.getTableColumnByName(table, entry.getKey()) != null) {
TableColumn<ObservableList<String>, String> column = (TableColumn<ObservableList<String>, String>) Utils.getTableColumnByName(table, entry.getKey());
column.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(entry.getValue()));
}
}
}
for (Integer stateIndex : states) {
tableData.add(FXCollections.observableArrayList("state " + stateIndex));
}
table.setItems(tableData);
我只是在寻找任何建议,没有完整的解决方案:)
编辑 2:我在执行开始时仅填充第一行。我不知道执行完成后如何填充另一行。这是在 foreach:
TableColumn<ObservableList<String>, String> varColumn = new TableColumn();
varColumn.setText(variable.getText());
varColumn.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(value.getText()));
table.getColumns().add(varColumn);
在 foreach 之后:
table.setItems(getTableData());
和getTableData():
ObservableList<ObservableList<String>> data = FXCollections.observableArrayList();
for (String row : map.keySet()) {
data.add(FXCollections.observableArrayList(row));
}
return data;
我希望这是清楚的...谢谢!
TableView 的数据结构是 ObservableList<SomeObject>
,这与模型的数据结构不同,后者是 Map<String, Map<String, String>>
。因此,您需要一些方法将模型数据结构转换为可在 TableView 中使用的 ObservableList。
我能想到的几种方法是:
- 创建一组进入列表的虚拟对象,每行一个,对应于模型中的真实项目,并提供单元格值工厂,动态地从模型中提取您需要的数据。
- 创建一个并行的 ObservableList 数据结构并根据需要在模型和 ObservableList 之间同步底层数据。
上面的选项2是我在这里提供的示例。它是一种MVVM(模型、视图、视图模型)架构方法。模型是您的基础 map-based 结构,视图模型是视图(即 TableView)使用的可观察列表。
这是一个示例。
import javafx.application.Application;
import javafx.collections.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
public class StateView extends Application {
@Override
public void start(Stage stage) {
ObservableMap<String, ObservableMap<String, String>> states = populateStates();
final TableView<StateItem> tableView = new TableView<>();
tableView.setItems(extractItems(states));
final TableColumn<StateItem, String> stateCol = new TableColumn<>("State");
final TableColumn<StateItem, String> variableCol = new TableColumn<>("Variable");
final TableColumn<StateItem, String> valueCol = new TableColumn<>("Value");
stateCol.setCellValueFactory(new PropertyValueFactory<>("stateName"));
variableCol.setCellValueFactory(new PropertyValueFactory<>("variableName"));
valueCol.setCellValueFactory(new PropertyValueFactory<>("variableValue"));
tableView.getColumns().setAll(stateCol, variableCol, valueCol);
states.addListener((MapChangeListener<String, ObservableMap<String, String>>) change ->
tableView.setItems(extractItems(states))
);
Scene scene = new Scene(tableView);
stage.setScene(scene);
stage.show();
}
private ObservableList<StateItem> extractItems(ObservableMap<String, ObservableMap<String, String>> states) {
return FXCollections.observableArrayList(
states.keySet().stream().sorted().flatMap(state -> {
Map<String, String> variables = states.get(state);
return variables.keySet().stream().sorted().map(
variableName -> {
String variableValue = variables.get(variableName);
return new StateItem(state, variableName, variableValue);
}
);
}).collect(Collectors.toList())
);
}
private static final Random random = new Random(42);
private static final String[] variableNames = { "red", "green", "blue", "yellow" };
private ObservableMap<String, ObservableMap<String, String>> populateStates() {
ObservableMap<String, ObservableMap<String, String>> states = FXCollections.observableHashMap();
for (int i = 0; i < 5; i ++) {
ObservableMap<String, String> variables = FXCollections.observableHashMap();
for (String variableName: variableNames) {
variables.put(variableName, random.nextInt(255) + "");
}
states.put("state " + i, variables);
}
return states;
}
public static void main(String[] args) {
launch(args);
}
public static class StateItem {
private String stateName;
private String variableName;
private String variableValue;
public StateItem(String stateName, String variableName, String variableValue) {
this.stateName = stateName;
this.variableName = variableName;
this.variableValue = variableValue;
}
public String getStateName() {
return stateName;
}
public void setStateName(String stateName) {
this.stateName = stateName;
}
public String getVariableName() {
return variableName;
}
public void setVariableName(String variableName) {
this.variableName = variableName;
}
public String getVariableValue() {
return variableValue;
}
public void setVariableValue(String variableValue) {
this.variableValue = variableValue;
}
}
}
我所做的是提供一个新的 StateItem class,它提供给视图模型的可观察列表,并包含用于 table 的每一行的 stateName、variableName 和 variableValue 值。有一个单独的提取函数,它从模型映射中提取数据并根据需要填充视图模型可观察列表。
"as needed" 对您意味着什么取决于您需要完成什么。如果您只需要在初始化时填充数据 up-front,则只需一次调用即可将数据提取到视图模型。
如果您需要视图模型根据基础数据的变化动态变化,那么您需要:
- 执行一些值从视图模型到模型的绑定或
- 为模型的更改添加一些侦听器,然后使用这些侦听器更新视图模型或
- 确保在基础模型发生变化时直接调用更新视图模型。
对于示例,我提供了一个基于侦听器的方法示例。我将基础模型 class 从 Map<String, Map<String, String>
更改为 ObservableMap<String, ObservableMap<String, String>>
然后使用 MapChangeListener
来监听最外层 ObservableMap 的变化(在你的情况下这将对应于添加一个全新的状态或删除现有状态)。
如果您需要在两个结构之间保持额外的同步性,例如动态反映变量的添加或删除,或者变量或状态的重命名或变量值的更新,那么您需要为inner-most ObservableMap 正在维护您的变量列表。您可能还会将类型从 String 更改为 StringProperty,以便您可以将模型视图 StateItem class 中的值绑定到模型中的值,并且您还将 property accessors 添加到 StateItem class.
无论如何,上述代码不太可能完全解决您的问题,但可能有助于更好地理解您可能希望评估以解决问题的潜在方法。
顺便说一句,也许使用 TreeTableView 比 TableView 更适合您的实现。就看你的需求了。
谢谢大家!我做到了! JavaFX 中的表格非常烦人,但我的解决方案是为任何需要它的人准备的:)
public class Row {
private String state;
private String[] values;
public Row(String state, String... values) {
this.state = state;
this.values = values;
}
public String getState() {
return state;
}
public List<String> getValues() {
return Arrays.asList(values);
}
设置列:
column.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(data.getValue().getValues().get(index)));
从地图获取数据:
public ObservableList<Row> getTableData() {
ObservableList<Row> data = FXCollections.observableArrayList();
for (Map.Entry<String, TreeMap<String, String>> entry : map.entrySet()) {
String[] values = new String[entry.getValue().values().size()];
int index = 0;
for (String value : entry.getValue().values()) {
values[index] = value;
index++;
}
Row row = new Row(entry.getKey(), values);
data.add(row);
}
return data;
}
最后:
table.setItems(getTableData());
Table with expected output
当然它需要一些具有未定义值的修复等等,但它最终起作用了:)
我的脑子已经烧焦了,我找不到在 JavaFX 中填充 TableView 的正确方法。我的数据图是 Map<String, Map<String, String>>
。第一个键是一个状态名,值是一个映射,键是变量,值是变量值。我需要一个像
| States | x | y | ...
| state 1 | 5 | 6 | ...
等等
编辑:这是我的最后一个解决方案,它只填充一列,其他列由相同的数据填充。这可以在另一个具有值的 foreach 中。
for (TableColumn<ObservableList<String>, ?> column : table.getColumns()) {
TableColumn<ObservableList<String>, String> col = (TableColumn<ObservableList<String>, String>) column;
col.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(someValue));
}
我考虑使用类似这样的解决方案,但它仅按最后一个值填充行:
ObservableList<ObservableList<String>> tableData = FXCollections.observableArrayList();
for (Map<String, String> map : map.values()) {
for (Map.Entry<String, String> entry : map.entrySet()) {
if (Utils.getTableColumnByName(table, entry.getKey()) != null) {
TableColumn<ObservableList<String>, String> column = (TableColumn<ObservableList<String>, String>) Utils.getTableColumnByName(table, entry.getKey());
column.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(entry.getValue()));
}
}
}
for (Integer stateIndex : states) {
tableData.add(FXCollections.observableArrayList("state " + stateIndex));
}
table.setItems(tableData);
我只是在寻找任何建议,没有完整的解决方案:)
编辑 2:我在执行开始时仅填充第一行。我不知道执行完成后如何填充另一行。这是在 foreach:
TableColumn<ObservableList<String>, String> varColumn = new TableColumn();
varColumn.setText(variable.getText());
varColumn.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(value.getText()));
table.getColumns().add(varColumn);
在 foreach 之后:
table.setItems(getTableData());
和getTableData():
ObservableList<ObservableList<String>> data = FXCollections.observableArrayList();
for (String row : map.keySet()) {
data.add(FXCollections.observableArrayList(row));
}
return data;
我希望这是清楚的...谢谢!
TableView 的数据结构是 ObservableList<SomeObject>
,这与模型的数据结构不同,后者是 Map<String, Map<String, String>>
。因此,您需要一些方法将模型数据结构转换为可在 TableView 中使用的 ObservableList。
我能想到的几种方法是:
- 创建一组进入列表的虚拟对象,每行一个,对应于模型中的真实项目,并提供单元格值工厂,动态地从模型中提取您需要的数据。
- 创建一个并行的 ObservableList 数据结构并根据需要在模型和 ObservableList 之间同步底层数据。
上面的选项2是我在这里提供的示例。它是一种MVVM(模型、视图、视图模型)架构方法。模型是您的基础 map-based 结构,视图模型是视图(即 TableView)使用的可观察列表。
这是一个示例。
import javafx.application.Application;
import javafx.collections.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
public class StateView extends Application {
@Override
public void start(Stage stage) {
ObservableMap<String, ObservableMap<String, String>> states = populateStates();
final TableView<StateItem> tableView = new TableView<>();
tableView.setItems(extractItems(states));
final TableColumn<StateItem, String> stateCol = new TableColumn<>("State");
final TableColumn<StateItem, String> variableCol = new TableColumn<>("Variable");
final TableColumn<StateItem, String> valueCol = new TableColumn<>("Value");
stateCol.setCellValueFactory(new PropertyValueFactory<>("stateName"));
variableCol.setCellValueFactory(new PropertyValueFactory<>("variableName"));
valueCol.setCellValueFactory(new PropertyValueFactory<>("variableValue"));
tableView.getColumns().setAll(stateCol, variableCol, valueCol);
states.addListener((MapChangeListener<String, ObservableMap<String, String>>) change ->
tableView.setItems(extractItems(states))
);
Scene scene = new Scene(tableView);
stage.setScene(scene);
stage.show();
}
private ObservableList<StateItem> extractItems(ObservableMap<String, ObservableMap<String, String>> states) {
return FXCollections.observableArrayList(
states.keySet().stream().sorted().flatMap(state -> {
Map<String, String> variables = states.get(state);
return variables.keySet().stream().sorted().map(
variableName -> {
String variableValue = variables.get(variableName);
return new StateItem(state, variableName, variableValue);
}
);
}).collect(Collectors.toList())
);
}
private static final Random random = new Random(42);
private static final String[] variableNames = { "red", "green", "blue", "yellow" };
private ObservableMap<String, ObservableMap<String, String>> populateStates() {
ObservableMap<String, ObservableMap<String, String>> states = FXCollections.observableHashMap();
for (int i = 0; i < 5; i ++) {
ObservableMap<String, String> variables = FXCollections.observableHashMap();
for (String variableName: variableNames) {
variables.put(variableName, random.nextInt(255) + "");
}
states.put("state " + i, variables);
}
return states;
}
public static void main(String[] args) {
launch(args);
}
public static class StateItem {
private String stateName;
private String variableName;
private String variableValue;
public StateItem(String stateName, String variableName, String variableValue) {
this.stateName = stateName;
this.variableName = variableName;
this.variableValue = variableValue;
}
public String getStateName() {
return stateName;
}
public void setStateName(String stateName) {
this.stateName = stateName;
}
public String getVariableName() {
return variableName;
}
public void setVariableName(String variableName) {
this.variableName = variableName;
}
public String getVariableValue() {
return variableValue;
}
public void setVariableValue(String variableValue) {
this.variableValue = variableValue;
}
}
}
我所做的是提供一个新的 StateItem class,它提供给视图模型的可观察列表,并包含用于 table 的每一行的 stateName、variableName 和 variableValue 值。有一个单独的提取函数,它从模型映射中提取数据并根据需要填充视图模型可观察列表。
"as needed" 对您意味着什么取决于您需要完成什么。如果您只需要在初始化时填充数据 up-front,则只需一次调用即可将数据提取到视图模型。
如果您需要视图模型根据基础数据的变化动态变化,那么您需要:
- 执行一些值从视图模型到模型的绑定或
- 为模型的更改添加一些侦听器,然后使用这些侦听器更新视图模型或
- 确保在基础模型发生变化时直接调用更新视图模型。
对于示例,我提供了一个基于侦听器的方法示例。我将基础模型 class 从 Map<String, Map<String, String>
更改为 ObservableMap<String, ObservableMap<String, String>>
然后使用 MapChangeListener
来监听最外层 ObservableMap 的变化(在你的情况下这将对应于添加一个全新的状态或删除现有状态)。
如果您需要在两个结构之间保持额外的同步性,例如动态反映变量的添加或删除,或者变量或状态的重命名或变量值的更新,那么您需要为inner-most ObservableMap 正在维护您的变量列表。您可能还会将类型从 String 更改为 StringProperty,以便您可以将模型视图 StateItem class 中的值绑定到模型中的值,并且您还将 property accessors 添加到 StateItem class.
无论如何,上述代码不太可能完全解决您的问题,但可能有助于更好地理解您可能希望评估以解决问题的潜在方法。
顺便说一句,也许使用 TreeTableView 比 TableView 更适合您的实现。就看你的需求了。
谢谢大家!我做到了! JavaFX 中的表格非常烦人,但我的解决方案是为任何需要它的人准备的:)
public class Row {
private String state;
private String[] values;
public Row(String state, String... values) {
this.state = state;
this.values = values;
}
public String getState() {
return state;
}
public List<String> getValues() {
return Arrays.asList(values);
}
设置列:
column.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(data.getValue().getValues().get(index)));
从地图获取数据:
public ObservableList<Row> getTableData() {
ObservableList<Row> data = FXCollections.observableArrayList();
for (Map.Entry<String, TreeMap<String, String>> entry : map.entrySet()) {
String[] values = new String[entry.getValue().values().size()];
int index = 0;
for (String value : entry.getValue().values()) {
values[index] = value;
index++;
}
Row row = new Row(entry.getKey(), values);
data.add(row);
}
return data;
}
最后:
table.setItems(getTableData());
Table with expected output
当然它需要一些具有未定义值的修复等等,但它最终起作用了:)