【问题标题】:Apply Formula Cell to a DataGridview将公式单元格应用于 DataGridview
【发布时间】:2016-08-09 13:38:45
【问题描述】:

我想在DataGridView 中添加公式单元格。是否有任何自定义 DataGridView 可以做到这一点?

例子:

grid[4, column].Text = string.Format("=MAX({0}6:{0}{1})", columnAsString, grid.RowCount);

【问题讨论】:

标签: c# winforms datagridview formula


【解决方案1】:

在很多很多情况下,数据实际上并不在DataGridView 中,而是在其他地方,例如DataTable 或某种集合(例如List<T>)。该控件只是向用户呈现数据视图。

有几种方法可以按照您的意愿进行操作。对于这两种情况,数据实际上驻留在DataTable 中。

表达式

可以为DataColumn 分配一个Expression。有关支持的表达式类型、关键字、运算符和函数,请参阅链接。以下将为某些行创建一个基于表达式的列到多个Quantity * Price

dtSample = new DataTable();
dtSample.Columns.Add(new DataColumn("Item", typeof(string)));
dtSample.Columns.Add(new DataColumn("Quantity", typeof(int)));
dtSample.Columns.Add(new DataColumn("Price", typeof(decimal)));
dtSample.Columns.Add(new DataColumn("Sale", typeof(decimal)));

// assign expression using the col names
dtSample.Columns[3].Expression = "(Quantity * Price)";

添加一些随机数据以及空行后,DataTable 将为您维护这些列。这就像您可能希望的那样:如果用户(或代码)更改了QuantityPrice 单元格的值,Sale 列的内容会自动更新。 (图片在第二种方法之后)。

Expressions 在行级别工作。没有像 TOTALS 行这样的全行/表格计数器部分 - 这是因为数据通常来自DataSource。添加计算行可能会意外地将新数据添加到该源(如数据库)。但是在代码中并不难做到:

事件驱动计算

与给出的 DGV CellFormatting 答案类似,您可以响应来自DataTable 的事件,例如RowChanged。在那里您可以执行任何操作并更新表格。

...create table and columns
...populate table
// hook up event
dtSample.RowChanged += RowChanged;

然后,在事件中,代码会计算每个单位的总体平均值以显示在最后一行。在某些情况下,您可以使用DataTableCompute() 方法。与Expression 不同,它不会自动更新,而as shown in this answer 更新起来可能很笨拙。

使用 DataTable 中的类型数据,执行计算以响应事件是相当容易的:

private void RowChanged(object sender, DataRowChangeEventArgs e)
{
    // number of rows used 
    int Rows = dtSample.Rows.Count-1;
    if (e.Row == dtSample.Rows[Rows]) return;

    // display TotalSales / TotalUnits
    // get the units
    int TotUnits = dtSample
        .AsEnumerable()
        .Where(r => !r.IsNull("Quantity"))
        .Take(Rows)
        .Sum(n => n.Field<int>("Quantity"));
    
    // sum Sales, divide and display in DGV
    dtSample.Rows[Rows]["Price"] = dtSample
        .AsEnumerable()
        .Where(r => !r.IsNull("Sale"))
        .Take(Rows)
        .Sum(n => n.Field<decimal>("Sale")) / TotUnits;
}

“销售”列通过Expression 自动维护,这意味着您无法手动对该列执行任何操作。

底部的整体平均价格也是“自动”更新的,不同之处在于我们必须编写少量代码才能做到这一点。

【讨论】:

    【解决方案2】:

    是否有任何自定义 DataGridView 可以做到这一点?

    题外话,但如果您正在寻找自定义控件,请查看Free .NET Spreadsheet Control。还支持formula

    如果您可以选择编写计算代码

    如果您可以选择编写计算代码,要根据其他一些单元格的值计算单元格的值,您可以使用DataGridViewCellFormatting 事件并将计算逻辑放在那里。还要处理 CellEndEdit 并调用 InvalidateCellInvalidate 以强制更新参考单元格中的每个单元格的值。

    这是一个例子:

    void Form1_Load(object sender, EventArgs e)
    {
        Random r = new Random();
        var dt = new DataTable();
        dt.Columns.Add("A", typeof(int));
        dt.Columns.Add("B", typeof(int));
        for (int i = 0; i < 10; i++)
            dt.Rows.Add(r.Next(100));
        grid.DataSource = dt;
        grid.CellFormatting += grid_CellFormatting;
        grid.CellEndEdit += grid_CellEndEdit;
    }
    void grid_CellEndEdit(object sender, DataGridViewCellEventArgs e)
    {
        grid.Invalidate();
    }
    void grid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
    {
        var grid = sender as DataGridView;
        var parameterColumnName = "A";       //Parameter column name
        var start = 0;                       //Start row index for formula
        var end = grid.RowCount - 1;         //End row index for formula
        var resultRowIndex = 0;              //Result row index
        var resultColumnName = "B";          //Result column name
        if (e.RowIndex == resultRowIndex &&
            grid.Columns[e.ColumnIndex].Name == resultColumnName)
        {
            var list = Enumerable.Range(start, end - start + 1)
                  .Select(i => grid.Rows[i].Cells[parameterColumnName].Value)
                  .Where(x => x != null && x != DBNull.Value)
                  .Cast<int>();
            if (list.Any())
                e.Value = list.Max();
        }
    }
    

    注意

    该解决方案不限于DataTable,无论您将DataSource 用于DataGridView,它都可以工作,并且您可以在此解决方案中使用任何类型的数据源。

    【讨论】:

    • 哪种方法更快?使用特定控件还是最后一个控件?
    • 事件方法足够快。链接控件我没试过,不过好像功能挺多的。
    • 顺便说一句,您只能选择一个答案作为接受。但是,您可以对任何您认为有用的答案进行投票,包括被接受的答案。目前,您在接受该帖子后将其设为未接受:)
    • 此外,该解决方案不仅限于DataTable,无论您将DataSource 用于DataGridView,它都可以工作。
    • 我在这些状态下检查了DataGridView:绑定到List&lt;T&gt;,绑定到DataTable,没有DataSource,并使用RowCountColumnCount 创建列和行,没有@ 987654341@ 并使用Columns.AddRows.Add 创建列和行,并且在所有情况下都可以正常工作。我怀疑你把grid.CellFormatting += grid_CellFormatting; 放在一个不运行的地方。例如,您可能忘记将Form1_Load 分配给Load 事件Form
    【解决方案3】:

    不,您正在处理纯数据字符串,您需要在后台运行一个线程来进行计算并相应地更新 UI。

    【讨论】:

      【解决方案4】:

      您可以尝试几件事:

      处理单元格值更改事件以检测计算中使用的单元格之一何时更改并计算适当的值。像这样(在新项目中尝试)

      Public Class Form1
        Private WithEvents DGV As New DataGridView
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
          DGV.SetBounds(20, 50, 400, 200)
          Controls.Add(DGV)
          DGV.Columns.Add("Qty", "Qty")
          DGV.Columns.Add("Price", "Price")
          DGV.Columns.Add("Total", "Total")
          DGV.Columns("Total").ReadOnly = True
          DGV.RowCount = 5
        End Sub
      
        Private Sub DGV_CellValueChanged(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles DGV.CellValueChanged
          If e.ColumnIndex = DGV.Columns("Qty").Index Or e.ColumnIndex = DGV.Columns("Price").Index Then
            DGV("Total", e.RowIndex).Value = CInt(DGV("Qty", e.RowIndex).Value) * CDbl(DGV("Price", e.RowIndex).Value)
          End If
        End Sub
      End Class
      

      或者不是直接将文件读取到 datagridview 中,而是将其读取到数据表中并将网格绑定到数据表。然后,您可以在数据表中添加一个表达式列来进行计算。像这样的:

      Public Class Form1
        Private WithEvents DGV As New DataGridView
        Private DT As New DataTable
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
          DGV.SetBounds(20, 50, 400, 200)
          Controls.Add(DGV)
          DT.Columns.Add("Qty", GetType(Int32))
          DT.Columns.Add("Price", GetType(Double))
          DT.Columns.Add("Total", GetType(Double))
          DT.Columns("Total").Expression = "Qty * Price"
          DGV.DataSource = DT
        End Sub
      End Class
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-12
        • 2019-07-28
        相关资源
        最近更新 更多