2013-06-05 13 views
7

Ich wollte wissen, ob es möglich ist, eine ObservableMap zu verwenden, um eine TableView zu füllen? Ich verwende ObservableMap anstelle von ObservableList, weil ich oft hinzufügen und löschen muss, so muss ich die Kosten minimieren.Popup TableView mit ObservableMap JavaFX

Meine hashMap verwendet einen BigInteger als Schlüsselfeld und einen Typ mit vielen Eigenschaften als Wertfeld. In meiner TableView möchte ich nur die Werte mit einer Spalte pro Eigenschaften anzeigen. Ich hoffe, dass

Dank

Antwort

3

klar ist, ich habe versucht, dies zu tun. Ich denke, der Beitrag ist alt, aber ich sehe keine Antworten im Netz. Die Beispiele verwenden den Map-Schlüssel für Spalten und dann eine Liste von Maps für jede Zeile. Ich möchte die Zeilen als Schlüssel und ihre zugehörigen Werte sehen. Es ist ein langes Beispiel.

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(); 
    } 
} 

Und der LineItem Klassencode

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"; 
    } 
} 

Sie nach der Bearbeitung von Daten, und klicken Sie heraus sehen können, dass Änderungen in der Liste in der Karte wiedergegeben werden. Ich muss immer noch den anderen Weg prüfen und Einfügungen und Löschungen handhaben, aber das sollte nicht zu schwer sein.

+0

Das wäre so viel schöner, wenn ObservableMap # entrySet kovariantly überladen wäre, um ein ObservableSet anstelle eines regulären Satzes zurückzugeben. - obwohl dies nicht wahr ist, weil eine Tabellenansicht eine beobachtbare Liste benötigt, und es gibt keinen einfachen Weg, von einer beobachtbaren Menge zu einer beobachtbaren Liste zu gelangen. – Groostav

0

Ich habe meine Map Table-Listener in eine Unterklasse von TableView gepackt.

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); 
    } 

} 

Es scheint so weit zu arbeiten. Ich erstelle mit folgendem Code:

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

Sie können sogar die Schlüssel bearbeiten.

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); 
}); 

Sie können sehen, ich habe eine Methode hinzugefügt, um die Kartenlistener zu entfernen und neu hinzuzufügen. Das Hinzufügen und Entfernen von 100k-Einträgen dauert 0,65 Sekunden ohne Hörer und 5,2 Sekunden mit ihnen.

Hier ist das Ganze in einer Datei auf Pastebin. http://pastebin.com/NmdTURFt