Using late binding to get a specific instance of Excel in C# - c#

Just after a little help with late binding.
I am trying to late bind excel and i don't have any issues doing that. It is only when I have more than one instance of excel open where I run into some problems.
I would like to be able to determine what instance of excel to bind to (and the link events etc.). Main reason being I have an application that opens a excel document from a third party tool and the events aren't handled. I want to be able to tap into the particular excel instance that I know is open to catch the events. Only problem is if excel is already open by the user (doesn't matter how).
If excel is opened after the bind, obviously, I don't get a problem. It is only when excel is already open.
It seems that the binding is done to the first instace that is open.
Here is the actual code:
Type excelType = Type.GetTypeFromProgID("Excel.Application");
// Throw exception if the type wasn't found
if (excelType == null)
throw new Exception(error);
//Get the Excel.Application Type by creating a new type instance
excelApplication = Marshal.GetActiveObject("Excel.Application");
//Throw exception if the object couldn't be created
if (excelApplication == null)
throw new Exception(error);
this.withEvents = withEvents;
//Create link between the Word.Applications events and the ApplicationEvents2_WordEvents class
if (this.withEvents)
{
excelEvents = new ExcelApplicationEvents();
//holds the connection point references of the Word.Application object
IConnectionPointContainer connectionPointContainer = excelApplication as IConnectionPointContainer;
//Find the connection point of the GUID found from Red Gate's .Net Reflector
Guid guid = new Guid("00024413-0000-0000-C000-000000000046");
connectionPointContainer.FindConnectionPoint(ref guid, out connectionPoint);
//Advise the Word.Application to send events to the event handler
connectionPoint.Advise(excelEvents, out sinkCookie);
excelEvents.WorkbookBeforeSaveEvent += new EventHandler<WorkbookEventArgs>(ExcelEventsWorkbookBeforeSaveEvent);
}
Any Ideas?
Cheers,
Dale.

Getting a particular instance of Excel requires that you make use of the AccessibleObjectFromWindow API.
This is explained well in the article Getting the Application Object in a Shimmed Automation Add-in by Andrew Whitechapel.
What you want, however, is to execute it using late binding. This is described in detail by divo here: How to use use late binding to get excel instance.

Related

Get excel filename and path error - C#

I am getting a very stupid error. I am new to VSTO and I need to get the location of the Excel file in some variable in my Addin.
string name = ActiveWorkbook.FullName;
I am getting a red line below ActiveWorkbook with error:
The name ActiveWorkBook does not exist in the current context.
I have added reference of Microsoft.Office.Interop.Excel in the code but its showing this error. I am new to this.. am I missing something?
In Excel VSTO, you need to use Globals.ThisAddIn.Application to get access to the Excel Application Model, see below :
var wb = Globals.ThisAddIn.Application.ActiveWorkbook;
string name = wb.FullName;
see also Programming VSTO Add-ins
If your code is inside the ThisAddIn class you can directly call: this.Application.ActiveWorkbook
ActiveWorkbook is not a class. It's the property of the Application interface. You can not call it in the class-name manner.
Then, you need to change your code to this.Application.ActiveWorkbook.FullName;

Excel C# interop worksheet delete

I have an app using Excel COM interop. It copies a template XLS to an existing doc, replacing same-named tabs by Delete old then Copy new from template.
I am getting 800A03ec exception when my app tries to delete a worksheet.
This problem is in code that has been working for years but now fails after an upgrade to Office 2016.
I find that if I set app.Visible = true, the operation completes properly! But I do not want Excel visible.
If app.Visible = false, I do not get an error from the first worksheet Delete(), but exception occurs on the second.
The first delete of the last tab seems to go OK, but 'Sheets' array of the worksheets object doesn't decrease as I would expect. However the corresponding Sheet item in the array becomes a "null" worksheet.
The second delete, of the tab before it, throws an exception on delete.
I have thoroughly ensured:
No COM reference leaks
All COM references are discarded before each delete, except for the worksheet to be deleted and its parent app, etc. objects
DisplayAlerts is false
Worksheets are simple and ordinary, nothing hidden etc.
Why would it work when app is visible, but not work when app is hidden??
UPDATE: code fragment
int _DeleteTargetTab(string tabName)
{
List<object> comRefs = new List<object>();
int prevTabIndex;
int tabToDelete = _FindTargetTemplateIndex(tabName, out prevTabIndex);
if (tabToDelete > 0)
{
try
{
Excel.Sheets targetSheets = _workbook.Sheets;
comRefs.Add(targetSheets);
Excel.Worksheet targetWorksheet = targetSheets[tabToDelete];
comRefs.Add(targetWorksheet);
targetWorksheet.Delete();
}
finally
{
ExcelUtility.ReleaseAll(comRefs);
}
}
return prevTabIndex;
}
and ExcelUtility.ReleaseAll() calls Marshal.ReleaseComObject(), then GC.Collect() and GC.WaitForPendingFinalizers().
Target worksheet has 8 tabs. Last tab is deleted, then copied, without exception. Then on tab 7 delete throws an exception, only when app is hidden. Works fine on older Office.
Copy code is
void _CopyTemplateTabToTarget(Excel.Worksheet templateWorksheet, int prevTargetTabIndex)
{
List<object> comRefs = new List<object>();
try
{
Excel.Sheets targetSheets = _workbook.Sheets;
comRefs.Add(targetSheets);
Excel.Worksheet prevSheet = targetSheets[prevTargetTabIndex];
comRefs.Add(prevSheet);
templateWorksheet.Copy(Type.Missing, prevSheet);
}
finally
{
ExcelUtility.ReleaseAll(comRefs);
}
}
Init code is
_app = new Excel.Application();
_app.DisplayAlerts = false;
_app.Visible = false;
I got further by adding this:
//
// Super important to activate a tab other than what needs to be deleted.
// Cast is required because an event and method have the same name "Activate".
//
((Excel._Worksheet)_weeklyDataSheet).Activate();
Then that got me to some code around my apps' Save function that used to work but was throwing exception:
_weeklyDataSheet.Select(Type.Missing);
I changed that to Activate() as well and made more progress. But yet Excel still threw exception on Delete() this time on the third workbook.
I was forced to run app Visible for the week's report. But even that ran through about 80 workbooks and then Excel locked up, mouse cursor rapidly flashing between arrow and wait timer animation, and my app got RPC error eventually.
Conclusion: ABANDON COM APIS for Office 2016. Microsoft seems not to support them properly anymore.

Outlook Interop ClearSelection Method

I am having difficulties with Interop in a WPF application.
What I actually want to do is drag and drop an Outlook file into my application and extract the attachments and store them. Apart from that I want to read the subject and search for a 4-digit-number which will then be the name of the folder the attachments are to be stored to.
I have been searching the web for solutions that don't use Interop, but I wasn't able to find anything that worked for me.
So I thought 'let's give it a shot' and it sounded pretty simple, because I found so many examples that followed this pattern:
if (e.Data.GetDataPresent("FileGroupDescriptor"))
{
Microsoft.Office.Interop.Outlook.Application app = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.Selection selection = app.ActiveExplorer().Selection;
foreach (object mi in selection)
{
Microsoft.Office.Interop.Outlook.MailItem mailItem = (Microsoft.Office.Interop.Outlook.MailItem)mi;
string subject = "Untitled";
if (!string.IsNullOrEmpty(mailItem.Subject))
{
subject = mailItem.Subject;
MessageBox.Show(subject);
}
}
}
This works, but I have one problem: the selection keeps on growing. I tried the methods RemoveFromSelection and ClearSelection, but they don't work. Everytime I drag a new Outlook item to the surface it keeps displaying all the previous items as well.
Can anybody help me? I'm at a complete loss
Do you handle the Drag event in your application?
If so, try to call the following code in the event handler:
e.Data.GetData(“RenPrivateMessages”);
See Outlook, custom task pane and drag-drop problem for more information.

How to get information about current user distribution list from outlook using WPF business app

In my WPF app, I need to enable/disable functionality based on the team. The team information is configured as outlook distribution list. Now I need retrieve this information from my App.
I googled and found the link
http://msdn.microsoft.com/EN-US/library/office/ff184638(v=office.15).aspx
Unfortunately it doesn't compile as it is. After bit of research I can make it compile by changing it by changing it as
currentUser = new Outlook.Application().Session.CurrentUser.AddressEntry;
However, this works only when outlook is opened, but when outlook is closed it throws the exception. Any idea?
Finally I managed to crack it. Apparently we need to briefly start the outlook application, the solution is explained in the link
https://groups.google.com/forum/#!msg/microsoft.public.outlook.program_vba/lLJwbwwl-XU/gRuQYRpJtxEJ
Hence I modified my code GetCurrentUserMembership() slightly to accomadate this change. Now it's working good. Tested in outlook 2007 and 2010.
The complete solution,
private List<string> GetCurrentUserMembership()
{
Outlook.Application outlook = new Outlook.Application();
Outlook.MailItem oMsg = (Outlook.MailItem)outlook.CreateItem(Outlook.OlItemType.olMailItem);
Outlook.Inspector oInspector = oMsg.GetInspector;
//session.Logon("", "", false, false);
var sb = new List<string>();
Outlook.AddressEntry currentUser = outlook.Session.CurrentUser.AddressEntry;
if (currentUser.Type != "EX") return sb;
var exchUser = currentUser.GetExchangeUser();
if (exchUser == null) return sb;
var addrEntries = exchUser.GetMemberOfList();
if (addrEntries == null) return sb;
foreach (Outlook.AddressEntry addrEntry in addrEntries)
{
sb.Add(addrEntry.Name);
}
return sb;
}
Could you please be more specific? What exception (error message and error code) do you get in the code?
I'd recommend starting from breaking the chain of calls and declare each property or method call on a separate line. Thus, you will find a problematic property or method call which fires an exception.
Most probably you need to call the Logon method of the Namespace class. As an example, you may find the C# app automates Outlook (CSAutomateOutlook) sample project helpful.

How do I create a new worksheet and populate it with rows of data using Excel-DNA?

I have c# code behind my Excel-dna addin which is successfully downloading data from a service. I have created a ribbon in Excel-dna with a button which triggers the download, and now I want to display the data in a new worksheet. How do I create a worksheet and add rows?
I tried calling xlcWorkbookInsert from my c# code using:
ExcelReference newSheet = (ExcelReference)XlCall.Excel(XlCall.xlcWorkbookInsert, 1);
but I always get a ExcelDna.Integration.XlCallException exception. Is this the correct approach, or is there a simpler way to go about doing this?
I also tried pasting an object[,] of data to an existing sheet:
ExcelReference sheet1 = (ExcelReference)XlCall.Excel(XlCall.xlSheetId, "Sheet1");
ExcelReference myTargetPasteArea = new ExcelReference(1, 1, 2, 10, sheet1.SheetId);
myTargetPasteArea.SetValue(result);
There are no errors this time, but nothing happens (although I can see the code being executed when I step through in debug).
Your code is calling to Excel via the C API (that's how the XlCall.Excel(...) and ExcelReference stuff in Excel-DNA works). But you can't call the C API directly from your ribbon event handler. You have two options:
Make a detour via a macro. This is easy if you change your ribbon xml code:
<button onAction="RunTagMacro" tag="MyMacro" />
and then define a macro:
public static void MyMacro()
{
// ... do your work here ....
}
You can also call the macro yourself from the event handler - the RunTagMacro internally just calls
object app = ExcelDnaUtil.Application;
app.GetType().InvokeMember("Run", BindingFlags.InvokeMethod,
null, app, new object[] { control.Tag }, new CultureInfo(1033));
Another option is to change your interaction code to use the COM API. For this you'll need to use the 'dynamic' support in .NET 4, or reference an interop assembly - either the version-specific Primary Interop Assemblies for Office, or some version-independent interop assemblies like NetOffice.
XlCall.Excel(XlCall.xlcWorkbookInsert, 1);
returns a bool: true - success, false - failure
So casting it to ExcelReference is the cause of the exception.
You may need an xlcNew before that xlcWorkbookInsert. Take a look in the Excel-Dna source at the GetApplication method in Excel.cs.

Categories

Resources