Hello,
The way is described in Morgan Skinner article is only the basic stuff which you should handle.
Actually Winform conrol handle all aspects of OLE/COM control but 2 of them (I think mandatory) are missed.
1. It does not allow change client site once it already activated and window handle created (see
IOleObject.SetClientSite
in MSDN).
2. It does not support manual drawing (see
IViewObject.Draw
in MSDN)
The first issue appear once
Control.Handle
and parent is set, but the application try to changed. You should use a WinAPI
DestroyWindow
and
SetParent
(DestroyWindow dispose the handle and it will be recreated by .NET with new parent).
The second issue appear once the control activated with the manually drawing - this can be in case if application wan't to draw the control content - it can be it own DC or the printer DC (Usually the applications with printing supports and allows to use COM/OLE controls activate control with manual draw parameter). The proper way to handle that is to make manual drawing, but at least you can just call you base method
Control.DrawToBitmap
to draw content on Bitmap and then draw that Bitmap into given DC.
To make it work you should better to make the handling interfaces manually, most of the calls can be just forwarded to AxHost but some methods better to override.
Interface necessary to implement on your object (Thats minimal list but some applicaions may require more):
IProvideClassInfo
,
IProvideClassInfo2
,
IQuickActivate
,
IViewObject
,
IViewObject2
,
IOleObject
,
IOleControl
,
IOleInPlaceObject
,
IOleInPlaceObjectWindowless
,
IOleInPlaceActiveObject
,
IOleWindow
.
Some applications may requre persits storage or persist stream initialization interfaces so keep that in mind.
You should set the reqistry entry to mark your .NET Control as COM/OLE Control as described in Morgan Skinner article but also with the changes.
Registration function:
[ComRegisterFunctionAttribute]
[RegistryPermissionAttribute(SecurityAction.Demand, Unrestricted=true)]
public static void RegisterFunction(Type _type)
{
if (_type != null && _type.IsSubclassOf(typeof(MyCOMControl)))
{
string sCLSID = "CLSID\\" + _type.GUID.ToString("B");
try
{
RegistryKey _key = Registry.ClassesRoot.OpenSubKey(sCLSID, true);
try
{
Guid _libID = Marshal.GetTypeLibGuidForAssembly(_type.Assembly);
int _major, _minor;
Marshal.GetTypeLibVersionForAssembly(_type.Assembly, out _major, out _minor);
using (RegistryKey _sub = _key.CreateSubKey("Control")) { }
using (RegistryKey _sub = _key.CreateSubKey("MiscStatus")) { _sub.SetValue("", "0", RegistryValueKind.String); }
using (RegistryKey _sub = _key.CreateSubKey("TypeLib")) { _sub.SetValue("", _libID.ToString("B"), RegistryValueKind.String); }
using (RegistryKey _sub = _key.CreateSubKey("Version")) { _sub.SetValue("", String.Format("{0}.{1}", _major, _minor), RegistryValueKind.String); }
using (RegistryKey _sub = _key.CreateSubKey("Control")) { }
using (RegistryKey _sub = _key.CreateSubKey("InprocServer32")) { _sub.SetValue("", Environment.SystemDirectory + "\\" + _sub.GetValue("", "mscoree.dll"), RegistryValueKind.String); }
}
finally
{
_key.Close();
}
}
catch
{
}
}
}
Unregister function:
[ComUnregisterFunctionAttribute]
[RegistryPermissionAttribute(SecurityAction.Demand, Unrestricted=true)]
public static void UnregisterFunction(Type _type)
{
if (_type != null && _type.IsSubclassOf(typeof(MyCOMControl)))
{
try
{
string sCLSID = "CLSID\\" + _type.GUID.ToString("B");
Registry.ClassesRoot.DeleteSubKeyTree(sCLSID);
}
catch
{
}
}
}
As in there for full solution a lot of code; and I'll show other underwater stones in the article how to proper make that in details. I'll add link to comment here once I have time to post an article.
Again for debators (Especially for Bill): I repeating if you don't know how to make that not write that it isn't possible - maybe better to start learning?
Regards,
Maxim.