1

我有一个后台线程,它使用传入的买卖订单、价格等更新列表。我从我的经纪人那里异步获取数据。列表中的更新顺序发生在后台线程中很重要。

我想在我的 javafx 表中显示 mainPortfolioList,而不会出现“不在 FX 应用程序线程 IllegalStateException”的风险。我找到的最接近的解决方案是JavaFX refresh TableView thread。但是,据我所知,如果列表在另一个线程中,这将不起作用。

我在java中很新,并试图用addlistener解决我的问题。我做了一个简化的例子来展示我想要什么以及到目前为止我做了什么。

如何在 JavaFX 表中显示来自 mainPortfolioList 的更新?

投资组合控制器

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

看法

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

1 回答 1

1

来自阅读器线程(假设是 IB)的所有会改变屏幕内容的东西都必须用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 
}

这样您就可以毫无顾虑地使用场景中的所有新数据。

于 2017-05-22T20:06:23.880 回答