I have a C# Excel Add-in project "MyExcelAddIn" that has a public method Foo() to do something complex. For testing purposes, the add-in also defines a toolbar button which is wired to Foo() so I can test this and verify that clicking the button calls Foo() and does what I want it to do. This is fine.
Now I want to call this method from a C# Windows Forms project. In the Windows Forms project I can create an Excel instance and make it visible and verify that my VSTO add-in is running as I can see the button and it works. But I can't work out how to call Foo() programatically from the Windows Forms project. I've googled a bit and got as far as getting the "MyExcelAddIn" COMAddIn object, but can't work out how to call Foo().
It looks something like this:
// Create Excel and make it visible
Application excelApp = new Application();
excelApp.Visible = true;
// I know my VSTO add-in is running because I can see my test button
// Now get a reference to my VSTO add-in
Microsoft.Office.Core.COMAddIns comAddIns = _excelApp.COMAddIns;
object addinName = "MyExcelAddIn";
Microsoft.Office.Core.COMAddIn myAddin = comAddIns.Item(ref addinName);
// This works, but now what? How do I make a call on myAddin?
// Note that myAddin.Object is null...
So I want to know what I can do to call Foo() from my Windows Forms application. Note that I have full control over both the Windows Forms application and the add-in and I suspect I have to make changes to both of them (particularly the add-in) but I have no idea how to do this.
Note that this is a VS2008 C# application and I'm using Excel 2003.
If you're building an application-level add-in, I believe this may be your answer: MSDN VSTO Article
It involves two steps: (From the article)
In your add-in, expose an object to other solutions.
In another solution, access the object exposed by your add-in, and call members of the object.
The other solution may be: (Again from the article)
Any solution that is running in a different process than your add-in (these types of solutions are also named out-of-process clients). These include applications that automate an Office application, such as a Windows Forms or console application, and add-ins that are loaded in a different process.
I'm using the SendMessage Win32 API to do this. My C# Add-in creates a "NativeWindow" with a uniqe window title that the WinForm app can locate.
I assume that your method Foo somehow interacts with Excel. Otherwise you can just add a reference to the assembly containing the class with the Foo method and call it from there without instantiating Excel.
The only other way I can think is to get a reference to your CommandBarButton through the excelApp object. CommandBarButton has a method called Execute which is similar to clicking the button. Something like this:
Excel.Application excelApp = new Excel.Application();
CommandBarButton btn = excelApp.CommandBars.FindControl(...) as CommandBarButton;
btn.Execute();
For anyone else who finds this here's what I did:
object addInName = "AddinName";
var excelApplication = (Microsoft.Office.Interop.Excel.Application)Marshal.GetActiveObject("Excel.Application");
COMAddIn addIn = excelApplication.COMAddIns.Item(ref addInName);
addIn.Object.AddinMethodName(params);
Also had to add a reference to Microsoft.Office.Core under COM and Excel.Interop under Assemblies.
Related
how I can programatically load and unload a VSTO add-in in Word on button click.
I have unloaded it on event click bu using below code.
foreach (Office.COMAddIn addin in Globals.ThisAddIn.Application.COMAddIns)
{
if (addin.ProgId == "DocDrafter")
{
addin.Connect = false;
return;
}
}
but on document change and document start I have to load the add-in again.
But once addin is unloaded I am unable to load it again.
You have a couple of possibilities - it depends on what, exactly, you want to do. To begin with, you should (have) read the information in the Word object model Help for the AddIns collection and the Addin object. (We're talking about Globals.ThisAddIn.Application.Addin/s for your VSTO project.)
There are basically two approaches. One is to used the Installed property of the Addin object which loads (=true)/unloads(=false) the add-in from the Word UI, leaving it in the list of Add-ins (the list in Word's File/Options/Add-ins tab) so that the user (or your code) can load it again as required. It sounds like this is what you need.
The other approach is to remove/add the add-in to/from that list. Use the Addin.Delete method to remove the add-in; use Addins.Add to add an add-in to the list.
If you disconnect the Add-in from within the VSTO project, as your code does, I don't think there's any way within the scope of VSTO that you're going to get it to connect, again...
Help topic in the documentation: start here:https://msdn.microsoft.com/en-us/vba/word-vba/articles/addins-add-method-word
I'm currently writing a VSTO add-in for Excel in C#, and am having trouble getting the undo functionality to work correctly.
As far as I can tell from the documentation, you are meant to use Application.OnUndo to register an undo callback. However, it's not clear to me whether it's possible for the Procedure argument to refer to a C# method.
Ideally I would like to set the undo callback to an instance method, eg:
this.Application = Globals.ThisAddIn.Application;
// ...
this.Application.OnUndo("Undo color change", "this.UndoTextColorChange");
Unfortunately, while this registers an undo, actually clicking 'undo' in Excel gives the error:
Cannot run the macro 'this.UndoTextColorChange'. The macro may not be available in this workbook or all macros may be disabled.
To me, this almost suggests that the Procedure argument has to be a VB macro (rather than a C# method). However, it's also possible that I haven't been able to work out the fully-qualified procedure name to use in the .OnUndo call.
Is it possible to have Application.OnUndo call a C# method? If so, what should I use as the argument for Procedure? If not, how is undo functionality typically implemented in C# VSTO add-ins?
You are right that the Procedure argument of the Application.OnUndo has to be a VBA macro.
If you want the Application.OnUndo to call a C# method you will have to add a VBA macro to your workbook which will be triggered by the Application.OnUndo. Then the macro may call C# code from your VSTO add-in. Here is a good article describing how you can call VSTO code from VBA macro.
In order to inject a VBA macro into a workbook from your VSTO add-in you may create an .xlam Excel Add-in and distribute it together with the VSTO Add-in (basically put it into the same folder or even embed it into VSTO Add-in itself as a resource file).
The .xlam Excel Add-in will be very simple and will contain a single function like below:
Sub UndoLastAction()
Application.COMAddIns("YourVSTOLibraryName").UndoInVSTO
End Sub
When VSTO Add-in is started it can load .xlam add-in, so all the workbooks will have access to that VBA macro even if the workbook itself is not macro-enabled.
Below is a sample how you can load .xlam Excel Add-in from VSTO:
var undoManager = Globals.ThisAddIn.Application.AddIns.Add("UndoManager.xlam", true);
undoManager.Installed = true;
Now, when you execute a certain modification via your VSTO Add-in and then would like to be able to Undo it you will have to call Application.OnUndo:
Globals.ThisAddIn.Application.OnUndo("Undo Last Operation", "UndoManager.xlam!UndoLastAction");
When Application.OnUndo is executed the Excel will allow user to click "Undo" button which will trigger UndoLastAction macro declared in the .xlam Add-in which will trigger UndoInVSTO function declared in YourVSTOLibraryName dll where you can do whatever you need to undo your last operation.
I want to control an Excel Add-In with C# from a Windows Forms Application.
This is what I have so far:
var excelAddin = excelApp.AddIns.Add("C:/.../NWPredict.xlam", Type.Missing);
MessageBox.Show("Predict: " + excelAddin.FullName);
This code works, but how would I start the Add-In and change its settings?
If you want to permanently change settings of an add-in then you would need to Open this file, not attach it as an add-in to the currently running instance of Excel. (This isn't usually done programmatically though.)
Perhaps you should clarify because, to me, an add-in is supposed to be "complete". If any settings need to be changed when the add-in is in use, then the add-in itself should make these settings available to the Excel-instance, via exposed properties (or some other technique). But, again, these settings wouldn't be permanently saved (in the add-in itself).
A Refresh button/method in an Excel Add-In needs to be invoked via an external winform application. Here is where I am up to:
private Microsoft.Office.Interop.Excel.Application excel = new Microsoft.Office.Interop.Excel.Application();
public FormMain()
{
InitializeComponent();
RefreshExcelSheet(#C:\a.xls");
}
private bool RefreshExcelSheet(string path)
{
using (var wb = excel.Workbooks.Open(path).WithComCleanup())
How do I click the Refresh button or simply invoke its event?
I was looking at these articles but they are using VBA, I want a Winform app to open the spreadsheets and click the button:
Accessing a VSTO application-addin types from VBA (Excel)
Expose VSTO functionality to VBA w/o local admin
The simplest way to solve this problem is to just expose the existing custom .net method as a COM method by making is a com callable wrapper object (CCW).
VSTO provides you with a simple method to expose the COM Automation server.
http://blogs.msdn.com/b/andreww/archive/2007/01/15/vsto-add-ins-comaddins-and-requestcomaddinautomationservice.aspx
EDIT BY OP:
Andrew Whitechapel has updated the article, but I cannot get it to work. Even with StandardOleMarshalObject and Register for COM interop I still get the same error message as detailed in this article: http://blogs.msdn.com/b/andreww/archive/2008/08/11/why-your-comaddin-object-should-derive-from-standardolemarshalobject.asp
Anonymous Type's suggestion is surely the best approach if you can extend the Excel add-in.
If it's a 3rd party tool it's still possible to use the ribbon's IAccessible interface to invoke the ribbon button. However, you can expect this to be much more complicated. In case you have to follow that path here are some links to get you started:
How to get Ribbon custom Tabs IDs?
http://www.codeproject.com/Articles/38906/UI-Automation-Using-Microsoft-Active-Accessibility
I have developed a Shared Addin for Excel using Extensibility IDTExtensibility2 interface in Visual Studio 2008.
It's quite basic in functionality. When a workbook is opened it is stored in a list of opened workbooks, when it is closed the addin will create a new text file then write out some XML to the file, this file is then read by another process which will then deserialize the XML.
The addin works under normal operations - so if the user opens and closes a file then the addin does what it should, if they quit Excel with open workbooks then it does what it should.
The problem is when the user has Excel open with open workbooks and does a Log-Off. The two methods: OnDisconnection and OnBeginShutdown don't appear to be called, at all.
I did two things to test this:
I created a TextWriterTraceListener which wrote to a log file when these two methods were called. When Excel is quit normally they are hit and information is logged in the log file, but when a user logs off there is nothing in the log file.
Inside both of these methods using File.CreateText(filename) I created a blank file. When the user quits Excel normally these files are created, but once again, when Excel is closed through a Log-Off these files aren't created.
Does anyone have any ideas how I can get around this problem? I need to capture when Excel is being closed when the user is logging off their machine...
The solution in the end was to hook into Microsoft.Win32.SystemEvents.SessionEnding and, when this System event was fired, to manually call the OnBeginShutdown method.
This used to cause a methode ~ of object ~ failed error in VB6 days.
Try WorkbookBeforeClose or potentially ProtectedViewWindowBeforeClose.
One issue you may have with these if I remember correctly, is that you can't capture when if the event is cancelled, so if you're using this to clean up, I believe you need to also do some work in one of the activate or open events such that your addin will be useable if the user cancels the closing action....
Hope this makes sense.
I had the same problem with my Outlook 2010 addin. It may be something to do with the fact that Outlook 2010 does not signal add-ins that it is shutting down.
Specifically, Outlook [2010] no longer calls the OnBeginShutdown and OnDisconnection methods of the IDTExtensibility2 interface during fast shutdown.
Similarly, an Outlook add-in written with Microsoft Visual Studio Tools for Office no longer calls the ThisAddin_Shutdown method when Outlook is shutting down.
If you still want your addin to be notified when Outlook 2010 is shutting down (as I did), you need to latch on to the Application's ApplicationEvents_Event_Quit event, using code like mine below (your shutdown code should still run in both the OnDisconnection and OnBeginShutdown methods, in any case):
public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)
{
// As this is an Outlook-only extension, we know the application object will be an Outlook application
_applicationObject = (Microsoft.Office.Interop.Outlook.Application)application;
// Make sure we're notified when Outlook 2010 is shutting down
((Microsoft.Office.Interop.Outlook.ApplicationClass)_applicationObject).ApplicationEvents_Event_Quit += new ApplicationEvents_QuitEventHandler(Connect_ApplicationEvents_Event_Quit);
}
private void Connect_ApplicationEvents_Event_Quit()
{
Array emptyCustomArray = new object[] { };
OnBeginShutdown(ref emptyCustomArray);
}
public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)
{
addinShutdown();
}
public void OnBeginShutdown(ref System.Array custom)
{
addinShutdown();
}
private void addinShutdown()
{
// Code to run when addin is being unloaded, or Outlook is shutting down, goes here...
}