Java 将实时数据注入POI电子表格

Java 将实时数据注入POI电子表格,java,apache-poi,vaadin,spreadsheet,Java,Apache Poi,Vaadin,Spreadsheet,我们正在web应用程序中使用Vaadin的电子表格组件。 Vaadin的电子表格组件使用ApachePOI作为底层引擎 使用Apache POI库,我成功地扩展了Vaadin电子表格组件,以支持新的公式类型: =njgetdata(源、元素、筛选器) 该公式从我们的vaadin web应用程序中提取数据,并允许根据返回的数据在电子表格中执行计算 到目前为止,一切顺利 问题是njgetdata返回的值会随着时间的推移而改变(在某些情况下,一秒钟会改变几次) 每当数据改变时,我想强制电子表格重新计算

我们正在web应用程序中使用Vaadin的电子表格组件。 Vaadin的电子表格组件使用ApachePOI作为底层引擎

使用Apache POI库,我成功地扩展了Vaadin电子表格组件,以支持新的公式类型:

=njgetdata(源、元素、筛选器)

该公式从我们的vaadin web应用程序中提取数据,并允许根据返回的数据在电子表格中执行计算

到目前为止,一切顺利

问题是njgetdata返回的值会随着时间的推移而改变(在某些情况下,一秒钟会改变几次)

每当数据改变时,我想强制电子表格重新计算是否有任何单元格依赖于改变的数据

为了提高效率,我猜测我需要确定哪些单元格正在使用njgetdata公式,以及如何告诉这些单元格重新计算公式以获得最新的值


我正在寻找关于如何做到这一点以及如何有效地做到这一点的建议-例如,我不希望电子表格被破坏。

因此,经过大量的麻烦和Gagravarr提供的一些很棒的提示,我解决了这个问题

这是一个完整的例子

基本上,我想为Vaadins电子表格组件创建一个用户定义函数(UDF),该组件为UI提供实时更新

考虑一个电子表格的情况,它希望实时更新股票价格,执行计算并显示结果。每次股价发生变化时,计算应自动更新

一个活的UDF可以让你做到这一点

因此,该示例实现了一个UDF,允许您编写以下表单的电子表格公式: =getprice(“IBM”,“投标”)

getprice UDF将在每次更改时返回IBM的出价

这是三门必修课。 享受吧

package au.com.noojee.dashboard;
导入javax.servlet.annotation.WebServlet;
导入com.vaadin.addon.spreadsheet.spreadsheet;
导入com.vaadin.annotations.Push;
导入com.vaadin.annotations.Theme;
导入com.vaadin.annotations.VaadinServletConfiguration;
导入com.vaadin.annotations.Widgetset;
导入com.vaadin.server.vaadin请求;
导入com.vaadin.server.VaadinServlet;
导入com.vaadin.ui.Component;
导入com.vaadin.ui.ui;
导入com.vaadin.ui.VerticalLayout;
/**
*此UI是应用程序入口点。UI可以表示浏览器
*窗口(或选项卡)或html页面的某个部分,其中包含Vaadin应用程序
*嵌入式。
*
*UI是使用{@link#init(VaadinRequest)}初始化的。这种方法是可行的
*用于覆盖以将组件添加到用户界面和
*初始化非组件功能。
*/
@主题(“仪表板”)
@Widgetset(“au.com.noojee.dashboard.DashboardWidgetset”)
@推
公共类电子表格用户界面扩展用户界面
{
私有静态最终长serialVersionUID=1L;
私人电子表格;
@WebServlet(urlPatterns=“/*”,name=“SpreadsheetUIServlet”,asyncSupported=true)
@VaadinServletConfiguration(ui=SpreadsheetUI.class,productionMode=false)
公共静态类SpreadsheetUIServlet扩展了VaadinServlet
{
私有静态最终长serialVersionUID=1L;
}
@凌驾
受保护的void init(VaadinRequest请求)
{
VerticalLayout布局=新建VerticalLayout();
此.setContent(布局);
layout.setSizeFull();
layout.addComponent(createSpreadsheet());
}
//创建电子表格并注入UDF。
组件创建电子表格()
{
VerticalLayout布局=新建VerticalLayout();
layout.setSizeFull();
电子表格=新电子表格();
电子表格。设置大小();
新数据源().initFormula(电子表格);
布局。添加组件(电子表格);
返回布局;
}
}
包au.com.noojee.dashboard;
导入java.util.HashSet;
导入java.util.Set;
导入java.util.concurrent.Executors;
导入java.util.concurrent.ScheduledExecutorService;
导入java.util.concurrent.TimeUnit;
导入org.apache.poi.ss.formula.functions.FreeRefFunction;
导入org.apache.poi.ss.formula.udf.aggregatingudfinder;
导入org.apache.poi.ss.formula.udf.DefaultUDFFinder;
导入org.apache.poi.ss.formula.udf.UDFFinder;
导入org.apache.poi.ss.usermodel.Cell;
导入org.apache.poi.ss.usermodel.CellStyle;
导入org.apache.poi.ss.usermodel.Font;
导入org.apache.poi.ss.usermodel.FormulaEvaluator;
导入org.apache.poi.ss.util.CellReference;
导入org.apache.poi.xssf.usermodel.xssfont;
导入com.vaadin.addon.spreadsheet.spreadsheet;
导入com.vaadin.ui.ui;
公共类数据源
{
私有静态最终字符串UDFNAME=“getstockprice”;
private Set cellTracker=new HashSet();
私有GetStockPrice公式=新GetStockPrice();
公共公式(电子表格)
{
字符串[]函数名=
{UDFNAME};
FreeRefFunction[]functionImpls=
{公式};
//获取UDF查找器
UDFFinder udfs=新的DefaultUDFFinder(functionNames,functionImpls);
UDFFinder udfToolpack=新的聚集式查找器(udfs);
电子表格.getWorkbook().addToolPack(udfToolpack);
//我们需要跟踪哪些细胞使用我们的UDF,以便知道哪些细胞使用UDF
//每次UDF值更改时刷新。
电子表格。addCellValueChangeListener(事件->{
Set cells=event.getChangedCells();
//一个细胞刚刚发生了变化。
//让我们看看它是否在使用我们的UDF。
用于(单元格参考:单元格)
{
单元格=电子表格.getCell(参考);
if(cell.getCellType()==cell.cell\u TYPE\u公式)
{
//单元格包含一个公式,让我们看看它是否包含我们的公式。
字符串公式=cell.getCellFormula();
if(formula.contains(UDFNAME))
{
//是的,它包含了我们的公式
package au.com.noojee.dashboard;

import javax.servlet.annotation.WebServlet;
import com.vaadin.addon.spreadsheet.Spreadsheet;
import com.vaadin.annotations.Push;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.annotations.Widgetset;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.ui.Component;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;

/**
 * This UI is the application entry point. A UI may either represent     a browser
 * window (or tab) or some part of a html page where a Vaadin     application is
 * embedded.
 * <p>
 * The UI is initialized using {@link #init(VaadinRequest)}. This     method is
 * intended to be overridden to add component to the user interface     and
 * initialize non-component functionality.
 */
@Theme("dashboard")
@Widgetset("au.com.noojee.dashboard.DashboardWidgetset")
@Push
public class SpreadsheetUI extends UI
{
    private static final long serialVersionUID = 1L;
    private Spreadsheet spreadsheet;

    @WebServlet(urlPatterns = "/*", name = "SpreadsheetUIServlet",     asyncSupported = true)
    @VaadinServletConfiguration(ui = SpreadsheetUI.class,     productionMode = false)
    public static class SpreadsheetUIServlet extends VaadinServlet
    {
        private static final long serialVersionUID = 1L;
    }

    @Override
    protected void init(VaadinRequest request)
    {

        VerticalLayout layout = new VerticalLayout();
        this.setContent(layout);

        layout.setSizeFull();

        layout.addComponent(createSpreadsheet());
    }

    // Create the spread sheet and inject the UDF.
    Component createSpreadsheet()
    {
        VerticalLayout layout = new VerticalLayout();
        layout.setSizeFull();
        spreadsheet = new Spreadsheet();
        spreadsheet.setSizeFull();

        new DataSource().initFormula(spreadsheet);
        layout.addComponent(spreadsheet);

        return layout;
    }
}

package au.com.noojee.dashboard;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.poi.ss.formula.functions.FreeRefFunction;
import org.apache.poi.ss.formula.udf.AggregatingUDFFinder;
import org.apache.poi.ss.formula.udf.DefaultUDFFinder;
import org.apache.poi.ss.formula.udf.UDFFinder;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.usermodel.XSSFFont;

import com.vaadin.addon.spreadsheet.Spreadsheet;
import com.vaadin.ui.UI;

public class DataSource
{
    private static final String UDFNAME = "getstockprice";
    private Set<Cell> cellTracker = new HashSet<>();
    private GetStockPrice formula = new GetStockPrice();

    public void initFormula(Spreadsheet spreadsheet)
    {
        String[] functionNames =
        { UDFNAME };
        FreeRefFunction[] functionImpls =
        {  formula };

    // Get the UDF finder
    UDFFinder udfs = new DefaultUDFFinder(functionNames, functionImpls);
    UDFFinder udfToolpack = new AggregatingUDFFinder(udfs);

    spreadsheet.getWorkbook().addToolPack(udfToolpack);

    // We need to track what cells use our UDF so we know which ones
    // to refresh each time the UDF value changes.

    spreadsheet.addCellValueChangeListener(event -> {
        Set<CellReference> cells = event.getChangedCells();

        // A cell has just changed.
        // Lets see if its using our UDF.
        for (CellReference ref : cells)
        {
            Cell cell = spreadsheet.getCell(ref);
            if (cell.getCellType() == Cell.CELL_TYPE_FORMULA)
            {
                // The cell contains a formula so lets see if it contains ours.
                String formula = cell.getCellFormula();
                if (formula.contains(UDFNAME))
                {
                    // Yep it contains our formula
                    // so add it to the tracker.
                    System.out.println("adding" + cell);
                    cellTracker.add(cell);
                }
            }
            else
            {
                // The cell isn't a formula, but it may have been
                // previously so lets ensure we remove it from tracking.
                System.out.println("Removing cell" + cell);
                if (cellTracker.remove(cell) == true)
                    System.out.println("Removed cell" + cell);
            }
            System.out.println(cellTracker.size());
        }

    });


    /**
     * This is not what you want to do!!
     * 
     * Essentially this is a background thread designed to simulate a price change.
     * In reality you would have a link to some external data source that provided 
     * pricing. Each time the price changes you would update the cells
     * that reference that price.
     * Try to be as selective as possible when choosing the cells to update as 
     * the refresh mechanism is expensive.
     */
    ScheduledExecutorService executor =     Executors.newSingleThreadScheduledExecutor();
        executor.scheduleAtFixedRate(new Runnable()
        {
            @Override
            public void run()
            {
                UI.getCurrent().access(new Runnable()
                {

                    @Override
                        public void run()
                    {
                        // simulate a stock price change
                        formula.updatePrice();
                        System.out.println("refresh");

                        // refresh all cells that use the stock     price UDF.
                        spreadsheet.refreshCells(cellTracker);
                    }

                });

            }
        }, 5, 5, TimeUnit.SECONDS);

    }
}


package au.com.noojee.dashboard;

import java.util.Random;

import org.apache.poi.ss.formula.OperationEvaluationContext;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException;
import org.apache.poi.ss.formula.eval.NumberEval;
import org.apache.poi.ss.formula.eval.OperandResolver;
import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.functions.FreeRefFunction;

/**
 * 
 * Method to simulate retrieving stock prices
 */
public class GetStockPrice implements FreeRefFunction
{

    // select a random starting price.
    volatile double  currentPrice = 10.50;

    @Override
    public ValueEval evaluate(ValueEval[] args,     OperationEvaluationContext ec)
    {
        double result = 0;

        if (args.length != 2)
        {
            return ErrorEval.VALUE_INVALID;
        }

        // The stock code that is being monitored
        String stockCode;
        // The price field that is being pulled (e.g. Bid, Last, High, Low etc)
        String priceField;

        try
        {
            ValueEval v1 = OperandResolver.getSingleValue(args[0],     ec.getRowIndex(), ec.getColumnIndex());
            ValueEval v2 = OperandResolver.getSingleValue(args[1],     ec.getRowIndex(), ec.getColumnIndex());

            stockCode = OperandResolver.coerceValueToString(v1);
            priceField = OperandResolver.coerceValueToString(v2);

            result = currentPrice;

            checkValue(result);

        }
        catch (EvaluationException e)
        {
            e.printStackTrace();
            return e.getErrorEval();
        }

        return new NumberEval(result);
    }

    /**
     * Excel does not support infinities and NaNs, rather, it gives     a #NUM!
     * error in these cases
     * 
     * @throws EvaluationException
     *             (#NUM!) if <tt>result</tt> is <tt>NaN</> or     <tt>Infinity</tt>
     */
    final void checkValue(double result) throws EvaluationException
    {
        if (Double.isNaN(result) || Double.isInfinite(result))
        {
            throw new EvaluationException(ErrorEval.NUM_ERROR);
        }
    }

    public void updatePrice()
    {
        this.currentPrice += new Random().nextDouble();

    }

}