Which way to read a cell from Excel using C#? - c#

I have found two ways those were being used by people if we want to read or write to a cell in an excel.
Declaration:
Excel.Application ExcelApp = new Excel.Application();
Excel.Workbook srcWorkBook = ExcelApp.Workbooks.Open(#"C:\test.xls");
Excel.Worksheet srcWorkSheet = srcWorkBook.Worksheets[1];
Excel.Range srcRange = srcWorkSheet.UsedRange;
Usage 1:
srcWorkSheet.Cells[1, 1].value2 = "foo bar";
Usage 2:
srcRange.Cells[2, 2].Value2 = "foo bar";
Which one is the best way to use ? or it's all fine in .NET ?

The two ways are very different.
srcWorkSheet.Cells[1, 1].value2 = "foo bar";
The 1. usage refers to the A1 cell of the first worksheet.
MSDN Worksheets.Cells property Excel
srcRange.Cells[2, 2].Value2 = "foo bar";
The 2. usage takes the cell on the 2. row, 2. column of the UsedRange. If the UsedRange in Excel is B2:D5, it would put value at C3:
MSDN Worksheets.Range Property Excel
Having a variable named Range as the Range class is really not a great idea. The same goes for WorkSheet and WorkBook.

Either option will work, but they get messy as soon as you have to modify the layout of the worksheet.
If it's an option, I like to add named ranges. This works particularly well if you're using a template where you can modify the "starting" state of the workbook.
Let's say you have a series of columns, and one of them contains the "foo" value. You can add a name like "fooColumn" to the entire column or the top cell in the column.
Then you can get the column number using
worksheet.Range("fooColumn").Column
That protects you from having to manually edit column or row numbers all over the place if you move elements around on your worksheet.
And to second what others have said, use EPPlus instead of Interop. You can still use named ranges. But EPPlus is good and Interop can bring all sorts of pain when COM objects aren't released. Interop automates an application that manipulates the file. EPPlus just works with the file.

Related

Create Excel workbook without any sheet

Is there a way to create an Excel workbook without any sheets in it?
This is the code that I use to create the workbook:
excelApp = new Microsoft.Office.Interop.Excel.Application();
excelBook = excelApp.Workbooks.Add();
I tried also adding
excelApp.SheetsInNewWorkbook = 0;
before the creation of the excelBook but the minimum value is 1 so the program crashes.
EDIT:
Tried also to delete the first sheet as soon as it is created but still doesn't work
Sheets excelSheetsToDelete = excelBook.Sheets[1];
excelSheetsToDelete.Delete();
I would like to be able to add my sheets with my names later without having to rename the first one.
You can't create an Excel without any sheet, Excel must contain at least one sheet. Try to delete single sheet in Excel application (desktop). You won't do that.
Like people have already said, Excel must contain at least 1 visible worksheet at all times. Since it seems like you just want to create a workbook, and manually specify your sheet name without having to rename the one created automatically, I wrote this quick VB Script that may work for you.
Dim objShell, objExcel, objWorksheet, strSheetName
strSheetName = InputBox("Enter a name for your new Worksheet:")
Set objShell = WScript.CreateObject( "WScript.Shell" )
objShell.Run("EXCEL.EXE")
WScript.Sleep 3000 'Wait for new excel file to open
Set objExcel = GetObject(, "Excel.Application")
Set objWorksheet = objExcel.ActiveWorkbook.Worksheets(1)
objWorksheet.Name = strSheetName
What this does is prompt you to enter a name for your new sheet. It then runs a new instance of Excel and sets the first worksheet of the newly created file as the name you specified.
If Excel includes 3 sheets when creating new workbooks, you can change this to 1 by going in any excel worbook and clicking - File > Options > General - and look for the "When the creating new workbooks" separator. There will be an option "Include this many sheets" which you can change to 1.
Also, to confirm what people have already said, if you try and change this value to 0, it will tell you "The entry must be greater than or equal to 1" meaning all workbooks must contain at least 1 visible worksheet. Hope this helped.

Save specific pages of an Excel workbook

I have one workbook with three sheets. Each sheet got 3 pages. What I want to reach is: I want to save only the first page of each sheet.
I can only count those pages with
int numberOfPages = 0;
foreach(Excel.Worksheet sheet in excelWorkbook.Sheets)
{
numberOfPages += sheet.PageSetup.Pages.Count;
}
But I cant find a way how to save these pages. Is there a way?
Here is how to copy a worksheet:
Excel.Worksheet worksheet1 = ((Excel.Worksheet)Application.ActiveWorkbook.Worksheets[1]);
Excel.Worksheet worksheet3 = ((Excel.Worksheet)Application.ActiveWorkbook.Worksheets[3]);
worksheet1.Copy(worksheet3);
Hope that helps.
I'd suggest using the Macro Recorder in such cases (which is available in Excel). The required VBA code can generated automatically in the background for you. Most probably you will need to correct it because an auto-generated code is not well-optimized, but at least you will have an idea what properties and methods should be used to get the job done. See Create or delete a macro for more information.

Copy cells in excel using C#

How to copy to specific row in target sheet?
I need to copy A1 to J10 from a sheet in one excel to location starting from A15 in second excel sheet. How can I achieve this in c#? In the below Copy method there seems to be no option to specify the location in target excel sheet.
ObjWorkSheet = (Microsoft.Office.Interop.Excel.Worksheet)ObjWorkBookTemp.Sheets[1];
ObjWorkSheet.Copy(Type.Missing, ObjWorkBookGeneral.Sheets[1]);
I think you are using the wrong method here... you want to use a Paste method not a copy method.
Try the Range.PasteSpecial method... should do the trick for you.
Something like this...
Excel.Range sourceRange = firstWorksheet.get_Range("A1", "J10");
Excel.Range destinationRange = secondWorksheet.get_Range("A15", "J25");
sourceRange.Copy(Type.Missing);
destinationRange.PasteSpecial(Microsoft.Office.Interop.Excel.XlPasteType.xlPasteFormulas, Microsoft.Office.Interop.Excel.XlPasteSpecialOperation.xlPasteSpecialOperationNone, false, false);

C#: ID-field of Microsoft.Office.Interop.Excel.Range is not persisted with an Excel-sheet

before I start with the problem, I want to motivate it. My task is to analyse changes in an Excel-Sheet, but different from recording changes via the in-build mechanism the changes should be detected programmatically and even if the user deactivated the recording of changes or my Excel-AddIn is not installed. Therefor I've used Microsoft.Interop.Excel-Library to access a sheet and the cells within.
Now to the problem: For finding changes, even if the user has sorted or moved the data, I wanted to have a uniqe id per cell, which sticks to it, even if moved or copied. Of course if copied, the id is twice in the sheet and new added cells have no id, but that is ok. Further on, this ID should not be visible to the user and the user should not be able to modify or delete it.
So I looked for a field and found the Range-Object which can represent a single cell and has different members which can be accessed. One special field drew my attention, the ID-field, which looked like what I was searching for.
Guid guid = Guid.NewGuid();
((Range) worksheet.Cells[rowNr, columnNr]).ID = guid.ToString();
and also be read like
Guid guid = Guid.Parse(((Range) worksheet.Cells[rowNr, columnNr]).ID);
This was perfect, because I was able to store a string (in this case a Guid as a string, like 123463-fc34-c43a-a391-399fc2) and read it. It also stuck to the cell, and was moved, when the cell was moved etc.
But unfortunately this ID-field is not persisted, when the file is saved, and I don't know why. I mean that after closing and reopening a workbook, all IDs are gone.
So my question is, if there is any other member of the Range object, that can hold a string (=Guid), and which is not visible to the user. I tried the Name-Member and the Comment-Member, but both of them are visible to the user, and can be modified easily.
Or is there a way of telling Excel, that I want to save the ID-field too, when saving the sheet?
For testing, you can create a project, add a reference to the Microsoft.Office.Interop.Excel-Dll and add the following code (you must have Excel installed on your system). It is a unit-test, that runs with JUnit, but simply remove the Assert-Command to test it without JUnit too:
using System;
using System.IO;
using Microsoft.Office.Interop.Excel;
using Excel = Microsoft.Office.Interop.Excel;
public void AddGuidAndRead()
{
Excel.Application excelApp = new Excel.Application();
Workbook excelWorkbook = excelApp.Workbooks.Add(Type.Missing);
Worksheet worksheet = excelWorkbook.Sheets[1]; //1-based index
Guid rowGuid1 = Guid.NewGuid();
const string filename = "C:\\temp\\anyTemporaryFilename.xlsx";
//Make sure, this file does not exist previously
if (File.Exists(filename))
File.Delete(filename);
//Write the ID to the worksheet
((Range)worksheet.Cells[1, 1]).ID = rowGuid1.ToString();
//Act (save and close the workbook)
excelWorkbook.SaveAs(filename);
excelWorkbook.Close();
//Now open the workbook again
Workbook openedWorkbook = excelApp.Workbooks.Open(filename);
//Fetch the worksheet, where we worked previously
Worksheet openedWorksheet = openedWorkbook.Sheets[1]; //1-based index
//Read the ID from the cell
string guid1 = ((Range)openedWorksheet.Cells[1, 1]).ID;
//Cleanup
openedWorkbook.Close(false);
File.Delete(filename);
excelWorkbook.Close(false, Type.Missing, Type.Missing);
excelApp.Quit();
//Assert - this fails!!
Assert.AreEqual(rowGuid1.ToString(), guid1);
}
I would appreciate any idea, how to put an ID to an Excel-Worksheet-Cell that is persisted, when saving the worksheet or anything on this subject.
Many thanks in advance,
Alex
Update 14.5.2011:
The Name-field seems not to be a solution to my problem for the following reasons:
First, and most serious is the fact, that it seems that the name must be unique, but I wanted to give all cells in a row the same ID, which doesn't work.
Second, to access the Name-Field in C# is not really clear to me. You can set the value with
((Range)worksheet.Cells[rowNr, columnNr]).Name = guid.ToString();
//Remark: Special dealing with guids required,
//if they start with a number or contain special characters.
but it has serious issues. If the name was already set, it throws an exception, if no name was set, and you try to access it with
string name = ((Range)worksheet.Cells[rowNr, columnNr]).Name.Name;
you get an exception. And you need the Name.Name, because the first Name-field is not the string but a whole Name-Object, which inside has another Name-Field which contains a string.
And finally, if you want to check if it has a name or not, you cannot do something like:
if(((Range)worksheet.Cells[rowNr, columnNr]).Name == null)
//Do something
because it already throws an exception when accessing a not existing Name-field.
Actually there is no way to persist the ID directly in the sheet as I though of it.
Neither in the ID-field (which is not persisted), nor as Names (only unique names allowed) or Comments (are visible to the user).
But there is the concept of CustomProperties in a workbook, which can hold strings, and since all serializable classes can be marshalled to strings, this allows the programmer to persist the IDs separately and restore them upon loading of a workbook.
Anyway, for my purpose another approach was used, that calculates hash-values of each line and compares the line-hash-values instead.
You can try using a named range for each cell. The name will persist. I have not tried this with interop but it works with good old vba. In the code below, note that the names can be hidden from the user.
Function try()
Dim x As Integer
Dim y As String
Worksheets("Sheet1").Range("a1").Name = "_firstCell"
Range("_firstCell").Value = 9999
Dim nm As Name
'hide
For Each nm In ActiveWorkbook.Names
If Left(nm.Name, 1) = "_" Then
nm.Visible = False
End If
Next
'move the named cell
Range("_firstCell").Cut Range("b1")
'check the value and address
x = Range("_firstCell").Value
y = Range("_firstCell").Address
End Function
From what I understand there is no logical limit to the number of named ranges in a workbook.
try using : FormatConditions
activecell.FormatConditions.Add xlExpression, formula1:="test_1234"
to get value for ID
IDRange = mid(activecell.FormatConditions(1).formula1,2)
"test_1234"

How to Select all the cells in a worksheet in Excel.Range object of c#?

I am trying to select all the cells in an Excel sheet in the Excel.Range object of C# for applying auto fit, border etc. I have some merged cells within the sheets.
Is there any simple trick to do so?
Excel.Range theRange = (Excel.Range)CurrentSheet.UsedRange;
In this example, CurrentSheet is the variable where you have stored the sheet you are currently using.
public void refreshSheetColumsSize(Worksheet ws)
{
ws.get_Range("a1").EntireRow.EntireColumn.Select();
}
Taken from here, this will select all cells in the worksheet:
lastCol = ActiveSheet.Range("a1").End(xlToRight).Column
lastRow = ActiveSheet.Cells(65536, lastCol).End(xlUp).Row
ActiveSheet.Range("a1", ActiveSheet.Cells(lastRow, lastCol)).Select
Officially, Excel.Worksheet.UsedRange.Rows and Excel.Worksheet.UsedRange.Columns.
In practice, it's buggy, you have to subtract the start row and column. The closest-to-correct answer is:
Public ReadOnly Property LastColumn() As Integer
Get
Return ExcelWorksheet.UsedRange.Columns.Count + _
ExcelWorksheet.UsedRange.Column - 1
End Get
End Property
Public ReadOnly Property LastRow() As Integer
Get
Return ExcelWorksheet.UsedRange.Rows.Count + _
ExcelWorksheet.UsedRange.Row - 1
End Get
End Property
This returns at least all the used cells, sometimes a little more. The 'little more' can be due to blank cells (rather than empty), and other random things. Form the research I did, this is the best that can be done.
If you really want to select everything then
ExcelWorksheet.Activate()
ExcelWorksheet.Cells.Select()
I've not done any excel development for a while (Excel 2003) but I always found that recording a macro while performing the tasks I was wanting to implement in code have sufficient pointers to help.
In this case, selecting all cells and autofitting gives the code:
Sub Macro1()
Cells.Select
Cells.EntireColumn.AutoFit
End Sub
which I would imagine would roughly translate to:
((Microsoft.Office.Interop.Excel.Range)_sheet.Cells.Select()).AutoFit();
where _sheet is the instance of the Worksheet you're using.
(untested)
To consider all cells of a worksheet you can write like:
workSheet.Cells[workSheet.Rows.Count,workSheet.Columns.Count]
OR
workSheet.get_Range("A1","IV65536")
To consider used cells of a worksheet, you can write:
workSheet.Rows.SpecialCells(XlCellType.xlCellTypeLastCell, XlSpecialCellsValue.xlTextValues)
Where "worksheet" represents the sheet you are working on. Both code sample returns a range.
Hope it helps!
Much cleaner and doesn't depend on the number of rows/cols which were increased in Excel 2007:
Provided your sheet is in a variable called wsData:
wsData.Range(wsData.Cells(1, 1), wsData.Cells(wsData.Rows.Count, wsData.Columns.Count))
Very simple:
xlWorkSheet.UsedRange.Columns.Select()
worksheet.Columns.AutoFit()
Where "worksheet" is a variable of type Worksheet
xlWorksheet.get_Range("a1").EntireRow.EntireColumn.AutoFit();
xlWorksheet.get_Range("a1").EntireColumn.EntireRow.AutoFit();
After cell filling has been finished.

Categories