FlexCalc (MAUI) (C# / Mobile / Maui)
Note
This demo is available in your FlexCel installation at <FlexCel Install Folder>\samples\csharp\VS2026\Mobile\Maui\FlexCalc and also at https://github.com/tmssoftware/TMS-FlexCel.NET-demos/tree/master/csharp/VS2026/Mobile/Maui/FlexCalc
Overview
This example shows how to use the FlexCel calculating engine as an interactive calculator on Android, iOS, macOS, and Windows using .NET MAUI.
How It Works
Under the hood, every text field in the app is mapped to a cell in a spreadsheet (A1, A2, A3...).
Whenever a text field changes, the entire sheet is recalculated and the results are shown in the column on the right. You can use the full range of Excel functions in this demo and reference cells in the usual way (for example, you can write =A2 + 1 in the text field for A3).
As in Excel, any text that starts with = will be considered a formula.
The backing spreadsheet is saved when you exit the app and reloaded when you open it, so your formulas persist between sessions. The file is stored as config.xlsx in the app's private storage directory.
FlexCel Features Demonstrated
- XlsFile: Creating and opening Excel files programmatically
- SetCellFromString / GetCellValue: Reading and writing cell values
- TFormula: Parsing formulas entered as text
- Recalc(): Real-time recalculation of the entire workbook when any cell changes
- Save with TExcelFileFormat.v2023: Saving workbooks in modern Excel format
Implementation Details
- CalcEngine.cs: Shared business logic wrapping the FlexCel API (same logic as the native Android and iOS versions).
- MainPage.xaml / MainPage.xaml.cs: XAML-based UI with an
ObservableCollection<RowData>for data binding. Each row uses two-way binding for the formula text input and one-way binding for the calculated value. When any formula changes, all calculated values are refreshed. - App.xaml.cs: Holds a static
CalcEnginesingleton instance shared across the app. - RowData: A
BindableObjectwith properties forRowNumber,CellName,FormulaText, andValue, implementing property change notifications for the MAUI binding system.
Files
App.xaml.cs
using Microsoft.Extensions.DependencyInjection;
namespace FlexCalc
{
public partial class App : Application
{
public static CalcEngine Engine { get; } = new CalcEngine();
public App()
{
InitializeComponent();
}
protected override Window CreateWindow(IActivationState? activationState)
{
return new Window(new AppShell());
}
}
}
AppShell.xaml.cs
namespace FlexCalc
{
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
}
}
}
CalcEngine.cs
using FlexCel.Core;
using FlexCel.XlsAdapter;
namespace FlexCalc
{
public class CalcEngine
{
readonly XlsFile xls = new(1, FlexCel.Core.TExcelFileFormat.v2023, true);
readonly string ConfigFile = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "config.xlsx");
readonly string[] Predefined =
[
"5", "=A1 * 3 + 7", "=Sum(A1, A2) * 9", "=Sin(a1) + cos(a2)", "=Average(a1:a4)",
"", "", "", "", "", "", "", "", "", "", "",
"5", "=A1 * 3 + 7", "=Sum(A1, A2) * 9", "=Sin(a1) + cos(a2)", "=Average(a1:a4)",
"", "", "", "", "", "", "", "", "", "", ""
];
public CalcEngine()
{
bool Restoring = false;
if (File.Exists(ConfigFile))
{
try
{
xls.Open(ConfigFile);
Restoring = true;
}
catch
{
//if the file is corrupt, we'll just ignore it.
//Restoring will be false, and we will create a new file.
}
}
if (!Restoring)
{
xls.NewFile(1, TExcelFileFormat.v2023);
for (int k = 0; k < Predefined.Length; k++)
{
xls.SetCellFromString(k + 1, 1, Predefined[k]); //Initialize the grid with something so users know what they have to do.
}
}
xls.Recalc();
}
public string GetRowValue(int row)
{
object cell = xls.GetCellValue(row, 1);
if (cell == null)
return "";
if (cell is TFormula fmla)
{
return Convert.ToString(fmla.Result) ?? "";
}
return Convert.ToString(cell) ?? "";
}
public string GetRowFormulaText(int row)
{
object cell = xls.GetCellValue(row, 1);
if (cell == null)
return "";
if (cell is TFormula fmla)
{
return fmla.Text ?? "";
}
return Convert.ToString(cell) ?? "";
}
public string SetRowFormula(int row, string value)
{
xls.SetCellFromString(row, 1, value);
xls.Recalc();
Directory.CreateDirectory(Path.GetDirectoryName(ConfigFile)!);
xls.Save(ConfigFile);
return GetRowValue(row);
}
public int GetRowCount()
{
return Math.Min(xls.RowCount, 100); //ensure it isn't too big, we only want to display 100 rows.
}
}
}
MainPage.xaml.cs
using System.Collections.ObjectModel;
namespace FlexCalc
{
public partial class MainPage : ContentPage
{
private readonly ObservableCollection<RowData> _rows = new();
public MainPage()
{
InitializeComponent();
RowList.ItemTemplate = CreateItemTemplate();
RowList.ItemsSource = _rows;
}
protected override void OnAppearing()
{
base.OnAppearing();
RefreshRows();
if (_rows.Count > 0)
RowList.ScrollTo(0, position: ScrollToPosition.Start);
}
private void RefreshRows()
{
var engine = App.Engine;
int count = engine.GetRowCount();
_rows.Clear();
for (int i = 1; i <= count; i++)
{
_rows.Add(new RowData
{
RowNumber = i,
CellName = $"A{i}",
FormulaText = engine.GetRowFormulaText(i),
Value = engine.GetRowValue(i)
});
}
}
private DataTemplate CreateItemTemplate()
{
return new DataTemplate(() =>
{
var grid = new Grid
{
ColumnDefinitions =
{
new ColumnDefinition(new GridLength(60)),
new ColumnDefinition(GridLength.Star),
new ColumnDefinition(GridLength.Star)
},
Padding = new Thickness(5, 2)
};
var cellLabel = new Label { VerticalTextAlignment = TextAlignment.Center };
cellLabel.SetBinding(Label.TextProperty, nameof(RowData.CellName));
var formulaEntry = new Entry { Margin = new Thickness(5, 0) };
formulaEntry.SetBinding(Entry.TextProperty, nameof(RowData.FormulaText));
formulaEntry.TextChanged += OnFormulaTextChanged;
var valueLabel = new Label { VerticalTextAlignment = TextAlignment.Center, Margin = new Thickness(5, 0) };
valueLabel.SetBinding(Label.TextProperty, nameof(RowData.Value));
grid.Add(cellLabel, 0);
grid.Add(formulaEntry, 1);
grid.Add(valueLabel, 2);
return grid;
});
}
private void OnFormulaTextChanged(object? sender, TextChangedEventArgs e)
{
if (sender is not Entry entry || entry.BindingContext is not RowData row)
return;
App.Engine.SetRowFormula(row.RowNumber, e.NewTextValue ?? "");
// Refresh all values since formulas can reference each other
var engine = App.Engine;
foreach (var r in _rows)
{
r.Value = engine.GetRowValue(r.RowNumber);
}
}
}
public class RowData : BindableObject
{
public int RowNumber { get; set; }
private string _cellName = "";
public string CellName
{
get => _cellName;
set { _cellName = value; OnPropertyChanged(); }
}
private string _formulaText = "";
public string FormulaText
{
get => _formulaText;
set { _formulaText = value; OnPropertyChanged(); }
}
private string _value = "";
public string Value
{
get => _value;
set { _value = value; OnPropertyChanged(); }
}
}
}
MauiProgram.cs
using Microsoft.Extensions.Logging;
namespace FlexCalc
{
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}
}