65.9K
CodeProject 正在变化。 阅读更多。
Home

带公式支持的 DataGridView

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2017 年 1 月 14 日

CPOL

7分钟阅读

viewsIcon

21539

downloadIcon

925

允许您向单元格添加公式的自定义 DataGridView

引言

本文介绍的项目是一个自定义的 DataGridView 实现,允许您向其单元格添加复杂公式。

公式引擎的实现基于 BNFUP 通用编译器,具体解释请参见本文

FormulaDataGridView 项目包含网格本身的实现,而 GridFormulas 项目用于公式引擎的实现。BNFUP 项目对于提供解析和编译服务是必需的。

因此,如果您计划在项目中使用此网格,则必须引用三个不同的类库:BNFUP.dllFormulaDataGridView.dllGridFormulas.dll

公式以纯文本字符串形式提供,是可以使用 -+*/^(幂运算)运算符的算术表达式。

您可以使用这些运算符的元素类型如下:

  • 数字,可以包含小数。
  • 变量。有两个变量,col 包含包含公式的单元格的列索引,row 包含行索引。
  • 单元格引用。您可以通过在括号 ([]) 中引用网格的单元格来获取其值。要指示列和行,您可以使用任何表达式,但值将被四舍五入为整数。用冒号字符 (:) 分隔两个表达式。第一个位置用于列索引,第二个位置用于行,例如:[col:row-1] 指的是包含公式的单元格上方单元格。
  • 数学函数。它们只有一个参数,可以是任何算术表达式。允许的函数有 abs(绝对值)、ceil(大于或等于参数的最小整数)、floor(小于或等于参数的最大整数)、ln(自然对数)、log(以 10 为底的对数)、sin(正弦)、cos(余弦)、tan(正切)、exp(e 的参数次幂)和 sqrt(平方根)。例如,使用 sqrt([0:0]),您可以获得网格中第一个单元格的平方根。
  • 聚合函数。它们有两个逗号分隔的参数,必须是单元格引用。第一个参数指示要处理的第一行,第二个参数指示最后一个单元格。由这两个单元格定义的矩形中的所有单元格都将由该函数处理。可用的函数有 maxmin(最大值和最小值)、sumprod(值的总和和乘积)、avg(算术平均值)、varsvar(方差和样本方差)以及 sdssd(标准差和样本标准差)。例如,sum([0:row],[col-1:row]) 计算公式左侧所有单元格的总和。

DataGridView 非常易于使用,您几乎不需要编写几行代码,但为了更好地理解,我已将 TestForm 项目添加到解决方案中,该项目展示了如何使用所有网格功能。您可以在我的博客的这篇文章中阅读有关此项目的更多信息,西班牙语版请点击此处

Using the Code

实际上,FormulaDataGridView 控件只不过是一个普通的 DataGridView。没有添加自定义单元格类型或其他复杂的东西。它可以像往常一样使用。公式由网格内部管理,使用一个将它们与单元格坐标链接起来的 Dictionary,我只使用了几个新的属性和方法来扩展其功能。以下是它们的列表:

public string LanguageFile { get; set; }
public TextBox FormulaEditor { get; set; }
public FormulaBase GetFormula(int col, int row);
public void SaveCSV(string filename);
public void ReadCSV(string filename);
public void UpdateFormula();
public void UpdateFormula(string formula, int col, int row);
public void BindFormulas();
public void Initialize();

通过 FormulaEditor 属性,您可以提供一个 TextBox 控件,允许用户编辑网格中的公式。当公式更改后,您必须使用不带参数的 UpdateFormula 方法来编译它。如果文本为空字符串,则公式将从网格中所有选定的单元格中删除;否则,每个选定的单元格都将获得一个编译公式的实例。它们都是不同的,因此,如果您更改其中任何一个,其余的将保持不变。

您还可以使用带参数的 UpdateFormula 方法版本以编程方式更改单元格的公式。您必须提供一个包含公式的 string 以及单元格的列和行索引。

如果您将 DataGrid 绑定到某种数据源,您也可以通过将公式作为以 = 字符开头的文本字符串来提供。在这种情况下,您必须调用 BindFormulas 方法来指示网格编译它们。例如,考虑一个如下的 SQL 命令:

SELECT Q1, Q2, Q3, Q4, '=sum([0:row],[col-1:row])' as Total FROM TABLE;

如果您使用这样的命令填充网格,将创建五列,最后一列将是一个计算列,包含每行前四列的总和。

使用 Initialize 方法,您可以清除网格中的所有公式。

SaveCSVReadCSV 方法可用于将网格数据保存或从 csv 文件加载。该文件的第一行将包含列标题,字段必须用分号字符 (;) 分隔,该字符在公式中任何地方都未使用。如果单元格具有关联的公式,则公式将以文本形式保存到文件中,在相应单元格位置以 = 字符开头。

GetFormula 方法返回与给定单元格关联的公式,如果单元格没有公式,则返回 null。公式具有在 GridFormulas 项目中定义的通用 FormulaBase 类型。从该类中,您只需要知道 CellReferences 属性,它以 Point 结构的形式枚举公式中引用的所有单元格(如果您希望在选择包含公式的单元格时向用户提供反馈),以及 Value 属性,它返回公式的结果,和 AsString 属性,它以文本字符串形式提供公式。

该项目的真正复杂性不在于 DataGridView 本身,而在于 GridFormulas 项目。由于您拥有源代码,因此可以通过添加更多函数来扩展公式中使用的语言。为此,您必须处理 BNF 规则和 BNFUPEditor 工具,该工具允许您定义用于编译它们的语言。请参阅上面引用的文章,了解如何使用此工具。这是项目中当前实现的语言的 BNF 格式定义:

<number>::=<digit>[<rnumber>]
    |<decimalsep><rdecimal>;
<rnumber>::=<digit>[<rnumber>]
    |<decimalsep><rdecimal>;
<rdecimal>::=<digit>[<rdecimal>];
<digit>::={0-9};
<decimalsep>::={,\.};
<variable>::='col','row';
<cell>::='['<expr>'';''<expr>']';
<function>::='sum','avg','max','min','prod','var','sd','svar','ssd' '('<cell>','<cell>')'
    |'sqrt','ln','exp','sin','cos','tan','abs','ceil','floor','log' '('<expr>')';
<<expr>>::=<expr2>['+','-'<expr>];
<expr2>::=<expr1>['*','/'<expr2>];
<expr1>::=<expr0>['^'<expr1>];
<expr0>::=['-']<element>;
<element>::=<pexpr>
    |<number>
    |<variable>
    |<cell>
    |<function>;
<pexpr>::='('<expr>')';

GridFormulas 项目中实现函数的类是 Function。如果您计划向语言添加更多函数,请使用 BNFUPEditor 规则编辑器修改 functions.bnf 文件(在 TestForm 项目目录中)中的规则,并将新的函数名称和实现添加到 Function 类。以下是您必须为此考虑的方法:

public override bool AddItem(ICompilableObject item);
public override double Value { get; set; }
private double PerformFunction(int c1, int r1, int c2, int r2);
private double PerformFunction();

当对象构建时,会调用 AddItem 方法。在这里,您必须验证函数名称。在 Value 属性中,您必须返回函数结果的值。PerformFunction 方法是您必须实现函数本身的地方。有两个版本。不带参数的版本用于数学函数,它们从 _exp 变量获取参数。带参数的版本用于聚合函数,参数是处理单元格的列和行的起始和结束索引。

修改了语言语法和 GridFormulas.dll 库后,您有两种选择。一种是使用 FormulaDataGridView 控件的 LanguageFile 属性来传递此文件的路径,以便网格使用扩展语言。另一种是替换 FromulaDataGridView 项目资源文件中的 .bnf 语言文件,并让网格在创建时使用它来构建规则表。

就这样!我认为这个控件非常易于使用,希望它能对某些人有所帮助,以额外的功能改进他们的应用程序。

感谢阅读!!!

历史

  • 2017年1月14日:初始版本
© . All rights reserved.