HomeDigital EditionSearch Dotnet Cd
ASP.NET C# Certification Exams The CLI Data Access Editorials Extending .NET Fundamentals Interoperability Interviews Migrate Mobile .NET Mono .NET Interface Object-Oriented Programming Open Source Optimization Product/Book Reviews Security Source Code UML Visual Studio .NET

ActiveX controls have been the mainstay of most Visual Basic 6 developers and have found their way into countless development tools and Windows applications. Most ActiveX controls, even the "free" controls included with Visual Basic, can be used with Visual C++, Access, Delphi, Oracle, Paradox, and many other languages.

With tens of thousands of ActiveX controls available, some free, some not, it's not difficult to find a control with the exact feature that you need for your latest project. Finding a "pure" .NET managed control is not always quite that simple.

.NET and Visual Studio .NET go a long way toward making ActiveX controls very easy to use, but there are some pitfalls that may cause some confusion (and possibly some bad language). These obstacles are really minor, but when you run into them the first time, they are extremely frustrating.

I'll try to guide you through most of these issues. No, we won't write an actual application, but once you have seen the problems you may encounter, you should be ready to go with the assurance that you can use most ActiveX controls with .NET. To illustrate the roadblocks ahead, I'll use the demo version of SftBox/ATL 3.0, an ActiveX ComboBox control. You can download the free demo from www.windowscontrols.com, but most any ActiveX control will do and could be used instead.

I'll use Visual Studio .NET 2003 in this article and for the sample code, but using Visual Studio .NET 2002 is essentially the same and raises the same issues when dealing with an ActiveX control. Both versions fully support ActiveX controls, and the techniques shown here apply to both.

Getting ready to use an ActiveX control and dropping it onto a Windows Form is quite straightforward. First, you'll need to create a new project. Using Visual Studio .NET's "File, New" menu command, create a new Windows Application using the Visual C# or Visual Basic Project type. As the blank form stares at you, it is time to drop an ActiveX control onto it. Your Toolbox has a number of Windows Forms controls, but you'll have to add the ActiveX controls you want to use first. If you right-click on the Toolbox and select Add/Remove Items (or Customize Toolbox in Visual Studio .NET 2002), you'll be presented with a list of .NET components and a list of COM components. ActiveX controls are COM components, so you'll have to pick one of the COM components. In this example, we'll select "SftBox/ATL 3.0 Combo Box Control" (see Figure 1).

Now you're all set. Locate the new Toolbox icon for the SftBox/ATL 3.0 Combo Box Control and drop it on your form (see Figure 2).

So far, so good. If your ActiveX control made it this far, we'll assume that it works okay with .NET. Just keep in mind that some ActiveX controls simply won't work with all development tools. For example, most tab controls that you may be familiar with from your Visual Basic 6 days do not work with anything but Visual Basic 6; some splitter controls also suffer from this limitation. But for the most part, a well-designed control that follows the ActiveX standards should offer little trouble.

At this point, you'll most likely need some help. Actually, most of us would. Unless you know the control you are using extremely well, you'll probably need a little guidance in the form of online help. Not all of us manage to remember the hundreds of properties and methods a control offers. Finding the help file is not always easy. Older ActiveX controls or controls that have not been updated specifically to support .NET will not offer online help - at least it's not easily accessible. Many current ActiveX controls do, however, have excellent help support. When a control is selected or a property is edited, the Dynamic Help window automatically offers a number of suggested topics for the control and for Visual Studio .NET in general. Dynamic Help is a great way to access context-related help for any activity. You can access Dynamic Help using Control F1. For controls that don't offer Dynamic Help, you'll have to locate the help file. Often you'll find a program group for a control in your Start menu, which has an entry for online help. If the control offers property pages, you may be able to access online help there. Otherwise, you'll have to hunt for the help file.

So far I've shown you how to add a control to the form and access online help. I skipped over a little detail when I told you to drop the control onto the form. Remember, this is an ActiveX control. .NET doesn't really "talk" directly to ActiveX controls. An ActiveX control is not a managed control; it uses unmanaged code. Visual Studio .NET creates a complete class wrapper for our ActiveX control, so to your application the control looks just like any other .NET managed control would. This class wrapper is called the runtime callable wrapper (RCW). Usually, this process is completely transparent and is a nice way to bring an ActiveX control to the .NET world. An ActiveX control will now look just like a .NET control - and in fact it is. The class wrapper creates a control derived from the AxHost class, which has the Control class as its base class.

Where is the class wrapper? You don't actually see it. It is created, compiled, and deleted. You get only the Interop DLLs in your output directory. But for the curious (and sometimes the desperate), you can take a look at the class wrapper. The .NET Framework SDK has a utility that can generate the source (in addition to creating the DLLs). The AxImp utility can create the class wrapper for you. Its output is also included as part of the sample source available with this article. Use this command- line code to invoke the utility:

"C:\program files\Microsoft Visual Studio .NET
2003\SDK\v1.1\Bin\AxImp.exe"
C:\Windows\System32\SftBox_IX86_A_30.ocx/source

It creates a source file full of quite boring properties and method definitions, as seen in Listing 1. For now, we won't actually need this class wrapper, but it will come in handy when we look at events and a few odd properties.

Up to this point an ActiveX control and a .NET managed control are virtually indistinguishable. They certainly look the same when your application runs. Even at design time, you can manipulate properties using the IDE's "Properties" window and you can get online help. Some ActiveX controls even offer property pages. You can access these using the Property Pages button in the Properties window. Or you can access the Properties pages by right-clicking on the control in design-view. Properties pages offered by ActiveX controls are specific to each control (see Figure 3) and often offer additional features, such as easier property editing, online help access, and additional descriptions.

Whether you modify properties through the IDE's Properties window or through the control's custom property pages doesn't matter. All the properties you define at design time are nicely saved in a resource and are set at runtime by the form's InitializeComponent method (see Listing 2).

It looks all too easy, and as usual in this business, it's the little details that get you. Let's start by adding three entries to the combo box and selecting the first entry (see Listing 3). As you enter this code, you'll notice that IntelliSense nicely offers all available properties and methods. The project actually runs without any problems, but it does look quite boring.

First, let's spruce it up a bit by using a different foreground color for the first entry. In old-style VB, we would be able to say something like:

SftBox1.Cell(index, 0).ForeColor = vbRed;

Basically, we're accessing the Cell object, indexed by item index and column index, so we can manipulate its ForeColor property. Because of the class wrapper generated by .NET, it's not quite that simple. This seemingly simple construct can make a seasoned developer cringe when it needs to be translated to .NET. But you'll get the hang of it once you know what to look for. Simple properties are no problem. But a property such as "Cell", which requires additional parameters (i.e., index and column number), is changed by .NET into a method. So instead of the Cell property, you'll have to use the get_Cell and set_Cell methods. Actually set_Cell doesn't exist in this case, but you get the idea. So let's try again.

C#
axSftBox1.get_Cell(index, 0).ForeColor = Color.Red;

VB
AxSftBox1.get_Cell(index, 0).ForeColor = Color.Red

Almost, but IntelliSense says ForeColor is of type uint or UInt32, and Red is a Color type. Fortunately, .NET has a ColorTranslator class, which comes in handy to convert between these types:

C#
axSftBox1.get_Cell(index, 0).ForeColor = (uint)
ColorTranslator.ToOle(Color.Red);
axSftBox1.get_Cell(index, 0).SelectForeColor =
axSftBox1.get_Cell(index, 0).ForeColor

VB
AxSftBox1.get_Cell(Index, 0).ForeColor =
Convert.ToUInt32(ColorTranslator.ToOle(Color.Red))
AxSftBox1.get_Cell(Index, 0).SelectForeColor =
AxSftBox1.get_Cell(Index, 0).ForeColor

Now let's add a nice little image to the first item. To make life easier, we'll add a PictureBox control from the Toolbox to the form, load an image into it, and we'll have access to the Image property of the PictureBox. Of course there are many other ways to get an image, but this is the easiest for our example. .NET thinks of bitmaps and images in terms of an Image object. ActiveX controls use the OLE Picture object (really an IPicture or IPictureDisp COM interface). These are truly worlds apart. In "VB6-speak" you would simply assign the image to the cell as in:

Set SftBox1.Cell(index, 0).Picture = PictureBox1.Picture;

A VB6 PictureBox happens to have the Picture property, which uses an OLE Picture object. .NET of course doesn't. Converting an Image object to an IPicture interface pointer seems difficult, if not impossible. At first, this is a real puzzler. A look at the class wrapper may help. It translated the MouseIcon property, which also uses an OLE Picture type, but it is exposed it as a System.Drawing.Image type. Upon inspection, it reveals the use of the GetIPictureFromPicture method.

this.ocx.MouseIcon = ((stdole.IPictureDisp)(GetIPicture
FromPicture(value)));

This method takes an object of type Image and returns an IPicture interface pointer. That's exactly what we need, but unfortunately the method is protected, meaning it can only be used in a class derived from AxHost. So, we'll have to create a small helper class for this. Fortunately, we'll only have to do this once. All controls and projects can use the same helper class. The OLEConvert.cs and OLEConvert.vb source files included with the source code for this article show how this class is implemented (see Listing 4).

The ToOLEPic function is used to easily convert from an Image object to an OLE Picture type.

C#
axSftBox1.get_Cell(index, 0).Picture = (stdole.IPictureDisp)
OLEConvert.OLECvt.ToOLEPic(pictureBox1.Image);

VB
p> AxSftBox1.get_Cell(Index, 0).Picture =
OLEConvert.OLECvt.ToOLEPic(PictureBox1.Image)

The complete code to add three entries, highlighting the first with color and an image, now looks as shown in Listing 5.

While entering code, you may have noticed that IntelliSense also offers a let_Picture method. Usually, a picture property offers two forms. The first form assigns a reference to a picture object (Picture property), the second form assigns a complete copy of the image to the picture property (let_Picture method). Unless you want to create actual copies of images, the use of let_Picture should be minimal.

Fonts also need somewhat special treatment. .NET knows the Font type, but ActiveX controls insist on the OLE Font type (a IFontDisp COM interface). The generated class wrapper again offers some assistance and handles the control's Font property, which accepts a Font type and translates it to the ActiveX control's preferred IFontDisp interface pointer, so we don't have to worry about this case (see Listing 6).

Unfortunately, other Font properties, such as the cells' Font properties, need our help. Our helper class that we implemented to better handle images can also help with fonts. The ToOLEFont function converts a Font object to an IFontDisp interface pointer (see Listing 7).

Dealing with images and fonts can be difficult at first, but using these techniques should make it quite straightforward. Of course, you'll still have to write the application, but at least working with ActiveX controls is now a bit easier.

Because .NET uses namespaces, even setting a simple property can end up becoming a typing effort.

axSftBox1.Items.Style =
SftBoxLib30.SftBoxItemsStyleConstants.itemsStyleSftBoxVariable;

Don't forget that you can use a using or Imports statement even for ActiveX controls. This will reduce the clutter. If you add a "using SftBoxLib30;" or an "Imports SftBoxLib30" statement to your source file, assigning a property becomes:

axSftBox1.Items.Style = SftBoxItemsStyleConstants.itemsStyleSftBoxVariable;

Events are a bit surprising in the .NET world. If you are used to Visual Basic or Visual C++, you expect event parameters to arrive as part of the event handler. For example, in Visual Basic 6 you would see a KeyDown event defined like this:

Private Sub object_KeyDown(KeyCode As Integer, ByVal Shift As Integer)

In .NET, every event handler has the same "signature" with exactly two arguments:

C#
void object_KeyDownEvent(object sender, EventArgumentType e);

VB
Private Sub AxSftBox1_KeyDownEvent(ByVal sender As Object, ByVal e As
AxSftBoxLib30._ISftBoxEvents_KeyDownEvent) Handles
AxSftBox1.KeyDownEvent

All the arguments that are specific to the event are passed in the "e" object. "e" is an object of class EventArgs or a derived class. You'll find all the different event classes listed in the class wrapper generated by AxImp. But generally, you really won't have to look there. If you need a specific argument, you can use IntelliSense by typing "e." and it will offer all available arguments. You should have no problem matching these up against the documented arguments in the online help. The generated class wrapper usually changes the argument's first letter to lowercase. In this example you would find an e.keyCode and an e.shift member as part of the class derived from EventArgs (see Listing 8).

At last, depending on the ActiveX control you are using, you may find that a method, property, or event is missing or may be in conflict with one of its base classes. (Remember, your ActiveX control is derived from the Control and AxHost base classes.) For example, the ComboBox control's "RightToLeft" property is nowhere to be found. With the generated class wrapper in hand, you can quickly find that the property has become "CtlRightToLeft" instead. .NET prefixes conflicting or reserved names with "Ctl". If a control has a "Refresh" method, it is renamed to "CtlRefresh", to distinguish it from the Refresh method defined in the Control base class. Some events receive an "Event" suffix. For example, an event name "KeyDown" (as defined by the ActiveX control) becomes the "KeyDownEvent".

Conclusion
Using ActiveX controls is just as easy with .NET as it is with many other development tools such as Visual Basic. Most ActiveX controls will work equally well in a .NET application. The .NET runtime offers great COM support, so you can continue to use ActiveX controls that you have used outside of .NET until now. Rather than reinventing the wheel or waiting for native .NET controls to catch up with the selection and the wealth of features already offered by ActiveX controls, you can use these controls today. Now that you know how to overcome some of the more frustrating issues involved in integrating ActiveX controls with .NET, what is stopping you?

Author Bio
Frank Sloane is support manager at Softel vdm, a company specializing in Windows ActiveX and .NET controls. He has coauthored (and fixed) a number of applications and controls since joining the company in 1997.

	


Listing 1

[System.ComponentModel.Browsable(false)]
[System.ComponentModel.DesignerSerializationVisibility
(System.ComponentModel.DesignerSerializationVisibility.Hidden)]
        [System.Runtime.InteropServices.DispIdAttribute(9)]
        public virtual SftBoxLib30.SftBoxEdit Edit {
            get {
                if ((this.ocx == null)) {
                    throw new System.Windows.Forms
			.AxHost.InvalidActiveX
			StateException("Edit", 
			System.Windows.Forms.AxHost.
			ActiveXInvokeKind.PropertyGet);
                }
                return this.ocx.Edit;
            }
        }



Listing 2

C#
private void InitializeComponent()
{
    System.Resources.ResourceManager resources = new
	System.Resources.ResourceManager(typeof(Form1));
    this.axSftBox1 = new AxSftBoxLib30.AxSftBox();
    ((System.ComponentModel.ISupportInitialize)(this.axSftBox1)).BeginInit();
    this.SuspendLayout();
    // 
    // axSftBox1
    // 
    this.axSftBox1.Location = new System.Drawing.Point(32, 24);
    this.axSftBox1.Name = "axSftBox1";
    this.axSftBox1.OcxState = ((System.Windows.Forms.
				AxHost.State)(resources.GetObject
				("axSftBox1.OcxState")));
    this.axSftBox1.Size = new System.Drawing.Size(224, 38);
    this.axSftBox1.TabIndex = 0;
    // 
    // Form1
    // 
    this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
    this.ClientSize = new System.Drawing.Size(292, 266);
    this.Controls.Add(this.axSftBox1);
    this.Name = "Form1";
    this.Text = "Form1";
    this.Load += new 	System.EventHandler(this.Form1_Load);
    ((System.ComponentModel.ISupportInitialize)(this.axSftBox1)).EndInit();
    this.ResumeLayout(false);
}

VB
Private Sub InitializeComponent()
    Dim resources As System.Resources.ResourceManager = New
	System.Resources.ResourceManager
				(GetType(Form1))
    Me.AxSftBox1 = New AxSftBoxLib30.AxSftBox
    CType(Me.AxSftBox1,
	System.ComponentModel.ISupportInitialize)
				.BeginInit()
    Me.SuspendLayout()
    '
    'AxSftBox1
    '
    Me.AxSftBox1.Location = New System.Drawing.Point(32, 24)
    Me.AxSftBox1.Name = "AxSftBox1"
    Me.AxSftBox1.OcxState = CType(resources.GetObject
			 ("AxSftBox1.OcxState"),
			 System.Windows.Forms.AxHost.State)
    Me.AxSftBox1.Size = New System.Drawing.Size(224, 38)
    Me.AxSftBox1.TabIndex = 3
    '
    'Form1
    '
    Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
    Me.ClientSize = New System.Drawing.Size(292, 266)
    Me.Controls.Add(Me.AxSftBox1)
    Me.Name = "Form1"
    Me.Text = "Form1"
    CType(Me.AxSftBox1, 
	System.ComponentModel.ISupportInitialize)
	.EndInit()
    Me.ResumeLayout(False)
	End Sub



Listing 3

C#
private void Form1_Load(object sender, System.EventArgs e) {
   axSftBox1.Items.Style = 
   SftBoxLib30.SftBoxItemsStyleConstants.
				itemsStyleSftBoxVariable;

    int index = axSftBox1.Items.Add("First Item");
    axSftBox1.Items.Add("Second Item");
    axSftBox1.Items.Add("Third Item");

    axSftBox1.Items.Selection = index;
}

VB
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs)
 Handles MyBase.Load
    Dim Index As Integer
    AxSftBox1.Items.Style = 
	SftBoxLib30.SftBoxItemsStyleConstants.itemsStyleSftBoxVariable

    Index = AxSftBox1.Items.Add("First Item")
    AxSftBox1.Items.Add("Second Item")
    AxSftBox1.Items.Add("Third Item")

    AxSftBox1.Items.Selection = Index
End Sub



Listing 4

C#
namespace OLEConvert 
{
    // This helper class allows access to pro	
	// tected members of AxHost
    // used to convert Image and Font objects to 
	//	OLE objects
    public class OLECvt : System.Windows.Forms.AxHost
    {
        private OLECvt() : base("")
        {
        }
        // convert an Image to an OLE Picture object
        public static stdole.StdPictureToOLEPic(Image i)
        {
            return 
			AxHost.GetIPictureDispFromPicture(i) as stdole.StdPicture;
        }

        // convert an Image to an OLE PictureIPictureDisp interface
        public static stdole.IPictureDisp
		ToOLE_IPictureDisp(Image i)
        {
            return 
			AxHost.GetIPictureDispFromPicture(i) as stdole.IPictureDisp;
        }

        // convert a Font to an OLE Font object
        public static stdole.StdFontToOLEFont(Font f)
        {
            return AxHost.GetIFontFromFont(f) as stdole.StdFont;
        }

        // convert a Font to an OLE Font 
		// IFontDisp interface
        public static stdole.IFontDispToOLE_IFontDisp(Font f)
        {
            return AxHost.GetIFontFromFont(f) as stdole.IFontDisp;
        }
    }
}

VB
Public Class OLECvt
    Inherits System.Windows.Forms.AxHost
	Public Sub New()
        MyBase.New("")
    End Sub
    ' convert an Image to a OLE Picture object
	Public Shared Function ToOLEPic(ByVal i As Image) As stdole.StdPicture
	   Return GetIPictureDispFromPicture(i)
    End Function
    ' convert an Image to an OLE Picture ' IPictureDisp interface
    Public Shared Function ToOLE_IPictureDisp(ByVal i As Image) As
	stdole.IPictureDisp
        Return GetIPictureDispFromPicture(i)
    End Function
    ' convert a Font to an OLE Font object   
    Public Shared Function ToOLEFont(ByVal f As Font) As stdole.StdFont
        Return GetIFontFromFont(f)
    End Function
    ' convert a Font to an OLE Font IFontDisp ' interface
    Public Shared Function ToOLE_IFontDisp(ByVal f As Font) As stdole.IFontDisp
        Return GetIFontFromFont(f)
    End Function
End Class

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.

  E-mail: info@sys-con.com