Any events supported in ExcelDna? - c#

I want to initialize a couple of variables on Excel Dna .dll gets loaded in the
Excel?

Excel-DNA will check whether your add has a public class that implements the interface ExcelDna.Integration.IExcelAddIn, and if so will run the AutoOpen method when the add-in is loaded, and the AutoClose method if the user removes the add-in from the add-ins list.
So you'd have something like:
public class MyAddIn : IExcelAddIn
{
public void AutoOpen()
{
// Do your initialization here...
}
public void AutoClose()
{
}
}

Related

VSTO Addin Bidirectional communication

I'm looking into the possibility of having a instance of an VSTO Word Add-in communicate with an instance of a VSTO Excel Add-in.
So far i've found an article from Microsoft on what I thought was going to be the solution:
https://learn.microsoft.com/en-us/visualstudio/vsto/calling-code-in-vsto-add-ins-from-other-office-solutions?view=vs-2022&tabs=csharp
However, when one iterates over the Globals.ThisAddIn.Application.COMAddIns collection, I can only see the current Office product (Word/Excel/PowerPoint/Outlook) installed Addin's.
I have a central project that all of the add-in's share, this includes the shared interface. Each add-in implements that interface and are decorated with attributes as per the above guide.
public interface IInterAddinCommsService
{
void Notify();
}
[ComVisible(true)]
[ClassInterfaceAttribute(ClassInterfaceType.None)]
public class WordAddinNotificationService : StandardOleMarshalObject, IInterAddinCommsService
{
public void Notify()
{
}
}
[ComVisible(true)]
[ClassInterfaceAttribute(ClassInterfaceType.None)]
class PowerPointAddinNotificationService : StandardOleMarshalObject, IInterAddinCommsService
{
public void Notify()
{
}
}
And here is my method trying to retrieve the com objects and calling the implemenetd method
public void OnAction(Office.IRibbonControl control, bool IsPressed)
{
//// Notify other addin's
var addins = Globals.ThisAddIn.Application.COMAddIns;
foreach (Office.COMAddIn addin in addins)
{
var service = addin.Object as IInterAddinCommsService;
if (service == null) continue; // skip if object doesn't implement IInterAddinCommsService
service.Notify();
}
}
Any help would be greatly appreciated.
In addition to implementing the interface you need to override the RequestComAddInAutomationService method which returns an object in your add-in that can be used by other solutions. For a code example that demonstrates how to override the RequestComAddInAutomationService method, see Walkthrough: Calling Code in a VSTO Add-in from VBA. For example, that is how it should like:
private WordAddinNotificationService utilities;
protected override object RequestComAddInAutomationService()
{
if (utilities == null)
utilities = new WordAddinNotificationService();
return utilities;
}
Note, the object that you return must be public, it must be visible to COM, and it must expose the IDispatch interface. If the object you return does not meet these requirements, the Visual Studio Tools for Office runtime will throw an InvalidCastException after it calls your implementation.

Workaround for generic event handler in Windows Forms

Quite some time ago, I noticed that the Windows Forms editor of Visual Studio does not support events which contain generic type parameters. For example, an event like
public event EventHandler<ListEventArgs<int>> MyStrangeEvent { add { ... } remove { ... } }
where
public class ListEventArgs<T> : EventArgs { List<T> args; }
does not even show up in the event list in the property manager of Visual Studio. Now, this is a somewhat artificial example that could easily be modified to work in Visual Studio by rewriting the classes and their events. However, I am currently working on a project where I cannot change some classes for compatibility reasons. The only thing I can do is to change the events of my user control. The events of this control currently look like this:
public event EventHandler<Plane<GDISurface>.DrawingErrorEventArgs> DrawingError { add { _Plane.DrawingError += value; } remove { _Plane.DrawingError -= value; } }
Note that the underlying Plane class (represented by the _Plane instance which is a protected field) cannot be changed. Its DrawingError event and its EventArgs type are declared in the Plane class like this:
public class Plane<T> where T : ISurface
{
...
public event EventHandler<DrawingErrorEventArgs> DrawingError = null;
...
public class DrawingErrorEventArgs : EventArgs { ... /* Uses T */ ... }
}
Of course, the Windows Forms editor of Visual Studio does not show any of the events of my user control. I have been looking for a number of workarounds to get them shown again, but have not been able to find a workaround that actually works. Here are some things that I tried:
Created a MyPlane class which inherits from Plane and used that instead: public event EventHandler<MyPlane.DrawingErrorEventArgs> DrawingError .... For reasons unknown to me, the events still don't show up in the editor. Perhaps this is due to the parameters of the event, some of which still are generic. Find a minimal working example below.
Created a helper class which defines implicit conversion operators between EventHandler<Plane<GDISurface>.DrawingErrorEventArgs> and EventHandler<GDIPlane.DrawingErrorEventArgs> where GDIPlane is just a dummy class which inherits from Plane<GDISurface>. This does work to some extent, but duplicates event calls since the conversion creates new event handlers which are passed down to _Plane which cannot be removed/unregistered properly.
Tried to inherit from EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>, which obviously does not work since EventHandler<T> is sealed.
Are there any other ways to make my events visible again in the Windows Forms editor?
Best regards
Andreas
EDIT: Minimal working example for 1:
public interface ISurface { }
public class GDISurface : ISurface { }
public class Plane<T> where T : ISurface
{
public event EventHandler<DrawingErrorEventArgs> DrawingError = null;
public class DrawingErrorEventArgs : EventArgs { T stuff; }
}
public class TestControl : UserControl
{
public class GDIPlane : Plane<GDISurface> { }
GDIPlane _Plane = null;
public event EventHandler<GDIPlane.DrawingErrorEventArgs> DrawingError { add { _Plane.DrawingError += value; } remove { _Plane.DrawingError -= value; } }
}
DrawingError does not show up in the list of events in the property manager when clicking on a TestControl instance.
EDIT2: This is the original problem (without any workarounds) where the DrawingError event does of TestControl does not show up either:
public interface ISurface { }
public class GDISurface : ISurface { }
public class Plane<T> where T : ISurface
{
public event EventHandler<DrawingErrorEventArgs> DrawingError = null;
public class DrawingErrorEventArgs : EventArgs { T stuff; }
}
public class TestControl : UserControl
{
Plane<GDISurface> _Plane = null;
public event EventHandler<Plane<GDISurface>.DrawingErrorEventArgs> DrawingError { add { _Plane.DrawingError += value; } remove { _Plane.DrawingError -= value; } }
}
This is behavior specific to Visual Studio, and the cause is rooted in the fact that EventHandler<> does not specify covariance on its 'TEventArgs' (it would impose seemingly silly restrictions) and the tools do not perform enough introspection of your code to suss out an appropriate type (even though you've left a trail of type data in constructing the control.) Thus, it seems as though VS does not support generic event properties. You may consider filing a feature request on Microsoft Connect, I wouldn't suggest filing it as a bug as they may label it "by design" and close it.
As a general rule, if you need generic type parameters on your events and you need design time support for them (which are different implementation concerns), you're looking at wrapping them in a presentation-specific facade (e.g. "extra layer of code to facilitate design-time needs".)
Personally, I would reduce the generic typing you have in play now, it seems a bit excessive and if you don't understand covariance/contravariance in generic types it might put you in a tight spot at some point, such as now.
However, to work around your problem:
Consider using a custom event args class which could transport data in a non-generic property, and also use a non-generic EventHandler event/property. Understanding the 'type' of the event is then shifted away from generic type parameters and made the responsibility of your non-generic event args instead. If the 'class' of the event args is insufficient, you can add a property to convey the event type (or data type) so that receiving code can properly interpret it (assuming, of course, that it does not already know by some other means.):
public class DataEventArgs : EventArgs
{
//public string EventTypeOrPurpose { get; set; }
public object Data { get; set; }
}
This is most often only used to ferry data through an event chain, and it is usually implemented as follows:
public class DataEventArgs<T> : EventArgs
{
public T Data { get; set; }
}
Unfortunately, this also has a covariance problem, to resolve it you would actually want something more like this:
public interface IDataArgs<out T>
{
T Data { get; }
}
public class DataEventArgs<T> : EventArgs, IDataArgs<T>
{
public DataEventArgs<T>(T data)
{
_data = data;
}
private T _data;
public T Data { get { return _data; } }
}
Even so, these generic versions still don't work around Visual Studio's limitations, this is merely more proper alternative forms of what you already have shown us.
UPDATE: As requested, here is what a "purpose built facade" might look like in the most basic sense. Note that the usercontrol functions as a facade layer in this case as the eventhandler it exposes delegates to the underlying object model. There is no direct access to underlying object model from the user control (from consumer/designer perspective.)
Please note the reference tracking for event handlers is not necessary unless you dispose of these user controls throughout the lifetime of the app (it is only done to ensure proper delegate removal based on the delegate provided, which is wrapped in a closure/delegate, as you see below.)
Also worth noting I did not test-run this code beyond verifying that the designer shows DrawingError in the property grid when dropped onto a form.
namespace SampleCase3
{
public interface ISurface { }
public class GDISurface : ISurface { }
public class Plane<T> where T : ISurface
{
public event EventHandler<DrawingErrorEventArgs> DrawingError;
public class DrawingErrorEventArgs : EventArgs { T stuff; }
}
public class TestControl : UserControl
{
private Plane<GDISurface> _Plane = new Plane<GDISurface>(); // requires initialization for my own testing
public TestControl()
{
}
// i am adding this map *only* so that the removal of an event handler can be done properly
private Dictionary<EventHandler, EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>> _cleanupMap = new Dictionary<EventHandler, EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>>();
public event EventHandler DrawingError
{
add
{
var nonGenericHandler = value;
var genericHandler = (EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>)delegate(object sender, Plane<GDISurface>.DrawingErrorEventArgs e)
{
nonGenericHandler(sender, e);
};
_Plane.DrawingError += genericHandler;
_cleanupMap[nonGenericHandler] = genericHandler;
}
remove
{
var nonGenericHandler = value;
var genericHandler = default(EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>);
if (_cleanupMap.TryGetValue(nonGenericHandler, out genericHandler))
{
_Plane.DrawingError -= genericHandler;
_cleanupMap.Remove(nonGenericHandler);
}
}
}
}
}
To complement the above, here is what a non-generic event handler would now look like:
private void testControl1_DrawingError(object sender, EventArgs e)
{
var genericDrawingErrorEventArgs = e as Plane<GDISurface>.DrawingErrorEventArgs;
if (genericDrawingErrorEventArgs != null)
{
// TODO:
}
}
Note that the consumer here has to have knowledge of the type for e to perform conversion. The use of the as operator will bypass ancestry checks under the assumption that the conversion should succeed.
Something like this is as close as you're going to get. Yes it is ugly by most of our standards, but if you absolutely 'need' design-time support on top of these components and you cannot change Plane<T> (which would be more appropriate) then this, or something close to this, is the only viable workaround.
HTH

In C# is there a way to override a private or public function through a plugin?

I have an application that loads plugins. I have a plugin that has complete access to a form instance. If I have a function in a form that needs to be overridden, but is not a virtual function, is there another way to override it?
Here is a very generic example:
//Form I am modifying
public partial class MyForm : Form
{
public int myVariable1;
public int myVariable2;
//Constructor and other methods here
private void setVar(int replacementValue)
{
myVariable1 = replacementValue;
}
}
...then in a separate dll...
//My plugin
public class MyPlugin : IMyPluginBase
{
MyForm theForm; //Reference to the form in the main application
//Constructor and other methods here
private void setVar(int replacementValue)
{
theForm.myVariable2 = replacementValue;
}
}
In this example the function in the form sets 'myVariable1', but the 'setVar' function in the plugin sets 'myVariable2'.
So, the question is, in the case of this example, can I replace/override the form's 'setVar' function with the one in the plugin? Maybe with messages or reflection?
No. You cannot "replace" or overide private non-virtual methods in C#.
The C# language (and .NET runtime) don't support dynamic replacement of methods in the manner you describe. Very few languages support this capability, to my knowledge (I believe that SmallTalk and Objective-C both do).
If this is the only place in your application where you need this kind of extensibility, you can achieve it through an interface, delegate, or inhertance+virtual methods. Any of these approaches could work ... which one you choose depends on what kind of extensibility you desire.
If you expect to have many such extensibility points in your app, then you should probably take a look at the Managed Extensibility Framework (MEF). It provides a Microsoft-supported model for creating plug-in architectures using patterns and technique that work well in .NET.
If a function is not marked as virtual or part of an interface that your class implements there's exactly 0 chance you would be able to override it. No plugin, no reflection, no nothing, simply forget about it or use some other dynamic language but not C#.
The short answer to your question is no. What you can do, however, is give your form a copy of the IMyPluginBase, and have Form.setVar() call out to MyPluginBase.SetVar().
The code will look something like this:
public partial class MyForm : Form
{
public int myVariable1;
public int myVariable2;
public IMyPluginBase MyPlugin;
//Constructor and other methods here
private void setVar(int replacementValue)
{
MyPlugin.setVar(replacementValue);
//myVariable1 = replacementValue;
}
}
public class MyPlugin : IMyPluginBase
{
MyForm theForm; //Reference to the form in the main application
public void setVar(int replacementValue)
{
theForm.myVariable2 = replacementValue;
}
}
Note that setVar() will need to be defined in IMyPluginBase.

How Can I Create My C# Com Class From JavaScript Using ActiveXObject

I wrote C# class to COM but I could not use it from JavaScript. Example
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(ICommandsEvents))]
[ProgId("Scripting.Commands")]
public class Commands : ICommands
{
public Commands()
{
}
public int CreateChannel(string channelName)
{
return 0;
}
public int GetChannelID(string channelName)
{
return CreateChannel(channelName);
}
public event ChannelEventsHandler OnChannelEvents;
}
[ComVisible(false)]
public delegate void ChannelEventsHandler(string a);
[ComVisible(true)]
[Guid("E2147768-8BA8-400b-8602-A1FDC31E6AA5")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ICommands
{
[DispId(5)]
int CreateChannel(string channelName);
[DispId(6)]
int GetChannelID(string channelName);
}
[ComVisible(true)]
[Guid("22316373-A8DF-4ace-B48C-EA9953BD73FF")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ICommandsEvents
{
[DispId(1)]
void OnChannelEvents(string a);
}
and I checked "Register for COM interop" checkbox of project property.
when I want to Create this from JavaScript like this.
var a = ActiveXObject("Scripting.Commands");
I am getting "Automation Server Can't create object" exception. What is my wrong.
Thank you
There are a large number of reasons for this kind of error.
Ensure you have an assembly level GuidAttribute for the type library
First check the registry that interface, type library and coclass registration are correct.
Use Process Monitor to check the registration is being read correctly.
Attach a debugger to the process, so you can add breakpoints to your code.
Does a C# client (using COM, so you'll need to import tge typelib to create a PIA) work?
But I notice your class does not have a GuidAttribute, so coclass registration will have failed.
Make sure that your site is in 'Trusted Sites' on the client's machine.

C# to VB6 COM events ("object or class does not support the set of events")

Really pulling my hair out with this one...
I have a C# project with an interface defined as:
/* Externally Accessible API */
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ISerial
{
[DispId(1)]
bool Startup();
[DispId(2)]
bool Shutdown();
[DispId(3)]
bool UserInput_FloorButton(int floor_number);
[DispId(4)]
bool Initialize();
}
/* Externally Accesssible Event API */
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ISerialEvent
{
[DispId(5)]
void DataEvent();
}
[ComSourceInterfaces(typeof(ISerialEvent), typeof(ISerial))]
[ClassInterface(ClassInterfaceType.None)]
public class SerialIface : ISerial
{
public delegate void DataEvent();
public event DataEvent dEvent;
public bool Initialize()
{
//testing the event callback
if (dEvent != null)
{
dEvent();
}
}
...
}
And the VB6 code looks like:
Private WithEvents objSerial As SerialIface
Private Sub objSerial_DataEvent()
'do something happy'
End Sub
Public Sub Class_Initialize()
Set objSerial = New SerialIface '<---this is the line that fails'
Call objSerial.Initialize '<--Initialize would trigger DataEvent, if it got this far'
End Sub
Well, the normal API-type functions appear to be working (if I declare objSerial without the WithEvents keyword), but I can't for the life of me get the "DataEvent" to work. It fails with the "object or class does not support the set of events" message.
I'd originally lumped the two interfaces together, but then C# complained that DataEvent was not defined in the class. The way it is currently, I am able to view all of the APIs and the one event perfectly in the VB6 object browser -- everything looks like it's there... I just can't make it actually work!
I'm sure I'm missing something obvious or doing something stupid -- but I'm new to the whole interop business, so it's just escaping me entirely.
Help!
Look at this article here.
Specifically it looks like you missing a declaration that looks something like this.
[Guid("9E5E5FB2-219D-4ee7-AB27-E4DBED8E123E"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(DBCOM_Events))]
public class DBCOM_Class : DBCOM_Interface
{
You have this part
// // Events interface Database_COMObjectEvents
[Guid("47C976E0-C208-4740-AC42-41212D3C34F0"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface DBCOM_Events
{
}
But without the second the vtable and typelib of the COM object doesn't have the Event Maps needed to work with VB6 (or other COM Consumers).
You can use the Google search terms "com event" c# and get a bunch of other good results.
I was defining the interface using the delegate instead of the event:
/* Externally Accesssible Event API */
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ISerialEvent
{
[DispId(5)]
void DataEvent();
}
should be
/* Externally Accesssible Event API */
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ISerialEvent
{
[DispId(5)]
void dEvent();
}

Categories