Create Excel workbook without any sheet - c#

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.

Related

How to use SSIS script task to delete second row in Excel destination? SQL Server 2012

I have a simple package that extracts data from SQL Server and writes it to Excel spreadsheet.
Why am I doing this: the problem is that the value of one of the column is greater than 255 characters, which causes an error. I already tried different ways to find a solution, but so far without success.
So I created dummy row in my Excel template that is able to accept more than 255 characters. After I load the data, I need to delete that dummy row.
I do not know much about C#. How can I delete second row in Excel destination by using SSIS script task C#? Do I need to create variables that hold the path folder and file name?
And then how can I map those variables to my C# code?
You can use Microsoft.Office.Interop.Excel.dll library inside the Script Task to achieve this.
You can use a similar code:
Note: I assumed that the file path variable name is FilePath. Remember to select the FilePath variable in the Script Task ReadOnly Variables (in the Script editor). Also don't forget to add Microsoft.Office.Interop.Excel.dll as a reference inside the script task
Imports Microsoft.Office.Interop
Public Sub Main()
Dim strFile as String = Dts.Variables("FilePath").Value
Dim m_XlApp = New Excel.Application
m_XlApp.visible = False
m_XlApp.DisplayAlerts = False
Dim m_xlWrkbs As Excel.Workbooks = m_XlApp.Workbooks
Dim m_xlWrkb As Excel.Workbook
m_xlWrkb = m_xlWrkbs.Open(strFile)
m_xlWrkb.DoNotPromptForConvert = true
Dim m_XlWrkSheet As Excel.Worksheet = m_xlWrkb.Worksheets(1)
m_XlWrkSheet.Cells(2, 1).EntireRow.Delete(Excel.XlDeleteShiftDirection.xlShiftUp)
m_xlWrkb.Save()
m_xlWrkb.Close(SaveChanges:=True)
Marshal.ReleaseComObject(m_xlWrkb)
Marshal.ReleaseComObject(m_xlWrkbs)
m_XlApp.Quit()
Marshal.ReleaseComObject(m_XlApp)
End Sub
I got the same damn problem.
Like you, I created dummy row in my Excel template that is able to accept more than 255 characters.
However, I works on Visual Studio 2008 and I can't install anythings on my computer.
My solution to delete the dummy row is to set in it's first column the value "DummyRow" and create this following macro in the Excel template:
Private Sub Workbook_Open()
Call SupprDummyRow
End Sub
Sub SupprDummyRow()
x = Range("A2").Value 'First row after the titles, first column
y = Range("A3").Value 'Second row after the titles, first column
If (x = "DummyRow" And y <> "") Then 'The Dummy Row is present AND the next row have value (we are in the export file)
Range("A2").EntireRow.Delete 'Delete the lign
End If
End Sub
Note that on my situation the first column have always a value (no empty string).
Hope this help!
Best regards,
Try this to increase the column length on the Excel destination. This way you would not even need to add your dummy record to the excel file and later delete it.
Right Click on your Excel Destination.
Click on "Show Advanced Editor".
In the Advanced Editor, go to the "Input and Output Properties" tab.
Expand the "External Column" of you Excel Destination Section and adjust the Length property values of the required column. You can also change data-types if needed. Please find the screenshot for the same.
Try this and let me know if it worked.
I created dummy row in my Excel template Which is able to accept more than 255 characters. After export data to excel file, delete dummy row from excel with SSIS scripts task.
Pass the exel file as variable like below immage
Write below code to delete 2 number row. worksheet.Rows[2].Delete();

Which way to read a cell from Excel using 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.

Excel Interop: Get named table (Range) from specific workbook

I'm trying to create a WinForms application that interacts with Excel using the Excel Object Library v.15.
I'm using this to get the Excel Application object
(Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
And I know I can get the Range for the named table I want by using
_application.Range["MyTableName"];
Now, the problem I'm facing is that if I have two workbooks opened at the same time and each one of them has a named table with the same name, I don't know which Range will be returned.
The Excel application is unique, so I cannot simply try to get the process based on the window title or something like that.
I can get the Workbook object based on its title:
_application.Workbooks.Cast<Workbook>().FirstOrDefault(w.Name.Equals(title))
However I cannot access the Ranges of a particular Workbook.
I know I could iterate through the Sheets of the Workbook element trying to find the table but I was wondering if there was another "cleaner" way.
I would appreciate any guideline on this.
Thanks,
Will
The simplest/most direct way I can think of would be to iterate through the Names collection, check the parent, and move on.
E.g:
For Each NamedRange as Range in MyApplication.Names
Dim Check as Object = TryCast(NamedRange.Parent, Workbook)
If Check Is Nothing Then
Check = TryCast(NamedRange.Parent, Worksheet)
Check = TryCast(Check.Parent, Workbook)
End If
If Not Check Is Nothing AndAlso Check.Name = "MyWorkbook" Then
'Do something
End If
Next

Example of Reading Data and Writing Directly Into Excel

I currently have a routine which reads rows/columns and writes into a SQL table from a SELECT statement.
We were thinking of then reading the SQL table and updating an empty Excel worksheet. What I would like to do is read and then update the data directly into Excel.
My program currently does the following:
Opening the workbook.
Accessing the different worksheets.
Saving As a new workbook name.
Closing Excel.
I just need an example that:
will allow the first row to be the column names
then read out the data into the various cells for each row.
I do not recommend using COM Interop either.
You can find here a sample about exporting dataset to Excel in C#.
It requires an Excel tool named EasyXLS.
to open the file you can do it like that:
Dim exAppl As New Excel.Application
exAppl.Visible = True
Dim exMappe As Excel.Workbook = exAppl.Workbooks.Open("C:\... .xlsx", , False)
You can acces to a Cell with
Console.WriteLine(exAppl.Range("A2").Value())
or when you whant to go throw it use something like that:
For i As Integer = 1 To exAppl.Rows.Count
Dim a As Excel.Range = exAppl.Rows(i)
For j As Integer = 1 To a.Columns.Count
Dim b As Excel.Range = a.Columns(j)
Console.WriteLine(b.Value)
Next
Next

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"

Categories