代码之家  ›  专栏  ›  技术社区  ›  Shanho

使用Map<String,Map<String,String>>JavaFX填充TableView

  •  0
  • Shanho  · 技术社区  · 8 年前

    我的大脑已经在燃烧,我无法找到在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;
    

    我希望这是清楚的。。。谢谢

    2 回复  |  直到 8 年前
        1
  •  1
  •   jewelsea    8 年前

    TableView的数据结构是 ObservableList<SomeObject> ,这与模型的数据结构不同 Map<String, Map<String, String>> . 因此,您需要某种方法将模型数据结构转换为可在TableView中使用的ObservableList。

    我可以想到以下几种方法:

    1. 创建列表中的一组虚拟对象,每行一个,对应于模型中的真实项目,并提供单元格值工厂,动态地从模型中提取所需的数据。
    2. 创建一个平行的ObservableList数据结构,并根据需要在模型和ObservableList之间同步基础数据。

    以上选项2是我在这里提供的示例。这是一种 MVVM (模型、视图、视图模型)体系结构方法。模型是基于地图的底层结构,视图模型是由视图(即TableView)使用的可观察列表。

    这是一个示例。

    state changes

    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类,该类输入到视图模型的可观察列表中,并包含用于表中每一行的stateName、variableName和variableValue值。有一个单独的提取功能,可以从模型映射中提取数据,并根据需要填充视图模型可观察列表。

    “按需”对你意味着什么将取决于你需要完成什么。如果只需要在初始化时预先填充数据,那么只需调用一次即可将数据提取到视图模型。

    如果需要根据基础数据的更改动态更改视图模型,则需要:

    1. 执行从视图模型到模型或
    2. 添加一些侦听器以更改模型,然后使用这些侦听器更新视图模型或
    3. 确保在基础模型更改时直接调用以更新视图模型。

    对于示例,我提供了一个基于侦听器的方法的示例。我将基础模型类从 Map<String, Map<String, String> ObservableMap<String, ObservableMap<String, String>> 然后使用 MapChangeListener 监听最外层ObservableMap的更改(在您的情况下,这将对应于添加一个全新状态或删除一个现有状态)。

    如果需要在两个结构之间保持额外的同步性,例如动态地反映变量的添加或删除、变量或状态的重命名或变量值的更新,那么需要为维护变量列表的最内部可观察映射应用额外的侦听器。您可能还将类型从String更改为StringProperty,以便可以将模型视图StateItem类中的值绑定到模型中的值,并且还可以添加 property accessors 到StateItem类。

    无论如何,上述代码不太可能完全解决您的问题,但可能有助于更好地理解您可能希望评估以解决问题的潜在方法。

    另一方面,使用TreeTableView可能比使用TableView更好地控制您的实现。取决于你的需要。

        2
  •  0
  •   Shanho    8 年前

    谢谢大家!我做到了!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

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