Apply Formula Cell to a DataGridview

后端 未结 4 1834
独厮守ぢ
独厮守ぢ 2020-12-29 09:49

I want to add formula cell in a DataGridView. Is there any custom DataGridView to do this?

Example:

grid[4, column].Text =         


        
相关标签:
4条回答
  • 2020-12-29 10:16

    A couple of things you could try:

    Handle the cell value changed event to detect when one of the cells used in the calculation changes and calculate the appropriate value. Something like this (try it in a new project)

    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
    

    Or rather than reading your file direct to the datagridview read it into a datatable and bind the grid to the datatable. You can then add an expression column in the datatable to do the calculation. Something like this:

    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
    
    0 讨论(0)
  • 2020-12-29 10:22

    Is there any custom DataGridView to do this?

    Off-topic, but if you are looking for a custom control, take a look at Free .NET Spreadsheet Control. Also it supports formula.

    If writing code for calculation is an option for you

    If writing code for calculation is an option for you, to calculate value of a cell based on values of some other cells you can use CellFormatting event of DataGridView and put calculation logic there. Also handle CellEndEdit and call InvalidateCell or Invalidate to force update value of cell after each in reference cells.

    Here is an example:

    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();
        }
    }
    

    Note

    The solution is not limited to DataTable, it will work regardless of the DataSource which you use for DataGridView and you can use any kind of data source in this solution.

    0 讨论(0)
  • 2020-12-29 10:25

    No, you are dealing with pure data strings, you need to have a thread running in the background to do the calculations and update the UI accordingly.

    0 讨论(0)
  • 2020-12-29 10:31

    In many, many cases the data is not actually in the DataGridView, but elsewhere like a DataTable or a collection of some sort (like List<T>). The control simply presents a view of the data to the user.

    There are several ways to do something along the lines of what you want. For both of these, the data actually resides in a DataTable.

    Expressions

    A DataColumn can be assigned an Expression. Consult the link for the types of Expression, keywords, operators and functions supported. The following will create an expression based columns to multiple Quantity * Price for some rows:

    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)";
    

    After some random data is added, as well as an empty row, the DataTable will maintain those columns for you. This works like you probably want it to: if the user (or code) changes the value of a Quantity or Price cell, the Sale column contents is automatically updated. (an image is later after the second method).

    Expressions work at the row level. There is not an all-rows/table-wise counter part for something like a TOTALS row - this is because the data would often come from a DataSource. Adding calculated rows could accidentally add new data to that source (like a DB). But it is not hard to do in code:

    Event Driven Calculations

    Similar to the DGV CellFormatting answer given, you can respond to events from the DataTable such as RowChanged. There you can perform whatever operations and update the table.

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

    Then in the event, the code calculates an over all per unit average to display in the last row. In some cases, you may be able to use the Compute() method of the DataTable. Unlike an Expression, it isnt updated automatically and as shown in this answer it can be clumsy to update.

    With typed data in a DataTable, it is fairly easy to perform calculations in response to events:

    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;
    }
    

    The "Sales" column is automatically maintained via an Expression which means you cannot manually do anything to that column.

    The overall average price at the bottom is also "automatically" updated, the difference is that we had to write a smattering of code to do so.

    0 讨论(0)
提交回复
热议问题