Working with CalcEngine in Windows Forms Calculation Engine
24 Aug 202118 minutes to read
CalcEngine encapsulates the code required to parse and compute the formulas. It manages several library functions, hash tables for Essential Calculate.
All the data’s in CalcEngine
is assumed to be part of a rectangular array reference through cell coordinates.
Computation using CalcEngine
The CalcEngine interface allows the CalcEngine
class to communicate with arbitrary data sources that implement this interface.
If any user defined class which implements the ICalcData
interface needs to be integrated with CalcEngine
, then that class’s object can be passed as parameter
to the CalcEngine
constructor.
The ParseAndComputeFormula method of CalcEngine
parses and computes the string formula passed in and updates the computed value.
Using ICalcData
The ICalcData
object can be integrated into CalcEngine
by passing it through constructor. Now, you can compute the expressions or equations using ParseAndComputeFormula
.
//Class derived from ICalcData,
CalcData calcData = new CalcData();
CalcEngine engine = new CalcEngine(calcData);
//Computing Expressions,
string formula = "(5+25) *2";
string result = engine.ParseAndComputeFormula(formula);
//Computing In-Built formulas,
string formula = “SUM (4,5,6)”;
string result = engine.ParseAndComputeFormula(formula);
Download Calculation with Engine demo from GitHub
Using CalcQuickBase
CalcQuickBase is predefined derived class from ICalcData
interface. So, user can pass the CalcQuickBase's
object as parameter to the constructor of CalcEngine
for
computations.
CalcQuickBase calcQuick = new CalcQuickBase();
//Computing Expressions,
string formula = "(5+25) *2";
string result = calcQuick.Engine.ParseAndComputeFormula(formula);
//Computing In-Built formulas,
string formula = “SUM (4,5,6)”;
string result = calcQuick.Engine.ParseAndComputeFormula(formula);
Data Objects maintained in CalcEngine
To track information used during calculations, CalcEngine
manages several hash tables. In CalcEngine
, the data’s are referred in cell coordinates and these references are maintained as keys in the hash tables.
Below find the list of hash tables in CalcEngine
and a description of their keys and values.
Hash Table | Key | Value | Description |
---|---|---|---|
Cell reference | FormulaInfo object | Tracks formula/value information for this cell. | |
Cell reference | Hashtable object | Tracks cells that depend on this cell. | |
Formula cell reference | Hashtable object | Tracks cells in which the formula cell depends upon. | |
Name string | Value string | Associates the named range with its value. | |
Function name | LibraryFunction delegate | Associates the function name with its method. |
Properties in CalcEngine
ExcelLikeComputations
To return the result of formula computations of Essential Calculate like Microsoft Excel computational result, this bool property ExcelLikeComputations is set to true.
//Class derived from ICalcData,
CalcData calcData = new CalcData();
CalcEngine engine = new CalcEngine(calcData);
engine.ExcelLikeComputations = true;
//Computing the DATE formula similar to excel,
string formula = “DATE(2004,5,6)”;
string result = engine.ParseAndComputeFormula(formula);
AllowShortCircuitIFs
To specifically avoid the computing of non-used alternative in IF function calculations, set AllowShortCircuitIFs to true.
The default value is false for code legacy consistency. But when it is set as true, only the necessary alternative of an IF function is computed which
leads to increase in performance.
AlwaysComputeDuringRefresh
If PullUpdatedValue
method is exclusively used to retrieve the computed values, then setting AlwaysComputeDuringRefresh property to false
may be more efficient as it will only recompute the value once during the calculations.
CalculatingSuspended
CalculatingSuspended property is set to true, to suspend calculations while a series of changes are made to dependent cells either by the user or programmatically.
When the changes are complete, set this property to false, and then call RecalculateRange to recalculate the affected ranges.
//Class derived from ICalcData,
CalcData calcData = new CalcData();
CalcEngine engine = new CalcEngine(calcData);
// Turn off calculations
engine.CalculatingSuspended = true;
// Makes multiple updates to cells involved in calculation
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
calcData.SetValueRowCol(random.Next(5) + 1,i,j);
}
}
// Turn on calculations
engine.CalculatingSuspended = false;
// invoke RecalculateRange, so any formulas in the data can be computed.
engine.RecalculateRange(RangeInfo.Cells(4, 4, 2, 1), calcData);
UseFormulaValues
This property is set to true, when a cell contains more dependency cells to return the formula value stored in FormulaInfoTable
to avoid
repeated calculations.
CheckDanglingStack
While computing certain formulas, Essential Calculate ignores the dangling value in the calculation stack and returns the computed value.
If you want this situation flagged as a invalid formula, then set CheckDanglingStack property to true. The default value is false for backward compatibility purpose.
ForceRefreshCall
If a value changes, then the Refresh method is called recursively every time whenever the ValueChanged event of ICalcData
is fired. Setting this ForceRefreshCall property to false will call the Refresh
method for only the cells whose calculated value is actually modified.
FormulaCharacter
In general, in order for Essential Calculate to recognize a string as containing a formula; then the string is required to start with the FormulaCharacter.
A static property that gets or sets the character to identify the formula. The default value is “=”.
GetValueFromArgPreserveLeadingZeros
If you want to preserve the leading zeros in GetValueFromArg method, then set GetValueFromArgPreserveLeadingZeros to true.
//Class derived from ICalcData,
CalcData calcData = new CalcData();
//Assign a value with leading zeros to "A1" cell,
calcData.SetValueRowCol(04510, 1, 1);
CalcEngine engine = new CalcEngine(calcData);
//Setting this property to true, for preserving leading zeros,
engine.GetValueFromArgPreserveLeadingZeros = true;
//Returns the result with leading zeros,
string result = engine.GetValueFromArg("A1")
IterationMaxCount
Gets or sets the maximum number of iterative calls that can be made on a cell to avoid circular exception. The default value is 0 indicating that iterative
calculation support is turned off. ThrowCircularException property will be automatically set to true when you set a non-zero value to IterationMaxCount.
IterationMaxTolerance
Gets or sets the success tolerance used by the CalcEngine's
iterative calculation support. The iterations in calculation continues until either the iteration count
exceeds IterationMaxCount, or two successive iteration return values have a relative difference less than IterationMaxTolerance. The default value is 0.001.
LockDependencies
A bool property that gets or sets whether a changed value should trigger dependent changes.
MaximumRecursiveCalls
Specifies the maximum number of recursive calls that can be used to compute a cell value.This property comes into play when you have a calculated formula cell that depends on
another calculated formula that depends on another calculated formula and so on. If the depends on another formula exceeds MaximumRecursiveCalls, you will see a too complex message displayed in the cell. The default value is 100, but you can set it higher or lower depending upon your expected needs. The purpose of the limit is to avoid a circular
reference locking up your application.
MaxStackDepth
Gets or sets the maximum calculation stack depth. The default value is 50. This is the number of recursive calls that can be made during calculations.
ParseArgumentSeparator
A static property that gets or sets the character to be recognized by the parsing code as the delimiter for arguments in a named formula’s argument list. The default value is ‘,’.
//Assign the argument separator,
CalcEngine.ParseArgumentSeparator = ';';
ParseDecimalSeparator
A static property that gets or sets the character to be recognized by the parsing engine as decimal separator for numbers. The default value is ‘.’.
//Assign the decimal separator,
CalcEngine.ParseDecimalSeparator = ',';
ParseDateTimeSeparator
A static property that gets/sets the character to be recognized by the parsing engine as decimal separator for date. The default value is ‘/’.
//Assign the date time separator,
CalcEngine.ParseDateTimeSeparator = '-';
RethrowLibraryComputationExceptions
Gets or sets whether the CalcEngine
throws any exception that occurs during the computation of a library function. It throws an error message from FormulaErrorStrings collection. The default value of of RethrowLibraryComputationExceptions
is false.The following page demonstrates the ErrorString and FormulaErrorString values.
RethrowParseExceptions
The default value of RethrowParseExceptions
is false.If the value is set as true, CalcEngine throws an exception from FormulaErrorStrings collection. If the value is set as false, and the parsed formula is not a valid one,it returns error message from ErrorStringscollection without throwing exception.
SupportLogicalOperators
If you want to use any logical operators such as AND, OR, XOR,… in a formula, you can set SupportLogicalOperators property of CalcEngine
as true .
//Class derived from ICalcData,
CalcData calcData = new CalcData();
CalcEngine engine = new CalcEngine(calcData);
engine.SupportLogicalOperators = true;
string result = engine.ParseAndComputeFormula(“=IF("C"="S "OR" C"="C",1,2)”);
SupportsSheetRanges
Gets or sets whether the sheet range notation is supported. The default value is true. For backward compatibility with earlier versions that did
not support the sheet range notation, you can set SupportsSheetRanges property to false.
//Initialization of first ICalcData object in CalcEngine,
calcData = new CalcData();
CalcEngine engine = new CalcEngine(calcData);
//Initialization of second ICalcData object in CalcEngine,
calcData1 = new calcData1();
engine = new CalcEngine(calcData1);
//Create a unique family id,
int i = CalcEngine.CreateSheetFamilyID();
//Register the first ICalcData object as "Sheet1",
engine.RegisterGridAsSheet("Sheet1", calcData, i);
//Register the second ICalcData object as "Sheet2",
engine.RegisterGridAsSheet("Sheet2", calcData1, i);
engine.SupportsSheetRanges = true;
string result = engine.ParseAndComputeFormula(“=SUM(sheet1!B1:B4,sheet2!B1:B4)”);
ThrowCircularException
Gets or sets whether the CalcEngine
should throw an exception when a circular calculation is encountered. If ThrowCircularException property is true, then CalcEngine
will throw an exception when it detects a circular calculation, else no exception is thrown and the calculation will loop recursively until MaximumRecursiveCalls is exceeded.
UseDependencies
Gets or sets whether the CalcEngine
should track dependencies or not. If you invoke PullUpdatedValue to access the computations, then setting UseDependencies property to false will make things more
efficient as any requested computed value will be fully computed every time it is retrieved. In this situation, the CalcEngine
does not need to track dependencies.
UseNoAmpersandQuotes
Gets or sets whether the computational result of strings should be returned with double quotes or not.
//Class derived from ICalcData,
CalcData calcData = new CalcData();
CalcEngine engine = new CalcEngine(calcData);
engine.UseNoAmpersandQuotes = true;
string result = engine.ParseAndComputeFormula("=CONCAT(\"abc\",\"sample\")");
UseDatesInCalculations
Gets or sets whether dates can be used as operands in calculations. The default value is false. Setting UseDatesInCalculations property to true,
you can compute DateTime related calculations in your application.
//Class derived from ICalcData,
CalcData calcData = new CalcData();
CalcEngine engine = new CalcEngine(calcData);
engine.UseDatesInCalculations = true;
calcData.SetValueRowCol("02/10/2017 11:15 am", 1, 1);
calcData.SetValueRowCol("02/12/2017 9:45 am", 2, 1);
//Computing the difference in hours between two date time values,
string result = engine.ParseAndComputeFormula("=(A2-A1)*24");
TreatStringsAsZero
Setting the property TreatStringsAsZero as true means that if a nonempty string is encountered during an arithmetic
operation or computation, it will be treated as zero.
Methods in CalcEngine
GetValueFromArg
This method takes the argument and checks whether it is a parsed formula, a raw number, or a cell reference. It returns the string
by computing the value based on the argument passed in.
//Class derived from ICalcData,
CalcData calcData = new CalcData();
CalcEngine engine = new CalcEngine(calcData);
string result = engine.GetValueFromArg("=SUM(4,5,6)");
GetValueFromParentObject
This method takes the argument and check whether it is a formula or a cell reference and returns either the computed value or the cell value.
//Class derived from ICalcData,
CalcData calcData = new CalcData();
//Assign values,
calcData.SetValueRowCol(2, 1, 2);
calcData.SetValueRowCol(3, 2, 2);
calcData.SetValueRowCol(4, 3, 2);
calcData.SetValueRowCol("=SUM(B1,B2,B3)", 1, 4);
CalcEngine engine = new CalcEngine(calcData);
//Passing the cell reference,
string result = engine.GetValueFromParentObject("D1")
//Passing the row/column index,
string result = engine.GetValueFromParentObject(1, 4);
NOTE
GetValueFromParentObject does not accepts the argument as raw number or parsed formula, it only accepts the cell references whereas GetValueFromArg accepts parsed formula, a raw number, or a cell reference
ParseAndComputeFormula
A method that parses and computes the string formula passed in. For more reference, see here.
ParseFormula
A method that parses the string formula passed in. For more reference, see here.
ComputeFormula
A method that computes a parsed formula. For more reference, see here.
RegisterGridAsSheet
A method that registers an ICalcData
object so it can be referenced in a formula from another ICalcData
object. For more reference, see here.
UpdateCalcID
Every formula has a calculation ID level associated with it. Every time a formula is retrieved, its calculation ID level is compared with the CalcEngine
ID level. If they do not agree, the formula is recomputed. Invoking UpdateCalcID will force any formula to be recomputed the next time it is retrieved.
//Class derived from ICalcData,
CalcData calcData = new CalcData();
//Assign values initially,
calcData.SetValueRowCol(2, 1, 2);
calcData.SetValueRowCol(3, 2, 2);
calcData.SetValueRowCol(4, 3, 2);
calcData.SetValueRowCol("=SUM(B1,B2,B3)", 1, 4);
CalcEngine engine = new CalcEngine(calcData);
//compute the formula,
string val = engine.ParseAndComputeFormula("D1");
//Changing the value of B2 cell,
calcData.SetValueRowCol(20, 2, 2);
//Invoking UpdateCalcID method to retrieve latest change,
engine.UpdateCalcID();
//Computing the formula with latest changes,
string val1 = engine.ParseAndComputeFormula("D1");
PullUpdatedValue
This method retrieves the value in the requested cell reference by computing with latest changes of cells that affect the value of the requested cell.
//Initialization of first ICalcData object in CalcEngine,
calcData = new CalcData();
CalcEngine engine = new CalcEngine(calcData);
//Initialization of second ICalcData object in CalcEngine,
ICalcData calcData1 = new CalcData1();
engine = new CalcEngine(calcData1);
//Register the first ICalcData object as "Sheet1",
engine.RegisterGridAsSheet("Sheet1", calcData, 1);
//Register the second ICalcData object as "Sheet2",
engine.RegisterGridAsSheet("Sheet2", calcData1, 1);
//Assign a formula in the calcData1's A1 cell,
calcData1.SetValueRowCol("=SUM(Sheet1!A1, Sheet1!B2, Sheet1!C2, Sheet1!D2)",1,1);
Random r = new Random();
engine.CalculatingSuspended = true;
//Set random values,
calcData.SetValueRowCol(r.Next(74) + 15, 1, 1);
calcData.SetValueRowCol(r.Next(2), 2, 2);
calcData.SetValueRowCol(r.Next(50),3, 2);
calcData.SetValueRowCol(r.Next(15),4, 2);
calcData.SetValueRowCol(r.Next(11),5, 2);
calcData.SetValueRowCol(33 + r.Next(1972),6, 2);
calcData.SetValueRowCol(r.Next(2),7, 2);
calcData.SetValueRowCol(r.Next(20),8, 5);
//Calculations are suspended so need to pull the computed value to make sure it has been calculated with the latest changes,
engine.UpdateCalcID();
engine.PullUpdatedValue(engine.GetSheetID(calcData1), 1, 1);
//Get the Calculated value from calcData1 object,
string val = calcData1.GetValueRowCol(1,1).ToString();
engine.CalculatingSuspended = false;
RecalculateRange
Recalculates any formula cells in the specified range of CalcEngine
.
// Creates some data object that implements ICalcData.
this.data = new ArrayCalcData(a);
// Creates a CalcEngine object using this ICalcData object.
CalcEngine engine = new CalcEngine(this.data);
//Turn off calculations.
engine.CalculatingSuspended = true;
// Makes multiple updates to this.data.
// Turn on calculations.
engine.CalculatingSuspended = false;
// Calls RecalculateRange so any formulas in the data can be computed.
engine.RecalculateRange(RangeInfo.Cells(1, 1, nRows + 1, nCols + 1), data);
UpdateDependenciesAndCell
This method triggers a calculation for any cells depending upon the given cell.
//Class derived from ICalcData,
CalcData calcData = new CalcData();
//Assign values initially,
calcData.SetValueRowCol(2, 1, 2);
calcData.SetValueRowCol(3, 2, 2);
calcData.SetValueRowCol(4, 3, 2);
calcData.SetValueRowCol("=SUM(B1,B2,B3)", 1, 4);
CalcEngine engine = new CalcEngine(calcData);
//Add the formula in formula info hash table,
engine.FormulaInfoTable.Add("D1", new FormulaInfo{FormulaText = "=SUM(B1,B2,B3)"});
//compute the formula,
string val = engine.ParseAndComputeFormula("D1");
//Changing the value of B2 cell,
calcData.SetValueRowCol(20, 2, 2);
//Invoke UpdateDependenciesAndCell method for the formula cell,
engine.UpdateDependenciesAndCell("D1");
//Computing the formula with latest changes,
string val1 = engine.ParseAndComputeFormula("D1");
Get list of supported formulas in CalcEngine
To get the list of supported formulas in CalcEngine, you could use the LibraryFunctions property from CalcEngine.
//Class derived from ICalcData,
CalcData calcData = new CalcData();
CalcEngine engine = new CalcEngine(calcData);
//To get the list of build in functions,
var computationFunctions = engine.LibraryFunctions;