Excel C# interop worksheet delete - c#

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.

Related

Trying to access excel COM object as admin user

The objective is to access the running Excel workbook in the machine. Intention is to close the respective workbook though this application which was opened by the user.
Achieved: I used the following code snippet to access the Excel COM object
Excel.Application instance = null;
try
{
instance = (Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
}
catch (Exception ex)//Excel not open
{
wasFoundRunning = false;
}
if (wasFoundRunning)
{
foreach (Excel.Workbook ss in instance.Workbooks)
{
string s = ss.Name;
}
}
Issue: Above code works only if the excel sheet and the application opened by the same user in the machine. I have to run the application as administrator and it fails. Exception as follows and it means that it cannot find any excel sheet running.
Exception: Operation unavailable (Exception from HRESULT: 0x800401E3 (MK_E_UNAVAILABLE))
It fails if I run my application elevated and user opens the sheet.
Any ideas to fix this?

Microsoft.Office.Interop.Word.Document won't prompt to save changes to single document without _Application.Quit()

Before I begin, I have googled this to death and there are many posts about how to prevent the save prompt. I am having an issue showing the save prompt.
I am building the template editing portion of a document generation system in C#. The system will edit 'dot' and 'dotx' files. Before outlining the problem, the environment I am using for development is running Visual Studio 2010 and Word 2010. It will eventually run on other versions, but i would like to get these versions functional first.
To set the scene I have a form open that has a list of columns returned from a stored procedure(Data Source) to add to the document as bookmarks. I have all of the bookmark and drag/drop operations functional. When I close the application, I catch the 'ApplicationEvents4_DocumentBeforeCloseEventHandler' event to close the form.
When I close the form, i check haw many documents there are open. If only one document is open, I close the application which prompts the user to save changes. If however there are multiple documents open (Most people have a number of different word documents open concurrently), I locate the correct document and close it with the flag set to prompt the user to save changes.
This is where the problem occurs. At this point, the save changes dialog never shows up and everything freezes up in Visual Studio. If I stop the debugging on Visual Studio 2010 the document flashes in the task bar indefinitely, and if you focus on it, it disappears and saves changes without a prompt.
This is the code to handle the form closing event:
private void Form2_FormClosing(object sender, FormClosingEventArgs e)
{
if (app != null)
{
if (app.Documents.Count < 2)
{
this.TopMost = false;
((Word._Application)app).Quit();
app = null;
}
else
{
foreach (Word.Document document in app.Documents)
{
if (document.FullName.Equals(wordDoc.FullName))
{
object saveChanges = Word.WdSaveOptions.wdPromptToSaveChanges;
((Word._Document)wordDoc).Close(ref saveChanges);
break;
}
}
}
}
}
The problem is this line should show a save changes dialog:
((Word._Document)wordDoc).Close(ref saveChanges);
I have tried debugging this without much luck. Putting a breakpoint on this line and on the
break;
line allows the program to stop at the 'Close' line but when you 'step' forward or 'continue' word becomes unresponsive, so does the form and the breakpoint on the very next line never gets hit.
Any help would be greatly appreciated as something this simple is so annoying to get stuck on.
To avoid the Prompt or to get the Prompt to you have to set the Saved property to true or false respectively:
var doco = WordApp.Documents.Add();
doco.Saved = true;
doco.Close(Microsoft.Office.Interop.Word.WdSaveOptions.wdDoNotSaveChanges, Type.Missing, Type.Missing);
Something fishy is going on if Word hangs on the line of code when you try to close the document. I recommend disposing of all the resources properly. Here is a great article on using VSTO Contrib that helps provide this functionality:
http://jake.ginnivan.net/vsto-com-interop
Update:
Enable your VSTO log file by adding the following on your system environment variables:
NAME: VSTO_LOGALERTS
VALUE: 1
There might be an exception error that is why your add-in is not loading.
You can check this source for more info on VSTO logging and alerts, but in essence you change two environment variable values depending on what you need to do:
Displaying VSTO Alert Prompts
To display each error in a message box, set the
VSTO_SUPPRESSDISPLAYALERTS variable to 0 (zero). You can suppress the
messages by setting the variable to 1 (one).
Logging VSTO Alerts to a Log file
To write the errors to a log file, set the VSTO_LOGALERTS variable to
1 (one).
Visual Studio Tools for Office creates the log file in the folder that contains the application manifest. The default name is .manifest.log. To stop logging errors, set the variable to 0 (zero).

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.

How do i load an automation addin (dll) programmatically from within a vsto addin

VSTO
VS2008 SP1
.NET 3.5
Excel 2007
I am a .net noob. I am trying to load an automation addin that is an excel application/automation addin (it is a dll not xla or xll) from within a vsto addin in the ThisAddIn_Startup() method of the vsto addin. From google I got the below solution which is not working.
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Application excel = Globals.ThisAddIn.Application;
//Also tried without display alerts being set to false
excel.DisplayAlerts = false;
foreach (AddIn addin in excel.AddIns)
{
if (addin.progID.Equals("MY_ADDIN_PROG_ID"))
{
Debug.WriteLine("Addin installed is " + addin.Installed);
addin.Installed = false;
Debug.WriteLine("Addin is: " + addin.FullName + ", " + addin.progID);
Debug.WriteLine("Addin installed is " + addin.Installed);
}
}
AddIn addIn = excel.AddIns.Add("MY_ADDIN_PROG_ID", false);
addIn.Installed = true;
excel.DisplayAlerts = true;
Debug.WriteLine("Addin is: " + addIn.FullName + ", " + addIn.progID);
Debug.WriteLine("Addin installed is " + addIn.Installed);
excel.DisplayAlerts = false;
//OTHER STARTUP CODE
Debug.WriteLine("Starting up addin!");
}
Note, I can see the addin.installed is being set to false and back to true on startup but when I try to populate worksheet with udfs from the addin I tried to load in a later button_click method, I get #NAME? error. I am at my wits end. Any help will be greatly appreciated.
If I first try to call the udf in excel by typing it in a cell by hand before I call my button click method, the worksheet population works and the udfs get evaluted as expected but this is not ideal.
Also setting installed property to true does not seem to be doing anything as i can still see the udf addin as inactive in excel, it is only if I type it into a cell that it gets activated. Is there anything else I need to do to activate the automation addin in my vsto startup?
Thanks!
I'm not sure you want to do this in the startup event. I have done something similar but not quite the same before which may be applicable. I exposed some COM visible functions to VBA in a different event handler:
protected override object RequestComAddInAutomationService()
{
// return something com-visible
}
So maybe you can try to load your automation dll this way? This happens before the startup event fires... Excel might be doing something like locking its list of addins while a startup event is being handled - who knows? If it were possible to know Excel programming would be less tedious.
It is harder than it seems to combine VSTO and Automation in Excel. You may find my blog post helpful:
Communicating Between VSTO and UDF's in Excel
Just need to add String Value to the following registry key and you are good.
For Office 2007
Find regkey, HKEY_CURRENT_USER\SOftware\Microsoft\Office\12.0\Excel\Options, then create string value, where name = OPEN, value = /A "YOUR ADDIN NAME HERE" (quotes need to be included as well.)
Note that for the first addin, value name should be called OPEN, for the second one and onwards, use OPEN1, OPEN2, ... etc.
For Office 2010
Just replace 12.0 with 14.0 in the above regkey path, the rest are all the same.
Check out below article on MSDN, which will also help you a lot.
http://support.microsoft.com/kb/291392
Looks like this is a bug specific to VSTO. I converted my addin to a COM addin and was able to use the automation addin from code after that. My team has sent the issue to microsoft so we'll see what they say.

Using late binding to get a specific instance of Excel in 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.

Categories

Resources