I am trying to read an excel file with C# and display every cell in the sheet inside a messagebox using the messagebox.Show() method. Problem is that my excel file has 5 rows and 3 columns. here is my excel sheet: http://postimg.org/image/xts9n1kif.
It displays everything until "roof" and after that stops leaving behind "light" and "iron", but if i fill the stuff3 column it reads everything perfectly fine. As is my case, the file may change. It may have more columns or rows in it, and some of them may be empty.
Any idea why it is not working?
Here is my code:
using System;
using System.Windows.Forms;
using Excel = Microsoft.Office.Interop.Excel;
namespace ReadFromExcell
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Load += new System.EventHandler(this.Form1_Load);
}
private void Form1_Load(object sender, EventArgs e)
{
Excel.Application xlApp = new Excel.Application();
Excel.Workbook xlWorkbook = xlApp.Workbooks.Open("C:\\Users\\User1\\Desktop\\ItemDB.xlsx");
Excel._Worksheet xlWorksheet = xlWorkbook.Sheets[1];
Excel.Range xlRange = xlWorksheet.UsedRange;
int rowCount = xlRange.Rows.Count;
int colCount = xlRange.Columns.Count;
for (int i = 1; i <= rowCount; i++)
{
for (int j = 1; j <= colCount; j++)
{
MessageBox.Show(xlRange.Cells[i, j].Value2.ToString());
}
}
//Close the excel file after reading it.
xlWorkbook.Close();
}
}
}
Most likely what happens in your code is that when the loop hits cell C4 (which is empty) xlRange.Cells[i, j].Value2 becomes null.
Trying to invoke the ToString() method (or any other method, for that matter) on a null reference will cause a NullReferenceException.
Change your code inside your loop from...
MessageBox.Show(xlRange.Cells[i, j].Value2.ToString());
...to something like that:
MessageBox.Show( (xlRange.Cells[i, j].Value2 ?? "<no value>").ToString() );
The ?? operator is handy in this situation. If the expression on the left side of ?? (that is xlRange.Cells[i, j].Value2) results in a null value, the ?? operator returns the value on the right-hand side of ?? instead.
(The cell C4 could contain a number of white-space characters, in which case the cell's value would not be null. But a cell with just a number of white-space characters is a rather unusual and rare occurrence.)
Now Try this..
for (int i = 0; i <= rowCount-1; i++)
{
for (int j = 0; j <= xlRange.Rows[0].Columns.Count-1; j++)
{
MessageBox.Show(xlRange.Cells[i, j].Value2.ToString());
}
}
Related
Basically what I need to do is, I have a lot of data that takes up 1 cell per row loaded into a datagridview, and I want to pivot it, and export it to an excel file. So what I have is a vertical list, and I would like to make it horizontal, but I only need the cells to be filled until getting to column 'L', and when the data gets to it, then it starts again at the next row, and repeating this until dgv has no more data to fill in to the excel.
So far what I have is:
private void button1_Click(object sender, EventArgs e)
{
Excel.Application xlApp = new Excel.Application();
Excel.Worksheet xlsht = new Excel.Worksheet();
Microsoft.Office.Interop.Excel.Workbook xlwb;
xlApp.Visible = true;
string path = #"myfilepath";
xlwb = xlApp.Workbooks.Open(path);
xlsht = xlwb.Worksheets["Tabelle1"];
for (int i = 0; i < dataGridView1.Rows.Count - 1; i++)
{
for (int j = 1; j < 12; j++)
{
xlsht.Cells[i + 1, j] = dataGridView1.Rows[i].Cells[0].Value.ToString();
}
}
The problem with this is the following: it fills the cells with the same value, until getting to the column 'L' (11th column, that's why I have 12 in the nested for loop), and then it starts again in the next row, but again, repeating the next value(to clarify, the value in the first row is 1, so it repeats 1 until getting to the 11th column.Then it starts again with the next value, for example 2, and does the same thing).
How could I fix this?
Thanks in advance!
You need to separate the Excel row and column indexes from the loop through the grids rows. All you need is one loop though all the rows to add the first column of that row to the worksheet. To keep it simple, pull the Excel indexs and manually update them.
In the loop through the rows, increment the worksheets column index by 1 until it reaches 12, Then increment the excel row index by 1 and reset the excel column back to 1.
Something likeā¦.
int xlRow = 1;
int xlCol = 1;
for (int i = 0; i < dataGridView1.Rows.Count; i++) {
if (!dataGridView1.Rows[i].IsNewRow) {
xlsht.Cells[xlRow, xlCol] = dataGridView1.Rows[i].Cells[0].Value.ToString();
if (xlCol == 12) {
xlRow++;
xlCol = 1;
}
else {
xlCol++;
}
}
}
I am trying to write this a set of data into a single column in excel with using a loop. I cant figure out why it currently does not write to different cells.
I have tried running this code and it shows up with just one of the values in the first cell that it should write in
foreach (Element e in wirelist)
{
Excel.Worksheet sheet =
(Excel.Worksheet)workbook.Worksheets.get_Item("Wires (Exported)");
int i = 3;
int j = 1;
((Excel.Range)sheet.Cells[i, j]).Value = e.Id;
i = i + 1;
}
It should print out a list of ID numbers in separate cells in the column.
Questions let me know!!
I think your issue is that you keep getting the Excel sheet and resetting i and j within the loop. Try this:
Excel.Worksheet sheet = (Excel.Worksheet)workbook.Worksheets.get_Item("Wires (Exported)");
int i = 3;
int j = 1;
foreach (Element e in wirelist)
{
((Excel.Range)sheet.Cells[i, j]).Value = e.Id;
i = i + 1;
}
I have a code that displays a table from an Access database, on my WinForm I have an option to export the table to excel, once the user click on it takes some time to copy all rows to excel, if the user try to close the excel before all cells get transfer to the sheet the application will stop and throw the error System.Runtime.InteropServices.COMException: 'Exception from HRESULT: 0x800AC472'
Here is the code for the "Export to Excel" button I have in my windows form
private void Export_btn_Click(object sender, EventArgs e)
{
Microsoft.Office.Interop.Excel._Application app = new Microsoft.Office.Interop.Excel.Application();
Microsoft.Office.Interop.Excel._Workbook workbook = app.Workbooks.Add(Type.Missing);
Microsoft.Office.Interop.Excel._Worksheet worksheet = null;
app.Visible = true;
worksheet = workbook.Sheets["Sheet1"];
worksheet = workbook.ActiveSheet;
for (int i = 1; i < fviAoi_tbl.Columns.Count + 1; i++)
{
worksheet.Cells[1, i] = fviAoi_tbl.Columns[i - 1].HeaderText;
}
for (int i = 0; i < fviAoi_tbl.Rows.Count - 1; i++)
{
for (int j = 0; j < fviAoi_tbl.Columns.Count; j++)
{
if (fviAoi_tbl.Rows[i].Cells[j].Value != null)
{
worksheet.Cells[i + 2, j + 1] = fviAoi_tbl.Rows[i].Cells[j].Value.ToString();
}
else
{
worksheet.Cells[i + 2, j + 1] = "";
}
}
}
}
Any ideas why this is happening or how can I make my application to ignore that error and continue running.
Surround the code line that emit the exception with a try... catch...
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/exceptions/
In general, Null is probably not what you think it is.
Null is not 0 nor empty string. Null is lack of data. What is the difference between ("") and (null)
Thus, C# and .NET probably throws an error here:
if (fviAoi_tbl.Rows[i].Cells[j].Value != null) because, it does not understand how can you compare some Excel cell with Null and what should it answer. Change the code to:
if (fviAoi_tbl.Rows[i].Cells[j].Value != "" or something similar.
I am trying to extract all text data from an Excel document in C# and am having performance issues. In the following code I open the Workbook, loop over all worksheets, and loop over all cells in the used range, extracting the text from each cell as I go. The problem is, this takes 14 seconds to execute.
public class ExcelFile
{
public string Path = #"C:\test.xlsx";
private Excel.Application xl = new Excel.Application();
private Excel.Workbook WB;
public string FullText;
private Excel.Range rng;
private Dictionary<string, string> Variables;
public ExcelFile()
{
WB = xl.Workbooks.Open(Path);
xl.Visible = true;
foreach (Excel.Worksheet CurrentWS in WB.Worksheets)
{
rng = CurrentWS.UsedRange;
for (int i = 1; i < rng.Count; i++)
{ FullText += rng.Cells[i].Value; }
}
WB.Close(false);
xl.Quit();
}
}
Whereas in VBA I would do something like this, which takes ~1 second:
Sub run()
Dim strText As String
For Each ws In ActiveWorkbook.Sheets
For Each c In ws.UsedRange
strText = strText & c.Text
Next c
Next ws
End Sub
Or, even faster (less than 1 second):
Sub RunFast()
Dim strText As String
Dim varCells As Variant
For Each ws In ActiveWorkbook.Sheets
varCells = ws.UsedRange
For i = 1 To UBound(varCells, 1)
For j = 1 To UBound(varCells, 2)
strText = strText & CStr(varCells(i, j))
Next j
Next i
Next ws
End Sub
Perhaps something is happening in the for loop in C# that I'm not aware of? Is it possible to load a range into an array-type object (as in my last example) to allow iteration over just the values, not the cell objects?
Excel and C# run in different environments completely. C# runs in the .NET framework using managed memory while Excel is a native C++ application and runs in unmanaged memory. Translating data between these two (a process called "marshaling") is extremely expensive in terms of performance.
Tweaking your code isn't going to help. For loops, string construction, etc. are all blazingly fast compared to the marshaling process. The only way you are going to get significantly better performance is to reduce the number of trips that have to cross the interprocess boundary. Extracting data cell by cell is never going to get you the performance you want.
Here are a couple options:
Write a sub or function in VBA that does everything you want, then call that sub or function via interop. Walkthrough.
Use interop to save the worksheet to a temporary file in CSV format, then open the file using C#. You will need to loop through and parse the file to get it into a useful data structure, but this loop will go much faster.
Use interop to save a range of cells to the clipboard, then use C# to read the clipboard directly.
I use this function. The loops are only for converting to array starting at index 0, the main work is done in object[,] tmp = range.Value.
public object[,] GetTable(int row, int col, int width, int height)
{
object[,] arr = new object[height, width];
Range c1 = (Range)Worksheet.Cells[row + 1, col + 1];
Range c2 = (Range)Worksheet.Cells[row + height, col + width];
Range range = Worksheet.get_Range(c1, c2);
object[,] tmp = range.Value;
for (int i = 0; i < height; ++i)
{
for (int j = 0; j < width; ++j)
{
arr[i, j] = tmp[i + tmp.GetLowerBound(0), j + tmp.GetLowerBound(1)];
}
}
return arr;
}
One thing which will speed it up is to use a StringBuilder instead of += on the previous string. Strings are immutable in C# and therefore you are creating a ton of extra strings during your process of creating the final string.
Additionally you may improve performance looping over the row, column positions instead of looping over the index.
Here is the code changed with a StringBuilder and row, column positional looping:
public class ExcelFile
{
public string Path = #"C:\test.xlsx";
private Excel.Application xl = new Excel.Application();
private Excel.Workbook WB;
public string FullText;
private Excel.Range rng;
private Dictionary<string, string> Variables;
public ExcelFile()
{
StringBuilder sb = new StringBuilder();
WB = xl.Workbooks.Open(Path);
xl.Visible = true;
foreach (Excel.Worksheet CurrentWS in WB.Worksheets)
{
rng = CurrentWS.UsedRange;
for (int i = 1; i <= rng.Rows.Count; i++)
{
for (int j = 1; j <= rng.Columns.Count; j++)
{
sb.append(rng.Cells[i, j].Value);
}
}
}
FullText = sb.ToString();
WB.Close(false);
xl.Quit();
}
}
I sympathize with you pwwolff. Looping through Excel cells can be expensive. Antonio and Max are both correct but John Wu's answer sums it up nicely. Using string builder may speed things up and making an object array from the used range IMHO is about as fast as you are going to get using interop. I understand there are other third party libraries that may perform better. Looping through each cell will take an unacceptable amount of time if the file is large using interop.
On the tests below I used a workbook with a single sheet where the sheet has 11 columns and 100 rows of used range data. Using an object array implementation this took a little over a second. With 735 rows it took around 40 seconds.
I put 3 buttons on a form with a multi line text box. The first button uses your posted code. The second button takes the ranges out of the loops. The third button uses an object array approach. Each one has a significant performance improvement over the other. I used a text box on the form to output the data, you can use a string as you are but using a string builder would be better if you must have one big string.
Again, if the files are large you may want to consider another implementation. Hope this helps.
private void button1_Click(object sender, EventArgs e) {
Stopwatch sw = new Stopwatch();
MessageBox.Show("Start DoExcel...");
sw.Start();
DoExcel();
sw.Stop();
MessageBox.Show("End DoExcel...Took: " + sw.Elapsed.Seconds + " seconds and " + sw.Elapsed.Milliseconds + " Milliseconds");
}
private void button2_Click(object sender, EventArgs e) {
MessageBox.Show("Start DoExcel2...");
Stopwatch sw = new Stopwatch();
sw.Start();
DoExcel2();
sw.Stop();
MessageBox.Show("End DoExcel2...Took: " + sw.Elapsed.Seconds + " seconds and " + sw.Elapsed.Milliseconds + " Milliseconds");
}
private void button3_Click(object sender, EventArgs e) {
MessageBox.Show("Start DoExcel3...");
Stopwatch sw = new Stopwatch();
sw.Start();
DoExcel3();
sw.Stop();
MessageBox.Show("End DoExcel3...Took: " + sw.Elapsed.Seconds + " seconds and " + sw.Elapsed.Milliseconds + " Milliseconds");
}
// object[,] array implementation
private void DoExcel3() {
textBox1.Text = "";
string Path = #"D:\Test\Book1 - Copy.xls";
Excel.Application xl = new Excel.Application();
Excel.Workbook WB;
Excel.Range rng;
WB = xl.Workbooks.Open(Path);
xl.Visible = true;
int totalRows = 0;
int totalCols = 0;
foreach (Excel.Worksheet CurrentWS in WB.Worksheets) {
rng = CurrentWS.UsedRange;
totalCols = rng.Columns.Count;
totalRows = rng.Rows.Count;
object[,] objectArray = (object[,])rng.Cells.Value;
for (int row = 1; row < totalRows; row++) {
for (int col = 1; col < totalCols; col++) {
if (objectArray[row, col] != null)
textBox1.Text += objectArray[row,col].ToString();
}
textBox1.Text += Environment.NewLine;
}
}
WB.Close(false);
xl.Quit();
Marshal.ReleaseComObject(WB);
Marshal.ReleaseComObject(xl);
}
// Range taken out of loops
private void DoExcel2() {
textBox1.Text = "";
string Path = #"D:\Test\Book1 - Copy.xls";
Excel.Application xl = new Excel.Application();
Excel.Workbook WB;
Excel.Range rng;
WB = xl.Workbooks.Open(Path);
xl.Visible = true;
int totalRows = 0;
int totalCols = 0;
foreach (Excel.Worksheet CurrentWS in WB.Worksheets) {
rng = CurrentWS.UsedRange;
totalCols = rng.Columns.Count;
totalRows = rng.Rows.Count;
for (int row = 1; row < totalRows; row++) {
for (int col = 1; col < totalCols; col++) {
textBox1.Text += rng.Rows[row].Cells[col].Value;
}
textBox1.Text += Environment.NewLine;
}
}
WB.Close(false);
xl.Quit();
Marshal.ReleaseComObject(WB);
Marshal.ReleaseComObject(xl);
}
// original posted code
private void DoExcel() {
textBox1.Text = "";
string Path = #"D:\Test\Book1 - Copy.xls";
Excel.Application xl = new Excel.Application();
Excel.Workbook WB;
Excel.Range rng;
WB = xl.Workbooks.Open(Path);
xl.Visible = true;
foreach (Excel.Worksheet CurrentWS in WB.Worksheets) {
rng = CurrentWS.UsedRange;
for (int i = 1; i < rng.Count; i++) {
textBox1.Text += rng.Cells[i].Value;
}
}
WB.Close(false);
xl.Quit();
Marshal.ReleaseComObject(WB);
Marshal.ReleaseComObject(xl);
}
I am trying to import an excel file into a data table using GemBox and I keep getting this error:
Invalid data value when extracting to DataTable at SourceRowIndex: 1, and SourceColumnIndex: 1.
As far as I can tell my code is correct and my file is file fine. Does anyone have any ideas?
Thanks.
ExcelWorksheet Ew = ExFi.Worksheets[0];
for (int i = 0; i < Ew.Columns.Count; ++i)
{
if (Ew.Rows[0].Cells[0, i].Value != null)
dsTable.Columns.Add(Ew.Rows[0].Cells[0, i].Value.ToString(), typeof(string));
}
try
{
Ew.ExtractToDataTable(dsTable, Ew.Rows.Count, ExtractDataOptions.StopAtFirstEmptyRow, Ew.Rows[1], Ew.Columns[0]);
}
GemBox.Spreadsheet component doesn't automatically convert numbers to strings in ExtractToDataTable() method.
That's mainly because of the culture issues; someone would expect that number 12.4 is converted to "12.4" and someone else to "12,4".
So if your Excel file has cell with the value of type int, and corresponding column is of type string -> an exception would be thrown. To override that, you can use ExcelWorksheet.ExtractDataEvent.
Here's sample:
// Create new ExcelFile
ExcelFile ef = new ExcelFile();
// Add sheet
ExcelWorksheet ws = ef.Worksheets.Add("Sheet1");
// Fill sheet
for (int i = 0; i < 5; i++)
{
ws.Cells[i, 0].Value = i; // integer value
ws.Cells[i, 1].Value = "Row: " + i; // string value
}
// Initialize DataTable
DataTable dt = new DataTable();
dt.Columns.Add("id", typeof(string));
dt.Columns.Add("text", typeof(string));
// Manage ExtractDataError.WrongType error
ws.ExtractDataEvent += (sender, e) =>
{
if (e.ErrorID == ExtractDataError.WrongType)
{
e.DataTableValue = e.ExcelValue == null ? null : e.ExcelValue.ToString();
e.Action = ExtractDataEventAction.Continue;
}
};
// Extract data to DataTable
ws.ExtractToDataTable(dt, 1000, ExtractDataOptions.StopAtFirstEmptyRow, ws.Rows[0], ws.Columns[0]);
I had the some issue, i overcame it by explicitly accessing worksheet cells
DataTable dtResult = new DataTable();
int nRows = ws.Rows.Count;
int nCols = 3; //change this according to number of columns in your sheet, for some reason ws.columns.count returns 0
for (int i = 0; i < nCols ; i++)
dtResult.Columns.Add();
for (int i = 0; i < nRows; i++)
{
if (ws.Cells[i, 0].Value != null)
dtResult.Rows.Add(ws.Cells[i, 0].Value.ToString(), ws.Cells[i, 1].Value.ToString(), ws.Cells[i, 2].Value.ToString());
}
return dtResult;
I had the same problem. I'd been using Typed DataSets so when I tried to populate one of my tables I had that error.
The problem here is the decimal numbers in the Excel file. Initialy I assigned the columns with decimal values to System.Decimal and that error was thrown.
The solution is to change the Type of the Column to System.Double. Why? I don't know, but it worked.