Dynamically registering UserControls in Sparx Enterprise Architect AddIns for showing them with Repository.AddTab

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 UserControls 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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: