DataGridView con soporte para fórmulas
En este artículo voy a presentar un control DataGridView personalizado que permite utilizar fórmulas compuestas por expresiones aritméticas y referencias a las diferentes celdas del control. Además de la librería de clases con el control, he preparado una pequeña aplicación para mostrar cómo utilizar las diferentes características que ofrece.
En este enlace puedes descargar el código fuente del proyecto FormulaDataGridView, escrito en CSharp con Visual Studio 2015.
El proyecto consta de tres librerías de clases. FormulaDataGridView es la implementación del control en sí, BNFUP implementa el compilador de fórmulas, y GridFormulas contiene el motor de ejecución de las fórmulas compiladas. En este artículo comentaré el uso del DataGridView, en este otro artículo leer más sobre la definición de la sintaxis y editor de reglas del compilador universal BNFUP.
Sintaxis de las fórmulas
Las fórmulas que se pueden utilizar con el control se asignan a cada una de las celdas del DataGridView, consisten en expresiones aritméticas que pueden utilizar los siguientes operadores: resta y menos unario (-), suma (+), producto (*), cociente (/) y exponenciación (^). También se pueden utilizar subfórmulas entre paréntesis.
Los elementos con los que se puede operar son los siguientes:
- Números: que pueden tener decimales.
- Variables: Existen dos variables, col es el índice de la columna en la que se encuentra la fórmula, y row es el índice de la fila.
- Referencias a celda: se escriben entre corchetes ([ y ]) y las coordenadas de la columna y la fila se separan con dos puntos (:), primero la columna y luego la fila. Las coordenadas pueden ser cualquier expresión, pero su valor se redondea a un valor entero. Por ejemplo, [col:row-1] hace referencia a la celda situada sobre la actual. Su valor es el valor de la celda. Si la celda está vacía o su valor no es numérico, se ignora.
- Funciones de agregación: Estas funciones procesan un rango de celdas. Todas tienen dos parámetros, separados por el carácter coma (,), el primero debe ser la referencia a la primera celda a procesar y el segundo la referencia a la última celda, pudiendo estar en filas diferentes. Las funciones disponibles son: sum (sumatorio de los valores de las celdas), prod (producto), avg (media aritmética), max y min (que devuelven el valor máximo y mínimo respectivamente), var y svar (varianza y varianza muestral) y sd y ssd (desviación típica y desviación típica muestral). Por ejemplo, sum([0:0],[col-1:row]).
- Funciones matemáticas: Solo tienen un parámetro, que puede ser cualquier expresión aritmética. Existen las siguientes funciones: ln (logaritmo neperiano), log (logaritmo en base 10), exp (el número e elevado al argumento), sqrt (raíz cuadrada), abs (valor absoluto), ceil (el menor entero mayor o igual que el número), floor (el mayor entero menor o igual que el número) sin (seno), cos (coseno) y tan (tangente). Por ejemplo sqrt([0:0]*[1:0]+ln(5)).
La sintaxis que define el lenguaje se encuentra en el archivo formulas.bnf, en el proyecto TestForm. Es un archivo binario, que se puede modificar para extender el lenguaje. Para abrirlo necesitarás el editor BNFUPEditor, que puedes descargar del artículo mencionado anteriormente sobre el proyecto BNFUP.
Para extender el lenguaje también es necesario modificar las clases del proyecto GridFormulas. En el artículo anterior también se explica todo lo necesario para modificar este tipo de proyectos.
Aplicación TestForm
La aplicación de ejemplo TestForm muestra cómo utilizar el control en tus propios proyectos. Este es el aspecto que presenta:
Los tres primeros controles se utilizan para añadir columnas y filas al DataGridView, para añadir una nueva columna, escribe un nombre en el cuadro de texto y pulsa Add Column. Puedes añadir filas una vez que exista alguna columna con New Row.
El botón Data Bind simplemente crea un objeto DataTable con tres columnas y dos filas, con dos fórmulas en la última columna y lo enlaza al control en la propiedad DataSouce. Puedes utilizar este ejemplo para ver como pasar las fórmulas al control DataGridView directamente desde los datos. Este es el código del controlador del evento:
DataTable dt = new DataTable();
dt.Columns.Add("A0");
dt.Columns.Add("A1");
dt.Columns.Add("Total");
DataRow row = dt.NewRow();
row["A0"] = 1;
row["A1"] = 3;
row["Total"] = "=sum([0:row],[col-1:row])";
dt.Rows.Add(row);
row = dt.NewRow();
row["A0"] = 5;
row["A1"] = 7;
row["Total"] = "=avg([0:row],[col-1:row])";
dt.Rows.Add(row);
frmGrid.Columns.Clear();
frmGrid.DataSource = dt;
frmGrid.BindFormulas();
Las fórmulas se deben pasar con un carácter = al principio, para que las reconozca como tales. Una vez enlazado el control basta llamar al método BindFormulas para que sean convertidas a fórmulas compiladas. Este es el resultado:
Al seleccionar una celda con una fórmula, esta aparece en el control TextBox asociado al FormulaDataGridView, mediante la propiedad FormulaEditor. Puedes modificar la fórmula y volverla a compilar con el botón que hay a la derecha del TextBox. Este botón simplemente llama al método UpdateFormula, sin parámetros.
También puedes actualizar la fórmula de una determinada celda programáticamente con otra versión del método UpdateFormula, que recibe como parámetros el texto de la fórmula, la columna y la fila de la celda.
Puedes ver que las celdas referenciadas por la fórmula aparecen resaltadas en amarillo. Esto se implementa en el método ShowReferencedCells de la aplicación TestForm:
private void ShowReferencedCells()
{
foreach (DataGridViewRow row in frmGrid.Rows)
{
foreach (DataGridViewCell cell in row.Cells)
{
cell.Style.BackColor = Color.White;
}
}
foreach (DataGridViewCell cell in frmGrid.SelectedCells)
{
FormulaBase frm = frmGrid.GetFormula(cell.ColumnIndex, cell.RowIndex);
if (frm != null)
{
foreach (Point p in frm.CellReferences)
{
frmGrid.Rows[p.Y].Cells[p.X].Style.BackColor = Color.Yellow;
}
}
}
}
La fórmula la podemos obtener con el método GetFormula del control FormulaDataGridView, pasando el índice de la columna y de la fila. El objeto que nos devuelve es de la clase FormulaBase, que tiene una propiedad CellReferences que enumera todas las celdas referenciadas en forma de estructuras Point, con la columna en la propiedad X y la fila en la propiedad Y.
Si el editor de fórmulas está vacío al llamar al método UpdateFormula, la fórmula se eliminará, aunque su valor permanecerá en la celda.
Los otros dos botones son para guardar el contenido del FormulaDataGridView en un archivo csv y para cargar los datos desde un archivo del mismo tipo. La primera fila del archivo contendrá los nombres de las columnas y el resto los datos. Las celdas con fórmulas se guardan con el texto de la fórmula precedido de un carácter =. El carácter que se utiliza para separar los campos es el punto y coma (;), que no se utiliza en ninguna de las expresiones de las fórmulas, por lo que si tus datos contienen este carácter, el fichero no se guardará correctamente.
El control FormulaDataGridView tiene dos métodos para guardar este tipo de ficheros, SaveCSV y ReadCSV, a los que se les pasa como parámetro el nombre del archivo. Puedes cargar un archivo de muestra que se encuentra en el directorio de la aplicación TestForm, gridsample.csv.
Por último, existe un método, Initialize, que borra todas las fórmulas del control FormulaDataGridView, y una propiedad LanguageFile con la que puedas cambiar el archivo con la sintaxis que utiliza el control para las fórmulas, que debe ser un archivo .bnf, generada por la herramienta BNFUPEditor. Simplemente asigna a esta propiedad una cadena de texto con la ruta del archivo.
Y eso es todo lo que es necesario saber para poder utilizar este control en tus aplicaciones. Recuerda añadir las referencias a los ensamblados FormulaDataGridView.dll, BNFUP.dll y GridFormulas.dll en tu proyecto.