2017-05-22 27 views
1

私は買い手の売り注文、価格などのリストを更新するバックグラウンドスレッドを持っています。私はブローカーから非同期的にデータを取得します。リストの更新はバックグラウンドスレッドで順番に発生することが重要です。JavaFXテーブルのバックグラウンドスレッドからのリストの変更を表示するにはどうすればよいですか?

「Not on FXアプリケーションスレッドIllegalStateException」のリスクなしに、私のjavafxテーブルにmainPortfolioListを表示したいとします。私が見つけた最も近い解決策はJavaFX refresh TableView threadでした。しかし、私が理解しているように、リストが別のスレッドにある場合、これは機能しません。

私はかなりJavaで新しいですし、addlistenerで私の問題を解決しようとしました。私はこれまでに何をしたいのか、私が何をしたのかを示す簡単な例を作った。

メインポートフォリオリストの更新を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); 
    } 
} 

ポートフォリオ

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

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

メイン試験シミュレーション

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); 
    } 
} 
画面上の何かを変更します(IBを想定)、リーダ・スレッドから来る3210

<?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> 

答えて

1

すべてがPlatform.runLaterに包まれなければなりません。

例えば、

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

他のすべてのコールに対して同じに、追加、削除のようになど

容易になるだろう何updatePortfolioのラッパーで、あなただけのPlatform.runLaterを使用してデータモデルアップデータに新しいコールをラップしている場合です。

//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 
} 

このようにして、シーン内のすべての新しいデータを心配無く使用することができます。

+0

あなたはインタラクティブブローカーにぴったりです。私はあなたの最初の解決策を避けることを望みました。 2番目はかなり面白いです。私は後でそれを試みます:) – akut

+1

私はあなたがそれぞれのラッパーメソッドで何をしているのか分かりませんが、考慮すべき2つのことがあります。 'Platform.runLater'はGUIスレッドのすべての処理を行います。長ければ、GUIスレッドは応答しなくなります。したがって、最小限の金額だけを実行してください。しかし、リーダースレッドの処理が長くなると、最終的にソケットをブロックする危険性があります。これは現代のハードウェアでは通常問題ではありません。極端な状況で別の計算スレッドを必要とするかどうかを判断したり、それらのニーズをバランスさせたり、各スレッドでどれくらいのことをするかはあなた次第です。 – brian

関連する問題