Files
radzen-blazor/RadzenBlazorDemos/Pages/DocumentProcessingCustomFunctions.razor
Atanas Korchev 3c9ab8386a Add Document Processing demo category
Replace the old Spreadsheet Import & Export demo with a dedicated
Document Processing section: Spreadsheet API (generate XLSX/CSV from
a typed list), Import & Export (XLSX and CSV subsections, imported
data rendered in RadzenTable, configurable CSV options with live
preview), and Formulas (in-code workbook formulas, stateless
Formula.Evaluate, stateful FormulaEngine, custom FormulaFunction).
Each multi-section demo follows the H5+RadzenExample convention with
matching Toc entries. Descriptions and titles rewritten around what
users search for ("Excel", "XLSX", "CSV", "import/export",
"evaluate Excel formulas in C#") rather than internal API names.
Also ships the public FormulaEngine surface and its contract tests.
2026-06-15 18:45:12 +03:00

99 lines
4.2 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@using Radzen.Documents.Spreadsheet
<RadzenStack Gap="1rem">
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.5rem">
<RadzenLabel Text="Principal" Component="principal" />
<RadzenNumeric @bind-Value=@principal TValue="double" Min="0" Step="100"
Change=@(_ => Recompute()) Name="principal" Style="width:130px" />
<RadzenLabel Text="Annual rate (0.05 = 5%)" Component="rate" />
<RadzenNumeric @bind-Value=@rate TValue="double" Step="0.01" Format="0.00"
Change=@(_ => Recompute()) Name="rate" Style="width:90px" />
<RadzenLabel Text="Years" Component="years" />
<RadzenNumeric @bind-Value=@years TValue="double" Min="1" Max="50"
Change=@(_ => Recompute()) Name="years" Style="width:80px" />
</RadzenStack>
<RadzenTable>
<RadzenTableHeader>
<RadzenTableHeaderRow>
<RadzenTableHeaderCell>Context</RadzenTableHeaderCell>
<RadzenTableHeaderCell>Formula</RadzenTableHeaderCell>
<RadzenTableHeaderCell>Result</RadzenTableHeaderCell>
</RadzenTableHeaderRow>
</RadzenTableHeader>
<RadzenTableBody>
<RadzenTableRow>
<RadzenTableCell>FormulaEngine (headless)</RadzenTableCell>
<RadzenTableCell Style="font-family:monospace">=COMPOUND(@principal, @rate, @years)</RadzenTableCell>
<RadzenTableCell Style="font-family:monospace">@engineResult</RadzenTableCell>
</RadzenTableRow>
<RadzenTableRow>
<RadzenTableCell>Worksheet.FunctionRegistry</RadzenTableCell>
<RadzenTableCell Style="font-family:monospace">=COMPOUND(A1, B1, C1)</RadzenTableCell>
<RadzenTableCell Style="font-family:monospace">@worksheetResult</RadzenTableCell>
</RadzenTableRow>
</RadzenTableBody>
</RadzenTable>
<RadzenAlert AlertStyle="AlertStyle.Info" Variant="Variant.Flat" AllowClose="false">
Both contexts share the exact same <code>FormulaFunction</code> subclass. Register it on the
<code>FunctionStore</code> exposed by either <code>FormulaEngine.Functions</code> or
<code>Worksheet.FunctionRegistry</code>, and any cell formula can call it.
</RadzenAlert>
</RadzenStack>
@code {
double principal = 1000;
double rate = 0.05;
double years = 3;
string engineResult = "";
string worksheetResult = "";
protected override void OnInitialized() => Recompute();
void Recompute()
{
// 1) Stateless via FormulaEngine
var engine = new FormulaEngine();
engine.Functions.Add<CompoundFunction>();
engineResult = engine.Evaluate($"=COMPOUND({principal},{rate},{years})")?.ToString() ?? "(null)";
// 2) Inside a Workbook via Worksheet.FunctionRegistry
var wb = new Workbook();
var sheet = wb.AddSheet("Calc", 1, 4);
sheet.FunctionRegistry.Add<CompoundFunction>();
sheet.Cells[0, 0].Value = principal;
sheet.Cells[0, 1].Value = rate;
sheet.Cells[0, 2].Value = years;
sheet.Cells[0, 3].Formula = "=COMPOUND(A1, B1, C1)";
worksheetResult = sheet.Cells[0, 3].Value?.ToString() ?? "(null)";
}
/// <summary>
/// Compound interest: principal × (1 + rate)^years.
/// One class, registered on either context, callable from any cell.
/// </summary>
public sealed class CompoundFunction : FormulaFunction
{
public override string Name => "COMPOUND";
public override FunctionParameter[] Parameters =>
[
new("principal", ParameterType.Single, isRequired: true),
new("rate", ParameterType.Single, isRequired: true),
new("years", ParameterType.Single, isRequired: true),
];
public override CellData Evaluate(FunctionArguments args)
{
System.ArgumentNullException.ThrowIfNull(args);
var p = args.GetSingle("principal")?.GetValueOrDefault<double>() ?? 0d;
var r = args.GetSingle("rate")?.GetValueOrDefault<double>() ?? 0d;
var y = args.GetSingle("years")?.GetValueOrDefault<double>() ?? 0d;
return CellData.FromNumber(p * System.Math.Pow(1d + r, y));
}
}
}