I am trying to get data from an Excel file. I know that indexing in excel starts from 1, but still when reading a simple matrix, I am getting an Index exceeds bounds error.
Here is a simple method I'm using:
public static string[,] ReadFromExcel(Worksheet excelSheet)
{
if (excelSheet == null) return new string[1,1];
Microsoft.Office.Interop.Excel.Range xlRange = excelSheet.UsedRange;
int firstRow = xlRange.Row;
int firstColumn = xlRange.Column;
int lastRow = xlRange.Row + xlRange.Rows.Count - 1;
int lastColumn = xlRange.Column + xlRange.Columns.Count - 1;
string[,] data = new string[xlRange.Rows.Count - xlRange.Row, xlRange.Columns.Count - xlRange.Column];
for (int i= 0; i<= lastRow - firstRow; i++)
{
for (int j= 0; j <= lastColumn - firstColumn; j++)
{
data[i,j] = (string)(excelSheet.Cells[firstRow + i][firstColumn + j] as Range).Value;
//When I try to read values I get an error
System.Windows.MessageBox.Show(data[i, j]);
}
}
return data;
}
What am I missing?
The size of your "data" array should match the number of rows & columns:
string[,] data = new string[xlRange.Rows.Count, xlRange.Columns.Count];
Although the start index for Excel Interop is 1, rather than zero, the count is correct & you don't need to subtract the start row & column.
Related
Sheet Change Event
The Target Range refers to the range values referred to by the address of the deleted row. I need the Range values that were just deleted. How can I do this?
In _Application_SheetActivate
dataArray = ((Excel.Worksheet)Sh).UsedRange.Value2;
In Application_SheetChange(object Sh, Excel.Range Target)
var deletedRow = _dataArray.ToJaggedArray();
var values = deletedRow[Target.Row - 1];
public static object[][] ToJaggedArray(this object[,] array)
{
object[][] jagged = new object[array.GetLength(0)][];
for (int i = 0; i < array.GetLength(0); i++)
{
jagged[i] = new object[array.GetLength(1)];
for (int j = 1; j <= array.GetLength(1); j++)
{
jagged[i][j - 1] = array[i + 1, j];
}
}
return jagged;
}
Hello I want import my data in Gridview to Excel, my data in Gridview is "00, 02, 04" but in excel my data change to "0, 2, 4" I dont wont it, i want my data in Excel like same in Gridview.And How to move rows to new columns, for exp i have 160 rows data, i want in excel split to 3 columns, 1 column have 40 rows. This my code :
private void exToExcel()
{
if (RekapdataGridView.Rows.Count > 0)
{
Microsoft.Office.Interop.Excel.Application XcelApp = new Microsoft.Office.Interop.Excel.Application();
XcelApp.Application.Workbooks.Add(Type.Missing);
//XcelApp.Cells[5,2].NumberFormat = "00";
XcelApp.Cells[1, 1] = "Tanggal";
XcelApp.Cells[1, 2] = labelTglRekap.Text;
XcelApp.Cells[2, 1] = "Kode Pemain";
XcelApp.Cells[2, 2] = labelKodePemain.Text;
XcelApp.Cells[4, 1] = "No";
int x = RekapdataGridView.RowCount;
for (int y = 1; y <= RekapdataGridView.RowCount; y++)
{
XcelApp.Cells[4+y, 1] = y;
}
for (int i = 2; i < RekapdataGridView.Columns.Count + 2; i++)
{
XcelApp.Cells[4, i] = RekapdataGridView.Columns[i - 2].HeaderText;
}
for (int i = 0; i < RekapdataGridView.Rows.Count; i++)
{
for (int j = 0; j < RekapdataGridView.Columns.Count; j++)
{
XcelApp.Cells[i + 5, j + 2] =string.Format("{0:00}", RekapdataGridView.Rows[i].Cells[j].Value);
}
}
XcelApp.Columns.AutoFit();
XcelApp.Visible = true;
}
}
This is my data in excel :
I want my data can showing like this image :
Sorry if my English is bad, i hope someone can help me.
You nearly had it with
XcelApp.Cells[5,2].NumberFormat = "00";
Just change it to
XcelApp.Cells[5,2].NumberFormat = "#";
Which will make the cell format be 'Text' instead of number. Excel always tries to simplify numbers from my experience.
Just don't forget to do it to ALL cells you are writing into.
Hope this helps!
I have an algorithm that takes a list of arrays and enters them into an Excel file, however it is very slow. Is there a better design for this algorithm?
public void WriteToExcel(List<string[]> parsedData, string path, string fileName)
{
// Get the Excel application object.
Excel.Application xlApp = new Excel.Application();
// Make Excel visible.
xlApp.Visible = true;
Excel.Workbook workbook = xlApp.Workbooks.Add(Excel.XlWBATemplate.xlWBATWorksheet);
Excel.Worksheet sheet = (Excel.Worksheet)xlApp.Worksheets[1];
sheet.Select(Type.Missing);
//Loop through arrays in parsedData list.
for (var lstElement=0;lstElement<parsedData.Count;lstElement++)
{
//Loop through array.
for(var arryElement = 0; arryElement<parsedData[lstElement].Count(); arryElement++)
{
sheet.Cells[lstElement + 1, arryElement + 1] = parsedData[lstElement][arryElement];
}
}
// Save the changes and close the workbook.
workbook.Close(true, fileName, Type.Missing);
// Close the Excel server.
xlApp.Quit();
}
When working with Office interop, the slowest part are the inter process calls which happen anytime you access some property or method of the automation class/interface.
So the optimization goal should be to minimize the roundtrips (inter process calls).
In your particular use case, instead of setting values cell by cell (i.e. doing a lot of calls), there is fortunately a way to set values of a whole Excel range with one call by passing array of values. Depending of how many columns contains your data, the following modification should give you a significant speedup.
The significant part:
//Loop through arrays in parsedData list.
int row = 1, column = 1;
object[] values = null; // buffer - see below. Avoids unnecessary allocations.
for (var lstElement = 0; lstElement < parsedData.Count; lstElement++)
{
var data = parsedData[lstElement];
if (data == null || data.Length == 0) continue;
if (data.Length == 1)
{
// Single cell
sheet.Cells[row, column] = data[0];
}
else
{
// Cell range
var range = sheet.Range[CellName(row, column), CellName(row, column + data.Length - 1)];
// We can pass the data array directly, but since it's a string[], Excel will treat them as text.
// The trick is to to pass them via object[].
if (values == null || values.Length != data.Length)
values = new object[data.Length];
for (int i = 0; i < data.Length; i++)
values[i] = data[i];
// Set all values in a single roundtrip
range.Value2 = values;
}
row++;
}
Helpers used:
static string CellName(int row, int column)
{
return ColumnName(column) + row;
}
static string ColumnName(int column)
{
const int StartLetter = 'A', EndLetter = 'Z', LetterCount = EndLetter - StartLetter + 1;
int index = column - 1;
var letter = (char)(StartLetter + (index % LetterCount));
if (index < LetterCount) return letter.ToString();
var firstLetter = (char)(StartLetter + index / LetterCount - 1);
return new string(new [] { firstLetter, letter });
}
Once you get the idea, you can get even better performance by extending the above to handle multi row ranges like this (the most important thing in this case is to use 2d array for values):
const int MaxCells = 1 * 1024 * 1024; // Arbitrary
var maxColumns = parsedData.Max(data => data.Length);
var maxRows = Math.Min(parsedData.Count, MaxCells / maxColumns);
object[,] values = null;
int row = 1, column = 1;
for (int lstElement = 0; lstElement < parsedData.Count; )
{
int rowCount = Math.Min(maxRows, parsedData.Count - lstElement);
if (values == null || values.GetLength(0) != rowCount)
values = new object[rowCount, maxColumns];
for (int r = 0; r < rowCount; r++)
{
var data = parsedData[lstElement++];
for (int c = 0; c < data.Length; c++)
values[r, c] = data[c];
}
var range = sheet.Range[CellName(row, column), CellName(row + rowCount - 1, column + maxColumns - 1)];
range.Value2 = values;
row += rowCount;
}
I am facing a strange phenomenon.
I wrote a method that copy values from datatable to an excel sheet.
The problem is that the rows are copied to columns and columns to rows.
For example:
DataTable:
Item Quantity UnitPrice TotalPrice
A 10 2 20
B 3 15 45
C 100 0.5 50
Excel Sheet:
Item Quantity UnitPrice TotalPrice
A B C
10 3 100
2 15 0.5
20 45 50
My code:
private void PopulateDataWorkSheet()
{
xl.Workbooks xlWorkbooks = _xlApp.Workbooks;
xl.Workbook xlWorkbook = xlWorkbooks.Open(_excelFileName);
xl.Sheets xlSheets = xlWorkbook.Sheets;
const int startRowIndex = 2; // first row for columns title
const int startColumnIndex = 1;
xl.Worksheet xlDataWorkSheet = xlSheets[DATA_SHEET];
xl.Range xlCells = xlDataWorkSheet.Cells;
for (var rowindex = 0; rowindex < _datatable.Rows.Count; rowindex++)
{
for (var columnindex = 0; columnindex < _datatable.Columns.Count; columnindex++)
{
xlCells[startRowIndex + rowindex][startColumnIndex + columnindex] = _datatable.Rows[rowindex][columnindex];
}
}
xlWorkbook.Save();
xlWorkbook.Close();
xlWorkbooks.Close();
ReleaseExcelObject(xlCells);
ReleaseExcelObject(xlDataWorkSheet);
ReleaseExcelObject(xlSheets);
ReleaseExcelObject(xlWorkbook);
ReleaseExcelObject(xlWorkbooks);
}
I can simply solve it by changing as following but I am not clear what is wrong:
xlCells[startColumnIndex + columnindex][startRowIndex + rowindex] = _datatable.Rows[rowindex][columnindex];
Did I miss something in my code?
I am facing a strange phenomenon. Did I miss something in my code?
Welcome to the world of Excel Range, dynamics, optional parameters and COM object Default member concept.
Excel Range is a strange thing. Almost every method/property returns another range. The indexer you used is something like this
dynamic this[object RowIndex, object ColumnIndex = Type.Missing] { get; set; }
i.e. has required row index and optional column index.
Let take this call
xlCells[startRowIndex + rowindex][startColumnIndex + columnindex] = ...;
it's equivalent to something like this
var range1 = (Excel.Range)xlCells[startRowIndex + rowindex];
var range2 = (Excel.Range)range1[startColumnIndex + columnindex];
// ^^^^^^
// this is treated as row index offset from the range1
range2.Value2 = ...;
Shortly, you need to change it to
xlCells[startRowIndex + rowindex, startColumnIndex + columnindex] = ...;
You need to change how you access your excel cells.
cells[StartRowIndex+ rowindex, StartColumnIndex + columnindex] is Row first and then column. What you do is cells[StartRowIndex + rowindex][StartColumnIndex + columnindex] which takes the column as a first argument.
The order of the loops does not matter at all.. so the right solution would be:
for (var rowindex = 0; rowindex < _datatable.Rows.Count; rowindex++)
{
for (var columnindex = 0; columnindex < _datatable.Columns.Count; columnindex++)
{
xlCells[startRowIndex + rowindex, startColumnIndex + columnindex] = _datatable.Rows[rowindex][columnindex];
}
}
Cheers!
You doing it wrong, you need to go like this:
for (var columnindex = 0; columnindex < _datatable.Columns.Count; columnindex++)
{
for (var rowindex = 0; rowindex < _datatable.Rows.Count; rowindex++)
{
xlCells[startRowIndex + rowindex][startColumnIndex + columnindex] = _datatable.Rows[rowindex][columnindex];
}
}
you need the column and then the row.
You can use following code to overcome the problem.It works fine for me.
xlApplication = new Microsoft.Office.Interop.Excel.Application();
// Create workbook .
xlWorkBook = xlApplication.Workbooks.Add(Type.Missing);
// Get active sheet from workbook
xlWorkSheet = xlWorkBook.ActiveSheet;
xlWorkSheet.Name = "Report";
xlWorkSheet.Columns.ColumnWidth = 30;
xlApplication.DisplayAlerts = false;
for (int rowIndex = 0; rowIndex < gridViewDataTable.Rows.Count; rowIndex++)
{
for (int colIndex = 0; colIndex < gridViewDataTable.Columns.Count; colIndex++)
{
if (rowIndex == 0)
{
xlWorkSheet.Cells[rowIndex + 1, colIndex + 1] = gridViewDataTable.Columns[colIndex].ColumnName;
xlWorkSheet.Cells[rowIndex + 1, colIndex + 1].Interior.Color = System.Drawing.Color.LightGray;
}
xlWorkSheet.Cells[rowIndex + 2, colIndex + 1].Borders.Color = Color.Black;
xlWorkSheet.Cells[rowIndex + 2, colIndex + 1].Interior.Color = System.Drawing.Color.White;
xlWorkSheet.Cells[rowIndex + 2, colIndex + 1] = gridViewDataTable.Rows[rowIndex][colIndex];
}
}
xlWorkBook.SaveAs(filename);
xlWorkBook.Close();
xlApplication.Quit();
Currently I am using the below segement of code to get a row with a value int in the 3rd column.
private int getRowByRDS(int id)
{
int bestfit = -1;
Boolean foundOne = false;
for (int i = 2; i < oSheet.Rows.Count; i++)
{
string val = getValueOfCell(i, 3);
if (val == null)
continue;
int rds = int.Parse(val);
if (rds == id)
{
bestfit = i;
foundOne = true;
}
else
if (foundOne)
return bestfit;
}
return bestfit;
}
The issues is that this is pathetically show at large amount of rows.
Can someone suggest a better way of searching col 3 for a int and returning the last row # that it is in.
For Joe:
public void inputRowData(string[] data, int rds)
{
int bestRow = getRowByRDS_a(rds);
string[] formatedData = formatOutput(bestRow, data);
string val = getValueOfCell(bestRow, 6);
if (val != null)
{
shiftRows(bestRow, data.Length);
bestRow++;
}
else
shiftRows(bestRow, data.Length - 1);
// transform formated data into string[,]
string[][] splitedData = formatedData.Select(s => s.Split('\t')).ToArray();
var colCount = splitedData.Max(r => r.Length);
var excelData = new string[splitedData.Length, colCount];
for (int i = 0; i < splitedData.Length; i++)
{
for (int j = 0; j < splitedData[i].Length; j++)
{
excelData[i, j] = splitedData[i][j];
}
}
oSheet.get_Range("A" + bestRow.ToString()).Resize[splitedData.Length, colCount].Value = excelData;
MainWindow.mainWindowDispacter.BeginInvoke(new System.Action(() => MainWindow.mainWindow.debugTextBox.AppendText("Done with " + rds + " input!" + Environment.NewLine)));
}
private void shiftRows(int from, int numberof)
{
from++;
Range r = oXL.get_Range("A" + from.ToString(), "A" + from.ToString()).EntireRow;
for (int i = 0; i < numberof; i++)
r.Insert(Microsoft.Office.Interop.Excel.XlInsertShiftDirection.xlShiftDown);
}
Here's how I'd do it:
Get a Range corresponding to the column you're interested in
Get the UsedRange of the sheet you're interested in
Get a Range that is the intersection of the above two ranges
Get the value of this Range, which will be an array of values from the column you're interested in
You can then iterate through this array to find the value you want, then use its index to derive the row number.
The number of calls to the Excel is O(1) with the above method, as opposed to O(n) in your version.
You could also use the Find method of the Excel.Range object. This method returns a Excel.Range object if a match is found or null. Follow the first three steps that Joe described to create the search range. If the Find method returns a valid Range, you can then use it's Row Property.
P.S.: Sorry Joe I wanted to upvote your answer, but I am not allowed to do so yet.