我们在网络应用程序中使用Vaadin的Spreadsheet组件。 Vaadin的Spreadsheet组件使用Apache POI作为底层引擎。
使用Apache POI库我已成功扩展Vaadin Spreadsheet组件以支持新的公式类型:
= njgetdata(来源,元素,过滤器)
该公式从我们的vaadin Web应用程序中提取数据,并允许根据返回的数据在电子表格中执行计算。
到目前为止,非常好。
问题是从njgetdata返回的值会随着时间的推移而变化(在某些情况下每秒会变化几次)。
每当数据发生变化时,我想强制电子表格重新计算是否有任何单元格依赖于更改的数据。
为了提高效率,我猜测我需要确定哪些单元格正在使用njgetdata公式,以及如何告诉这些单元格重新计算公式以获取最新值。
我正在寻找有关如何做到这一点的建议并有效地进行 - 例如我不希望电子表格发生颠簸。
答案 0 :(得分:1)
因此,经过Gagravarr的一些琐事和一些很棒的提示,我解决了这个问题。
所以这是一个完整的例子。
基本上我想为Vaadins Spreadsheet组件创建一个用户定义函数(UDF),它为UI提供了实时更新。
考虑一个电子表格的情况,该电子表格想要获取股票价格的实时更新,执行计算并显示结果。每次股票价格变动时,计算都应自动更新。
直播UDF可以让你做到这一点。
因此,该示例实现了一个UDF,允许您编写表单的电子表格公式: = getprice(“IBM”,“BID”)
每次更改时,getprice UDF都会返回IBM的出价。
所以这是所需的三个类。 享受!
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();
}
}