I have two columns in a excel sheet. I am populating the first column with a formula and I have some values in second column. Now I want to compare these two columns and need to display True/false in third column. But when I use 'IF' condition all I am getting is FALSE. Here is my code.
Formulating the column
using (ExcelPackage xlPackage = new ExcelPackage(newFile))
{
ExcelWorksheet worksheet = xlPackage.Workbook.Worksheets[GetConfigValue("Reconsheet")];
int totalRows = worksheet.Dimension.End.Row;
for (int row = startupRow; row <= totalRows; row++)
{
//Formula
string vlookforH = "IF(ISNA(VLOOKUP(C" + row + ",PWA!A:B,2,FALSE)),0,VLOOKUP(C" + row + ",PWA!A:B,2,FALSE))";
worksheet.Cells[row, 8].Formula = vlookforH;
}
xlPackage.Save();
MessageBox.Show("PWA hours received");
}
Comparing Formula column and Normal value column:
for (int row = startupRow; row <= totalRows; row++)
{
if (Convert.ToInt32(worksheet.Cells[row, 18].Value) != 0)
{
decimal hvalue = (worksheet.Cells[row, 8].Value) != null ? Convert.ToDecimal(worksheet.Cells[row, 8].Value.ToString()) : 0;
decimal rvalue = (worksheet.Cells[row, 18].Value) != null ? Convert.ToDecimal(worksheet.Cells[row, 18].Value.ToString()) : 0;
if (hvalue == rvalue)
{
worksheet.Cells[row, 31].Value = "True";
}
else
{
worksheet.Cells[row,31].Value = "False";
Count = Count + 1;
}
}
}
When I am debugging the application, I realized hvalue is always zero because it's a formula column.
I've tried in different ways, but unable to find the solution. Can anyone help me? What am I doing wrong?
You will have to call worksheet.Calculate(); after writing the formulas into the cells to actually calculate the values. Calling it once after your first for-loop is enough.
I verified that on a test-project.
EDIT:
If worksheet.Calculate() does not work you can try xlPackage.Workbook.Calculate();
Here is a link to the documentation: EPPlus Calculate Documentation
My code is save datagrid to csv. when process to
value = dr.Cells[i].Value.ToString();
Error message is the following:System.Windows.Forms.DataGridViewCell.Value.get return null.
Then, I add corner case to check cell is null and replace those cells by "Null".
foreach (DataGridViewRow rw in this.dataGridView1.Rows)
{
for (int i = 0; i < rw.Cells.Count; i++)
{
if (rw.Cells[i].Value == System.DBNull.Value)
{
swOut.Write("Null");
}
}
}
But the error message is there still.
The following is my code:
public void writeCSV(DataGridView gridIn, string outputFile)
{
//test to see if the DataGridView has any rows
if (gridIn.RowCount > 0)
{
string value = "";
DataGridViewRow dr = new DataGridViewRow();
StreamWriter swOut = new StreamWriter(outputFile);
foreach (DataGridViewRow rw in this.dataGridView1.Rows)
{
for (int i = 0; i < rw.Cells.Count; i++)
{
if (rw.Cells[i].Value == System.DBNull.Value)
{
swOut.Write("Null");
}
}
}
//write header rows to csv
for (int i = 0; i <= gridIn.Columns.Count - 1; i++)
{
if (i > 0)
{
swOut.Write(",");
}
swOut.Write(gridIn.Columns[i].HeaderText);
}
swOut.WriteLine();
//write DataGridView rows to csv
for (int j = 0; j <= gridIn.Rows.Count - 1; j++)
{
if (j > 0)
{
swOut.WriteLine();
}
dr = gridIn.Rows[j];
for (int i = 0; i <= gridIn.Columns.Count - 1; i++)
{
if (i > 0)
{
swOut.Write(",");
}
value = dr.Cells[i].Value.ToString();
//replace comma's with spaces
value = value.Replace(',', ' ');
//replace embedded newlines with spaces
value = value.Replace(Environment.NewLine, " ");
swOut.Write(value);
}
}
swOut.Close();
}
}
The current code appears to work… IF the DataGridView.AllowUserToAddRows is false! The default is true. If the grid allows users to add rows, the code will crash at the line…
value = dr.Cells[i].Value.ToString();
when it hits the “new” row. The cells in the “new” row are null not DBNull. If you want to allow the user to add rows (which I assume is the case since the code is stripping out commas and new lines), then the code will need to check for this “new” row and ignore it when exporting the grid.
With that said, I believe you are making this way more complicated than it has to be. The goal is to export the cells in a DataGridView to a comma delimited file (CSV). This can be done with much less code and still avoid the dreaded null values in the cells.
From the perspective of the CSV file, if a cell is “null” that means that we want to output an “empty” string to the CSV file. This will maintain the column schema. Therefore, a simple double loop through the grids cells is all that needs to be done. While looping through the cells and writing the values to the file, a simple check is needed before we try and grab a cells value.ToString(). If value is null, the code will crash as a null does not have a ToString() method. Therefore if value “is” null then write an empty string to the file… problem solved!
Therefore, to help, I recommend you use a different strategy to export the grids cells. There appears no reason for the dr variable nor the value variable. In addition, I would assume that the cells text does “NOT” contain commas (,). If you “know” the grid is going to be exported to a CSV file… I would set the grids cells such that the user would “not” be able to type a comma. Therefore, the code below does not strip out commas or new lines. Hope that makes sense.
public void writeCSV(DataGridView gridIn, string outputFile) {
try {
using (StreamWriter swOut = new StreamWriter(outputFile)) {
//write header rows to csv
for (int i = 0; i < gridIn.Columns.Count; i++) {
swOut.Write(gridIn.Columns[i].HeaderText);
if (i < gridIn.ColumnCount - 1) {
swOut.Write(",");
}
else {
swOut.WriteLine();
}
}
//write DataGridView rows to csv
for (int row = 0; row < gridIn.Rows.Count; row++) {
if (!gridIn.Rows[row].IsNewRow) {
for (int col = 0; col < gridIn.Columns.Count; col++) {
if (dataGridView1.Rows[row].Cells[col].Value != null) {
swOut.Write(dataGridView1.Rows[row].Cells[col].Value.ToString());
}
else {
swOut.Write("");
}
if (col < gridIn.Columns.Count - 1) {
swOut.Write(",");
}
else {
swOut.WriteLine();
}
}
}
}
}
}
catch (Exception e) {
MessageBox.Show("Error: " + e.Message);
}
}
It is possible to somehow delete all following rows from specific (empty) row ? I tried for cyclus
for (int rowNum = 1; rowNum <= worksheet.Dimension.End.Row; rowNum++)
{
var rowCells = from cell in worksheet.Cells
where (cell.Start.Row == rowNum)
select cell;
if (!rowCells.Any(cell => cell.Value != null))
{
worksheet.DeleteRow(rowNum);
}
}
but it takes minutes if in excel are millions of empty rows.
Epplus offer this method worksheet.DeleteRow(int rowFrom, int rows) but i do not know the count of all additional empty rows.
In following example i need to delete all rows 12+ but the problem is that i do not know the specific row, where the empty rows begin.
The alternative aproach can be finding last non empty row and delete everything with the range, which will be faster, but there is another issue with empty row inside the table.
ws.DeleteRow(lastFilledTableRow, workSheet.Dimension.End.Row - tableRowsCount,true);
In this example the problem is the red row but maybe i will tell the users that this kind of excel format is invalid and circumvent the problem.
I know that it is old but I could not find any solution so made one my by own.
It is checking the last row if it is empty and if yes it deletes it and doing this until finds non-empty row. (non-empty means here: all columns in this row have some value)
worksheet.TrimLastEmptyRows();
public static void TrimLastEmptyRows(this ExcelWorksheet worksheet)
{
while (worksheet.IsLastRowEmpty())
worksheet.DeleteRow(worksheet.Dimension.End.Row);
}
public static bool IsLastRowEmpty(this ExcelWorksheet worksheet)
{
var empties = new List<bool>();
for (int i = 1; i <= worksheet.Dimension.End.Column; i++)
{
var rowEmpty = worksheet.Cells[worksheet.Dimension.End.Row, i].Value == null ? true : false;
empties.Add(rowEmpty);
}
return empties.All(e => e);
}
Above solution is to delete last empty rows in the file. This will not work if file has empty rows in the middle of the rows list somewhere.
Below is the solution to identify the empty rows in the middle of the rows list.
I used combination of both above and mine to delete empty rows at the end of the rows list and empty rows in the middle of the rows list
private void TrimEmptyRows(ExcelWorksheet worksheet)
{
//loop all rows in a file
for (int i = worksheet.Dimension.Start.Row; i <=
worksheet.Dimension.End.Row; i++)
{
bool isRowEmpty = true;
//loop all columns in a row
for (int j = worksheet.Dimension.Start.Column; j <= worksheet.Dimension.End.Column; j++)
{
if (worksheet.Cells[i, j].Value != null)
{
isRowEmpty = false;
break;
}
}
if (isRowEmpty)
{
worksheet.DeleteRow(i);
}
}
}
I have user supplied excel files that need to be converted to PDF. Using excel interop, I can do this fine with .ExportAsFixedFormat(). My problem comes up when a workbook has millions of rows. This turns into a file that has 50k+ pages. That would be fine if the workbook had content in all of those rows. Every time one of these files shows up though, there are maybe 50 rows that have content and the rest are blank. How can I go about removing the empty rows so I can export it to a decent sized PDF?
I've tried starting at the end row and, one-by-one, using CountA to check if the row has content and if it does, delete it. Not only does this take forever, this seems to fail after about 100k rows with the following error:
Unable to evaluate expression because the code is optimized or a native frame is on top of the call stack.
I've tried using SpecialCells(XlCellType.xlCellTypeLastCell, XlSpecialCellsValue.xlTextValues) but that includes a row if any cell has formatting (like a bg color).
I've tried using Worksheet.UsedRange and then deleting everything after that but UsedRange has the same problem as point two.
This is the code I've tried:
for (int i = 0; i < worksheets.Count; i++)
{
sheet = worksheets[i + 1];
rows = sheet.Rows;
currentRowIndex = rows.Count;
bool contentFound = false;
while (!contentFound && currentRowIndex > 0)
{
currentRow = rows[currentRowIndex];
if (Application.WorksheetFunction.CountA(currentRow) == 0)
{
currentRow.Delete();
}
else
{
contentFound = true;
}
Marshal.FinalReleaseComObject(currentRow);
currentRowIndex--;
}
Marshal.FinalReleaseComObject(rows);
Marshal.FinalReleaseComObject(sheet);
}
for (int i = 0; i < worksheets.Count; i++)
{
sheet = worksheets[i + 1];
rows = sheet.Rows;
lastCell = rows.SpecialCells(XlCellType.xlCellTypeLastCell, XlSpecialCellsValue.xlTextValues);
int startRow = lastCell.Row;
Range range = sheet.get_Range(lastCell.get_Address(RowAbsolute: startRow));
range.Delete();
Marshal.FinalReleaseComObject(range);
Marshal.FinalReleaseComObject(lastCell);
Marshal.FinalReleaseComObject(rows);
Marshal.FinalReleaseComObject(sheet);
}
Do I have a problem with my code, is this an interop problem or maybe it's just a limitation on what Excel can do? Is there a better way to do what I'm attempting?
I would suggest you to get the count of rows which contain some values, using CountA (as you have tried in point 1). Then copy those rows into a new sheet and export it from there. It will be easier to copy few rows to new sheet and working on it, rather than trying to delete huge number of rows from source sheet.
For creating new sheet and copying rows you can use the following code:
excel.Worksheet tempSheet = workbook.Worksheets.Add();
tempSheet.Name = sheetName;
workbook.Save();
//create a new method for copy new rows
//as the rowindex you can pass the total no of rows you have found out using CountA
public void CopyRows(excel.Workbook workbook, string sourceSheetName, string DestSheetName, int rowIndex)
{
excel.Worksheet sourceSheet = (excel.Worksheet)workbook.Sheets[sourceSheetName];
excel.Range source = (excel.Range)sourceSheet.Range["A" + rowIndex.ToString(), Type.Missing].EntireRow;
excel.Worksheet destSheet = (excel.Worksheet)workbook.Sheets[DestSheetName];
excel.Range dest = (excel.Range)destSheet.Range["A" + rowIndex.ToString(), Type.Missing].EntireRow;
source.Copy(dest);
excel.Range newRow = (excel.Range)destSheet.Rows[rowIndex+1];
newRow.Insert();
workbook.Save();
}
Have you tried Sheet1.Range("A1").CurrentRegion.ExportAsFixedFormat() where Sheet1 is a valid sheet name and "A1" is a cell you can test to ensure it is located in the range you want to export?
The question remains, why does Excel think there is data in those "empty" cells? Formatting? A pre-existing print area that needs to be cleared? I know I've encountered situations like that before, those are the only possibilities that come to mind at this moment.
Try these steps -
copy Worksheet.UsedRange to a separate sheet (sheet2).
use paste special so that formatting is retained
try parsing sheet2 for unused rows
If this doesnt help try repeating step 2 with formatting info being cleared and then parsing sheet2. you can always copy format info later (if they are simple enough)
If you can first load the Excel file into a DataSet via the OleDBAdapter, it's relatively easy to remove blank rows on the import...
Try this OleDBAdapter Excel QA I posted via stack overflow.
Then export the DataSet to a new Excel file and convert that file to PDF. That may be a big "IF" though of course depending on the excel layout (or lack there of).
I had to solve this problem today for what might be a subset of your possible cases.
If your spreadsheet meets the following conditions:
All columns with data have header text in line 1.
All rows with data are in sequence until the first BLANK row.
Then, the following code may help:
private static string[,] LoadCellData(Excel.Application excel, dynamic sheet)
{
int countCols = CountColsToFirstBlank(excel, sheet);
int countRows = CountRowsToFirstBlank(excel, sheet);
cellData = new string[countCols, countRows];
string datum;
for (int i = 0; i < countCols; i++)
{
for (int j = 0; j < countRows; j++)
{
try
{
if (null != sheet.Cells[i + 1, j + 1].Value)
{
datum = excel.Cells[i + 1, j + 1].Value.ToString();
cellData[i, j] = datum;
}
}
catch (Exception ex)
{
lastException = ex;
//Console.WriteLine(String.Format("LoadCellData [{1}, {2}] reported an error: [{0}]", ex.Message, i, j));
}
}
}
return cellData;
}
private static int CountRowsToFirstBlank(Excel.Application excel, dynamic sheet)
{
int count = 0;
for (int j = 0; j < sheet.UsedRange.Rows.Count; j++)
{
if (IsBlankRow(excel, sheet, j + 1))
break;
count++;
}
return count;
}
private static int CountColsToFirstBlank(Excel.Application excel, dynamic sheet)
{
int count = 0;
for (int i = 0; i < sheet.UsedRange.Columns.Count; i++)
{
if (IsBlankCol(excel, sheet, i + 1))
break;
count++;
}
return count;
}
private static bool IsBlankCol(Excel.Application excel, dynamic sheet, int col)
{
for (int i = 0; i < sheet.UsedRange.Rows.Count; i++)
{
if (null != sheet.Cells[i + 1, col].Value)
{
return false;
}
}
return true;
}
private static bool IsBlankRow(Excel.Application excel, dynamic sheet, int row)
{
for (int i = 0; i < sheet.UsedRange.Columns.Count; i++)
{
if (null != sheet.Cells[i + 1, row].Value)
{
return false;
}
}
return true;
}
Can you try with below code :
for (int rowIndex = workSheet.Dimension.Start.Row; rowIndex <= workSheet.Dimension.End.Row; rowIndex++)
{
//Assume the first row is the header. Then use the column match ups by name to determine the index.
//This will allow you to have the order of the header.Keys change without any affect.
var row = workSheet.Cells[string.Format("{0}:{0}", rowIndex)];
// check if the row and column cells are empty
bool allEmpty = row.All(c => string.IsNullOrWhiteSpace(c.Text));
if (allEmpty)
continue; // skip this row
else{
//here read header
if()
{
//some code
}
else
{
//some code to read body
}
}
}
Hope this help,else let me know if you need description about code.
Updated :
below code is used to check how many rows are in the worksheet. a for loop will traverse untill end of row of the worksheet.
for (int rowIndex = workSheet.Dimension.Start.Row; rowIndex <= workSheet.Dimension.End.Row; rowIndex++)
here we are checking if the row and column cells are empty using linq:
bool allEmpty = row.All(c => string.IsNullOrWhiteSpace(c.Text));
if (allEmpty)
continue; // if true then skip this row
else
// read headers(assuming it is presented in worksheet)
// else read row wise data
and then do necessary steps.
hoping this clears now.
I had the same problem and managed to fix it using the CurrentRegion:
var lastcell = sheet.Cells.SpecialCells(XlCellType.xlCellTypeLastCell);
var filledcells = sheet.Cells.Range[sheet.Cells.Item[1, 1],
sheet.Cells[lastcell.Row - 1, lastcell.Column]]
.CurrentRegion;
filledcells.ExportAsFixedFormat(
and so on. The CurrentRegion is said to expand to the borders where cells are empty, and apparently that means it also shrinks if it contains many empty cells.
Please try the following code:
for (int i = 0; i < worksheets.Count; i++)
{
sheet = worksheets[i + 1];
sheet.Columns("A:A").SpecialCells(XlCellType.xlCellTypeBlanks).EntireRow.Delete
sheet.Rows("1:1").SpecialCells(XlCellType.xlCellTypeBlanks).EntireColumn.Delete
Marshal.FinalReleaseComObject(sheet);
}
I have datagridview on my winform. I am displaying records in the datagridview. Now after displaying the records on the datagridview, I want to remove the row from datagridview which has one or more empy cells that is no value in the cell for that row. So for that I am checking each cell for every row if there is any cell empty or null then I remove that rows using RemoveAt() function.
My code is :
for (int i = 0; i < dataGridView1.Rows.Count - 1; i++)
{
for (int j = 0; j < dataGridView1.Columns.Count; j++)
{
if (string.IsNullOrEmpty(dataGridView1.Rows[i].Cells[j].Value.ToString()))
{
dataGridView1.Rows.RemoveAt(i);
break;
}
}
}
Then problem is it does not work properly that it does not remove all the rows which has empty cell. So what should I do here ?
The simplest way to do what you want is to loop through the datagridview rows in reverse. This way your indices stay correct.
for (int i = dataGridView1.Rows.Count -1; i >= 0; i--)
{
DataGridViewRow dataGridViewRow = dataGridView1.Rows[i];
foreach (DataGridViewCell cell in dataGridViewRow.Cells)
{
string val = cell.Value as string;
if (string.IsNullOrEmpty(val))
{
if (!dataGridViewRow.IsNewRow)
{
dataGridView1.Rows.Remove(dataGridViewRow);
break;
}
}
}
}
I'm doing a couple of extra things that you may not need to do (the code is just cut and pasted from my test application)
I usually grab the row in question into a DataGridViewRow object.
I am checking the IsNewRow property because my grid was editable.
I am assigning the value to a string variable (with an as to cast it) since the way you had it was throwing an exception for me.