I've checked a few online resources, maybe I'm blind but I've as yet been unable to find an answer to this.
I'm uploading a file, converting it to a stream, feeding it into SpreadSheetGear. Now, I need to loop through every row and read the data (which is fine). Here is my code so far:
IWorkbook wb = Factory.GetWorkbookSet().Workbooks.OpenFromStream(file.InputStream);
IWorksheet ws = wb.ActiveWorksheet;
IRange cells = ws.Cells;
for (int i = 2; i <= cells.RowCount; i++)
{
//Code for every row
for (int x = 1; x <= cells.ColumnCount; x++)
{
//Code for every column
}
}
Here is the problem: cells.RowCount is equal to 1048576 which is clearly the Excel upper limit on number of rows. Is there a call to SpreadSheetGear that returns the number of rows that have data? I cant use a fixed amount as the spreadsheet provided could have anything from 500 to 2,000 rows.
I understand SpreadSheetGear may not be that widely used but I thought I'd chance my arm here anyway.
Cheers!
Edit :
Before somebody says use a while loop, it's possible that a row can be empty so checking for null strings is a bit of a messy one.
Answered my own question, always the way.
Anyway, eventually found IWorksheet.UsedRange which returns just the used cells.
Related
I am using Excel as a reporting tool, for my desktop application. The app is written in C# (VS 2019).
The gist of my code is to obtain a range object from the worksheet, populate an array of null-able integers and write it back out to the same worksheet.
So far I have obtained a range of cells, where startRow is the first row highlighted in yellow:
Range startRow = (Range) input.Range["E" + row.ToString(), "K" + row.ToString()];
Initialise the local array as:
double?[,] startArray = new double?[1, 7];
Set the relevant values:
startArray[1,2] = 9.75;
And finally, write the data back:
startRow.Value2 = startArray;
When I write the array back, I get the error
The parameter is incorrect. (Exception from HRESULT: 0x80070057 (E_INVALIDARG))
In this case, I assume that it is because I have some null-able values in the array, as if I do not use a null-able array and as a result its values are zero by default, this works OK, but wites zeros out into the spreadsheet.
I did try using:
string[,] startArray = new string[1, 7];
which does work, but any fields that does have a value, are not seen as numeric and causes Excel formulae not to work.
So to my question, can I get the array to write back to Excel with nulls, or do I have to revert to using the cell object and only update the cells which contain a value?
As it happens, just the fact of writing this post has enabled me to fix it.
The simple answer is to write directly to the range only the values which contain a value.
So something like this:
for (int i = 0; i < 7; i++)
{
if (startArray[0, i] != null) startRow[1, i + 1] = startArray[0, i];
}
As for EPPlus, it looks a fantastic product, but as I already pay for an Excel license would find this a little over kill as a paid license would also be needed for EPPlus.
I want to copy an 1D array to a column range in excel. I'm using interop for this purpose.
I have already tried these things:
range.get_Resize(Ary.Length, 1).Value2 = Ary;
range.set_Value(Excel.XlRangeValueDataType.xlRangeValueDefault, Ary);
and as simple as range.Value = Ary;
I have tried using even range.value2, but these things copy the very first index value in the array to the entire range.
So say suppose, if there are 200 rows in the range and the array contains integers 101-300, than only 101 is copied throughout the range with the above tried methods.
Can anyone please help me with this? It would be more helpful if someone can explain me this strange behavior! Thanks in advance.
Note: I know I can do it through a for loop, but that takes time. I would surely like to know a method which takes less than a minute to iterate a million rows.
I seriously don't know what exactly is wrong with the above methods. But I found the solution:
Excel.Range range = sheetSource.UsedRange.Columns[columnIndx];
range.Value = application.WorksheetFunction.Transpose(Ary);
I have an excel.range called rng. I iterate through it using a foreach loop.
foreach(Excel.Range cell in rng)
{
//do something
}
The foreach loop is very useful in my application and I cannot use a for loop. Now to my question:
Is there any way to move to the i:th cell relative to the current cell? And continue my foreach loop from that cell. Like this:
foreach(Excel.Range cell in rng)
{
If(cell.value.ToString() == "Something")
//move 3 cells forward in rng relative to the current cell
}
I'm not looking for the continue; expression. I need to move forward in my range somehow. How I do it is less important. It could be a loop using some kind of moveNext() command or some kind of indexing.
I appreciate any help I can get!
To answer your question, no, there's no way to "skip ahead" an arbitrary number of items within a foreach loop. Not being able to use a for loop is a bizarre requirement so I'm wondering if this is a puzzle and not a real problem. But I digress...
Remember that each access to an Excel property in C# is a COM call, so it is very expensive. You will get better performance (and make your programming easier) by pulling the entire range into an array:
object[,] data = rng.Value2 as object[,];
The array will contain strings (for text cells) and doubles (for numeric/date cells). THen you can use standard loops to navigate the array. When you;'re done, just put the data back:
rng.Value2 = data;
Looking at your question it seems that you need answer for "How to refer adjacent cells?"
For that you can use Offset() property in excel vba.
It works like this: Assume you are referring to Row no. 1 of column A. Now you want to refer same row but column D; you can use:
Range("Your_Range").Offset(0, 3).Select
For more on OFFSET function:
https://msdn.microsoft.com/en-us/library/office/ff840060.aspx?f=255&MSPPError=-2147217396
if you just want to skip the foreach, in this case, you could do something like this:
int i = 0;
foreach(Excel.Range cell in rng)
{
if(i>0)
{
i--;
}
else
{
if(cell.value.ToString() == "Something")
i=3;
}
with that you'd be "skipping" the foreach, but still, try to use a for loop instead, it would be much more simple than this
I am grabbing a value at a time and dynamically loading it into a grid.
Is there a way to index a csv file to only look up a value at a certain row and column?
I can't read all the rows as that would defeat the purpose of loading dynamically.
The CSV parser, Fast CSV Parser in my case, can grab a value like so csv[row][column]. When looking at the source, I noticed that it loops over everything in the file until it reaches the correct index column pair. To grab a value at row 100,000 column 80, it can take quite a long time.
Any help much appreciated.
Well, you could do a fast first pass and store the offsets of each row. That would make subsequently locating a row much faster. If you have 80 columns but 100K rows, I'd focus on fast row-finding rather than fast column-finding.
ETA: OK, I'm assuming your CSV file is on disk and that you can get exclusive access to it. Some of this code was based on this.
List<int> offsets = new List<int>();
using (StreamReader reader = new StreamReader("myfile.csv"))
{
int offset = 0;
string line;
while ((line = reader.ReadLine()) != null)
{
offsets.Add(offset);
offset += (line.Length + 2); // The 2 is for NewLine(\r\n)
}
offsets.Add(offset); // pick up the last one
}
At the end of this, you will have the List variable offsets, which is indexed by line number and contains the offset to each line. You can then, when reading the file (when doing your grid-building) use offsets[n] to get the offset to Seek to (I'm assuming you're using a FileStream or a StreamReader) and offsets[n+1] - offsets[n] to get the length.
As far as parsing the returned line of text is concerned, I assume the CSV library you're adapting has good logic for that.
CSV files do not have any support for indexing where a particular row might be, no.
The best I think you can do is read each row until you find the one you want. So you'll average reading half the file when scanning for a row, which is better than reading the entire file.
If you use the CSV parser I present in the article Reading and Writing CSV Files in C#, you can just read one row at a time.
The other option is if you are going to be accessing multiple rows from the same file. In this case, you could run through the file and build a list of indexes. But this only pays off if your going to lookup multiple lines in a single session.
If you are permitted to use 3rd party libraries I'd look at some of those. MySQL has CSV engine support and therefor it seems likely that you'd be able to do this using a library from them.
C# however doesn't provide a great way to handle CSV files.
http://dev.mysql.com/doc/refman/5.0/en/csv-storage-engine.html
I am converting an app from vba to C#. It's going slowly. Here's my first challenge...
In vba, I can assign an Excel range to a vba two-dimensional array:
Dim strSheetArray as Variant
strSheetArray = xlSH.UsedRange
...
debug.print strSheetArray[1,2] 'etc.
When I convert to C#, the "UsedRange" property seems to return an object. I can't seem to use that object like an array, getting code-time errors. So, how do I refer to the returned object so I can iterate through it?
And, as long as I'm at it, the cells have no type, so how do I use them (some are number, others are text, etc.) It was pretty easy in vba....
Thanks for the help.
It returns a Range Interface. You can read more about it at MSDN.
And, as long as I'm at it, the cells have no type, so how do I use them (some are number, others are text, etc.) It was pretty easy in vba....
The cells do have a type, but they are just objects.You will have to cast to what you want it to be. So if the cell is text, you will have to do something like:
(string)xlSheet.Cells[row,col].Value2
or
xlSheet.Cells[row,col].Value2.ToString();
You would have to cast to a numeric type as well if you knew they were numbers.
Edit based comments:
So if you want to get the used range you would do this:
using Excel = Microsoft.Office.Interop.Excel;
Excel.Range range = xlSheet.UsedRange;
This will give you everything from the worksheet (I used the variable xlSheet, which is Excel.Worksheet).
Now we can iterate over this by doing:
int rows = range.Rows.Count;
int cols = range.Columns.Count;
for(int r=1; r <= rows; r++)
{
for(int c=1; c <= cols; c++)
{
Console.Write(range.Cells[r,c].Value2.ToString());
}
Console.WriteLine();
}
This will iterate over the range and basically replicate what is in your spreadsheet in the console window. I hope this helps. I also wrote a blog post on reading Excel spreadsheets using C# which you can read here if you want.