【问题标题】:Populate TableView with ObservableMap JavaFX使用 ObservableMap JavaFX 填充 TableView
【发布时间】:2013-06-01 06:35:06
【问题描述】:

我想知道是否可以使用 ObservableMap 来填充 TableView ? 我使用ObservableMap而不是ObservableList,因为我需要经常添加和删除,所以我需要尽量减少成本。

我的 hashMap 使用 BigInteger 作为键字段,使用具有许多属性的类型作为值字段。 在我的 tableView 中,我只想用每个属性的列显示值。我希望这很清楚

谢谢

【问题讨论】:

    标签: javafx tableview


    【解决方案1】:

    我一直在尝试这样做。我猜这个帖子很旧,但我在网上的任何地方都看不到任何答案。这些示例对列使用映射键,然后对每一行使用映射列表。我希望将行视为键及其关联值。这是一个很长的例子。

    package tablemap;
    
    import static java.lang.Math.random;
    import java.util.Map;
    import java.util.TreeMap;
    import javafx.application.Application;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.event.ActionEvent;
    import javafx.event.EventHandler;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.TableColumn;
    import javafx.scene.control.TableColumn.CellEditEvent;
    import javafx.scene.control.TableView;
    import javafx.scene.control.cell.TextFieldTableCell;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    
    public class TableMap extends Application {
    
        @Override
        public void start(Stage primaryStage) {
            VBox root = new VBox();
            Map<String,LineItem> mapData = new TreeMap<>();
            for (int i = 0; i < 3; i++)
                mapData.put(String.valueOf(random()), new LineItem(String.valueOf(i),"i"));
    
            ObservableList<Map.Entry<String,LineItem>> listData = 
                    FXCollections.observableArrayList(mapData.entrySet());
            TableView<Map.Entry<String,LineItem>> tv = new TableView(listData);
    
            TableColumn<Map.Entry<String,LineItem>,String> keyCol = new TableColumn("Key");
            keyCol.setCellValueFactory(
                (TableColumn.CellDataFeatures<Map.Entry<String,LineItem>, String> p) -> 
                    new SimpleStringProperty(p.getValue().getKey()));
    
            TableColumn<Map.Entry<String,LineItem>,String> lineNoCol = new TableColumn("Line No");
            lineNoCol.setCellValueFactory(
                (TableColumn.CellDataFeatures<Map.Entry<String,LineItem>, String> p) -> 
                    new SimpleStringProperty(p.getValue().getValue().getLineNo()));
    
            TableColumn<Map.Entry<String,LineItem>,String> descCol = new TableColumn("Desc");
            descCol.setCellValueFactory(
                (TableColumn.CellDataFeatures<Map.Entry<String,LineItem>, String> p) -> 
                    new SimpleStringProperty(p.getValue().getValue().getDesc()));
    
            descCol.setCellFactory(TextFieldTableCell.forTableColumn());
    
            descCol.setOnEditCommit((CellEditEvent<Map.Entry<String,LineItem>, String> t) -> {
                 t.getTableView().getItems().get(t.getTablePosition().getRow())
                         .getValue().setDesc(t.getNewValue());
            });
    
            tv.getColumns().addAll(keyCol,lineNoCol, descCol);
            tv.setEditable(true);
            tv.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
    
            Button btnOut = new Button("out");
            btnOut.setOnAction(new EventHandler<ActionEvent>() {
                @Override
                public void handle(ActionEvent t) {
                    for (Map.Entry<String,LineItem> me : mapData.entrySet()){
                        System.out.println("key "+me.getKey()+" entry "+me.getValue().toCSVString());
                    }
                    for (Map.Entry<String,LineItem> me : listData){
                        System.out.println("key "+me.getKey()+" entry "+me.getValue().toCSVString());
                    }
                }
            });
    
            root.getChildren().addAll(tv,btnOut);
            Scene scene = new Scene(root, 300, 200);
    
            primaryStage.setTitle("Map Table Test");
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    }
    

    以及 LineItem 类代码

    package tablemap;
    
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    
    /* LineItem class */
    
    public class LineItem {
        private final StringProperty lineNo = new SimpleStringProperty();
        private final StringProperty desc = new SimpleStringProperty();
    
        public LineItem(String ln, String dsc) {
            lineNo.set(ln); desc.set(dsc);
        }
    
        public String getLineNo() {return (lineNo.getValue() != null) ?lineNo.get():"";}
        public void setLineNo(String lineNo) {this.lineNo.set(lineNo);}
        public StringProperty lineNoProperty() {return lineNo;}
    
        public String getDesc() {return (desc.getValue() != null) ?desc.get():"";}
        public void setDesc(String desc) {this.desc.set(desc);}
        public StringProperty descProperty() {return desc;}
    
        public String toCSVString(){
            return lineNo.getValueSafe()+","+
                    desc.getValueSafe()+"\n"; 
        }
    }
    

    您可以在编辑数据并单击后看到列表中的更改已反映在地图中。我仍然需要检查其他方式并处理插入和删除,但这应该不难。

    【讨论】:

    • 如果 ObservableMap#entrySet 协变重载以返回 ObservableSet 而不是常规集,这会更好。 --尽管不是这样,因为 tableview 需要一个可观察的列表,并且没有简单的方法可以从可观察的集合转到可观察的列表。
    【解决方案2】:

    我将 Map Table 侦听器打包到 TableView 的子类中。

    package tablemap;
    
    import java.util.AbstractMap;
    import java.util.Map;
    import javafx.collections.FXCollections;
    import javafx.collections.ListChangeListener;
    import javafx.collections.MapChangeListener;
    import javafx.collections.ObservableList;
    import javafx.collections.ObservableMap;
    import javafx.scene.control.TableView;
    
    public class MapTableView<K,V> extends TableView<Map.Entry<K,V>>{
        private final ObservableList<Map.Entry<K,V>> obsList;
        private final ObservableMap<K,V> map;
        private final MapChangeListener<K,V> mapChange;
        private final ListChangeListener<Map.Entry<K,V>> listChange;
    
        public MapTableView(ObservableMap<K,V> map) {
            this.map = map;
            obsList = FXCollections.observableArrayList(map.entrySet());
            setItems(obsList);
    
            mapChange = new MapChangeListener<K, V>() {
                @Override
                public void onChanged(MapChangeListener.Change<? extends K, ? extends V> change) {
                    obsList.removeListener(listChange);
                    if (change.wasAdded())
                        obsList.add(new AbstractMap.SimpleEntry(change.getKey(),change.getValueAdded()));
                    if (change.wasRemoved()){
                        //obsList.remove(new AbstractMap.SimpleEntry(change.getKey(),change.getValueRemoved()));
                        // ^ doesn't work always, use loop instead
                        for (Map.Entry<K,V> me : obsList){
                            if (me.getKey().equals(change.getKey())){
                                obsList.remove(me);
                                break;
                            }
                        }
                    }
                    obsList.addListener(listChange);
                }
            };
    
            listChange = (ListChangeListener.Change<? extends Map.Entry<K, V>> change) -> {
                map.removeListener(mapChange);
                while (change.next()){
                    //maybe check for uniqueness here
                    if (change.wasAdded()) for (Map.Entry<K, V> me: change.getAddedSubList())
                        map.put(me.getKey(),me.getValue());
                    if (change.wasRemoved()) for (Map.Entry<K, V> me: change.getRemoved())
                        map.remove(me.getKey());
                }
                map.addListener(mapChange);
            };
    
            map.addListener(mapChange);
            obsList.addListener(listChange);
        }
    
        //adding to list should be unique
        public void addUnique(K key, V value){
            boolean isFound = false;
            //if a duplicate key just change the value
            for (Map.Entry<K,V> me : getItems()){
                if (me.getKey().equals(key)){
                    isFound = true;
                    me.setValue(value);
                    break;//only first match 
                }
            }
            if (!isFound) // add new entry
                getItems().add(new AbstractMap.SimpleEntry<>(key,value));
        }
    
        //for doing lenghty map operations
        public void removeMapListener(){
            map.removeListener(mapChange);
        }
    
        //for resyncing list to map after many changes
        public void resetMapListener(){
            obsList.removeListener(listChange);
              obsList.clear();
              obsList.addAll(map.entrySet());
            obsList.addListener(listChange);
            map.addListener(mapChange);
        }
    
    }
    

    到目前为止,它似乎有效。我使用以下代码创建:

    final ObservableMap<String, LineItem> obsMap = FXCollections.observableHashMap();
    final MapTableView<String,LineItem> mtv = new MapTableView(obsMap);
    

    您甚至可以编辑键。

    final TableColumn<Map.Entry<String,LineItem>,String> keyCol = new TableColumn("Key");
    keyCol.setCellValueFactory(
        (TableColumn.CellDataFeatures<Map.Entry<String,LineItem>, String> p) -> 
            new SimpleStringProperty(p.getValue().getKey()));
    keyCol.setCellFactory(TextFieldTableCell.forTableColumn());
    keyCol.setOnEditCommit((CellEditEvent<Map.Entry<String,LineItem>, String> t) -> {
        final String oldKey = t.getOldValue();
        final LineItem oldLineItem = obsMap.get(oldKey);
        obsMap.remove(oldKey);//should remove from list but maybe doesn't always
        obsMap.put(t.getNewValue(),oldLineItem);
    });
    

    您可以看到我添加了一个删除和重新添加地图侦听器的方法。添加和删​​除 100k 个条目需要 0.65 秒(不带侦听器)和 5.2 秒(不带侦听器)。

    这是 pastebin 上一个文件中的全部内容。 http://pastebin.com/NmdTURFt

    【讨论】:

      猜你喜欢
      • 2016-11-24
      • 1970-01-01
      • 2016-02-16
      • 2021-07-26
      • 2014-10-08
      • 2018-10-14
      • 2013-06-06
      相关资源
      最近更新 更多