Download as pdf or txt
Download as pdf or txt
You are on page 1of 79

“Addressing Changes to the Visual Basic Language from

VB6 to VB.NET, and how to deal with those changes.”

By David Ross Goben


Copyright © 2010-2011 by David Ross Goben
All rights reserved.
(Last updated May 21, 2011)
(The above two logos are registered trademarks of Microsoft Corporation)
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

TABLE OF CONTENTS
Introduction.................................................................................................................................................. 4
1. Dealing with structures passed to P/Invokes containing strings ................................................................ 7
2. Dealing with structures passed to P/Invokes not containing strings .......................................................... 7
3. Dealing with the loss of the VB6 BackStyle property ................................................................................ 7
4. Dealing with passing strings directly to P/Invokes .................................................................................... 8
5. Dealing with VarPtr, ObjPtr, StrPtr, VarPtrArray, and StrPtrArray ............................................................. 8
6. More on StrPtr......................................................................................................................................... 8
7. Dealing with passing strings ByRef to P/Invokes ...................................................................................... 9
8. Dealing with fixed-length strings............................................................................................................. 10
9. Dealing with fixed-length arrays ............................................................................................................. 13
10. Dealing with RichTextBox Property Renaming ....................................................................................... 14
11. Dealing with recovering the LenB() function ........................................................................................... 15
12. Dealing with passing parameters to P/Invokes “As Any”......................................................................... 16
13. Dealing with StrConv: converting between Unicode and ANSI strings .................................................... 17
14. Dealing with AddressOf/Missing Delegate issues................................................................................... 19
15. Dealing with Dir() function warnings....................................................................................................... 21
16. Dealing with Item issues in Collections .................................................................................................. 21
17. Dealing with late-bound Object references............................................................................................. 22
18. Dealing with VB6 parameterless defaults ............................................................................................... 22
19. Dealing with VB6 Null Propagation ........................................................................................................ 23
20. Dealing with referencing Objects before they are initialized .................................................................... 23
21. Dealing with TextChanged and Resize events firing before the Form Load event ................................... 23
22. Dealing with renamed properties............................................................................................................ 24
23. Dealing with the loss of the ListCount property....................................................................................... 24
24. Dealing with changed MousePointer warnings ....................................................................................... 24
25. Dealing with changes to RECT structures .............................................................................................. 25
26. Dealing with the loss of the Initialized and Terminate events .................................................................. 26
27. Dealing with changes to Enumeration references................................................................................... 27
28. Dealing with VB6 Namespace Twips conversions .................................................................................. 27
29. Dealing with user-defined Twips constants............................................................................................. 27
30. Speeding code by removing references to the VB6 Compatibility Library................................................ 28
31. Speeding returned VB6 Namespace List Item values............................................................................. 28
32. Dealing with changed Date/Time shortcut format options ....................................................................... 29
33. Dealing with Date value conversions...................................................................................................... 29
34. Speeding Format command use in VB.NET ........................................................................................... 30
35. Dealing with Screen properties .............................................................................................................. 30
36. Dealing with On Iexpr GOTO ................................................................................................................. 31
37. Dealing with On Iexpr GoSub................................................................................................................. 34
38. Dealing with updating VB6 error trapping ............................................................................................... 34
39. Dealing with destroying Objects............................................................................................................. 35
40. Dealing with changes to Common Dialogs ............................................................................................. 36
41. Dealing with VB6.CopyArray.................................................................................................................. 38
42. Dealing with the loss of the ItemData List Object property...................................................................... 39
43. Dealing with changes to Font manipulation ............................................................................................ 40
44. Dealing with changes to Form commands.............................................................................................. 44
45. Dealing with VB6’s automatic Boolean conversions ............................................................................... 46
46. Dealing with Option Strict On issues ...................................................................................................... 47
47. Dealing with Image and Picture Object upgrades ................................................................................... 48
48. Dealing with the loss of VB6 Control Lists and how to recover their functionality..................................... 49
49. Dealing with changes to MouseMove parameter list changes................................................................. 52
50. Dealing with changes to remotely firing Button clicks ............................................................................. 52
51. Dealing with no AVI animation control in VB.NET and how to easily add a free one................................ 52
52. Dealing with changes to Resources Management .................................................................................. 54
53. Dealing with the loss of the App statement............................................................................................. 57
54. Dealing with updating default ByRef and ByVal method parameters flags .............................................. 58

Page –2–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

55. Dealing with Collection and List clearing ................................................................................................ 58


56. Dealing with Adding Auxiliary Files to ClickOnce Deployment (Publish).................................................. 58
57. Dealing with changes to Text Box SelStart and SelLen Properties ......................................................... 59
58. Dealing with changes to ToolTips .......................................................................................................... 60
59. Dealing with changes to ListView........................................................................................................... 60
60. Dealing with changes to Toolbar Button and Button Menu Clicks ........................................................... 61
61. Dealing with Unload Form commands.................................................................................................... 63
62. Dealing with The Loss of the VB6 NewIndex Property............................................................................ 63
63. Dealing with Process Handling in the KeyPress, KeyDown, and KeyUp Events...................................... 64
64. Dealing With Invoking Handled Events Under VB.NET........................................................................... 65
65. Dealing With TextBox Locked Property Changes ................................................................................... 67
66. Dealing With changes to the Tag Property ............................................................................................. 67
67. Dealing With Changes to the GotFocus and LostFocus Events.............................................................. 67
68. Dealing With Long-Pathing Through Namespaces ................................................................................. 68
69. Dealing With Changes to the VB6 SetFocus Command ......................................................................... 68
70. Dealing With Changes to Multiple Document Interfaces ......................................................................... 68
71. Dealing With Changes to a Button’s Cancel and Default Properties ....................................................... 67
72. Dealing With Changes to CheckBoxes................................................................................................... 69
73. Dealing With Property Conflicts With VB Commands ............................................................................. 69
74. Dealing With using Icons for Menu Images (Bitmaps) under VB.NET ..................................................... 69
75. Dealing With Converting VB6 Fast FSO File I/O to Faster VB.NET Streaming Methods ......................... 70
76. Dealing With Changes to Counting CheckBoxed ListBoxes.................................................................... 70
77. Dealing With Changes to Mouse Pointer Icons....................................................................................... 71
78. Dealing With Specific Changes to the KeyDown and KeyPress Events .................................................. 71
79. Dealing With Changes to Drag and Drop ............................................................................................... 73
80. Dealing With the Loss of MAPI Controls................................................................................................. 76
Closing Remarks ....................................................................................................................................... 77
About the Author ....................................................................................................................................... 78

Page –3–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

Introduction
When a Visual Basic 6.0 (VB6) application is upgraded to Visual Basic .NET (VB.NET; to at least
VB2008) using the Visual Basic Upgrade Wizard, chances are that, once the upgraded application comes
up in Visual Studio or Visual Basic Express, its Task List will present you with a number of, or typically
quite a number of alerts, upgrade issues, to-dos, warnings, notes, global warnings, run-time warnings,
and design issues. DO NOT PANIC! It is not as bad as you might at first think it is. Most of them are
notices, and are only that: Notices. And virtually all of these added tagged comments can be safely
reviewed, ignored, and then deleted without fanfare. Of those that actually do require attention; most can
be solved in very short order using quick and easy edits. The numbered solution points provided in this
document will solve almost every one of your upgrade woes. These solutions will work when you are
upgrading to either the full Visual Studio.NET editions, or to the Visual Basic .NET Express Editions.
But before diving into those solutions, you should also understand exactly why all these upgrade issues exist
in the first place; why the current generation of Visual Basic has just a little bit of trouble upgrading from the
previous generation (VB6). It is not, as many detractors will most vehemently (but without any merit)
contrarily shout, because VB is now somehow broken (from my own personal point of view, I believe that it
is the other way around – VB6 was a patchwork of hacks, fixes, and upgrades that did not seem to always
follow uniform standards of syntax). But the best answer is that it is because VB.NET is now what those VB6
programmers desperately wished for VB to be (so be careful what you wish for).
When the majority of VB6 developers demanded the ability to build VB code in a common language IDE
(Integrated Development Environment), have 100% unfettered cross-language interoperability, and expecting
no less than 100% unrestricted object oriented programming language capabilities, most of them did not
have the first clue, not the slightest understanding, of the earth-shattering impact their passionate, relentless,
spittle-laced howling would have on their beloved VB. An immense host of them, mostly amateur
programmers and hobbyists, naively believed that after such a necessarily monumental undertaking, they
could simply continue on their merry little way, writing VB code exactly as they had done before, using the
very same often non-uniform syntax that they had been using before, and, oh yes, they expected there would
be a few additional commands here and there to address full class inheritance, and also allow seamless access
to methods whose source code was written in some other programming language, such as C++. They did not
grok the fact that in order to provide them with exactly what they were keenly expecting would also clearly
necessitate colossal changes to their beloved VB so that it would be a fully integrated, object oriented
environment that would also interoperate with and act exactly like the other Visual Studio languages (or other
.NET-compliant languages). This requires perfect synchronicity between all those languages; that each of
them could clearly understand and use objects from each other without the slightest misconstruction.
Though most VB6 users feel they are alone in this upgrade quandary, they most certainly are not. They have
it easy compared to some language upgrades. Try translating between C (C89; K&R C) and C99, or C++85
and C++95, or better, between upgrades of the very first high level language, FORTRAN (Formula
Translation), between FORTRAN57 and FOTRAN77, or even FORTRAN95. Upgrades bite everyone.
Regardless of whether you write something in VB.NET, C#, C++, Delphi.NET, Google Chrome, or
even IL (Intermediate Language – Microsoft’s brilliant cross-platform assembly language), their syntax
must always sync up perfectly. In fact, code written in one language should be instantly translatable into
one of the other languages based solely on the compiled IL instructions from any of those languages.
For example, suppose you wrote the following VB.NET code within Form1 so that you could emulate
the old VB6 command “ZOrder(Me, 0)”, to bring your form to the front of the window display stack:
Public Sub ZOrder(ByVal frm As Form, ByVal Position As Integer)
If (Position = 0) Then
frm.BringToFront()'use VB.NET function to bring the form to the top of the z-order
Else
frm.SendToBack() 'use VB.NET function send the form to the back of the z-order
End If
End Sub

Page –4–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

This code is compiled into Microsoft Intermediate Language (MIL) as the following:
method public instance void ZOrder(class [System.Windows.Forms]System.Windows.Forms.Form frm, int32 Position) cil
managed
{
.maxstack 8
L_0000: ldarg.2
L_0001: ldc.i4.0
L_0002: bne.un.s L_000c
L_0004: ldarg.1
L_0005: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::BringToFront()
L_000a: br.s L_0012
L_000c: ldarg.1
L_000d: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::SendToBack()
L_0012: ret
}

The above machine independent language can then be immediately translated into C#:
public void ZOrder(Form frm, int Position)
{
if (Position == 0)
{
frm.BringToFront();
}
else
{
frm.SendToBack();
}
}

Or into C++:
public: void __gc* ZOrder(Form __gc* frm, Int32 __gc* Position)
{
if (Position == 0)
{
frm->BringToFront();
}
else
{
frm->SendToBack();
}
}

Or into Delphi.NET:
procedure Form1.ZOrder(frm: Form; Position: Integer);
begin
if (Position = 0) then
frm.BringToFront
else
frm.SendToBack
end;

Or into Google Chrome:


method Form1.ZOrder(frm: Form; Position: Int32);
begin
if (Position = 0) then
frm.BringToFront
else
frm.SendToBack
end;

Or into any other .NET-compliant language.


But to achieve such a fluid transition, one cannot hide a lot of code. So much of the code that was
previously hidden away from VB6 developers had to be brought out into the open. The fact that it had
been hidden in the first place was an attempt to simplify the language to fully empower it as a RAD
(Rapid Application Development) platform, which that end most certainly achieved, but it was also one
of the main reasons why many C++ programmers looked down their noses at VB6, chiding it as a tinker
toy language, because the total breadth, the total power, and the total unreserved system control that VB
developers could otherwise wholly exploit was withheld from their grasp, which in my view severely
handicapped VB developmental capabilities, limiting them far beyond that which most might venture to
imagine, and, as Microsoft later agreed, this tactic also severely inhibited VB’s RAD potential.

Page –5–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

Having said that, am I a fan of VB6? Yes, I am. But will I continue to use it? I doubt it, except for
maintenance purposes, to support my customer base who have invested heavily in VB6 software and either
have no desire to upgrade, or do not currently wish to incur the expense or can afford the time of an upgrade.
But I like too much the colossal power and the magnificent freedom I enjoy while using VB.NET to want to
turn back to VB6. In fact, I have become so comfortable in VB.NET that I may now have trouble writing
original code in VB6 again, mainly because of the conflicting syntax between controls and commands that
VB6 permitted, or even required, is more complicated than the smoothly consistent syntax of VB.NET.
I have always stressed the point that, because VB6 was a serious RAD platform, a developer could write in
two hours what would take two weeks to match it in robustness using C++. I stand firm that this declaration
is now even more true of VB.NET (this explains why I have finally made a full migration from C++). By the
time VB2005 came out, VB.NET was clearly coming into its own as the new RAD platform of choice. The
fact that its free version, VB2005 Express, was a full VB.NET compiler, with its only limitations being that it
did not support direct interoperability with other .NET languages (which most developers do not bother with,
anyway), it was missing a few professional-level templates, and it lacked support of a number of other
features made available only to the full Visual Studio environment, such as the free Code Rush! Express
editor enhancements (see www.devexpress.com/Products/Visual_Studio_Add-in/CodeRushX/). It also
featured much stronger compatibility to VB6 code than VB2003 offered. VB2008 and VB2010 exhibit far
greater compatibility to VB6, and they are in fact the only VB.NET platforms that I will recommend to VB6
users who have yet to migrate to the .NET domain. Like VB2005, free VB2008 and VB2010 Express editions
are also available (see www.microsoft.com/express/download), plus Microsoft has also provided a number of
excellent, previously pricey books regarding VB6 to VB.NET migration on hand for free download (see the
end of this document for details and their web links).
On top of that, the many numbered points listed in this document will make addressing pesky warnings
and upgrade issues a snap to deal with. Not only will these points help you address those issues, but they
will also hopefully give you a glimpse into the VB.NET approach to application development, and with
any luck I will also be able to convey my excitement for this extremely potent and much more
empowering rendition of Visual Basic, which is now a true force to be reckoned with; it finally being
just as capable, just as powerful, but can be developed much faster and can be executed just as fast as the
other contenders in the development world, such as VC++.
The lists of notes in this document were initially compiled from personal references I had jotted down as I
made my own migration over to VB.NET from VB6. It started out as a 6-page hodge-podge of terse cuff
notes, but continued to grow as I learned new and better ways of adapting VB6 code to the .NET
environment, first to VB2002 (a painful experience), then VB2003 (a little less painful), VB2005 (a rather
pleasant upgrade), VB2008 (a very easy upgrade), and now VB2010. Prior to the release of VB2005,
transitioning VB6 code to the .NET platform was in most cases, except for the simplest of applications, a
dreadfully rocky path, fraught with aggravation and dead ends that often required major re-writing for
segments of code. When VB2005 arrived on the scene, I noticed that massive pages of personal upgrade
notes were now no longer required, and so they were duly discarded. When VB2008 was released, far greater
amounts of my upgrade notes were no longer relevant. Indeed, VB2008 is probably the very first edition of
VB.NET that I would even consider recommending to people who were thinking about making the transition
over from VB6. VB2005 and VB2008 were focused strongly on the language itself and upon VB6
compatibility. VB2010 is geared mostly toward ADO.NET, web development, and business transactions.
With just a little more tweaking, and I think these notes will shrink back to their original 6 pages, if not
eliminate their need entirely. We can only dream.
And now: on to those solutions…
VERY IMPORTANT NOTICE: Every solution involving GCHandles, embracing angle brackets denoting Attributes “<>”, or have
Marshaling instructions will simply assume that you will have also placed the following line in the heading of any file they are involved in;
below any Option statements, if any, but before the Class or Module declaration:
Imports System.Runtime.InteropServices

Page –6–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

1. Dealing with structures passed to P/Invokes containing strings


If a Structure (a VB6 User-Defined Type) is passed to a P/Invoke (a Processor Invocation; in VB6
parlance – an API Call) and it contains at least one string member that is expected to be a string of 8-bit
ANSI Chars (as are most string-handling Win32 P/Invokes), always be sure to marshal the structure
declaration by preceding it with the following special attribute: “<StructLayout(LayoutKind.Sequential,
CharSet:=CharSet.Auto)>”. This ensures that all structure members are stored sequentially in memory in
the exact order declared, Unicode/ANSI string conversion will be automatic between the application and
the invoked code, and that all is packed tightly together; duplicating exactly what was done by default
under VB6 (most standard Win32 DLLs process 8-bit ANSI text, but VB, both VB6 and VB.NET and
newer Win32 DLLs, use 16-bit Unicode text). This attribute may not always be needed, but it is better to
have it and not need it, than need it and not have it. For example:
'***************************************************************
' structure for system version information (Win32 P/INVOKE)
'***************************************************************
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
Private Structure OSVERSIONINFO
Dim dwOSVersionInfoSize As Integer
Dim dwMajorVersion As Integer
Dim dwMinorVersion As Integer
Dim dwBuildNumber As Integer
Dim dwPlatformId As Integer
<VBFixedString(128)> Dim szCSDVersion As String 'Fixed maintenance string (128 8-bit characters)
End Structure

NOTE: You will want to change the ‘Auto’ CharSet to ‘Unicode’ if the P/Invoke instead expects 16-bit Unicode text.

2. Dealing with structures passed to P/Invokes not containing strings


If a Structure is passed to a P/Invoke but it does not contain strings, or if the P/Invoke will process
those strings as 16-bit Unicode, you can instead precede the structure declaration with the following
attribute, even if it is not really needed (though I usually still use the previous string template, even if
I have to set Charset:=Charset.Unicode, because it would then become more of a habit):
<StructLayout(LayoutKind.Sequential)>

3. Dealing with the loss of the VB6 BackStyle property


One upgrade warning that may frustrate a few users is reported when a control’s Backstyle is being
modified. Consider the following upgraded VB6 code:
frmMain.lblLoc.BackColor = Color.White 'set label background color to White
'UPGRADE_ISSUE: Label property lblLoc.BackStyle is not supported at runtime. Click for more: BLAH-BLAH-BLAH
frmMain.lblLoc.BackStyle = 1 'set label BackStyle to opaque in order to show the color White

Under VB6, the BackStyle was by default set to 0 (Transparent). If set to 1, its background was set to
Opaque, which allowed the color set in the control’s BackColor property to be displayed. Under
VB.NET, these two properties were combined; it was such a waste of resources to have a BackColor
property that was in most cases unused. This added to an overall heavier code overhead. Therefore, the
BackColor property, when set to a color, determined the background color of the object, and logically
forced the back style to be opaque. By setting the BackColor to the new color value Color.Transparent,
the background and back style would be logically rendered transparent.
To correct the above code, we simply delete the line setting BackStyle, because by setting the
BackColor property under VB.NET to a non-Transparent color, we have in fact set it to opaque. If,
however, you want to set it to transparent, simply set “frmMain.lblLoc.BackColor = Color.Transparent”,
though you would still delete the BackStyle setting line: “frmMain.lblLoc.BackStyle = 0”.
NOTE: Even with the BackColor set to Transparent, its background assumes the color of its parent; the form (its default parent). To
make it transparent to a control beneath it, at runtime you must set the object’s Parent to the control it is over, and adjust its Left and
Top properties to those relative to its new parent. For example, on a form you have a label (Label1) and a PictureBox (PictureBox1).
To display the label over the PictureBox with the background transparent on the image, in the Form_Load event, enter this:
'Set Label1 over Picture1, relative to their current positioning
With Me.Label1
.Parent = Me.PictureBox1 'set Label1 parent to Picture1
.BackColor = Color.Transparent 'set Label1 background color to transparent
.Left = .Left - .Parent.Left 'adjust to relative coordinates of new parent
.Top = .Top - .Parent.Top
End With

Page –7–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

4. Dealing with passing strings directly to P/Invokes


If a string is passed as a parameter to a P/Invoke, you should usually be sure to also include the Auto
verb after Declare in the P/Invoke definition statement if the P/Invoke will be expecting to process 8-bit
ANSI text, especially in declarations that to do not have an Alias modifier. These take the forms “Declare
Auto Function” and “Declare Auto Sub”. The additional “Auto” verb will tell VB.NET to automatically
convert its string parameter(s) to/from Unicode/ANSI when being passed to/from P/Invoke procedures.
However, if the P/Invoke will also specify an Alias that will explicitly declare the method to in fact
invoke, then do not specify Auto after Declare. The reason for this is that .NET would then actually
double-convert; once to address the Auto specification, and a second time for the direct specification
of the method, which the .NET compiler will know will be either ASCII or Unicode. For example,
consider the following declarations for GetTempPath(), which will return the directory path to the
system’s TEMP folder. The following signature using the Alias verb is valid:
Private Declare Function GetTempPath Lib "kernel32" Alias "GetTempPathA" _
(ByVal nBufferLength As Integer, _
ByVal lpBuffer As String) As Integer 'Valid using Alias

This alternative declaration using the Auto verb (and no Alias) is also valid:
Private Declare Auto Function GetTempPath Lib "kernel32" _
(ByVal nBufferLength As Integer, _
ByVal lpBuffer As String) As Integer 'Valid using Auto (or Unicode or ANSI)

But this declaration, specifying both Auto and Alias, is not, returning non-recognizable text:
Private Declare Auto Function GetTempPath Lib "kernel32" Alias "GetTempPathA" _
(ByVal nBufferLength As Integer, _
ByVal lpBuffer As String) As Integer 'This will generate invalid text data

NOTE: The execution of this erroneous code will not blow up or crash the computer, but your text will look as though it
had gone through a kitchen blender set on Liquefy.

5. Dealing with VarPtr, ObjPtr, StrPtr, VarPtrArray, and VarPtrStrArray


If your upgraded VB6 code requires the undocumented VB6 functions VarPtr, ObjPtr, StrPtr,
VarPtrArray, or VarPtrStrArray, use these VB.NET functions to replace and duplicate them:
Imports System.Runtime.InteropServices
'******************************************************************************
' This Module Provides the addresses of Numeric Variables, Objects, and Strings
'******************************************************************************
Module modVarPtr
'VB.NET version of VB6 VarPtr 'also works for VarPtrArray
Public Function VarPtr(ByVal o As Object) As Integer 'use Object as a 'catch all' universal data type
Dim GC As GCHandle = GCHandle.Alloc(o, GCHandleType.Pinned) 'get a trackable handle and pin the object address
VarPtr = GC.AddrOfPinnedObject.ToInt32 'get (and return) the memory address of the pinned object
GC.Free() 'free the allocated space used
End Function

'VB.NET version of VB6 ObjPtr


Public Function ObjPtr(ByVal o As Object) As Integer
Return VarPtr(o) 'use VarPtr -- it will return the address of the VB.NET object
End Function

'VB.NET version of VB6 StrPtr 'also works for VarPtrStrArray


Public Function StrPtr(ByVal o As String) As Integer
Return VarPtr(o) 'use VarPtr -- it will return the address of the VB.NET string
End Function
End Module

6. More on StrPtr
If your VB6 code used the StrPtr() function, be aware that strings are no longer stored in BSTR
format, and if one passes a string ByVal to a function (this is true in both VB6 and VB.NET), you are
performing the exact same function as StrPtr(). Therefore, in most cases the StrPtr() function is not
actually needed in VB.NET, just as under VB6. However, if you do need to pin down and pass or
manipulate the actual address of the string, then of course refer back to the previous point.
NOTE: This addition feature of ByVal was introduced in VB2005 in order to enhance compatibility to VB6 and simplify
conversion from VB6. Previous to this version, under VB.NET you had to pass a string ByRef and include a special
VBByRefStr attribute, which will be discussed in the next point.

Page –8–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

7. Dealing with passing strings ByRef to P/Invokes


If a string has to be passed ByRef to an interop P/Invoke under VB.NET, because the invoked
process will need to alter that text string or assign a string of text to it, then you must marshal it with
a special prefix. For example, if the VB6 API parameter ByRef (or ByVal) declaration was:
ByRef myString As String 'Note that, under VB6, this is the VERY SAME as using: ByVal myString As String

Upgrade it so that a string ByRef actually works for a P/Invoke in VB.NET like this:
<MarshalAs(UnmanagedType.VBByRefStr)> ByRef myString As String

NOTE: Passing strings ByRef, as a rule, does pass them By Reference, such as when passed to a VB.NET method, but
an interop P/Invoke is a special exception to ByRef processing, because it passes string data to an unmanaged memory
method, which VB.NET endeavors to protect itself from, and so an additional attribute is required to allow it.
Consider the following VB.NET example using a typical ByRef string:
'this function returns with lpBuffer containing the System directory and the function result is set to the string length
Declare Auto Function GetSystemDirectory Lib "kernel32" ( _
<MarshalAs(UnmanagedType.VBByRefStr)> ByRef lpBuffer As String, _
ByVal nSize As Integer) As Integer
...
Dim Sd As String = New String(Chr(0), BufSize) 'init string as a receiving buffer to get API return data
Dim I As Integer = GetSystemDirectory(Sd, BufSize) 'now get system directory to String and its length to I
Dim S As String = Left(Sd, I) 'aquire returned text as a string in variable S

In the above example, we marshaled a ByRef string using the VBByRefStr attribute.
Using A StringBuilder
As an alternative to using a ByRef string, you can instead use a StringBuilder, an object introduced
in VB2008 that is one of the fastest (200 times faster than standard string manipulation), most
powerful, and most amazing string manipulation objects you may never have even heard of. A
StringBuilder is a class defined in the System.Text namespace. One of the great thing about it, other
than awfully fast string manipulation, is that you can pass it to a P/Invoke ByVal and it will be
handled as a string. Even though sent ByVal, its member string will still receive data.
A native VB.NET StringBuilder does not require any special marshaling tags, which internet gurus
typically recommend, as shown below:
'this function returns with lpBuffer containing the System directory and the function result is set to the string length
Declare Auto Function GetSystemDirectory Lib "kernel32" ( _
ByVal lpBuffer As System.Text.StringBuilder, _
ByVal nSize As Integer) As Integer 'note the ByVal for StringBuilder. An exception results if sent ByRef
...
Dim Sd As New System.Text.StringBuilder(nSize) 'use a StringBuilder as a receiving buffer to get API return data
GetSystemDirectory(Sd, Sd.Capacity) 'get system dir to StringBuilder and its length (result no longer needed)
Dim S As String = Sd.ToString 'aquire returned text as a string in variable S

NOTE: This may seem to go against logic, but we must pass our StringBuilder ByVal to the P/Invoke, not ByRef, even
though it will receive data. The reason for this is that the Common Language Runtime (CLR) has special knowledge of
StringBuilder objects. Passing them ByVal will in fact pass a pointer to its internal string buffer. Were we to pass a
StringBuilder ByRef, it would actually be treated as passing a pointer to the pointer, because we are passing a pointer
(ByRef), and the CLR will in turn pass a pointer to it, ultimately resulting in an exception error being generated.
Using A ByVal String
A substitute to using a StringBuilder or a ByRef String is to actually use a ByVal String, as you may
have surmised from reading Point 4 (but mind you that this additional approach has only been valid since
the release of VB2005, and so it would have been flagged as an exception error under VB2002 and
VB2003). As indicated under Point 6, sending a string ByVal will in fact pass a 32-bit Integer pointer to
its base address. This is also what would happen under VB6 (passed as what it would call a 32-bit Long).
When VB.NET passes a 16-bit Unicode string to an 8-bit ANSI string and it knows that it must convert
between the string formats (either informed through the Auto modifier verb or by specifying a P/Invoke
name that VB.NET recognizes as using ANSI strings; either through an Alias or by the declared method
name, which .NET keeps track of in an internal reference library), it will set aside additional temporary
space for the converted string, pass the address of that string to the method, and then translate the ANSI
string (modified or not) back to the original Unicode string variable. Interestingly enough, this is also the
same process that passing strings ByRef and using the marshalling tags performed, as described earlier.

Page –9–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

For example, consider the following working declaration for the above GetSystemDirecty() P/Invoke
that will pass a string ByVal , but can still receive valid string data:
Declare Auto Function GetSystemDirectory Lib "Kernel32" ( _
ByVal Path As String, _
ByVal nSize As Integer) As Integer
...
'*************************************************
' GetSystemDir(): get SYSTEM directory
'*************************************************
Public Function GetSystemDir() As String
Dim sd As New String(ChrW(0), nSize) 'set aside space for string
Dim I As Integer = GetSystemDirectory(sd, nSize) 'now get system directory to string and its length to I
Return Left(sd,I) 'return system directory path
End Function

Please note that the above GetSystemDirectory() P/Invoke could have been alternately declared as:
Declare Function GetSystemDirectory Lib "Kernel32" Alias "GetSystemDirectoryA" ( _
ByVal Path As String, _
ByVal length As Integer) As Integer

Or in native .NET format:


<System.Runtime.InteropServices.DllImport("kernel32.dll", CharSet:=System.Runtime.InteropServices.CharSet.Auto)> _
Public Function GetSystemDirectory(ByVal Path As String, ByVal length As Integer) As Integer
End Function

8. Dealing with fixed-length strings


If the VB6 code uses fixed-length strings, it will be upgraded in the context of their use. Specifically,
a Field or a Local Variable will be upgraded differently than a Member Variable of a Structure or
Class. By default, the Upgrade Wizard will take the following VB6 Local Variable or Field:
Dim FixedLengthTest As String * 128 'Test Code

And upgrade it for VB.NET use like this:


Dim FixedLengthTest As New VB6.FixedLengthString(128) 'Test Code

The greatest advantage of this is that everything is done for you. No mess. No fuss. The disadvantage of
this is that it is more complex than we actually need it to be. Remember, VB.NET does not actually
support fixed-length strings. Though we used a FixedLengthString() method, the string is still just a
VB.NET dynamic string (it is simply a protected string, preventing accidental alteration of its size).
Alternatively, we can easily 1) dramatically reduce code overhead, 2) gain a more string-test-friendly
object, 3) make the program code execute faster, and 4) you will not have to resort to using the object’s
ToString() method just to obtain its actual string text. And that is to, in its place, simply do this:
Dim FixedLengthTest As New String(ChrW(0), 128) 'Test Code – ChrW() accepts 16-bit UShort-range values (You can also just use Chr())

Fixed-Length String Members of Structures and Classes


AN upgrade acts differently with string members of Structures and Classes. Consider this string member
of a VB6 UDT (User Defined Type) that will be passed to a P/Invoke (note also that it shall be assumed
that the VB.NET declaration for the containing structure will stipulate a Charset.Auto Attribute (see
point 1)):
Dim szCSDVersion As String * 128 'Fixed-length maintenance version string

You may notice that the VB.NET upgrade to the above VB6 code will have been rewritten into
something like the following Char array (the following is actually a single line, but you can trim off the
highlighted command path qualifiers if you also imported “system.Runtime.InteropServices”):
<VBFixedString(128),System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray,SizeConst:=128)>P
ublic szCSDVersion() As Char 'Fixed-length maintenance version string

You can simplify it much further, while still provide the very same functionality and ignoring the above
conversion from a string to a fully compatible array of type Char (I want my reviewers to specifically
know that we are dealing with text here; not with array elements), by instead defining it as follows:
<VBFixedString(128)> Public szCSDVersion As String 'Fixed-length maintenance version string

Or, employing a simpler version of the above upgraded Char array, you can also use the following:
<VBFixedString(128)>Public szCSDVersion() As Char 'Fixed-length maintenance version string

Page –10–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

What is really important here is that if we read the documentation for VBFixedString, it states: “The
VBFixedStringAttribute is informational and cannot be used to convert a variable length string to a fixed string. The
purpose of this attribute is to modify how strings in structures and non-local variables are used by methods or API calls
that recognize the VBFixedStringAttribute. Keep in mind that this attribute does not change the actual length of the
string itself. The VBFixedStringAttribute specifies the length of a string in bytes, not characters.”
What does this mean to us? Mainly, it means that the VBFixedString attribute is 1) Informational and 2)
It does not allocate space. If we are new to this sort of thing and had just read the above documentation,
it may make us pause to wonder if we will need to afterward manually allocate real space for this string
after we have instantiated a copy of the structure. The quick, fast answer to that is: No, we will not. We
must understand that when the term “allocate” is used, it actually refers to initialized space. The fact is,
VB.NET will set aside space for each fixed-length string member of a structure (by proxy, essentially,
because the next pointer will point beyond where that “unallocated” string data will be located). It would
have been better to qualify that by saying that space for the string will be set aside, though it will not be
initialized space. VB.NET has all the information it needs to compute these string sizes, making any
additional allocation unnecessary, except in the rare cases when we will be required to pre-fill the string
with some sort of default text data. But as it is, VB.NET will initialize it to an empty string. Hence, we
never have to do any post-allocation, allowing us to process it exactly as we had done it under VB6.
On the other hand, if you would feel more comfortable initializing them, even though it is not necessary,
here are four very good options:
I. The first and quickest best option is to simply assign the structure to a reference variable using the New
keyword, such as “Dim Struct1 As New MYSTRUCT”. The New modifier verb will force all scalar members
(numeric fields) to be initialized to zero, and all others, such as strings, to Nothing.
II. The next option is to assign the structure to a reference variable and then initialize its string member(s).
For example, were the string variable a member of the MYSTRUCT Structure (see the structure
declaration in sub-point IV, below), then we could perform our declaration and initialization in two
lines:
Dim Struct1 As New MYSTRUCT 'declare structure variable (NEW required due to next line)
Struct1.szText = New String(Chr(0), 128) 'initialize new blank buffer (16-bit Chars will become 8-bit)

III. If you want to initialize multiple members after assigning a reference variable to your structure, you
should consider writing a method that you would be able to pass the structure variable you just declared
to, and initialize it that way. Send it ByRef, so that the members of the structure can be accessed and
updated. Otherwise, if passed ByVal, only a copy of the data will be passed, leaving the original data
unchanged. Consider this code:
'initialize fixed strings in MYSTRUCT structure after the MYSTRUCT reference is declared
Private Sub InitStructStrVar(ByRef stVar As MYSTRUCT)
With stVar
.szText = New String(Chr(0), 128)
'place other initializations here...
End With
End Sub

IV. Lastly, you may want to consider a solution based on the above method, but merging it with the
solution that the Upgrade Wizard provides for the VBFixedArray attribute (discussed in the next point,
below), and that is to take advantage of the fact that a VB.NET Structure is, in Object Oriented
Programming terms, an Abstract Class1. An Abstract Class can be seen as a class with exceptions.
Significantly, because it is a class type, it can contain methods and properties, in addition to data fields.

1
An Abstract Class would be used to declare such objects as scalars (value-type variables), and, of course, structures. Unlike the classes
that you are likely familiar with from VB6, an Abstract Class can only be assigned to one variable that can reference its actual data. When
assigning an Abstract Class to another variable, it passes a copy of itself, giving the target variable a replica of the original (much like
using the Clone() method, discussed much later in Point 41). This is juxtaposed to a Concrete Class, which is the standard class type in
VB.NET that can have instances instantiated, and a single instance can be pointed to by multiple reference variables (copying a reference
variable to another reference variable would simply copy the referenced address in the absence of the Clone method).
Page –11–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

What all of this boils down to is that after all our member variables are declared in our
structure; we can also adjoin structure-embedded methods. I typically name an imbedded
helper method Initialize(), for consistency. Consider the following:
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
Public Structure MYSTRUCT
Dim dwInfoSize As Integer
Dim dwEntryId As Integer
<VBFixedString(128)> Dim szText As String 'Fixed reference string
'
' Initialize the 'fixed-length' string to the proper size
'
Public Sub Initialize()
szText = New String(Chr(0), 128)
'place other initializations here...
End Sub
End Structure

With the above, you can declare and initialize a new copy of the structure with these two lines:
Dim Struct1 As New MYSTRUCT 'The New keyword also makes sure that member variables are in default states;
Struct1.Initialize() ' meaning scalars are 0, and all else, such as strings, are set to Nothing

Do not panic if you must also provide this structure to a P/Invoke. Since a Structure is passed
ByRef to a P/Invoke (or to one’s own methods), the P/Invoke process will pass a pointer to its
data in the same old way it had done so reliably under VB6. Further, internally, data and
program code are also stored unconnectedly in separate system memory locations, so there is
no danger of this program code being passed right along with the data fields.
As an aside, if you were to take a look at the length of the structure declared in Option IV, above,
such as by using “Len(OSver)” (see Point 11 regarding VB6 and its LenB() method, which would
have been used here, instead of Len()), you will find that the returned length would report 136
(bytes). This is the size of 2 Integer variables (32-bit/4-byte), allocating 8 bytes, plus the length of
the string, which is 128 bytes long, yielding a total of 136 bytes.
Notice that I stated that the length of the string was 128 bytes. This is something very important to
remember! It is because most Win32 P/Invokes use 8-bit ANSI characters instead of VB’s 16-bit
Unicode characters. Even more, the documentation for the GetVersionEx P/Invoke I extracted this
example from is actually the GetVersionExA (ANSI) version (as declared in its Alias modifier).
Further, if you re-read the previously-stated documentation, the very last line states: “The
VBFixedStringAttribute specifies the length of a string in bytes, not characters.” Had I been using the Unicode
(Wide) version of the Kernel32.DLL function, GetVersionExW, I would have had to have stated
256 (bytes) as the designated width of the VBFixedArray attribute, in order to fully accommodate
128 16-bit Unicode characters. However, even so, do not be wracked with dread because we had
initialized our string to just 128 bytes, yet we, within our code, may be, by proxy, anyway,
associating it with a string of 128 Unicode characters. This is because the CharSet.Auto property
will tell the VB.NET interop marshaler to automatically handle the Unicode/ANSI conversion for
us, providing the required 128 8-bit ANSI characters needed during interop from the structure by the
P/Invoke.
This material seemed complicated the first time I encountered it, but it quickly became quite simple.
My personal solution is to only apply special attributes to the body of the structure when I will be
performing interop processes with it, which is to say that I will be invoking methods declared in
unmanaged or non-.NET modules. Also, I prepend any structure strings that must be of a fixed size
with “<VBFixedString(xxx)>”, specifying the number of bytes the string will occupy, or doubling it if I
am dealing with Unicode, and I typically declare structure variables using the “New” verb so that I
will know that its data fields will be initialized. When specifying fixed-size arrays in my structures, I
must also perform the additional easy step of adding a simple initialization method to my structure,
which is outlined in the following point.

Page –12–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

9. Dealing with fixed-length arrays


If a Structure requires at least one fixed-length array (VB.NET only allows un-initialized dynamic
arrays to be declared within a Structure), then an upgrade will modify the structure to define an un-
initialized dynamic array in place of the fixed-size array, but unlike Point 8, above, to address this
discrepancy, the upgrade will also provide a structure-embedded Initialize() method. It will further
inform you in an “UPGRADE TODO” comment that you must invoke the Initialize() method after
declaring the structure reference (duh!). This method is meant to be used to ensure that the array can
be initially set to its mandatory startup dimensions after creation, easily. Consider the following
upgraded code segment of a VB6 LOGFONT structure:
Structure LOGFONT
Dim lfHeight As Integer
Dim lfWidth As Integer
Dim lfEscapement As Integer
Dim lfOrientation As Integer
Dim lfWeight As Integer
Dim lfItalic As Byte
Dim lfUnderline As Byte
Dim lfStrikeOut As Byte
Dim lfCharSet As Byte
Dim lfOutPrecision As Byte
Dim lfClipPrecision As Byte
Dim lfQuality As Byte
Dim lfPitchAndFamily As Byte
<VBFixedArray(LF_FACESIZE)> Dim lfFaceName() As Byte '(the Upgrade modified this from "Dim lfFaceName(LF_FACESIZE) As Byte")

'UPGRADE_TODO: "Initialize" must be called to initialize instances of this structure. BLAH-BLAH-BLAH


Public Sub Initialize()
ReDim lfFaceName(LF_FACESIZE)
End Sub
End Structure

In the above (also remembering to marshal the structure as outlined in point 1 or 2), what we see is
that our fixed array, lfFaceName, appears to be declared to the initial size we require, which is a
value defined by a constant named LF_FACESIZE. We can leave this code alone and simply
invoke the structure’s Initialize() method after we assign the structure to a reference variable.
If we need to supply this structure to a P/Invoke, then some of you may feel that you cannot safely
keep the Initialize() method present. But rest assured that only the structure’s data fields is passed
(ByRef) to a P/Invoke. Further, a Len() function performed on the above structure will return only its
data’s byte length. But if you feel really nervous about it, try something really goofy, like deleting
the Initialize() method (I strongly advise against it).
Is it OK to not bother with or delete the Initialize() method, assuming that array initialization can be
ignored, just like simple string members can be? ABSOLUTELY NOT! The VBFixedArray
documentation clearly states “The VBFixedArrayAttribute is informational and does not allocate any storage. The
purpose of this attribute is to modify how arrays in structures and non-local variables are used by methods or API calls
that recognize the VBFixedArrayAttribute. Keep in mind that this attribute does not convert a variable length array to a
fixed array and that you must still allocate array storage using Dim or ReDim statements (underlining mine).”
Meaning? It means that the VBFixedArray attribute, just like the VBFixedString attribute, does not
actually allocate space. However, in the case of arrays, it boils down to us being required to declare
dimensions for the array after we have assigned the structure to a new field or variable, and hence;
the upgrade’s automatic addition of the Initialize() method. That means to us that after we declare a
variable to be of type LOGFONT, such as “Dim lfLogFont As New LOGFONT”, we are required to then
also dimension the lfFaceName array to size LF_FACESIZE. If we had retained the Initialize()
method, as I hope you did, then we can simply invoke it using “lfLogFont.Initialize()”. Or, if we
had suffered a brain fart and deleted it, we must manually resize our dynamic array in this form:
“ReDim lfLogFont.lfFaceName(LF_FACESIZE)”.

Page –13–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

Consider these two examples:


' Declare LOGFONT structure and initialize using Initialize() method (E-Z. It gives me time to raid the Fridge)
Dim lfLogFont As New LOGFONT 'NEW causes all data members to be initialized to their default values.
lfLogFont.Initialize() 'initialize using upgrade-provided method.

' Declare LOGFONT structure and initialize manually (yuck; more typing for me)
Dim lfLogFont As New LOGFONT 'NEW causes all data members to be initialized to their default values.
ReDim lfLogFont.lfFaceName(LF_FACESIZE) 'initialize manually.

NOTE: A good rule of thumb to follow is this: If a structure contains an Initialize() method, then always invoke it after
you instantiate it. If it does not contain one, then no worries. But remember that in applications you later develop under
VB.NET, it is strongly recommended that you also create an Initialize() method within a structure containing any
dimensioned arrays in order to properly dimension those array members, because VB.NET will not do that for you.
NOTE: The reason that we separately allocate a fixed-sized array and not a fixed-sized string all has to do with
VB.NET’s inability to predetermine preset array pointers (which, goofy me, I think is easy, because I create them all the
time). With fixed-length strings declared with the <VBFixedString(nnn)> attribute, it can easily determine, reserve, and
point to the base of the data in a structure or class for that string. A Fixed-size array is a different thing entirely,
typically being a list or a matrix of pointers to data. Therefore, simply allocating space and pointing to its base for
arrays is not acceptable, because the structure is expecting a list or matrix of pointers, even though VB.NET can and will
reserve the space required to store all those many pointers. Call me silly, but I do not understand why VB.NET does not
simply initialize that set-aside pointer space to a list or matrix of null pointers. Some may argue that multi-dimensional
arrays is where this simplicity ends, because in such a matrix, the lower dimension arrays must always contain pointers
to lists of the array dimension immediately higher than it (in multidimensional arrays, even blank ones, all lower array
specifications in fact do contain pointer data, because it is the final dimension that specifies the actual data we typically
access). However, even so, since VB.NET already has the dimensions required, why could it not simply perform this
initialization for us, or at least auto-invoke an Initialize method during instantiation, if an Initialize method exists?
NOTE: If you require multiple dimensions, such as 32x32, adjust pertinent lines in the structure as demonstrated here:
<VBFixedArray(32,32)> Dim strMatrix(,) As String 'set aside space for 32 rows of 32 columns of strings
'(note the rank setting (,) in strMatrix)
...
ReDim StrMatrix(32,32) 'note that these parameters MUST match the VBFixedArray parameters, above

NOTE: As noted above, you can specify multiple dimensions, such as “<VBFixedArray(1023, 63)> Dim I(,) As
Integer”. Notice that we should match the values in the VBFixedArray parameter in the ReDim statement. Note also
that the dimension Rank must be included in the declared un-dimensioned variable (the commas). For example, a 3-
dimensional array such as “Dim I(20, 40, 128) As Integer” would be declared as “<VBFixedArray(20, 40, 128)>
Dim I(,,) As Integer”. Notice that the rank for “I” changed to 2 commas, implying 3 dimensions.

10. Dealing with RichTextBox Property Renaming


For some reason, during an upgrade, when Rich Text format data is being copied from one
RichTextBox to another through their RichText property fields, the assigned property can change.
First, under VB.NET, the VB6 “TextRTF” property is simply named “Rtf”. This typically upgrades
OK, except during an assignment; a target “TextRTF” property will be incorrectly upgraded to “Text”
rather than properly to “Rtf”. For example, consider the following original VB6 code:
With Me.rtbInfo
.TextRTF = Me.rtbSearch.TextRTF 'now copy data to help display form
...
End With

When it is upgraded to VB.NET, this code results in:


With Me.rtbInfo
'UPGRADE_WARNING: TextRTF was upgraded to Text and has a new behavior. Click for more: BLAH-BLAH-BLAH
.Text = Me.rtbSearch.Rtf 'now copy data to help display form
...
End With

Notice that the source “TextRTF” property has been properly upgraded to “Rtf”, but the destination
“TextRTF” was upgraded instead to “Text”. This statement will have to be fixed by you by further
changing the “Text” property to correctly reflect “Rtf”.

Page –14–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

11. Dealing with recovering the LenB() function


A VB6 program would use LenB() to obtain the actual byte length of a string or structure, usually to
insert the length of a structure into its first member (some variable-sized structures require this).
However, during an upgrade to VB.NET, code using LenB() is always flagged as not being
supported by VB.NET. Although Microsoft’s help is a bit lacking in providing a clear resolution to
this important issue, the clarification below should resolve this quandary.
Online gurus often state the VB.NET Len() function does not support Structures (misinformation
blamed on Microsoft; but that was only true for early releases, to which Microsoft was referring to),
and advise you to look to the Marshal Class (part of the System.Runtime.InteropServices
namespace), which has a SizeOf() method that will accept objects, such as structures, and will return
its size in bytes. At first glance, this seems to be exactly what we need.
In actual testing, however, I found that the result does not accommodate the actual allocated sizes of
string members declared within those structures, except as 4-byte IntPtr types (Integer Pointers).
Using that class, the MYSTRUCT structure described in Point 8, shown below, will return a value of
12, which accounts for 2 Integers, plus 1 IntPtr referencing the string data ((2+1) x 4):
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
Public Structure MYSTRUCT
Dim dwInfoSize As Integer '4 byte footprint
Dim dwEntryId As Integer '4 byte footprint
<VBFixedString(128)> Dim szText As String 'A string with a declared footprint size of 128 bytes

Public Sub Initialize() 'Initialize the 'fixed-length' string to the proper size
szText = New String(Chr(0), 128) 'This method occupies no space in regard to the size of the structure
End Sub
End Structure

On the other hand, VB.NET’s Len() function now fully supports Structures and will return its full
allocated size, to include the allocated lengths of any string members.
NOTE: Allocated lengths of String members in structures refers to an actual declared size. For normal string
declarations, this would be a length of 4 (the size of an IntPtr). But if the string is declared with a prefix, such as
“<VBFixedString(128)>”, then this alters its initial footprint size to the byte size that is declared.
For individual Unicode Strings, such as “Dim S As String = "abc" : Dim I As Integer = Len(S)” you
can either double the value of the string’s length, or you can use the GetByteCount property of the
System.Text namespace’s Unicode.Encoding class.
Consider the following upgraded VB6 code:
'UPGRADE_ISSUE: LenB function is not supported. 'Click for more: 'ms-help://MS.VSCC.v90/... BLAH BLAH
CopyMemory(MnMxInfo, lParam, LenB(MnMxInfo)) 'get structure byte size
'UPGRADE_ISSUE: LenB function is not supported. 'Click for more: 'ms-help://MS.VSCC.v90/... BLAH BLAH
Dim iLen As Integer = LenB(myString) 'get string byte size

We can manually fix the above two lines by updating them to this:
CopyMemory(MnMxInfo, lParam, Len(MnMxInfo)) 'get structure byte size
Dim iLen As Integer = System.Text.Encoding.Unicode.GetByteCount(myString) 'get string byte size
'Or:
Dim iLen As Integer = 2 * Len(myString) 'get string byte size

Or better, the above fixes can be eliminated and the original code will become once again viable
simply by adding our own overloaded LenB functionality. In a module (here named modLenB), just
add these two functions:
Module modLenB
'********************************************************
' Provide the VB6 LenB Functionality – Type-Safe approach
'********************************************************
Public Function LenB(ByVal ObjStr As String) As Integer
'Note that ObjStr.Length will fail if ObjStr was set to Nothing, so use Len()
If Len(ObjStr) = 0 Then Return 0
Return System.Text.Encoding.Unicode.GetByteCount(ObjStr)
End Function

Public Function LenB(ByVal Obj As Object) As Integer


If Obj Is Nothing Then Return 0
Try 'Structure
Return Len(Obj)
Catch 'Leave blank for catch-all
Try 'Type-def objects
Return System.Runtime.InteropServices.Marshal.SizeOf(Obj)
Catch 'Leave blank for catch-all
Return -1 'Allow user to check for <0 as an error
End Try
End Try
End Function
End Module

Page –15–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

12. Dealing with passing parameters to P/Invokes “As Any”


If a P/Invoke has parameters tagged “As Any”, you are much better off using type-safe overloaded
methods in VB.NET. With it you are able to declare more than one type-safe method with the same
name, but using different parameters. This simplifies coding because you do not need to worry about
which method to invoke. This is a feature that VB6 users had for years been clamoring loudly for,
but which VB6, as its platform had been implemented, was sadly not able to support. Indeed, VB6
had to jump through enough hoops as it was just to enable support to perform API calls in the first
place (a feat worthy of praise for the VB Development Team at Microsoft – a goal that many had
believed was impossible). But function overloading was just asking too much of a platform that was
already stretching to its limits. In fact, the whole reason why the As Any type was ever introduced
was just as a cheat to get around VB6’s inability to support method overloading.
Now, it seems, many VB programmers cannot seem to survive without the As Any type, which was,
purely and simply, a hack to enable functionality that was otherwise inaccessible to VB6 developers,
but also one that was absolutely essential for the pre-VB.NET platforms, as otherwise too many
Win32 API methods would have been off limits to VB programmers. This would have certainly been
totally unacceptable, because it would have held VB programmers too far back, and would have
been a nail in VB’s coffin, not to mention giving its detractors more ammunition to fire at it.
For example, consider the following typical VB6 API Call that has found a lot of use in VB6:
Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( _
ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, _
ByVal lParam As Any) As Long

This method was often used to send string text as the lParam value to another control in another
application running on the computer, or to a different process, or to a different control. If there was
no text, then a value of zero was sent as lParam. Using “As Any” allowed both text and values to be
passed by the same parameter.
Under VB6, if you wanted to break this API up to support values and strings separately without
resorting to As Any, you did it by declaring two separate APIs and giving them separate names, such
as SendMessageByNum and SendMessageByStr, and manually invoking the appropriate method,
depending on what type of data you needed to send. For example:
Declare Function SendMessageByNum Lib "user32" Alias "SendMessageA" ( _
ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, _
ByVal lParam As Long) As Long

Declare Function SendMessageByStr Lib "user32" Alias "SendMessageA" ( _


ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, _
ByVal lParam As String) As Long

With VB.NET, you are now able to declare two separate methods, but provide them with the same
name. This way, you do not have to think about which method to use, because they will be the same,
though the compiler will be able to tell by examining the parameters, and it will select the proper
method for you. Consider the following declarations, both named SendMessage:
Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( _
ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, _
ByVal lParam As Integer) As Integer

Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( _


ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, _
ByVal lParam As String) As Integer

However, if you are one who still feels as though you cannot live without it, or simply feel that using
it will get around having to learn how to write overloaded methods, what are actually quite simple,
as shown above, you can “upgrade” them as follows. If the VB6 parameter was declared:
lpKeyName As Any 'not type-safe, so do not just throw any ol' thing into this stew pot

Upgrade it to actually work As Any under VB.NET like this:


<MarshalAs(UnmanagedType.AsAny)> ByVal lpKeyName As Object 'not type-safe, but works just like VB6

Page –16–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

13. Dealing with StrConv: converting between Unicode and ANSI strings
Normally you can use the StrConv() method as you had used it under VB6, and the upgrade will
adjust parameter-naming changes. But when converting to/from ANSI and Unicode text, you will
have to do this differently, because Unicode/ANSI conversion is not presently supported by
VB.NET’s StrConv, which now seems to be geared more toward needed foreign language handling.
To convert an ANSI Byte array (Bytes) to a Unicode String (strText), upgrade these VB6 statements:
Dim strText As String
strText = StrConv(Bytes, vbUnicode) ' convert ANSI text to Unicode text

To VB.NET using the following equivalent statement:


Dim strText As String = System.Text.Encoding.ASCII.GetChars(Bytes) ' equivalent to VB6 StrConv(Bytes, vbUnicode)

To convert a Unicode String to an ANSI Byte array, you can upgrade the following VB6 statements:
Dim Bytes() As Byte
Bytes = StrConv(strText, vbFromUnicode) ' convert Unicode text to ANSI text

To VB.NET using the following equivalent statement (you can also use UTF7/8 instead of ASCII):
Dim Bytes() As Byte = System.Text.Encoding.ASCII.GetBytes(StrText) ' equivalent to VB6 StrConv(strText, vbFromUnicode)

NOTE: Some online techs have widely reported different VB.NET functions to achieve these ends, but their solutions
most often maintain 16-bit encoding, which is not what many users asked for (but in some cases we do need that). Let us
take a quick look at this. To convert each two consecutive byte array elements into one Unicode character, use this:
Dim strText As String = System.Text.Encoding.Unicode.GetChars(Bytes) 'convert each 2 consecutive byte array elements to Unicode Chars

Conversely, to convert a Unicode String to an equivalent Byte array, you should use this:
Dim Bytes() As Byte = System.Text.Encoding.Unicode.GetBytes(strText) 'convert each Unicode Char into 2 consecutive byte array elements

The above two methods both maintain 16-bit encoding, the first combining every two consecutive byte array elements
into one consecutive Unicode character, and the other breaking each 16-bit Character into two consecutive Bytes.
What follows is a practical example for user-side conversion between Unicode text and ANSI text:
One thing that has driven many VB.NET developers crazy has been the process of converting a Unicode
String into an ANSI string required by some WIN32 P/Invokes. In certain situations we will need to set
aside a string area in unmanaged space, copy a string to that memory area, next provide the address to
that stored text to a structure, then finally provide that structure to a P/Invoke (SHBrowseForFolder() is
the example I see most often used to exemplify this problem). Consider the following partial code clip:
' ---------------------------Declare P/Invokes-------------------------
'copy memory P/Invoke (do you see the error in the following P/Invoke declaration? It will be discussed in the text)
Private Declare Auto Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal pDest As Integer, _
ByVal pSource As String, _
ByVal dwLength As Integer)
' Allocation P/Invoke to reserve a specified byte count of unmanaged local memory space
Private Declare Function LocalAlloc Lib "kernel32" (ByVal uFlags As Integer, ByVal uBytes As Integer) As Integer
' Consider using System.RunTime.Interopservices: Dim GCptr As GCHandle = GCHandle.Alloc(x) in place of the above (DRG)
'
Private Const LMEM_FIXED As Integer = &H0 'allocated buffer location will be locked in place
Private Const LMEM_ZEROINIT As Integer = &H40 'allocated buffer will be initialized to zeros
Private Const Lflgs As Integer = (LMEM_FIXED Or LMEM_ZEROINIT) 'combine constants
'
' Deallocation P/Invoke to release unmanaged local memory space set aside by LocalAlloc P/Invoke
Private Declare Function LocalFree Lib "kernel32" (ByVal hMem As Integer) As Integer
' Consider using GCptr.Free in place of the above (DRG)
'
' This API displays a browser dialogbox, enabling the user to select a file or folder.
Private Declare Function SHBrowseForFolder Lib "shell32" (ByRef lpbi As BrowseInfo) As Integer
...
Dim udtBI As BrowseInfo 'set up structure reference variable
Dim lpmt As Integer 'used to store pointer to allocated space
'
' Build the BrowseInfo structure assigned to udtBI
With udtBI
.hwndOwner = hwndOwner 'owner handle (Me.Handle, or the handle of the form to 'parent' the dialog)
.pidlRoot = 0 'Address of an ITEMIDLIST structure, or use NULL (0) if it is not used
Dim Pmt As String = sPrompt & Chr(0) 'set local prompt copy and include a trackable null terminator in its size
lPmt = LocalAlloc(Lflgs, Len(Pmt)) 'set aside space for text (use char count of the prompt for its 8–bit ANSI length)
CopyMemory(lPmt, Pmt, Len(Pmt)) 'copy string (Pmt) to set-aside space (lPmt) for the char count of Pmt
.lpszTitle = lPmt 'set address of string stored in unmanaged memory to structure member
... 'other initializations go here...
End With
'
' browse...
Dim lpIDList As Integer = SHBrowseForFolder(udtBI) 'browse and automatically allocate resources for aquired data
'
' free set-aside unmanaged memory space created by LocalAlloc()
LocalFree(lPmt) 'free space we allocated for our custom prompt
...

Page –17–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

What we are going to quickly discover is that the prompt being displayed will consist of only the first
character of the prompt (displaying “S”, when it should display “Select Source Path”, for example).
Why? Because most developers automatically placed an Auto verb in the declaration of the
CopyMemory() P/Invoke, remembering only that we will be processing a string (Private Declare Auto
Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory"). To clarify, refer to Point 4 concerning the
warning against including both the Auto and Alias verbs in a P/Invoke declaration. Further, because
VB.NET has special knowledge of RtlMoveMemory(), it will enforce Unicode to ANSI text translation if
any string is used in this method. What results is our Unicode string remains intact, and so, in binary
terms, the string begins with the Unicode character “S” (x’0083’), which is actually stored internally in
memory as x’83’ and then x’00’ (the ‘high’ byte x’00’ is listed after the low x’83’ because, internally,
memory progresses, relatively, from left to right; from low to high – even bit sequences are reversed
from how we typically imagine them as right to left – but this is all hidden from you), and because text is
processed internally as ANSI by the P/Invoke, it finds an 8-bit null text terminator following the “S”.
NOTE: Even though the Auto verb had been present, be aware that if it was retained, but no parameter contained a
string type, then the method would have worked perfectly, because strings would not be a factor in those situations.
Even though the actual solution to this problem is to simply remove the Auto verb from the
CopyMemory() declaration, those developers apparently did not figure that out. Even so, we can still
explore the useful alternative method they developed for passing 8-bit ANSI strings to P/Invokes.
One idea they came up with was to pass MoveMemory() a pre-converted ANSI string as a Byte
array instead of a Unicode string. I will also assume that the declared CopyMemory() P/Invoke will
not be used elsewhere in the current module, so I will simply redefine it to a version that will accept
a Byte array (I could have easily just added an overloaded method). Since the P/Invoke now does not
contain strings, we will remove the Auto verb (but you watching at home know that we have just
fixed the actual bug in the program, because apparently those developers were not yet aware of the
rule warning against using both Auto and Alias in a P/Invoke declaration).
'copy memory API (note that if Auto were still present in this type of declaration, it
' (would still work perfectly, because string parameters are not present)
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal pDest As Integer, _
ByVal pSource() As Byte, _
ByVal dwLength As Integer)

And to pass ANSI text to the CopyMemory() method, I will need to translate the Unicode text to a
Byte Array. I would therefore update my code with the following darker shaded segments:
With udtBI
.hwndOwner = hwndOwner 'owner handle (Me.Handle, or the handle of the form to 'parent' the dialog)
.pidlRoot = 0 'Address of an ITEMIDLIST structure, or use NULL (0) if it is not used
Dim Pmt As String = sPrompt & Chr(0) 'set local prompt copy and include a trackable null terminator in its size
Dim Byt() As Byte = System.Text.Encoding.ASCII.GetBytes(Pmt) 'convert Unicode String to ANSI Byte Array
lPmt = LocalAlloc(Lptr, Len(Pmt)) 'set aside space for text (use char count of the prompt for its 8-bit ANSI length)
CopyMemory(lPmt, Byt, Len(Pmt)) 'copy byte array (Byt()) to set aside space (lPmt) for the char count of Pmt
.lpszTitle = lPmt 'set address of string stored in unmanaged memory to structure member
... 'other initializations go here...
End With

NOTE: By the way, on NEW projects that involve folder and file selection dialogs, you should take the E-Z route and
simply employ the FolderBrowserDialog or OpenFileDialog controls available in the VB.NET IDE Toolbox. With these
you can set custom prompts and starting paths easily and quickly, and they require absolutely no interop code from you.
For reference, what follows is a module that will provide Unicode/ANSI string conversion:
Module modUnicodeANSI
'******************************************************
' StrConv_Uni2ANSI
' Provide conversion from Unicode string to ANSI string
'******************************************************
Public Function StrConv_Uni2ANSI(ByVal StrUni As String) As Byte()
If Len(StrUni) = 0 Then Return Nothing
Return System.Text.Encoding.UTF8.GetBytes(StrUni) 'convert Unicode string to ASCII bytes
End Function
'******************************************************
' StrConv_ANSI2Uni
' Provide conversion from ANSI string to Unicode string
'******************************************************
Public Function StrConv_ANSI2Uni(ByVal Bytes() As Byte) As String
Return System.Text.Encoding.UTF8.GetChars(Bytes) 'convert ASCII bytes to Unicode string
End Function
End Module

Page –18–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

14. Dealing with AddressOf/Missing Delegate issues


When a warning about the use of the AddressOf() function indicates that the object needs a
Delegate, this is easy to remedy, but the solution is a 2-step process. First, a Delegate (a prototype)
of the object must be declared (E-Z to do), and then the storage location it is being assigned to must
be changed, usually from Integer or IntPtr (both VB6 Long) to the name of the Delegate we must
define (this is also E-Z to do). This solution can be implemented quickly.
A Delegate is used as a template to tell the compiler how it should treat a method name when it is
encountered. A Delegate is very similar to a C/C++ prototype declaration; it provides clues to the
system so the system knows that when a method name is specified, if it is a function, what its return
type is, and the types of any parameters. Unlike VB6, which was subject to errors in this venue,
VB.NET takes steps to avoid misused method invocation errors by requiring method prototypes.
Probably the quickest way to deal with this situation is to place a bookmark at the offending location
(the line containing the erroneous assignment and the AddressOf statement), so we can return to it.
So, the first thing to do is to go to the line containing the offending AddressOf function.
Suppose that we are assigning a Callback function to a member of some structure, and the line
instigating the Upgrade Warning is flagged on the following line of code:
'UPGRADE_WARNING: Add a delegate for AddressOf BrowseCallbackProcStr. Click for more: BLAH BLAH
myStruct.lpfnCallback = AddressOf BrowseCallbackProcStr

First, click on that line to place the cursor in it, then either hit the Toggle Bookmark toolbar icon, or
select Edit / Bookmarks / Toggle Bookmark from the menu, to place a bookmark at that location.
Next, right-click “BrowseCallbackProcStr” and select “Go To Definition”. At the definition, we will
need to select the full function heading, copy it to the clipboard, then go to the beginning of the class
or module, but still within the class or module, and paste the function heading to a new line.
What we need to do with this copied heading is to edit it in order to convert it into a Delegate. For
example, if the copied function heading looked like the following:
Public Function BrowseCallbackProcStr(ByVal hwnd As Integer, _
ByVal uMsg As Integer, _
ByVal lParam As Integer, _
ByVal lpData As Integer) As Integer

To convert it to a Delegate, simply insert the term Delegate before Function (or Sub, if a subroutine),
and change the name of the method. I simply add the text “Delegate” to the end of the name, like so:
Public Delegate Function BrowseCallbackProcStrDelegate(ByVal hwnd As Integer, _
ByVal uMsg As Integer, _
ByVal lParam As Integer, _
ByVal lpData As Integer) As Integer

After that, select the text “BrowseCallbackProcStrDelegate” and copy it to the clipboard.
Next, go back to our book-marked location, right-click “lpfnCallback”, and then select “Go To
Definition”. For this example, it took me to the following line:
Dim lpfnCallback As Integer 'Address of Callback

Change the “Integer” to “BrowseCallbackProcStrDelegate” by double-clicking “Integer” and hitting


Ctrl-V to replace it with the Delegate name. My line would afterward look like the following:
Dim lpfnCallback As BrowseCallbackProcStrDelegate 'Address of Callback

We have just fixed the entire missing delegate problem.


NOTE: If the above were not a member of a structure, but a parameter provided directly to a P/Invoke; simply change
its type to the name of the Delegate, just as we did above. You may also want to go back to your book-marked location
and toggle that bookmark off, as well as deleting the UPGRADE_WARNING comment inserted by the Upgrade Wizard).
NOTE: Be aware that the Delegate will be internally interpreted as an IntPtr, which is an Integer Pointer, being the
exact same size as an Integer. All addresses returned by AddressOf also return an Intptr (treaded by VB6 as a Long).

Page –19–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

In another case, in instances where you hook and unhook window/form subclasses, intercepting the
Windows Message Queue, this requires using the SetWindowLong P/Invoke. You may want to
simplify the delegate handling process by defining multiple versions of this P/Invoke. Consider the
following code clipping, which must invoke SetWindowsLong in two different ways; one using a
Delegate (notice the use of AddressOf), and the other using a simple Integer value, as shown below:
'**************************************
' Declare our P/Invoke to hook/unhook our code to the Windows Message Queue
'**************************************
Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Integer, _
ByVal nIndex As Integer, _
ByVal dwNewLong As Integer) As Integer
'***********************************************
' HookWin(): Subclass hwnd to szWndProc: Insert a hook in the Windows Message Queue to divert queue processing to szWndProc
'***********************************************
Public Sub HookWin(ByVal hWnd As Integer, ByRef PrvhWnd As Integer)
'UPGRADE_WARNING: Add a delegate for AddressOf SzWndProc. Click for more: BLAH BLAH
PrvhWnd = SetWindowLong(hWnd, GWL_WNDPROC, AddressOf szWndProc) 'insert our hook, and save the previous one
End Sub
'***********************************************
' UnhookWin(): remove subclass hook to szWndProc: remove our function diversion and stuff the old saved invocation back in
'***********************************************
Public Sub UnhookWin(ByVal hWnd As Integer, ByRef PrvhWnd As Integer)
If CBool(PrvhWnd) Then 'if somethinging (the old windproc) to unhook...
Call SetWindowLong(hWnd, GWL_WNDPROC, PrvhWnd) 'reset previous hook by over-writing ours
PrvhWnd = 0 'indicate no held hooks set aside
End If
End Sub
'***********************************************
' Our custom subclassing method to insert into the Windows Message Queue.
'***********************************************
Private Function szWndProc(ByVal hWnd As Integer, _
ByVal uMsg As Integer, _
ByVal wParam As Integer, _
ByVal lParam As Integer) As Integer
...

After defining the needed Delegate using the technique previously outlined, say szWndProcDelegate, to
prototype our subclass method, szWndProc, some creative people have attempted to dance the Delegate
name around, which resolves to an IntPtr/Integer, and declared PrvhWnd in HookWin, the PrvHwnd
parameter for UnhookWin, and so on, to this Delegate. We might try fraught things like this as we blink
excessive sweat from our eyes while struggling desperately to stop VB.NET from incessantly barking at
us after each compile attempt with it badgering us about improper data typing; this stemming from our
myopic attempts to resolve common addressing to our P/Invoke, SetWindowLong.
But the above is making a simple problem a lot more complicated than it needs to be. Instead of
trying to adjust variable typing to suit our P/Invoke, why not think broadly and simply massage our
P/Invoke into addressing the two different ways that HookWin and UnhookWin need to invoke it?
So, after thinking peaceful Zen thoughts about fluffy puppies and kittens, and having the full power of
object oriented programming at hand, we should realize that all we really need to do is take advantage of
VB.NET’s capacity to support overloaded methods. With that in our bag of tricks, all we must do to
correct our problem is 1) create the needed Delegate for szWndProc, and then 2) define another, but
overloaded, SetWindowLong method to support the Delegate. This way we will have one version of
SetWindowLong with an Integer type to handle parameter dwNewLong, and another version of
SetWindowLong that uses our Delegate as the type for dwNewLong. With just that, suddenly this
otherwise potentially very complicated problem (well, it is if we think too narrowly) is completely
solved, as the darker shaded areas demonstrate below:
'***********************************************
' Declare our Delegate – declare it before it is referenced. This delegate resolves to a simple Integer/IntPtr
'***********************************************
Public Delegate Function szWndProcDelegate(ByVal hWnd As Integer, _
ByVal uMsg As Integer, _
ByVal wParam As Integer, _
ByVal lParam As Integer) As Integer
...
'***********************************************
' Declare our overloaded P/Invokes to hook/unhook our code to/from the Windows Message Queue
'***********************************************
Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Integer, _
ByVal nIndex As Integer, _
ByVal dwNewLong As szWndProcDelegate) As Integer
' "dwNewLong As szWndProcDelegate" will be treated at the machine level as simply an Integer/IntPtr
Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Integer, _
ByVal nIndex As Integer, _
ByVal dwNewLong As Integer) As Integer

Page –20–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

15. Dealing with Dir() function warnings


If you are warned that the Dir() method has changed behavior, this concerns the sequencing of folder
shortcuts “.” (Current) and “..” (Parent) during a directory scan. In VB6 these two folders were
always the first two entries gathered. Some programmers got goofy and just skipped checking the
first two entries based on this assumption. This is no longer true, nor should it have ever been true.
To ignore them, see if the left character of the file/folder name is not “.” (If DirEntry.Substring(0, 1)
<> "." Then).As long as you are doing this, reports of changed behavior can be safely ignored.
However, you should consider abandoning the archaic and slow Dir() function and resort to using
the extremely fast and much more powerful My.Computer.FileSystem or System.IO classes and
methods, which work much faster than even the File System Object declared in the ActiveX COM
reference Windows Script Host Object (also known as Windows Scripting Host Object Model, or
WSHOM), embodied by wshom.ocx (this OCX in turn redirects to IWshRuntimeLibrary.DLL) or
scrrun.dll (if you reference Microsoft Scripting Runtime instead).

16. Dealing with Item issues in Collections


If you use Collection-type objects, such as Collections, ListBoxes, or ComboBoxes, you often get a
warning that a default property for Item in a Collection cannot be determined. This is because
Collection Items in VB.NET do not default to a text property as they did under VB6. The Item
property of a VB6 Collection was of type String, but it is now a more versatile type Object under
VB.NET (but with the proviso that the provided Object also features a ToString method).
You can resolve this issue by choosing to ignore the warning, because you have in fact already been
assigning a String Object to the Item property, then this will be corrected during the late binding
process. Yet, if you want to avoid this supported late-binding bit-fiddling, which is much slower, you
can greatly speed it up by employing the collection Item object’s ToString() method (i.e.,
myListBox.Items.Item(Index).ToString()) to resolve it during the much faster early-binding stage.

If you are using an actual Collection, consider changing the collection object itself, such as into one
of the System.Collections.Generic strongly-typed zero-based Collection classes. If you will only be
working with text data, then define it as a List, or, if you also require an associated Key, try using a
Dictionary (or a SortedDictionary if you would like to auto-sort the dictionary based on the Key).
For example, you could change your VB6 declaration from something like this:
Dim myCol As New Collection

To something more robust and strongly typed, like this VB.NET declaration:
Dim myCol As New Collections.Generic.List(Of String)

You can of course separate the definition from the instantiation of the collection. You can also use a
structure or class instead of String as the type of object to strongly tie this collection to. Or, you can
simplify editing even more by redefining the Collection control in your app in just 3 lines of code:
Public Class Collection 'make this OF a custom Class or Structure if you want to add more features, such as keys.
Inherits System.Collections.ObjectModel.Collection(Of String) 'replace 1-based Collection with 0-based string-typed collection.
End Class 'or you can use the KeyedCollection or ReadOnlyCollection classes of ObjectModel.

NOTE: Because a generic collection’s Remove() method will expect an object (used like a Key) to identify what object
to delete, use instead the RemoveAt() method if you wish to specify an index.
NOTE: Remember, the warning about an Item’s default property will probably be the most frequently encountered
warning in an upgrade if the VB6 project did much of anything with collections. Keep in mind that using the Item
object’s ToString() property will be the safest, easiest solution, especially if you are instead using a ListBox or a
ComboBox, but which can also use custom classes for data instead of just strings (as it had been for VB6). However, be
mindful that these new collections are zero-based, not 1-based as the default ‘classic’ Collection. If you need it to be
1-based, stuff a dummy entry as the first item, but remember that the Count property will always be one higher.

Page –21–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

17. Dealing with late-bound Object references


You may surmise from the last point that using late-binding (supposedly a bonus feature of Visual Basic)
can in turn be confusing to the Upgrade Wizard. This is because sometimes it cannot be sure what kind
of objects it is working with. As a rule, you should only take advantage of late-binding as a last resort;
for it can take a heavy toll in time during execution, because internally, the processing engine must scan
the object for supported functionality, which is stored internally not as fast indexes, but as a text data that
must be parsed against internal tables also stored within the DLL (OCX – ActiveX – files simply redirect
you to a DLL). Once the entry is found (if it exists), then an invocation address and a delegate
(prototype) of the method must be matched to any parameters. Using late-binding is very much like using
slow interpreted DOS BATCH programs (to be fair, I should have said VBScript or JavaScript). It should
only be used when you have absolutely no other choice, because it is slow and cumbersome.
Consider the following simple VB6 code segment:
Dim o As Object
o = Me.Label1
o.Caption = "SomeText"

When this is upgraded to VB.NET, the wizard will not know, when processing the last line, that the
object “o” is associated with a Label control, because its scope of knowledge of its surroundings is
confined strictly to each statement as it is being upgraded. As such, even though in the second line a
Label control is assigned to “o”, the Upgrade Wizard has discarded that knowledge as it rolls up its
sleeves and clears the table to begin work on the next line. The only thing it knows for sure that is
that “o” is declared as type Object. Hence, during the upgrade, it will not upgrade Caption to the
new and now-uniform property Text in VB.NET. There is no sure way that it can assume that the
actual object stored within “o” is a Label control, because it might be a user-defined class that will in
fact continue to use a Caption property. These will have to be manually, though easily repaired.

18. Dealing with VB6 parameterless defaults


You will quickly discover that VB.NET does not support parameterless default properties. A lot of
warnings will be issued in your upgraded code if you were one prone to using them.
The decision not support parameterless default properties was not an easy one at Microsoft, but I wholly
agree with their decision. Parameterless default properties make code too indefinite, giving you no direct
clue in many cases to what the code’s actual intent is. Having said that, it would have been nice if a Text
property or at least the ToString() function, if they exist, were to be fallen back upon as a default, but this
would not have worked seamlessly in all cases because there is a level of uncertainty when using
parameterless default properties regarding whether one is actually wishing to pass on an object or its
default property. For example, what should be done in the following case, if the Text property of the Item
object were a default property:
Dim obj As Object = Item

Are we clear as to what is actually passed into “obj”? Is it the Item object itself, which “obj” will
accommodate, or is it its Text data, its default property, to which “obj” is equally accommodating?
Perhaps the most commonly-used default property is the Item list when referring to a member of a
Collection. Item allows an index or key property, which fully qualifies it to be declared a default
property. You can specify “Items.Item(Index)” or “Items(Index)”, but its intent is still clear.
But having said all that, keep in mind that default properties are often processed late-bound, meaning
that processing them and determining them is a process-slowing, time-consuming practice. For the
fastest possible program execution, always early bind your code as much as possible. Also, as is
probably made evident in the previous example, you should avoid using default properties with the
Object and Variant data types in your VB6 code, because these can be difficult to resolve during an
upgrade to VB.NET, and you will likely have to further modify the code yourself after the upgrade.

Page –22–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

19. Dealing with VB6 Null Propagation


One obscure area that may run you into trouble is in regard to Null Propagation. In VB6 and
previous, Null propagation supported the premise that wherever Null is used in an expression, the
result of the expression will itself be Null. Consider the following VB6 code:
Dim V
V = 1 + Null
V = Null + Right$("SomeText", 1)
V = Right("SomeText", 0)

Each of the above expressions yields a NULL result under VB6. However, under VB.NET, the statement
1+Null generates a type mismatch. Also, where VB6 had two versions of the Right function — Right$
returning a string, and Right returns a variant that could be Null — VB.NET only has one version, Right,
that always returns a string. Further, V is declared as a variant by default in VB6 (you could also use “As
Variant”). The Upgrade Wizard will change this to be cast as type Object. Also, Null, a variant type, is
not supported in VB.NET, though a database-oriented System.DBNull is.
And speaking of databases, Null propagation is commonly used in database applications, where you
need to check if a database field contains Null. In these cases you should check results against
System.DBNull, or by using the function IsNull() and performing the appropriate action based upon
the result of the test, because Null propagation is still supported in Databases.

20. Dealing with referencing Objects before they are initialized


Sometimes you may receive a warning that an object or variable was being altered before it had been
initialized or instantiated. Although by default numeric variables will initialize to Zero, strings to
Nothing, and likewise for structure members, these warnings are easy to rectify. One choice is to
both ignore the warning and simply delete its comment (the code will work properly), or change the
variable’s declaration so that it is also initialized. For example, if a string variable is in question, and
it is declared “Dim S As String”, then change it to “Dim S As String = ""” or “Dim S As String =
String.Empty”. If a variable is numeric, declare it with a value of 0. If you are told a structure is being
updated before it has been initialized, simply change the declaration of the structure to be as New,
such as “Dim structVar As New myStruct”. For structures, this forces initialization of its data members.

21. Dealing with TextChanged and Resize events firing before the Form Load event
If your application controls handle TextChanged or Resize events, then you will receive a warning in
your upgrade that the TextChanged or Resize event may fire when the form is initialized (trust me; there
is no “may” about it – they WILL fire). You should check to see if these event firings will affect how
your program operates, such as causing application errors becase of resizing before their form is loaded.
It has been my experience that these events (including various Selection Change events) will always
fire before the form’s Load event executes. See point 43 concerning VB.NET’s TRLAP event firing
sequence; TextChanged, Resize, Load, Activate, Paint; Trouble Really Loves A Programmer –
though I wish Microsoft could change the sequence to a more logical LTRAP, or even LAP,
ignoring TextChanged and Resize events before the Load event is fully processed. Even if the
TextChanged and Resize event code is not fired during a Load event, it will not adversely affect
overall form size when certain controls resize due to content changes. I grant that there may be
exceptions, but the answer would still be through the solution offered in the paragraph, below.
I have found that I can eradicate all frustration resulting from the early firing of TextChange and
Resize events by declaring a Boolean flag at the procedure-level of the form, named mFormLoaded
(Private mFormLoaded As Boolean = False) that would be set to True (mFormLoaded = True) at the very
bottom of the form’s Load event. Then, as the first statement in every existing TextChanged and
Resize event I have on my form, I insert the following as each method’s first statement:
If Not mFormLoaded Then Return 'do not process this event until the form load method has completed

Page –23–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

22. Dealing with renamed properties


Often you will find that form controls such as Listboxes and ComboBoxes now have renamed
members in VB.NET from what they were in VB6. Most of the time it is because you may have
accessed the object’s “Hwnd” (Windows Handle) property. For reasons of cross-language
uniformity, VB.NET changed all Window Handle properties to “Handle”. Simply edit the properties
from hwnd to Handle and they will work correctly. Why do upgrades not automatically fix this?

23. Dealing with the loss of the ListCount property


If form controls contain lists of any sort, then they likely implement Collections.IList. As such,
upgraded VB6 properties that generate warning errors such as an unsupported ListCount property
can be fixed by addressing the Items collection within the control (all collection-type lists now use
the Items collection, and all with a Default Property of Item – there is no longer a List collection or
ListItem property). For example, if a ListBox control named LstBox used the now-unrecognized
ListCount property, edit it to instead use “LstBox.Items.Count”. Why do upgrades not fix this?

24. Dealing with changed MousePointer warnings


You may encounter warnings that the Screen property Screen.MousePointer has a new behavior. It
has been my experience that in almost all cases you can safely ignore them, except in the situations
where you will be saving/loading the current cursor. Under VB6, this was typically to an integer
value. This is now a Cursor object. For example, the following upgraded VB6 code:
Dim OldPointer As Integer
'UPGRADE_WARNING: Screen property Screen.MousePointer has a new behavior. Click for more: BLAH-BLAH-BLAH
OldPointer = System.Windows.Forms.Cursor.Current 'this generates a type-mismatch error if it is not fixed

Can be corrected to (note that in a form, the System.Windows.Forms spec is not actually required):
Dim OldPointer As Cursor 'Fix it by changing the old VB6 Integer into a Cursor object reference variable
OldPointer = System.Windows.Forms.Cursor.Current

Setting the Mouse Cursor has changed a bit, but I think for the better; it has been compacted and is
now much easier to use. For the most part, it has been upgraded to use a more convenient
enumerator, Cursors (technically, System.Windows.Forms.Cursors), from which the old VB6
standbys can be set, such as Cursors.Default, Cursors.Arrow, Cursors.Cross, Cursors.AppStarting
(new; the Aero Circle), and so on.
The most significant change that you will notice is how you assign standard or custom cursors to an
object, such as the form – you now set all cursors, both custom and system-provided, to a single and
more logical Cursor property, and the old MouseIcon and MousePointer properties have
disappeared, being moot. Now to set the form cursor to the Hourglass, you would submit “Me.Cursor
= Cursors.WaitCursor” (note the new name) rather than the VB6 “Me.MousePointer = vbHourglass”.
Moreover, thanks to method overloading, you will also load it (set it) with either an existing custom
Cursor object, such as “Me.Cursor = OldPointer” (declared above), or create a new Cursor object for
it. What this means is that when you load it with a cursor object, VB.NET knows that you are setting
it to a Custom cursor, and when you set it to one of the standbys, you are setting it to an enumerator
value. Previously, in VB6, you would load a custom cursor to the current form like this:
Me.MouseIcon = LoadPicture("c:\Icons\EW_06.CUR") 'use an external file resource
Me.MousePointer = vbCustom 'use the new custom cursor object

But now, with VB.NET, you would instead use this single line:
Me.Cursor = New Cursor("c:\Icons\EW_06.CUR") 'use an external file resource

NOTE: Even though you can assign enumerator values to the Cursor property, you should still obtain the cursor object
through the Cursor.Current property. This is because even if you are using standard enumerators, such as
Cursor.WaitCursor, and you can also test for it using something like“If Me.Cursor = Cursors.WaitCursor Then”, and
although it is possible to assign the Cursor property to an Integer variable (it is returned as an IntPtr), the value will
have little apparent meaning, regardless of some texts reporting that their VB6 and VB.NET values are equivalent.

Page –24–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

Loading Cursors from an Embedded Resource


If you would rather load a cursor that is an embedded resource (compiled within your program
executable, but not in the typical Resources location) rather than use an external cursor file, this is
quite easy to do, but you will first have to be sure store a copy of the cursor file locally.
To add a local copy of the cursor to your project (if it is not added to your project already), in the
Soultion Explorer, right-click where you want to store the resource (I create folders named Classes,
Forms, Modules, and Resources – using the AddAdd New Folder option – to keep my projects
tidy), or the project name itself if you simply want to store in in the local pool of files. Select
AddAdd Existing Item, and browse to and select your cursor file (even it is stored locally). It will
be added to your project and you will see it listed in the target storage list.
Next, we need to ensure that it is actually embedded within our application executable. To do that,
select the cursor item in the Solution Explorer list, then in the item’s Properties window, ensure that
the Build Action property is set to “Embedded Resource”.
Next, to use your new embedded cursor within your application, in this example assuming that the
cursor file is named Dilbert.cur (and mind you that this name will be case sensitive), load it using a
command like the following:
Cursor = New Cursor(Me.GetType(), "Dilbert.cur") 'use an embedded cursor resource

Loading Cursors from the Application Resource File


If you would rather store the cursor in the Application Resources, select
ProjectPropertiesResources, Select either Files or Other (it does not matter which) from the
first dropdown, then Add Existing File from the Add Resource dropdown, then select your cursor.
Next, to load your Cursor resource, you need to read it as a stream. However, because it is stored in
the resources as an 1-dimensional array of Bytes, we cannot cast it directly into a stream. However,
we can first read these bytes into a memory stream (rather than writing it first to a file). A memory
stream exists wholly in-memory, and we can dispose of it when we are finished with it, or, as I will
demonstrate, we will let the Garbage Collector handle that for us.
Thus, once we have the item stored, we can use a command sequence like the following to get it:
Cursor = New Cursor(New System.IO.MemoryStream(My.Resources.Dilbert)) 'read cursor resource as a stream

25. Dealing with changes to RECT structures


I did a lot of VB6 programming with the Rect structure, typically defined as the following:
Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type

This presents a problem in VB.NET forms, because the Left and Right members conflict. VB.NET
upgrades resolve this by renaming the structure members Left_Renamed and Right_Renamed. As a
result, I have gotten into the habit of defining my RECT structures like this in VB6 code:
Type RECT
iLeft As Long
iTop As Long
iRight As Long
iBottom As Long
End Type

This change to VB6 code makes so much difference, because a RECT UDT upgrades cleanly to:
Structure RECT
Dim iLeft As Integer
Dim iTop As Integer
Dim iRight As Integer
Dim iBottom As Integer
End Structure

Page –25–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

Otherwise, you may need to burrow through your code and correct these afterward, unless you do
not mind having items that have been tagged “_Renamed”.

26. Dealing with the loss of the Initialized and Terminate events
If you have classes containing Initialized and/or Terminate events, then after they are upgraded you
will find that the Initialized event is renamed Initialized_Renamed, and the Terminate event is
renamed Terminate_Renamed. You will also notice that a New (constructor event), and a Finalize
(destructor event) have been added to your class code. The New event will in turn invoke the
Initialized_Renamed code, and the Finalize event will invoke the Terminate_Renamed code.
Regardless of the fact that this is a workable strategy for class code upgrades, to me it looks a bit
goofy, and is too much work than just leaving the original function names alone and invoking them
from New and Finalize. As a bit of extra self-imposed work, I simply cut the code from within the
body of the Initialized_Renamed code and paste it over the top of its invocation line (replacing it)
for Initialized_Renamed within the Sub New code. I do likewise for Terminate_Renamed and Sub
Finalize. I then of course delete the empty bodies Initialize_Renamed and Terminate_Renamed.
For Example, the following VB6 Initialized method:
Public Sub Initialized()
LnkPrev = Nothing 'init links to nothing for now
LnkNum = 0 'assume base of new array list (at least for now)
Init() 'reset potential variable data
End Sub

Is upgraded to VB.NET to:


Public Sub New()
MyBase.New()
Initialized_Renamed()
End Sub
Public Sub Initialized_Renamed()
LnkPrev = Nothing 'init links to nothing for now
LnkNum = 0 'assume base of new array list (at least for now)
Init() 'reset potential variable data
End Sub

But I further combine these two methods by changing it to:


Public Sub New()
MyBase.New()
LnkPrev = Nothing 'init links to nothing for now
LnkNum = 0 'assume base of new array list (at least for now)
Init() 'reset potential variable data
End Sub

Page –26–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

27. Dealing with changes to Enumeration references


Various states are often checked against constants. In VB6 you check the form’s WindowState property
for the constants vbNormal, vbMaximized, or vbMinimized. Although an upgrade sets these constants to
FormWindowState.Normal, FormWindowState.Maximized, and FormWindowState.Minimized, it may
have trouble determining from the program context what property the term should be upgraded to. After
all, VB.NET also recognizes vbNormal to be a property constant for file attributes. As such, you can
usually simply glance at the code in a wider context and determine what property should be checked,
such as WindowState, FileAttributes, Constants, or other constant enumerators.

28. Dealing with VB6 Namespace Twips conversions


Nearly all individual command upgrades thankfully do not warrant warnings. Most-times these silent
fixes will efficiently redress code, but once in a while these fixes will leave something to be desired
in-as-far as efficiency is concerned. It is not that they do not operate correctly; logically, they work
perfectly, but the code they generate can sometimes be a bit of over-kill. Most times this over-kill
involves twips. Twips (1440 dots per logical inch) were the default unit of graphical measurement in
VB6 and previous, allowing fast integer conversions between pixels (96 dots per logical inch) and
points (72 points per logical inch), but it made the code incompatible as-is with API functions,
which only operated in terms of pixels. Now that VB.NET has eliminated this confusion and by
default uses display/printer-compatible and universally-accepted pixel measurements, we now have
to suffer with VB6 upgrades that still need to deal in twips, mostly due to so many apps using hard-
coded twip values or offsets (I am also guilty of this).
Consider this VB6 line of code from one of my apps:
Me.Height = (Me.Height - Me.ScaleHeight) + Me.Animation1.Top + 60 'set visible height of form

The above code will compute the container height of the form (Me.Height - Me.ScaleHeight), add the
top location of the Animation1 control to set the client area height, and then add 60 twips as a buffer.
When the code is upgraded to VB.NET, it becomes the following confusing line of code:
Me.Height = VB6.TwipsToPixelsY((VB6.PixelsToTwipsY(Me.Height) - VB6.PixelsToTwipsY(Me.ClientRectangle.Height)) +
VB6.PixelsToTwipsY(Me.Animation1.Top) + 60) 'set visible height of form

This code uses the VB6 namespace (a child of the Microsoft.Windows.Compatability namespace).
Although we could leave this code as-is; all this conversion back and forth between twips and pixels
eats precious loads of time. Consider modifying it to use just pixels. First some simple math: 60
twips / 15 twips per pixel = 4 pixels. Following is our pixel-only conversion:
Me.Height = (Me.Height - Me.ClientRectangle.Height) + Me.Animation1.Top + 4 'set visible height of form

Notice that the only real differences from the original VB6 statement is that we added a 4-pixel
buffer instead of an exactly equivalent 60 twips, and since the ScaleHeight property does not exist
(the old and really confusing name given to the VB6 Client space height), we read the Height
property from the more logical ClientRectangle structure.

29. Dealing with user-defined Twips constants


In light of the above mention of the confusion regarding twips and why the upgrade jumps through so
many hoops to upgrade the code, but at the same time not breaking it, it is strongly recommended that
any time you specify a lot of offsets, to instead resort to constants, which can usually be centrally located
in a module somewhere. By using constants, you can update a lot of code spread throughout your
application, but all from one place. For example, if I were to be offsetting screen coordinates by 60
twips, I might define a constant named PIXELS4 or TWIPS60. For example:
Public Const TWIPS60 As Long = 60 'set twip offset for 4 pixels

This would be upgraded to VB.NET to:


Public Const TWIPS60 As Integer = 60 'set twip offset for 4 pixels

Page –27–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

But, knowing that my code now operates with pixels (which I prefer, and not only because of my C++
development work or from constantly interfacing from VB6 to the Win32 API), I would divide this by 15
(15 twips per pixel), and change this line to:
Public Const TWIPS60 As Integer = 4 'set twip offset for 4 pixels

30. Speeding code by removing references to the VB6 Compatibility Library


It would be a really good idea to search for “VB6” throughout your code and see if you need to dress the
code back down a bit to using only native VB.NET methods. I prefer to eliminate all VB6 namespace
usage so I can remove the compatibility reference.
Although Microsoft has stated that they “implemented the VB6 namespace because the conversion of some VB6
code is impossible due to syntactical or architectural differences, and for this reason the functions in the VB6
Compatibility library are used to allow the code to run in VB.NET,” I disagree. Perhaps it is “impossible” in
terms of duplicate syntax, but it is nothing much more than that, except, I cede, that some VB6 functions
address COM-based (registered DLL) features; a virtual codeword in .NET Framework lingo for the
possibility for the dreaded DLL Hell2. But I do not agree that invoking objects in unmanaged space
makes them impossible to run in VB.NET without the VB6 namespace functions, though, I grant, it is
probably the absolute novice-safe route to follow. However, it is clear to me that we do in fact find it
essential to access COM objects quite often, and therefore we are in fact using unmanaged space, but
with the proper precautions we also do it in a managed way (or most of us do). Using COM objects
makes VB.NET a much more powerful development platform, making accessible immense reservoirs of
pre-existing, reusable code (if you look into the dependencies of the .NET platform, you are going to find
a sea of P/Invokes to these same DLL functions).

31. Speeding returned VB6 Namespace List Item values


Keep in mind, as stated before, that ListBoxes and ComboBoxes in VB.NET accept Objects for their
Item collections, not VB6 strings. As such, VB.NET will upgrade the following VB6 command line:
SaveSetting App.Title, "Settings", "History" & CStr(Idx), Me.cboRecent.List(Idx) 'this assumes List Item is string

To the following:
SaveSetting(My.Application.Info.Title, "Settings", "History" & CStr(Idx), VB6.GetItemString(Me.cboRecent, Idx))

Apart from the requisite parentheses that must surround all .NET method parameters, notice that the
“App.Title” property was upgraded to use “My.Application.Info.Title”, but more important to our
point, the “Me.cboRecent.List(Idx)” was upgraded to “VB6.GetItemString(Me.cboRecent, Idx)”.
This last fix ends up fully functional, but it is more work than we require. Remember that Strings are
also Objects, and in VB6 we had always supplied String text to the collection in Listboxes and
ComboBoxes. We can therefore simplify the last change to “Me.cboRecent.Items(Idx).ToString”.
NOTE: You may have noticed that ListBox and ComboBox controls are now syntactically aligned with Collections, all
implementing System.Collections.IList. As such, all collection-type controls have an Items array that accepts data of
generic type Object, and have a Default property of Item. Hence, we no longer have collection-type controls that have
an Items() collection in one control and a List() collection in another.

2
DLL Hell: a term referring to DLL versioning nightmares, where some un-cautious installers, or unwitting users, do not
check versioning and overwrite newer COM-based DLLs with older, less feature-filled versions of the DLL.
Page –28–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

32. Dealing with changed Date/Time shortcut format options


Prior to VB2008, some date/time formats were not recognized, such as "Short Date" and "Long
Time", but that did not mean that their functionality was not still built into VB.NET.
Although they are once more recognized, the VB6 statement “S = Format(Now, "Short Date")”, for
example, is still upgraded to VB.NET as “S = VB6.Format(Now, "Short Date")”, even though it is no
longer necessary as of VB2008, and the original statement is syntactically perfect. However,
regardless of this, it would still be much faster to take advantage of VB.NET’s own built-in
functionality and upgrade either statement to “S = Now.ToShortDateString”.
A slicker, more versatile means to express dates and times is to take advantage of a Date object’s
ToString() method (Search help for “DateTimeFormatInfo” and “DateTime.ToString Method”),
where you can optionally specify date and time formats with very little typing. For example, we
could change the above “S = Now.ToShortDateString” assignment to “S = Now.ToString("d")” instead.
The “d” format tag, a shortcut tag representing “M/d/yyyy”, is one among many tags that VB.NET has
reserved to express dates and times in just about any format you wish, to include, of course, custom
formats. Following are some sample formats (Note that CultureInfo is En-US):
Format Comment Result for January 22, 2010
"d" M/d/yyyy (ShortDatePattern) 1/22/2010
"D" dddd, MMMM dd, yyyy (LongDatePattern) Friday, January 22, 2010
"t" h:mm tt (ShortTimePattern) 2:22 PM
"T" h:mm:ss tt (LongTimePattern) 2:22:48 PM
"f" dddd, MMMM dd, yyyy h:mm tt (FullDateShortTimePattern) Friday, January 22, 2010 9:53 AM
"F" dddd, MMMM dd, yyyy h:mm:ss tt (FullDateTimePattern) Friday, January 22, 2010 9:53:21 AM
"g" GeneralDateShortTimePattern 1/22/2010 9:53 AM
"G" GeneralDateLongTimePattern (default) 1/22/2010 9:53:21 AM
"M" MMMM dd (MonthDayPattern) January 22
"R" ddd, dd MMM yyyy HH':'mm':'ss 'GMT' (RFC1123Pattern) Fri, 22 Jan 2010 09:53:21 GMT
"s" yyyy'-'MM'-'dd'T'HH':'mm':'ss (SortableDateTimePattern) 2010-01-22T09:53:21
"u" yyyy'-'MM'-'dd HH':'mm':'ss'Z' (Universal sortable) (invariant) 2010-01-22 09:53:21Z
"U" Universal sortable Friday, January 22, 2010 2:53:21 PM
"Y" MMMM, yyyy (YearMonthPattern) January, 2010
"o" Roundtrip (local) 2010-01-22T09:53:21.2512235-05:00
"o" Roundtrip (UTC) 2006-01-22T09:53:21.2512235Z
"o" Roundtrip (Unspecified) 2010-01-22T09:53:21.0000000
“h:mm:ss.ff tt” Customized format 9:53:21.00 AM
“d MMM yyyy” Customized format 22 Jan 2010
“HH:mm:ss.f” Customized format 09:53:21.0
“dd MMM HH:mm:ss” Customized format 22 Jan 09:53:21
“\Mon\t\h\: M” Customized format Month: 1
“HH:mm:ss.ffffzzz” Customized format 09:53:21.0000-05:00

33. Dealing with Date value conversions


Another issue concerning dates is the ability of VB6 to store Date information not only in Date-type
variables, but also in Doubles. This was a fluke simply because VB6 and earlier had used a Double
as the general storage format for its Date type. I must admit that I had taken advantage of it when
using VB6, because it was simply too easy to do. In VB.NET they are no longer stored as simple
doubles, but have additional functionality stored along with them, making them more powerful.
However, if you have VB6 code that does manipulate the date data that is assumed to be stored in a
Double, use the Date object function ToOADate() to convert the VB.NET Date data to a Double,
and FromOADouble() to convert from a Double to a VB.NET Date value. For example, often a
double was used to strip the time (or date) from a Date variable, which stored both the date and time:
Dim netDate As Date = Now 'get date and time of day
Dim dblDate As Double = Fix(netDate.ToOADate()) 'get date without time of day
netDate = netDate.Subtract(netDate.TimeOfDay) 'or, simply remove time factor from self

The first two lines emulate the VB6 method. The third emulates this in VB.NET without any helper
functions (I have also seen this same solution written in some on-line VB.NET code as “netDate =
Date.FromOADate(Fix(netDate.ToOADate()))”, which accomplishes the same task, but eats more time).

Page –29–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

34. Speeding Format command use in VB.NET


Statements using VB6’s Format command, like “myStr = Format(TotalFolders - FolderCnt, "#,##0")”
will be upgraded in VB.NET to “myStr = VB6.Format(TotalFolders - FolderCnt, "#,##0")”. However,
you can speed up this code by instead using “myStr = (TotalFolders – FolderCnt).ToString("#,##0")”.
The integer result is a value-type, and as such it acts exactly like a variable or field; hence, embraced
(encapsulated) expressions, even for strings or function results, have method and property members.
By the way, VB.NET also fully supports the original statement, “myStr = Format(TotalFolders -
FolderCnt, "#,##0")”, so you could simply remove the “VB6.” that the upgrade prepends to the Format
statement, and it will continue to be operational, even though I prefer the isolation the ToString
method provides the result’s format, on top of it being a more elegant solution.

35. Dealing with Screen properties


In VB6, you could access the Me.Screen object and obtain its dimension properties, such as Left,
Right, Top, Bottom, Width, and Height. The new VB.NET Screen class is now loaded with
functionality, and so for us to access the Screen’s Left, Right, Top, Bottom, Width, and Height
properties, we have to drill a little deeper into the Screen object, going to its PrimaryScreen.Bounds
structure, which exposes the desired properties. For example, if your VB6 code contained:
Dim ScrHeight As Double
ScrHeight = Me.Screen.Height

It will be upgraded to VB.NET like this:


Dim ScrHeight As Double
ScrHeight = VB6.PixelsToTwipsY(System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height)

However, we can simplify this code in 3 ways: 1) if we are in a form, then the
System.Windows.Forms namespace is already loaded, so we can cut that from the code. 2) We are
now working in Pixels, so we can remove all Twip conversions. 3) In VB.NET we can combine
declaration with assignment. All these options give us this much shorter and simpler code:
Dim ScrHeight As Double = Screen.PrimaryScreen.Bounds.Height 'you may want to set this to an Integer

NOTE: In case you were not aware of it, adding or removing “System.Windows.Forms” in the above does not lengthen
or shorten the final compiled code one bit. This framework mapping simply allows the compiler to zero in on target
methods. Once a target is determined, the compiled code does not need to calculate the address of a method or class or
enumerator each time it is accessed; it is already known, so when the preprocessing is finished and the actual output
code is generated, absolute addressing has already been established.

Page –30–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

36. Dealing with On Iexpr GOTO


To handle upgrading “On Iexpr GOTO n1, n2, n3,...” one first cannot help but wonder why such code
still exists in VB6 to begin with. But no matter. The best resolution and fastest means of adapting
this code to operate best in VB.NET is to convert it to a Select…Case block. For example:
Select Case IExpr
Case n1
'do code associated with n1
Case n2
'do code associated with n2
Case n3
'do code associated with n3
...
End Select

By moving the scattered code to within this block, we have contained it. Also, when each routine
completes its tasks under its Case heading, it immediately transfers control, not falling into the next
Case block as it does in C/C++, but directly to the End Select, where program flow continues (VB
code contains an invisible embedded C/C++ Break command at the end of each case block, ultimately
translating to either a hidden Goto or jump statement).
If you cannot easily adapt your code to this format, then you are guilty of
writing the infamous “spaghetti code”, which was an old software engineering
term used to describe procedural programs whose logic went all over the
place, like spaghetti on a plate. The advent of languages such as ADA, C, and
Pascal were supposed to provide developers with the means to avoid writing
spaghetti code. It was not until .NET Framework’s introduction of VB.NET
and C# where everything is virtually forced to be encapsulated and
modularized (managed), and likewise (hopefully) forced developers to write
modular, logical code for them to even work on these platforms (I say
virtually, as you will very soon understand).
Many amateur programmers have often made the claim to me that it is
impossible for some spaghetti code be written any other way. It has been my
long experience that their excuse is a load of horse pucks, and that by
rewriting the code in a modular fashion it will also make debugging that code
easier. Therefore, if you do not want to re-write the code cleanly, then you
should consider leaving it in VB6. A good example that is frequently cited is
the Shell-Metzner Sort algorithm. I am shown code, similar almost to an
instruction, to that which I had once found in a Creative Computing Magazine
in the 1970s, back in the day when TRS-80 was King and a 36-bit PDP-10
was the envy of university computer science departments. It was described as shown to the above-right:
The program written to support it was like the following, though here is code that, believe it or not,
even VB.NET will accept and executes flawlessly, and all without a single complaint (provided that
Option Strict is turned off, Option Explicit is turned off, and Option Infer is turned on):

Page –31–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

This subroutine is simply the previous program flowchart translated to old DOS BASIC as much as
possible. Back then, you could specify variables at a whim, never having to DIM them or define
their type (the origin of Variants in VB), as we have emulated with Option Infer On. Lines
beginning with “L” create labels for what would have been line numbers. Now you might appreciate
why flowcharts were so popular back then, because the actual program code is convoluted and hard
to follow without doing what I did back then, which was to take a pencil and draw dividing lines, as
well as arrowed lines from the GOTO locations to their destinations.
I have been told repeatedly that this routine cannot be structured because the GOTO instructions go
in too many directions and places that cannot be logically blocked. But when I look at the flow chart,
I am seeing loops and blocks and IF…ELSE segments of code. I first wrote the following structured
translation long ago in FORTRAN, then in C++, then in QuickBASIC (predecessor to VB1), then in
VB6, and finally in VB.NET. Following is a segment of a string sort, comparing the original with
the current (some variables have already been declared by this time, which we will ignore for now):
' sort initialization Original Algorythm (1-Based)
' ----------------------------
NumberofItems = m_MyCount 'get number if items to sort (N=Number of Items)
HalfDown = NumberofItems 'number of items to sort (M=N)
'
' perform the sort
'
Do While CBool(HalfDown \ 2) 'while counter can be halved A: IF(M\2)=0 THEN STOP
HalfDown = HalfDown \ 2 'back down by 1/2 (M=M\2)
HalfUp = NumberofItems - HalfDown 'look in upper half (K=N-M)
IncIndex = 0 'init index to start of array (J=1)
Do While IncIndex < HalfUp 'do while we can index range
IndexLo = IncIndex 'set base B: I=J
Do
IndexHi = IndexLo + HalfDown 'if (IndexLo) > (IndexHi), then swap C: L=I+M
If StrComp(StrAry(IndexLo), StrAry(IndexHi), _
CompareMethod.Text) = CompFlag Then ' IF D(I)>D(L) THEN GOTO D
Tmp = StrAry(IndexLo) 'swap string items T=D(I)
StrAry(IndexLo) = StrAry(IndexHi) ' D(I)=D(L)
StrAry(IndexHi) = Tmp ' D(L)=T
IndexLo = IndexLo - HalfDown 'back up index I=I-M
Else ' IF I>=1 THEN GOTO C
IncIndex += 1 'else bump counter D: J=J+1
Exit Do ' IF J>K THEN GOTO A
End If ' GOTO B
Loop While IndexLo >= 0 'while more things to check
Loop
Loop

For completeness, following is my module to sort string arrays, ascending or descending:


Module modSortStringArray
'Sort a String Array Alphabetically
'*******************************************************************************
' modSortStringArray - Sort a string array in Ascending or Descending order using
' the Shell-Metzner Sort algorythm. This sort is extremely
' fast. Though longer than QuickSort, it sorts much faster
' with fewer replacments.
'EXAMPLE:
' Dim Test(3) As String 'or Dim Test() As String = {"Bob", "Zed", "Allen", "Rick"}
' Test(0) = "Bob"
' Test(1) = "Zed"
' Test(2) = "Allan"
' Test(3) = "Rick"
' If SortStringArray(Test) Then
' Dim S As String = "The array is sorted:" & vbCrLf
' For Index As Long = 0 To 3
' S = S & " " & Test(Index) & vbCrLf
' Next Index
' MsgBox S
' Else
' MsgBox "The array was not sorted. The string is not an array"
' End If
'*******************************************************************************
Public Function SortStringArray(ByRef StrArray() As String, Optional ByVal SortDescending As Boolean = False) As Boolean
'
' get number of elements to do. Exit if this is not an array
'
Dim NumberofItems As Integer
Try
NumberofItems = UBound(StrArray) + 1 'number of strings to do
Catch
Return False 'Array not dimensioned, so error
End Try
'
' determine if we are sorting in Ascending or Descending order
'
Dim AscDecFlag As Integer = 1 'default to ascending
If SortDescending Then
AscDecFlag = -1 'we will be doing descending
End If
'

Page –32–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

' now perform the sort


'
Dim HalfDown As Integer = NumberofItems 'number of items to sort
Dim HalfUp, IndexLo, IndexHi, IncIndex As Integer

Do While CBool(HalfDown \ 2) 'while counter can be halved


HalfDown \= 2 'back down by 1/2
HalfUp = NumberofItems - HalfDown 'look in upper half
IncIndex = 0 'init index to start of array
Do While IncIndex < HalfUp 'do while we can index range
IndexLo = IncIndex 'set base
Do
IndexHi = IndexLo + HalfDown
If StrComp(StrArray(IndexLo), StrArray(IndexHi), CompareMethod.Binary) = AscDecFlag Then 'check strings
Dim Tmp As String = StrArray(IndexLo) 'swap strings
StrArray(IndexLo) = StrArray(IndexHi)
StrArray(IndexHi) = Tmp
IndexLo = IndexLo - HalfDown 'back up index
Else
IncIndex += 1 'else bump counter
Exit Do
End If
Loop While IndexLo >= 0 'while more things to check
Loop
Loop
End Function
End Module

NOTE: Most VB.NET lists and arrays already have a built-in sort method that employs the QuickSort algorythm that
you can invoke by selecting Array.Sort(strArray), for example. The advantage here is that a sort method is already
present and easy to access. However, the above Shell-Metzner sort is much faster than QuickSort, significantly so in
large lists, and uses fewer replacements. The above version can also sort in descending order, if you wish it.
For your amusement, here, though incomplete, is the guts of the QuickSort used by .NET:
Friend Sub QuickSort(ByVal left As Integer, ByVal right As Integer)
Do
Dim low As Integer = left
Dim hi As Integer = right
Dim median As Integer = Array.GetMedian(low, hi)
Me.SwapIfGreaterWithItems(low, median)
Me.SwapIfGreaterWithItems(low, hi)
Me.SwapIfGreaterWithItems(median, hi)
Dim y As Object = Me.keys.GetValue(median)
Do
Try
Do While (Me.comparer.Compare(Me.keys.GetValue(low), y) < 0)
low += 1
Loop
Do While (Me.comparer.Compare(y, Me.keys.GetValue(hi)) < 0)
hi -= 1
Loop
Catch exception1 As IndexOutOfRangeException
Throw New ArgumentException(Environment.GetResourceString("Arg_BogusIComparer", _
New Object() {y, y.GetType.Name, Me.comparer}))
Catch exception As Exception
Throw New InvalidOperationException(Environment.GetResourceString("InvalidOperation_IComparerFailed"), exception)
Catch obj1 As Object
Throw New InvalidOperationException(Environment.GetResourceString("InvalidOperation_IComparerFailed"))
End Try
If (low > hi) Then
Exit Do
End If
If (low < hi) Then
Dim obj3 As Object = Me.keys.GetValue(low)
Me.keys.SetValue(Me.keys.GetValue(hi), low)
Me.keys.SetValue(obj3, hi)
If (Not Me.items Is Nothing) Then
Dim obj4 As Object = Me.items.GetValue(low)
Me.items.SetValue(Me.items.GetValue(hi), low)
Me.items.SetValue(obj4, hi)
End If
End If
If (low <> &H7FFFFFFF) Then
low += 1
End If
If (hi <> -2147483648) Then
hi -= 1
End If
Loop While (low <= hi)
If ((hi - left) <= (right - low)) Then
If (left < hi) Then
Me.QuickSort(left, hi)
End If
left = low
Else
If (low < right) Then
Me.QuickSort(low, right)
End If
right = hi
End If
Loop While (left < right)
End Sub

Page –33–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

37. Dealing with On Iexpr GoSub


To handle upgrading “On Iexpr GOSUB n1, n2, n3, ...” one once again cannot help but wonder why
such code still existed in VB6 to begin with. The best resolution and fastest means of adapting this
code to operate in VB.NET is to convert this to a Select…Case block, much as outlined in the
previous point, but here it is more ideally suited to address the issue, breaking it down to this:
Select Case IExpr
Case n1
'Invoke subroutine associated with n1
Case n2
'Invoke subroutine associated with n2
Case n3
'Invoke subroutine associated with n3
...
End Select

As you can see, invoking a selection of subroutines is a natural choice for a Select…Case block.
When a subroutine returns, its control will not fall into the next Case block, but will go directly to
the End Select, where program flow will continue.

38. Dealing with updating VB6 error trapping


Although VB.NET still supports “On Error Resume Next” and “On Error Goto 0” unstructured exception
handling to support VB6-style error trapping, you really should consider upgrading it to the more
controlled (encapsulated) Try…End Try structured exception handling statement.
For example, consider the following VB6-style error trapped function as implemented (and still
supported) in VB.NET:
Private Function ReadFile(ByVal FilePath As String) As String()
Dim fso As New FileSystemObject 'using COM object IWshRuntimeLibrary
Dim ts As TextStream

On Error Resume Next 'Reume on errors


ts = fso.OpenTextFile(FilePath, IOMode.ForReading, False) 'open file
If CBool(Err.Number) Then 'if error generated...
MsgBox("Cannot open " & FilePath & ". It does not exist", _
MsgBoxStyle.OkOnly, _
"File Open Error")
Return Nothing 'nothing for invoker to process
End If
On Error GoTo 0 'turn off error Trapping
Dim TxtLines() As String = Split(ts.ReadAll(), vbCrLf) 'place each line in an array element
ts.Close() 'close text stream
Return TxtLines 'return array of text lines
End Function

The preceding can easily be adapted to the following Try…End Try block:
Private Function ReadFile(ByVal FilePath As String) As String()
Dim fso As New FileSystemObject 'use COM object IWshRuntimeLibrary. We should upgrade this to a StreamReader
Dim ts As TextStream 'We really should upgrade all this to a faster System.IO.StreamReader

Try 'try the following...


ts = fso.OpenTextFile(FilePath, IOMode.ForReading, False) 'open file
Catch ex As Exception 'cath errors (you can STILL check Err.Number)
MsgBox("Cannot open " & FilePath & ". It does not exist", _
MsgBoxStyle.OkOnly, _
"File Open Error")
Return Nothing 'nothing for invoker to process
End Try 'end of error trapping
Dim TxtLines() As String = Split(ts.ReadAll(), vbCrLf) 'place each line in an array element
ts.Close() 'close text stream
Return TxtLines 'return array of text lines
End Function

NOTES: You cannot mix VB6-style error trapping and Try…End Try error trapping within the same block of code (one
type in one place, and the other in another place). Choose one or the other for the block. Also, the optional Finally block
segment can precede the End Try statement, but following the last, or only Catch block, holding code that will follow
Try and Catch, regardless of there being errors or not.
There is a lot more to the Catch statement than meets the eye. You can also catch multiple
exceptions by applying multiple Catch phrases, each Catch phrase encapsulating its own type of
error. For example, by adding a ‘When’ clause to a Catch phrase we can narrow down errors. This
way we could specifically trap “File Not Found” errors if we wanted to, and trap all other errors in
another generic block. With that in mind, we can replace the above Catch block with:
Page –34–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

Catch When Err.Number = 53 'catch <File Not Found>


MsgBox("Cannot open " & FilePath & ". It does not exist", _
MsgBoxStyle.OkOnly, _
"File Open Error")
Return Nothing 'nothing for invoker to process
Catch ex As Exception 'general error trap
MsgBox("Error with " & FilePath & "." & vbCrLf & _
ex.Message, _
MsgBoxStyle.OkOnly, _
"File I/O Error")
Return Nothing 'nothing for invoker to process

Each Catch phrase should have its own unique Catch exception filter variable (use a blank Catch line
–a Catch phrase without an exception parameter– if it should catch everything, but you will not need
to process an exception variable). Also, always place the generic “catch-all” trap as the last in the
list, otherwise it might execute before any narrower traps that might also be present are checked.
You can also add an optional Finally block to the bottom of the Try block (before “End Try”). A
Finally block is always executed when execution leaves any part of the Try statement, regardless if
there were errors or not. Although in my examples I used “Return Nothing” to exit from the traps, I
did this because there was nothing else to do. However, a trapped error in no way whatsoever means
that continued processing is not possible.
You can also have Try statements that have a blank Catch block, because it might not matter if
errors were generated or not. However, remember that the Try statement must contain at least one
Catch block, even if the Catch block is empty.
For example, you could have something like this:
Try
'Insert commands you do not care what happens in here

Catch 'blank Catch block, which is immediately transferred to during any exception error in -Try-
End Try

This would be like using “On Error Resume Next” at the beginning of such code, and is also like ending
the block with “On Error Goto 0” in VB6.

39. Dealing with destroying Objects


If you receive a warning that an object may not be destroyed until it is garbage-collected, then you
can bet your last penny that there is no “may” about it. This is not a problem at all (though it does
not stop some people from whining). An object is accessible as long as there is at least one reference
to it. After the last reference is broken (set to Nothing), the randomly-starting garbage collector can
collect and destroy it. Until then, even though the object actually exists, unlike objects in VB6, it
cannot be re-connected to. As such, it is, at least from our application’s perspective, destroyed.
(Now comes the infamous and inevitable “however” part) However, if an object is set to Nothing inside
a procedure, and the very next line of code creates an object of the same name, the first object may not
yet be destroyed and a reference to the new object might incorrectly return the first object (this is all due
to an object tending to persist to the end of its code block under VB). Granted, the chances of this result
raising its ugly head may be stored in the same place where they keep hen’s teeth (though baby chicks do
have a single tooth that they later lose; using it to break out of their shell), but if you ever do encounter
such a scenario, you can keep your code safe by simply invoking the object’s Dispose() method, if it has
one, instead of setting it to Nothing, which will destroy the object immediately, just like VB6 did.
If you break links to a lot of objects, such as a linked list or a branching tree, then you may want to
go ahead and force garbage collection to execute immediately, which is as simple as issuing the
command “GC.Collect()”. Also, if you want to wait until the now-running garbage collector has
completely finished its work (it is a background task that can run while your application is doing
whatever it does), then follow that with “GC.WaitForPendingFinalizers”, which will cause your
application to suspend operations until the garbage collector completes its task.

Page –35–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

40. Dealing with changes to Common Dialogs


Common Dialogs have changed significantly, and in my view, for the better. Still, you are going to
find some interesting warnings that may at first confuse (or worse, panic) you. For example, on a
form I have a CommonDialog control named CommonDialog1. With it I am going to open a text
file and read it in. We will only look at the dialog interface for opening the file in the VB6 code,
below:
With frmSpellCheck.CommonDialog1
.Flags = cdlOFNFileMustExist Or cdlOFNPathMustExist Or cdlOFNLongNames Or cdlOFNExplorer
.DefaultExt = "txt"
.FileName = vbNullString
.Filter = "Text File (*.txt)|*.txt"
.Title = "Open an Existing Text File"
.CancelError = True
On Error Resume Next
.ShowOpen
If CBool(Err.Number) Then Exit Sub
On Error GoTo 0
TxtFile = Trim(.FileName)
If Len(TxtFile) = 0 Then Exit Sub
End With

After an upgrade to VB.NET, our code block looks something like this mini-nightmare:
'UPGRADE_WARNING: CommonDialog variable was not upgraded
With frmSpellCheck.CommonDialog1
'UPGRADE_ISSUE: Constant cdlOFNLongNames was not upgraded.
'UPGRADE_ISSUE: MSComDlg.CommonDialog property CommonDialog1.Flags was not upgraded.
.Flags = MSComDlg.FileOpenConstants.cdlOFNLongNames
'UPGRADE_ISSUE: Constant cdlOFNExplorer was not upgraded.
'UPGRADE_ISSUE: MSComDlg.CommonDialog property CommonDialog1.Flags was not upgraded.
.Flags = MSComDlg.FileOpenConstants.cdlOFNExplorer
'UPGRADE_WARNING: MSComDlg.CommonDialog property frmSpellCheck.CommonDialog1.Flags was upgraded to
frmSpellCheck.CommonDialog1Open.CheckFileExists which has a new behavior.
'UPGRADE_WARNING: MSComDlg.CommonDialog property frmSpellCheck.CommonDialog1.Flags was upgraded to
frmSpellCheck.CommonDialog1Open.CheckPathExists which has a new behavior.
.CheckFileExists = True
.CheckPathExists = True 'MUST EXIST
.DefaultExt = "txt"
.FileName = vbNullString
'UPGRADE_WARNING: Filter has a new behavior.
.Filter = "Text File (*.txt)|*.txt"
.Title = "Open an Existing Text File"
'UPGRADE_WARNING: The CommonDialog CancelError property is not supported in Visual Basic .NET.
.CancelError = True
On Error Resume Next
.ShowDialog()
If CBool(Err.Number) Then Exit Sub
On Error GoTo 0
TxtFile = Trim(.FileName)
If Len(TxtFile) = 0 Then Exit Sub
End With

Although this may look messy, it is actually quite easy and pain-free to clean up. First, the single
CommonDialog control available to VB6 users has (finally) been broken up into five separate
dialogs (which I think they should have been in the first place, considering that the system interface
had kept them separate since, if I recall, Windows 3.0), called OpenFileDialog, SaveFileDialog,
ColorDialog, FolderBrowserDialog, and FontDialog. By default, the Upgrade Wizard will add
“Open” to the end of the CommonDialog control’s name, and the CommonDialog control will be
upgraded to a FileOpenDialog type control. In my case, my CommonDialog1 control (now an
OpenFileDialog object) is now named CommonDialog1Open.
To fix the above problems, I first rename the erroneous CommonDialog1 as CommonDialog1Open.
The reason that this name was not automatically changed in the code to the new control by the
Upgrade Wizard was because the wizard was not sure if you actually wanted one of the other four
dialog controls. This way you will have to apply your personal touch to the control before your
application can actually run. By the way, if you do want to save the file, here or elsewhere, you will
need to add a SaveFileDialog control to your form. If it is an upgraded VB6 project, you might
consider some naming uniformity, so you would rename the new SaveFileDialog1 to
CommonDialog1Save, for instance. This also brings the dialog controls up sequentially on an
Intellisense dropdown list.

Page –36–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

The next thing you should be aware of is that the Flags property has been replaced by individual
Boolean properties; and each enumerator you had applied to it are now set on separate lines; the
actual Flags property replaced by the fitting property allied with the control it is being used with.
But if an applied flag is no longer recognized for that application of the control, then it is simply set
to the now-non-existing Flag property, plus an error warning is issued for each one. If a property is
still being assigned to Flags, it is a safe bet that you can just delete them. That clears another hurdle.
You are next told that the properties CheckFileExists and CheckPathExists have been upgraded to a
new name and a new behavior. The new names are obvious, but the only new behavior is that they
are now individual Boolean properties and they are set to True or False instead of having an
enumerated constant applied. You can ignore the warnings, so simply delete the warning comments.
The next thing we look at is that the filter has a new behavior. This one originally had me confused,
because the formatted text we place into it is clearly the same as we did before in VB6, so I simply
ignore this warning and have yet to have trouble with it. By the way, in case you are new to dialog
filters, we can add filters in pairs, where the first half is purely descriptive, having nothing to do
with actual filtering, such as “Text File (*.txt)”, and the second part is the actual filter pattern, such
as “*.txt”. We combine these by separating them with a pipe “|” character, rendering “Text File
(*.txt)|*.txt”. You can also select alternative selection filters, which the user can select from a
dropdown list, by appending more Description | Filer pairs, separating them from the others with a
pipe character as well. You can even combine multiple filters into a single choice, such as “Image
Files (BMP, JPG, PNG)|*.bmp;*.jpg;*.png”. We separate the patterns in the filter pattern portion with a
semicolon (the descriptive portion is as you choose, but a comma separator is traditional).
The last warning we run into tells us that the CancelError properties is not supported in VB.NET.
The way we deal with it in VB6 is pretty much as shown in our VB6 example. I use the On Error
Resume Next, because when CancelError was set to True, if the user selected Cancel, a Cancel
Exception Error was thrown. The On Error Resume Next prevented the program from aborting on
this error, and then all we had to do is check if Err.Number was non-zero (under VB6, a non-zero
value would be automatically cast to a Boolean, eliminating the need for Cbool(Err.Number), but
that is actually a very bad habit to get into, and VB.NET does not like it when Option Strict is turned
on). We then clear the error trap with On Error Goto 0. Although this type of error trapping is still
accepted, one really should get into using VB.NET’s much superior Try…Catch…Finally format (I
have been using it since VC++ in Visual Studio 6, and it is a treasure).
However, we can completely eliminate the error trapping or setting the now unsupported
CancelError flag. All we need to do is catch the returned value directly from the ShowDialog()
function. If we were testing for multiple replies, such as checking for Abort, Cancel, Retry, Ignore,
Yes, No, OK, or None, I might consider placing it in a Select…Case block, such as:
Select Case .ShowDialog()
Case Windows.Forms.DialogResult.Abort 'if we are doing this within a form, we can shorten this
'Do something here 'to DialogResult.Abort
Case Windows.Forms.DialogResult.Retry 'can be shorted to DialogResult.Retry
'Do another thing here
Case Windows.Forms.DialogResult.Cancel 'can be shorted to DialogResult.Cancel
Exit Sub
End Select

But since we are simply checking for a user Cancel, we can replace all this VB6 code:
.CancelError = True
On Error Resume Next
.ShowDialog()
If CBool(Err.Number) Then Exit Sub
On Error GoTo 0

With this single VB.NET line of code:


If .ShowDialog() = DialogResult.Cancel Then Exit Sub

Page –37–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

With all that quick work (which you will find easier and faster to deal with as you get a few under
your belt), we will quickly end up with the following block of problem-free code:
With frmSpellCheck.CommonDialog1Open
.CheckFileExists = True
.CheckPathExists = True 'MUST EXIST
.DefaultExt = "txt"
.FileName = vbNullString 'in VB.NET, this is the same as 'Nothing'
.Filter = "Text File (*.txt)|*.txt"
.Title = "Open an Existing Text File"
If .ShowDialog() = DialogResult.Cancel Then Exit Sub
TxtFile = Trim(.FileName)
If Len(TxtFile) = 0 Then Exit Sub
End With

41. Dealing with VB6.CopyArray


One upgrade that was initially a little confusing to me was a quiet one (no upgrade warning issued)
that employs the VB6.CopyArray() helper function (from the Microsoft.VisualBasic.Compatibility
namespace) that will convert an array of objects to a system array, happening when the VB6 code
passes any array to another variable. This is because VB6 passed a copy of the array, whereas
VB.NET passes a reference to the array, so the Clone() method is needed to actually copy the array.
However, if you were to set Option Strict On, which I always do, then the return type from
VB6.CopyArray() generates an error, because it returns type System.Array, so we must recast it.
Suppose we had a Class named dynNodes, which has an array named Item of objects named
dynNode, and the dynNode object had a recursive function named GetAllMarked() that returns an
array of all marked dynNode objects in dynNodes. In VB6, suppose we had this block of code:
'*******************************************************************************
' Function Name : GetAllMarked
' Purpose : Get all marked items from this node down
'*******************************************************************************
Friend Function GetAllMarked() As dynNode()
Dim Nds() As dynNode
Dim Cnt As Integer

If m_MyMarker Then 'is THIS node marked?


ReDim Nds(0) 'yes, so save it to the list
Nds(0) = Me 'stuff a reference to self in it
Cnt = 1 'count 1 gathered
Else
Cnt = 0 'else list still empty
End If
'
' now scan through child nodes and gather their lists
'
With m_MyNodes
Dim sNds() As dynNode
Dim UB As Integer
For Index As Integer = 1 To .Count 'process all child nodes
sNds =.Item(Index).GetAllMarked() 'recurse through each for all marked
On Error Resume Next 'error trap
UB = UBound(sNds) 'get upper bounds
If Err.Number = 0 Then 'we have a dimmed array
ReDim Preserve Nds(Cnt + UB) 'make local container bigger
For I As Integer = 0 To UB
Nds(Cnt + I) = sNds(I) 'append new list to local
Next I
Cnt = Cnt + UB + 1 'bump new count
End If
Next Index 'do all child nodes
If CBool(Cnt) Then
GetAllMarked = Nds 'return list
Else
GetAllMarked = 0
End If
End With
End Function

When this code is upgraded, 3 lines are changed (marked with a darker shading, above):
sNds = VB6.CopyArray(.Item(Index).GetAllMarked()) 'recurse through each for all marked

Return VB6.CopyArray(Nds) 'return list

Return 0

These work fine, until you set Option Strict to On (I am a strict typing nut from my many years as a
FORTRAN/C/C++ software engineer). Afterward, they are all flagged in error. The last one simply
has to be changed to return Nothing in order to fix it. The other two report that “Option Strict On
disallows implicit conversions from System-Array to '1-dimensional array of DynamicNodes.dynNode'”.

Page –38–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

If we look at the disassembly of the VB6.CopyArray() function (thanks to the excellent utility .NET
Reflector from RedGate Software (http://Reflector.Red-Gate.com)), we find this simple code:
Public Shared Function CopyArray(ByVal SourceArray As Array) As Array
If (SourceArray Is Nothing) Then
Return Nothing
End If
Return DirectCast(SourceArray.Clone, Array)
End Function

The way to address this issue is simple: in cases where we are passing a freshly constructed array, as
we are in our recursion example, you can simply remove the VB6.CopyArray() function from our
upgraded VB code, but keeping its parameter contents (“sNds =.Item(Index).GetAllMarked()” and
“Return Nds”). However, if you are indeed passing a copy of an existing array, then apply the “.Clone”
method to the source array being passed and cast it to the proper type (i.e., “DirectCast(Nds.Clone,
dynNode)”).

The Clone method would be required because all arrays are reference types in VB.NET. Thus, the
debug output from the following small program will yield “Zero”, not “First” (To fix it, change “Dst
= Src” to “Dst = DirectCast(Src.Clone, String())”):
Module modTestStringCopy
Sub Main()
Dim Src() As String = {"First", "Second", "Third"} 'init first string
Dim Dst() As String 'declare second
Dst = Src 'get src data to dst
Src(0) = "Zero" 'change src array
Debug.Print(Dst(0)) 'see if Dst also changed
End Sub
End Module

NOTES: The Clone method returns a generic type Object. Also, be aware that although simple strings are arrays of
type Char, and are therefore reference types, copying a simple string from one variable to another would normally
require the Clone method, but VB.NET will take care of this for us internally, eliminating our need to address it.
Consider this unnecessary, but working VB.NET code:
Dim Src As String = "123" 'initialize simple source string
Dim Dst As String = DirectCast(Src.Clone, String) 'apply copy to Dst (more work than required. Just use 'Dim Dst As String = Src')
Src = "ABC" 'change source data
Debug.Print(Dst) 'check result; it will report "123", even if we used 'Dim Dst As String = Src'

Because VB.NET actually handles the cloning of simple strings, you can change the declaration of Dst to “Dim Dst As
String = Src”, emulating what is done in VB6. Further, be aware that the Clone method greatly simplifies what is
required in C++. Be aware that all this is actually done in VB6, but “behind the curtain”. It had to be brought more out
into the open for reasons of cross-language operability, but the process is still simplified enough in VB.NET to not
remove its RAD (Rapid Application Development) moniker. Copying strings in C/C++ can be a big bother, and almost a
nightmare to copy complex arrays. VB.NET still has it way too easy.

42. Dealing with the loss of the ItemData List Object property
VB.NET no longer supports the simple ItemData property of a ListBox or ComboBox. In VB6, the
ItemData property could be set at design time in the Properties window to associate an Integer with
a ListBox or ComboBox item, often used to associate an ID number with a text string. In VB.NET,
the ItemData property no longer exists, but this is due to the more powerful functionality given to
Listboxes/Comboboxes, where list items are now able to be of any object, not just as simple text.
In the upgrade from VB6 to VB.NET, you may notice that the upgrade uses the VB6.SetItemData()
method to initialize any design time ItemData information, usually invoked in the constructor of the
parent form (Public Sub New). To access the ItemData information, it uses the VB6.GetItemData()
method to emulate the functionality of the lost VB6 ItemData property. For example, “Dim I As
Integer = VB6.GetItemData(List1, List1.SelectedIndex)”. To obtain the text from ItemData, it used
VB6.GetItemString(), as in “Dim Result As String = VB6.GetItemString(List1, List1.SelectedIndex)”.
Because I personally want to avoid using code-heavy helpers and get rid of the VB6 Compatibility
Library reference altogether (the reference to the Microsoft.VisualBasic.Compatibility namespace),
and I would simply rather write my applications in native VB.NET code, I would instead extract my
string using “Dim Result As String = List1.Items(List1.SelectedIndex).ToString()”. However, it does

Page –39–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

not emulate all that the VB6 helper did, which, if you looked at the VB6 class’s source code using
Red-Gate Software’s .NET Reflector (http://Reflector.Red-Gate.com), it is quite extensive, and eats
up a lot of precious time (though I will give it kudos for providing a solution to the issue).
So what I want to do, at the cost of only a little (re-usable) work, but with the benefit of involving
significantly less overall code and overhead, I would first create a simple ItemData class, such as the
following, which is based on the ListBoxItem class used within the VB6 helper:
Public Class ListItem
'-------------------------------
' Field Data - You can store more than just these fields here
'-------------------------------
Public ItemString As String 'Text data for Item
Public ItemData As Integer 'You can also declare this as String, if you want a string key.
'-------------------------------
' Custom constructor; used to actually add data to a Listbox or Combobox.
' You are not limited to adding just one or two items, or of just these types
'-------------------------------
Public Sub New(ByVal ItemString As String, ByVal ItemData As Integer)
Me.ItemString = ItemString ' You may want to add more string parameters
Me.ItemData = ItemData ' and then combine them with a space separator in ToString
End Sub ' or even via a custom reporting method. Limitless possibilities!
'-------------------------------
' Constructor for assigning just text and no ItemData
'-------------------------------
Public Sub New(ByVal ItemString As String)
Me.New(ItemString, 0)
End Sub
'-------------------------------
' Provide a text data property to override the useless default in the Item() object.
' you can also use this to combine stored items when more than one text item is added.
'-------------------------------
Public Overrides Function ToString() As String
Return Me.ItemString
End Function
End Class

I would assign new text and indexes to my Listbox named ListBox1 using something like this:
Me.ListBox1.Items.Add(New ListItem("David", 50159))

Extracting the text data can be done easily enough:


Dim SName As String = Me.ListBox1.Items(Index).ToString ' you can also specify ItemString instead of ToString

But extracting the index trades off with slightly more work, but it is still simple enough:
Dim Idx As Integer = DirectCast(Me.ListBox1.Items(Index), ListItem).ItemData

Because Items(Index) returns a generic object, we have to take the extra step of directly casting it to
the type that we know it contains, which is a ListItem object. But if you have a lot of these to punch
into the keyboard, this can result in a lot of typing. But even so, we can still employ less typing and
much less code overhead than using VB6.GetItemData() and VB6.GetItemString() simply by
writing our own little helper function, which we can add to a small module at the end of the class:
Public Function ExtLI(ByRef Obj As Object) As ListItem
If TypeOf Obj Is ListItem Then 'if the object is type ListItem
Return DirectCast(Obj, ListItem) 'return the object as a ListItem
End If
Return Nothing 'else return a null object
End Function

Using the above helper function, you can now obtain the item data using the following:
Dim Idx As Integer = ExtLI(Me.ListBox1.Items(Index)).ItemData

And to get the item string, we can use either of the following two methods:
Dim SName1 As String = Me.ListBox1.Items(Index).ToString 'Method 1 example
Dim SName2 As String = ExtLI(Me.ListBox1.Items(Index)).ItemString 'Method 2 example

43. Dealing with changes to Font manipulation


One big change from VB6 to VB.NET is font manipulation. Under VB6 you could toggle Bold,
Italic, Underline, and Strikethrough, and set Font Size and Name on the fly. Under VB.NET, these
are set at development time, but setting them at runtime at first seems impossible. The only way to
change fonts in VB.NET is to do it literally: change the font to a brand new font with different
characteristics. This is actually what is done in VB6, but we just do not see it happen; it was done for
us behind the scenes. Which approach is better? I say the VB.NET method, and the reason I say this
is because you not only see and know exactly what is going on, but if you later have to transition the
Page –40–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

VB.NET code over to C# or C++, the transition is much smoother, because this process is exactly
what you would have to do in those environments. The same now applies when transitioning C# and
C++ code to VB.NET. As a C++ developer, I am very much used to and happy with this approach.
When we upgrade a VB6 program to VB.NET, the VB6 Compatibility Library takes care of these
processes for you. But you may actually be wondering how would you do it in a new program? It is
surprisingly simple. All you have to really do is create a new font that can be based on the old one
you are replacing, and then apply a new state to it. For example, suppose Label1 has a size of 10
points, and you want to simply set it to 12 points. You might do something like the following:
Me.Label1.Font = New Font(Me.Label1.Font.Name, 12, Me.Label1.Font.Style, GraphicsUnit.Point, Me.Label1.Font.GdiCharSet) 'NOTE:
Overloaded methods actually allow the last 2 parameters to be dropped

What we did was simply create a new font using the old font’s name, style, character set, as well as
set it to using Points (default), and with a new size (12). Of course, you may not want to change it if
it is already set to the desired size. We can accommodate this with a single function (be sure to also
import the System.Drawing namespace in the heading of the file):
'***************************************************************************************************************
' FontChangeSize - Set/reset Selected Font Size
' CurrentFont = font reference to change
' NewSize = point size to set
'***************************************************************************************************************
Public Function FontChangeSize(ByVal CurrentFont As Font, ByVal NewSize As Single) As Font
If (Math.Round(CDbl(CurrentFont.SizeInPoints), 2) = Math.Round(CDbl(NewSize), 2)) Then
Return CurrentFont
End If
Return New Font(CurrentFont.Name, NewSize, CurrentFont.Style)
End Function

To use it, we simply issue the command “Me.Label1.Font = FontChangeSize(Me.Label1.Font, 12)”.

NOTE: In case you did not notice it, we can borrow font settings from other controls that are similarly set, or already
set to the desired format. We can even do this: “Me.Label1.Font = New Me.TextBox1.Font”.
To change the fonts Bold, Italic, Underline, or Strikethrough state is even easier, due to other
overloaded prototypes for Font. Suppose I wanted to set Label1 to have a Bold style, I might try this:
Me.Label1.Font = New Font(Me.Label1.Font, FontStyle.Bold) 'base on an existing font, but alter style

Or add more than one style simultaneously, like this:


Me.Label1.Font = New Font(Me.Label1.Font, FontStyle.Bold Or FontStyle.Italic) 'apply bold + italic

This works great, but toggling is a bit of a problem, especially if we want to turn on or off a single
style option, but leaving others alone. The best approach is to test if a change is needed, and then if it
is, to apply the appropriate state. We can do that with another simple function, like the one below:
'****************************************************************************************************
' FontChangeStyle - Support method for Bold, Italic, Underline, StrikeThrough set/reset
'****************************************************************************************************
Private Function FontChangeStyle(ByVal CurrentFont As Font, _
ByVal StyleFlag As System.Drawing.FontStyle, _
ByVal SetStyle As Boolean) As Font
'mask desired style against current (do this in case multiple selections)
Dim fntStyle As FontStyle = (CurrentFont.Style And StyleFlag)
'set flag to true if selected style is set, or if all selected styles are already set
Dim flag As Boolean = (fntStyle = StyleFlag)
'if not EXACT match because something IS different, then FORCE change
If Not flag AndAlso (fntStyle <> FontStyle.Regular) Then flag = Not SetStyle
If (flag = SetStyle) Then 'if nothing will change, then simply return the current font
Return CurrentFont
End If
'define a new style value minus the current selection(s), based on current font style settings
Dim newStyle As FontStyle = DirectCast(CurrentFont.Style And Not StyleFlag, FontStyle)
If SetStyle Then 'are we setting the new style(s)?
newStyle = (newStyle Or StyleFlag) 'yes, so set new style(s) to new font
End If
Return New Font(CurrentFont, newStyle) 'return new font based on current, w/selection set/reset
End Function

In the above function, we set the state of flag to True if the style we selected, such as FontStyle.Bold,
is already set. We then check it against the SetStyle flag. If we want to set bold (SetStyle = True), but
bold is already set (flag = True), then there is nothing to do and the old font is returned. If the state of
SetFlag does not equal flag, then we have something to do. We first define a local variable that will
contain the style flags without our selected style, in case we will be toggling it off (SetStyle = False).
We then check the SetStyle value, and if it is set to True then we will apply the selected style to the
Page –41–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

flag value. Finally, we define a new font based on the old one, and apply the new style change to it.
The great thing about this flag is that we can set multiple styles at once. Suppose we want to set both
Bold and Italic styles to the label. We can do this by using this command “Me.Label1.Font =
FontChangeStyle(Me.Label1.Font, FontStyle.Bold Or FontStyle.Italic, True)”. To toggle a selected style
off, we simply change the state of the SetStyle parameter to False.
The last thing we might want to do is to change the actual font. Suppose we wanted to change the
font of our label to Courier New. We could do this:
Me.Label1.Font = New Font("Courier New", _
Me.Label1.Font.Size, _
Me.Label1.Font.Style)

But this is a lot of work if we have to do a lot of it. We can instead create a simple little function that
will do all the dirty work for us, such as this:
'****************************************************************************************************
' FontChangeName - Set/reset Selected Font Name
' CurrentFont = font reference to change
' NewName = new font family to change it to
'****************************************************************************************************
Public Function FontChangeName(ByVal CurrentFont As Font, ByVal NewName As String) As Font
If (CurrentFont.Name = NewName) Then
Return CurrentFont
End If
Return New Font(NewName, _
CurrentFont.SizeInPoints, _
CurrentFont.Style)
End Function

With this function, all we have to do to change the font to Courier New is execute this statement:
Me.Label1.Font = FontChangeName(Me.Label1.Font, "Courier New")

But suppose we want to set an entirely new font, or change more than one property, such as the font size
and the style. We could write, as I have, a function to do all this, but in the end, probably the easiest
method is to simply define a new font on the spot, just as had been demonstrated at the beginning of this
point. For example, suppose I want to change the font of Label1 to Courier New, the point size to 12, and
the style to Bold and Italic. All I would have to do is this:
Me.Label1.Font = New Font("Courier New", 12, FontStyle.Bold Or FontStyle.Italic)

Or, if we will be doing a lot of this, perhaps a full function is in order after all. Here is my complete
module, which is based on the font support provided by the VB6 Compatibility Library:
Imports System.Drawing
Module modFontChanges
'***************************************************************************************************************
' FontChangeBold - Set/reset Selected Font Bold
' CurrentFont = font reference to change
' Bold = True:Set, else reset
'***************************************************************************************************************
Public Function FontChangeBold(ByVal CurrentFont As Font, _
ByVal Bold As Boolean) As Font
Return FontChangeStyle(CurrentFont, _
FontStyle.Bold, _
Bold)
End Function

'***************************************************************************************************************
' FontChangeItalic - Set/reset Selected Font Italic
' CurrentFont = font reference to change
' Italic = True:Set, else reset
'***************************************************************************************************************
Public Function FontChangeItalic(ByVal CurrentFont As Font, _
ByVal Italic As Boolean) As Font
Return FontChangeStyle(CurrentFont, _
FontStyle.Italic, _
Italic)
End Function

'***************************************************************************************************************
' FontChangeUnderline - Set/reset Selected Font Underline
' CurrentFont = font reference to change
' Underline = True:Set, else reset
'***************************************************************************************************************
Public Function FontChangeUnderline(ByVal CurrentFont As Font, _
ByVal Underline As Boolean) As Font
Return FontChangeStyle(CurrentFont, _
FontStyle.Underline, _
Underline)
End Function

Page –42–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

'***************************************************************************************************************
' FontChangeStrikeout - Set/reset Selected Font Strikeout
' CurrentFont = font reference to change
' Strikeout = True:Set, else reset
'***************************************************************************************************************
Public Function FontChangeStrikeout(ByVal CurrentFont As Font, _
ByVal Strikeout As Boolean) As Font
Return FontChangeStyle(CurrentFont, _
FontStyle.Strikeout, _
Strikeout)
End Function

'***************************************************************************************************************
' FontChangeSize - Set/reset Selected Font Size
' CurrentFont = font reference to change
' NewSize = point size to set
'***************************************************************************************************************
Public Function FontChangeSize(ByVal CurrentFont As Font, _
ByVal NewSize As Single) As Font
If (Math.Round(CDbl(CurrentFont.SizeInPoints), 2) = Math.Round(CDbl(NewSize), 2)) Then
Return CurrentFont
End If
Return New Font(CurrentFont.Name, _
NewSize, _
CurrentFont.Style)
End Function

'***************************************************************************************************************
' FontChangeName - Set/reset Selected Font Name
' CurrentFont = font reference to change
' NewName = new font family to change it to
'***************************************************************************************************************
Public Function FontChangeName(ByVal CurrentFont As Font, _
ByVal NewName As String) As Font
If StrComp(CurrentFont.Name, NewName, CompareMethod.Text) = 0 Then
Return CurrentFont
End If
Return New Font(NewName, CurrentFont.SizeInPoints, CurrentFont.Style)
End Function

'****************************************************************************************************
' FontChangeStyle - Support method for Bold, Italic, Underline, StrikeThrough set/reset
'****************************************************************************************************
Private Function FontChangeStyle(ByVal CurrentFont As Font, _
ByVal StyleFlag As System.Drawing.FontStyle, _
ByVal SetStyle As Boolean) As Font
'mask desired style against current (do this in case multiple selections)
Dim fntStyle As FontStyle = (CurrentFont.Style And StyleFlag)
'set flag to true if selected style is (or all selected styles are) already set
Dim flag As Boolean = (fntStyle = StyleFlag)
'if not EXACT match because but something IS different, then FORCE change
If Not flag AndAlso (fntStyle <> FontStyle.Regular) Then flag = Not SetStyle
If (flag = SetStyle) Then 'if nothing will change, then simply return the current font
Return CurrentFont
End If
'define a new style value minus the current selection(s), based on current font style settings
Dim newStyle As FontStyle = DirectCast(CurrentFont.Style And Not StyleFlag, FontStyle)
If SetStyle Then 'are we setting the new style(s)?
newStyle = (newStyle Or StyleFlag) 'yes, so set new style(s) to new font
End If
Return New Font(CurrentFont, newStyle) 'return new font based on current, w/selection set/reset
End Function

'***************************************************************************************************************
' ChangeFont - Support changing multiple properties
'***************************************************************************************************************
Public Function ChangeFont(ByVal CurrentFont As Font, _
Optional ByVal NewName As String = vbNullString, _
Optional ByVal NewSize As Single = 0, _
Optional ByVal StyleFlag As System.Drawing.FontStyle = FontStyle.Regular, _
Optional ByVal SetStyle As Boolean = False) As Font

Dim Changes As Boolean = False


Dim flag As Boolean = False

Dim Nam As String = CurrentFont.Name 'get current name


Dim Siz As Single = CurrentFont.Size 'get current point size
Dim Styl As FontStyle = CurrentFont.Style 'get current style flags
Dim fntStyle As FontStyle = (Styl And StyleFlag)

If CBool(Len(NewName)) Then 'if we will change the font family name...


If StrComp(Nam, NewName, CompareMethod.Text) <> 0 Then 'if name actually changes
Nam = NewName 'set new name
Changes = True
End If
End If

If NewSize <> 0 Then 'if we will change the point size, and diff. is big enough...
If (Math.Round(CDbl(CurrentFont.SizeInPoints), 2) <> Math.Round(CDbl(NewSize), 2)) Then
Siz = NewSize 'set new size
Changes = True
End If
End If

If StyleFlag <> FontStyle.Regular Then 'if we are setting a style flag


'set flag to true if selected style is (or all selected styles are) already set
flag = (fntStyle = StyleFlag)
'if not EXACT match, but something IS different, then FORCE change
If Not flag AndAlso (fntStyle <> FontStyle.Regular) Then flag = Not SetStyle
If (flag <> SetStyle) Then 'if something will change...

Page –43–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

'define a new style value minus the current selected type, based upon the current font style settings
Styl = DirectCast(CurrentFont.Style And Not StyleFlag, FontStyle)
If SetStyle Then 'are we setting the new style?
Styl = (Styl Or StyleFlag) 'yes, so set new style to flag
End If
Changes = True
End If
End If
'if there are changes, try returning new font
If Changes Then
Try
Return New Font(Nam, _
Siz, _
Styl)
Catch 'on error, fall through
End Try
End If
Return CurrentFont 'return current if nothing to change, or error
End Function

End Module

44. Dealing with changes to Form commands


Some form commands have changed. You may notice that a few of the standard VB6 form
processing commands you often used in VB6 no longer exist in VB.NET. Well, actually, they still do
exist, but just in a form more open to your inspection than they were before, where most everything
was hidden from you. These changes were not put in place to frustrate you; they were designed to be
better tuned to a more focused and advanced RAD paradigm. Indeed, the whole reason code had
been hidden from you before was to promote previous RAD principles. However, it has become
quite clear that most VB developers are a fast, intelligent lot, and so the focus on maximum gizmo
simplicity has changed focus to better sustain this hoard, bristling with creativity.
When I first started using VB.NET, I felt a bit uncomfortable because some familiar VB6 form-
processing commands seemed to be missing, such as Load, Unload, and the Query_Unload event.
But as I researched them and experimented, I found that the way to handle these features was the
way that the VB6 compiler had previously done it for us, but behind the scenes.
Also, I noticed that events that I previously depended on were either not needed, or they fired in a
different sequence. Under VB6, we used the I Love (or Loathe) RAP slogan to remember the order:
Initialize, Load, Resize, Activate, and Paint. Under VB.NET, the Initialize process no longer fires an
event (this is now taken care of when the Form New (constructor) method invokes the
InitializeComponent method). The TextChanged and the Resize events first fire during that
initialization process (which makes sense, because the PerformLayout method is invoked at the end
of InitializeComponent). Indeed, the exact order in VB.NET is now TRLAP: TextChanged, Resize,
Load, Activate, and Paint. Trouble Really Loves A Programmer?
The Load command in VB6 was a means to instantiate objects. Under VB6, we typically referenced
them through loading them into an indexed array. Forms were different in that you could create new
instances of a form and assign them to a reference variable. One thing you can still do in VB.NET is
to simply load the form and address it through its class name. We could also ignore the Load
command and go directly to Show. For example, if we had a form named Form2, we could
instantiate it to a reference variable like this:
Dim Form2 As Form2 'declare a reference pointer to type Form2
Set Form2 = New Form2 'declare new instance of prototype form (this and the previous command can be handled by 'Load Form2')
Form2.Show Me, vbModal 'show new form, make current form its parent (this command could auto-execute the previous 2 commands)
Unload Form2 'remove instance of new form (close the form and remove it from memory)

The vbModal option allowed the new form to be displayed as a dialog box, where control of the
application cannot continue until the new form is closed. The vbModeless parameter told it to
display like a regular form, not hogging focal control. VB.NET now has two display methods that
clearly reflect these principles: Show(), which shows the form as an ordinary window (exactly like
vbModeless), and ShowDialog(), which shows the form as a dialog form, controlling program flow
until it closes (exactly like vbModal).

Page –44–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

The above VB6 code would be written in VB.NET like this:


Dim Form2 As New Form2 'declare new instance of prototype form
Form2.ShowDialog(Me) 'show new form, make current form its parent
Form2.Close() 'close form, It will release resources if opened by the Show() method, but if you used ShowDialog()...
Form2.Dispose() 'immediately deallocate form resources; add this if form is dialog or was instantiated but not displayed

The Close() method is like VB6’s Unload. Although you probably could get by without invoking the
Dispose() method, using only Close(), but if you know anything about any class that features a
Dispose() method, then you would know that it is a very good idea to invoke it, because it causes all
of its resource to be removed now, not later, when the randomly running garbage collector
encounters it and figures out that it is not being used anymore. If you look at the documentation for
the Close() method (search Help for Form.Close), it states “When a form is closed, all resources created
within the object are closed and the form is disposed. You can prevent the closing of a form at run time by handling the
Closing event and setting the Cancel property of the CancelEventArgs passed as a parameter to your event handler. If
the form you are closing is the startup form of your application, your application ends. The two conditions when a form
is not disposed on Close is when (1) it is part of a multiple-document interface (MDI) application, and the form is not
visible; and (2) you have displayed the form using ShowDialog. In these cases, you will need to call Dispose manually to
mark all of the form's controls for garbage collection.” Elsewhere, it states the point more directly: “Dispose
will be called automatically if the form is shown using the Show method. If another method such as ShowDialog is used,
or the form is never shown at all, you must call Dispose yourself within your application.”
NOTE: If you invoke Dispose() before Close(), the FormClosing event will not fire. You can also invoke Dispose() from
within the FormClosed event if you wish to (good idea). Further, you can invoke Dispose() after Close() even if it was
not opened by ShowDialog(), though in this case it will do nothing, because the form had already been disposed.
We can emulate the VB6 Show method easily, if we cannot live without it, using a simple module:
Module modShowForm
Public Enum ShowFormConst As Integer
Modal = 1 'as dialog box
Modeless = 0 'as normal window
End Enum

'*************************************************************************
' ShowForm emulates the VB6 method of displaying a form, specifying a
' parent, and selecting a display mode (Modal or Modeless)
'*************************************************************************
Public Sub ShowForm(ByVal ThisFrm As Form, _
Optional ByVal Modal As ShowFormConst = ShowFormConst.Modeless, _
Optional ByVal OwnerFrm As Form = Nothing)
ThisFrm.Owner = Nothing 'ensure no parent
If (OwnerFrm IsNot Nothing) Then 'new owner exists
ThisFrm.Owner = OwnerFrm 'set new owner
End If
If (Modal = ShowFormConst.Modeless) Then 'modeless?
ThisFrm.Show() 'yes, so show normally (automatically invokes Dispose() method)
Else
ThisFrm.ShowDialog() 'else show as dialog
ThisFrm.Dispose() '(we must invoke Dispose manually. It will not be invoked by ShowDialog because
End If ' it may be returning a result to the invoker, which would be lost by Dispose)
End Sub
End Module

To use it, we provide the method with our form, the optional modal setting (default is Modeless), and
the optional parent window, in the form: “ShowForm(frm, FormShowConstants.Modal, Me)”.
The weird thing about the Load command was that it was not really necessary with forms, unless
you wanted to load a form without displaying it. This way you could load the form, initialize some
control fields, and then display the form once it is set up. The documentation indicated that the Load
command simply instantiated the form. So basically the less obvious VB6 “Load Form2” command
could have been used in place of the much clearer “Set Form2 = New Form2” command.
This brings us to an issue I have with VB. During the Load event, we could initialize components,
which is a handy place to do that sort of thing, but then we could also display it using the Show()
method while we are still executing the Load event. That never settled well with me. It is still legal
under VB.NET, sad to say. Everything must have an order to it. VB.NET should be more stringent
in this situation. I just never saw the logic in the ILRAP/TRLAP sequences being able to be
processed out of their defined orders. It makes sense to me in the new VB.NET TRLAP sequence
that TextChange events fire before Resize because of the anchoring capabilities of VB.NET. A

Page –45–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

dynamic label or textbox should reflect and resize its bounds before a form Resize event fires. And
Activate and finally Paint, in perfectly logical order, should not fire until the Load process has
finished doing its thing. The final thing to do is always painting. It is also in this last event that you
should do any special shaping commands3, such as draw form-surface lines and circles and such. It
makes much more sense to me if Microsoft had forced an LTRAP sequence, which would have
resolved every sequencing frustration we have had with VB.NET form processing. But during the
Load event, changes made to labels and fields can cause the TextChanged and Resize events to fire
again, so that would frustrate an LTRAP sequence. So we are forced to dealing with these
idiosyncrasies with a flag, such as using the Boolean mFormLoaded flag I outlined in Point 21.
One event I once thought lacking in VB.NET was the VB6 QueryUnload event. This event had fired
before the Unload event. I often used it to test if the user had chosen to close the form using either
the window frame’s menu or had selected the “X” icon in the upper right corner of the frame, or they
had pressed ALT-F4. I simply had to check the UnloadMode parameter for a value of
vbFormControlMenu. If this test was true, then the user was blowing out. You could also check in a
Multi-Document Interface form for a value of vbFormMDIForm to see if a child form was closing,
or if the form owner was closing by testing for vbFormOwner. If I decided I did not want a form to
close, I would set the Cancel parameter to a non-zero value (usually 1 or True).
Finally, I took a much closer look at the FormClosing event (Private Sub Form1_FormClosing(ByVal sender As
Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing). There was the answer in
front of me. Apart from the “ByVal sender As Object” parameter, the second parameter, e, was of type
FormClosingEventArgs. So in the body of the method I typed ‘e’ and then the dot. This offered me two
properties: “Cancel”, a flag I could use to check or set if the form should close, and “CloseReason”,
a read-only property that provides a value indicating to me why the form is being closed. These
values are defined in the following enumeration within the System.Windows.Forms namespace:
Public Enum CloseReason
None 'the closing reason could not be defined
WindowsShutDown 'the operating system is shutting down
MdiFormClosing 'an MDI child form is closing
UserClosing 'the user is closing the window
TaskManagerClosing 'Windows Task Manager is closing the window
FormOwnerClosing 'the form's owner is closing
ApplicationExitCall 'Application.Exit() method was invoked
End Enum

Therefore, if I want to cancel the form closing because the user is trying to close the form (assuming
that some data-critical application is running that must run to its end, otherwise data will be
corrupted, for example), I might write my FormClosing event like this:
Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
If e.CloseReason = CloseReason.UserClosing And CriticalProcessing = True Then e.Cancel = True
End Sub

45. Dealing with VB6’s automatic Boolean conversions


If you are one to use values in integer variables and fields as Boolean flags in conditional statements,
such as using “If Idx Then” to test if the integer variable Idx contains a zero (False) or non-zero
(True) value, if you have Option Strict turned On (which I highly recommend), you will need to
properly cast them to Boolean, such as “If CBool(Idx) Then”.

3
VB.NET lacks native shaping controls because VB.NET cannot work with windowless (lightweight) controls, but it can work
around this using Microsoft’s free Visual Basic Power Packs (http://msdn.microsoft.com/en-us/vbasic/bb735936.aspx),
which deliver new controls for your IDE toolbox, featuring line and shape controls that can draw lines, rectangles and ovals
at design time, eliminating the need to draw shapes manually in the form’s Paint event. This pack also includes a printer
control and collection, emulated printer I/O from VB6, a PrintForm component to allow you to print forms as you did in
VB6, and a really neat data repeater that allows you to display rows of data in a scrollable container.
Page –46–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

46. Dealing with Option Strict On issues


One thing that will crop up, not as an upgrade warning, but as a program error during compiling if
you have Option Strict turned On (I always have this and Option Explicit turned On; more work for
us during development, but worth its weight in video games if you want to have clear, concise code
that is always early-bound, and therefore runs much faster), is that if you work with Short integers
(former Integers in VB6) or Single precision floating point, you will often get compiler errors
because Short values are being promoted to Integer, and Singles are promoted to Doubles. This is
especially noticed when the receiving medium cannot accept these greater-precision values, such as
method parameters that are declared as Short or Single. Consider these examples:
Dim shValue As Short = 0
shValue += 1 'generates "Option Strict On disallows conversions from 'Integer' to 'Short'"
Dim sngValue As Single = 0.0
sngValue += 1.0 'generates "Option Strict On disallows conversions from 'Double' to 'Single'"

The reason the compiler errors occur is due to the fact that expressions cast their component parts to
their equal or most senior member. If you let the mouse hover over any of the literal values, you will
see why we run into problems. ‘0’ and ‘1’ are by default considered to be Integer values, and “0.0”
and “1.0” are by default considered Double values. The values seem to work well enough during
initial assignment, but they run into problems when they are in multi-type expression statements. We
will always run into this problem when working with these smaller types because the default types in
VB.NET are Integer for non-floating point values and Double for floating point values.
The way around this is simple. Just append the offending integer literals with “S”, which will cast
them to type Short, and append “!” to the offending floating point literals to cast then to type Single.
More, you should also do the same during the assignments, even though there is no error reported,
because you are never really sure when the values are going to be demoted to the appropriate type. If
they are demoted during run-time, then that requires extra computer cycles to convert those values to
the proper type as a late-bound process. But even if the compiler automatically demoted them during
compilation so that their conversion was early-bound, it still does not clear up the problem for
someone reviewing your code and they see that the values are displayed in their promoted state.
Here are some more Literal Type Characters appending flags, so that you can ensure literal values
are what you would expect, or need them to be:
Value Type VB6 Symbol VB.NET Symbol(s) VB.NET Example(s)
Char Not Available c String(" "c, 128)
Short Not Available S 123S
Integer % % or I 123% or 123I
Long & & or L 123& or 123L
Single ! ! or F (Float) 123.45! or 123.45F
Double # # or R (Real) 123.45# or 123.45R
Decimal Not Available @ or D 123.45@ or 123.45D

NOTE: It may seem redundant to use a ‘C’ tag because a single character of a string is a Char, it is actually an element
in an array of 1 of type Char. For example, “Dim s As String = New String(" ", 128)” will generate the error
“Option Strict On disallows conversions from 'String' to 'Char'.” Use instead Chr(32) or ChrW(32) or CChar(" ") or " "c.

Page –47–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

47. Dealing with Image and Picture Object upgrades


Coping with not being able to copy a Picture property to an Image property is a big problem that has
cornered most developers migrating over to VB.NET. Sometimes an upgrade will involve using
VB6 or VB5 COM components right within the VB.NET upgraded application, such as VB5’s
Common Control ComCtl32.dll, though the Upgrade Wizard will copy it locally and rename it
axInterop.ComCtlLib.dll (a lot of us used this library in VB6 because it allowed us to display our
windows using XP styles when we included a manifest file). There is nothing wrong with doing this,
and your application can even be stronger for it, because additional resources, especially a huge
reserve like all the COM-based code generated from Visual Studio 6, equals superior strength.
However, I have also seen these controls used for importing image lists, and the problem with the VB5/6
ImageList control is that they only have a Picture property. That worked OK in VB5 and VB6, because a
PictureBox control has both an Image and a Picture property, and so you could assign the Imagelist’s
Picture property to the PictureBox’s matching Picture property like this: “Me.PictureBox1.Picture =
Me.ImageList1.ListImages(4).Picture”. A VB.NET PictureBox control may have much more muscle, but
it is also leaner, having only an Image property (fixing the persistence problem eliminated the need for
two separate properties). And being that VB.NET does not support lightweight controls, the VB6 Image
control went away, but it had also become mostly dead weight now that a PictureBox was able to serve
both purposes. That is, except that an Image control could treat icon transparency colors as transparent.
PictureBoxes cannot (well, directly, anyway). I think Microsoft had a little screw up here. (Still, there is
a way to get PictureBox controls to display Icons, complete with transparency fields: refer to the article,
Placing Movable Images with Transparent Backgrounds on a Form, within the companion to this document,
Enhancing Visual Basic .NET Far Beyond the Scope of Visual Basic 6.0 (www.slideshare.net/davidrossgoben).)
We now have an annoying problem: A VB.NET PictureBox lacks a Picture property. When we
upgrade our VB6 project, the line shown above is upgraded to “Me.PictureBox1.Image =
Me.ImageList1.ListImages(4).Picture”. It compiles fine. It might even get chatty and ask you to add
some references to the fore-mentioned ax-control, but at least it will do it for you automatically if
you authorize it. But when you try to run it, you are told “Unable to cast COM object of type
'System.__ComObject' to class type 'System.Drawing.Image'. Instances of types that represent COM components cannot
be cast to types that do not represent COM components; however they can be cast to interfaces as long as the underlying
COM component supports QueryInterface calls for the IID of the interface.”
Yeah, yeah, whatever… I do happen to know that a stdole.Picture is the same as a VB6 Picture, but
it was still COM. However, from my C++ days, I recalled that I converted images through their
Handle property. I threw the following module together to address situations when the VB5/6
ImageList contains Bitmaps and/or Icons, and you need to convert them to VB.NET-style Images.
Module modPicToImg
'Reference to .NET stdole required (If COM OLE Automation is referenced, then stdole is ALREADY referenced at a deeper level)
'******************************************************************************************
' PicToImage: Convert VB6 Bitmap picture to VB.NET Image (Bitmap)
'******************************************************************************************
Public Function PicToImage(ByVal picProperty As stdole.IPictureDisp) As System.Drawing.Image
Return System.Drawing.Image.FromHbitmap(CType(picProperty.Handle, IntPtr))
End Function

'******************************************************************************************
' IcnToImage: Convert VB6 Icon picture to VB.NET Image (Bitmap)
'******************************************************************************************
Public Function IcnToImage(ByVal picProperty As stdole.IPictureDisp) As System.Drawing.Image
Return System.Drawing.Icon.FromHandle(CType(picProperty.Handle, IntPtr)).ToBitmap
End Function
End Module

NOTE: COM’s OLE Automation may have been auto-added during upgrades if you the upgrade involved ImageList controls. Also,
stdole can be added to Visual Studio .NET through the free Visual Studio Tools for Office, available from Microsoft:
www.microsoft.com/downloads/details.aspx?familyid=54EB3A5A-0E52-40F9-A2D1-EECD7A092DCB&displaylang=en. However,
before you add it, you might check your project properties and see if stdole is already installed. Just go to the References, hit Add, and
check under the .NET list for stdole. It may have been installed through the Primary Interop Assemblies. You could even find 3 or 4
different instances of stdole declared within the .NET Reference list; do not worry about which one to pick – just choose one of them).

Page –48–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

To employ these conversion functions, you can use something similar to “Me.PictureBox1.Image =
PicToImage(Me.AxImageList1.ListImages(1).Picture)” to copy a bitmap/picture, or “Me.PictureBox1.Image =
IcnToImage(Me.AxImageList1.ListImages(2).Picture)” to copy an icon.

This is fast, but if you need to process them even faster, you will have to work around this Picture
format conversion process by opening the VB5/6 ImageList control by selecting its form-top control
to reveal a little option expansion selector, and then go into its ActiveX properties and make a note of
the image size on the General tab, then on the Image tab, write down each image, index, key, and
tag, if any of the additional properties are set. Next, add a VB.NET ImageList to your form, and then
commence filling it with duplicate data. Mind you, the VB.NET ImageList is 0-based, as opposed to
the VB5/6 ImageList, which is 1-based, and so you will want to stuff a Dummy image into that zero
location so that your upgraded code will not have to otherwise be offset-adjusted. Afterward, delete
the now-unused VB5/6-control and rename your new ImageList control to that of the old one.

48. Dealing with the loss of VB6 Control Lists and how to recover their functionality
If you are trying to get rid of VB6 Compatibility Library features, you may find yourself running into
a few VB6 helper class control lists. For as much as VB6 users whined about the loss of control lists
in VB.NET, such lists are sure easy to implement. I will show you easy ways of doing it, providing
you with alternative methods and functions to make building control arrays this way easy and fast,
and allowing you to customize it to your particular tastes.
When building control arrays under VB6, you could copy a control and paste numerous copies to the
form, or give them the same name. The first had an index of zero, the next 1, the next 2, and so on.
When they are upgraded to VB.NET, each control must have a unique name, but they are also
collected into a special VB6 Compatibility Library control list. For example, suppose I have a form
with a lot of images displayed on it, and the user made a selection of an image (now a PictureBox)
to activate some option. Suppose further that we have 18 such images, lined up 6 by 3, all named
Image1, and indexed from 0 to 17. When you bring the form up in designer mode under VB.NET,
you may notice, if you tinker around with the Properties list, that the Image1 controls are now a
series of PictureBoxes named _Image1_0 through _Image1_17. You will also notice a new Image1
gearbox control on the form control’s ribbon below the form (interestingly, when I now create fresh
‘duplicate’ controls on a new VB.NET project, I tend to name them in a similar manner, trailing the
“group name” with an underscore and then an incrementing index number, starting with zero).
Delete the VB6 Compatibility Library’s Image1 control from the form ribbon. Note that this will
also remove the “Handles Image1.Click” tag from the Image1_Click event, plus from any handlers
associated with the Image1 control, such as MouseMove. It will also remove a lot of superfluous
code from the application. I say good riddance, though it is a good idea to check and tag all events
that used Image1 (searching for “Sub Image1_” works, or you can even search for “Handles
Image1.” if you have not yet deleted the Image1 gearbox control).
As a first attempt, go to each control and edit its Tag property to match its named index value (I will
later introduce a means to forego this task if you do not want to go through this extra effort, or if you
may be using the Tag property for something else). Notice that after you enter the tag for one of the
controls (0, 1, 2, 3, etc.), you can simply click on the next control and begin typing its numeric index
value; the Tag property will now be the default selected property, until you change to another.
After that, go into your form’s code and create an array beneath the declaration of the form’s class:
Dim Image1(17) As PictureBox

Page –49–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

Alternatively, as of VB2008, you can use a strongly typed Generic List. Though I recommend using the
array shown above, a Generic List might be handy when using a list of CheckBoxes. Besides, you can do
a For…Each search through either implementation, when you might want to scan for those that are
checked, or which RadioButton is selected. But, for me, working with arrays simply has less overhead.
Here is how you would declare a Generic List, if you wish to use it, instead:
Dim Image1 As New System.Collections.Generic.List(Of PictureBox) 'A collection OF (Type) PictureBox

In the Form_Load event, stuff the array with the controls (this sort of thing is usually done behind
the scenes in the automatically generated code – to see such code yourself, be sure the “Show All
Files” button is selected in the toolbar of the Solution Explorer and select the form’s file that ends in
“.Designer.vb”). I cheat by entering the first complete line, then copying it and pasting copies, then
editing their index value. This is how you would add them to the array of PictureBoxes:
Image1(0) = _Image1_0 ' copy and paste, then edit sure makes this process a whole lot easier
Image1(1) = _Image1_1
Image1(2) = _Image1_2
...
Image1(17) = _Image1_17

Or, if you chose to use a strongly-typed Generic List, this is how you would add them to it:
Image1.Add(_Image1_0)
Image1.Add(_Image1_1)
Image1.Add(_Image1_2)
...
Image1.Add(_Image1_17)

Unlike regular Collection objects that will return a generic Object and that you will have to cast to
work with its result, Generic Collection objects are strongly typed and return the object type stored.
Scanning a collection of type RadioButton could go something like this:
Dim Index As Integer = 0 'init index to 1st entry (0-based)
For Each rBtn As RadioButton In RadioButtons 'check each RadioButton in the RadioButtons List (or Array)
If rBtn.Checked Then 'check its checked state
Exit For 'it is checked, so Index is set
End If
Index += 1 'else bump index by 1 and loop
Next
MsgBox("You Selected " & RadioButtons(Index).Text)

NOTE: All things considered, you may want to instead consider placing the controls on a Panel, which is a borderless
container class (hence runtime-invisible, unless you change its background color). You can loop through it with a
For…Each, just like a Generic List, but all without having to programmically stuff the container at all.
After that, go to your Image1_MouseMove event (and any other events, such as Image1_Click, that
the images had triggered), and after the event command, add “Handles _Image1_0.MouseMove,
_Image1_1. MouseMove, _Image1_2. MouseMove, etc.” (I cheat by copying one, pasting several copies, then
editing the trailing index values). Afterward, the command line might look like the following:
Private Sub Image1_MouseMove(ByVal sender As System.Object, ByVal eventArgs As System.Windows.Forms.MouseEventArgs) _
Handles _Image1_0.MouseMove, _Image1_1.MouseMove, _Image1_2.MouseMove, _
_Image1_3.MouseMove, _Image1_4.MouseMove, _Image1_5.MouseMove, _
_Image1_6.MouseMove, _Image1_7.MouseMove, _Image1_8.MouseMove, _
_Image1_9.MouseMove, _Image1_10.MouseMove, _Image1_11.MouseMove, _
_Image1_12.MouseMove, _Image1_13.MouseMove, _Image1_14.MouseMove, _
_Image1_15.MouseMove, _Image1_16.MouseMove, _Image1_17.MouseMove

If you do not want to do it this way (I don’t), which looks messy when it gets over a few handles,
you can instead dynamically add handlers right after you filled the array (above), using the command
AddHandler (a runtime alternative to the Handles clause) to add the MouseMove, Click, or whatever
event you want to process and assign the controls to the Address of the selected event method.
So if we were to strip the above line of all its trailing handlers, it would look something like this:
Private Sub Image1_MouseMove(ByVal sender As System.Object, ByVal eventArgs As System.EventArgs)

We can then programmically add event handlers to the event Image1_MouseMove (you could have
actually named the event anything, to include constructing the whole method from scratch). In the
following list, notice that we are taking each image defined, and notice also that by applying the
AddHandler command to them, the controls expose event properties (technically Delegates), and we
apply them to the address of the desired event method, such as the following:

Page –50–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

AddHandler _Image1_0.MouseMove, AddressOf Image1_MouseMove 'Add the MouseMove event of our controls
AddHandler _Image1_1.MouseMove, AddressOf Image1_MouseMove ' to the Image1_Mousemove list of
AddHandler _Image1_2.MouseMove, AddressOf Image1_MouseMove ' supported events.
...
AddHandler _Image1_17.MouseMove, AddressOf Image1_MouseMove '(NOTE: You can use RemoveHandler to remove, but not needed)

Finally, at the beginning of the event code, if you upgraded, you will notice some now-dead (actually
now error-marked) code that was used to get an Index of type Short (though you can use Integer if you
wish) from the control (it was expressed as “Dim Index As Short = Image1.GetIndex(sender)”). I simply
replace that error line with this one:
Dim Index As Short = CShort(DirectCast(sender, PictureBox).Tag)

This will grab the Tag of the control and extract a value from it, assigning it to Index.
If you are processing quite a number of control lists, you may want to employ a function to do all the
heavy work for you. Consider the following function:
'*******************************************************************************
' Subroutine Name : GetTagIndex
' Purpose : Return Index based on Tag value
'*******************************************************************************
Public Function GetTagIndex(ByVal eventSender As System.Object) As Short
If TypeOf eventSender Is Label Then
Return CShort(DirectCast(eventSender, Label).Tag) 'Handle labels
End If
If TypeOf eventSender Is PictureBox Then
Return CShort(DirectCast(eventSender, PictureBox).Tag) 'handle pictureboxes
End If
Return -1S 'flag error
End Function

With the above function, you need only enter the following line in an event to get its index like this:
Dim Index As Short = GetTagIndex(sender)

If you are using the Tag property of the controls for something else, which should be no big surprise,
you may want to instead write a function that will accept the sender parameter and return an Index
based on the naming of the control (I use the upgrade’s underscore and index naming method as a
useful personal standard in my own VB.NET projects). Consider this function, which will also
thankfully relieve you of the task of editing all the individual Tag properties of all the controls:
Module modGetNameIndex
'*******************************************************************************
' Subroutine Name : GetNameIndex
' Purpose : Return index based on Name (assume index value trails name after underscore)
' : Add as many type checks as you need. Example: Label1_13 for ID=13
'*******************************************************************************
Public Function GetNameIndex(ByVal eventSender As System.Object) As Integer
Dim Nam As String = vbNullString 'assign initial data so VB.NET will not accuse us of processing an uninitialized variable

Select Case eventSender.GetType.Name 'Get the Object control type name


Case "Label"
Nam = DirectCast(eventSender, Label).Name 'Handle Labels, Get name
Case "PictureBox"
Nam = DirectCast(eventSender, PictureBox).Name 'Handle PictureBoxes. Get name
Case "Button"
Nam = DirectCast(eventSender, Button).Name 'Handle Button. Get name
'If you need them, you can add more Case tests here...
End Select
'---------------------------------------------------------------------------------------
If CBool(Len(Nam)) Then 'something to process?
Dim I As Integer = InStrRev(Nam, "_") 'find trailing underscore (1-based index)
If CBool(I) Then 'found one? Non-zero means Yes
Return CInt(Nam.Substring(I)) 'yes, return trailing value (this works due to 0-based Substring method)
End If '(for Substring, the index value is 1 higher than Instr's 1-based index)
End If
Return -1 'return a flag to indicate Unsupported Type Encountered
End Function
End Module

And that is all there is to it. This simple technique will solve most old or new control array issues
without a ton of extra overhead, and the most tedious part of it being the populating of Handles
statements. But this type of thing was even done under VB6, though it was hidden from you.

Page –51–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

49. Dealing with changes to MouseMove parameter list changes


You may discover that when using the VB.NET MouseMove event that several VB6 parameters are
often assumed “missing”, which told you the button pressed, if any special keys were pressed (Shift,
Control, Alt), and the coordinates of the cursor. However, if you were to look at an upgraded
MouseMove event from VB6, you will see that the upgrade will provide you with the information
you need to reacquire those “lost” parameters, even if you do not need them. Consider the following
VB6 event heading:
'*******************************************************************************
' Subroutine Name : Image1_MouseMove
' Purpose : Mouse is moving over a point. Display it's usability graphically
'*******************************************************************************
Private Sub Image1_MouseMove(ByVal Index As Integer, ByVal Button As Integer, _
ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)

After it is upgraded to VB.NET, it looks like this:


'*******************************************************************************
' Subroutine Name : Image1_MouseMove
' Purpose : Mouse is moving over a point. Display it's usability graphically
'*******************************************************************************
Private Sub Image1_MouseMove(ByVal eventSender As System.Object, _
ByVal eventArgs As System.Windows.Forms.MouseEventArgs) Handles Image1.MouseMove
Dim Button As Short = eventArgs.Button \ &H100000
Dim Shift As Short = System.Windows.Forms.Control.ModifierKeys \ &H10000
Dim X As Single = eventArgs.X
Dim Y As Single = eventArgs.Y
Dim Index As Short = Image1.GetIndex(eventSender)

NOTE: The backslash ‘\’ performs an integer division, tossing away any remainder, so no Float results.
Mind you, this is convenient, but most-times we will not even need to use these provided values, so
you can just delete what is not needed. However, they do reinforce to us how we can obtain them in
new VB.NET projects (note that this example is actually the one I drew from in the last point). As an
aside, the expressions for Button and Shift should be cast to Short (CShort(eventArgs.Button \
&H100000) and CShort(System.Windows.Forms.Control.ModifierKeys \ &H10000)) if Option Strict is On.
Alternatively, you can change them to Integers, which will eliminate the need to recast anything.

50. Dealing with changes to remotely firing Button clicks


The way that button clicks are forced has changed in VB.NET. Previously in VB6, you could force a
click event on a button or menu by setting its Value property to True. For example:
Me.cmdHelp.Value = True 'treat as forced button click

The Value property no longer exists under VB.NET, nor does such a property have a logical place in
an object oriented world. With VB.NET, you employ the PerformClick() method on the control:
Me.cmdHelp.PerformClick()'treat as forced button click

51. Dealing with no AVI animation control in VB.NET and how to easily add a free one
Many users have complained that VB.NET does not have an animation control to play AVI files.
Although several online solutions exist, they normally require running the application with administrator
permissions because they involve adding OCX interfaces. However, there is an even easier way to do
this, and all without permission issues, or even running the AVI as a separate process. If you have VB6
installed, or just the free VB6 SP6 Redistribution Pack (see below), this is very easy to do (note that your
application users will not ever need to go through this process at all).
First, if you have tried to install VB6 on a Vista or Windows 7 operating system, you will have been told
that there are known compatibility issues with this application. However, these issues are easy to
surmount. If you have not done so already, and you need it, go ahead and install VB6. It will report
incompatibilities, but just ignore them for now, and even tell the operating system, if it asked you, that it
installed OK. The trick to getting past the incompatibilities issue is to right-click the installed VB6.EXE
application, or any shortcut to it, select Properties, select the Compatibility tab, and then place a
checkmark in the “Run this program as an administrator” checkbox. Afterward, any time you launch

Page –52–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

VB6 under Vista or Windows 7, you will have to hit the “Allow” option in a User Account Control
dialog, but this is a very small price to pay for unfettered access to the VB6 programming environment,
especially if you need support source code that compiles under it.
However, many of these systems will not allow you to install Service Pack 6 for Visual Basic 6.0 at all
due to incompatibility issues (http://www.microsoft.com/downloads/details.aspx?familyid=9EF9BF70-
DFE1-42A1-A4C8-39718C7E381D&displaylang=en). But, you can get around this, and optionally not
need to install VB6 at all by instead installing the free Runtime Distribution Pack for Service Pack 6 for
Visual Basic 6.0 (http://www.microsoft.com/downloads/details.aspx?FamilyId=7B9BA261-7A9C-43E7-
9117-F673077FFB3C&displaylang=en). This installation may still inform you that it might not have
installed correctly, even though it actually did (this alert is in fact due to a problem it has trying to
register OLEAUT32.DLL, which is already installed and registered), so select the “This program
installed correctly” option if this message prompt comes up. If you are unsure about this, install it in Safe
Mode (Select Start / Run (or WinKey-R), then enter msconfig, select the Boot tab, and select Safe Boot).
You will have to undo this in Safe Mode to reboot normally. However, it will still report an error, but this
time it will clearly inform you that it could not register OLEAUT32.DLL (it is because of heightened
system protections that have been added that prevent this DLL file from being tampered with, and
especially by an older version of it, which the VB6 version most certainly is).
What is important for our main point, which is installing an animation control, is that the COM
support library that provides this VB6 control is now loaded onto your computer and registered. But
once it is loaded into your system, you are now able to easily add an animation control to your
VB.NET Toolbox.
To add the VB6 animation control to your Toolbox, and a new toolbox tab to contain it, do the following:
1) With any, or a new VB.NET Window Application form up in the IDE, select the Toolbox tab.
2) Right-click one of the Toolbox tabs and choose “Add Tab”. A new Tab will be added to the Toolbox, and it will wait for you
to name it (this might not at first seem apparent, but notice a new blank tab with a cursor blinking in its heading). Name it
“COM”, or to something of your own personal preference. And lock in the change by hitting Enter.
3) Next, right-click the new COM Tab and select “Choose Items…” from the popup menu. After a rather long pause to gather
its massive reserve of available system resources, a “Choose Toolbox Items” dialog window appears.
4) Click the “COM Components” tab on the dialog window (there will be another delay to format its list).
5) Locate “Microsoft Animation Control 6.0 (SP6)” in the displayed list and place a check in its checkbox.
6) Select OK to apply the selection and close the dialog window.

Notice that you now have an animation control in your new COM Toolbox Tab, which you can use just
as you had done with VB6. Notice further that when you place an animation control on a form, the
properties for the control report that it is of type AxMSComCtl2.AxAnimation. (Ax represents ActiveX)
You will also notice that once you add an animation control to a form, that two new references are added
to your project properties, both for “Microsoft Windows Common Controls-2 6.0 (SP6)”; one of them
for Interop.MSComCtl2.dll, and another for AxInterop.MSComCtl2.dll, which are required to support the
animation control. Notice further that their “Copy Local” properties are both set to True, meaning that
these will be, and must be loaded into your program directory, being non-registration versions of the
ActiveX MSComCtl2 registered controls (the system will copy them automatically for you).
Developing this solution was like a mission for me. I first figured out how a VB6 to VB.NET
upgrade did it, and then figured from that code-only solution that there had to be a way to add such a
control to my toolbox, rather than instantiate everything programmically, which the upgrade code
did. It may be simple enough to do it programmically, but it is not something that is going to be
sitting at the top of my head every time I need to use it. On the other hand, a toolbox control is easy
to find and even easier to apply to a form than it is to go about writing a bunch of instantiation and
form-placement code (I say, let the background form designer do all of that programming for me).
My only real question to Microsoft is why they could not have added a native animation control to
VB.NET long before now? I would have though it essential, even for the release of VB2002.
However, considering that AVI animation is a COM process, I think they hesitates to add it to .NET.
Page –53–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

52. Dealing with changes to Resources Management


Hoards of VB6 users have grumbled loudly about the resource storage format used under .NET,
though those complaints are mainly for the fact that .NET resources are stored differently than they
were for VB6 (Win32). As a counterpoint, I firmly believe that they are stored better. What is more,
two very annoying things have also been totally eliminated under .NET that drove me to frothing
distraction under VB6: 1) With .NET, you no longer need to separately compile the resources (or
figure out what to type into the resource compiler source file) as you had to do under VB6, and 2)
.NET resources can be used immediately, even during development, such as sound, which did not
work from the IDE at all under VB6. Just load and go. You can also do a whole lot more with
them, particularly if you write multi-cultural apps, especially with the ResourceManager namespace.
Audio Resources
Probably the biggest complaint I hear is that embedded resources are supposedly more difficult to
access under VB.NET. But from how I see it, I know that this process is a whole lot easier, though I
think it really has something to do with their belief that they are only able to play audio files through
the PlaySound() P/Invoke in WINMM.DLL, which cannot access the .NET-embedded resources
(though its File I/O part will still work OK). My advice to those people is simple – abandon the
PlaySound() P/Invoke entirely! Instead, use .NET’s My.Computer.Audio class methods, which are
much easier to use than a P/Invoke. And, considering how easy it is to now add and access those
embedded resources, I am glad for it, especially because I can now play those resources, extract
them, check them, check for their existence, etc., all without giving them a great deal of thought.
For example, what follows is typical VB6 P/Invoke code I had written for playing sounds:
Private Declare Function PlaySound Lib "winmm.dll" Alias "PlaySoundA" (ByVal lpszName As String, _
ByVal hModule As Integer, _
ByVal dwFlags As Integer) As Integer
Private Const SND_FILENAME As Integer = &H20000
Private Const SND_RESOURCE As Integer = &H40004
Private Const SND_SYNC As Integer = &H0
Private Const SND_ASYNC As Integer = &H1
Private Const SND_NODEFAULT As Integer = &H2
Private Const SND_LOOP As Integer = &H8
Private Const SND_NOWAIT As Integer = &H2000
'*******************************************************************************
' PlayWavFile(): Play sound from a file
'*******************************************************************************
Public Function PlayWavFile(ByVal FileName As String, _
Optional ByRef PlayAsync As Boolean = False, _
Optional ByRef PlayLoop As Boolean = False, _
Optional ByRef NoWait As Boolean = False) As Integer
Dim flags As Integer

If PlayAsync Then
flags = SND_FILENAME Or SND_SYNC Or SND_NODEFAULT
Else
flags = SND_FILENAME Or SND_ASYNC Or SND_NODEFAULT
End If
If NoWait Then flags = flags Or SND_NOWAIT 'check for NoWait flag
If PlayLoop Then flags = flags Or SND_LOOP 'check for continuous play

PlayWavFile = Cint(PlaySound(FileName, 0, flags)) 'play file


End Function
'*******************************************************************************
' PlayWavResource(): Play sound from a resource file
'*******************************************************************************
Public Function PlayWavResource(ByVal SoundName As String, _
Optional ByRef PlayAsync As Boolean = False, _
Optional ByRef PlayLoop As Boolean = False, _
Optional ByRef NoWait As Boolean = False) As Integer
Dim flags As Integer = 0

If PlayAsync Then
flags = SND_RESOURCE Or SND_SYNC Or SND_NODEFAULT
Else
flags = SND_RESOURCE Or SND_ASYNC Or SND_NODEFAULT
End If
If NoWait Then flags = flags Or SND_NOWAIT 'check for NoWait flag
If PlayLoop Then flags = flags Or SND_LOOP 'check for continuous play
PlayWavResource = PlaySound(SoundName, GetHInstance(), flags)
End Function
'*******************************************************************************
' PlayWavStop(): stop playing any sounds that might be playing
'*******************************************************************************
Public Sub PlayWavStop()
Call PlaySound(vbNullString, 0, 0)
End Sub

Page –54–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

This was a tried and true module that served me well for about a decade, both in VB6 and in the C++
(with my C++ version of it). However, with the introduction of the .NET Framework, the choice of
options available to us has expanded substantially, rendering API invocations totally unnecessary.
In VB.NET, from the My.Computer namespace, we now have a LOT of computer control only as far
away as our fingertips. From the My.Computer.Audio namespace, we can easily play WAV files,
stop them, or issue system sounds (the message beeps, such as alerts, warnings, etc.). From
My.Resources, we have full access to all of our embedded resources. From
My.Resources.ResourceManager, we can poke and prod and check our resource data to no end.
One loud complaint I have heard regarding this is that many programmers cannot seem to use a
string name to get access to an audio resource, so they have resorted to using a Select block to check
a text name against a list, then play the associated embedded WAV resource. Well, I can understand
that one. The first day I started playing with VB.NET resources, I thought the same. But being a
software engineer, I knew that it could not be implemented so poorly. With all that raw power at my
fingertips, Microsoft was going to just settle on some weak solution like that? So I took a look at the
resource manager and between experiments (and stopping to actually read documentation), I quickly
figured it all out. By simply paying attention to the popup tooltips, I learned that by providing the
resource manager’s GetObject method with the text name of the desired resource, it would return an
object that I could cast into the appropriate type (byte array, stream, or string), which comprises my
audio data (byte array if from VB6, or stream if VB.NET-added). I could then take advantage of the
Play method in My.Computer.Audio and play that resource, or from a file path, with equal ease.
Following is my new VB.NET version of the previous VB6 PlayWav routines:
'*******************************************************************************
' PlayWavFile(): VB.NET Play sound from a file
'*******************************************************************************
Public Function PlayWavFile(ByVal FileName As String, _
Optional ByRef PlayAsync As Boolean = False, _
Optional ByRef PlayLoop As Boolean = False, _
Optional ByRef NoWait As Boolean = False) As Boolean
'see if the audio file exists
If Not System.IO.File.Exists(FileName) Then Return False 'nope; error
'process our flags
Dim flags As AudioPlayMode = AudioPlayMode.Background
If Not NoWait Then flags = AudioPlayMode.WaitToComplete
If PlayLoop Then flags = AudioPlayMode.BackgroundLoop

My.Computer.Audio.Play(FileName, flags) 'play audio file


Return True 'success
End Function

'*******************************************************************************
' PlayWavResource(): VB.NET Play sound from a resource file
'*******************************************************************************
Public Function PlayWavResource(ByVal SoundName As String, _
Optional ByRef PlayAsync As Boolean = False, _
Optional ByRef PlayLoop As Boolean = False, _
Optional ByRef NoWait As Boolean = False) As Boolean
'get data for sound from resource
Dim obj As Object = My.Resources.ResourceManager.GetObject(SoundName)
If obj Is Nothing Then Return False 'bad name, so return error
'process our flags
Dim flags As AudioPlayMode = AudioPlayMode.Background
If Not NoWait Then flags = AudioPlayMode.WaitToComplete
If PlayLoop Then flags = AudioPlayMode.BackgroundLoop
'process file
If TypeOf obj Is System.Array Then 'byte array (usually upgraded VB6 files)
My.Computer.Audio.Play(DirectCast(obj, Byte()), flags)
Return True 'all is good, so return success
End If
If TypeOf obj Is System.IO.Stream Then 'typical VB.NET resource file
My.Computer.Audio.Play(DirectCast(obj, System.IO.Stream), flags)
Return True 'all is good, so return success
End If
If TypeOf obj Is String Then 'typical external wave file
Return PlayWavFile(DirectCast(obj, String), PlayAsync, PlayLoop, NoWait)
End If
Return False
End Function

'*******************************************************************************
' PlayWavStop(): VB.NET stop playing any sounds that might be playing
'*******************************************************************************
Public Sub PlayWavStop()
My.Computer.Audio.Stop()
End Sub

Page –55–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

Now this is definitely easy. True, I was initially disappointed that the PlaySound() P/Invoke no longer
worked with .NET resource files, but what I got in trade has rendered the PlaySound() P/Invoke useless.
AVI Resources
AVI files are a source of frustration with VB6 users and VB.NET users alike. In VB6, it was a bit of a
bother. To store the file FileCopy.avi in a VB6 resource file you had to:
1) Add an entry, such as “101 CUSTOM "FileCopy.avi"” to a resource text file, such as myResources.rc.
2) Compile the resource from a DOS prompt, using something like “rc myResources”.
3) Finally, add the resulting myResources.RES file via Projects / Add File to your VB6.
Why many VB6 users claim this hack is easier than VB.NET’s methods, I do not know. In VB.NET, all I
do is go to Project / Properties, select the Resources tab, set the resource type to Files, hit the Add
Resource button, and navigate the browser to and select my FileCopy.avi file, and it is added to my
resources. Close the project properties and it is automatically compiled and associated to my application.
Yet reading and playing the AVI resource file can be frustrating, because I have heard all sorts of tales of
woe about how someone cannot figure out how to play it, or how they cannot convert the resource to a
string so they can use a StreamWriter to save it as a file, and various variations of this dilemma.
Unlike Audio files, which can be played directly from the resources, an AVI must currently be played
from a file (actually, there is a way to play AVI files from a VB6 resource file, by using something like
“Lresult = SendMessage(Animation1.hwnd, ACM_OPEN, ByVal App.hInstance, ByVal aviResourceID)”, where
ACM_OPEN is declared as WM_USER (&H400) + 100&). I was hoping that the new .NET architecture
would enable the Animation control to read byte arrays (byte arrays exhibiting read/write/seek features,
which is a byte array wrapped within a Stream interface) or streams directly, but sadly has yet to happen.
Therefore, our first task is to copy the resource data to a file. I will assume that we have the
FileCopy.avi file loaded in our resources. When it is added, it will automatically be saved in the
resources by its file-defined name, such as FileCopy (you can alter this by renaming the resource). I
like to play it as a file from wherever the executable program is running from, so I will do that,
though you may require a different non-locked local resource, such as a temp folder on the user’s
system if the application runs from a CD or DVD or some other similar write-protected source.
Also, the properties box reports that FileCopy has a type of System.Byte[], which is saying that it is a
Byte Array (C uses square braces to denote arrays). As such, instead of trying to convert the resource to a
string, I will treat it as a byte array and use a FileStream object so that I can write a binary file. With
VB6, all we had to do was issue the command “LoadResource 101, AviFile”, where AviFile is a string
specifying the file destination to save the ID 101 resource as a file; if we had ‘named’ our AVI resource
101 (see the above 3-step VB6 resource compiling process). Now consider the following VB.NET code:
'set up the local variable to hold the path to our FILECOPY.avi file
Dim AviFile As String = Application.StartupPath & "\FILECOPY.avi"
'first see if it already exists. If so, then we will not have to create it...
'if the AVI file does not already exist...
If Not System.IO.File.Exists(AviFile) Then
'set up our FileStream object to create it
Dim fs As New System.IO.FileStream(AviFile, System.IO.FileMode.Create)
'write byte array resource directly from resource to file from its beginning (0) for its length
fs.Write(My.Resources.FILECOPY, 0, My.Resources.FILECOPY.Length)
'close the new local AVI file copy of the resource
fs.Close()'this automatically invokes fs.Dispose(True) to release all used resources
End If

The only thing left for us to do is to play it in our Animation control:


With Me.Animation1 'start animation...
.AutoPlay = True 'turn animation on
.Open(AviFile) 'load the animation file
.Visible = True 'show the animation AVI
Application.DoEvents() 'let display catch up
'
' ... Do application processes, then finally...
'
.Close() 'close AVI animation
.AutoPlay = False 'turn off AVI animation
.Visible = False 'and hide it
End With

Page –56–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

A final thing you may want to do when you are leaving your application is to perhaps delete the AVI
file, although keeping it present will mean future runs will not have to start off saving another copy
to a file, but, because I like to clean house afterward, I normally do this in my application exit code:
System.IO.File.Delete(AviFile) 'delete the avi file resouce

Other standard resources you can store are Icons, Images, and String. Other than the auxiliary Files,
which I prefer to use, you may like to use Other, which is much the same as Files, but it is a way to
separately catalogue your various data. You can even set the access modifiers of your resources as
Public or Friend (default).

53. Dealing with the loss of the App statement


The App statement in VB6 is completely missing in VB.NET, and so we are left with extracting its
many useful application-related values by other, sometime more complicated means. However, we
can instantiate a globally-accessible class and obtain this information just as simply as we did under
VB6. By instantiating the AppInfo Class using “Public App As New AppInfo”, we can, for example, get
the running application’s version number exactly the same way we did under VB6, using:
Dim Version As String = CStr(App.Major) & "." & CStr(App.Minor) & "." & CStr(App.Revision)

Following is my AppInfo class, featuring most of the old App statement’s many useful features:
Imports System.Reflection.Assembly, System.IO.Path, System.Diagnostics, System.Diagnostics.Process, System.Runtime.InteropServices
'******************************************************************************
' Class AppInfo (App)
' Used to create App class instance to access application information
' Declare this class globally using "Public App As New AppInfo"
'
' These function emulate the VB6 App command. For example, to get the current app's title,
' Dim S As String = App.Title
'******************************************************************************
Public Class AppInfo
Public Function FullPath() As String ' Support function
Return GetExecutingAssembly.Location()
End Function

Public Function EXEName() As String


Return GetFileName(FullPath())
End Function

Public Function Path() As String


Return GetDirectoryName(FullPath())
End Function

Public Function Comments() As String


Return FileVersionInfo.GetVersionInfo(FullPath()).Comments
End Function

Public Function FileDescription() As String


Return My.Application.Info.CompanyName
End Function

Public Function LegalCopyright() As String


Return My.Application.Info.Copyright
End Function

Public Function LegalTrademarks() As String


Return My.Application.Info.Trademark
End Function

Public Function Major() As Integer


Return My.Application.Info.Version.Major
End Function

Public Function Minor() As Integer


Return My.Application.Info.Version.Minor
End Function

Public Function Revision() As Integer


Return My.Application.Info.Version.Revision
End Function

Public Function Build() As Integer


Return My.Application.Info.Version.Build
End Function

Public Function Title() As String


Return My.Application.Info.Title
End Function

Public Function ProductName() As String


Return My.Application.Info.ProductName
End Function

Page –57–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

Public Function PrevInstance() As Boolean


Return (UBound(GetProcessesByName(GetCurrentProcess.ProcessName)) <> 0)
End Function

Public Function Instance() As Integer


Return Marshal.GetHINSTANCE(GetCallingAssembly.GetModules()(0)).ToInt32
End Function
End Class

54. Dealing with updating default ByRef and ByVal method parameters flags
Be sure to check your ByRef and ByVal references in your method parameter lists. When a program
is updated from VB6 to VB.NET, parameter references that are not explicitly stated as flagged as
ByRef or ByVal will default to ByRef, simply because VB6 had defaulted to ByRef (a policy I had
always disagreed with, but understand due to its evolution from FORTRAN). But, most references
should be tagged as ByVal, unless they are passing objects that will be modified. ByVal makes data
safer, and so if the data should not be modified, be sure to change such ByRef entries to ByVal.

55. Dealing with Collection and List clearing


Be aware that with all the new features and muscle available to collections and lists, such as the
various new forms of the Collection object, ListBox, and ComboBox, that a new method, Clear(), is
now available that makes clearing the list out a snap, and fast. Previously, a loop had to be employed
to clear out a Collection or ListBox or ComboBox, such as is shown in this VB6 sample:
With colHistory 'Quickly purge the History Collection...
Do While CBool(.Count) 'While entries exist in the collection
.Remove 1 'remove the entries (standard collections use a 1-based list)
Loop 'loop until all entries are purged (Removing from 1 rather than .Count is =MUCH= faster)
End With

The above method is extremely fast; beat by only a few seconds in gigantic, memory-filling
collections by sending the handle of the Listbox the LB_DELETESTRING message (&H182), sent
via the SendMessage P/Invoke. This all can now be accomplished, even in gargantuan collections
with this simple and even faster command:
colHistory.Clear() 'More quickly purge the History Collection in VB.NET

NOTE: If you use a Generics collection, which also features the Clear() method, you can alternatively use the Remove()
method to remove entries by a Key, but you will have to use the RemoveAt() method to remove indexed entries.

56. Dealing with Adding Auxiliary Files to ClickOnce Deployment (Publish)


A lot of VB6 users liked to use the Package & Deployment Wizard to build simple installs for their
projects. However, a lot of people have complained that they cannot seem to add auxiliary files to a
ClickOnce Deployment (Publish) under VB.NET. For all the frustration and chatter I have seen on the
web regarding this, the solution is almost too easy. First, you must be sure that your auxiliary files are
actually included in your project. Second, you must be sure that their Build Action is set to other than
None. Third, you must be sure that your file will be written out to the installation location.
Just because you see your file in the Solution Explorer, this does not
mean that the file is also included in your project. It may simply be a
consequence of the file existing in your project folder(s), because maybe
you copied it to that location. If you notice that the icon for the needed
file seems a bit faded, or that a Build property is not shown in the file’s
attributes when you click on it in the Solution Explorer, or if when you
right-click it and you notice an “Include in project” option is enabled in
the pop-up menu, you can bet that the file is not included in your project.
If it is not currently included in your project, simply right-click it and select “Include in Project”. Or,
select Project from the menu, then select Add Existing Item, if it is located elsewhere.

Page –58–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

The second step is to address the Build issue. One at a time, click on
a now-included file and be sure the Build Action property is set to
Content, meaning that the file will be part of the project’s content.

You may notice the build


actions for other files. Files
that are already embedded in
the final executable, such as
resource objects, normally
have a build action of None. Methods, classes, and forms have
build actions of Compile.

You will also want to check the “Copy to Output Directory”


property while you are here. If it reports “Do not copy”, then it
will not be sent to the destination folder. You may want to
change this to “Copy always” or “Copy if newer” (copy if it is newer than an existing file of that name, or
copy it if it does not yet exist).

Another thing you may want to do is to determine how it should be supplied. If you go to the
project properties, and select the PUBLISH tab, you will notice a button named “Application
Files”. Select it and a list of application files that will be included in the publication will be
listed.

Depending on how you set the Target Framework version under


the Advanced Compile Options (see the Compile tab in the Project
Properties, under the Advanced Compile Options button), you
may want to also check the Prerequisites list. For example, if you
compiled your application to use .NET Framework 3.0, you may
want to be sure that the person installing your application has the
ability to load .NET Framework 3.0 if it is not already installed. In
the Prerequisites list (see the Prerequisites button on the Publish
tab). You can have the prerequisites loaded from the vendor
website (Microsoft), or from the same location as your application (the prerequisites will be bundled
right within your published installation), or you can specify the URL of a web address where they
can be downloaded. If you are publishing your application for web distribution, it is usually easier to
have prerequisites downloaded from the vendor. If you are building a CD/DVD installation, it is
probably best to provide them yourself.

Finally, once you have gone through the Publishing Wizard at least once and made your few choices
(where to publish to, how the app will be installed, if updates can/should be checked, and from
where), you can usually process subsequent builds by simply choosing the Publisher’s Finish button.

57. Dealing with changes to Text Box SelStart and SelLen Properties
This is a very minor change. The VB6 SelStart and SelLen properties of TextBox and RichTextBox
controls has become SelectionStart and SelectionLength under VB.NET.

Page –59–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

58. Dealing with changes to ToolTips


Tooltips have changed between VB6 and VB.NET. The VB6 ToolTipText property is no longer
supported as it was. It is now handled by a ToolTip control. The purpose of the control is to help
speed the execution of code. Having it built into each control ate a lot of time and resources.
Fortunately, it translates very well during an automatic upgrade from VB6 to VB.NET.
By simply dropping a ToolTip control onto a VB.NET form, tool tips are suddenly enabled for the
other controls on the form, exposing a ToolTip property on each. For example, if you drop a fresh
ToolTip control onto a form, it is named Tooltip1 by default, and the property is exposed on each of
the form’s controls (through a process called Reflection) as “Tooltip on Tooltip1”. You can pre-fill
the text of these tips at design time through the property interface just as you had done it in VB6.
However, you now have a much more powerful control. You can even drop more than one ToolTip
control on a form, which in turn exposes more than one ToolTip property. This may seem like a silly
thing to do, but due to your now being able to customize the presentation of your tooltips, you can
assign one set and style of ToolTips to certain controls, and assign another set and style to others.
These customizations feature the ability to enable a balloon-type format, changing foreground and
background colors, modify display timing, to include being always on, setting titles for the ToolTip
windows, create owner-drawn ToolTips (user-drawing enabled through the ToolTip controls Draw
and PopUp Events), plus many other features.
By the way, with multiple ToolTip styles defined for a form, you would simply set ToolTip text to
the ToolTip control interface (ToolTip on ToolTip1, ToolTip on ToolTip2, etc.) that you want to have
displayed, leaving the other(s) blank (blank ToolTip text disables that ToolTip interface).
In VB6 program code, you would set a ToolTip through a control’s ToolTipText property, like so:
Me.cmdContinue.ToolTipText = "Continue processing" 'continue processing with this button

With VB/NET, this command could be expressed as:


Me.ToolTip1.SetToolTip(Me.cmdContinue, "Continue processing") 'continue processing with this button

To retrieve ToolTip text, in VB6 you would use a command such as this:
Dim ttText As String
ttText = Me.cmdContinue.ToolTipText

In VB.NET, the above could be implemented as:


Dim ttText As String = Me.ToolTip1.GetToolTip(Me.cmdContinue)

59. Dealing with changes to ListView


The VB6 ListView control was 1-based. In VB.NET, it is 0-based, and you must adjust your
indexing accordingly (this includes icon indexes). Like with other list controls, the VB6 ListView’s
ListItems collection has become a uniform Items collection. Also be aware that the slow Clear
method offered in VB6 has been replaced in VB.NET with a much faster Clear method. In VB6, it
had been several times faster to actually loop through the list and perform a Remove(1) statement as
long as property Count was non-zero, than it was to use the Clear method (in VB.NET, this would
have to be upgraded to a RemoveAt(0) statement). Further, the Item property of the Items list
(previously ListItems in VB6) is no longer a simple String, but rather an Object.

Page –60–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

60. Dealing with changes to Toolbar Button and Button Menu Clicks
When a Toolbar program is upgraded from VB6 to VB.NET, a couple things happen. First, both
normal and special dropdown button list click events must be handled differently than under VB6.
Second, ToolBars, though still supported by VB.NET for reasons of backward compatibility, are
automatically converted during an upgrade to the newer, more capable ToolStrip controls. These
issues are quite easy to adapt to and resolve programmically, but you really should understand what
is going on by examining the differences between the aging ToolBar and the newer ToolStrip, which
are both featured in VB6 and VB.NET. Also, even though continuing to implement the old ToolBars
might seem to be a more attractive choice from the perspective of a person upgrading from VB6 to
VB.NET, you cannot just drop a ToolBar control onto a form from
a fresh VB.NET install. The VB.NET Toolbox initially offers only
ToolStrip controls. Even so, you are able to add a ToolBar control
to the Toolbox, if you cannot seem to live without it, through its
Choose Items option (right-click a desired Toolbox group header
to access this option, and then in the dialog box select the Toolbar
entry declared in the .NET tab’s Global Assemble Cache
directory).
When a normal button is clicked on a ToolBar under VB6, its ButtonClick event will provide you
with a Button parameter that represents the clicked button on the ToolBar. If your processing code
will identify these buttons by the key you assigned to it during development, you can acquire the
Button object’s key through its Key property, or, if you process them by their assigned toolbar index
number (beginning from 1), you should instead use the Button object’s Index property, or finally, if
you process them by assigned text, you should use the Button’s Caption property. For example:
Private Sub Toolbar1_ButtonClick(ByVal Button As MSComctlLib.Button)
Select Case Button.Key 'Process by Key (use Button.Index for the button's 1-based index)
Case "Exit" 'if Exit button, then simply unload the main form
Unload(Me)
...
End Select
End Sub

When developing code for VB.NET’s ToolStrip buttons, this process is different. However, it is still
very easy to use. By default, VB.NET generates a separate click event for each button. For example:
Private Sub ToolStrip1_tbbExit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ToolStrip1_tbbExit.Click
Me.Close() 'if Exit button, then simply unload the form (replaces VB6 Unload(Me))
Me.Dispose() 'also do this in case the form was opened as a Dialog or is an MdiChild and was not open
End Sub

However, you can consolidate all button code by creating event code named something like
ToolStrip1_ButtonClick and adding a list of button event handlers at its end. Additionally, be sure to
also define a Button object of type ToolStripItem in order to access each selected button, like so:
Private Sub ToolStrip1_ButtonClick(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles ToolStrip1_tsiOpen.Click, ToolStrip1_tsiClose.Click, _
ToolStrip1_tsiOptions.Click, ToolStrip1_tsiExit.Click
'Define Button as a ToolStripItem object (DirectCast is faster than CType() -- no actual conversion taking place)
Dim Button As ToolStripItem = DirectCast(sender, ToolStripItem)

Select Case Button.Name 'Process by the button's Name (no Key available. Use Text if the
'ImageStyle does not display the Text field, otherwise try the Tag field)
Case "tsiExit" 'if Exit button, then simply unload the main form
Me.Close() 'Close the form
Me.Dispose() 'also do this in case the form was opened as a Dialog or is an MdiChild
...
End Select
End Sub

The above example generally covers how a VB6 to VB.NET upgrade also handles it, excepting that
the original upgraded code will retain the old VB6 “Unload(Me)” command and mark it as
unsupported and request editing (see Point 61 for an explanation). I simply replace it with
“Me.Close()”, and then add “Me.Dispose()” if the form was handled as a dialog (though adding it
even if it was not does no harm).

Page –61–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

NOTE: During an upgrade from VB6, the upgrade automatically generates the code to declare the Button object.
However, it will replace the original “Button.Key” with “Button.Name”, even though the VB6 button Name and Key
values were seldom, if ever identical. This can be a potential problem because it will not provide an upgrade note
informing you of this. So be aware that this will happen. I like to take the time to assign the upgraded button’s Tag
property to reflect the old VB6 Key property, or, as I have done in the above snippet, simply update the test for the
button name. Be aware that you could have also originally tested against the button’s Name property under VB6, in
which case the code would have upgraded more smoothly.
However, when developing the code under VB.NET, you can alternatively collectively process these
clicks through the ToolStrip itself in a single subroutine, and from its click event you can extract the
button information. Remember also that VB.NET button indexes begin at 0, not 1. For example:
Private Sub ToolStrip1_ItemClicked(ByVal sender As Object, ByVal e As ToolStripItemClickedEventArgs) Handles ToolStrip1.ItemClicked
'The following is how you would get the clicked Button's Name property from this ToolStrip event:
Dim ButtonName As String = e.ClickedItem.Name 'notice the type assigned to 'e', above.
'The following is how you would get the Button's definition index (from zero):
Select Case ToolStrip1.Items.IndexOf(e.ClickedItem) + 1 'optionally add 1 (compensate 0 option base if code is 1-based)
Case 4 'if Exit Button, then simply unload the form (4th button)
Me.Close() 'Close the form
Me.Dispose() 'also do this in case the form was opened as a Dialog or is an MdiChild
...
End Select
End Sub

Button Menus (VB6 ToolBar Button style 5; tbrDropdown) are menus that drop down from toolbar
buttons and offer a list of items to select from (you will notice a dropdown indicator on the right side
of the button (▼). Under VB6, such selections could be handled through the ButtonMenuClick event,
and you could acquire the index of the item in the button’s dropdown list from the menu button’s
Index property, regardless of whether you predefined the dropdown button list or dynamically added
them within the application code. Consider the following VB6 example:
Private Sub Toolbar1_ButtonMenuClick(ByVal ButtonMenu As MSComctlLib.ButtonMenu)
Call ShowListItem(ButtonMenu.Index) 'process according to the index value of the button
End Sub

But when you process a pre-defined dropdown button in VB.NET from a ToolStrip (style
ToolStripDropDownButton), event handling takes two different approaches. First, it can provide an
individual event code block for each dropdown button you care to declare code for, like this:
Private Sub Option0ToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Option0ToolStripMenuItem.Click
'Handle code for predefined dropdown button with displayed text "Option 0"
End Sub

Of course, the above is only practical when you pre-define dropdown button values in VB.NET. If
you add them dynamically by adding entries to a DropDown button, we named, say, tsddbOptions,
through its DropDownItems collection, such as we might have done in the following example:
With Me.tsddbOptions.DropDownItems
.Add("Option 1")
.Add("Option 2")
.Add("Option 3")
.Add("Option 4")
End With

You could handle selections to those dynamically added buttons, as well as through any that had
already been predefined at development time through the dropdown button’s DropDownItemClicked
event, much like the following:
Private Sub tsddbOptions_DropDownItemClicked(ByVal sender As Object, ByVal e As ToolStripItemClickedEventArgs) _
Handles tsddbOptions.DropDownItemClicked
'The following is how you would acquire the dropdown Button's Text property (in lieu of a VB6 Key):
Dim KeyName As String = e.ClickedItem.Text 'this is typically practical, because the text data is defined.
' The following is how you would acquire the Button's index (from zero):
Select Case Me.tsddbOptions.DropDownItems.IndexOf(e.ClickedItem) + 1 'add 1 to compensate for a zero index in 1-based code
Case 4 'Option 4 (4th option)
'you must do the following command IF you are closing a form, otherwise an exception error will occur.
Me.tsddbOptions.HideDropDown() 'Close list, so exception will not fire thru the following Me.Close().
Me.Close() 'Close the form.
Me.Dispose() 'Do this also in case the form was opened as a Dialog or an MdiChild.
Exit Sub
...
End Select
End Sub

Page –62–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

NOTE: Please notice the “HideDropDown()” invocation in the above code. If the dropdown is still open during a form
close, an exception will occur because the closing form will destroy form resources, such as the dropdown menu object,
which will still have code running as a background process, to handle collapsing the dropdown. Forcing the closing of
the button dropdown menu will detach that background process, which allows a clean removal of resources.
NOTE: By default, some of the above examples for the “e” parameter will have their types preceded by
“System.Windows.Forms.”, but this additional tagging is not really necessary, because that namespace has already
been loaded into the current Form’s code block, but it would be required if this code executes in an outside module.
To conclude, you can still add a ToolBar control to the Toolbox and process them much as you did
under VB6, but the newer MenuStrips are much more powerful and offer many new features, which
are far more flexible and dynamic and in keeping with current software development needs, such as
the ability to dock and undock from a ToolStrip Container, and are easier to write customization
code for, if you wish the application’s user to be able to do so. ToolBars, though still supported in
VB.NET simply for reasons of backward compatibility, are clearly by now very dated technology
that had been the initial mainstay in Visual Basic since the days of VB1 and QuickC for Windows.

61. Dealing with Unload Form commands


When VB6 forms are unloaded, their resources are released, and so many developers tend to invoke
a lot of form unloading commands. However, during an upgrade, a command such as “Unload
frmHelp” is not automatically changed to an equivalent “frmHelp.Close” command, as you might
think it should. Instead, an upgrade warning is issued. This is E-Z enough to deal with, but there is
good reason for why it did what it did without taking care of business for you.
First, fix it by changing the command to invoke the form’s Close method. The reason why this was
not automatically changed for us is that the Upgrade Wizard was not sure if it should also invoke the
form’s Dispose method. This non-change will force our attention to it, because it will force a
compile error, (Unload will not be recognized). Were it up to me, I would have had the upgrade
automatically apply Close and Dispose, regardless (Dispose methods are and should be designed to
be invoked multiple times, but they should execute their resource releasing functions only once). The
reason Dispose is not automatically added is due to how the window was processed. If it was open
due to its Show method, closing it will automatically invoke its Dispose method. However, if you
displayed the form using the ShowDialog command, it will not automatically invoke Dispose,
because a dialog is expected to return a result, which would be lost if it were disposed. You would
not want to release those resources before you can process the dialog result in its invoking code (i.e.,
Dim iResult As DialogResult = frmHelp.ShowDialog(Me)). Also, MDI child windows should also
have their Dispose methods invoked after Close if they are not displayed. The Upgrade Wizard
simply cannot make these assumptions for us.

62. Dealing with The Loss of the VB6 NewIndex Property


When you added a new entry to a VB6 ListBox or ComboBox, the control has a property called
NewIndex which contained the index of the item just added. This was especially useful for sorted
lists. However, this property would get you in trouble if you had removed entries from the list before
reading it. As such, because this property is only guaranteed to contain a reliable value immediately
after a new item was added to the control’s list, VB.NET was forced to eliminate this less-than
robust property. It instead provides this value as the return value of its Add() method, which is the
only place where this value can be guaranteed to be 100% reliable. For example, the following code
will return the “lost” NewIndex value:
Dim NewIndex As Integer = Me.ListBox1.Items.Add("Some Data") 'The Add method returns the old VB6 NewIndex property value

Page –63–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

63. Dealing with Process Handling In the KeyPress, KeyDown, and KeyUp Events
One minor change that you may not notice is to the KeyPress, KeyDown, and KeyUp events.
Unlike VB6, KeyPress, KeyDown, and KeyUp event handling under VB.NET gives you more
control over what is done to key processing. A key down or up event is triggered when the user
pressed or releases a keyboard key. A key press event is triggered when the user simply types a key
on the keyboard. Typically, most developers concern themselves with just the KeyPress event.
Usually, the developer wants to restrict what the user types into a TextBox. For example, suppose
you wanted to ensure that the user entered only alphabetic or numeric characters into a TextBox, and
further, the alphabetic characters should always be upper case. One might use code such as this:
'*******************************************************************************
' Subroutine Name : txtName_KeyPress
' Purpose : Filter keyboard so that invalid data cannot creep in
'*******************************************************************************
Private Sub txtName_KeyPress(ByVal eventSender As System.Object, _
ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles txtName.KeyPress
Dim KeyAscii As Integer = Asc(e.KeyChar)
Select Case KeyAscii 'check the current key being entered into the txtName textbox
Case 1 To 31 'ignore checking control keys (let them pass through)
Case Else
Dim C As String = UCase(Chr(KeyAscii)) 'get uppercase character from code
Select Case C
Case "A" To "Z", "0" To "9", "_" 'check against the range of allowed characters
e.KeyChar = ChrW(AscW(C)) 'all is well for these, so be sure code reflects uppercase
Case Else
e.KeyChar = ChrW(0) 'out of range, so nullify it
End Select
End Select
End Sub

However, if we handle processing of a key code, we may want to prevent the system default handlers
from processing it further. For example, under VB.NET, if we set KeyChar to zero, downstream the
system will still process key code zero, because the system only knows that a key has been pressed. As a
result, the system will ring an alert bell because it determined that nil is not a valid key code.
If we want to suppress the bell, we may want to tell the system that it is not necessary to process the key
further at all. By informing it so, it will not be processed by downstream methods. To do that, you would
simply add the command “eventArgs.Handled = True” where you want further processing ignored:
Select Case C
Case "A" To "Z", "0" To "9", "_" 'range of allowed text
e.KeyChar = ChrW(AscW(C))
Case Else
e.KeyChar = ChrW(0) 'out of range, so nullify it (probably no longer necessary, but it makes me feel better)
e.Handled = True 'indicate Keypress event was handled (no need for further system processing)
End Select

NOTE: You would not add “eventArgs.Handled = True” after you updated the KeyChar property to an uppercase
state, because it would then not find its way into the textbox at all. This flag tells it to stop further processing.
An alternative is to do testing in the KeyDown event (KeyDown is handled before KeyPress, which is in
turn handled before KeyUp), and suppress processing by KeyPress if a key is invalid by setting the
e.SupressKeyPress property to True (which also sets its e.Handled property to True):
Private Sub txtName_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles txtName.KeyDown
Select Case e.KeyValue 'check the value of the ReadOnly key
Case 1 To 31 'ignore control keys
Case Else
Select Case UCase(Chr(e.KeyValue)) 'check character from code
Case "A"c To "Z"c, "0"c To "9"c, "_"c 'check range of allowed characters (note the c appendage for chars, not strings)
Case Else
e.SuppressKeyPress = True 'let no other keys reach the KeyPress event (this also sets Handled to True)
End Select
End Select
End Sub

With this, we can simplify our KeyPress event code to just this little block of code:
Private Sub txtName_KeyPress(ByVal eventSender As System.Object, _
ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles txtName.KeyPress
e.KeyChar = UCase(e.KeyChar) 'we can assume all values are valid from KeyDown event (Ucase will not affect controls or digits).
End Sub 'this method would not be needed AT ALL if we do not require Uppercase-only text in the textbox.

Page –64–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

64. Dealing With Invoking Handled Events Under VB.NET


Under VB6, you may have seen, or have even written, code that invoked another event method. For
example, it was common to see code that forced a click event of an “ADD” button if the user typed
text into a TextBox, and then hit the Enter key. This was, and is, a handy little shortcut.
Suppose you have a TextBox named txtCommand, and a Button named btnAdd (with a caption of
“&Add”, – which displays as “Add” – so that the user can also fire the Add button with Alt-A), which
will add the user text to a Collection named colList. You might see VB6 code like this:
'VB6 Code
Option Explicit
Dim colList As New Collection

'******************************************************************
' txtCommand_GotFocus
' When textbox gets focus (the user selected it, for example),
' then select entire contents.
'******************************************************************
Private Sub txtCommand_GotFocus()
With Me.txtCommand
.SelStart = 0 'set selection to start of text
.SelLength = Len(.Text) 'select entire text
End With
End Sub

'******************************************************************
' txtCommand_KeyPress
' When typing data into the textbox, if the user types ENTER,
' then automatically invoke the ADD button.
'******************************************************************
Private Sub txtCommand_KeyPress(KeyAscii As Integer)
Select Case KeyAscii
Case 13 'CR?
Call btnAdd_Click() 'Yes, so for button click (we could have also used 'btnAdd.Value = True')
KeyAscii = 0 'nullify code so it is not processed further
End Select
End Sub

'******************************************************************
' btnAdd_Click
'******************************************************************
Private Sub btnAdd_Click()
colList.Add(Me.txtCommand.Text) 'add user command to our list
Call txtCommand_GotFocus() 'reset full selection and focus to text box
End Sub

As you can see in the KeyPress() event of txtCommand, the (ByRef) KeyAscii variable is checked
for a value of 13, which is a Carriage Return, applied by the Enter/Return key. If 13 is found, then
the ADD button is invoked by calling the BtnAdd_Click() event code, and then nullifying the
KeyAscii value to prevent further processing of it. The Click() event for BtnAdd then adds the
contents of the textbox to the Collection colList, and then the GotFocus() event is manually invoked
to ensure that the contents of the TextBox are now fully selected.
For those who must still do VB6 programming, be aware that we could have invoked the Click()
event for BtnAdd by instead using the command “Me.btnAdd.Value = True” (which would upgrade to
VB.NET to a more logical “Me.btnAdd.PerformClick()”), which is the official VB6 method for forcing
the invocation of its click method, but what I am interested in is how to deal with the VB.NET side,
because all events under VB.NET will require parameters to be passed. And besides, such a change
would not work for invoking the GotFocus() event of txtCommand.
Most of you already know what I am driving at here. If you have seen a button click event under
VB.NET, it would be declared like this:
Private Sub btnAdd_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAdd.Click

And the GotFocus() event for txtCommand (which is an Enter() event under VB.NET, just as a
LostFocus() event becomes a Leave() event under VB.NET), would be expressed as:
Private Sub txtCommand_Enter(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtCommand.Enter

Page –65–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

But now that these events have parameters, many are wondering how to invoke them under
VB.NET? One can squeeze by on invoking a button click by instead invoking the button’s
PerformClick() method. But we would still be stuck with the GotFocus()event (now Enter()) for
txtCommand. Actually, the solution is very simple.
The first parameter of each event is the actual object being process; btnAdd for the btnAdd_Click()
event code, and txtCommand for the txtCommand_Enter() event code. Both of these have a second
argument object of an Eventargs type. As such you would simply specify the second parameter as
“New System.EventArgs()”. We use “New” because we are instantiating an new object.
However, if the method we invoke will not modify or use the Enventargs parameter, we could sneak
by cleanly by simply passing the EnventArgs parameter from the event we are invoking it from, if
we are indeed invoking it from within an event.
Following is the VB.NET upgrade of the above code:
Option Strict Off
Option Explicit On
Friend Class Form1
Inherits System.Windows.Forms.Form

Dim colList As New Collection

'******************************************************************
' txtCommand_GotFocus (this becomes txtCommand_Enter in VB.NET -- txtCommand_LostFocus would become txtCommand_Leave)
' When textbox gets focus (the user selected it, for example),
' then select entire contents.
'******************************************************************
Private Sub txtCommand_Enter(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtCommand.Enter
With Me.txtCommand
.SelectionStart = 0 'set selection to start of text
.SelectionLength = Len(.Text) 'select entire text
End With
End Sub

'******************************************************************
' txtCommand_KeyPress
' When typing data into the textbox, if the user types ENTER,
' then automatically invoke the ADD button.
'******************************************************************
Private Sub txtCommand_KeyPress(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles txtCommand.KeyPress
Dim KeyAscii As Short = Asc(e.KeyChar)
Select Case KeyAscii
Case 13 'CR?
Call btnAdd_Click(btnAdd, New System.EventArgs())
KeyAscii = 0
End Select
e.KeyChar = Chr(KeyAscii)
If KeyAscii = 0 Then
e.Handled = True
End If
End Sub

'******************************************************************
' btnAdd_Click
'******************************************************************
Private Sub btnAdd_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAdd.Click
colList.Add(Me.txtCommand.Text)
Call txtCommand_Enter(txtCommand, New System.EventArgs())
End Sub
End Class

And sure enough, the upgrade performs the conversions of these invocations just as I described. All
that is left here is to tighten up the code. For example, event txtCommand_KeyPress, although it works
perfectly, could be seriously tightened up for much faster operation:
Private Sub txtCommand_KeyPress(ByVal sender As Object, ByVal e As KeyPressEventArgs) Handles txtCommand.KeyPress
If e.KeyChar = vbCr Then 'CR?
btnAdd.PerformClick() 'yes, force adding the current data
e.KeyChar = vbNullChar 'nullify the key
e.Handled = True 'tell system not to handle this key code further
End If
End Sub

Page –66–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

63. Dealing With TextBox Locked Property Changes


The VB6 TextBox Locked property has become the VB.NET ReadOnly property, which makes
more sense. VB.NET still has a Locked property, but it is now used to prevent a control from being
resized. This is usually due to anchoring of a control on a form, which is affected when a form
resizes. For example, were a TextBox to be anchored to the top, bottom, left, and right of a form,
then as the form size changes, the TextBox would proportionally stretch. However, if the TextBox has
its Locked property set to True, then it will not resize with the form and maintain a fixed size.

64. Dealing With changes to the Tag Property


The Tag property on various VB6 controls is no longer limited to simply String data, but is now an
Object property under VB.NET. This makes it much more powerful, because you can now use it to
reference whole class objects or structures. But in most cases one will continue to use a control’s
Tag property with text data. As such, use it’s ToString method to easily read its data as string text.

65. Dealing With Changes to the GotFocus and LostFocus Events


The VB6 upgrade will change a VB6 GotFocus event to an Enter event under VB.NET. However,
some may notice that the Enter event simply precedes the GotFocus event in sequence under
VB.NET. Likewise, a LostFocus event is upgraded to a Leave event, though under VB.NET, a
LostFocus event precedes a Leave event when you are changing focus using a Mouse, but the Leave
event precedes a LostFocus event when you change focus using the keyboard. Here is the
breakdown, according to Microsoft:
When you change the focus by using the keyboard (TAB, SHIFT+TAB, and so on), by calling the Select or SelectNextControl
methods, or by setting the ContainerControl.ActiveControl property to the current form, focus events occur in the following order:
1. Enter
2. GotFocus
3. Leave
4. Validating
5. Validated
6. LostFocus

When you change the focus by using the mouse or by calling the Focus method, focus events occur in the following order:
1. Enter
2. GotFocus
3. LostFocus
4. Leave
5. Validating
6. Validated

NOTE: If the CausesValidation property is set to False, the Validating and Validated events are suppressed.
The reason that the Enter and Leave events are used instead of the GotFocus and LostFocus events
is because under VB.NET, GotFocus and LostFocus have been changed to low-level focus events
that are tied directly to the WM_KILLFOCUS and WM_SETFOCUS Windows messages.
A LostFocus event corresponds to the WM_KILLFOCUS system message, which is send
immediately before the focus is removed from the control. A GotFocus event corresponds to the
WM_SETFOCUS system message, which is sent when a control has gained keyboard focus. As
such, their definition has certainly changed between VB6 and VB.NET.
The VB.NET Enter and Leave events correspond more directly to the VB6 GotFocus and
LostFocus events. Hence, the Enter and Leave events should now be used for all controls except the
Form class, because the Enter and Leave events are suppressed by the Form class. The equivalent
events used by the Form class are instead the Activated and Deactivate events, which also
correspond more directly to the VB6 GotFocus and LostFocus events for Forms.
NOTE: Do not attempt to set focus from within the Enter, GotFocus, Leave, LostFocus, Validating, or Validated event
handlers. Doing so forces the thread to yield control and can cause the application to stop responding to messages.

Page –67–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

66. Dealing With Changes to the VB6 SetFocus Command


Under VB6, when you wanted to set focus to a control or form, you specified the control’s SetFocus
method, such as TextBox1.SetFocus. Under VB.NET, this has changed to the Focus method, as in
TextBox1.Focus.

67. Dealing With Long-Pathing Through Namespaces


If you are apprehensive about long-pathing, and wonder if using MessageBoxButtons.AbortRetryIgnore
instead of System.Windows.Forms.MessageBoxButtons.AbortRetryIgnore is more efficient – do not be
concerned. The pathing, shortcutted or not, is only a map to allow direct access to a feature, such as
the AbortRetryIgnore constant. When the program is compiled, it no longer bothers with these long
paths, because by then it already knows precisely where to access information, because all target
locations are by then pre-computed.

68. Dealing With Changes to Multiple Document Interfaces


First, displaying a Window List for Multiple-Document Interfaces (MDIs)
has changed between MainMenu and ToolStripMenu implementations.
When using a MainMenu interface, which VB6 used, you set the MdiList
property of a MenuItem control, typically a Main Menu item named
&Window or &Windows. With the new VB.NET ToolStripMenu controls,
you should set the ToolStripMenu’s MdiWindowListItem property to the
menu item that will be used to display the window list under.
Second, under VB6, you added a MDI Parent window by selecting it from a
template, and you created an MDI Child menu by setting a standard window’s
MDIChild property to True. Also, the MDI Parent form’s menu was replaced
by the menu of any active Child form.
Under VB.NET, you create an MDI Parent window by setting a standard
window’s IsMdiContainer property to True, and you created an MDI Child
window by setting its MdiParent property to the MDI Parent form at runtime,
usually when instantiating the child form. For example:
'*******************************************************************************
' Subroutine Name : LoadFile
' Purpose : Load a text file into a new MDI Child Form and display it
'*******************************************************************************
Private Function LoadFile(ByVal Path As String) As Boolean
Static FileIndex As Integer = 0 'static refernce used for unique child form names

If Not FileIO.FileSystem.FileExists(Path) Then 'if the file does not exist...


Return False 'report failure
End If

Me.Cursor = Cursors.WaitCursor 'show that we are busy

Dim frm As New mdiChild 'instantiate a new Child Form


frm.MdiParent = Me 'make the form an MDI Child of this form
frm.Tag = Path 'save the full path to the file
FileIndex = FileIndex + 1 'bump the static file naming index
frm.Text = "Window" & CStr(FileIndex) 'apply a new title to the Child Form
frm.Show() 'display our child form (note that we MUST NOT specify Show(Me))

Dim TS As New System.IO.StreamReader(Path) 'open the file


With frm.TextBox1
.Text = TS.ReadToEnd 'read its contents into a textbox
.SelectionStart = 0 'show start of file
.SelectionLength = 0 'make sure that nothing is selected (otherwise whole file may be)
End With
TS.Close() 'close the file
TS.Dispose() 'dispose of resources

Me.Cursor = Cursors.Default 'show that we are no longer busy


Return True 'report success
End Function

Page –68–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

69. Dealing With Changes to a Button’s Cancel and Default Properties


Under VB6, a button control had a Cancel and Default property. If Cancel was set to True, then
pressing the Escape key (ESC) automatically triggered this button. Likewise, if the Default propery
were set to True, then pressing the Enter key, even within a TextBox, triggered that button. Only one
button on a form could have its Cancel and/or Default properties set to True (a single button could
have both set). The problem here is that each button on a form had these properties, and although the
system was nice enough to reset any other button that had one of these properties set, in case you
enabled it on another, I had always thought that it would have made more logical sense to place
these properties on the form, where you can simply assign a single button to each property.
Coincidentally, under VB.NET, this configuration has been simplified by placing these options on
the Form, through the form’s CancelButton and AcceptButton properties, which are assigned the
button control for which the Escape (Cancel) or Enter (Accept) keys will trigger.

70. Dealing With Changes to CheckBoxes


Under VB6, when you clicked a CheckBox, it triggered a Clicked event for the checkbox, and it also
toggled its Integer Value property between 0 (Unchecked) and 1 (Checked). A third value, 3
(Grayed), indicated an Indeterminate state. Under VB.NET, this is upgraded to a CheckChanged
event, and you can check the checkbox’s boolean Checked property for a value of False
(Unchecked), or True (Checked). Note further that you can also check the CheckState property for a
value of CheckState.Checked, CheckState.Unchecked, and CheckState.Indeterminate (Grayed).

71. Dealing With Property Conflicts With VB Commands


You may notice that VB.NET sometimes has conflicts (referred to as Pollution in Object Oriented
Programming) between different loaded enumerations. The most frequent clash is between a Form’s
Left and Right properties, and the VB string commands Left and Right. The easiest way around this
conflict is to declare a special VB alias at the top of the form, declared as “Imports VB =
System.VisualBasic”. This simple command can be used to ensure that when you want to grab the left
or the right of a string variable, you can simply specify VB.Left and VB.Right, as otherwise, if you
simply specified Left or Right, VB.NET would assume that you are referring to the Left or Right
properties of a form.
This VB alias is also handy for helping you to remember a forgotten VB command. All you have to
do is type VB and the dot, and a dropdown list of VB commands is immediately presented. I use this
feature a lot, even on non-Form source files.

72. Dealing With using Icons for Menu Images (Bitmaps) under VB.NET
VB.NET Menus allow you to specify bitmaps as menu images, which will be displayed on the left
side of a dropdown menu ribbon. However, you can use icons instead, which are more plentiful and
more available, by simply invoking its ToBitmap method. For example, if we were creating a new
ToolStripMenuItem, we could declare one like this:
'create a new menu item
Dim mnuItem As New ToolStripMenuItem("&Open", My.Resources.icnOpen.ToBitmap, New EventHandler(AddressOf mnuFileOpen_Click))

Or modify the image of an existing menu item by specifying something like this:
Me.mnuFileOpen.Image = My.Resources.icnOpen.ToBitmap

Or even from a file, doing it something like this:


Me.mnuFileOpen.Image = New Icon("C:\MyHipBag\Icons\MyOpenIcon.ico").ToBitmap

Page –69–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

73. Dealing With Converting VB6 Fast FSO File I/O to Faster VB.NET Streaming Methods
Under VB6, the fastest File Input/Output processing possible was with a File System Object, available
by setting a reference to either Microsoft Scripting Runtime (Scrrun.dll) or to Windows Scripting Host
Object Model (wshom.ocx, which ultimately rerouted to the IwshRuntimeLibrary.dll).
For example, under VB6 with an FSO, you could quickly read a text file into a TextBox like this:
Dim FSO As FileSystemObject 'file system object (reference to scrrun.dll or wshom.ocx needed)
Dim Ts As TextStream 'textstream object for file I/O

Set FSO = New FileSystemObject 'instantiate our File System Object


Set Ts = FSO.OpenTextFile(Path, ForReading, False) 'open the filepath for reading
myForm.TextBox1.Text = Ts.ReadAll 'read its contents into a textbox
Ts.Close() 'close the file
Set Ts = Nothing 'dispose of the TextStream resource
Set FSO = Nothing 'dispose of our file system object

If you wanted to do the same FSO operation under VB.NET, you would have to add a COM
reference to Windows Scripting Host Object Model (wshom.ocx) and then add “Imports
IWshRuntimeLibrary” to the top of the file, or you would have to add a COM reference to Microsoft
Scripting Runtime (scrrun.dll) and then add “Imports Scripting” to the top of the file. You could then
add the code above to do the same thing:
Dim FSO As New FileSystemObject 'instantiate a File System Object for File I/O
Dim Ts As TextStream = FSO.OpenTextFile(Path, ForReading, False) 'open the filepath for reading through a textstream object
'NOTE:if you use scrrun.dll instead of wshom.ocx, the above second parameter would be specified as 'IOMode.ForReading'
myForm.TextBox1.Text = Ts.ReadAll 'read its contents into a textbox
Ts.Close() 'close the file
Ts.Dispose() 'dispose of the TextStream resource
FSO = Nothing 'dispose of our file system object

As though a File System Object did not speed File I/O up fast enough, VB.NET features
StreamReaders and StreamWriter objects that kick File I/O into turbo charge. To perform the same
task as shown above, but without adding references or importing namespaces, and also do it a whole
lot faster, do the following:
Dim Ts As System.IO.StreamReader = FileIO.FileSystem.OpenTextFileReader(Path) 'open a Stream reader to the text filepath
myForm.TextBox1.Text = Ts. ReadToEnd 'read its contents into a textbox
Ts.Close() 'close the file and automatically invoke Ts.Dispose()

74. Dealing With Changes to Counting CheckBoxed ListBoxes


Under VB6, counting checkboxes under a Checked ListBox (a ListBox with its Style property set to
CheckBox), you counted the number of selected checkboxes by examining its Integer SelCount
property. You could quickly loop through the checked (selected) items in the list by going through
its Selected() array.
Under VB.NET, the Selected Items are kept within a Collection as Objects. To get a count of the
number of selected (checked) items, you would examine the Integer ListBox.SelectedItems.Count
property. To extract an item from the Collection, you would specify ListBox.SelectedItems(Index).
Because the returned item is an Object, you will have to cast it using CType (use DirectCast if you
know the actual type it is, which is much faster). However, since most people simply store strings in
the list, you can simply specify “Dim S As String = DirectCast(Me.ListBox1.SelectedItems(Index),
String)”, or better, “Dim S As String = Me.ListBox1.SelectedItems(Index).ToString”.

Page –70–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

75. Dealing With Changes to Mouse Pointer Icons


Under VB6, you could show the system was busy by specifying “Screen.MousePointer = vbHourglass”.
You may want to follow this with “DoEvents” to ensure that all other system messages are processed,
so that you will actually see the cursor become busy. And then, after you are finished performing a
long task, show that is no longer busy by specifying “Screen.MousePointer = vbDefault”.
Under VB.NET, this has changed slightly, but the documentation does not help. The documentation
tells you to set “System.Windows.Forms.Cursor.Current” to the cursor you want displayed. For example:
System.Windows.Forms.Cursor.Current = Cursors.WaitCursor 'note that WaitCursor replaces the VB6 vbHourglass
Application.DoEvents() '(although this is NOT necessary nor advisable for VB.NET, I just tried this to see if it helped)

Unfortunately, this seems to do absolutely nothing, which was driving me to distraction. However,
after some unrelenting experimentation, I discovered that I could do the following instead, and it
works perfectly every time:
Me.Cursor = Cursors.WaitCursor 'show that we are busy by assigning the wait cursor to the current form

NOTE: In the above, “Me” represents the current form. Also, if you added the command “Application.DoEvents()”
afterward, which was the trick to get the busy cursor to display under VB6, that this may even prevent the cursor from
changing on the screen under VB.NET.
Of course, to reset the cursor to the default, once you are fisnished performing a long task, you
would issue the following command:
Me.Cursor = Cursors.Default

76. Dealing With Specific Changes to the KeyDown and KeyPress Events
NOTE: Although much of this material was covered in a previous point, it is worth going through again with a fine-
toothed comb to address specific changes to the KeyDown() and KeyPress() events.
Under VB6, the KeyDown event supplied you with an Integer Keycode parameter, which gabbed the
system code for the key typed, and it provided an Integer Shift property, which allowed you to check
for the Shift, Cntrl, or Alt keys being pressed. You could disable the KeyCode by setting it to 0,
which in turn caused the KeyPress event to be supressed. For example, consider this VB6 code:
'*******************************************************************************
' Subroutine Name : lstEnums_KeyDown (VB6)
' Purpose : Allow DEL key to select Delete button
'*******************************************************************************
Private Sub lstEnums_KeyDown(KeyCode As Integer, Shift As Integer)
Select Case KeyCode
Case 46 'DEL key
If Me.cmdDelete.Enabled Then 'activate delete button if it is enabled
Me.cmdDelete.Value = True 'click Delete button
KeyCode = 0 'disable KeyCode
End If
End Select
End Sub

The VB6 KeyPress event was handled after the KeyDown event, but before the KeyUp event. The
KeyPress event provides a KeyAscii parameter that was the ASCII value of the key pressed (duh).
You could disable it by setting the KeyAscii code to 0, which cancelled further system processing of
the code. For example, consider this VB6 code:
'*******************************************************************************
' Subroutine Name : txtNewName_KeyPress (VB6)
' Purpose : Filter keyboard so that invalid data cannot creep in
'*******************************************************************************
Private Sub txtNewName_KeyPress(KeyAscii As Integer)
Select Case KeyAscii
Case 1 To 31 'allow control keys
Case Else
Select Case UCase$(Chr$(KeyAscii)) 'check uppercase character code
Case "A" To "Z", "0" To "9", "_" 'allow A-Z, a-z, 0-9, and "_"
Case Else
KeyAscii = 0 'disallow others
End Select
End Select
End Sub

Page –71–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

Under VB.NET, the rules have changed a bit, but I think it is for the better. Consider the following
example of a VB.NET version that merges the above VB6 KeyDown event:
'*******************************************************************************
' Subroutine Name : txtName_KeyDown (VB.NET)
' Purpose : Allow DEL key to select Delete button
'*******************************************************************************
Private Sub txtName_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles txtName.KeyDown
Select Case e.KeyValue
Case 46 'DEL key (we could have also checked ReadOnly e.KeyCode against Keys.Delete)
If Me.cmdDelete.Enabled Then 'command button enabled?
Me.cmdDelete.PerformClick() 'yes, press Delete key
e.SuppressKeyPress = True 'disable further processing in KeyPress() event
End If
End Select
End Sub

Notice that there are 3 key differences noted. First, notice that we obtained the KeyValue (the
VB.NET version of a VB6 KeyCode), from the “e” parameter, which is declared as a KeyEventsArg.
Second, we invoked the Delete key using its PerformClick() method. Third, instead of setting
e.KeyValue to 0 to supress the KeyPress event, as we might do if we were following the VB6 model,
we instead supressed the KeyPress event (and further processing) by setting the “e” parameter’s
Boolean SuppressKeyPress property to True. If we had tried to change e.KeyValue to 0, as we had
done under VB6, an error would be generated, because under VB.NET, KeyValue is ReadOnly.
Under VB.NET, there is a slight change to how the KeyPress event handles its code from the way
VB6 did it. For example, the “e” parameter is now a KeyPressEventArg, which provides not an
Integer KeyAscii code as VB6 did, but rather a KeyChar parameter that is of type Char. Also, to
cancel further processing from the KeyPress event and to prevent an invalid code from being sent to
the operating system for default processing, instead of setting the value to zero as we did under VB6,
we set the boolean e.Handled parameter to True, which tells the operating system that we have
handled the processing of the key and that no further processing is required.
Consider the following rather involved, but useful syntax parsing example:
'*******************************************************************************
' Subroutine Name : txtConstValue_KeyPress (VB.NET)
' Purpose : Filter keyboard so that invalid data cannot creep in
' : Process a command line such as "(BASE + 3) 'lower value"
'*******************************************************************************
Private Sub txtConstValue_KeyPress(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles txtValue.KeyPress
Dim KeyAscii As Integer = Asc(e.KeyChar) 'grab ASCII code of character
Dim S As String = Trim(Me.txtConstValue.Text) 'get contents of assignment data (a Constant value assignment)
Dim J As Integer = -1 'init location index
Dim I As Integer = InStrRev(S, "'") 'comment tag present?
If CBool(I) Then
J = Me.txtConstValue.SelectionStart + 1 'if so, check for selection point
End If
Dim AllowLC As Boolean = J >= I 'set lowercase allowance flag if selection beyond comment tag

Select Case KeyAscii


Case 1 To 31 'ignore control keys
Case Else
Dim C As String = UCase(Chr(KeyAscii)) 'get text version of code
If CBool(InStr(1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ &1234567890()'", C)) Then
If Not AllowLC Then 'if not allowing lowercase data...
e.KeyChar = CType(C, Char) 'update e.KeyChar to uppercase
End If
ElseIf CBool(InStr("+-", C)) Then 'if math involved, ensure enbraced by parenstheses
S = Trim(Me.ConstValue.Text) 'grab trimmed text
If VB.Left(S, 1) <> "(" Then 'no paren?
S = "(" & S & ")" 'no, so enclose it
With Me.ConstValue
.Text = S
.SelectionStart = Len(S) - 1 'set insert point immediately before ')'
End With
End If
Else
e.Handled = True 'cancel the KeyPress event, and prevent further OS handling of this key
End If
End Select
End Sub

Page –72–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

77. Dealing With Changes to Drag and Drop


Drag and Drop has changed profoundly between VB6 and VB.NET. The changes are significant enough
that the Upgrade Wizard will make absolutely no attempt to upgrade the code at all, except to tell you in
an upgrade note that it failed to upgrade something. This sends many developers into a tail-spin panic.
One the cover of the book, The Hitchhikers Guide to the Galaxy, written by the late Douglas Adams,
there were two words printed in large bold friendly letters: DON’T PANIC. This is good advice to those
with a predisposition toward tail-spin panics, or jumping off cliffs when startled by a small mouse, who
had absolutely no intention of starting anybody, or for seeing you hurling to a gloomy end.
First, the changes to the VB.NET version were not meant to frustrate you, but rather to make Drag
and Drop much more powerful (and ironically, easier – actually it has, once you understand it).
Second, the reason that the VB6 code was not upgraded was because the Upgrade Wizard claimed
that there were too many subtle changes to keep track of in a reliable manner by an automated
processor like the Upgrade Wizard (or so they say). But that does not mean that the actual upgrading
of the code must be difficult or frustrating at all. In fact, resolving it is so easy that you may truly
wonder why the Upgrade Wizard did not bother with automatically upgrading them, thus avoiding
all the frustration, hair-pulling, the tossing of hands into the air, tail-spin panics, or hurling off cliffs.
If you do a search on the web or in the MSDN Help Library for “Drag and Drop Overview”, or just
go to http://msdn.microsoft.com/en-us/library/ms742859.aspx, you will find a great starting point for
easily resolving any problem that you might encounter while using Drag and Drop. I wish I had
found this resource before I had gone through all my heartaches; but I hope that this helps you. On
top of that, I should have known that all I would have to do is search for a topic with “Overview” in
the title and I would be where I should always want to start, anyway.
When VB6 was the only game in town, developers kept complaining, “Why can’t Drag and Drop do
this,” or, “Why can’t Drag and Drop do that?” This list of complaints was long. Well, VB.NET’s
Drag and Drop now does all that. And that is why you now see all the differences between VB6’s
and VB.NET’s implementation of that technology. In addition, VB6 used OLE (Olé; Object Linking
and Embedding) for Drag and Drop support, but OLE is an aging technology that .NET, with its
much faster messaging technology, is trying to wean us away from.
When doing Drag and Drop under VB.NET, usually all we need to remember is that filenames are
returned as string arrays, that the Clipboard’s DataFormats object should become our new best
friend, and that you must always remember to now implement a DragEnter() event on your target
objects, even if it is to simply issue “e.Effect = DragDropEffects.All”, just so you can drop
something onto it (it is not enough to just enable EnableDrop on the control). These few pieces of
advice will help you out of most any trouble you may wander into when using Drag and Drop.
Imagine this scenario (an actual one I faced when I first met this implementation difference): Under
VB6, I want to drop one or more files that can be dragged from my File Browser onto a ListBox
named lstFiles in an application. The lstFiles object is set to accept Drag and Drop. I did this by
setting that object’s OLEDropMode property from None (0) to Manual (1). This property enables
drag recognition and will display a drag icon for the mouse cursor when it is dragged over it. I also
have an OLEDragDrop() event that reacts to dropping objects onto lstFiles.
All that OLEDragDrop() does is adds the file or files to the bottom of the list. For example:
Private Sub lstFiles_OLEDragDrop(ByRef Data As Object, ByRef Effect As Integer, ByRef Button As Integer, _
ByRef Shift As Integer, ByRef X As Single, ByRef Y As Single)
Dim Idx As Integer
For Idx = 1 To Data.Files.Count 'parse through all possible files in the list
Me.lstFiles.Items.Add(Data.Files(Idx)) 'append each to the end of the file list
Next Idx
End Sub

Page –73–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

As you can see, the Data object contains a 1-based Collection object that holds a list of one or more
file paths, which we can easily loop through to pick up all of its entries. This, and single items that
you could grab using the Drag Data object’s GetData() method were all the capability VB6 Drag
and Drop had. Hmm. Not very impressive.
However, VB.NET comes along and it seems like a monkey wrench was thrown into the works (a
monkey wrench, or spanner in Britain, is a large adjustable wrench with a long fulcrum arm that was
popular in the nineteenth century, though now typically used in only really tough, tight
situations, or plumbing, where you need to monkey around with a stuck pipe or large nut – it
also made a good, but sloppy hammer).
When I upgraded my application, I discovered that the Drag and Drop code did not likewise
upgrade. So I tried to take a look at what I needed to do. I dutifully enabled the EnableDrop
property on my upgraded lstFiles object by setting it to True.
I then added a DragDrop()event to the lstFiles object, but I was not quite sure what to do with it. I
did notice that my “e” parameter was of type DragEventArgs, and that it had a Data object, but this
Data object did not have a Files collection. So I took a look at the documentation to see what I could
glean from that. What I gathered was that I needed to use the Data object’s GetData() method to
acquire data (duh). All I had to do was provide GetData with a Format string. All the examples I saw
were for basic types, like “String” and such. But I was not getting anywhere with that, so I thought I
would use the GetFormats() method, which returned a string array, listing all the acceptable
formats, and I put them into a message box. But I was still not getting any results. Plus, the mouse
cursor looked like this when the mouse was dragged over my lstFiles control:
More reading revealed that I had to parse the data being dragged and decide if it could be dropped
onto my target object, and to set the proper copy effect for that object, which was accomplished
through the object’s DragEnter()event. Though I was so far getting nowhere on the format, I really
liked this new DragEnter()event idea a whole lot better than the really limited support we had in
VB6. So I cheated, for the time being, by simply setting my Effects property to Copy, just so I could
finally fiddle around in the DragDrop() event:
Private Sub lstFiles_DragEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles lstFiles.DragEnter
e.Effect = DragDropEffects.Copy
End Sub

With that, my message box in my DragDrop event popped up with the list of formats
available. Well, the last three formats looked promising, especially FileDrop and
FileName. I recognized FileDrop from working with Clipboard I/O, using the
DataFormats class, which featured a number of string members specifying,
surprisingly enough, data formats. FileDrop was used in clipboard I/O to handle
copying a number of files. As such, I assumed (rightly so, as it turned out) that I could
use Filename or FileDrop interchangeably, though I prefer FileDrop simply because I
can avoid misspellings by using the DataFormats class.
So with that, I changed my above DragEnter() event to:
Private Sub lstFiles_DragEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles lstFiles.DragEnter
If e.Data.GetDataPresent(DataFormats.FileDrop, True) Then 'Note that you can use "FileName" or even "FileDrop" in
e.Effect = DragDropEffects.Copy 'place of DataFormat.FileDrop. Parameter TRUE allows other compatible
Else 'formats to be converted to the specified target format.
e.Effect = DragDropEffects.None 'Put up a no-entry sign
End If
End Sub

This worked for file dragging, but then I ran into only a minor snag in my DragDrop event. But this
ended up only helping me to better understand the special handling that Drag and Drop still affords
files. As an experiment, I had placed the following line into my DragDrop event:

Page –74–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

MsgBox(e.Data.GetData(DataFormats.FileDrop).ToString) 'I used ToString because the returned data is of type Object

The result, instead of a filename, was “System.String[]”. This did not do very well as a
filename, but it was informing me that the result was actually a string array (C# and C++
use square brackets instead of VB’s parentheses to indicate arrays). So, as a further
experiment, I changed my test code to this:
Dim DataFiles() As String = e.Data.GetData(DataFormats.FileDrop, True) 'grab file list
Dim Nms As String = vbNullString 'init list accumulator
For idx As Integer = 0 To UBound(DataFiles) 'loop through file list
Nms &= CStr(idx) & ": " & DataFiles(idx) & vbCrLf & vbCrLf 'append each file
Next
MsgBox(Nms) 'display result

And that did the trick.


In reviewing all this, I have come to the conclusion that it would have been possible for the Upgrade
Wizard to have upgraded the code. It would have had to add a DragEnter event, but it should have
simply set “e.Effect = DragDropEffects.All”, but added an UPGRADE TODO notice that the
developer should specify the formats that they want to allow, and the type of drop events they want
to support. In upgrading the “Data.Files” VB6 collection, a generic “Dim DataFiles() As String =
e.Data.GetData("FileName", Yes)” is easily possible, and instead of looping from 1 to the count of
Collection items, you loop from 0 to the Ubound size of the DataFiles array.
So in review, the following VB6 code:
Private Sub lstFiles_OLEDragDrop(ByRef Data As Object, ByRef Effect As Integer, ByRef Button As Integer, _
ByRef Shift As Integer, ByRef X As Single, ByRef Y As Single)
Dim Idx As Integer
For Idx = 1 To Data.Files.Count 'parse through all possible files in the list
Me.lstFiles.Items.Add(Data.Files(Idx)) 'append each to the end of the file list
Next Idx
End Sub

Can be upgraded to the following VB.NET Code:


Private Sub lstFiles_DragDrop(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles lstFiles.DragDrop
Dim DataFiles() As String = e.Data.GetData(DataFormats.FileDrop, True) 'get file list
For idx As Integer = 0 To UBound(DataFiles) 'copy filepaths to list
Me.lstFiles.Items.Add(DataFiles(idx))
Next
End Sub

NOTE: If you have set Option Strict On, as I do, then you must also cast the data returned by GetData(), because it
returns a generic Object type. We know that this type is actually a String Array, so we can use the DirectCast()
command to cast it to string: Dim DataFiles() As String = DirectCast(e.Data.GetData(DataFormats.FileDrop,
True), String()).

Of course, you will also have to remember the DragEnter() event, which will allow you to specify
what kinds of files your target control will allow, which is now handled during dragging, rather than
generically by the enablement of a control parameter, which had invited the undesired possibility of
invalidly formatted data being dropped under VB6:
Private Sub lstFiles_DragEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles lstFiles.DragEnter
If e.Data.GetDataPresent(DataFormats.FileDrop, True) Then 'Note that you can use "FileName" or even "FileDrop" in
e.Effect = DragDropEffects.Copy 'place of DataFormat.FileDrop. Parameter TRUE allows other compatible
Else 'formats to be converted to the specified target format.
e.Effect = DragDropEffects.None 'Put up a no-entry sign
End If
End Sub

NOTE: Just a reminder, I use the optional parameter “True” to get the GetData() and GetDataPresent() methods
to allow compatible format that might not be of that type to be converted to that type.
Overall, I am very impressed with VB.NET’s implementation of Drag and Drop. It is easy to use
and easy to upgrade, and offers me much tighter control over what can or cannot be dropped on a
control that accepts dropped items.

Page –75–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

78. Dealing With the Loss of MAPI Controls


Plain and simple, MAPI (Messaging Application Program Interface) is not yet a .NET technology; it is
still COM technology (Common Object Model), used by ASP (Active Server Page), IIS (Internet
Information Server), and any other COM application that access email. Being COM-based, you should
not expect to see the VB.NET Toolbox sport controls such as VB6’s MAPISession or MAPMessage.
Under the VB6 implementation of MAPI, it used the MAPSession control to (what else?) manage a
MAPI session. The MAPIMessage control was used to process email messages, both incoming (POP3;
Post Office Protocol – Version 3) and outgoing (SMTP; Simple Mail Transfer Protocol).
When you upgrade a VB6 MAPI application to VB.NET, you will notice that the upgraded
application will still have the VB6 MAPISession and MAPIMessage controls on any form
that had them before. This is because their control sources have been copied locally and are
referenced internally. Under .NET, a copy of the COM-based MSMAPI32.DLL is converted
into a non-COM version (its DLLRegisterServer() entry is disabled) and saved to a project-local file
named Interop.MSMAPI32.DLL. But, because both VB6 controls actually accessed this DLL provider
through the MSMAPI32.OCX ActiveX interface, another project-local non-COM DLL named
AxInterop.MSMAPI32.DLL is internally compiled by .NET that will duplicate both the ActiveX visual
Interface construction services for the controls, as well as the function mapping services to the new
Interop.MSMAPI32.DLL.
Having found these controls on their upgraded applications, many developers also want to add them to
other VB.NET projects so they can take advantage of them there, but they cannot seem to find a way to
easily access the new DLLs from those new projects. It is doable, but it requires numerous coding hacks.
But relax. Why not just add these two VB6 controls to your VB.NET Toolbox and access them directly?
1. With any form up on the Visual Studio screen so that the IDE toolbox is active, right-click a toolbox category you want to add the
MAPI controls to (if you want to add them to their own category, such as to one named COM, right-click any category and select the
Add Tab option, then type the name of your category, such as COM, press ENTER, then right-click that tab).
2. Select the Choose Items… option, and wait (a long while) for the IDE to build a massive control reference list from the computer.
3. Once the Choose Toolbox Items dialog is finally displayed – select the COM Components tab.
4. Scroll down and put checkmarks in the check boxes for Microsoft MAPI Messages Control, Version 6.0, and Microsoft MAPI
Sessions Control, Version 6.0. (Both of these were actually linked to MSMAPI32.OCX, which in turn drilled down to MAPI32.DLL,
but they will now both link to a .NET-compiled axInterop.MSMAPI32.DLL and drill down to Interop.MSMAPI32.DLL).
5. Click the OK button, and you will find these two controls now in your selected Toolbox category list, and you can begin using these
controls just exactly as you would had been using them under VB6.

NOTE: If you do not find these entries in the Choose Toolbox Items dialog box, then you may not or no longer have the VB6
redistributables on your system, so you will have to minimally install the free Runtime Distribution Pack for Service Pack 6
for Visual Basic 6.0, available from Microsoft (http://www.microsoft.com/downloads/details.aspx?FamilyId=7B9BA261-
7A9C-43E7-9117-F673077FFB3C&displaylang=en). You are allowed to do this even if you no longer own VB6.
However, if you do not want to use VB6 form controls and use as little resources and code as possible,
you can easily add VB.NET native methods to perform these tasks for you. The code to do that in
covered in an article named “Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET” in
the companion volume to this book, “Enhancing Visual Basic .NET Far Beyond the Scope of Visual
Basic 6.0” (www.slideshare.net/davidrossgoben). Download it for free to learn how to do it.

Page –76–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

Closing Remarks
This concludes the assistance guide, “Navigating Your Way through Visual Basic 6.0 to Visual Basic .NET
Application Upgrades”. Anything not covered by this document can usually be resolved with similar ease. Always
remember that each warning inserted by the Upgrade Wizard also includes a link to the MSDN help, which offers
you further assistance on the problem it either finds or, most often, simply cautions you to the per chance of a
problem. Using these tools and this guide together, you should find upgrading to be much less of a chore.
See the free companion manual, “Enhancing Visual Basic .NET Far Beyond the Scope of Visual Basic 6.0”
(www.slideshare.net/davidrossgoben) for more detailed information of the differences between VB6 and VB.NET.
It is also recommended that you download the free 719-page VB6 to VB.NET upgrade guide e-book, “Upgrading
Visual Basic 6.0 to Visual Basic .NET and Visual Basic 2005” from Microsoft Developer Network (MSDN) at
http://msdn.microsoft.com/en-us/library/aa480541.aspx. Pay particular attention to Chapter 7 through 9. You can
also download the “Visual Basic 6.0 Upgrade Assessment Tool” from here, along with before and after sample
files. This guide was developed jointly by the Microsoft patterns & practices team and ArtinSoft,
(www.artinsoft.com) a company with vast experience in Visual Basic upgrades and the developer of the Visual
Basic Upgrade Wizard and the Visual Basic Upgrade Wizard Companion. This guide provides valuable
information for organizations who are considering upgrading their VB6-based applications and components to
VB.NET. It provides proven practices to reach functional equivalence with a minimal amount of effort and cost,
as well as guidance for common advancements after the application is running on the .NET framework.
Another free 547-page e-book from Microsoft is “Upgrading Microsoft Visual Basic 6.0 to Microsoft Visual
Basic .NET” at http://msdn.microsoft.com/en-us/vbrun/ms788236.aspx. It is the complete technical guide to
upgrading Visual Basic 6 applications to Visual Basic .NET, covering all upgrade topics from APIs to ZOrders. It
shows how to fix upgrade issues with forms, language, data access, and COM+ Services, and how to upgrade
applications with XML Web services, ADO.NET, and .NET remoting. It also provides big-picture architectural
advice, a reference of function and object model changes, and hundreds of before-and-after code samples.
Still another free 274-page e-book from Microsoft is “Introduction to Microsoft Visual Basic 2005 for
Developers” (http://msdn.microsoft.com/en-us/vbasic/ms788235). Even though it is designed around VB2005, it
is worth its weight in gold. VB2005 is a giant step forward from earlier editions of VB.NET, and is in my view
almost VB2008, which is the starting platform you need to get really serious in VB development. If you currently
work with Visual Basic 6, these authors fully understand the adoption and code migration issues you'll encounter.
They'll step you through a quick primer on .NET Framework programming, offering guidance for a productive
transition. If you already work with .NET, you'll jump directly into what's new, learning how to extend your
existing skills. From the innovations in rapid application development, debugging, and deployment, to new data
access, desktop, and Web programming capabilities, you get the insights and code walkthroughs you need to be
productive right away.
Though some of the information provided in the above free Microsoft books are clearly dated, sometimes offering
solutions using older, more convoluted command paths than those offered by VB2008 or VB2010 (often thanks to
the new “My” namespace), those older solutions are still valid (I have tried to address those particular solutions
in this document and its companion).
To see how P/Invokes to the system are defined and used in .NET, visit PInvoke.net (www.pinvoke.net). This site is
a massive knowledgebase of .NET P/Invoke information, providing examples for VB.NET and C#. This is an
interactive, user-supported site. If you have a P/Invoke that is not yet documented, or is not presented for your
development language, such as VB.NET, here is your chance to contribute your knowledge to the hive.
Another place to get great upgrade information is VB Migration Partners. Although products and services are
sold here, it also features a wealth of freely available information (www.vbmigration.com).

David Ross Goben


Last update: Saturday, May 21, 2011, 4:47:22 PM
Contact: david.ross.goben@gmail.com
Please feel free to inform me of any errors or omissions, or of topics that I should cover.

Page –77–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

About the Author


David Ross Goben has been a professional software engineer, a writer, and an obsessive
researcher. Of Jewish descent, he has extensively explored Biblical history, ancient
cultural thinking, and ancient slang for over three decades, which has resulted in his
seminal work: A Gnostic Cycle: Exploring the Origin of Christianity. He has written
numerous books, manuals, and magazine articles, many uncredited, or authored under
pen names. He has been involved in computing since long before personal computers
were available even in kit form, he was a contributing Editor to 80 Micro Magazine, and
there, co-wrote the Feedback Loop column with Beve Woodbury, under the pen name, Mercedes Silver. His
interests include Cosmology, Quantum Physics, human-machine interaction, the Global Warming Myth, The
Electric Universe Theory, Perpetual Energy Technology, Quartz Technology, Dream Walking, and the study of
the bio-mechanical origins of life.
He is currently exploring the possibility that Albert Einstein got his Theory of Special Relativity wrong. Just about
everyone is familiar with his equation, E=MC2, Energy (E) equals the Mass (M) times the speed of light (C)
squared. But this Mass to Energy conversion is only half of the Special Relativity expression.
The other half of Special Relativity involves Velocity (V), and is the calculation of Time Displacement (T):

Einstein calculated E = MC2 / T, which would mean that at the speed of light, an object would attain infinite
volume and infinite mass. Correct me if I am wrong, but even an ordinary mortal like me sees this as ridiculous,
because this would mean that we would attain several quintillion times the volume and mass that exists in the
entire universe (and even that may an infinite understatement). All you have to do is simply ponder it for a
moment.
However, reverse the order, where E = T / MC2, and all calculations yield the same results, save one, and that is
that instead of attaining infinite volume and infinite mass at the speed of light, one instead moves into an alternate
universe. What most people do not realize is that the very atoms of our body, at this very moment, are already
vibrating at just under the speed of light. Just a tiny push and we would become multidimensional beings. I think
this is why Quantum Physics, which is still nowhere near being a perfect model for planck-scale physics, will,
even so, discover the Far World, or what the ancients called the Underworld or the Hidden World, or most
everyone today refers to as the Afterlife or Heaven. Quantum Physics has already demonstrated that nothing in the
universe has ever existed without Consciousness. So, where was Consciousness when the Universe was created
out of literally nothing during a causal Singularity Event? Evidence shows that it existed. And that has been the
crux of great debates throughout history.
There is an easy experiment that can prove that Einstein’s Theory of Special Relativity is inverted. Weigh a very
heavy object, and then drop it onto a solid base, such as a concrete floor. Now weight the object again. It will
weigh less… for about 20 minutes; at which time its full weight will finally return. Where did the missing mass go
to during those 20 minutes? If Einstein’s calculation had been correct, the object would have actually weighed
more after being dropped, not less.

Page –78–
Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben

The following Visual Basic documents are publicly available at: http://www.slideshare.net/DavidRossGoben, and at Google Docs at:
http://docs.google.com/leaf?id=0B_Dj_dKazINlN2JlY2EwMmEtNGUyMy00NzQzLTliN2QtMDhlZTc5NDUzY2E5&sort=name&layout=list&num=50.

Page –79–

You might also like