Search Results for

    Show / Hide Table of Contents

    Excel user defined functions (UDF) (Delphi)

    Note

    This demo is available in your FlexCel installation at <FlexCel Install Folder>\Demo\Delphi\Modules\10.API\78.Excel User Defined Functions and also at https:​//​github.​com/​tmssoftware/​TMS-​FlexCel.​VCL-​demos/​tree/​master/​Delphi/​Modules/​10.​API/​78.​Excel User Defined Functions

    Overview

    Here we will explore how to handle Excel files with UDFs. FlexCel has full support for adding Excel UDFs to cells, retrieving UDFs from cells or recalculating files containing UDFs. But you need to create Delphi/C++ functions that will mimic the UDF behavior, and add them to the FlexCel recalculation engine.

    Make sure you read Using Excel's User-defined Functions (UDF) in the API developers guide for a conceptual explanation of what we are doing here.

    Concepts

    • How to recalculate a sheet containing User Defined Functions (UDfs).

    • How to read and write UDFs from and to an Excel file.

    • In order to compare the results calculated by Excel and By FlexCel, this demo will save two files: one pdf (that will not use Excel recalculation) and one xls (that will be recalculated by Excel when opened).

    Files

    UExcelUserDefinedFunctions.pas

    unit UExcelUserDefinedFunctions;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls, FlexCel.VCLSupport, FlexCel.Core, FlexCel.XlsAdapter,
      {$IFDEF FPC} LResources,{$ENDIF}
      {$if CompilerVersion >= 23.0} System.UITypes, {$IFEND}
      ShellAPI;
    
    type
      TFExcelUserDefinedFunctions = class(TForm)
        Memo1: TMemo;
        btnGo: TButton;
        SaveDialog: TSaveDialog;
        procedure btnGoClick(Sender: TObject);
      private
        procedure AddData(const Xls: TExcelFile);
        procedure AutoRun;
        procedure LoadUdfs(const Xls: TExcelFile);
     end;
    
    var
      FExcelUserDefinedFunctions: TFExcelUserDefinedFunctions;
    
    implementation
    uses IOUtils, UPaths, UUserFunctions;
    {$R *.dfm}
    
    { TFExcelUserDefinedFunctions }
    
    procedure TFExcelUserDefinedFunctions.btnGoClick(Sender: TObject);
    begin
      AutoRun;
    end;
    
    /// <summary>
    /// Loads the user defined functions into the Excel recalculating engine.
    /// </summary>
    /// <param name="Xls"></param>
    procedure TFExcelUserDefinedFunctions.LoadUdfs(const Xls: TExcelFile);
    begin
      Xls.AddUserDefinedFunction(TUserDefinedFunctionScope.Local, TUserDefinedFunctionLocation.Internal, TSumCellsWithSameColor.Create);
      Xls.AddUserDefinedFunction(TUserDefinedFunctionScope.Local, TUserDefinedFunctionLocation.Internal, TIsPrime.Create);
      Xls.AddUserDefinedFunction(TUserDefinedFunctionScope.Local, TUserDefinedFunctionLocation.Internal, TBoolChoose.Create);
      Xls.AddUserDefinedFunction(TUserDefinedFunctionScope.Local, TUserDefinedFunctionLocation.Internal, TLowest.Create);
    end;
    
    procedure TFExcelUserDefinedFunctions.AddData(const Xls: TExcelFile);
    var
      Data: TXlsNamedRange;
      r: Int32;
      FmlaText: String;
      o: TCellValue;
      //pdf: TFlexCelPdfExport;
    begin
      LoadUdfs(Xls);  //Register our custom functions. As we are using a local scope, we need to register them each time.
      Xls.Open(TPath.Combine(DataFolder, 'udfs.xls'));  //Open the file we want to manipulate.
       //Fill the cell range with other values so we can see how the sheet is recalculated by FlexCel.
      Data := Xls.GetNamedRange('Data', -1);
      for r := Data.Top to Data.Bottom - 1 do
      begin
        Xls.SetCellValue(r, Data.Left, r - Data.Top);
      end;
    
      FmlaText := '=BoolChoose(TRUE,"This formula was entered with FlexCel!","It shouldn''t display this")';  //Add an UDF to the sheet. We can enter the function "BoolChoose" here because it was registered into FlexCel in LoadUDF()
      //If it hadn't been registered, this line would raise an Exception of an unknown function.
      Xls.SetCellValue(11, 1, TFormula.Create(FmlaText));
      o := Xls.GetCellValue(11, 1);  //Verify the UDF entered is correct. We can read any udf from Excel, even if it is not registered with AddUserDefinedFunction.
      Assert(o.IsFormula, 'The cell must contain a formula');
      if o.IsFormula then
        Assert(o.AsFormula.Text = FmlaText, ((('Error in Formula: It should be "' + FmlaText) + '" and it is "') + o.AsFormula.Text) + '"');
    
       //Recalc the sheet. As we are not saving it yet, we ned to make a manual recalc.
      Xls.Recalc;
    
      {//pdf exporting is not yet implemented
       //Export the file to PDF so we can see the values calculated by FlexCel without Excel recalculating them.
      pdf := FlexCelPdfExport.Create(Xls, true);
      try
        pdf.Export(TPath.ChangeExtension(SaveDialog.FileName, 'pdf'));
      finally
        FreeObj(pdf);
      end;                                   }
    
       //Save the file as xls too so we can compare.
      Xls.Save(SaveDialog.FileName);
    end;
    
    procedure TFExcelUserDefinedFunctions.AutoRun;
    var
      Xls: TExcelFile;
    begin
      if not SaveDialog.Execute then
        exit;
    
      Xls := TXlsFile.Create(true);
      try
      AddData(Xls);
      if MessageDlg('Do you want to open the generated file?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then
      begin
        ShellExecute(0, 'open', PCHAR(SaveDialog.FileName), nil, nil, SW_SHOWNORMAL);
      end;
      finally
        Xls.Free;
      end;
    
    end;
    
    
    end.
    

    UUserFunctions.pas

    unit UUserFunctions;
    interface
    uses FlexCel.VCLSupport, FlexCel.Core;
    type
    
      /// <summary>
      /// Creates a new instance and registers the class in the FlexCel recalculating engine as "BoolChoose".
      /// </summary>
      TBoolChoose = class (TUserDefinedFunction)
      public
        constructor Create;
        /// <summary>
        /// Chooses between 2 different strings.
        /// </summary>
        /// <param name="arguments"></param>
        /// <param name="parameters">In this case we expect 3 parameters: The first is a boolean, and the other 2 strings. We will return an error otherwise.</param>
        /// <returns></returns>
        function Evaluate(const arguments: TUdfEventArgs; const parameters: TFormulaValueArray): TFormulaValue; override;
      end;
    
      TSumCellsWithSameColor = class (TUserDefinedFunction)
      public
        constructor Create;
        /// <summary>
        /// Returns the sum of cells in a range that have the same color as a reference cell.
        /// </summary>
        /// <param name="arguments"></param>
        /// <param name="parameters">In this case we expect 2 parameters, first the reference cell and then
        /// the range in which to sum. We will return an error otherwise.</param>
        /// <returns></returns>
        function Evaluate(const arguments: TUdfEventArgs; const parameters: TFormulaValueArray): TFormulaValue; override;
      end;
    
      TIsPrime = class (TUserDefinedFunction)
      public
        /// <summary>
        /// Creates a new instance and registers the class in the FlexCel recalculating engine as "IsPrime".
        /// </summary>
        constructor Create;
    
          /// <summary>
          /// Returns true if a number is prime.
          /// </summary>
          /// <param name="arguments"></param>
          /// <param name="parameters">In this case we expect 1 parameter with the number. We will return an error otherwise.</param>
          /// <returns></returns>
          function Evaluate(const arguments: TUdfEventArgs; const parameters: TFormulaValueArray): TFormulaValue; override;
      end;
    
      TLowest = class (TUserDefinedFunction)
      public
        /// <summary>
        /// Creates a new instance and registers the class in the FlexCel recalculating engine as "Lowest".
        /// </summary>
        constructor Create;
    
        /// <summary>
        /// Chooses the lowest element in an array.
        /// </summary>
        /// <param name="arguments"></param>
        /// <param name="parameters">In this case we expect 1 parameter that should be an array. We will return an error otherwise.</param>
        /// <returns></returns>
        function Evaluate(const arguments: TUdfEventArgs; const parameters: TFormulaValueArray): TFormulaValue; override;
      end;
    
    implementation
    constructor TBoolChoose.Create;
    begin
      inherited Create('BoolChoose');
    end;
    
    function TBoolChoose.Evaluate(const arguments: TUdfEventArgs; const parameters: TFormulaValueArray): TFormulaValue;
    var
      Err: TFlxFormulaErrorValue;
      ChooseFirst: Boolean;
      s1: String;
      s2: String;
    begin
      if not CheckParameters(parameters, 3, Err) then
        exit(Err);
    
       //The first parameter should be a boolean.
      if not TryGetBoolean(arguments.Xls, parameters[0], ChooseFirst, Err) then
        exit(Err);
    
       //The second parameter should be a string.
      if not TryGetString(arguments.Xls, parameters[1], s1, Err) then
        exit(Err);
    
       //The third parameter should be a string.
      if not TryGetString(arguments.Xls, parameters[2], s2, Err) then
        exit(Err);
    
       //Return s1 or s2 depending on ChooseFirst
      if ChooseFirst then
        Result := s1 else
        Result := s2;
    end;
    
    { TSumCellsWithSameColor }
    
    constructor TSumCellsWithSameColor.Create;
    begin
     inherited Create('SumCellsWithSameColor');
    end;
    
    function TSumCellsWithSameColor.Evaluate(const arguments: TUdfEventArgs;
      const parameters: TFormulaValueArray): TFormulaValue;
    var
      Err: TFlxFormulaErrorValue;
      SourceCell: TXls3DRange;
      SumRange: TXls3DRange;
      fmt: TFlxFormat;
      SourceColor: Int32;
      _Result: double;
      s: Int32;
      r: Int32;
      c: Int32;
      XF: Int32;
      val: TFormulaValue;
      sumfmt: TFlxFormat;
    begin
      if not CheckParameters(parameters, 2, Err) then
        exit(Err);
    
       //The first parameter should be a range
      if not TryGetCellRange(parameters[0], SourceCell, Err) then
        exit(Err);
    
       //The second parameter should be a range too.
      if not TryGetCellRange(parameters[1], SumRange, Err) then
        exit(Err);
    
       //Get the color in SourceCell. Note that if Source cell is a range with more than one cell,
       //we will use the first cell in the range. Also, as different colors can have the same rgb value, we will compare the actual RGB values, not the ExcelColors
      fmt := arguments.Xls.GetCellVisibleFormatDef(SourceCell.Sheet1, SourceCell.Top, SourceCell.Left);
      SourceColor := fmt.FillPattern.FgColor.ToColor(arguments.Xls).ToArgb;
      _Result := 0;
       //Loop in the sum range and sum the corresponding values.
      for s := SumRange.Sheet1 to SumRange.Sheet2 do
      begin
        for r := SumRange.Top to SumRange.Bottom do
        begin
          for c := SumRange.Left to SumRange.Right do
          begin
            XF := -1;
            val := arguments.Xls.GetCellValue(s, r, c, XF);
            if val.IsNumber then  //we will only sum numeric values.
            begin
              sumfmt := arguments.Xls.GetCellVisibleFormatDef(s, r, c);
              if sumfmt.FillPattern.FgColor.ToColor(arguments.Xls).ToArgb = SourceColor then
              begin
                _Result:= _Result + val.AsNumber;
              end;
    
            end;
    
          end;
    
        end;
    
      end;
    
      Result := _Result;
    end;
    
    { IsPrime }
    
    constructor TIsPrime.Create;
    begin
      inherited Create('IsPrime');
    end;
    
    function TIsPrime.Evaluate(const arguments: TUdfEventArgs;
      const parameters: TFormulaValueArray): TFormulaValue;
    var
      Err: TFlxFormulaErrorValue;
      Number: double;
      n: Int32;
      i: Int32;
    begin
      if not CheckParameters(parameters, 1, Err) then
        exit(Err);
    
       //The parameter should be a double or a range.
      if not TryGetDouble(arguments.Xls, parameters[0], Number, Err) then
        exit(Err);
    
      n := Trunc(Number);  //Return true if the number is prime.
      if n = 2 then
        exit(true);
    
      if (n < 2) or ((n mod 2) = 0) then
        exit(false);
    
      begin
        i := 3;
        while i <= Sqrt(n) do
        try
          if (n mod i) = 0 then
            exit(false);
    
        finally
          i:= i + 2;
        end;
    
      end;
      Result := true;
    end;
    
    { TLowest }
    
    constructor TLowest.Create;
    begin
      inherited Create('Lowest');
    end;
    
    function TLowest.Evaluate(const arguments: TUdfEventArgs;
      const parameters: TFormulaValueArray): TFormulaValue;
    var
      Err: TFlxFormulaErrorValue;
      SourceArray: TFormulaValueArray2;
      sa: TSingleFormulaValueArray;
      _Result: double;
      First: Boolean;
      o: TFormulaValue;
    begin
      if not CheckParameters(parameters, 1, Err) then
        exit(Err);
    
       //The first parameter should be an array.
      if not TryGetArray(arguments.Xls, parameters[0], SourceArray, Err) then
        exit(Err);
    
      _Result := 0;
      First := true;
      for sa in SourceArray do
      begin
        for o in sa do
        begin
          if o.IsNumber then
          begin
            if First then
            begin
              First := false;
              _Result := o.AsNumber;
            end else
            begin
              if o.AsNumber < _Result then
                _Result := o.AsNumber;
    
            end;
    
          end else
            exit(TFlxFormulaErrorValue.ErrValue);
    
        end;
      end;
      Result := _Result;
    end;
    
    end.
    
    In This Article
    Back to top FlexCel Studio for VCL and FireMonkey v7.24
    © 2002 - 2025 tmssoftware.com