• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Navigating Your Way Through Visual Basic 6.0 to Visual Basic.NET Application Upgrades
 

Navigating Your Way Through Visual Basic 6.0 to Visual Basic.NET Application Upgrades

on

  • 8,830 views

This document helps users deal with most issues they will face when transitioning VB6 applications to VB.NET. Many examples of solutions are featured.

This document helps users deal with most issues they will face when transitioning VB6 applications to VB.NET. Many examples of solutions are featured.

Statistics

Views

Total Views
8,830
Views on SlideShare
8,826
Embed Views
4

Actions

Likes
1
Downloads
282
Comments
0

3 Embeds 4

http://www.techgig.com 2
file:// 1
http://tvjose1969.blogspot.mx 1

Accessibility

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Navigating Your Way Through Visual Basic 6.0 to Visual Basic.NET Application Upgrades Navigating Your Way Through Visual Basic 6.0 to Visual Basic.NET Application Upgrades Document Transcript

    • “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-2012 by David Ross Goben All rights reserved. (Last updated July 22, 2012) (The above two logos are registered trademarks of Microsoft Corporation) NOTE: If you have trouble downloading a PDF copy of this document, go to Google Docs and download it freely: https://docs.google.com/open?id=0B_Dj_dKazINlMjViMGUzZTUtMWFiZS00ZGNhLWE1NjEtMDQ4NjcwNmNiOTFm
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross GobenTABLE OF CONTENTSIntroduction.................................................................................................................................................. 41. Dealing with structures passed to P/Invokes containing strings ................................................................ 72. Dealing with structures passed to P/Invokes not containing strings .......................................................... 73. Dealing with the loss of the VB6 BackStyle property ................................................................................ 74. Dealing with passing strings directly to P/Invokes .................................................................................... 85. Dealing with VarPtr, ObjPtr, StrPtr, VarPtrArray, and StrPtrArray ............................................................. 86. More on StrPtr......................................................................................................................................... 87. Dealing with passing strings ByRef to P/Invokes ...................................................................................... 98. Dealing with fixed-length strings............................................................................................................. 109. Dealing with fixed-length arrays ............................................................................................................. 1310. Dealing with RichTextBox Property Renaming ....................................................................................... 1411. Dealing with recovering the LenB() function ........................................................................................... 1512. Dealing with passing parameters to P/Invokes “As Any”......................................................................... 1613. Dealing with StrConv: converting between Unicode and ANSI strings .................................................... 1714. Dealing with AddressOf/Missing Delegate issues................................................................................... 1915. Dealing with Dir() function warnings....................................................................................................... 2116. Dealing with Item issues in Collections .................................................................................................. 2117. Dealing with late-bound Object references............................................................................................. 2218. Dealing with VB6 parameterless defaults ............................................................................................... 2219. Dealing with VB6 Null Propagation ........................................................................................................ 2320. Dealing with referencing Objects before they are initialized .................................................................... 2321. Dealing with TextChanged and Resize events firing before the Form Load event ................................... 2322. Dealing with renamed properties............................................................................................................ 2423. Dealing with the loss of the ListCount property....................................................................................... 2424. Dealing with changed MousePointer warnings ....................................................................................... 2425. Dealing with changes to RECT structures .............................................................................................. 2526. Dealing with the loss of the Initialized and Terminate events .................................................................. 2627. Dealing with changes to Enumeration references................................................................................... 2628. Dealing with VB6 Namespace Twips conversions .................................................................................. 2729. Dealing with user-defined Twips constants............................................................................................. 2730. Speeding code by removing references to the VB6 Compatibility Library................................................ 2831. Speeding returned VB6 Namespace List Item values............................................................................. 2832. Dealing with changed Date/Time shortcut format options ....................................................................... 2933. Dealing with Date value conversions...................................................................................................... 2934. Speeding Format command use in VB.NET ........................................................................................... 3035. Dealing with Screen properties .............................................................................................................. 3036. Dealing with On Iexpr GOTO ................................................................................................................. 3137. Dealing with On Iexpr GoSub................................................................................................................. 3438. Dealing with updating VB6 error trapping ............................................................................................... 3439. Dealing with destroying Objects............................................................................................................. 3540. Dealing with changes to Common Dialogs ............................................................................................. 3641. Dealing with VB6.CopyArray.................................................................................................................. 3842. Dealing with the loss of the ItemData List Object property...................................................................... 3943. Dealing with changes to Font manipulation ............................................................................................ 4044. Dealing with changes to Form commands.............................................................................................. 4445. Dealing with VB6’s automatic Boolean conversions ............................................................................... 4646. Dealing with Option Strict On issues ...................................................................................................... 4747. Dealing with Image and Picture Object upgrades ................................................................................... 4848. Dealing with the loss of VB6 Control Lists and how to recover their functionality..................................... 4949. Dealing with changes to MouseMove parameter list changes................................................................. 5250. Dealing with changes to remotely firing Button clicks ............................................................................. 5251. Dealing with no AVI animation control in VB.NET and how to easily add a free one................................ 5252. Dealing with changes to Resources Management .................................................................................. 5453. Dealing with the loss of the App statement............................................................................................. 5754. 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 Goben55. Dealing with Collection and List clearing ................................................................................................ 5856. Dealing with Adding Auxiliary Files to ClickOnce Deployment (Publish).................................................. 5857. Dealing with changes to Text Box SelStart and SelLen Properties ......................................................... 5958. Dealing with changes to ToolTips .......................................................................................................... 6059. Dealing with changes to ListView........................................................................................................... 6060. Dealing with changes to Toolbar Button and Button Menu Clicks ........................................................... 6161. Dealing with Unload Form commands.................................................................................................... 6362. Dealing with The Loss of the VB6 NewIndex Property............................................................................ 6363. Dealing with Process Handling in the KeyPress, KeyDown, and KeyUp Events...................................... 6464. Dealing With Invoking Handled Events Under VB.NET........................................................................... 6565. Dealing With TextBox Locked Property Changes ................................................................................... 6766. Dealing With changes to the Tag Property ............................................................................................. 6767. Dealing With Changes to the GotFocus and LostFocus Events.............................................................. 6768. Dealing With Long-Pathing Through Namespaces ................................................................................. 6869. Dealing With Changes to the VB6 SetFocus Command ......................................................................... 6870. Dealing With Changes to Multiple Document Interfaces ......................................................................... 6871. Dealing With Changes to a Button’s Cancel and Default Properties ....................................................... 6772. Dealing With Changes to CheckBoxes................................................................................................... 6973. Dealing With Property Conflicts With VB Commands ............................................................................. 6974. Dealing With using Icons for Menu Images (Bitmaps) under VB.NET ..................................................... 6975. Dealing With Converting VB6 Fast FSO File I/O to Faster VB.NET Streaming Methods ......................... 7076. Dealing With Changes to Counting CheckBoxed ListBoxes.................................................................... 7077. Dealing With Changes to Mouse Pointer Icons....................................................................................... 7178. Dealing With Specific Changes to the KeyDown and KeyPress Events .................................................. 7179. Dealing With Changes to Drag and Drop ............................................................................................... 7380. Dealing With the Loss of MAPI Controls................................................................................................. 7681. Dealing With Displaying a Checkbox as a Button ................................................................................... 76Closing Remarks ....................................................................................................................................... 77About the Author ....................................................................................................................................... 78 Page –3–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross GobenIntroductionWhen a Visual Basic 6.0 (VB6) application is upgraded to Visual Basic .NET (VB.NET; to at leastVB2008) using the Visual Basic Upgrade Wizard, chances are that, once the upgraded application comesup in Visual Studio or Visual Basic Express, its Task List will present you with a number of, or typicallyquite 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 arenotices, and are only that: Notices. And virtually all of these added tagged comments can be safelyreviewed, ignored, and then deleted without fanfare. Of those that actually do require attention; most canbe solved in very short order using quick and easy edits. The numbered solution points provided in thisdocument will solve almost every one of your upgrade woes. These solutions will work when you areupgrading 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 existin the first place; why the current generation of Visual Basic has just a little bit of trouble upgrading from theprevious 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 itis the other way around – VB6 was a patchwork of hacks, fixes, and upgrades that did not seem to alwaysfollow uniform standards of syntax). But the best answer is that it is because VB.NET is now what those VB6programmers 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 expectingno less than 100% unrestricted object oriented programming language capabilities, most of them did nothave 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 amateurprogrammers and hobbyists, naively believed that after such a necessarily monumental undertaking, theycould simply continue on their merry little way, writing VB code exactly as they had done before, using thevery same often non-uniform syntax that they had been using before, and, oh yes, they expected there wouldbe a few additional commands here and there to address full class inheritance, and also allow seamless accessto methods whose source code was written in some other programming language, such as C++. They did notgrok the fact that in order to provide them with exactly what they were keenly expecting would also clearlynecessitate colossal changes to their beloved VB so that it would be a fully integrated, object orientedenvironment 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 ofthem 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 haveit easy compared to some language upgrades. Try translating between C (C89; K&R C) and C99, or C++85and C++95, or better, between upgrades of the very first high level language, FORTRAN (FormulaTranslation), 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, oreven IL (Intermediate Language – Microsoft’s brilliant cross-platform assembly language), their syntaxmust always sync up perfectly. In fact, code written in one language should be instantly translatable intoone 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 emulatethe 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 IfEnd Sub Page –4–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross GobenThis 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) cilmanaged{ .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.SendToBackend;Or into Google Chrome:method Form1.ZOrder(frm: Form; Position: Int32);begin if (Position = 0) then frm.BringToFront else frm.SendToBackend;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 waspreviously hidden away from VB6 developers had to be brought out into the open. The fact that it hadbeen 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 oneof the main reasons why many C++ programmers looked down their noses at VB6, chiding it as a tinkertoy language, because the total breadth, the total power, and the total unreserved system control that VBdevelopers could otherwise wholly exploit was withheld from their grasp, which in my view severelyhandicapped VB developmental capabilities, limiting them far beyond that which most might venture toimagine, 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 GobenHaving said that, am I a fan of VB6? Yes, I am. But will I continue to use it? I doubt it, except formaintenance purposes, to support my customer base who have invested heavily in VB6 software and eitherhave 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 toturn back to VB6. In fact, I have become so comfortable in VB.NET that I may now have trouble writingoriginal code in VB6 again, mainly because of the conflicting syntax between controls and commands thatVB6 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 intwo hours what would take two weeks to match it in robustness using C++. I stand firm that this declarationis now even more true of VB.NET (this explains why I have finally made a full migration from C++). By thetime VB2005 came out, VB.NET was clearly coming into its own as the new RAD platform of choice. Thefact that its free version, VB2005 Express, was a full VB.NET compiler, with its only limitations being that itdid 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 otherfeatures made available only to the full Visual Studio environment, such as the free Code Rush! Expresseditor enhancements (see www.devexpress.com/Products/Visual_Studio_Add-in/CodeRushX/). It alsofeatured much stronger compatibility to VB6 code than VB2003 offered. VB2008 and VB2010 exhibit fargreater compatibility to VB6, and they are in fact the only VB.NET platforms that I will recommend to VB6users who have yet to migrate to the .NET domain. Like VB2005, free VB2008 and VB2010 Express editionsare also available (see www.microsoft.com/express/download), plus Microsoft has also provided a number ofexcellent, previously pricey books regarding VB6 to VB.NET migration on hand for free download (see theend 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 warningsand upgrade issues a snap to deal with. Not only will these points help you address those issues, but theywill also hopefully give you a glimpse into the VB.NET approach to application development, and withany luck I will also be able to convey my excitement for this extremely potent and much moreempowering rendition of Visual Basic, which is now a true force to be reckoned with; it finally beingjust as capable, just as powerful, but can be developed much faster and can be executed just as fast as theother 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 Imade my own migration over to VB.NET from VB6. It started out as a 6-page hodge-podge of terse cuffnotes, but continued to grow as I learned new and better ways of adapting VB6 code to the .NETenvironment, first to VB2002 (a painful experience), then VB2003 (a little less painful), VB2005 (a ratherpleasant 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, adreadfully rocky path, fraught with aggravation and dead ends that often required major re-writing forsegments of code. When VB2005 arrived on the scene, I noticed that massive pages of personal upgradenotes were now no longer required, and so they were duly discarded. When VB2008 was released, far greateramounts of my upgrade notes were no longer relevant. Indeed, VB2008 is probably the very first edition ofVB.NET that I would even consider recommending to people who were thinking about making the transitionover from VB6. VB2005 and VB2008 were focused strongly on the language itself and upon VB6compatibility. 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 noteliminate 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 haveMarshaling 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 Goben1. 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 Goben4. 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 declaration 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 to us 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 Module6. More on StrPtr If your VB6 code used the StrPtr() function to pass the 32-bit address of the string, be aware that strings are no longer stored in the old BSTR format. Yet, like under VB6, if one passes a string ByVal to a function, you perform the function of StrPtr(). Hence, in most cases the StrPtr() function is not actually required under VB.NET, just as with 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 additional feature of ByVal was introduced in VB2005 in order to enhance compatibility to VB6 and to simplify conversion from it. Previous to this update, under VB.NET you had to pass a string ByRef and also include a special VBByRefStr attribute, which shall be thoroughly discussed in the next point. Page –8–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben7. 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 it needs to assign a string of text to it, then you should marshal it with a VBByRefStr prefix. For example, if the VB6 API parameter ByRef 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 under 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 (an alternative to ByRef processing) As an alternative to using a ByRef string, you can instead use a StringBuilder, an object introduced with 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 might seem to go against rules, but we must pass a StringBuilder ByVal to the P/Invoke, not ByRef, even though it requires the address of string data. The reason for this is that the Common Language Runtime (CLR) has 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 that, thus resulting in an exception error being generated. Using A ByVal String (the BEST solution to ByRef processing) A better solution than 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 VB6-style solution 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 happens 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, such as through an Alias or by the declared method name that .NET tracks in a library), it will set aside temporary 8-bit ANSI string space, transcribe .NET’s 16-bit managed string to it, pass the address of the ANSI string to the method, and finally translate the ANSI string back to the 16-bit managed string space. As a point of interest, this also happens to be how passing strings ByRef using the marshalling tag did it, 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 which can still receive back 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 (note the easy fixed-string-size substitute) 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 (which I do not care much for, considering the complicated parameters): <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 Function8. 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 to 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, yet 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 is to be located). It would have been better to qualify it 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 the space, even though it is not usually necessary, here are four very good solutions for doing that: I. The first, quickest, and best solution is to simply assign the structure to a variable using the New keyword, such as “Dim Struct1 As New MYSTRUCT”. Although a structure is not a Reference Class (it does not instantiate reference objects), it is an Abstract Class1, which acts like a scalar variable. However, just like its object-based cousin, it does feature an optional New() method. The New() method will force all scalar members (numeric fields) to be initialized to zero, and all others, such as strings, to Nothing. II. The next solution 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 a structure named MYSTRUCT (see its declaration in 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. Alternatively, 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 actually be accessed and updated. Otherwise, if passed ByVal, only a copy of the data will be passed, leaving the original data unchanged. Consider this subroutine: 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 Class. An Abstract Class can be viewed 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 items as scalars (value-type variables), and, of course, structures. Unlike the classesthat you are likely familiar with from VB6, an Abstract Class can only be assigned to one variable that can reference its actual data. Whenassigning an Abstract Class to another variable, it passes a copy of itself, giving the target variable a replica of the original (much likeusing the Clone() method, discussed much later in Point 41). This is juxtaposed to a Concrete Class, which is the standard class type inVB.NET that can have instances instantiated, and a single instance can be pointed to by multiple reference variables (copying a referencevariable to another reference variable would simply copy the referenced address in the absence of the Clone method (which would in turnsimply instantiate another referenced object)). 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 example: <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 entirely separate system memory locations, so there is no chance of this program code being passed right along with the structure’s data fields.As an aside, if you were to take a look at the length of the structure declared in solution IV, above,such as by using “Len(OSver)” (see Point 11 regarding VB6 and its LenB() method, which wouldhave 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 ofthe 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 tokeep in mind! It is because most Win32 P/Invokes use 8-bit ANSI characters instead of VB’s 16-bitUnicode characters. Even more, the documentation for the GetVersionEx P/Invoke I extracted thisexample 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: “TheVBFixedStringAttribute 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 stated256 (bytes) as the designated width of the VBFixedArray attribute, in order to fully accommodate128 16-bit Unicode characters. However, even so, do not be wracked with dread because we hadinitialized 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 propertywill tell the VB.NET interop marshal operator to automatically handle the Unicode/ANSIconversion for us, providing the required 128 8-bit ANSI characters needed during interop from thestructure by the P/Invoke.This material appeared complicated the first time I ran into it, but after using it for a while, it quicklybecame quite simple and normal. My own personal solution is to only apply special attributes to thebody of the structure when I will be performing interop processes with it, which is to say that I willbe invoking methods declared in unmanaged or non-.NET invocations. Also, I prepend any structurestrings that must be of a fixed size with “<VBFixedString(xxx)>”, specifying the number of bytes thestring will occupy, or doubling it if I am dealing with Unicode, and I typically declare structurevariables 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 ofadding 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 Goben9. 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 easily set to its mandatory startup dimensions after creation. 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 (further 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 still 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. Assuming we had wisely 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 alternatively resize our dynamic array manually 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 dumb 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 will 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”. Some upgrade… Page –14–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben11. 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 clarifications 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 bit of bother: 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 Goben12. 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 Goben13. 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 GobenWhat we are going to discover is that the displayed prompt will consist of just its first character(showing “S”, when it should be “Select Source Path”, for example). Why? Because most developersautomatically placed an Auto verb in the declaration of the CopyMemory() P/Invoke, remembering onlythat we will be processing a string (Private Declare Auto Sub CopyMemory Lib "kernel32" Alias"RtlMoveMemory"), just as all the online examples I have seen do. To clarify, refer to Point 4 concerningmy warning against including both the Auto and Alias verbs in a P/Invoke declaration. Further, becauseVB.NET has special knowledge of RtlMoveMemory(), it will enforce Unicode to ANSI text translation ifany string is used in this method. What results is our Unicode string remains intact, and so, in binaryterms, the string begins with the Unicode character “S” (x’0083’), which is actually stored internally inmemory 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 reversedfrom how we typically imagine them as right to left – but this is all hidden from you), and because text isprocessed 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 astring type, then the method would have worked OK, 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 theCopyMemory() declaration, those developers apparently did not figure that out. Even so, we can stillexplore 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 Bytearray instead of a Unicode string. I will also assume that the declared CopyMemory() P/Invoke willnot be used elsewhere in the current module, so I will simply redefine it to a version that will accepta Byte array (I could have easily just added an overloaded method). Since the P/Invoke now does notcontain strings, we will remove the Auto verb (but you watching at home know that we have justfixed the actual bug in the program, because apparently those developers were not yet aware of therule 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 aByte 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 WithNOTE: By the way, on NEW projects that involve folder and file selection dialogs, you should take the E-Z route andsimply employ the FolderBrowserDialog or OpenFileDialog controls available in the VB.NET IDE Toolbox. With theseyou 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 FunctionEnd Module Page –18–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben14. 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 or signature) 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 GobenIn another case, in instances where you hook and unhook window/form subclasses, intercepting theWindows Message Queue, this requires using the SetWindowLong P/Invoke. You may want tosimplify the delegate handling process by defining multiple versions of this P/Invoke. Consider thefollowing code clipping, which must invoke SetWindowsLong in two different ways; one using aDelegate (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 oneEnd 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 IfEnd 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, toprototype our subclass method, szWndProc, some creative people have attempted to dance the Delegatename around, which resolves to an IntPtr/Integer, and declared PrvhWnd in HookWin, the PrvHwndparameter for UnhookWin, and so on, to this Delegate. We might try fraught things like this as we blinkexcessive sweat from our eyes while struggling desperately to stop VB.NET from incessantly barking atus after each compile attempt with it badgering us about improper data typing; this stemming from ourmyopic 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 oftrying to adjust variable typing to suit our P/Invoke, why not think broadly and simply massage ourP/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 ofobject oriented programming at hand, we should realize that all we really need to do is take advantage ofVB.NET’s capacity to support overloaded methods. With that in our bag of tricks, all we must do tocorrect our problem is 1) create the needed Delegate for szWndProc, and then 2) define another, butoverloaded, SetWindowLong method to support the Delegate. This way we will have one version ofSetWindowLong with an Integer type to handle parameter dwNewLong, and another version ofSetWindowLong that uses our Delegate as the type for dwNewLong. With just that, suddenly thisotherwise potentially very complicated problem (well, it is if we think too narrowly) is completelysolved, 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/IntPtrPublic 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 Goben15. 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 been assumed. 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 Goben17. 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 to 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 an unavoidable 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 Goben19. 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 while the form is initializing (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 might 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 Goben22. 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:MyCursorsEW_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:MyCursorsEW_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 Add Add 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 Add Add 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, right-click the cursor item in the Solution Explorer list, and then in its Properties window, ensure that the Build Action property is set to “Embedded Resource”. Next, to use your embedded cursor within your application, assuming that the cursor file is named Dilbert.cur (and note that this name is 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 Project Properties Resources, 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 stream25. 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 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”. Page –25–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben26. 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 Sub27. 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. Page –26–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben28. 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 as guilty of this as anyone). 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 the 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 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 Page –27–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben30. 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, even though it is probably the absolute best solution of novice developers. 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 notcheck 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 Goben32. 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 (long form definition) Result for January 22, 2010 "d" M/d/yyyy (Short Date Pattern) 1/22/2010 "D" dddd, MMMM dd, yyyy (Long Date Pattern) Friday, January 22, 2010 "t" h:mm tt (Short Time Pattern) 2:22 PM "T" h:mm:ss tt (Long Time Pattern) 2:22:48 PM "f" dddd, MMMM dd, yyyy h:mm tt (Full Date Short Time Pattern) Friday, January 22, 2010 9:53 AM "F" dddd, MMMM dd, yyyy h:mm:ss tt (Full Date Time Pattern) Friday, January 22, 2010 9:53:21 AM "g" (General Date Short Time Pattern) 1/22/2010 9:53 AM "G" (General Date Long Time Pattern) (default) 1/22/2010 9:53:21 AM "M" MMMM dd (Month Day Pattern) January 22 "R" ddd, dd MMM yyyy HH:mm:ss GMT (RFC 1123 Pattern) Fri, 22 Jan 2010 09:53:21 GMT "s" yyyy-MM-ddTHH:mm:ss (Sortable Date Time Pattern) 2010-01-22T09:53:21 "u" yyyy-MM-dd HH:mm:ssZ (Universal sortable) (invariant) 2010-01-22 09:53:21Z "U" Universal sortable Friday, January 22, 2010 2:53:21 PM "Y" MMMM, yyyy (Year Month Pattern) 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 “Month: M” (Customized format) Month: 1 “HH:mm:ss.ffffzzz” (Customized format) 09:53:21.0000-05:0033. 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 Goben34. 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 Goben36. 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 GobenThis subroutine is simply the previous program flowchart translated to old DOS BASIC as much aspossible. Back then, you could specify variables at a whim, never having to DIM them or definetheir type (the origin of Variants in VB), as we have emulated with Option Infer On. Linesbeginning with “L” create labels for what would have been line numbers. Now you might appreciatewhy flowcharts were so popular back then, because the actual program code is convoluted and hardto follow without doing what I did back then, which was to take a pencil and draw dividing lines, aswell 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 goin 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 structuredtranslation long ago in FORTRAN, then in C++, then in QuickBASIC (predecessor to VB1), then inVB6, and finally in VB.NET. Following is a segment of a string sort, comparing the original withthe 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 sortDo While CBool(HalfDown 2) while counter can be halved A: IF(M2)=0 THEN STOP HalfDown = HalfDown 2 back down by 1/2 (M=M2) 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 LoopLoopFor 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 FunctionEnd ModuleNOTE: Most VB.NET lists and arrays already have a built-in sort method that employs the QuickSort algorythm thatyou can invoke by selecting Array.Sort(strArray), for example. The advantage here is that a sort method is alreadypresent and easy to access. However, the above Shell-Metzner sort is much faster than QuickSort, significantly so inlarge 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 Goben37. 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” under 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 runs as a separate thread that can execute while your application is still 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 Goben40. 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 original VB6 code, shown below: With frmSpellCheck.CommonDialog1 .Flags = cdlOFNFileMustExist Or cdlOFNPathMustExist Or cdlOFNLongNames Or cdlOFNExplorer .DefaultExt = "txt" default extension if the user does not supply one .FileName = vbNullString .Filter = "Text File (*.txt)|*.txt" we will be looking for files with a .txt extension .Title = "Open an Existing Text File" add a header message to the dialog form .CancelError = True generate an error if the user hits CANCEL in the dialog On Error Resume Next Ignore errors for now (we will check for them) .ShowOpen display the Open dialog box If CBool(Err.Number) Then Exit Sub Assume user hit cancel On Error GoTo 0 else clear error trapping TxtFile = Trim(.FileName) grab filename If Len(TxtFile) = 0 Then Exit Sub exit if no filepath was supplied End With After an upgrade to VB.NET, our code block looks something like this presumed 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 (the upgrade wizard’s knowledge is usually confined the singular line it is currently working on, never taking an ‘overall’ view of the code). 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 instead, 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 in sequential order on an Intellisense dropdown list. Page –36–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross GobenThe next thing you should be aware of is that the Flags property has been replaced by individualBoolean properties; and each enumerator you had applied to it are now set on separate lines; theactual 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 setto the now-non-existing Flag property, plus an error warning is issued for each one. If a property isstill 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 anew name and a new behavior. The new names are obvious, but the only new behavior is that theyare now individual Boolean properties and they are set to True or False instead of having anenumerated 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 provide it is clearly the same as we used before in the VB6 code, so Isimply ignore this warning and have yet to have trouble with it. By the way, in case you are new todialog filters, we add filters in pairs, where the first half is purely descriptive, having nothing to dowith actual filtering, such as “Text File (*.txt)”, and the second part is the actual filter pattern, suchas “*.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 adropdown list, by appending more Description | Filer pairs, separating them from the others with apipe character as well. You can even combine multiple filters into a single choice, such as “ImageFiles (BMP, JPG, PNG)|*.bmp;*.jpg;*.png”. We separate the patterns in the filter pattern portion with asemicolon (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 ErrorResume Next, because when CancelError was set to True, if the user selected Cancel, a CancelException Error was thrown. The On Error Resume Next prevented the program from aborting onthis error, and then all we had to do is check if Err.Number was non-zero (under VB6, a non-zerovalue would be automatically cast to a Boolean, eliminating the need for Cbool(Err.Number), butthat is actually a very bad habit to get into, and VB.NET especially does not like it with OptionStrict turned on). We clear the error trap with On Error Goto 0. Although this type of error trappingis still accepted, one really should get into using VB.NET’s much superior Try…Catch…Finallyformat (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 unsupportedCancelError 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 SubEnd SelectBut 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 0With 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 With41. 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 actually passing a freshly constructed array anyway, as we do in our example, we can simply remove the VB6.CopyArray() function from our upgraded code, but keep 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. Also, be aware that the Clone method greatly simplifies what is required in C++. Be aware that all this is actually done in VB6, but hidden from us “behind the curtain”. It had to be brought more out into the open under VB/NET for reasons of cross-language operability, but the process is still simple enough to not remove VB.NET’s 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 and 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 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). Page –39–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 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 would rather have 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 example43. 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 simply did not see it happen; it was all done behind the scenes. Which way 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 you can change multiple characteristics simultaneously, and if you later need to transition the code to C# or C++, the transition is much smoother because this process is exactly what you will 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. Page –40–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross GobenWhen we upgrade a VB6 program to VB.NET, the VB6 Compatibility Library takes care of theseprocesses for you. But you may actually be wondering how would you do it in a new program? It issurprisingly simple. All you have to really do is create a new font that can be based on the old oneyou are replacing, and then apply a new state to it. For example, suppose Label1 has a size of 10points, 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)What we did was simply create a new font using the old font’s name, style, character set, as well asset it to using Points (default), and with a new size (12). Of course, you may not want to change it ifit is already set to the desired size. We can accommodate this with a single function (be sure to alsoimport 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 FunctionTo 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 alreadyset 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 otheroverloaded 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 styleOr add more than one style simultaneously, like this:Me.Label1.Font = New Font(Me.Label1.Font, FontStyle.Bold Or FontStyle.Italic) apply bold + italicThis works great, but toggling is a bit of a problem, especially if we want to turn on or off a singlestyle option, but leaving others alone. The best approach is to test if a change is needed, and then if itis, 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/resetEnd FunctionIn 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), butbold is already set (flag = True), then there is nothing to do and the old font is returned. If the state ofSetFlag does not equal flag, then we have something to do. We first define a local variable that willcontain 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 theflag 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 bothBold and Italic styles to the label. We can do this by using this command “Me.Label1.Font = Page –41–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross GobenFontChangeStyle(Me.Label1.Font, FontStyle.Bold Or FontStyle.Italic, True)”. To toggle a selected styleoff, 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 thefont 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 thatwill 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 FunctionWith 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 sizeand the style. We could write, as I have, a function to do all this, but in the end, probably the easiestmethod is to simply define a new font on the spot, just as had been demonstrated at the beginning of thispoint. For example, suppose I want to change the font of Label1 to Courier New, the point size to 12, andthe 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 completemodule, which is based on the font support provided by the VB6 Compatibility Library:Imports System.DrawingModule 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/resetEnd 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 Module44. 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 system-level PerformLayout method is internally 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. Under VB6, 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 GobenThe above VB6 code would be written in VB.NET like this:Dim Form2 As New Form2 declare new instance of prototype formForm2.ShowDialog(Me) show new form, make current form its parentForm2.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 displayedThe Close() method is like VB6’s Unload. Although you probably could get by without invoking theDispose() method, using only Close(), but if you know anything about any class that features aDispose() method, then you would know that it is a very good idea to invoke it, because it causes allof its resource to be removed now, not later, when the randomly running garbage collectorencounters it and figures out that it is not being used anymore. If you look at the documentation forthe Close() method (search Help for Form.Close), it states “When a form is closed, all resources createdwithin the object are closed and the form is disposed. You can prevent the closing of a form at run time by handling theClosing event and setting the Cancel property of the CancelEventArgs passed as a parameter to your event handler. Ifthe form you are closing is the startup form of your application, your application ends. The two conditions when a formis not disposed on Close is when (1) it is part of a multiple-document interface (MDI) application, and the form is notvisible; and (2) you have displayed the form using ShowDialog. In these cases, you will need to call Dispose manually tomark all of the forms controls for garbage collection.” Elsewhere, it states the point more directly: “Disposewill 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() fromwithin the FormClosed event if you wish to (good idea). Further, you can invoke Dispose() after Close() even if it wasnot 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 SubEnd ModuleTo use it, we provide the method with our form, the optional modal setting (default is Modeless), andthe optional parent window, in the form: “ShowForm(frm, FormShowConstants.Modal, Me)”.The weird thing about the Load command under VB6 was that it was not really necessary, unlessyou wanted to load a form without displaying it. This way you could load the form, initialize somecontrol fields, and then display the form once it is set up. The documentation indicated that the Loadcommand simply instantiated the form. So basically the less obvious VB6 “Load Form2” command isbeing 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 legalunder VB.NET, sad to say. Everything must have an order to it. VB.NET should be more stringentin this situation. I just never saw the logic in the ILRAP/TRLAP sequences being able to beprocessed out of their defined orders. It makes sense to me in the new VB.NET TRLAP sequencethat 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 forms 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 AndAlso CriticalProcessing = True Then e.Cancel = True End Sub45. 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 workaround 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 ovalsat design time, eliminating the need to draw shapes manually in the form’s Paint event. This pack also includes a printercontrol and collection, emulated printer I/O from VB6, a PrintForm component to allow you to print forms as you did inVB6, 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 Goben46. 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. NOTE: If your Long, Integer, or Short values are Unsigned, then precede the character tag with a U, such as 123US for Unsigned Short, 123UI for Unsigned Integer, or 123UL for Unsigned Long. Further, you can also cast values to their unsigned version using CUShort(), CUInt (), and CULong(). Also notice that a Byte is an unsigned values from 0-255. You can cast a Signed Byte (SByte), storing values from -127 to +128 by using the CSByte() casting function. Sadly, there is not a Literal Type Character for Byte or Signed Byte, but this is normally not an issue of great concern. Page –47–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben47. 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 screwed up a little 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 format) ****************************************************************************************** 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 format) ****************************************************************************************** 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 trial attempt, go to each control and edit its Tag property to match its named index value (I will later introduce a means to totally skip 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 GobenAlternatively, as of VB2008, you can use a strongly typed Generic List. Though I recommend using thearray shown above, a Generic List might be handy when using a list of CheckBoxes. Besides, you can doa For…Each search through either implementation, when you might want to scan for those that arechecked, 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) PictureBoxIn the Form_Load event, stuff the array with the controls (this sort of thing is usually done behindthe scenes in the automatically generated code – to see such code yourself, be sure the “Show AllFiles” 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, thenediting their index value. This is how you would add them to the Image1() array of PictureBoxes:Image1(0) = _Image1_0 copy and paste, then edit sure makes this process a whole lot easierImage1(1) = _Image1_1Image1(2) = _Image1_2 ...Image1(17) = _Image1_17Or, 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 workwith its result, Generic Collection objects are strongly typed and return the object type stored, just likeusing a simple array. Scanning a collection or array of type RadioButton might 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 loopNextMsgBox("You Selected " & RadioButtons(Index).Text)NOTE: All things considered, you may even want to instead consider placing the controls on a Panel, which is aborderless container class (hence runtime-invisible, unless you change its background color). You can loop through itwith a For…Each, just like an array or a Generic List, but without having to programmically stuff a container at all.After that, we are often told to go to our Image1_MouseMove event (and other events, such asImage1_Click, that the images trigger), and after the command, add “Handles _Image1_0.MouseMove,_Image1_1. MouseMove, _Image1_2. MouseMove, etc.” Were we to do so, afterward, this command linemight look like the following cluttered-up mess: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.MouseMoveIf 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 we filled the array/list using the commandAddHandler (a runtime alternative to the Handles clause) to add the MouseMove, Click, or whateverevent 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 (presently, it should not have any,anyway), it would look something like this:Private Sub Image1_MouseMove(ByVal sender As System.Object, ByVal eventArgs As System.EventArgs)We can then programmically assign event handlers to the event Image1_MouseMove. In thefollowing list, notice that we are taking each image defined, and notice further that by applying theAddHandler command to them, the controls expose event properties (technically Delegates), and weapply 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 GobenAddHandler _Image1_0.MouseMove, AddressOf Image1_MouseMove Add the MouseMove event of our controlsAddHandler _Image1_1.MouseMove, AddressOf Image1_MouseMove to the Image1_Mousemove list ofAddHandler _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 in most apps)Finally, at the beginning of the event code, if you upgraded, you will notice some now-dead (actuallynow error-marked) code that was used to get an Index of type Short (though you can use Integer if youwish) from the control (it was expressed as “Dim Index As Short = Image1.GetIndex(sender)”). I simplyreplace this error line with the following 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 instead employ a function to do allthe 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 errorEnd FunctionWith 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 Indexbased on the naming of the control (I use the upgrade’s underscore and index naming method as auseful personal standard in my own VB.NET projects). Consider this function, which will alsothankfully 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 Instrs 1-based index) End If Return -1 return a flag to indicate Unsupported Type Encountered End FunctionEnd ModuleAnd that is all there is to it. This simple technique will solve most old or new control array issueswithout a ton of extra overhead, and the most tedious part of it being the populating of Handlesstatements. 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 Goben49. 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 its 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 its 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 need or use most of 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 would 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 click51. 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 presently have VB6 installed, or just the free VB6 SP6 Redistribution Pack (see below), this is very easy to do (note that your application end-users will never need to go through this process at). 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 GobenVB6, you will have to hit the “Allow” option in a User Account Control dialog, but this is a small pricefor unfettered access to the VB6 IDE, especially if you must support source code that compiles under it.Some systems, such as those mentioned above, will absolutely prevent you from installing Service Pack 6 forVisual Basic 6.0 (http://www.microsoft.com/downloads/details.aspx?familyid=9EF9BF70-DFE1-42A1-A4C8-39718C7E381D&displaylang=en) at all due to incompatibility issues. Instead, even if you do notinstall VB6, install the 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). ). You are allowed to do this even if you no longer own VB6. You shouldalso be sure to install the Microsoft Visual Basic 6.0 Service Pack 6 Cumulative Update(http://www.microsoft.com/download/en/details.aspx?amp;displaylang=en&id=7030). This installation maystill inform you that it might not have installed correctly, even though it actually did (this alert is in fact due toa 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 -R), then enter msconfig, select the Boot tab, and select SafeBoot). You will have to undo this in Safe Mode to reboot normally. However, it will still report an error, butthis time it will clearly inform you that it could not register OLEAUT32.DLL (it is because of heightenedsystem protections that have been added that prevent this DLL file from being tampered with, and especiallyby 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 COMsupport library that provides this VB6 control is now loaded onto your computer and registered. Butonce it is loaded into your system, you are now able to easily add an animation control to yourVB.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 justas you had done with VB6. Notice further that when you place an animation control on a form, theproperties 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 addedto your project properties, both for “Microsoft Windows Common Controls-2 6.0 (SP6)”; one of themfor Interop.MSComCtl2.dll, and another for AxInterop.MSComCtl2.dll, which are required to support theanimation control. Notice further that their “Copy Local” properties are both set to True, meaning thatthese will be, and must be loaded into your program directory, being non-registration versions of theActiveX 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.NETupgrade did it, and then figured from that code-only solution that there had to be a way to add such acontrol to my toolbox, rather than instantiate everything programmically, which the upgrade codedid. It may be simple enough to do it programmically, but it is not something that is going to besitting at the top of my head every time I need to use it. On the other hand, a toolbox control is easyto find and even easier to apply to a form than it is to go about writing a bunch of instantiation andform-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 toVB.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 hesitated to add it to .NET. Page –53–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben52. 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 GobenThis 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 ofoptions 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 faraway 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.). FromMy.Resources, we have full access to all of our embedded resources. FromMy.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 astring name to get access to an audio resource, so they have resorted to using a Select block to checka text name against a list, then play the associated embedded WAV resource. Well, I can understandthat one. The first day I started playing with VB.NET resources, I thought the same. But being asoftware engineer, I knew that it could not be implemented so poorly. With all that raw power at myfingertips, Microsoft was going to just settle on some weak solution like that? So I took a look at theresource manager and between experiments (and stopping to actually read documentation), I quicklyfigured it all out. By simply paying attention to the popup tooltips, I learned that by providing theresource manager’s GetObject method with the text name of the desired resource, it would return anobject that I could cast into the appropriate type (byte array, stream, or string), which comprises myaudio data (byte array if from VB6, or stream if VB.NET-added). I could then take advantage of thePlay 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 successEnd 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 FalseEnd 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 GobenNow this is definitely easy. True, I was initially disappointed that the PlaySound() P/Invoke no longerworked with .NET resource files, but what I got in trade has rendered the PlaySound() P/Invoke useless.AVI ResourcesAVI files are a source of frustration with VB6 users and VB.NET users alike. In VB6, it was a bit of abother. 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 application.Why many VB6 users claim that this resource-adding hack is easier than adding resources underVB.NET can only be due to insanity. To add such a resource under VB.NET, all I need to do is go toProject / 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 theproject 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 ofwoe about how someone cannot figure out how to play it, or how they cannot convert the resource to astring 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 playedfrom 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)”, whereACM_OPEN is declared as WM_USER (&H400) + 100&). I was hoping that the new .NET architecturewould 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 theFileCopy.avi file loaded in our resources. When it is added, it will automatically be saved in theresources by its file-defined name, such as FileCopy (you can alter this by renaming the resource). Ilike 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’ssystem 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 aByte Array (C uses square braces to denote arrays). As such, instead of trying to convert the resource to astring, I will treat it as a byte array and use a FileStream object so that I can write a binary file. WithVB6, all we had to do was issue the command “LoadResource 101, AviFile”, where AviFile is a stringspecifying the file path 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 fileDim 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 resourcesEnd IfThe 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 itEnd 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 apps 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 Class54. 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 passed 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 Goben58. 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 control’s 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. Under 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 Goben60. 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 buttons 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 buttons 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 GobenNOTE: 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 Keyvalues were seldom, if ever identical. This can be a potential problem because it will not provide an upgrade noteinforming you of this. So be aware that this will happen. I like to take the time to assign the upgraded button’s Tagproperty to reflect the old VB6 Key property, or, as I have done in the above snippet, simply update the test for thebutton name. Be aware that you could have also originally tested against the button’s Name property under VB6, inwhich case the code would have upgraded more smoothly.However, when developing the code under VB.NET, you can alternatively collectively process theseclicks through the ToolStrip itself in a single subroutine, and from its click event you can extract thebutton 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 Buttons 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 Buttons definition index (from zero): Select Case ToolStrip1.Items.IndexOf(e.ClickedItem) + 1 optionally add 1 (compensate 0 option base if VB6 code was 1-based) Case 4 if Exit Button, then simply unload the form (4th of 4 options) Me.Close() Close the form Me.Dispose() also do this in case the form was opened as a Dialog or is an MdiChild ... End SelectEnd SubButton Menus (VB6 ToolBar Button style 5; tbrDropdown) are menus that drop down from toolbarbuttons and offer a list of items to select from (you will notice a dropdown indicator on the right sideof 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’sIndex property, regardless of whether you predefined the dropdown button list or dynamically addedthem 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 buttonEnd SubBut when you process a pre-defined dropdown button in VB.NET from a ToolStrip (styleToolStripDropDownButton), event handling takes two different approaches. First, it can provide anindividual 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 SubOf course, the above is only practical when you pre-define dropdown button values in VB.NET. Ifyou 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 options to ToolStrip Drop-Down Button tsddbOptions .Add("Option 1") .Add("Option 2") .Add("Option 3") .Add("Option 4")End WithYou could handle selections to those dynamically added buttons, as well as through any that hadalready been predefined at development time through the dropdown button’s DropDownItemClickedevent, 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 Buttons 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 Buttons 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 of 4 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 SelectEnd 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 that 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 Goben63. 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 Goben64. 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 GobenBut now that these events have parameters, many are wondering how to invoke them underVB.NET? One can squeeze by on invoking a button click by instead invoking the button’sPerformClick() method. But we would still be stuck with the GotFocus()event (now Enter()) fortxtCommand. 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 secondargument 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 sneakby cleanly by simply passing the EnventArgs parameter from the event we are invoking it from, ifwe are indeed invoking it from within an event.Following is the VB.NET upgrade of the above code:Option Strict OffOption Explicit OnFriend 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 SubEnd ClassAnd sure enough, the upgrade performs the conversions of these invocations just as I described. Allthat is left here is to tighten up the code. For example, event txtCommand_KeyPress, although it worksperfectly, 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 Goben65. 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.66. 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.67. 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 Goben68. 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.69. 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.70. 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 Goben71. 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.72. 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).73. 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.74. 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:MyHipBagIconsMyOpenIcon.ico").ToBitmap Page –69–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben75. 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()76. 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 Goben77. 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 finished performing a long task, you would issue the following command: Me.Cursor = Cursors.Default78. 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 GobenUnder VB.NET, the rules have changed a bit, but I think it is for the better. Consider the followingexample 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 SelectEnd SubNotice that there are 3 key differences noted. First, notice that we obtained the KeyValue (theVB.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 settinge.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’sBoolean SuppressKeyPress property to True. If we had tried to change e.KeyValue to 0, as we haddone 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 wayVB6 did it. For example, the “e” parameter is now a KeyPressEventArg, which provides not anInteger KeyAscii code as VB6 did, but rather a KeyChar parameter that is of type Char. Also, tocancel further processing from the KeyPress event and to prevent an invalid code from being sent tothe 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 havehandled 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 SelectEnd Sub Page –72–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben79. 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. On 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 GobenAs you can see, the Data object contains a 1-based Collection object that holds a list of one or morefile paths, which we can easily loop through to pick up all of its entries. This, and single items thatyou could grab using the Drag Data object’s GetData() method were all the capability VB6 Dragand Drop had. Hmm. Not very impressive.However, VB.NET comes along and it seems like a monkey wrench was thrown into the works (amonkey wrench, or spanner in Britain, is a large adjustable wrench with a long fulcrum arm that waspopular in the nineteenth century, though now typically used in only really tough, tightsituations, or plumbing, where you need to monkey around with a stuck pipe or large nut – italso made a good, but sloppy hammer).When I upgraded my application, I discovered that the Drag and Drop code did not likewiseupgrade. So I tried to take a look at what I needed to do. I dutifully enabled the EnableDropproperty 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. Idid notice that my “e” parameter was of type DragEventArgs, and that it had a Data object, but thisData object did not have a Files collection. So I took a look at the documentation to see what I couldglean from that. What I gathered was that I needed to use the Data object’s GetData() method toacquire data (duh). All I had to do was provide GetData with a Format string. All the examples I sawwere for basic types, like “String” and such. But I was not getting anywhere with that, so I thought Iwould use the GetFormats() method, which returned a string array, listing all the acceptableformats, and I put them into a message box. But I was still not getting any results. Plus, the mousecursor 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 droppedonto my target object, and to set the proper copy effect for that object, which was accomplishedthrough the object’s DragEnter()event. Though I was so far getting nowhere on the format, I reallyliked this new DragEnter()event idea a whole lot better than the really limited support we had inVB6. So I cheated, for the time being, by simply setting my Effects property to Copy, just so I couldfinally 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.CopyEnd SubWith that, my message box in my DragDrop event popped up with the list of formatsavailable. Well, the last three formats looked promising, especially FileDrop andFileName. I recognized FileDrop from working with Clipboard I/O, using theDataFormats class, which featured a number of string members specifying,surprisingly enough, data formats. FileDrop was used in clipboard I/O to handlecopying a number of files. As such, I assumed (rightly so, as it turned out) that I coulduse Filename or FileDrop interchangeably, though I prefer FileDrop simply because Ican 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 IfEnd SubThis worked for file dragging, but then I ran into only a minor snag in my DragDrop event. But thisended up only helping me to better understand the special handling that Drag and Drop still affordsfiles. 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 GobenMsgBox(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 afilename, 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 furtherexperiment, I changed my test code to this:Dim DataFiles() As String = e.Data.GetData(DataFormats.FileDrop, True) grab file listDim Nms As String = vbNullString init list accumulatorFor idx As Integer = 0 To UBound(DataFiles) loop through file list Nms &= CStr(idx) & ": " & DataFiles(idx) & vbCrLf & vbCrLf append each fileNextMsgBox(Nms) display resultAnd that did the trick.In reviewing all this, I have come to the conclusion that it would have been possible for the UpgradeWizard to have upgraded the code. It would have had to add a DragEnter event, but it should havesimply set “e.Effect = DragDropEffects.All”, but added an UPGRADE TODO notice that thedeveloper should specify the formats that they want to allow, and the type of drop events they wantto 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 ofCollection 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 IdxEnd SubCan 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)) NextEnd SubNOTE: If you have set Option Strict On, as I do, then you must also cast the data returned by GetData(), because itreturns 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 specifywhat kinds of files your target control will allow, which is now handled during dragging, rather thangenerically by the enablement of a control parameter, which had invited the undesired possibility ofinvalidly 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 IfEnd SubNOTE: Just a reminder, I use the optional parameter “True” to get the GetData() and GetDataPresent() methodsto allow compatible formats 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 useand easy to upgrade, and offers me much tighter control over what can or cannot be dropped on acontrol that accepts dropped items. Page –75–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben80. 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. You may also want to install the Microsoft Visual Basic 6.0 Service Pack 6 Cumulative Update, available at http://www.microsoft.com/download/en/details.aspx?amp;displaylang=en&id=7030. 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.81. Dealing With Displaying a Checkbox as a ButtonBe aware that under VB6, you could display a Checkbox as a button by setting its Style property fromStandard to Graphical. This was useful for buttons you wanted to display as toggling up or down oneach click, and you could easily check its pushed state through its Value property. VB.NET also allowsthis, but by setting its Appearance property from Standard to Button. You check for it being toggleddown by checking its Checked property for being True. Page –76–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben82. Closing RemarksThis concludes the assistance guide, “Navigating Your Way through Visual Basic 6.0 to Visual Basic .NETApplication Upgrades”. Anything not covered by this document can usually be resolved with similar ease. Alwaysremember that each warning inserted by the Upgrade Wizard also includes a link to the MSDN help, which offersyou further assistance on the problem it either finds or, most often, simply cautions you to the per chance of aproblem. 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, “UpgradingVisual Basic 6.0 to Visual Basic .NET and Visual Basic 2005” from Microsoft Developer Network (MSDN) athttp://msdn.microsoft.com/en-us/library/aa480541.aspx. Pay particular attention to Chapter 7 through 9. You canalso download the “Visual Basic 6.0 Upgrade Assessment Tool” from here, along with before and after samplefiles. 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 VisualBasic Upgrade Wizard and the Visual Basic Upgrade Wizard Companion. This guide provides valuableinformation for organizations who are considering upgrading their VB6-based applications and components toVB.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 VisualBasic .NET” at http://msdn.microsoft.com/en-us/vbrun/ms788236.aspx. It is the complete technical guide toupgrading Visual Basic 6 applications to Visual Basic .NET, covering all upgrade topics from APIs to ZOrders. Itshows how to fix upgrade issues with forms, language, data access, and COM+ Services, and how to upgradeapplications with XML Web services, ADO.NET, and .NET remoting. It also provides big-picture architecturaladvice, 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 forDevelopers” (http://msdn.microsoft.com/en-us/vbasic/ms788235). Even though it is designed around VB2005, itis worth its weight in gold. VB2005 is a giant step forward from earlier editions of VB.NET, and is in my viewalmost VB2008, which is the starting platform you need to get really serious in VB development. If you currentlywork with Visual Basic 6, these authors fully understand the adoption and code migration issues youll encounter.Theyll step you through a quick primer on .NET Framework programming, offering guidance for a productivetransition. If you already work with .NET, youll jump directly into whats new, learning how to extend yourexisting skills. From the innovations in rapid application development, debugging, and deployment, to new dataaccess, desktop, and Web programming capabilities, you get the insights and code walkthroughs you need to beproductive right away.Though some of the information provided in the above free Microsoft books are clearly dated, sometimes offeringsolutions using older, more convoluted command paths than those offered by VB2008 or VB2010 (often thanks tothe new “My” namespace), those older solutions are still valid (I have tried to address those particular solutionsin 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 isa massive knowledgebase of .NET P/Invoke information, providing examples for VB.NET and C#. This is aninteractive, user-supported site. If you have a P/Invoke that is not yet documented, or is not presented for yourdevelopment 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 aresold here, it also features a wealth of freely available information (www.vbmigration.com).David Ross GobenLast update: Sunday, July 22, 2012, 12:52:27 PMContact: david.ross.goben@gmail.comPlease 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 GobenAbout the AuthorDavid Ross Goben has been a professional software engineer, a writer, and an obsessiveresearcher. Of Jewish descent, he has extensively explored Biblical history, ancientcultural thinking, and ancient slang for over three decades, which has resulted in hisseminal work: A Gnostic Cycle: Exploring the Origin of Christianity. He has writtennumerous books, manuals, and magazine articles, many uncredited, or authored underpen names. He has been involved in computing since long before personal computerswere available even in kit form, he was a contributing Editor to 80 Micro Magazine, andthere, co-wrote the Feedback Loop column with Beve Woodbury, under the pen name, Mercedes Silver. Hisinterests include Cosmology, Quantum Physics, human-machine interaction, the Global Warming Myth, TheElectric Universe Theory, Perpetual Energy Technology, Quartz Technology, Dream Walking, and the study ofthe bio-mechanical origins of life.He is currently exploring the possibility that Albert Einstein got his Theory of Special Relativity wrong. Just abouteveryone 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 infinitevolume 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 theentire universe (and even that may an infinite understatement). All you have to do is simply ponder it for amoment.However, reverse the order, where E = T / MC2, and all calculations yield the same results, save one, and that isthat instead of attaining infinite volume and infinite mass at the speed of light, one instead moves into an alternateuniverse. What most people do not realize is that the very atoms of our body, at this very moment, are alreadyvibrating at just under the speed of light. Just a tiny push and we would become multidimensional beings. I thinkthis 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 mosteveryone today refers to as the Afterlife or Heaven. Quantum Physics has already demonstrated that nothing in theuniverse has ever existed without Consciousness. So, where was Consciousness when the Universe was createdout of literally nothing during a causal Singularity Event? Evidence shows that it existed. And that has been thecrux of great debates throughout history.There is an easy experiment that can prove that Einstein’s Theory of Special Relativity is inverted. Weigh a veryheavy object, and then drop it onto a solid base, such as a concrete floor. Now weight the object again. It willweigh less… for about 20 minutes; at which time its full weight will finally return. Where did the missing mass goto during those 20 minutes? If Einstein’s calculation had been correct, the object would have actually weighedmore after being dropped, not less. Page –78–
    • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross GobenThe 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–