I'm trying to merge the same values together for Excel using ClosedXML package in C#.
This is my current output.. it's not merging the same values together and leaving some out.
e.g. No.7 merged two cells and not the third.
e.g. No.9 keeps merging 2 cells and repeats the same merge.
My current logic:
// Foreach record
int i = 1;
foreach (var record in ...)
{
i++;
// Minimum Months
// Create Value
worksheet.Cell($"D{i}").Value = record.MinimumMonths.ToString();
// Evaluate value - if they're the same then merge
if(i > 2 && worksheet.Cell($"D{i}").Value.ToString() == worksheet.Cell($"D{i - 1}").Value.ToString())
{
worksheet.Range($"D{i - 1}:D{i}").Merge();
}
}
I don't understand where my logic is going wrong. How can I merge all the same values together?
worksheet.Range($"D{i - 1}:D{i}").Merge();
You shouldn't be merging your cells in pairs. Identify the first and last cells of a range of cells with the same value and merge that entire range in one .Merge() statement.
Also notice that when you merge a range of cells, only the top left cell keeps its value. All the other cells' values in the merged range are cleared. This is in line with what Excel does.
Your .Value.ToString() equality comparison is a bit naive. Rather check with cell1.Value is double d1 && cell2.Value is double d2 && d1.Equals(d2).
Related
i tried Trial version of Gembox.SpreadSheet.
when i Get Cells[,].value by for() or Foreach().
so i think after Calculate() & get Cell[].value, but that way just take same time,too.
it take re-Calculate when i Get Cell[].value.
workSheet.Calcuate(); <- after this, values are Calculated, am i right?
for( int i =0; i <worksheet.GetUsedCellRange(true).LastRowIndex+1;++i)
{
~~~~for Iteration~~~
var value = workSheet.Cells[i,j].Value; <- re-Calcuate value(?)
}
so here is a Question.
Can i Get calculated values? or you guys know pre-Calculate function or Get more Speed?
Unfortunate, I'm not sure what exactly you're asking, can you please try reformulating your question a bit so that it's easier to understand it?
Nevertheless, here is some information which I hope you'll find useful.
To iterate through all cells, you should use one of the following:
1.
foreach (ExcelRow row in workSheet.Rows)
{
foreach (ExcelCell cell in row.AllocatedCells)
{
var value = cell.Value;
// ...
}
}
2.
for (CellRangeEnumerator enumerator = workSheet.Cells.GetReadEnumerator(); enumerator.MoveNext(); )
{
ExcelCell cell = enumerator.Current;
var value = cell.Value;
// ...
}
3.
for (int r = 0, rCount = workSheet.Rows.Count; r < rCount; ++r)
{
for (int c = 0, cCount = workSheet.CalculateMaxUsedColumns(); c < cCount; ++c)
{
var value = workSheet.Cells[r, c].Value;
// ...
}
}
I believe all of them will have pretty much the same performances.
However, depending on the spreadsheet's content this last one could end up a bit slower. This is because it does not exclusively iterate only through allocated cells.
So for instance, let say you have a spreadsheet which has 2 rows. The first row is empty, it has no data, and the second row has 3 cells. Now if you use 1. or 2. approach then you will iterate only through those 3 cells in the second row, but if you use 3. approach then you will iterate through 3 cells in the first row (which previously were not allocated and now they are because we accessed them) and then through 3 cells in the second row.
Now regarding the calculation, note that when you save the file with some Excel application it will save the last calculated formula values in it. In this case you don't have to call Calculate method because you already have the required values in cells.
You should call Calculate method when you need to update, re-calculate the formulas in your spreadsheet, for instance after you have added or modified some cell values.
Last, regarding your question again it is hard to understand it, but nevertheless:
Can i Get calculated values?
Yes, that line of code var value = workSheet.Cells[i,j].Value; should give you the calculated value because you used Calculate method before it. However, if you have formulas that are currently not supported by GemBox.Spreadsheet's calculation engine then it will not be able to calculate the value. You can find a list of currently supported Excel formula functions here.
or you guys know pre-Calculate function or Get more Speed?
I don't know what "pre-Calculate function" means and for speed please refer to first part of this answer.
Trying to iterate through the rows and cells on an excel spreadsheet, deleting empty ones. I'm using the following routine to do so.
foreach(Range row in sheet.UsedRange.Rows)
{
for (int i = 0; i < row.Columns.Count; i++)
{
Range cell = row.Cells[1, i + 1];
if (cell.Value == null || String.IsNullOrEmpty(cell.Value.ToString()))
{
cell.Delete();
}
}
}
Which works fine for the first two rows. However, it then seems to go haywire.
The third row is completely empty. Yet as it iterates through the columns, when this loop gets to column "I", it reads a value there. The value is what's actually in row 4, column "J".
After that, it just gets worse, missing whole rows and reading incorrect values from the rows it does find.
I am baffled by this. Is there something obvious that I have missed?
Yes, you are missing something very obvious. You are deleting cells. After that operation, your calculation of which cell to pick doesn't work any more.
If you delete a cell, all other cells will move up. That causes your row.Cells[1, i + 1] to be incorrect. If you for example delete one cell in row 2, the value of the cell in the same column in row 3 will never get checked, since it is in row 2 then.
The direction of shift on deletion may also be a factor - you can control it by passing a parameter to the Delete function.
Simply recheck the same column when you delete one:
foreach (Range row in Globals.ThisAddIn.Application.ActiveWorkbook.ActiveSheet.UsedRange.Rows)
{
for (int i = 0; i < row.Columns.Count; i++)
{
Range cel = row.Cells[1, i + 1];
if (cel.Value == null || String.IsNullOrEmpty(cel.Value.ToString()))
{
// default shift is up
cel.Delete();
// to shift left use cel.Delete(XlDeleteShiftDirection.xlShiftToLeft);
i--; // this will do
}
}
}
I have a simple excel sheet:
Now, I filter it such that cell value > 1. Now my data looks like:
Now, I select the data that I require:
Note that I have selected all the Mobile Numbers.
Now in my code, I am trying to retrieve all the selected data as follows:
Range selection = (Range)Globals.ThisAddIn.Application.ActiveWindow.Selection;
But, it gives me the cells from starting to ending. I think excel selects the non-visible rows also. Because row no 4 that contains 0 is also retrieved. Look at the image below:
So, now I created another Range and tried to add all the values of cells that are visible as follows:
Range onlyFilteredSelection = selection.Cells.SpecialCells(XlCellType.xlCellTypeVisible);
Now, I can see that c# shows me only the two rows. Why is it not displaying the last row, which is after the non-filtered row. Take a look at the values here:
Update:
After posting this question, I got a thought in my mind that I might be getting multiple ranges instead of 1 and so, I started exploring. And look what I have found. I found that I was exactly right. I get multiple ranges.
Here is the code that I have tried:
Range selection = (Range)Globals.ThisAddIn.Application.ActiveWindow.Selection;
List<Range> onlyFilteredSelection = new List<Range>();
foreach (Range range in selection.Cells.SpecialCells(XlCellType.xlCellTypeVisible))
{
onlyFilteredSelection.Add(range);
}
Now, I get 4 items in selection variable. And in onlyFilteredSelection has got 3 items.
Now, I am in another trouble:
Previously, I was getting a Range, so I converted it to a Comma-Separated String very much easily using the below mentioned code:
string[] AllRecepientMobileNumberValues = ((Array)(selection.Cells.Value2)).OfType<object>().Select(o => o.ToString()).ToArray();
string RecepientMobileNumberValue = AllRecepientMobileNumberValues.Aggregate((a, x) => a + ", " + x);
But now, I get a List. So, now my big question is how to Convert a List to Comma-Separated string?????????
You can use one more Select to get the values out of a list.
string[] AllRecepientMobileNumberValues = onlyFilteredSelection.Select(x => x.Cells.Value2).OfType<object>().Select(o => o.ToString()).ToArray();
string RecepientMobileNumberValue = AllRecepientMobileNumberValues.Aggregate((a, x) => a + ", " + x);
I tried around a bit and had problems too. But I think I may have found a possible workaround for your situation. First some things I found out.
I was able to reproduce your behaviour
the selection of all visible cells fails, because a filter (seems?) not to hide the rows, but it set the row height to 0!
I could not find any other useful method/property when looking around on the Application Member or the Range Member.
I created a macro to record what VBA code would be generated on a copy action of your selection´too. Strange thing is that there is nothing special in the code as you can see.
vba macro code
Range("A3:A5").Select ' correct as three lines are selected,
' but only two of them have a rowHeight > 0
Selection.Copy
Range("F8").Select
ActiveSheet.Paste ' and here is the magic?? why does vba only paste 2 cells??
So I decided to come up with a workaround. Why not simulation what VBA seemingly does too. Only handle those cells whose rowHeight > 0.
exampleCode.cs
private static void readFilteredCells()
{
Excel.Application xlApp = (Excel.Application)Marshal.GetActiveObject("Excel.Application");
Workbook xlBook = (Excel.Workbook)xlApp.ActiveWorkbook;
Worksheet wrkSheet = xlBook.Worksheets[1];
Range selection = xlApp.Selection;
for (int rowIndex = selection.Row; rowIndex < selection.Row + selection.Rows.Count; rowIndex++)
{
if (wrkSheet.Rows[rowIndex].EntireRow.Height!=0)
{
// do something special
}
}
}
I hope my answer is of any use for you. If you need any further assistance please let me know.
I'm working on a C# application that contains a lot of DataGridViews which are empty. The user has to fill them with copy/pasted data from excel. What I do is the following:
int i = 0;
string s = Clipboard.GetText();
// Separate lines
string[] lines = Regex.Split(s, "\r\n");
foreach (string line in lines)
{
// Separate each cell
string[] cells = line.Split('\t');
foreach (string cell in cells)
{
// If we selected as many cells as copied
if (dataGridView.SelectedCells.Count == (lines.Length-1)*(cells.Length))
{
dataGridView.SelectedCells[i].Value = cell;
i++;
}
}
}
The problem is that if I copy something like this (on excel):
1 2 3
4 5 6
My datagridview will look like:
6 4 2
5 3 1
I don't really know what to do to fix this...
Thanks in advance
Convert your clipboard data to a 2-dimensional array. Remember length of each dimension.
Iterate through the selected cells and find the top-left and bottom-right cells. From that you can determine it is of the right size.
Using a double loop, map your clipboard data from array position directly to cell coordinate (not using selected cells) using the topleft cell coordinate as an offset.
Alternatively, rather than a 2 dimensional array, you could create a List of a small class/struct containing the properties Row, Col and Value. Then just iterate through that rather than the double loop.
Replace
dataGridView.SelectedCells[i].Value = cell;
with
dataGridView.SelectedCells[(dataGridView.SelectedCells.Count-1) - i].Value = cell;
I have an excel sheet, in which i have multiple cells selected, which are not adjacent to each other. When the user click on the button, we need to read all the cells data, process it and write it to some other cell.
If the cells are adjacent to each other, i was able to get the range and able to perform the operation. But if the cells are not adjacent to each other, i am not able to get the range.
The Selection.Range is always giving the address of the last cell we selected.
But we need to get the addresses of all Cells, which i am not able to do it.
Please can anybody suggest me a way to handle this scenario.
Sample code:
Range objRange = (Range) Globals.ThisAddIn.Application.Selection;
int nColCount = objRange.Columns.Count;
int nRowCount = objRange.Rows.Count;
Vinay,
I have tried this code based on your suggestion,
Range objRange = (Range) Globals.ThisAddIn.Application.Selection;
foreach (Range cell in objRange)
{
MessageBox.Show("" + cell.Value2);
}
But it didn't worked. Always it's giving the last selected cell. i have selected A1, A4, A13, A16 cells. But this code is returning the A16 Cell Value Only.
Range inherits from IEnumerable. So you can use for each iterator to enumerate via all cells.
See the equivalent VBA code below:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim result As String
result = ""
Dim c As Range
For Each c In Me.Application.Selection
result = result & ", " & c.Text
Next c
Me.Cells.Item(1, 1).Value = result
End Sub
You can always use Range.Row, Range.Column for getting the cell address.
As said in comments, use foreach synatx:
foreach(Range cell in objRange)
{
// now access cell's properties - c.Value will give value
}
After trying alot, I got the answer.
Here is the working code,
Areas objAreas = (Areas)objRange.Areas;
foreach (Range area in objAreas)
{
string CellAddress = (GetExcelColumnName(area.Column) + "" + area.Row);
MessageBox.Show(CellAddress);
}
GetExcelColumnName is the custom function u have written to convert Column number to Column Code(like a, b,... aa, ab.. etc)