VB Migration Partner supports all the controls included in the Visual Basic 6 package,
with the only exception of the OLE and Repeater controls. When migrating a form
that contains unrecognized 3rd-party (non Microsoft) ActiveX controls, such controls
are transformed into “placeholder” controls that appear on the form
as red rectangles. (For each unrecognized ActiveX control a warning is also generated
in the migrated project)
VB Migration Partner can recognize and correctly convert a 3rd-party (non-Microsoft)
ActiveX control only if a wrapper class exists for it. To create such a wrapper
class you use the AxWrapperGen utility.
NOTE: you don’t need to run AxWrapperGen if the
ActiveX control is written in VB6 and you have its source code. In such case you
can just convert the ActiveX Control project with VB Migration Partner as usual
and then deploy the resulting DLL as explained in the “Deploying the wrapper
class” section below. If the ActiveX control is used only by a few client
projects you might also group them in a VB6 project group (*.vbg) and convert all
of them in a single operation.
Contents
Running AxWrapperGen
Deploying the wrapper class
Fixing and polishing the wrapper class
Troubleshooting
Inheriting from a .NET control
Conclusion
Running AxWrapperGen
AxWrapperGen is a tool that is part of the VB Migration Partner package. (You can
find it in the main installation directory, which by default is C:\Program Files\Code
Architects\VB Migration Partner.) It is a command-line utility, therefore you must
open a command window and run AxWrapperGen from there. You can read the complete
AxWrapperGen reference in chapter
4 of our online manual.
We also recommend that you read the A smart approach
to 3rd-party ActiveX control conversion whitepaper to become familiar with
the concept of wrapper classes, ActiveX control migration and ActiveX control replacement.
IMPORTANT NOTE: You can run AxWrapperGen only on ActiveX
controls for which you own the design-time license.
AxWrapperGen takes one mandatory argument, that is, the path of the .ocx file that
contains the ActiveX control to be wrapped. For example, the following command creates
the wrapper class for the Microsoft Calendar control (and of course assumes that
such control is installed in the c:\windows\system32 folder):
AxWrapperGen c:\windows\system32\mscal.ocx
(Notice that in this example we use the MSCAL.OCX control only for illustration
purposes, as VB Migration Partner already supports this control.) If the file name
contains spaces, you must enclose the name inside double quotes. AxWrapperGen is
able to convert multiple ActiveX controls in one shot
AxWrapperGen c:\windows\system32\mscal.ocx "c:\windows\system32\threed32.ocx"
By default, AxWrapperGen generates a new project and solution named AxWrapper in
current directory. You can change these default by means of the /out
switch (to indicate an alternative output directory) and /project
switch (to set the name of the new project and solution):
AxWrapperGen c:\windows\system32\mscal.ocx /out:c:\myapp /project:NetCalendar
By default, AxWrapperGen generates VS2008 projects. You can generate VS2005 or VS2010
projects by adding a /version option:
AxWrapperGen c:\windows\system32\mscal.ocx /version:2010
AxWrapperGen runs the AxImp tool – which is part of the .NET Framework SDK
– behind the scenes, to generate two DLLs that work as the RCW (Runtime Component
Wrapper) for the selected control. For example, the mscal.ocx control generates
the following two files: msacal.dll and axmsacal.dll. You can specify the following
five options, which AxWrapperGen passes to AxImp: /keyfile, /keycontainer, /publickey,
/delaysign, and /source. For more information, read
.NET Framework SDK documentation.
At the end of generation process, AxWrapperGen runs Microsoft Visual Studio and
loads a solution that contains one VB.NET class library (DLL) project, containing
a pair of classes for each ActiveX control:
* The first class of each pair works as a wrapper for the original
ActiveX control.
* The second class of each pair provides support during the migration
process.
In our example, the MSCAL.OCX file contains only one ActiveX control, therefore
only two classes will be created: the class named VB6Calendar inherits
from AxMSACAL.AxCalendar and wraps the ActiveX control; the second class is named
VB6Calendar_Support and inherits from VB6ControlSupportBase. This
*_Support class is instantiated and used by VB Migration Partner during the migration
process, but is never used at runtime during execution.
NOTE: if the ActiveX control in question uses databinding
to display database data using DAO and the “classic” Data control, converting
it with AxWrapperGen is pretty useless, because the resulting .NET control won’t
find any Data control on the migrated form. Unfortunately, DAO databinding relies
on a number of undocumented, proprietary Microsoft interfaces that cannot be implemented
under .NET. This is the reason why VB Migration Partner doesn’t (and can’t)
support the DBGrid32, DBCombo and DBList
ActiveX controls, nor any other control whose main purpose is being bound to a DAO
Data control. The same reasoning apply to controls that bind to the RDC Data control.
Deploying the wrapper class
In most cases AxWrapperGen generates a wrapper class – or a set of wrapper
classes - that compiles with no errors to a .NET DLL (NetCalendar.dll in our example).
This DLL depends on at least the two DLLs that were generated by AxImp
(AxMSCAL.dll and MSACAL.dll in our example), plus other files that AxImp may generate.
You should consider all these DLLs as a single deployment unit and to always
move all of them together when advised to do so.
If the AxWrapperGen-generated project compiles correctly, the next step is to deploy
the set of DLLs to a folder where VB Migration Partner can find it.
The first candidate for the deployment is VB Migration Partner’s own setup
folder. This approach is quick and simple, but has two serious shortcomings:
- If you later fix and recompile the wrapper class project you have to manually copy
again the wrapper class to VB Migration Partner’s folder. It’s easy
to skip this important step, in which case VB Migration Partner continues to use
the previous (presumably bugged) DLL version without showing any error message.
- As noted above, you must remember to also copy all dependency DLLs. At times this
set of DLLs includes one or more files that are already present in VB Migration
Partner’s folder, either because they were installed with VB Migration Partner
itself – for example, the ADODB.dll – or because they were deployed
there when migrating another ActiveX control. If you overwrite these DLLs many strange
and unexpected behaviors may result.
A better approach for deploying wrapper classes is storing them in a separate folder
tree – say, c:\wrappers\<controlname> – and then use an AddLibraryPath
pragma that points to that folder. This pragma can be only stored inside a file,
which you can edit from inside VB Migration Partner’s user interface:
Remember that the pragma file must be stored in the same folder as the VB6 project
you are converting to .NET.
Fixing and polishing the wrapper class
In some cases the wrapper class doesn’t compile correctly and you have to
manually fix these problems before continuing. Even if the wrapper class compiles
correctly, however, it is a good idea to revising and polishing it before proceeding.
NOTE: all the common actions that need to be performed
on a wrapper class are summarized in the “Instructions” remark regions
at the top of the wrapper class. This section expands on the recommendations described
in that section.
1. ActiveX control name
The first and most important step is checking that AxWrapperGen generated the correct
ProgID for the ActiveX control, thus you need to compare the first argument of the
VB6Object attribute (at the top of the wrapper class) with the control name as it
appears in VB6 Object Browser:
If these strings don’t match exactly, VB Migration Partner fails to replace
the original ActiveX control with this wrapper class during the migration step.
After receiving and solving tons of support queries from our customers, we concluded
that name mismatch is by far the most common cause of malfunctioning.
2. ActiveX control “alternate” name
Some VB6 ActiveX controls have an “alternate” name, which comes into
play when the control is dynamically added to a VB6 form by means of the Controls.Add
method. For example, the standard name for the VB6 TreeView is “MSComCtlLib.TreeView”,
yet you must use its alternate name “MSComCtlLib.TreeCtrl.2” when adding
it dynamically:
Me.Controls.Add "MSComCtlLib.TreeCtrl.2", "MyTreeView"
To support this feature also for the ActiveX control wrapped by the class you must
assign the AlternateName property of the VB6Object property. This
property doesn’t appear in the AxWrapperGen-generated source code and must
be added manually.
For example, if a fictitious ActiveX control named “XLib.XGrid” had
an alternate name of “XLib.XGridControl.2” then you should edit its
VB6Object attribute as follows:
<VB6Object("XLib.XGrid", _
AlternateName:="XLib.XGridControl.2", _
TypeLibrary:="XLib", TypeLibraryNamespace:="XLibCtrl", _
DependsOnAssemblies:="XLib.dll,AxXLib.dll", _
IgnoreMembers:="")> _
Public Class VB6XGrid
3. The IgnoreMember property
Not all the properties of the original ActiveX control can or should be migrated
to .NET. For example, properties related to docking (e.g. Align), transparent background,
and color palettes don’t work under .NET; they can (and should) be ignored
when an instance of the control is converted from a VB6 form to a .NET form.
During the migration of a VB6 application, VB Migration Partner looks for the IgnoreMember
property of the VB6Object attribute and ignores all the properties listed there.
This property is expected to contain the pipe-delimited list of members to be ignored.
For example, this is how this property might be set for the fictitious XGrid control:
<VB6Object("XLib.XGrid", _
AlternateName:="XLib.XGridControl.2", _
TypeLibrary:="XLib", TypeLibraryNamespace:="XLibCtrl", _
DependsOnAssemblies:="XLib.dll,AxXLib.dll", _
IgnoreMembers:="Negotiate|MaskColor|Palette|UsePalette")> _
Public Class VB6XGrid
4. The TranslateProperties property
When VB Migration Partner converts a form and all the controls it hosts from VB6
to .NET, it reads the design-time value for all the control’s property from
the .frm file, more precisely from the topmost portion of this file (that doesn’t
appear when you edit the form from inside the VB6 code editor).
The vast majority of properties appear in the .frm file with the same name you can
use to read or assign the property from code, but in some cases the two names differ.
If the properties differ you must instruct VB Migration Partner about the correspondence
between the name in the .frm file and the name in the object’s public interface.
This information is conveyed in the TranslateProperties property
of the VB6Object attribute, as a comma-delimited series of frmname=codename
pairs.
If you notice that one or more properties of the ActiveX control aren’t converted
correctly by VB Migration Partner when it migrates a form, then you should open
the .frm file with Notepad and check whether the properties in question have a name
different from the name you see in the object browser.
Let’s suppose that you realize that VB Migration Partner fails to correctly
convert the design-time value of two properties – named HotTrack and Rows
– of the fictitious XGrid control. In the VB6 form these properties are assign
the values True and 12, respectively, but these values are lost in the migration.
Open the .frm file with Notepad and look for properties with similar name (and same
assigned value) in the Begin… End block dedicated to this control. You might
find something like:;
…
Begin XLib.XGrid XGrid1
…
HotTracking = -1
RowCount = 12
…
End
It is obvious that the HotTrack property is stored in the .frm with the name HotTracking,
and that the Rows property is stored as RowCount. You now have all the necessary
info to modify the VB6Object attribute to let VB Migration Partner know about this
detail:
<VB6Object("XLib.XGrid", _
TypeLibrary:="XLib", TypeLibraryNamespace:="XLibCtrl", _
DependsOnAssemblies:="XLib.dll,AxXLib.dll", _
TranslateProperties:="HotTracking=HotTrack,RowCount=Rows", _
IgnoreMembers:="Negotiate|MaskColor|Palette|UsePalette")> _
Public Class VB6XGrid
5. Data-bound controls
By default, AxWrapperGen assumes that the ActiveX control supports ADODB databinding
and generates a region of code named “-- Data-binding support –“,
containing a basic implementation of all the members of the IVB6Control interface.
This interface is defined inside CodeArchitects.VBLibrary.dll and is used by VB
Migration Partner’s support library to simulate data binding in the converted
VB.NET form:
Public Class VB6XGrid
Inherits AxXLib.AxXGrid
Implements IVB6Control
Implements ISupportInitialize
Implements IVB6BoundControl
...
#Region "-- Data-binding support"
#End Region
NOTE: these code elements aren’t generated if the
ActiveX control exposes its own DataXxxx members.
If you know for sure that the control does not support data-binding – or if
you know that you never use data-binding features in your client forms – then
it is a good idea to remove both the Implements statement and the code region.
If the ActiveX control does support data-binding and you want to leverage this feature
even in migrated programs, then you should ensure that the BoundValue property (in
the IVB6BoundControl interface) takes and returns the bound property. In most cases
AxWrapperGen makes a correct guess about the name of the bound property, but in
some cases you need to correct it. For example, let’s suppose that the bound
property for your control is named Value (Integer), whereas AxWrapperGen mistakenly
assumed it was the Text string property:
Private Property BoundValue() As Object Implements IVB6BoundControl.BoundValue
Get
Return Me.Text
End Get
Set(ByVal value As Object)
Me.Text = VB6Utils.ValueToBoundProperty(Of String)(value)
End Set
End Property
Fixing this code is simple:
Private Property BoundValue() As Object Implements IVB6BoundControl.BoundValue
Get
Return Me.Value
End Get
Set(ByVal value As Object)
Me.Value = VB6Utils.ValueToBoundProperty(Of Integer)(value)
End Set
End Property
6. Support for “classic” drag-and-drop
By default, AxWrapperGen assumes that the ActiveX control supports “classic”
(VB4-style) drag-and-drop and generates a basic implementation of all the members
that can implement it, such as the DragIcon property and the Drag method):
Public Class VB6XGrid
Inherits AxXLib.AxXGrid
Implements ISupportInitialize
Implements IClassicDragDrop
...
#Region "-- Drag-and-drop support"
#End Region
If the original control doesn’t support “classic” drag-and-drop
– or if it does, but your client application doesn’t use this feature
– then you should remove both the Implements statement and the code region.
7. Rename events to avoid name collisions
It is legal for an ActiveX control to expose an event that has the same name as
a property or a method. A common case is an ActiveX control that exposes a Format
property and a Format event. Different members with same name aren’t valid
under .NET,
Another reason for renaming an event is if its name matches a VB.NET keyword.
Regardless of the reason, you must rename the event, else
the wrapper class can’t compile. The naming convention that we recommend –
and that we have used in our VBLibrary – is to append a “6” suffix
to the original name to avoid the name collision.
For example, let’s assume that the ActiveX control exposes an event named
“Error”, which is a reserved VB.NET keyword:
Public Shadows Event Error As VB6BeforePageBreakEventHandler
Private Sub Control_Error(ByVal sender As Object, ByVal e As AxXLib._IAxGridEvents_ErrorEvent) _
Handles MyBase.Error
VB6ErrorEventDispatcher.Raise(Me, "Error", e.errorCode, e.showMsgBox)
End Sub
The first thing to do to rename the event is changing the event name:
Public Shadows Event Error6 As VB6BeforePageBreakEventHandler
Private Sub Control_Error(ByVal sender As Object, ByVal e As AxXLib._IAxGridEvents_ErrorEvent) _
Handles MyBase.Error
VB6ErrorEventDispatcher.Raise(Me, "Error6", e.errorCode, e.showMsgBox)
End Sub
Next, you have to make VB Migration Partner aware that the Error event has now a
different name. You do so by editing the TranslateEvents property
of the VB6Object attribute:
<VB6Object("XLib.XGrid", _
TypeLibrary:="XLib", TypeLibraryNamespace:="XLibCtrl", _
DependsOnAssemblies:="XLib.dll,AxXLib.dll", _
TranslateEvents:="Error=Error6", _
IgnoreMembers:="Negotiate|MaskColor|Palette|UsePalette")> _
Public Class VB6XGrid
If you rename more than one event, you should use a comma-delimited list of value pairs, as in:
TranslateEvents:="Error=Error6,Format=Format6"
8. Properties with multiple overloads
If the ActiveX control exposes a property with optional parameters, then the wrapper
class will expose two or more overloads of that property. If you later use the wrapper
class to convert forms containing the ActiveX control, the .NET form can’t
be edited inside Visual Studio. The solution is to remove the overloads by manually
merging them back to a property with optional parameters.
Let’s see a practical example. When you run AxWrapperGen on the VideoSoft
VSFlexGrid control, the generated wrapper class contains this code:
Public ReadOnly Property ArchiveInfo(ByVal arcFileName As String, _
ByVal infoType As VSFlex8L.ArchiveInfoSettings) As Object
Get
Return MyBase.get_ArchiveInfo(arcFileName, infoType)
End Get
End Property
Public ReadOnly Property ArchiveInfo(ByVal arcFileName As String, _
ByVal infoType As VSFlex8L.ArchiveInfoSettings, ByVal index As Object) As Object
Get
Return MyBase.get_ArchiveInfo(arcFileName, infoType, index)
End Get
End Property
The wrapper class compiles correctly and you can use it to migrate VB6 forms that
contain the VSFlexGrid control; the generated form runs and behaves correctly and
everything seems to be working fine. However, if you later try to modify the .NET
form inside Visual Studio you see an error message.
The solution is merging the two code blocks in a single property by making the 3rd
argument Optional and writing the code that delegates to either get_ArchiveInfo
method in the base class, depending on whether the optional argument has been omitted:
Public ReadOnly Property ArchiveInfo(ByVal arcFileName As String, _
ByVal infoType As VSFlex8L.ArchiveInfoSettings, _
Optional ByVal index As Object = Nothing) As Object
Get
If index Is Nothing Then
Return MyBase.get_ArchiveInfo(arcFileName, infoType)
Else
Return MyBase.get_ArchiveInfo(arcFileName, infoType, index)
End If
End Get
End Property
9. Wrapper class members with names not found in original ActiveX control
As you know, AxWrapperGen runs AxImp.exe behind the scenes. AxImp.exe may change
the name of a member if the AxHost base class exposes a member with same name. For
example, a property named EditMode might be converted into CtlEditMode, and Text
might be converted into CtlText.
If you notice that one or more members in the wrapper class have a name that can’t
be found in the original ActiveX control, then it is necessary to edit the TranslateMembers
property of the VB6Object attribute accordingly. This property is then used by VB
Migration Partner during the conversion process to detect which member names must
be modified when generating the client form’s source code. The value of the
TranslateMembers attribute is a comma-delimited list of oldname=newname
pairs, as in:
<VB6Object("XLib.XGrid", _
TypeLibrary:="XLib", TypeLibraryNamespace:="XLibCtrl", _
DependsOnAssemblies:="XLib.dll,AxXLib.dll", _
TranslateMembers:="Text=CtlText,EditMode=CtlEditMode", _
IgnoreMembers:="Negotiate|MaskColor|Palette|UsePalette")> _
Public Class VB6XGrid
Quite conveniently, AxWrapperGen creates a list of members that are candidate for
this treatment. You can find it in section #9 of the “Instructions”
region:
10. ScaleMode-related properties
Properties and members that are related to position and size values require minor
manual adjustments. For example, our fictitious XGrid control might expose a property
named RowHeight and a method named SetTitleBar:
Public Property RowHeight() As Single
Get
Return MyBase.RowHeight
End Get
Set(ByVal value As Single)
MyBase.RowHeight = value
End Set
End Property
Public Sub SetTitleBar(ByVal caption As String, ByVal left As Single, ByVal top As Single, _
ByVal width As Single, ByVal height As Single)
MyBase.SetTitleBar(caption, left, top, width, height)
End Sub
The problem with position- and size-related members such as these is that –
in most cases – the ActiveX controls expects values expressed in pixels, whereas
the client application uses values that are expressed in the current ScaleMode setting.
By default, these values are in twips, but the client app might have set a different
ScaleMode, including user-defined modes.
You understand that a property requires to be scaled from pixels to a different
measure unit if you see that the control stretches or shrinks abnormally after the
migration. For example, if a grid column is about 15x larger than it should, then
the problem is likely to be a missed conversion from twips to pixels.
The VB6Utils helper class – defined in CodeArchitects.VBLibrary.dll
– exposes four methods that you can use to convert between pixels and the
current ScaleMode setting: FromPixelX, FromPixelY, ToPixelX,
and ToPixelY. The first argument is the current control,
the second argument is the value to be converted, and the third argument must be
False if you are converting a position value or True if you are converting a size
value. The FromPixelY and ToPixelY methods take a fourth, optional argument that
must be True if the height of the menu on the parent form should be ignored:
Here’s how the RowHeight and SetTitleBar members look like after the fixes.
Public Property RowHeight() As Single
Get
Return VB6Utils.FromPixelY(Me, CInt(MyBase.RowHeight), True, True)
End Get
Set(ByVal value As Single)
MyBase.RowHeight = VB6Utils.ToPixelY(Me, value, True, True)
End Set
End Property
Public Sub SetTitleBar(ByVal caption As String, ByVal left As Single, ByVal top As Single, _
ByVal width As Single, ByVal height As Single)
MyBase.SetTitleBar(caption, VB6Utils.ToPixelX(Me, left, False), _
VB6Utils.ToPixelY(Me, top, False, True), _
VB6Utils.ToPixelX(Me, width, True), _
VB6Utils.ToPixelY(Me, height, True, True))
End Sub
11. Readonly, non-browsable, and transient properties
By default, all the public properties defined in the wrapper class appear in Visual
Studio’s Property window and are stored in the form’s code-behind section
(i.e. the *.Designer.vb file that Visual Studio creates for each form). You need
to take a few additional steps if you want to hide a property in the property browser
and/or prevent that its value be saved in the code-behind section (transient
property).
Hiding a property in Visual Studio’s Property window is as simple as marking
the property with a Browsable attribute. Likewise, preventing a
property value from being saved in the code-behind section requires that you mark
the property with a DesignerSerializationVisibility attribute,
as in this example:
<Browsable(False)> _
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
Public ReadOnly Property VisibleRowCount() As Single
Get
Return MyBase.VisibleRowCount
End Get
End Property
NOTE: good candidates for the Browsable attribute are
all readonly properties, unless they return an object or a collection.
In some cases you also need to mark the property with the Shadows
keyword, as in:
Public Shadows ReadOnly Property VisibleRowCount() As Single
...
End Property
12. Properties with ByRef arguments
Under COM and VB6 you can define properties that take a ByRef argument, whereas.NET
requires that properties only take ByVal arguments. Conveniently, AxWrapperGen flags
all properties with one or more ByRef arguments with a special TODO comment:
Public ReadOnly Property RowStatus(ByRef index As Integer) As Integer
Get
Return MyBase.RowStatus(index)
End Get
End Property
You can quickly find all such comments in Visual Studio’s Task List window.
Basically, there are two ways to fix this problem. First, you should read the ActiveX
documentation and check that – even if passed by-reference – the control
never modifies the argument being passed. If this is the case you can safely modify
the ByRef keyword into ByVal.
Public ReadOnly Property RowStatus(ByVal index As Integer) As Integer
Get
Return MyBase.RowStatus(index)
End Get
End Property
In other cases the ByRef semantics must be preserved, therefore you must split the
property into two separate get_propertyname and set_propertyname
methods, which don’t suffer from the ByRef restriction:
Public Function get_RowStatus(ByRef index As Integer) As Integer
Return MyBase.get_RowStatus(index)
End Property
Public Sub set_RowStatus(ByRef index As Integer, ByVal value As Integer)
MyBase.set_RowStatus(index, value)
End Sub
In this latter case, be prepared to modify the code in the client form to account
for these two new methods.
13. Size properties that aren’t migrated correctly
You might experience the following problem: the ActiveX control is migrated correctly
and appears to work as expected, except its size is different from the original
or its size isn’t preserved when the .NET form is modified inside Visual Studio’s
form designer.
In this case, you should uncomment two lines in the support class that AxWrapperGen
has created for the ActiveX control:
control.Width = CInt(comp.GetPropertyValue("Width", 0) / _
Microsoft.VisualBasic.Compatibility.VB6.TwipsPerPixelX)
control.Height = CInt(comp.GetPropertyValue("Height", 0) / _
Microsoft.VisualBasic.Compatibility.VB6.TwipsPerPixelY)
Troubleshooting
This section summarizes the most common causes for malfunctioning. Most of them
have been already described in depth elsewhere in this document.
The first thing to do if anything doesn’t work correctly
In some cases an ActiveX control simply can’t be translated to .NET and the
blame isn’t on AxWrapperGen. When you find out that anything isn’t working
as described in this document, the first thing to do is attempting to add a reference
to the ActiveX control from Visual Studio and – if successful – drop
an instance of the control onto a .NET form.
If any of these operations fail, then you just can’t create a .NET wrapper
class for that control using AxImp or AxWrapperGen. The only solution is to create
a wrapper class manually, by inheriting from a .NET control. (This approach is described
later in this document.)
More in general, whenever you see that the AxWrapperGen-generated wrapper class
doesn’t work as expected, your first attempt should be trying to use the same
feature on an ActiveX control that you manually dropped on a .NET form inside Visual
Studio. If you get the same or similar error, then the problem is likely to be in
Microsoft COM Interop technology.
VB Migration Partner doesn’t correctly convert the ActiveX control
There are a few common causes for this problem:
- If VB Migration Partner still fails to recognize the compiled DLL that contains
the wrapper class, odds are that the control name specified in the VB6Object attribute
doesn’t match the ActiveX control name as shown by VB6 object browser. (See
paragraph 1 in previous section.)
- If the names match, then the next likely cause for this problem is that you failed
to correctly deploy it in a place where VB Migration Partner can find it. For more
details, double-check the “Deploying the wrapper class” section.
- Another possible cause for this problem is that your application consists of multiple
project and they use different versions of the ActiveX control. Read this
KB article for more information.
- The path of the ActiveX control specified in the project .vbp file isn’t correct.
To ensure that the contents of the VBP file is up-to-date, please load the project
in the VB6 IDE and then save it again.
VB Migration Partner works errantly after deploying the wrapper class
This problem usually occurs if you deployed the wrapper class DLLs to VB Migration
Partner’s setup folder. Please notice that we recommend that you instead deploy
the DLLs to a folder pointed to by an AddLibraryPath pragma, as explained in the
“Deploying the wrapper class” section.
The ActiveX control is recognized but its design-time properties aren’t
initialized correctly
ActiveX controls store their properties in the code-behind section of a form –
that is, the *.Designer.vb file – like standard .NET controls, except they
use to pack all property values in a special binary property named OcxState.
During the conversion process, VB Migration Partner generates the value for the
OcxState property, so that the control can restore all its properties when it is
instantiated on a .NET form.
In some (rare) cases, VB Migration Partner fails to generate a correct value for
the OcxState property, often because the specific control uses a nonstandard mechanism
to store its properties. If you realize that the control’s design-time properties
aren’t initialized correctly or if the form fails to run correctly, you should
attempt to use Microsoft Upgrade Wizard (included in Visual Studio 2005 and 2008)
to migrate that form and generate a valid OcxState, which you can later reuse with
VB Migration Partner by means of the
ReuseResxFiles pragma.
For more information, please read this KB article:
[PRB] ActiveX controls aren’t migrated correctly.
The generated .NET form cannot be displayed in Visual Studio designer
There can be many causes for this issue. The most common ones are:
- VB Migration Partner has generated one or more properties in the *.Designer.vb that
shouldn't be there. For example, we have met this problem with a property named
LicenseKey, which refers to COM licensing mechanism that can’t be used from
inside .NET. Such spurious properties typically cause an exception when you display
the form at design-time or when you run the VB.NET project. In such cases you must
prevent VB Migration Partner from generating a property with that name, by means
of the IgnoreMembers property of the VB6Object attribute. (See paragraph 3 of previous
section.)
- The wrapper class exposes one or more overloaded properties. (See paragraph 8 of
previous section.)
- The name of a property used in the hidden portion of a VB6 .frm file differs from
the property name that appears in VB6 object browser. (See paragraph 4 of previous
section.)
- One or more transient properties should be marked with a DesignerSerializationVisibility
attribute but aren’t. (See paragraph 11 of previous section.)
The ActiveX control is migrated, but its size (or the size/position of its UI
elements) is incorrect, either at design-time or runtime
There can be two causes for this issue:
- The control exposes properties or methods that take values that are affected by
the parent form’s ScaleMode. In this case you should fix the wrapper class
as described in paragraph 10 of previous section.
- If the control’s size appear different at design-time – usually much
larger than it should – then you should edit the support class, as described
in paragraph 13 of previous section.
The code in client form contains compilation errors
The most common causes for this issue are:
- The name of a property used in the hidden portion of a VB6 .frm file differs from
the property name that appears in VB6 object browser. (See paragraph 4 of previous
section.)
- You changed the name of an event to prevent name collisions but forgot to edit the
TranslateEvent property of the VB6Object attribute. (See paragraph 7 of previous
section.)
- AxImp changed the name of one or more members of the ActiveX control (See paragraph
9 of previous section.)
The client application crashes with COM error 80040154
You are likely to run the client as a 64-bit application. Read this
KB article for more information.
Databinding doesn’t work
If the converted control appears to work correctly except that data-binding features
throw an exception or silently fail, odds are that you are using a control that
binds to a DAO or RDO Data control. These controls can’t be migrated correctly,
either by Upgrade Wizard or VB Migration Partner. (See note at the end of “Running
AxWrapperGen” section.)
The ActiveX control can’t be instantiated dynamically with the Controls.Add
method
Read paragraph 2 in “Fixing and polishing the wrapper class” section.
Inheriting from a .NET control
Once your wrapper class works well, you can use VB Migration Partner to convert
all the VB6 projects that use that ActiveX control. However, keep in mind that the
resulting .NET project will still depend on COM and ActiveX legacy technologies,
which is usually undesirable in the long run.
To get rid of this dependency you must edit the wrapper class so that it inherits
from a “pure” .NET control rather than the ActiveX control. This section
describes all the steps you must take to do the replacement.
1. Select the most suitable .NET control
The first thing to do is deciding which .NET control you want to use to replace
the legacy ActiveX control. In general, you should select a .NET control that has
all the features that you need (among those exposed by the ActiveX control) and,
preferably, a control that exposes an object model that is similar to the programming
interface of the ActiveX control.
If the vendor of the original ActiveX control is still in business and if it released
a .NET version of the original control, then the decision is obvious. This is the
case, for example, of the majority of the controls that were sold by companies such
as Sheridan (which changed its name into Infragistics), or Apex and VideoSoft (which
joined in the company named ComponentOne).
Many VB6 developers used 3rd-party ActiveX controls only because the corresponding
VB6 built-in control wasn’t powerful enough. This is the case, for example,
of the many “super textbox” controls offered by companies such as Crescent
and MicroHelp. Most of these ActiveX controls can be conveniently replaced by the
controls that are included in the System.Windows.Forms namespace, in which case
you don’t have to purchase any additional control.
2. Change the base class
Next you must edit the Inherits statement, so that the wrapper
class now inherits from the selected control. For example, let’s suppose that
you found out that the fictitious XGrid control can be replaced by the DataGridView
control. The Inherits clause therefore becomes
<VB6Object("XLib.XGrid", _
TypeLibrary:="XLib", TypeLibraryNamespace:="XLibCtrl", _
IgnoreMembers:="")> _
Public Class VB6XGrid
Inherits System.Windows.Forms.DataGridView
Also notice that you should delete the DependsOnAssemblies property of the VB6Object
attribute, to inform VB Migration Partner that this class doesn’t depend on
the ActiveX DLLs any longer.
3. Remark out non-essential members
As soon as you change the Inherits class, many compilation errors will appear in
the wrapper class when the MyBase keyword is used, because the
new .NET control has programming interface that differs from the ActiveX control.
You can temporarily solve all these compilation errors by remarking out all the
properties and methods of the wrapper class, except the ones listed below:
- The Name, Index, Parent, and
Container properties are part of the IVB6Control interface, which
must be exposed by all controls that are converted by VB Migration Partner. The
code that AxWrapperGen emits for these properties is correct, thus you don’t
need to remark them out.
- The Left, Top, Width, and Height properties are
always error-free and are mandatory, therefore you don’t need to remark them.
- The ZOrder, Move, and Focus methods
are always common to all controls and don’t need to be commented.
- The list of members that you can safely leave uncommented includes ToolTipText,
MousePointer, MouseIcon, Object,
and all FontXxxx properties.
4. Clean up the support class
Now that the ActiveX control inherits from a .NET control, the support class that
AxWrapperGen generated must be modified accordingly, by removing all references
to the ActiveX control. After cleaning up the support class should look like the
following code:
<VB6ControlSupport(GetType(VB6XGrid))> _
Public Class VB6XGrid_Support
Inherits VB6ObjectSupportBase
Public Overrides Function ParseProperties(ByVal objInfo As Object) As Object
On Error Resume Next
Dim info As ControlSupportInfo = DirectCast(objInfo, ControlSupportInfo)
Dim comp As VBComponent = info.Component
Return Nothing
End Function
Public Overrides Function GeneratePropertyCode(ByVal objInfo As Object) As String
Dim info As ControlSupportInfo = DirectCast(objInfo, ControlSupportInfo)
Return ""
End Function
End Class
NOTE: as modified in this example, the support class does
nothing and might be completely removed from the DLL that contains the wrapper class.
Don’t do that now, however, because you might later find out that some nonstandard
actions must be necessary during the migration process.
5. Determining the list of used members
An ActiveX control – especially complex controls such as grids and charting
controls – expose literally hundreds of properties, methods, and events. In
general you don’t want the wrapper class implement all of them, because it
would be a huge waste of time in most cases. The members that you really want to
implement are only those that your VB6 client projects actually use.
You therefore need to determine what are the members that you really need to implement.
There are at least three ways to obtain this list:
- You compile the wrapper class (with most members still remarked out – see
previous point), use VB Migration Partner to migrate the VB6 project(s) and take
note of all compilation errors caused by the members that the wrapper class should
expose but doesn’t.
- You use a code analysis tool to analyze the VB6 code and automatically create this
list for you. One of the best VB6 code analysis tool we know is
VBDepend, so you can download a demo version and check how well it can create
this list for you.
- You just browse the VB6 source code and manually take note of all the ActiveX members
that the wrapper class needs to implement. (This approach is ok only if the ActiveX
control is used sparingly.)
Now that you know which properties and methods the wrapper class must implement,
you can start uncommenting them, one at a time, while fixing the statement that
delegates the call to the inner base class. In completing this task you can face
many different situations, which are described in following numbered paragraphs.
6. Deleting members with same name, syntax, and behavior
In the simplest case, a member has exactly the same name, syntax, and behavior in
the ActiveX control and in the selected .NET control. For example, Boolean properties
named Enabled, Visible, and ReadOnly are likely to work in the same way in the two
controls:
Public Property Enabled() As Boolean
Get
Return MyBase.Enabled
End Get
Set(ByVal value As Boolean)
MyBase.Enabled = value
End Set
End Property
In this very favorable case, you can just delete the entire property or method,
because it will be correctly inherited from the base .NET control.
7. Implementing members with different name or syntax, but same behavior
In most cases, the .NET control implements the same functionality of the original
control by means of a property or method that has a different name and/or syntax,
but that behaves exactly like in the original ActiveX control.
For example, an ActiveX control that implements a “super TextBox” control
usually exposes the SelText, SelStart, and SelLength properties, which correspond
to the SelectedText, SelectionStart, and SelectionLength properties of the .NET
TextBox control. You can therefore uncomment the code for these properties and simply
replace the name of the property:
Public Class VB6SuperText
Inherits System.Windows.Forms.TextBox
Public Property SelText() As String
Get
Return MyBase.SelectedText
End Get
Set(ByVal value As String)
MyBase.SelectedText = value
End Set
End Property
In other cases the property has a slightly different syntax. For example, the VB6
RightToLeft property takes a Boolean value, whereas the .NET RightToLeft property
takes an enumerated value:
Public Shadows Property RightToLeft() As Boolean
Get
Return (MyBase.RightToLeft = System.Windows.Forms.RightToLeft.Yes)
End Get
Set(ByVal value As Boolean)
If value Then
MyBase.RightToLeft = System.Windows.Forms.RightToLeft.Yes)
Else
MyBase.RightToLeft = System.Windows.Forms.RightToLeft.No)
End If
End Set
End Property
8. Implementing events
The implementation of events greatly differs between similar VB6 and .NET controls.
For example, all .NET events take two arguments – the sender Object
reference and the e object, of type EventArgs or a type that inherits from
EventArgs. For example, this is the code that AxWrapperGen generates for the Click
event of most ActiveX controls:
Public Shadows Event Click As VB6EventHandler
Private Sub Control_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Control.Click
VB6EventDispatcher.Raise(Me, "Click")
End Sub
As you see, this code intercepts the Click event coming from the inner .NET control
and forwards it to all control’s clients that have registered this event.
This latter forwarding action must be performed by means
of the VB6EventDispatcher.Raise method, else the control might fire events at the
wrong time (for example, while the form is being loaded).
In most cases, these code sections can be uncommented and they will work correctly.
In other cases you might need to edit the code to account for event arguments (which
aren’t needed in this specific case).
9. Implementing remaining members
The cases discussed in paragraphs 6, 7, and 8 above account for the vast majority
of members, but not for all of them. In remaining cases you must perform the “mapping”
between the outer member (that has the original ActiveX name and syntax) and the
inner member (as exposed by the .NET base control).
There are so many cases that it is virtually impossible to describe how to handle
each of them. However, the key point is that the wrapper class mechanism is
powerful enough to allow you to account for things such as different member names,
syntax, and behavior. For example, you can alter the order in which events fire
or can even prevent an event from firing.
10. Re-deploy the wrapper class DLL
You can now compile the “.NET-only” wrapper class to a DLL and deploy
the DLL as you did with the old DLL, as described in the “Deploying the wrapper
class” section.
In most cases all migrated projects will work exactly as before. In some cases,
however, it might be necessary that you manually adjust projects references, to
ensure that they point to the new DLL.
In some rare circumstances you might also need to convert again the client project(s).
This is necessary, for example, if you modified the TranslateProperties, IgnoreMembers,
or TranslateMembers properties of the VB6Object attribute.
Conclusion
The wrapper class approach to ActiveX control conversion and replacement is powerful
enough to allow you to face even the most intricate situations.
Writing a bullet-proof wrapper class is simple, because most of the job is performed
by the AxWrapperGen utility, yet you must be prepared to handle some special cases
yourself. This document – especially its Troubleshooting section - should
be your first reference when something doesn’t work as expected.
If you meet an issue that isn’t covered by this article, please send us a
note: we will help you in migrating your specific control and will extend this document
so that other VB Migration Partner users can find a solution to similar problems.