Sparx Enterprise Architect provides a means for AddIns to display Windows via Repository.AddTab()
which are based on System.Windows.Forms.UserControl
. However the call AddTab
expects a name for the actual tab and a PROGID
. EA actually takes this PROGID
and loads the UserControl via the registry (as it is actually an Active COM Control).
In this post I show you how these UserControl
s can be registered at AddIn StartUp time and deregistered at ShutDown time.
The actual registration entries needed for an ActiveX control to be registered on an x64 Windows for an x86 executable are as follows:
Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\Software\Classes\IMASE.UserControl1] [HKEY_CURRENT_USER\Software\Classes\IMASE.UserControl1\CLSID] @="{deaddead-dead-dead-dead-deaddeaddead}" [HKEY_CURRENT_USER\Software\Classes\WOW6432Node\CLSID\{deaddead-dead-dead-dead-deaddeaddead}] @="biz.dfch.CS.EA.ProductModeler.AddIn.UserControl1" [HKEY_CURRENT_USER\Software\Classes\WOW6432Node\CLSID\{deaddead-dead-dead-dead-deaddeaddead}\Implemented Categories] [HKEY_CURRENT_USER\Software\Classes\WOW6432Node\CLSID\{deaddead-dead-dead-dead-deaddeaddead}\Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}] [HKEY_CURRENT_USER\Software\Classes\WOW6432Node\CLSID\{deaddead-dead-dead-dead-deaddeaddead}\InprocServer32] @="mscoree.dll" "ThreadingModel"="Both" "Class"="biz.dfch.CS.EA.ProductModeler.AddIn.UserControl1" "Assembly"="biz.dfch.CS.EA.ProductModeler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" "RuntimeVersion"="v4.0.30319" "CodeBase"="file:///C:/src//biz.dfch.CS.EA.ProductModeler.dll" [HKEY_CURRENT_USER\Software\Classes\WOW6432Node\CLSID\{deaddead-dead-dead-dead-deaddeaddead}\InprocServer32\1.0.0.0] "Class"="biz.dfch.CS.EA.ProductModeler.AddIn.UserControl1" "Assembly"="biz.dfch.CS.EA.ProductModeler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" "RuntimeVersion"="v4.0.30319" "CodeBase"="file:///C:/src//biz.dfch.CS.EA.ProductModeler.dll" [HKEY_CURRENT_USER\Software\Classes\WOW6432Node\CLSID\{deaddead-dead-dead-dead-deaddeaddead}\ProgId] @="biz.dfch.CS.EA.ProductModeler.AddIn.UserControl1"
The code for the actual UserControl
looks like this:
namespace biz.dfch.CS.EA.ProductModeler.AddIn.Controls { [ProgId("SMV.UmlControl")] [Guid("deaddead-dead-dead-dead-deaddeaddead")] [ComVisible(true)] public partial class UmlControl : UserControl { // here goes the code ... } }
As we can see the value of the ProgIdAttribute
and GuidAttribute
have to match the entries in the registry. So all we have to do during the startup of our AddIn is to create the above registry keys and values and delete them at when EA closes.
For this we use the .ctor
and finalizer
of our AddIn class which is called EntryPoint
in my example:
public sealed class EntryPoint { public EntryPoint() { new ComRegistration(typeof(UmlControl).Register(); } ~EntryPoint { new ComRegistration(typeof(UmlControl).UnRegister(); } }
The actual code for registration and deregistration is simple as this:
using System; using System.Reflection; using System.Runtime.InteropServices; using System.Windows.Forms; using Microsoft.Win32; namespace biz.dfch.CS.EA.ProductModeler.AddIn { public class ComRegistration { private const string DEFAULT_VALUE = ""; private const char BACKSLASH = '\\'; private const string HKEY_SOFTWARE_CLASSES = @"SOFTWARE\Classes\"; private const string HKEY_SOFTWARE_WOW_CLSID = @"Software\Classes\WOW6432Node\CLSID\"; private const string IMPLEMENTED_CATEGORY_DOTNET = @"Implemented Categories\\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}"; private const string THREADING_MODEL = "ThreadingModel"; private const string CLASS = "Class"; private const string ASSEMBLY = "Assembly"; private const string RUNTIME_VERSION = "RuntimeVersion"; private const string CODEBASE = "CodeBase"; public string ProgName { get; } public Guid ProgGuid { get; } public Type Type { get; } private readonly string typeFullName; public ComRegistration(Type type) { this.Type = type; var nameAttribute = Type.GetCustomAttribute(); ProgName = nameAttribute.Value; var guidAttribute = Type.GetCustomAttribute(); ProgGuid = new Guid(guidAttribute.Value); typeFullName = Type.FullName; } public bool Register() { var keyClasses = $"{HKEY_SOFTWARE_CLASSES}{ProgName}{BACKSLASH}CLSID"; var regKeyClasses = Registry.CurrentUser.CreateSubKey(keyClasses); regKeyClasses.SetValue(DEFAULT_VALUE, ProgGuid.ToString("B")); var keyClsid = $"{HKEY_SOFTWARE_WOW_CLSID}{ProgGuid:B}"; var regKeyClsid = Registry.CurrentUser.CreateSubKey(keyClsid); regKeyClsid.SetValue(DEFAULT_VALUE, typeFullName); var implementedCategories = regKeyClsid.CreateSubKey(IMPLEMENTED_CATEGORY_DOTNET); var assembly = Type.Assembly; var regKeyInprocServer32 = regKeyClsid.CreateSubKey("InprocServer32"); regKeyInprocServer32.SetValue(DEFAULT_VALUE, "mscoree.dll"); regKeyInprocServer32.SetValue(THREADING_MODEL, "Both"); regKeyInprocServer32.SetValue(CLASS, typeFullName); regKeyInprocServer32.SetValue(ASSEMBLY, assembly.ToString()); regKeyInprocServer32.SetValue(RUNTIME_VERSION, assembly.ImageRuntimeVersion); regKeyInprocServer32.SetValue(CODEBASE, assembly.CodeBase); var regKeyVersion = regKeyInprocServer32.CreateSubKey("1.0.0.0"); regKeyVersion.SetValue(CLASS, typeFullName); regKeyVersion.SetValue(ASSEMBLY, assembly.ToString()); regKeyVersion.SetValue(RUNTIME_VERSION, assembly.ImageRuntimeVersion); regKeyVersion.SetValue(CODEBASE, assembly.CodeBase); var regKeyProgId = regKeyClsid.CreateSubKey("ProgId"); regKeyProgId.SetValue(DEFAULT_VALUE, typeFullName); return true; } public bool UnRegister() { try { var keyClasses = $"{HKEY_SOFTWARE_CLASSES}{ProgName}"; Registry.CurrentUser.DeleteSubKeyTree(keyClasses); } catch (Exception) { /* */ } try { var keyClsid = $"{HKEY_SOFTWARE_WOW_CLSID}{ProgGuid:B}"; Registry.CurrentUser.DeleteSubKeyTree(keyClsid); } catch (Exception) { /* */ } return true; } } }
Side note: in my AddIn class I look up all UserControl
classes via my IoC container so I do not have to specify the registration of every single class explicitly.
This is all for today’s post. I hope you might find this useful.