2017-05-22 2 views
1

J'ai un fil de fond qui met à jour une liste avec les ordres de vente d'achat entrant, les prix etc. Je reçois les données de mon courtier de manière asynchrone. Il est important que les mises à jour de la liste se déroulent dans le thread d'arrière-plan de manière séquentielle.Comment afficher les modifications d'une liste d'un thread d'arrière-plan dans une table JavaFX?

Je veux afficher le mainPortfolioList dans ma table javafx sans le risque d'un "Pas sur le thread d'application FX IllegalStateException". La solution la plus proche que j'ai trouvée était JavaFX refresh TableView thread. Mais cela ne fonctionnera pas si la liste est dans un autre thread, si je comprends bien.

Je suis assez nouveau en java et j'ai essayé de résoudre mon problème avec un addlistener. J'ai fait un exemple simplifié pour montrer ce que je veux et ce que j'ai fait jusqu'ici.

Comment puis-je afficher les mises à jour de mon mainPortfolioList dans la table JavaFX?

PortfolioController

import application.Test; 
import javafx.application.Platform; 
import javafx.collections.FXCollections; 
import javafx.collections.ListChangeListener; 
import javafx.collections.ObservableList; 
import javafx.event.ActionEvent; 
import javafx.fxml.FXML; 
import javafx.scene.control.Button; 
import javafx.scene.control.TableColumn; 
import javafx.scene.control.TableView; 
import javafx.scene.control.cell.TextFieldTableCell; 
import javafx.util.converter.NumberStringConverter; 

public class PortfolioController { 

    @FXML private Button btnTest; 

    @FXML private TableView<Portfolio> tblPortfolio; 
    @FXML private TableColumn<Portfolio, String> colSymbol; 
    @FXML private TableColumn<Portfolio, Number> colQuantity; 
    @FXML private TableColumn<Portfolio, Number> colPrice; 

    private ObservableList<Portfolio> fxPortfolioList; 

    @FXML 
    private void initialize() { 

     tblPortfolio.setEditable(true); 

     colSymbol.setCellValueFactory(data -> data.getValue().colSymbolProperty()); 
     colQuantity.setCellValueFactory(data -> data.getValue().colQuantityProperty()); 
     colPrice.setCellValueFactory(data -> data.getValue().colPriceProperty()); 

     colSymbol.setCellFactory(TextFieldTableCell.forTableColumn()); 
     colQuantity.setCellFactory(TextFieldTableCell.<Portfolio, Number>forTableColumn(
       new NumberStringConverter("#,##0.00"))); 
     colQuantity.setOnEditCommit(event -> { 
      int newValue = event.getNewValue().intValue(); 
      event.getRowValue().setColQuantity(newValue);}); 
     colPrice.setCellFactory(TextFieldTableCell.<Portfolio, Number>forTableColumn(
       new NumberStringConverter("#,##0.00"))); 

     fxPortfolioList = FXCollections.observableArrayList(); 
     tblPortfolio.setItems(fxPortfolioList); 

     Test.mainPortfolioList.addListener((ListChangeListener.Change<? extends Portfolio> c) -> { 
      while (c.next()) { 
       if (c.wasAdded()) { 
        Platform.runLater(() -> { 
         for (Portfolio asset : c.getAddedSubList()) { 
          fxPortfolioList.add(asset); 
         } 
        }); 
       } else if (c.wasRemoved()) { 
        Platform.runLater(() -> { 
         for (Portfolio asset : c.getRemoved()) { 
          fxPortfolioList.remove(asset); 
         } 
        }); 
       } else if (c.wasUpdated()) { 
        Platform.runLater(() -> { 
         for (int i = c.getFrom(); i < c.getTo(); ++i) { 
          fxPortfolioList.set(i, c.getList().get(i)); 
         } 
        }); 
       } 
      } 
     }); 
    } 

    @FXML 
    void btnTestClicked(ActionEvent event) { 
     Test test = new Test(); 
     test.dataStream(this); 
    } 
} 

Portefeuille

import javafx.beans.Observable; 
import javafx.beans.property.DoubleProperty; 
import javafx.beans.property.IntegerProperty; 
import javafx.beans.property.SimpleDoubleProperty; 
import javafx.beans.property.SimpleIntegerProperty; 
import javafx.beans.property.SimpleStringProperty; 
import javafx.beans.property.StringProperty; 
import javafx.util.Callback; 

public class Portfolio { 
    private final StringProperty colSymbol; 
    private final IntegerProperty colQuantity; 
    private final DoubleProperty colPrice; 

    public Portfolio(String symbol, int quantity, double price) { 
     this.colSymbol = new SimpleStringProperty(symbol); 
     this.colQuantity = new SimpleIntegerProperty(quantity); 
     this.colPrice = new SimpleDoubleProperty(price);  
    } 

    // extractor 
    public static Callback<Portfolio, Observable[]> extractor() { 
     return (Portfolio p) -> new Observable[] { 
      p.colSymbolProperty(), 
      p.colQuantityProperty(), 
      p.colPriceProperty(), 
     }; 
    } 

    // property 
    public StringProperty colSymbolProperty() { 
     return colSymbol; 
    } 

    public IntegerProperty colQuantityProperty() { 
     return colQuantity; 
    } 

    public DoubleProperty colPriceProperty() { 
     return colPrice; 
    } 

    // getter 
    public String getColSymbol() { 
     return colSymbol.get(); 
    } 

    public int getColQuantity() { 
     return colQuantity.get(); 
    } 

    public double getColPrice() { 
     return colPrice.get(); 
    } 

    // setter 
    public void setColSymbol(String newValue) { 
     colSymbol.set(newValue); 
    } 

    public void setColQuantity(int newValue) { 
     colQuantity.set(newValue); 
    } 

    public void setColPrice(double newValue) { 
     colPrice.set(newValue); 
    } 
} 

simulation de test

import controller.portfolio.Portfolio; 
import controller.portfolio.PortfolioController; 
import javafx.collections.FXCollections; 
import javafx.collections.ObservableList; 

public class Test { 

    public static ObservableList<Portfolio> mainPortfolioList = 
     FXCollections.observableArrayList(Portfolio.extractor()); 

    public void dataStream(PortfolioController portfolioController) { 

     // Need to be sequentially 
     // The task only simulates simplified operations 
     Runnable task =() -> { 

      // add stock 
      mainPortfolioList.add(new Portfolio("AAPL", 13, 153.03)); 
      mainPortfolioList.add(new Portfolio("MSFT", 31, 67.51)); 

      // Change the quantity 
      for (Portfolio asset : mainPortfolioList) { 
       if (asset.getColSymbol().equals("AAPL")) { 
        asset.setColQuantity(55); 
       } 
      } 

      // run price updates 
      for (int k = 0; k < 15; k++) { 
       for (int m = 0; m < mainPortfolioList.size(); m++) { 
        double random = Math.random() * 50 + 1; 
        String symbol = mainPortfolioList.get(m).getColSymbol(); 
        setTickPrice(symbol, 4, random); 
        randomSleep(); 
       } 
      } 

      // remove stock 
      for (Portfolio asset : mainPortfolioList) { 
       if (asset.getColSymbol().equals("AAPL")) { 
        mainPortfolioList.remove(asset); 
       } 
      } 
     }; 
     Thread t = new Thread(task, "Simulation"); 
     t.setDaemon(true); 
     t.start(); 
    } 

    public void setTickPrice(String symbol, int tickType, double price) { 

     for (Portfolio asset : mainPortfolioList) { 
      if (asset.getColSymbol().equals(symbol)) { 
       switch(tickType){ 
        case 4: // Last Price 
         asset.setColPrice(price); 
         break; 
       } 
      } 
     } 
    } 

    private void randomSleep() { 
     try { 
      Thread.sleep((int)(Math.random() * 300)); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
    } 
} 

principal

import java.io.IOException; 
import javafx.application.Application; 
import javafx.fxml.FXMLLoader; 
import javafx.scene.Parent; 
import javafx.scene.Scene; 
import javafx.stage.Stage; 

public class Main extends Application { 

    @Override 
    public void start(Stage primaryStage) throws IOException { 

     Parent root = FXMLLoader.load(getClass().getResource("/view/Portfolio.fxml")); 
     Scene scene = new Scene(root, 500, 300); 

     primaryStage.setScene(scene); 
     primaryStage.show(); 
    } 

    public static void main(String[] args) {  
     launch(args); 
    } 
} 

vue

<?xml version="1.0" encoding="UTF-8"?> 

<?import javafx.geometry.Insets?> 
<?import javafx.scene.control.Button?> 
<?import javafx.scene.control.TableColumn?> 
<?import javafx.scene.control.TableView?> 
<?import javafx.scene.layout.BorderPane?> 
<?import javafx.scene.layout.HBox?> 

<BorderPane prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.portfolio.PortfolioController"> 
    <top> 
    </top> 
    <center> 
     <TableView fx:id="tblPortfolio" prefHeight="200.0" prefWidth="200.0" tableMenuButtonVisible="true" BorderPane.alignment="CENTER"> 
     <columns> 
      <TableColumn fx:id="colSymbol" maxWidth="80.0" minWidth="60.0" prefWidth="-1.0" text="Symbol" /> 
      <TableColumn fx:id="colQuantity" maxWidth="60.0" minWidth="40.0" prefWidth="-1.0" text="Quantity" /> 
      <TableColumn fx:id="colPrice" maxWidth="69.0" minWidth="49.0" prefWidth="-1.0" text="Price" /> 
     </columns> 
     <columnResizePolicy> 
      <TableView fx:constant="CONSTRAINED_RESIZE_POLICY" /> 
     </columnResizePolicy> 
     </TableView> 
    </center> 
    <bottom> 
    </bottom> 
    <top> 
     <HBox BorderPane.alignment="CENTER"> 
     <children> 
      <Button fx:id="btnTest" mnemonicParsing="false" onAction="#btnTestClicked" text="Test"> 
       <HBox.margin> 
        <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> 
       </HBox.margin> 
      </Button> 
     </children> 
     </HBox> 
    </top> 
</BorderPane> 

Répondre

1

Tout vient du fil de lecture (en supposant IB) qui va changer quelque chose sur l'écran doit être enveloppé dans Platform.runLater.

par ex.

Platform.runLater(() -> asset.setColPrice(price)); 

La même chose pour tous les autres appels, comme ajouter supprimer etc.

Quelle serait plus facile est de savoir si dans l'emballage pour updatePortfolio-vous conclure un nouvel appel à votre modèle de données avec Platform.runLater Updater.

//in wrapper implementation, this call happens on EReader thread. 
void updatePortfolio(Contract contract, int position, double marketPrice, double marketValue, 
     double averageCost, double unrealizedPNL, double realizedPNL, String accountName){ 
    //this moves it to FXApplication thread. 
    Platform.runLater(() -> 
     updateMyPortfolio(contract, position, marketPrice));//etc.. more flds 
} 

De cette façon, vous pouvez utiliser toutes les nouvelles données de la scène sans soucis.

+0

Vous avez raison avec Interactive Brokers. J'espérais éviter ta première solution. Le second est assez intéressant. Je vais essayer ça plus tard :) – akut

+1

Je ne suis pas sûr de ce que vous faites avec chaque méthode d'emballage, mais il y a 2 choses à considérer. 'Platform.runLater' fait tout le traitement sur le thread graphique, donc si c'est long, le thread graphique ne répond plus. Alors, faites seulement le montant minimal. Cependant, un long traitement sur le thread du lecteur risque de bloquer le socket éventuellement. Ce n'est généralement pas un problème sur du matériel moderne. C'est à vous de décider combien faire sur chaque thread, d'équilibrer ces besoins ou de déterminer si vous avez besoin d'un autre thread de calcul dans des circonstances extrêmes. – brian