I'm stuck in an easy task: convert an multidimensional Array (Object[,]) in a monodimensional string[].
Long story short: I'm reading an excel file using a com library which returns a dynamic type casted to Object[,] at runtime.
SortedSet<string> rows = new SortedSet<string>();
for (int i = 3; i <= 5; i++)
{
Microsoft.Office.Interop.Excel.Range range = worksheet.get_Range("A"+i, "J"+i);
Array cells = (Array) range.Cells.Value; // value return dynamic [,] -> Object[,] -> cast to Array[,]
string[] rowCells = ConvertArrayToStringArray(cells); //
rows.Add(string.Join("\t", rowCells));
}
return string.Join("\r\n", rows);
Since Array[,] always have 1 row (I read the excel file row by row) I'd like to cast it to a simple string[] (or List) without using another loop in ConvertArrayToStringArray function.
I could read all cells in an action (not row by row like in code above), but I've no idea of how many rows are in excel file.
I appreciate any help
string[] rowCells = cells.Cast<string>().ToArray();
Related
The Excel spreadsheet should be read by .NET. It is very efficient to read all values from the active range by using the property Value. This transfers all values in a two dimensional array, by one single call to Excel.
However reading strings is not possible for a range which contains more than one single cell. Therefor we have to iterate over all cells and use the Text property. This shows very poor performance for larger document.
The reason of using strings rather than values is to obtains the correct format (for instance for dates or the number of digits).
Here is a sample code written in C# to demonstrate the approach.
static void Main(string[] args)
{
Excel.Application xlApp = (Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
var worksheet = xlApp.ActiveSheet;
var cells = worksheet.UsedRange();
// read all values in array -> fast
object[,] arrayValues = cells.Value;
// create array for text of the same extension
object[,] arrayText = (object[,])Array.CreateInstance(typeof(object),
new int[] { arrayValues.GetUpperBound(0), arrayValues.GetUpperBound(1) },
new int[] { arrayValues.GetLowerBound(0), arrayValues.GetLowerBound(1) });
// read text for each cell -> slow
for (int row = arrayValues.GetUpperBound(0); row <= arrayValues.GetUpperBound(0); ++row)
{
for (int col = arrayValues.GetUpperBound(0); col <= arrayValues.GetUpperBound(1); ++col)
{
object obj = cells[row, col].Text;
arrayText[row, col] = obj;
}
}
}
The question is, if there is a more efficient way to read the complete string content from an Excel document. One idea was to use cells.Copy to copy the content to the clipboard to get it from there. However this has some restrictions and could of course interfere with users which are working with the clipboard at the same time. So I wonder if there are better approaches to solve this performance issue.
You can use code below:
using (MSExcel.Application app = MSExcel.Application.CreateApplication())
{
MSExcel.Workbook book1 = app.Workbooks.Open( this.txtOpen_FilePath.Text);
MSExcel.Worksheet sheet = (MSExcel.Worksheet)book1.Worksheets[1];
MSExcel.Range range = sheet.GetRange("A1", "F13");
object value = range.Value; //the value is boxed two-dimensional array
}
The code is provided from this post. It should be much more efficient than your code, but may not be the best.
I have a CSV which I want to import to a c# console application.
I want to create arrays from each row of the CSV so I end up with a bunch of arrays, like the ones shown below (but would need to ignore the fisrt row if possible as it's a row of headings):
Array1 - [row2column1] [row2column2] [row2column3]
Array2 - [row3column1] [row3column2] [row3column3]
Array3 - [row4column1] [row4column2] [row4column3]
etc...
Is this possible? if so, how would i do this?
Thanks for the help
You could use TextFieldParser. Each time you call ReadFields() it returns the next row of data as a string array. Do as you want with each array..
var parser = new Microsoft.VisualBasic.FileIO.TextFieldParser(#"C:\temp\test.csv");
parser.SetDelimiters(",");
parser.ReadFields(); // discard first row
while (!parser.EndOfData)
{
var array = parser.ReadFields(); // next row returned as an array of strings
}
I need to build an excel sheet from a list of test-cases in a specific format in order to upload it it to the server.
I've trubles to populate the two dimensional range of "expected" and "actual" in the file.
I use the same methods in order to populate the headers, which is a one-dimensional array, and the steps (which is two-dims).
The flow is:
Defunding the TestCase range (some headers + steps). Let's say: A1 to E14 for the 1st iteration.
Depunding a sub (local) range within the testCase range for the headers (e.g: A1 to C1).
Depunding another sub (local) range within the testCase range for the headers (in my case: D1 to E14).
Populate the two sub-ranges with a test-case values (headers and steps).
Repeat by defunding the next spreadsheet range (A14 to E28 in my case) with same local ranges (steps 2-3), and populate them, and so on...
The source value is a Dictionary which represents the test-case's steps (key = expected and value = actual).
Here is the code I use:
public class TestCase
{
public Dictionary<string, string> steps;
}
Microsoft.Office.Interop.Excel.Application excelApp = new Microsoft.Office.Interop.Excel.Application();
Workbooks workBooks = excelApp.Workbooks;
Workbook workbook = workBooks.Add(XlWBATemplate.xlWBATWorksheet);
Worksheet worksheet = (Worksheet)workbook.Worksheets[1];
excelApp.Visible = true;
foreach (TestCase testCase in TestCaseList.Items.GetList())
{
Range worksheetRange = GetRangeForTestCase(worksheet);
//The step above is equivalent to:
// Range worksheetRange = worksheet.get_Range("A1", "E14");
//for the first foreach iteration.
Range stepsRange = worksheetRange.get_Range("D1", "E14");
//This is a local range within the worksheetRange, not the worksheet,
//so it is always have to be between D1 to E14 for a 14th steps test case.
//Anyway, it should work at least for the 1st iteration.
//for test evaluation only. All cells between D1 to E14 are filled with "ccc"
test.Value = "ccc";
//This list of pairs which are converted to Array[,] is about to be converted to a two dimensional array
list = new List<object>();
foreach (KeyValuePair<string, string> item in testCase.Steps)
{
//Here I build the inside Array[,]
object[] tempArgs = {item.Key, item.Value};
list.Add(tempArgs);
}
object[] args = { Type.Missing, list.ToArray() };
test.GetType().InvokeMember("Value", System.Reflection.BindingFlags.SetProperty, null, test, args);
//Now, all the "ccc" within the Excel worksheet are disapeared, so the Range is correct, but the value of args[] are not set!!
}
The actual results of running this code is that the range is defined (probably correctly) but its values are set to null,
although - I can see the correct values in the args array in run time.
I've also tried to set a wider range and populate it with range.Value = "Pake value" and saw that, after running my peace of code, the correct range of steps become blank!
So, the range is correct, the array is filled with my values, the InvokeMember method is correctly invoked :)
But, all values are set to null..
Help...
One cell or 1dim array can be set via one of the following:
Range range = SetRange();//Let's say range is set between A1 to D1
object[] args = {1, 2, 3, 4 };
//Directly
range.Value = args;
//By Reflection
range.GetType().InvokeMember("Value", System.Reflection.BindingFlags.SetProperty, null, range, args);
A 2dim array cannot be directly set, so one has to use the reflection flow to set a matrix of values. This matrix has to be built before the set, like this:
Range range = SetRange();//Let's say range is set between A1 to C5
int rows = 5;
int columns = 3;
object[,] data = new object[rows, columns];
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < columns; j++)
{
//Here I build the inside Array[,]
string uniqueValue = (i + j).ToString();
data[i, j] = "Insert your string value here, e.g: " + uniqueValue;
}
}
object[] args = { data };
range.GetType().InvokeMember("Value", System.Reflection.BindingFlags.SetProperty, null, range, args);
As for your issue, all the range set to null, I think this is due to wrong arguments.
Indeed why the Type.Missing in the arguments list?
Hence this should be a step in the right direction:
object[] args = { list.ToArray() };
test.GetType().InvokeMember("Value", System.Reflection.BindingFlags.SetProperty, null, test, args);
Moreover list.ToArray will only generate an array of arrays not a matrix, so you should build your matrix differently, e.g.:
object[,] data = new object[14, 2];
int row = 0;
foreach (KeyValuePair<string, string> item in testCase.Steps)
{
//Here I build the inside Array[,]
data[row, 0] = item.Key;
data[row, 1] = item.Value;
++row;
}
object[] args = { data };
And what's the rational behind the use of InvokeMember instead of a simpler:
test.Value = data;
?
Hope this helps...
I know how to write single cell into excel but when im trying it on array excel sheet is filling with only last value
this is my range
Excel.Range ServiceName = (Excel.Range)_sheet.get_Range(_sheet.Cells[38, "B"] as Excel.Range, _sheet.Cells[45, "B"] as Excel.Range);
_ServiceName is List which contains 1,2,3,4,5,6
for (int i = 0; i < _ServiceName.Count; i++)
{
ServiceNameArray[0, i] = _ServiceName[i];
}
this i my trying to write into excel but as i said it there is only last item (6) in excel book
for (int i = 0; i < _ServiceName.Count; i++)
{
ServiceName.set_Value(Type.Missing, ServiceNameArray[0,i]);
}
does anyone have an idea?
Davide Piras is right. And you're doing a few other strange things there, I can elaborate by request.
For now I just want to point out that you can directly assign the .Value property of a Range to an array:
ServiceName.Value2 = _ServiceName.toArray();
This is much, much faster for bigger amounts of data.
(Side note: If you want to do the same with Formulas, for some strange reason you have to take an extra step (doubling the time):
range.Formula = array;
range.Formula = range.Formula;
unless there is a better way I don't know about yet.)
I see you looping on the ServiceName array to get all values one after the other but not see you changing the focused cell inside the cellrange at every loop iteration. Of course, I would say, you see only the last value, because you are writing all values one over the other always in the same place.
I have one simple problem when trying to write List into the excel workbook. on string its work perfect but problem is how i can put list into excel
public List<string> _RoomType = new List<string>();
Excel.Range RoomType = (Excel.Range)_sheet.get_Range(_sheet.Cells[22, "B"] as Excel.Range, _sheet.Cells[25, "B"] as Excel.Range);
for (int i = 0; i < _RoomType.Count; i++)
{
RoomType.set_Value(Type.Missing, _RoomType[i]);
if im using for loop it sets from 22B to 25B only first value which is in list
and if i dont use 'for' visual studio gives me exception : Exception from HRESULT: 0x800A03EC
can anyone help me?
You need to pass a 2-dimensional array to the set_Value method. You have to make sure though that number of items in your list equals the number of cells in your range.
Object[,] dataArray = new object[1, _RoomType.Count];
for (int i = 0; i < _RoomType.Count; i++)
{
dataArray[0, i] = _RoomType[i];
}
RoomType.set_Value(Type.Missing, dataArray);