I wanted to remove unwanted columns towards the last or any blank columns without header in between columns, then remove formatting from excel worksheet from each rows and column using EPPlus.
Please find the sample progress.
public static class EpPlusExtension
{
public static string[] GetHeaderColumns(this ExcelWorksheet sheet)
{
List<string> columnNames = new List<string>();
foreach (var firstRowCell in sheet.Cells[sheet.Dimension.Start.Row, sheet.Dimension.Start.Column, 1, sheet.Dimension.End.Column])
columnNames.Add(firstRowCell.Text);
return columnNames.ToArray();
}
public static ExcelWorksheet RemoveCellFormatter(this ExcelWorksheet worksheet)
{
try
{
dynamic cellValue = null;
int lastColumnWithHeaderIndex = worksheet.GetHeaderColumns().Count();
for (int i = worksheet.Dimension.Start.Row; i <= worksheet.Dimension.End.Row; i++)
{
for (int j = worksheet.Dimension.Start.Column; j <= lastColumnWithHeaderIndex; j++)
{
if (worksheet.Cells[i, j].Value != null)
{
cellValue = worksheet.Cells[i, j].Value;
worksheet.Cells[i, j].Clear();
worksheet.Cells[i, j].Value = cellValue;
cellValue = null;
}
}
}
return worksheet;
}
catch (Exception ex)
{
_logger.LogError($"Error Message: Exception While clearing formatting from worksheet. |Exception: {ex.Message}");
return worksheet;
}
}
}
Sample code handles the last unwanted columns:
How to handle also null columns in-between?
Thanks in advance
I am not an EPPlus expert and was unable to find a method that would remove “empty” columns. However, as VDWWD notes, it should not be difficult to loop through the rows of a column and check to see if all the cells in that column are null and if so, then remove that column.
Obviously, if there are many rows, then this may not be the best approach. If there are many rows and columns, there is possibly another option, however, in this case, I will assume the execution time is acceptable. In my tests, the worksheet contained 735 rows and 9 columns with two (2) empty columns and it took less than a second.
Given this, a simple solution is to loop through the columns. In each column iteration, loop through all the rows. We will use two variables: a bool valueFound and set it to false. This will be used to indicate if we found a non null value in a cell. And a List<int> emptyColIndexes is a list of ints to hold the indexes of the columns that are "empty" in the worksheet.
NOTE: it should be obvious that we DO NOT want to “delete” the empty
column as soon as we find one. We only want to store its index and
delete the columns later. The reason for this… is… deleting the column
while “looping” through the columns will obviously cause problems.
Therefore, we only want to get a list of the indexes of the “empty”
columns.
When looping through the rows of a column, IF we find a value that is NOT null, then we set valueFound to true and exit the rows loop using a break command. If we loop through all the rows in a column and exit the loop, we will check the valueFound variable, IF it is false, then this would mean all the row values in that column are null, then we add that columns index to the emptyColIndexes list.
Lastly, after collecting all the “empty” column indexes, we loop through the indexes and delete the columns.
NOTE: this loop needs to start from the “last” column index and move
to the first. This should be obvious being that if we delete a column
on the left of another column in the list, then the indexes will get
messed up. Basically, we want to delete the columns from the right to
left. Therefore, the loop starts at the last index and goes to the
first index.
In addition, and it is not important, however, it appears that returning the worksheet in the RemoveCellFormatter is unnecessary.
Below is the code that demonstrates what is described above.
public static void RemoveEmptyColumns(ExcelWorksheet worksheet) {
try {
List<int> emptyColIndexes = new List<int>();
bool valueFound;
for (int curCol = worksheet.Dimension.Start.Column; curCol <= worksheet.Dimension.End.Column; curCol++) {
valueFound = false;
for (int curRow = worksheet.Dimension.Start.Row; curRow <= worksheet.Dimension.End.Row; curRow++) {
if (worksheet.Cells[curRow, curCol].Value != null) {
valueFound = true;
break;
}
}
if (!valueFound) {
emptyColIndexes.Add(curCol);
}
}
if (emptyColIndexes.Count > 0) {
for (int i = emptyColIndexes.Count - 1; i >= 0; i--) {
worksheet.DeleteColumn(emptyColIndexes[i]);
}
}
}
catch (Exception ex) {
MessageBox.Show("Error Message: Exception while removing empty columns from worksheet. " + ex.Message);
}
}
Related
so when filling my datagridview I normally do something like
public void FillTable(CoBRAMetaField[] metaFields)
{
dataGridView.Rows.Clear();
// do something with metaFields
}
Important:
CoBRAMetaField is a object with a ID and other stuff
Each row in the grid holds a metafield object
My grid gets filled correctly (sorry, the language is german)
When I fill the grid another time I only want to remove the rows with metaFields that don't exist in the new metaFields array. I want this behaviour because when a user selected a value for this row I don't want it to get removed and created again because then the selected value is removed too.
I came up with this
public void FillTable(CoBRAMetaField[] metaFields)
{
for (int i = 0; i < dataGridView.Rows.Count; i++) // loop through the grid rows
{
double metaFieldID = (dataGridView.Rows[i].Cells[0].Tag as CoBRAMetaField).ID; // get the ID from the row metaField
if (metaFields.Any(field => field.ID == metaFieldID)) // Does it exist?
metaFields = metaFields.Where(field => field.ID != metaFieldID).ToArray(); // Remove it from the new array
else // it doesn't exist
dataGridView.Rows.Remove(dataGridView.Rows[i]); // remove the row
}
// Fill the grid with the remaining metaFields
}
The first run gets initialized correctly
the second run seems to crash, some fields remain empty
when I press the button on this row I get a nullpointer exception. I only get this error when using the "new code" so am I missing something? Is there something I didn't think about?
I will provide a full example here
At first create a DataGridView and Button on the form. Create a file for all required classes and take this snippet
https://pastebin.com/BFmr2ps9
After that fill the forms code with some test data
https://pastebin.com/Yz84Akkj
and now setup the DataGridView logic
https://pastebin.com/qH6kZKZv
I added
dataGridView.AllowDrop = false;
dataGridView.AllowUserToAddRows = false;
dataGridView.AllowUserToDeleteRows = false;
dataGridView.AllowUserToOrderColumns = false;
dataGridView.AllowUserToResizeRows = false;
if you just want to copy paste but you can also do it by the forms designer. Have a look at dataGridView.Rows.Clear(); this provides a working example. Comment it out and use the code above to test the incorrect example
The main problem is in the code for adding rows (taken from the link):
// Fill the grid with the remaining metaFields
for (int i = 0; i < metaFields.Length; i++)
{
MetaField currentMetaField = metaFields[i];
dataGridView.Rows.Add(currentMetaField.Name, null);
DataGridViewRow newRow = dataGridView.Rows[i]; // <-- Problem!
DataGridViewCell metaFieldCell = newRow.Cells[0];
metaFieldCell.Tag = currentMetaField;
(newRow.Cells[1] as DataGridViewAllocationCell).Initialize(releaseSetupData);
}
In the marked line you are assuming that the index of the added row is the same as i, which is true when you start with empty grid, bit not when the grid is updated and some old records are kept.
The proper way of handling it is to not assume the new row index - it is returned by the Add method:
int rowIndex = dataGridView.Rows.Add(currentMetaField.Name, null);
DataGridViewRow newRow = dataGridView.Rows[rowIndex];
This will solve the original issue from the question.
There is also a problem in the remove part of the code - the for loop will miss checking the rows next to the removed ones. Anytime you want to iterate some list and remove items during the iteration, use reverse for loop and RemoveAt:
for (int i = dataGridView.Rows.Count - 1; i >= 0; i--) // <--
{
double metaFieldID = (dataGridView.Rows[i].Cells[0].Tag as MetaField).ID;
if (metaFields.Any(field => field.ID == metaFieldID))
metaFields = metaFields.Where(field => field.ID != metaFieldID).ToArray();
else
dataGridView.Rows.RemoveAt(i); // <--
}
The removal code could further be improved (currently looks inefficient with these Any, Where + ToArray), but at least with the above changes it will work correctly.
I have a DataGridView where the values found in the 1st column should not be repeated, and neither should it have any blank columns. This is the method I've implemented in order to remove this repetition:
public static void eliminateRepetition(DataGridView table)
{
for (int currentRow = 0; currentRow < table.RowCount; currentRow++)
{
DataGridViewRow rowToCompare = table.Rows[currentRow];
foreach (DataGridViewRow row in table.Rows)
{
if (row.Equals(rowToCompare)) // If the row to compare is the current row, skip
{
continue;
}
if (row.Cells[0].Value != null && rowToCompare.Cells[0].Value != null)
{
if (int.Parse(row.Cells[0].Value.ToString()) != int.Parse(rowToCompare.Cells[0].Value.ToString())) // 1st column values must match in order to be considered as a repetition
{
continue;
}
else
{
table.Rows.Remove(row); // Remove repeated row
}
}
else
{
table.Rows.Remove(row); // Remove blank rows
}
}
}
}
The result I have is always 2 rows with the same value in the 1st column.
Any help will be much appreciated.
After removing a row from the table, the indexes change. Next iteration will not be as expected.
I think it would be better to create a list of objects. Make them unique in terms of your rule. Then set the DataSource property of the datagridview to the object list you have.
My datagridView1 contains 7 columns, and first one has dates.
I'm trying to transfer january data from dataGridView1 to dataGridView2
I did try using a loop, which logically thought it would work, but it would give me nullExceptions
Here's my code:
for (int i = 0; i < f1.dataGrivView1.Rows.Count; i++)
{
if (f1.dataGrivView1.Rows[i].Cells[0].Value.ToString().Split('/')[1].StartsWith("01"))//if january..
{
dataGrivView1.Rows.Add();//datagridview in current form
dataGrivView1.Rows[i].Cells[0].Value = f1.dataGrivView1.Rows[i].Cells[0].Value.ToString();
dataGrivView1.Rows[i].Cells[1].Value = f1.dataGrivView1.Rows[i].Cells[1].Value.ToString();
}}
And after some googling, I saw you can't just copy data this way and you need to clone it, so I tried.
foreach (DataGridViewRow dgvr in f1.dataGridView1.Rows)
{
DataGridViewRow r = dgvr.Clone() as DataGridViewRow;
foreach (DataGridViewCell cell in dgvr.Cells)
{
if (cell.Value.ToString().Contains("/01/"));
r.Cells[cell.ColumnIndex].Value = cell.Value;
}
dataGridView1.Rows.Add(r);
}
But.. same thing.
Any idea?
This does indeed copy the first column values from a DataGridView to another.
for(int i=0; i<f1.dataGridView1.Rows.Count; i++)
dataGridView1.Rows[i].Cells[0].Value = f1.dataGridView1.Rows[i].Cells[0].Value;
Any exceptions thrown are probably due to the new dataGridView1 not having the existing rows at the corresponding index in the iteration.
I have a DataSet with several DataTables inside. I'm displaying the DataTables in a ListView (I didn't know about databinding when I wrote the code). Anyway, I would like to remove rows from the DataTables inside the DataSet.
I have tried this:
foreach (DataRow row in dsData.Tables["Table1"].Rows)
{
//find the row that contains the username I'm after
if (item.SubItems[2].Text == row["LoginName"].ToString())
{
dsData.Tables["Table1"].Rows.Remove(row); //<- main code of interest
}
}
I've also tried
dsData.Tables["Table1".Rows.Delete(row);
The problem I'm experiencing is that the when you remove a row I get the exception:
Collection was modified; enumeration operation might not execute.
From what I understand it's because when you remove a row from a ListView the row below it moves up and causes all this trouble. The code itself does what it's supposed to but it's not nice to see that exception when when you run it.
I was about to rewrite the whole class with a DataGridView but would rather correct that single line if possible :).
EDIT: I'm not even sure a DataGridView would solve the problem anyway.
change the loop to a for loop counting backwards so you don't get that message.
for(int i = dsData.Tables["TAble1"].Rows; i > 0; i--)
{
if(item.SubItems[2].Text == dsData.Tables["Table1"].Rows[i - 1]["LoginName"].ToString())
dsData.Tables["Table1"].Rows.Remove(i - 1)
}
Try:
DataSet dsData = new DataSet();
List<DataRow> rowsToDelete = new List<DataRow>();
foreach (DataRow row in dsData.Tables["Table1"].Rows)
{
if (item.SubItems[2].Text == row["LoginName"].ToString())
{
rowsToDelete.Add(row);
}
}
foreach(DataRow row in rowsToDelete)
{
dsData.Tables["Table1"].Rows.Remove(row);
}
You need a backwards for loop if you're going to be removing things
(explanation of why here)
for (int i = dsData.Tables["Table1"].Rows.Count - 1; i >= 0; i--)
{
DataRow row = dsData.Tables["Table1"].Rows[i];
//find the row that contains the username I'm after
if (item.SubItems[2].Text == row["LoginName"].ToString())
{
dsData.Tables["Table1"].Rows.Remove(row); //<- main code of interest
}
}
in general you can't remove items from a collection within a loop that is iterating on it
what you could do is keep a list of all the row you want to remove (creating it within the loop) and remove all of them OUTSIDE the loop
You can't modify a collection that you are iterating though with a foreach loop, from inside the loop. Do this instead:
for (int i = 0; i < dsData.Tables["Table1"].Rows.Count; i++)
{
DataRow row = dsData.Tables["Table1"].Rows[i];
if (item.SubItems[2].Text == row["LoginName"].ToString())
{
dsData.Tables["Table1"].Rows.Remove(row); //<- main code of interest
}
}
In my below code I'm comparing values (email addresses) in my Arrarylist (called emails) with values in my Datatable (called dtResult). I was hoping to be able to remove rows from the datatable if it has an email address that is found in the arraylist but I'm starting to think I'm better off creating a new datatable that does not have those rows instead (I just don't know how to do that). Obviously my code below bombs after a row is deleted because it loses its place in the foreach loop. How can I fix this?
Thanks.
foreach (DataRow row in dtResult.Rows)
{
var tmpl = row[3];
for (int x = 0; x < emails.Count; x++) //emails is an ArrayList of email addresses
{
string one = emails[x].ToString().ToUpper();
string two = tmpl.ToString().ToUpper();
//compare datatable value to arraylist value..
if (one == two)
{
//if they are equal then I want to remove them (or maybe create a new datatable??)
dtResult.Rows.Remove(row);
dtResult.AcceptChanges();
}
}
}
The easiest way would be to loop through the DataTable backward:
for (int i = dtResult.Rows.Count - 1; i >= 0; i--)
{
var row = dtResult.Rows[i];
// ...
}
You also might want to consider whether you can just mark the row state on those rows as deleted (and then observe that status later) instead of actually removing them from the table.
You could go through the DataTable in reverse
for(int i = dtResult.Rows.Count; i >= 0; i--)
{
foreach(object email in emails)
{
if(email.ToString().ToUpper() == dtResult[i].ToString().ToUpper())
{
dtResult.Rows.Remove(dtResult.Rows[i]);
dtResult.AcceptChanges();
}
}
}
Why don't you do it in two steps?
I didn't try this, so let me know how it goes.
var deleteRows = new List<DataRow>();
then in your loop, instead of deleting the row right away, just add the row to your list:
deleteRows.Add(row);
and after you loop through your database, you loop through the deleteRows list and delete each row one by one.