使用 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。

我能想到的几种方法是:

  1. 创建一组进入列表的虚拟对象,每行一个,对应于模型中的真实项目,并提供单元格值工厂,动态地从模型中提取您需要的数据。
  2. 创建一个并行的 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,则只需一次调用即可将数据提取到视图模型。

如果您需要视图模型根据基础数据的变化动态变化,那么您需要:

  1. 执行一些值从视图模型到模型的绑定或
  2. 为模型的更改添加一些侦听器,然后使用这些侦听器更新视图模型或
  3. 确保在基础模型发生变化时直接调用更新视图模型。

对于示例,我提供了一个基于侦听器的方法示例。我将基础模型 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

当然它需要一些具有未定义值的修复等等,但它最终起作用了:)