How to persist Excel cell formats in C# Interop? - c#

I am reading an Excel sheet programmatically using Microsoft.Office.Interop.Excel in C#.
I am able to read it row by row and converting each row to a string arrray. Then, I am adding these rows to a DataTable.
Every thing works fine except the one of the column in the Excel contains Date values, and when I fetch it from the Excel Range object and cast it to string array, the date values gets converted to some sort of decimal numbers.
For e.g.-
If the date value is '6/4/2016 8:14:39 PM', I get the value as '42522.5224305556'
If the date value is '5/27/2016 1:10:12 PM', I get the value as '42517.54875'
Below is my code-
private System.Data.DataTable GetTicketsFromExcel(string excelFilePath)
{
System.Data.DataTable dtblTickets = new System.Data.DataTable();
Microsoft.Office.Interop.Excel.Application excelApp = new Microsoft.Office.Interop.Excel.Application();
Worksheet ws = new Worksheet();
Workbook wb = null;
try
{
wb = excelApp.Workbooks.Open(excelFilePath, Type.Missing, Type.Missing,
Type.Missing, Type.Missing,
Type.Missing, Type.Missing,
Type.Missing, Type.Missing,
Type.Missing, Type.Missing,
Type.Missing, Type.Missing,
Type.Missing, Type.Missing);
ws = (Microsoft.Office.Interop.Excel.Worksheet)wb.Sheets.get_Item(1);
Range usedRange = ws.UsedRange;
Range rowRange;
string[] lsRow = null;
for (int i = 1; i <= usedRange.Columns.Count; i++)
{
dtblTickets.Columns.Add(usedRange.Cells[5, i].Value.ToString());
}
string sortColumn = "Reported On";
string sortDirection = "DESC";
dtblTickets.Columns[sortColumn].DataType = typeof(DateTime);
for (int row = 6; row <= usedRange.Rows.Count; row++)
{
//dtblTickets.Columns.Add()
rowRange = usedRange.Rows[row];
object[,] cellValues = (object[,])rowRange.Value2;
lsRow = cellValues.Cast<object>().Select(o => Convert.ToString(o)).ToArray<string>();
dtblTickets.Rows.Add(lsRow.ToArray());
}
dtblTickets.DefaultView.Sort = sortColumn + " " + sortDirection;
dtblTickets = dtblTickets.DefaultView.ToTable();
}
catch (Exception ex)
{
}
finally
{
wb.Close();
excelApp.Quit();
Marshal.ReleaseComObject(ws);
Marshal.ReleaseComObject(wb);
Marshal.ReleaseComObject(excelApp);
ws = null;
wb = null;
excelApp = null;
}
return dtblTickets;
}
Please note-
I don't want to use OLEDB to read and export this
I want to able to read the Excel row by row (without extracting each cell value and converting them)
I don't want to convert/format the original Excel document data
Can someone please help me with this?

Not quite sure, if you want to solve the problem this way, but one way is to change the property of the Cells (or the whole row or column) in Excel.
Right click on a Cell
Format Cells
Under "Number" select Category "Text" for the Cells.
I've tested it and it worked.

Related

Excel is not updated with interop in C#

I have the following code that based on the found value in an excel i have to update the other cell values in that row. But it is not updating the values back to the excel sheet. MasterTable is of DataTable which has the same column values as the excel i need to update these values to the excel. please help me find the issue.
for (int row = 0; row < masterTable.Rows.Count; row++)
{
// Get the CIS ID
string CISid = Convert.ToString(masterTable.Rows[row][RemoveSpecialCharachtersAndSpace("SOW / CIS No")]);
Excel.Range range = xlWorksheet.UsedRange.Rows.Find(CISid, System.Type.Missing, Excel.XlFindLookIn.xlValues, Excel.XlLookAt.xlPart, Excel.XlSearchOrder.xlByRows, Excel.XlSearchDirection.xlNext, System.Type.Missing, System.Type.Missing, System.Type.Missing);
//FindRangeCISByID(CISid);
if (range != null && range.Rows.Count > 0 && (!string.IsNullOrEmpty(Convert.ToString(range.Rows.Cells[1, 1].Value2))))
{
for (int cell1 = 1; cell1 < 5; cell1++)
{
//Master Excel Copy
string masterColumnCopy = Convert.ToString(range.Rows.Cells[1, cell1].Value2);
// Local Data table copy of column value
string datatableColumnValue = GetColumnValueFromDataTable(masterTable, cell1);
xlWorksheet.UsedRange.Find(CISid).Cells[1, cell1].Value = datatableColumnValue ;
}
}
}
xlWorkbook.Save();
xlWorkbook.Close();
xlApp.Quit();`
As odd it sounds , but it is working with saveAs function for work book not with the save.
I have changed the save code to the following
xlWorkbook.SaveAs(#"D:\ExcelData_new.xlsx", Microsoft.Office.Interop.Excel.XlFileFormat.xlWorkbookDefault, Type.Missing, Type.Missing,
false, false, Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlNoChange,
Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
and it is working

delete from multiple worksheet in Excel

I like to delete rows from multiple worksheet in Excel. Currently my code only delete rows from active worksheet. I am looking for a specific value on the cell then when I find this value then I am deleting all the rows up to that value that stored on that row.
Code
private void button2_Click(object sender, EventArgs e)
{
Microsoft.Office.Interop.Excel.Application Excel = new Microsoft.Office.Interop.Excel.Application();
Workbook workBook = Excel.Workbooks.Open(FilePath);
Worksheet ws = (Worksheet)Excel.ActiveSheet;
for (int j = 1; j <= 10; j++)
{
for (int i = 1; i <= 20; i++)
{
if (Convert.ToString(((Microsoft.Office.Interop.Excel.Range)ws.Cells[j, i]).Value2) == "Matter")
{
for (int r = 1; r <= j; r++)
{
((Range)ws.Rows[r]).Delete(XlDeleteShiftDirection.xlShiftUp);
MessageBox.Show(Convert.ToString(r));
workBook.SaveAs(#"C:\Users\Separate\New.xlsx", Microsoft.Office.Interop.Excel.XlFileFormat.xlWorkbookDefault, Type.Missing, Type.Missing,
false, false, Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlNoChange,
Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
}
}
}
}
workBook.Close(Type.Missing, Type.Missing, Type.Missing);
Excel.Quit();
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(Excel);
Excel = null;
}
I would highly suggest trying to use ClosedXML as it makes working with Excel so much easier. Specifically with ClosedXML you can have a line like this:
ws.Row(5).Delete();
where ws is your initialized worksheet, and it handles the heavy lifting for deleting that row.
Getting or setting cell data is just as simple:
ws.Cell(2, 2).Value = "Initial Value";
or alternatively
ws.Cell("B2").Value = "Value;
They also have type specific value assignment like this:
ws.Cell("B2").SetValue<T>(object);
The documentation is very thorough, and you can get the package through nuget (note it requires the DocumentFormat.OpenXML package installed as well)
EDIT:
I missed the part about multiple worksheets so here it is. The XLWorkbook type has a property Worksheets, which is an enumerable of all the worksheets in the workbook. Use this to get the worksheets you want to delete rows on

How do I get and compare the contents of a cell in an Excel spreadsheet by name?

I'm trying to compare a cell with a string to replace it if it is equal. But when I try to do the code below the 0x800A03EC error occurs.
int cont = 0;
string cell;
do
{
cont++;
cell = rCol.ToUpper() + cont.ToString(); // = "D1"
string cellData = ((Excel.Range)sheet.Cells[cell]).Value2.ToString();
if (cellData == from)
{
sheet.Cells[cell] = to;
}
} while (sheet.Cells[cell] == null);
How can I do this?
If you know the cell you want to check, for example A1, you can do it like this:
using System;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;
namespace Test
{
class Program
{
static void Main(string[] args)
{
// create app
var excelApp = new Excel.Application();
// open workbook
var workbook = excelApp.Workbooks.Open(
#"C:\Users\Home\Documents\Book1.xlsx",
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing);
// open sheet
var sheet = (Excel.Worksheet)workbook.Sheets[1];
// create some variables
var from = "Pete";
var to = "Dave";
// compare cell A1 [1,1] with 'from'
if (string.Equals(sheet.Cells[1,1].Value, from))
{
sheet.Cells[1, 1].Value = to;
}
// save the workbook
workbook.Save();
// close the workbook and release resources
workbook.Close(true, workbook.Path);
Marshal.ReleaseComObject(workbook);
workbook = null;
}
}
}
Try this to get at a simple range:
int row = 1;
string col = "D";
string text = sheet.get_Range(col + row.ToString()).Value;
The 0x800A03EC error is a value returned by Excel, which means NAME_NOT_FOUND (see this SA question). Looks like you were passing a parameter that Excel could not find, probably because you were passing a string ("D1"), rather than two integer parameters (4,1).
It's impossible to tell where you are getting the rCol.ToUpper() value, without seeing more of your code. However, if you are attempting to go through a series of columns and rows to check for an equality condition (that's what it looks like you are attempting), you will quickly run into the pesky problem of how to increment the column value using capital letters (just try it; not much fun!).
One solution I did in VB recently was to use the native Excel function index, which uses numeric values to get at a particular cell. You would need to cast an object of the type Excel.WorksheetFunction to use that function. But I have since discovered there are easier solutions than using an Excel function:
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;
namespace exceltest
{
class Program
{
static void Main(string[] args)
{
Microsoft.Office.Interop.Excel.Application xl = new Microsoft.Office.Interop.Excel.Application();
Microsoft.Office.Interop.Excel.Workbook workbook = xl.Workbooks.Open(#"C:\test.xlsx");
Microsoft.Office.Interop.Excel.Worksheet sheet = workbook.Sheets[1];
xl.Visible = true;
//use this if you want to use native Excel functions (such as index)
Microsoft.Office.Interop.Excel.WorksheetFunction wsFunc = xl.WorksheetFunction;
int maxNum = 100; // set maximum number of rows/columns to search
string from = "blah";
string to = "blum";
//this is pretty slow, since it has to interact with 10,000 cells in Excel
// just one example of how to access and set cell values
for (int col = 1; col <= maxNum; col++)
{
for (int row = 1; row <= maxNum; row ++)
{
Range cell = (Range)sheet.Cells[row, col];
if ((string)cell.Value == from) //cast to string to avoid null reference exceptions
{
cell.Value = to;
}
}
}
}
}
}

How to NOT skip empty rows when reading Excel sheet?

I am using Microsoft.Office.Interop.Excel to read an Excel file.
The problem I am having is that when reading, it counts the first row that has information as row 1. So, if the first row to have information is row 3, it counts that as the first row. This is a problem because the Excel sheets sometimes have data on the first 2 rows, sometimes they don't. But the data I need to read always starts on the third row.
Here is the routine I am using to read the sheets:
Microsoft.Office.Interop.Excel.Application exl = new Microsoft.Office.Interop.Excel.Application();
Workbooks wkbs = exl.Workbooks;
Microsoft.Office.Interop.Excel.Workbook wkb = null;
Sheets shts;
Microsoft.Office.Interop.Excel.Worksheet wks = null;
// load work book
//wkb = wkbs.Open(races_data_path, 0, true, 5, "", "", true, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "\t", false, false, 0, true, 1, 0);//oMissing
wkb = wkbs.Open(races_data_path, Type.Missing, true, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);//oMissing
shts = wkb.Sheets;
wks = (Microsoft.Office.Interop.Excel.Worksheet)shts.get_Item(Properties.Settings.Default.excel_worksheet);
// read lines from worksheet
Microsoft.Office.Interop.Excel.Range range = wks.UsedRange;
for (int rCnt = 1; rCnt <= range.Rows.Count; rCnt++)
{
try
{
object[,] values = (object[,])range.Value2;
if (Convert.ToString(values[rCnt, 4]) != "")
{
//Console.WriteLine(String.Format("Ldd: {0} {1} {2}", Convert.ToInt16(values[rCnt, 2]), Convert.ToInt16(values[rCnt, 3]), Convert.ToString(values[rCnt, 4])));
if (races[Convert.ToInt16(values[rCnt, 1])] == null) races[Convert.ToInt16(values[rCnt, 1])] = new RaceObject();
races[Convert.ToInt16(values[rCnt, 1])].add_racer(Convert.ToInt16(values[rCnt, 2]), Convert.ToInt16(values[rCnt, 3]), Convert.ToString(values[rCnt, 4]), Convert.ToString(values[rCnt, 7]));
}
}
catch (Exception ex)
{
data_loaded = false;
rCnt = range.Rows.Count;
}
}
This example, you can see the loop count starts at '1'. This was for reading a sheet where the first 2 lines of the sheet were empty. If the first 2 lines have data, then this loop only works if I change the count start to '3'.
For example:
The start value = 1 if the sheet looks like this:
no data
no data
has data
has data
has data
The start value = 3 if the sheet looks like this:
has unnecessary data
has unnecessary data
has data
has data
has data
Option 1: Add something to the first cell immediately before doing the rest: wks.cells(1, 1).value = "'" & wks.cells(1, 1).value This will extend the usedRange to the first cell.
Then the data will always start on the third row and you can easily always skip the first two rows.
Option 2: You can also use wks.UsedRange.Cells(1, 1).Row to see whether the first row is 1 or 3. Then you can adjust whether you use those rows or not.
(Edited post based on comments)

Writing contents of two list to two different sheets in the same excel workbook

I am working on couple of C# list which need to be written to an excel workbook into two different sheets Sheet1 and Sheet2 but having no idea on how to deal with excel using interop I am using the following method
public void ExportListToExcel(List<String> listExport,string sheetName)
{
try
{
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;
worksheet = (Microsoft.Office.Interop.Excel._Worksheet)workbook.Sheets[sheetName];
worksheet = (Microsoft.Office.Interop.Excel._Worksheet)workbook.ActiveSheet;
for (int i = 1; i < listExport.Count + 1; i++)
{
//int count = 1;
worksheet.Cells[i, 1] = listExport[i - 1];
//count++;
}
string fileDestination = #"C:\Atlas Applications\AxiomParser\axiom.xls";
if (File.Exists(fileDestination))
{
File.Delete(fileDestination);
}
workbook.SaveAs(fileDestination, Microsoft.Office.Interop.Excel.XlFileFormat.xlWorkbookNormal, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlExclusive, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
workbook.Close(true, Type.Missing, Type.Missing);
Process.Start(fileDestination);
app.Quit();
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
ExportListToExcel(listMultiRowThreeWay,"Sheet1");
ExportListToExcel(listMultiRowButterFly, "Sheet2");
I am calling the above method twice with two sheet names in the method but it give me an invalid index error when executing the method for the second time
**Is there a better way to do it?**

Categories

Resources