Click here to Skip to main content
15,896,201 members
Articles / Programming Languages / C#
Article

Exposing Windows Forms Controls as ActiveX controls

Rate me:
Please Sign up or sign in to vote.
4.86/5 (33 votes)
27 Aug 20014 min read 787.3K   9.4K   180   159
This article describes how to build a Windows Forms control in C# and expose it as an ActiveX control

Introduction

This article will describe how to utilise Windows Forms controls outside of .NET. In a recent MSDN magazine article on .NET Interop available here, various ways of exposing .NET objects to 'legacy' environments are discussed, including the exposure of Windows Forms controls as ActiveX controls.

The problem is that the goalposts have moved since the article was written as Beta 2 is now available, and unfortunately this support has been removed - see this posting on the .NET list at http://discuss.develop.com.

The following image shows a control, written purely within .NET, hosted within an ActiveX control container - in this instance tstcon32.exe.

Image 1

As Beta1 supported this facility, and being somewhat inquisitive, I decided to see if I could find a way to expose controls anyway. The attached project creates the 'Prisoner' control, which won't set the world on fire but does show the main things you need to do in order to get a .NET control up & running within VB6.

CAVEAT: As this support has been dropped from Beta2 of .NET, don't blame me if it fries your PC or toasts the cat.

Now that's out of the way, how's it done?.

Writing the control

  1. Create a new control project from within Visual Studio - my examples are all in C# but VB.NET could also be used.

  2. Add controls etc to the form, put in the code etc.

  3. Add in the following using clauses...

    C#
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Reflection;
    using Microsoft.Win32;
  4. Attribute your class so that it gets a ProgID. This isn't strictly necessary as one will be generated, but it's almost always best to be explicit.
    C#
    [ProgId("Prisoner.PrisonerControl")]
    [ClassInterface(ClassInterfaceType.AutoDual)]

    This assigns the ProgID, and also defines that the interface exposed should be 'AutoDual' - this crufts up a default interface for you from all public, non-static members of the class. If this isn't what you want, use one of the other options.

  5. Update the project properties so that your assembly is registered for COM interop.

    Image 2

    If you're using VB.NET, you also need a strong named assembly. Curiously in C# you don't - and it seems to be a feature of the environment rather than a feature of the compiler or CLR.

  6. Add the following two methods into your class.

    C#
    [ComRegisterFunction()]
    public static void RegisterClass ( string key )
    { 
      // Strip off HKEY_CLASSES_ROOT\ from the passed key as I don't need it
      StringBuilder sb = new StringBuilder ( key ) ;
      sb.Replace(@"HKEY_CLASSES_ROOT\","") ;
    
      // Open the CLSID\{guid} key for write access
      RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(),true);
    
      // And create the 'Control' key - this allows it to show up in 
      // the ActiveX control container 
      RegistryKey ctrl = k.CreateSubKey ( "Control" ) ; 
      ctrl.Close ( ) ;
    
      // Next create the CodeBase entry - needed if not string named and GACced.
      RegistryKey inprocServer32 = k.OpenSubKey ( "InprocServer32" , true ) ; 
      inprocServer32.SetValue ( "CodeBase" , Assembly.GetExecutingAssembly().CodeBase ) ; 
      inprocServer32.Close ( ) ;
    
      // Finally close the main key
      k.Close ( ) ;
    }
    The RegisterClass function is attributed with ComRegisterFunction - this static method will be called when the assembly is registered for COM Interop. All I do here is add the 'Control' keyword to the registry, plus add in the CodeBase entry.

    CodeBase is interesting - not only for .NET controls. It defines a URL path to where the code can be found, which could be an assembly on disk as in this instance, or a remote assembly on a web server somewhere. When the runtime attempts to create the control, it will probe this URL and download the control as necessary. This is very useful when testing .NET components, as the usual caveat of residing in the same directory (etc) as the .EXE does not apply.

    C#
    [ComUnregisterFunction()]
    public static void UnregisterClass ( string key )
    {
      StringBuilder sb = new StringBuilder ( key ) ;
      sb.Replace(@"HKEY_CLASSES_ROOT\","") ;
    
      // Open HKCR\CLSID\{guid} for write access
      RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(),true);
    
      // Delete the 'Control' key, but don't throw an exception if it does not exist
      k.DeleteSubKey ( "Control" , false ) ;
    
      // Next open up InprocServer32
      RegistryKey inprocServer32 = k.OpenSubKey ( "InprocServer32" , true ) ;
    
      // And delete the CodeBase key, again not throwing if missing 
      k.DeleteSubKey ( "CodeBase" , false ) ;
    
      // Finally close the main key 
      k.Close ( ) ;
    }

    The second function will remove the registry entries added when (if) the class is unregistered - it's always a good suggestion to tidy up as you go.

Now you are ready to compile & test your control.

Testing the Control

For this example I have chosen tstcon32.exe, which is available with the installation of .NET. The main reason I've used this rather than VB6 is that I don't have VB6 anymore.

Inserting the Control

First up you need to insert your control, so to do that choose Edit -> Insert New Control, and choose your control from the dropdown...

Image 3

This will result in a display as shown at the top of the article, if you're following along with my example code.

Testing Methods

The example control only includes one method, 'Question'. To test this within TstCon32, choose Control -> InvokeMethods from the menu, and select the method you want to call. Note that because I defined the interface as AutoDual, I get gazillions of methods. If you implement an interface and expose this as the default interface then the list of methods will be more manageable.

Image 4

Click on the 'Invoke' button will execute the method, which in this instance displays the obligatory message box.

Wrap Up

Dropping support for creating ActiveX controls from Windows Forms controls is a pain, and one decision I wish Microsoft had not made.

This article presents one way of exposing .NET controls as ActiveX controls, and seems to work OK. Having said that, I've not exhaustively tested this and who knows what bugs might be lurking in there. I haven't delved into events yet, nor property change notifications, so there's some fun to be had there if you like that sort of thing.

The .NET framework truly is the best thing since sliced bread, but the lack of support for creating ActiveX controls from Windows Forms controls is inconvenient. There are many applications out there (ours included) which can be extended with ActiveX controls. It would be nice given the rest of the support in the framework to be able to expose Windows Forms controls to ActiveX containers, and maybe someday the support will be available.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Founder MS Application Development Consulting Ltd
United Kingdom United Kingdom
I started work in 1989 for LabSystems, based in Altrincham. I had originally expected to stay for six months, but the job was so good I stayed for 12.5 years.

In December 2001 I joined Microsoft as an Application Development Consultant, where I spent lots of time assisting customers with their use of Microsoft technologies.

I left in June 2011 to start my own business, and I do contract development, knowledge sharing and bespoke application development.

Comments and Discussions

 
GeneralRe: Events needed work - otherwise Great Job Pin
ndecreve10-Feb-06 2:52
ndecreve10-Feb-06 2:52 
GeneralRe: Events needed work - otherwise Great Job Pin
tabyss16-Feb-06 22:54
tabyss16-Feb-06 22:54 
GeneralRe: Events needed work - otherwise Great Job Pin
Michal Vlk21-Feb-07 7:01
Michal Vlk21-Feb-07 7:01 
GeneralRe: Events needed work - otherwise Great Job Pin
Madhu babu T11-Jun-06 22:42
Madhu babu T11-Jun-06 22:42 
GeneralRe: Events needed work - otherwise Great Job Pin
ElleryFamilia12-Jun-06 6:14
ElleryFamilia12-Jun-06 6:14 
AnswerRe: Events needed work (solved) - otherwise Great Job Pin
MrKagami2-Nov-08 8:36
MrKagami2-Nov-08 8:36 
GeneralRe: Events needed work (solved) - otherwise Great Job Pin
Camilo Sanchez3-Aug-10 10:37
Camilo Sanchez3-Aug-10 10:37 
General.net activex used in vb6 or .net control wrapped in MFC Pin
Nat2412-Jan-06 16:21
Nat2412-Jan-06 16:21 
The below class is a compilation of all the information I could get on hosting a .Net textbox in VB6. I am able to see this control on the VB6 toolbox and drag it onto the form. I can then use its properties and methods, but I cannot handle events in VB6. If I declare a control of type VBTest6.VBNetText5 in my code I can get it to work properly with events, but it does not look like the control that has been dropped on to the form from the toolbox. I can also declare a control of type VBTest6Ctl.VBNetText5 in my code and this type of control then seems to act the same as the control from the toolbox.

Imports System.Runtime.InteropServices
Imports System.Text
Imports System.Reflection
Imports Microsoft.Win32

<guid("e73cd054-1247-4853-af05-b7d26d993f02")> _
<interfacetypeattribute(cominterfacetype.interfaceisidispatch)> _
Public Interface VBNetText5Interface
<dispid(1)> _
Property Text() As String
<dispid(2)> _
Property Visible() As Boolean
<dispid(3)> _
Sub Show()
<dispid(4)> _
Sub BringToFront()
<dispid(5)> _
Property Enabled() As Boolean
<dispid(6)> _
Sub AppendText(ByVal text As String)
<dispid(7)> _
Property Top() As Integer
<dispid(8)> _
Property Left() As Integer
<dispid(9)> _
Property MaxLength() As Integer
<dispid(10)> _
Property Width() As Integer
<dispid(11)> _
Property Multiline() As Boolean
<dispid(12)> _
Property BackColor() As Color
<dispid(13)> _
Sub SetColor()
End Interface

<guid("e73cd054-1247-4853-af05-b7d26d993f03")> _
<interfacetypeattribute(cominterfacetype.interfaceisidispatch)> _
Public Interface VBNetText5InterfaceEvents
<dispid(1)> _
Sub TextChanged5(ByVal sender As Object, ByVal e As System.EventArgs)
<dispid(2)> _
Sub Click5(ByVal sender As Object, ByVal e As System.EventArgs)
<dispid(3)> _
Sub VisibleChanged5(ByVal sender As Object, ByVal e As System.EventArgs)
<dispid(4)> _
Sub Click5a(ByVal e As System.EventArgs
End Interface

<classinterface(classinterfacetype.none)> _
<progid("vbtest6.vbnettext5")> _
<comsourceinterfaces(gettype(vbnettext5interfaceevents))> _
<guid("e73cd054-1247-4853-af05-b7d26d993f04")> _
Public Class VBNetText5
Inherits System.Windows.Forms.TextBox
Implements VBNetText5Interface

Public Delegate Sub TextChangedDelegate5(ByVal sender As Object, ByVal e As System.EventArgs)
Public Event TextChanged5 As TextChangedDelegate5
Public Delegate Sub ClickDelegate5(ByVal sender As Object, ByVal e As System.EventArgs)
Public Event Click5 As ClickDelegate5
Public Delegate Sub VisibleChangedDelegate5(ByVal sender As Object, ByVal e As System.EventArgs)
Public Event VisibleChanged5 As VisibleChangedDelegate5

Public Sub New()
MyBase.New()
End Sub

Protected Shadows Sub OnTextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.TextChanged
RaiseEvent TextChanged5(sender, e)
End Sub

Protected Shadows Sub OnClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Click
RaiseEvent Click5(sender, e)
End Sub

Protected Shadows Sub OnVisibleChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.VisibleChanged
RaiseEvent VisibleChanged5(sender, e)
End Sub

Public Overrides Property Text() As String Implements VBNetText5Interface.Text
Get
Return MyBase.Text
End Get
Set(ByVal value As String)
MyBase.Text = value
Me.AppendText(" Text")
End Set
End Property

Public Overrides Property MaxLength() As Integer Implements VBNetText5Interface.MaxLength
Get
Return MyBase.MaxLength
End Get
Set(ByVal value As Integer)
MyBase.MaxLength = value
Me.AppendText(" Max")
End Set
End Property

Public Overrides Property Multiline() As Boolean Implements VBNetText5Interface.Multiline
Get
Return MyBase.Multiline
End Get
Set(ByVal value As Boolean)
MyBase.Multiline = value
Me.AppendText(" Multi")
End Set
End Property

Public Shadows Property Top() As Integer Implements VBNetText5Interface.Top
Get
Return MyBase.Top
End Get
Set(ByVal value As Integer)
MyBase.Top = value
Me.AppendText(" Top")
End Set
End Property

' Works the same as Shadowing the Width property
Public Property Width2() As Integer Implements VBNetText5Interface.Width
Get
Return MyBase.Width
End Get
Set(ByVal value As Integer)
MyBase.Width = value
Me.AppendText(" Width2")
End Set
End Property

Public Overloads Property Left() As Integer Implements VBNetText5Interface.Left
Get
Return MyBase.Left
End Get
Set(ByVal value As Integer)
MyBase.Left = value
Me.AppendText(" Left")
End Set
End Property

' Doesnt seem to make a difference if the property is shadowed or overloaded.
Public Overloads Property Visible() As Boolean Implements VBNetText5Interface.Visible
Get
Return MyBase.Visible
End Get
Set(ByVal value As Boolean)
MyBase.Visible = value
Me.AppendText(" Visible")
End Set
End Property

Public Overloads Property Enabled() As Boolean Implements VBNetText5Interface.Enabled
Get
Return MyBase.Enabled
End Get
Set(ByVal value As Boolean)
MyBase.Enabled = value
Me.AppendText(" Enabled")
End Set
End Property

' VB 6 does not recognise Color
Public Overloads Property BackColor() As Color Implements VBNetText5Interface.BackColor
Get
Return MyBase.BackColor
End Get
Set(ByVal value As Color)
MyBase.BackColor = value
Me.AppendText(" back")
End Set
End Property

Public Shadows Sub Show() Implements VBNetText5Interface.Show
'Me.Size = New System.Drawing.Size(1000, 111)
MyBase.Show()
Me.ForeColor = Color.Green
End Sub

Public Overloads Sub AppendText(ByVal text As String) Implements VBNetText5Interface.AppendText
Me.ForeColor = Color.Orange
MyBase.AppendText(text)
End Sub

Public Overloads Sub BringToFront() Implements VBNetText5Interface.BringToFront
MyBase.BringToFront()
Me.ForeColor = Color.Yellow
End Sub

Public Sub setColor() Implements VBNetText5Interface.SetColor
MyBase.BackColor = Color.Gold '- does same thing as Me.Backcolor =
MyBase.ForeColor = Color.White '- does same thing as Me.Backcolor =
End Sub

<comregisterfunction()> _
Public Shared Sub ComRegister(ByVal t As Type)
' Not all keys strictly needed for the control to appear as a control in VB6.
Dim keyName As String
keyName = "CLSID\" + t.GUID.ToString("B")
Dim key As RegistryKey
key = Registry.ClassesRoot.OpenSubKey(keyName, True)
key.CreateSubKey("Control").Close()
key.CreateSubKey("Insertable").Close()
key.CreateSubKey("Implemented Categories\{40FC6ED4-2438-11CF-A3DB-080036F12502}")
Dim subkey As RegistryKey
subkey = key.CreateSubKey("MiscStatus")
subkey.SetValue("", "131457")
subkey = key.CreateSubKey("TypeLib")
Dim libid As Guid
libid = Marshal.GetTypeLibGuidForAssembly(t.Assembly)
subkey.SetValue("", libid.ToString("B"))
subkey = key.CreateSubKey("Version")
Dim ver As Version
ver = t.Assembly.GetName().Version
Dim version As String
version = String.Format("{0}.{1}", ver.Major, ver.Minor)
If version = "0.0" Then
version = "1.0"
End If
subkey.SetValue("", version)
' Should enable the VB6 toolbox to display a specific icon for the textbox, but it is not working at this stage. The
' textbox is currently dispayed with the default icon.
subkey = key.CreateSubKey("ToolBoxBitmap32")
Dim path As String = Assembly.GetAssembly(GetType(VBTest6.VBNetText)).Location
subkey.SetValue("", path + ", 1")
End Sub

<comunregisterfunction()> _
Public Shared Sub ComUnregister(ByVal t As Type)
Dim keyName As String
keyName = "CLSID\" + t.GUID.ToString("B")
Registry.ClassesRoot.DeleteSubKeyTree(keyName)
End Sub
End Class


If you can see where I am going wrong dont hesitate to let me know, but I have already decided that this is not going to work (its unsupported by Microsoft etc.) and so now I am working on wrapping a .net textbox as an ActiveX control in MFC 8 (C++ 2005). I would appreciciate any help from anyone who has managed to do this.

Natasa
GeneralRe: .net activex used in vb6 or .net control wrapped in MFC Pin
Nat2415-Jan-06 8:45
Nat2415-Jan-06 8:45 
GeneralProblems adding control to InfoPath 2003 Pin
netoec8423-Oct-05 3:33
netoec8423-Oct-05 3:33 
GeneralUserControl references another DLL Pin
Omri Shaked12-Apr-05 3:07
Omri Shaked12-Apr-05 3:07 
GeneralRe: UserControl references another DLL Pin
RipplingCreek1-Aug-05 13:36
RipplingCreek1-Aug-05 13:36 
GeneralImportant note on using this with Visual Basic 6 Pin
GabM27-Oct-04 13:31
GabM27-Oct-04 13:31 
GeneralRe: Important note on using this with Visual Basic 6 Pin
Nat2421-Dec-05 10:14
Nat2421-Dec-05 10:14 
GeneralRe: Important note on using this with Visual Basic 6 Pin
ndecreve10-Feb-06 0:23
ndecreve10-Feb-06 0:23 
GeneralUsing control in MS Word Pin
bondarch24-Sep-04 11:58
bondarch24-Sep-04 11:58 
GeneralRe: Using control in MS Word Pin
Phil Crosby11-Feb-06 18:21
Phil Crosby11-Feb-06 18:21 
QuestionCan this hold another activex control? Pin
Aibynn20-May-04 19:09
Aibynn20-May-04 19:09 
GeneralThis doesn't seem to work, please advise. Pin
AweAwe2-May-04 4:08
AweAwe2-May-04 4:08 
QuestionShould this work with MSDE 2003? Pin
AweAwe21-Apr-04 23:18
AweAwe21-Apr-04 23:18 
AnswerRe: Should this work with MSDE 2003? Pin
Zukoff_8-Dec-05 1:40
Zukoff_8-Dec-05 1:40 
QuestionHow to embed the control in IE Pin
prdas30-Mar-04 7:43
prdas30-Mar-04 7:43 
AnswerRe: How to embed the control in IE Pin
Paul Qualls1-Apr-04 12:07
sussPaul Qualls1-Apr-04 12:07 
AnswerRe: How to embed the control in IE Pin
VinnyCar12-May-04 7:34
VinnyCar12-May-04 7:34 
AnswerRe: How to embed the control in IE Pin
granadaCoder6-Dec-05 9:53
granadaCoder6-Dec-05 9:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.