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

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

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.

Getting the worksheet to which the area belong to

In VSTO, in developing an excel add-in, i've an excel area name, is it possible to get the worksheet it belong to?
Is it also possible to get the worksheet that a cell belong to?
If you have a range object just use .parent to get the worksheet. Then you can get the name of the worksheet or whatever else.
If you only have a range name as a string then you need to loop through the names in the workbook and worksheets looking for that name. Then use the refersTo property to get the address. Parse for the worksheet name.

Import a second spread sheet into Microsoft.Office.Interop.Excel C# project

I'm stuck on the last hurdle to finish my program. I have a excel doc I want to import into the one I'm building in C#
wb.Sheets.Add();
Microsoft.Office.Interop.Excel.Worksheet staffCosts = (Microsoft.Office.Interop.Excel.Worksheet)wb.Worksheets[1];
staffCosts.Name = "Staff Costs";
staffCosts.QueryTables[1].Name = Path.GetFileNameWithoutExtension("C:\\tilldataoutput\\excelcreator\\excelcreator\\bin\\Debug\\Staff.xlsx");
Any help would be massively appreciated.
Take a look at this MSDN link, which states...
Returns the QueryTables collection that represents all the query
tables on the specified worksheet. Read-only.
Since you're getting a QueryTable by using an Index, you should check the collection first to check if any exist, or 2 in your case since you're looking at the second QueryTable.

Import from Excel

I use Access 2007, SQL Server 2005 and 2008, and C#.net with VS20010.
I need to validate, then import an Excel workbook that has been posted to a SP document library. I wrote the program using VBA in A2k7 so I could get it working, get all my thoughts together, etc. Shortly, I'm going to start converting it to C# so I can run it on a scheduled basis from my SQL Server agent. Authentication may be fun, we'll see.
In my Access VBA program, I use a simple statement to create a multi-dimensional array of "variants". This allows me to test every single cell for the correct type before I try to assign it to a local variable. Certain cells must be numeric. Others, I use as strings, even if they're numeric. Since the data may be hand-entered, I could see virtually any content in the cells...one of the reasons I don't just import it, since most import vehicles (ACCESS, SSIS) use Excel's determination as to the column's "type".
'
' Get the range of used cells from the worksheet and stuff it into an array
'
xlApp.visible = False 'Don't let the workbook be shown
Set xlWB = xlApp.Workbooks.Open(URL) 'Open the workbook
Set xlSH = xlWB.Worksheets(1) 'Must be the first worksheet in the workbook
HandleMessages 1, 0, 1, "Working on Workbook " & xlWB.NAME & ", Worksheet " & xlSH.NAME
Set xlRA = xlSH.UsedRange 'xlRA is the range of cells in the first workbook that are "USED"
strSheetArray = xlRA 'This sets an array of variants to the two dimensional range xlRA
So, going to C#, what do I do about the lack of a non-typed "variant"? How do I proceed? Anyone done it?
You can use dynamic in C# 4.0.
http://msdn.microsoft.com/en-us/library/dd264736.aspx
The type is a static type, but an object of type dynamic bypasses
static type checking. In most cases, it functions like it has type
object. At compile time, an element that is typed as dynamic is
assumed to support any operation. Therefore, you do not have to be
concerned about whether the object gets its value from a COM API, from
a dynamic language such as IronPython, from the HTML Document Object
Model (DOM), from reflection, or from somewhere else in the program.
However, if the code is not valid, errors are caught at run time.
The link shows examples of using dynamic with Excel.

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