# dotNet.ReoGrid 扩展库 - 自定义函数

添加自定义公式函数
-------------------------------------------------------------------

要添加自定义函数，可以使用 `ReoGrid.Formula.FormulaExtension` 类。以下示例添加一个将给定字符串转换为大写的函数。

```aardio
// 添加自定义函数
ReoGrid.Formula.FormulaExtension.CustomFunctions["myUpper"] = function(cell, args){
 
    /*
    注意这是一个 .NET 中的 object 数组，
    这不是 aardio 数组不能用 # 操作符取数组长度。
    应当用 args.Length  取数组长度
    */
    if(args.Length == 0) {
        // 此函数至少需要一个参数
        return null;
    }
    
    //args[1] 是字符串，不用再转换
    return string.upper( args[1] );
};

```

测试此函数，输入公式 `=myUpper("hello")`：  
![169](../images/169.png)

按回车后，单元格值将变为大写：  
![170](../images/170.png)

[关于 FormulaExtension](#关于-formulaextension)
-------------------------------------------------

`ReoGrid.Formula.FormulaExtension` 是一个用于在公式计算期间添加扩展的接口。

在具有脚本执行功能的 ReoGrid 扩展版本中，公式扩展可以通过一些 ReoScript 功能实现。对于使用 ReoGrid 标准版本的应用程序，`ReoGrid.Formula.FormulaExtension` 接口是实现公式扩展功能的更简单方式。

![177](../images/177.png)

`ReoGrid.Formula.FormulaExtension.CustomFunctions` 属性在 C# 源码中定义为：

```cs
Dictionary<string, Func<ReoGridCell, object[], object>> CustomFunctions
```

### 最简单的函数示例

aardio:

```aardio
ReoGrid.Formula.FormulaExtension.CustomFunctions["myFunc"] = function(cell, args){
    // 函数体
    return null;
};
```

### [使用 lambda 表达式](#使用委托函数)

也可以使用 lambda 表达式：

```aardio
// 使此函数在公式中可用
ReoGrid.Formula.FormulaExtension.CustomFunction["myFunc"] = lambda(cell, ...) null;
```

### 单元格实例

第一个参数是拥有公式的单元格实例，例如：

```aardio
sheet1["A1"] = "=myFunc()";
```

A1 单元格有一个调用自定义函数 "myFunc" 的公式，上面定义的函数体将被调用，cell 参数就是 "A1" 单元格。

```aardio
function MyFunc(cell, ...){
    win.msgbox(cell.Position.ToAddress()); // 输出: A1
    return null;
}
```

### 从函数获取工作表实例

通过获取单元格实例的 Worksheet 属性可以获取工作表实例，以及从工作表实例的 Workbook 属性获取工作簿实例。

```aardio
ReoGrid.Formula.FormulaExtension.CustomFunction["myFunc"] = function(cell, args){
    var sheet1 = cell.Worksheet;
    var workbook = sheet1.Workbook;
    // ...
    return null;
};
```

限制函数仅在指定工作簿或工作表中可用：

```aardio
{
    var sheet1 = cell.Worksheet;
    var workbook = sheet1.Workbook;
    if (workbook != myWorkbook) {
        // 不在有效的工作簿范围内
        return null;
    }
}
```

### 获取函数参数

第二个参数是从公式调用者传递的参数。

```aardio
ReoGrid.Formula.FormulaExtension.CustomFunction["myFunc"] = function(cell, args){

    //.NET 对象数组要用 Length 属性取数组长度，不能用 `#` 操作符
    if(args.Length < 2) {
        // 此函数至少需要两个参数
        return null;
    }
    return null;
};
```

公式中的数据类型
---------------------------------------------

公式计算期间使用多种数据类型：

*   数字 (int, long, short, float, double)
*   字符串 (string 和 StringBuffer)
*   布尔值
*   日期时间
*   对象

ReoGrid 在公式计算中始终使用 double 作为数字类型。ReoGrid 在将值传递给函数之前执行以下转换：

*   int, long, short, float 将被转换为 double
*   string 和 StringBuffer 对象将被转换为字符串

### 数值常量

公式中使用的数值常量将被初始化为 double 类型，例如：

```aardio
sheet1["A1"] = "=myFunc(10)";
```

数字 10 将被解析并作为 double 类型存储在内存中。参数数组中的第一个参数将是 double 类型。

```aardio
ReoGrid.Formula.FormulaExtension.CustomFunction["myFunc"] = function(cell, args){
    if(args.Length < 1 || type(args[1]) != type.number) {
        // 此函数需要一个 double 值
        return null;
    }
    // ...
};
```

### 数值变量

当数值来自单元格时，它将被转换为 double 类型。创建以下函数：

```aardio
ReoGrid.Formula.FormulaExtension.CustomFunction["myFunc"] = function(cell, args){
    var value = args[1];
    return value * 2;
};
```

然后在公式中使用：

```aardio
sheet1["A1"] = 10;
sheet1["B1"] = "=myFunc(A1)";
```

不会发生数据类型转换错误。（但要注意参数长度）

函数中的引用
-------------------------------------------------

### 单元格引用

ReoGrid 会自动从公式中使用的引用单元格获取值，如"数值变量"示例所示。

### 范围引用

如果在公式中使用范围字面量，ReoGrid 会将 ReoGrid.RangePosition 结构传递给函数，例如：

```aardio
sheet1["G2:K3"] = {1, 2, 5, 7, 8, 10, 12, 15, 16, 19};
sheet1["L2"] = "=CountEvenNumber(G2:K3)";
```

有一个范围引用 (G2:K3) 在公式中使用，需要在自定义函数体内手动处理。以下示例函数计算给定范围内的偶数数量：

```aardio
ReoGrid.Formula.FormulaExtension.CustomFunction["CountEvenNumber"] = function(cell, args){
    if(args.Length < 1 || type(args[1]) != type.table) {
        return null; // 我们需要一个范围引用
    }
    
    var range = args[1];
    var count = 0;
    
    // 遍历范围内的单元格
    cell.Worksheet.IterateCells(range, function(r, c, inCell){
        var value;
        
        // 尝试从单元格数据获取数字
        if(ReoGridCellUtility.TryGetNumberData(inCell.Data, value)) {
            if((value % 2) == 0) {
                count++;
            }
        }
        
        // 继续迭代
        return true;
    });
    
    return count;
};
```

### 名称引用

#### 名称到单元格

如果名称引用单元格，单元格的值将被自动获取并传递给函数。

```aardio
sheet1.DefineNamedRange("r1", "A1");
sheet1["r1"] = 20;
sheet1["B1"] = "=myFunc(r1)";
```

#### 名称到范围

如果公式中使用了引用范围的名称，ReoGrid 会首先按名称查找范围，然后传递 ReoGrid.RangePosition 结构对象，可以像范围引用一样处理。

```aardio
sheet1.DefineNamedRange("evenRange", "G2:K3");
sheet1["evenRange"] = {1, 2, 4, 6, 9, 11, 13, 14, 15, 17};
sheet1["I2"] = "=CountEvenNumber(evenRange)";
assert(sheet1["I2"] == 4);
```

自定义名称引用提供程序
-----------------------------------------------------------------------

可以添加在公式中使用但由 .NET 程序提供的常量值。以下示例添加两个返回不同常量值的名称：

```aardio
ReoGrid.Formula.FormulaExtension.NameReferenceProvider = function(cell, name){
    if(name == "myName1") {
        return 10;
    } else if(name == "myName2") {
        return 20;
    } else {
        return null;
    }
};
```

NameReferenceProvider 定义为：

```cs
Func<ReoGridCell, string, object> NameReferenceProvider { get; set; }
```

测试名称1，在单元格中输入 `=myName1`：  
![171](../images/171.png)

名称返回值10：  
![172](../images/172.png)

输入 `=myName2`：  
![173](../images/173.png)

名称返回值20：  
![174](../images/174.png)

### 添加数学常量

通过使用自定义名称引用提供程序，还可以添加一些常量，如数学数字，以下代码定义了一个常量提供程序：

aardio:

```aardio
function MathConstantProvider(cell, name){
    select(name) {
        case "PI" 
            return math.pi;
        case "E" 
            return math.exp(1);
        case "MyConst" 
            return 1.23456;
    }
    return null;
}

// 应用此名称引用提供程序
ReoGrid.Formula.FormulaExtension.NameReferenceProvider = MathConstantProvider;
```

在公式中使用常量：

```aardio
sheet1["A1"] = "=MyConst"
```

结果：  
![181](../images/181.png)

组合自定义函数和自定义名称提供程序
-----------------------------------------------------------------------------------------------------------------

在自定义函数中使用自定义名称提供程序也受支持。名称将被转换为值并传递给函数。

```aardio
ReoGrid.Formula.FormulaExtension.NameReferenceProvider = function(cell, name){
    select(name) {
        case "Pi" 
            return math.pi;
        case "E" 
            return math.exp(1);
    }
    return null;
};
```

在自定义函数中使用名称 Pi：

```aardio
sheet1["D3"] = "=Div100(Pi)";
assert(sheet1["D3"] == math.pi / 100);
```

[添加 ReoScript 语言自定义函数](#添加-reoscript-语言自定义函数)
---------------------------------------------------------------------------------------

当启用 ReoScript 模块扩展时，可以使用 ReoScript 脚本语言处理单元格的数据、样式、边框、数据格式等，类似于 Excel 中的 VBA。ReoScript 支持为脚本语言制作 .NET 扩展，包括在脚本中调用 .NET 函数。

ReoGrid 支持调用 ReoScript 自定义扩展函数：

ReoScript 语言扩展仅在完整发布包中可用。

![178](../images/178.png)

### [在 aardio 中添加 ReoScript 函数](#在-c-中添加-reoscript-函数)

添加自定义函数扩展控件：

```aardio
//注册自定义函数
grid.Srm["sqrt"] = ReoScript.NativeFunctionObject( 
	"sqrt", //指定函数名
	function(ctx,world,args){ 
		if(args.Length < 1) return 0/0/*NaN*/; 
		
		//获取第一个参数，其实在 aardio 里这句是多余的
		//arg1 = ReoScript.ScriptRunningMachine.GetDoubleValue(args[1],0);
			
		return math.sqrt( args[1] ); 
	} 
) 
```

测试：

```aardio
var grid = grid.CurrentWorksheet;
sheet1[2, 1] = "=sqrt(400)";
```

![44](../images/44.png)

双击单元格查看其公式：  
![43](../images/43.png)

完整示例代码：

```aardio
import win.ui;
/*DSG{{*/
var winform = win.form(text="ReoGrid 示例")

/*}}*/

import dotNet.ReoGrid; 
var grid = ReoGrid.ReoGridControl(winform);
var sheet1 = grid.CurrentWorksheet;

//注册自定义函数
grid.Srm["sqrt"] = ReoScript.NativeFunctionObject( 
	"sqrt", //指定函数名
	function(ctx,world,args){ 
		if(args.Length < 1) return 0/0/*NaN*/; 
		
		//获取第一个参数，其实在 aardio 里这句是多余的
		//arg1 = ReoScript.ScriptRunningMachine.GetDoubleValue(args[1],0);
			
		return math.sqrt( args[1] ); 
	} 
) 

//调用自定义函数
sheet1[2, 1] = "=sqrt(400)";

winform.show();
win.loopMessage();

```

自定义函数也可以在脚本中使用。

[ReoScript 上下文中的自定义函数](#reoscript-上下文中的自定义函数)
-------------------------------------------------------------------------------------

对象 'workbook' 是在公式和脚本中都可用的全局对象。（类似于 JavaScript 中的 'window'）  
![自定义函数](../images/45.png)

运行上面的代码一次并在公式中使用它。  
![使用自定义函数](../images/46.png)

按 'enter'：  
![查看自定义函数结果](../images/47-2.png)

[在 aardio 中运行脚本添加函数](#在-aardio-中运行脚本添加函数)
----------------------------------------------------------------------

也可以在 aardio 中运行 ReoScript 脚本来创建 ReoScript 函数，以下是用于创建新函数的 ReoScript lambda 表达式。

```aardio
grid.RunScript("script.myfunc = data => '[' + data + ']';");
```

完整示例：

```aardio
import win.ui;
/*DSG{{*/
var winform = win.form(text="ReoGrid 示例")
/*}}*/

import dotNet.ReoGrid; 
var grid = ReoGrid.ReoGridControl(winform);
var sheet1 = grid.CurrentWorksheet;

//执行 ReoScript 脚本添加自定义函数
grid.RunScript("script.myfunc = data => '[' + data + ']';");

//调用自定义函数
sheet1[2, 1] = "=myfunc(400)";

winform.show();
win.loopMessage();

```