Navigating Your Way Through Visual Basic 6.0 to Visual Basic.NET Application Upgrades
Upcoming SlideShare
Loading in...5
×
 

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

on

  • 9,405 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
9,405
Views on SlideShare
9,401
Embed Views
4

Actions

Likes
1
Downloads
294
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-2014 by David Ross Goben All rights reserved. (Last updated August 30, 2014) (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 Goben TABLE OF CONTENTS Introduction.................................................................................................................................................. 4 1. Dealing with structures passed to P/Invokes containing strings................................................................................. 7 2. Dealing with structures passed to P/Invokes not containing strings........................................................................... 7 3. Dealing with the loss of the VB6 BackStyle property................................................................................................. 7 4. Dealing with passing strings directly to P/Invokes..................................................................................................... 8 5. Dealing with VarPtr, ObjPtr, StrPtr, VarPtrArray, and StrPtrArray.............................................................................. 8 6. More on StrPtr ........................................................................................................................................................ 8 7. Dealing with passing strings ByRef to P/Invokes ...................................................................................................... 9 8. Dealing with fixed-length strings ............................................................................................................................ 10 9. Dealing with fixed-length arrays............................................................................................................................. 13 10. Dealing with RichTextBox Property Renaming....................................................................................................... 14 11. Dealing with recovering the LenB() function........................................................................................................... 15 12. Dealing with passing parameters to P/Invokes “As Any” ......................................................................................... 16 13. Dealing with StrConv: converting between Unicode and ANSI strings..................................................................... 17 14. Dealing with AddressOf/Missing Delegate issues................................................................................................... 19 15. Dealing with Dir() function warnings....................................................................................................................... 21 16. Dealing with Item issues in Collections .................................................................................................................. 21 17. Dealing with late-bound Object references............................................................................................................. 22 18. Dealing with VB6 parameterless defaults............................................................................................................... 22 19. Dealing with VB6 Null Propagation ........................................................................................................................ 23 20. Dealing with referencing Objects before they are initialized .................................................................................... 23 21. Dealing with TextChanged and Resize events firing before the Form Load event .................................................... 23 22. Dealing with renamed properties ........................................................................................................................... 24 23. Dealing with the loss of the ListCount property....................................................................................................... 24 24. Dealing with changed MousePointer warnings ....................................................................................................... 24 25. Dealing with changes to RECT structures.............................................................................................................. 25 26. Dealing with the loss of the Initialized and Terminate events .................................................................................. 26 27. Dealing with changes to Enumeration references................................................................................................... 26 28. Dealing with VB6 Namespace Twips conversions .................................................................................................. 27 29. Dealing with user-defined Twips constants ............................................................................................................ 27 30. Speeding code by removing references to the VB6 Compatibility Library ................................................................ 28 31. Speeding returned VB6 Namespace List Item values ............................................................................................. 28 32. Dealing with changed Date/Time shortcut format options ....................................................................................... 29 33. Dealing with Date value conversions ..................................................................................................................... 29 34. Speeding Format command use in VB.NET........................................................................................................... 30 35. Dealing with Screen properties.............................................................................................................................. 30 36. Dealing with On Iexpr GOTO................................................................................................................................. 31 37. Dealing with On Iexpr GoSub................................................................................................................................ 34 38. Dealing with updating VB6 error trapping............................................................................................................... 34 39. Dealing with destroying Objects............................................................................................................................. 35 40. Dealing with changes to Common Dialogs............................................................................................................. 36 41. Dealing with VB6.CopyArray ................................................................................................................................. 38 42. Dealing with the loss of the ItemData List Object property ...................................................................................... 39 43. Dealing with changes to Font manipulation............................................................................................................ 40 44. Dealing with changes to Form commands.............................................................................................................. 44 45. Dealing with VB6’s automatic Boolean conversions................................................................................................ 46 46. Dealing with Option Strict On issues...................................................................................................................... 47 47. Dealing with Image and Picture Object upgrades ................................................................................................... 48 48. Dealing with the loss of VB6 Control Lists and how to recover their functionality ..................................................... 49 49. Dealing with changes to MouseMove parameter list changes ................................................................................. 52 50. Dealing with changes to remotely firing Button clicks.............................................................................................. 52 51. Dealing with no AVI animation control in VB.NET and how to easily add a free one................................................. 52 52. Dealing with changes to Resources Management .................................................................................................. 54 53. Dealing with the loss of the App statement............................................................................................................. 57 54. Dealing with updating default ByRef and ByVal method parameters flags ............................................................... 58 55. Dealing with Collection and List clearing................................................................................................................ 58 56. Dealing with Adding Auxiliary Files to ClickOnce Deployment (Publish) .................................................................. 58 57. Dealing with changes to Text Box SelStart and SelLen Properties.......................................................................... 59 58. Dealing with changes to ToolTips .......................................................................................................................... 60 59. Dealing with changes to ListView .......................................................................................................................... 60 60. Dealing with changes to Toolbar Button and Button Menu Clicks............................................................................ 61 Page –2–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 61. Dealing with Unload Form commands ................................................................................................................... 63 62. Dealing with The Loss of the VB6 NewIndex Property............................................................................................ 63 63. Dealing with Process Handling in the KeyPress, KeyDown, and KeyUp Events....................................................... 64 64. Dealing With Invoking Handled Events Under VB.NET........................................................................................... 65 65. Dealing With TextBox Locked Property Changes ................................................................................................... 67 66. Dealing With changes to the Tag Property............................................................................................................. 67 67. Dealing With Changes to the GotFocus and LostFocus Events .............................................................................. 67 68. Dealing With Long-Pathing Through Namespaces ................................................................................................. 68 69. Dealing With Changes to the VB6 SetFocus Command ......................................................................................... 68 70. Dealing With Changes to Multiple Document Interfaces ......................................................................................... 68 71. Dealing With Changes to a Button’s Cancel and Default Properties ........................................................................ 67 72. Dealing With Changes to CheckBoxes .................................................................................................................. 69 73. Dealing With Property Conflicts With VB Commands ............................................................................................. 69 74. Dealing With using Icons for Menu Images (Bitmaps) under VB.NET...................................................................... 69 75. Dealing With Converting VB6 Fast FSO File I/O to Faster VB.NET Streaming Methods .......................................... 70 76. Dealing With Changes to Counting CheckBoxed ListBoxes .................................................................................... 70 77. Dealing With Changes to Mouse Pointer Icons....................................................................................................... 71 78. Dealing With Specific Changes to the KeyDown and KeyPress Events................................................................... 71 79. Dealing With Changes to Drag and Drop ............................................................................................................... 73 80. Dealing With the Loss of MAPI Controls ................................................................................................................ 76 81. Dealing With Displaying a Checkbox as a Button ................................................................................................... 76 Closing Remarks ....................................................................................................................................................... 77 About the Author ....................................................................................................................................................... 78 Free Online PDF Documents Available by David Ross Goben................................................................................. 79 Open Letters Sent to Advocates for the Electric Universe and Expansion Tectonics Theories................................. 79 Navigating Your Way through Visual Basic 6.0 to Visual Basic .NET Application Upgrades .................................... 80 Enhancing Visual Basic .NET Applications Far Beyond the Scope of Visual Basic 6.0............................................ 81 Doom 3 Walkthrough and Strategy Guide ............................................................................................................. 82 Also Available from the Author ................................................................................................................................. 83 A Gnostic Cycle: Exploring the Origin of Christianity............................................................................................... 83 NOTE: This manual was first written when VB2008 was still a new product. Presently VB2013 is the current contender, but the contents of this manual are still fully applicable to the VB2010, 2012, and VB2013 environments. Further, the Upgrade Wizard referenced herein was last available in VB2008, which is still available in its free Microsoft Visual Basic 2008 Express edition. Also, it is my feeling that VB2008 has so far actually been their very best effort, where Microsoft® finally fully realizing their .NET philosophy in Visual Basic. Although VB2005 was a decent start, I have always looked at it as more of a “Release Candidate” for Visual Basic .NET, because translating between VB6 and VB2005 was still sometimes a rather tricky, and often frustratingly complicated effort, almost always never worth all the fuss. VB.NET betas, VB2002, and VB2003 were extremely poor implementations that were filled with too often convoluted, complicated ‘trash’ that should have never been found or accepted within the .NET environment. It is my feeling that they should have never bothered releasing any of that code as a salable item, but releasing it only to those who wanted to freely download and test it, and to report bugs and enhancement suggestions. It is my strongest feeling that VB6 should have been fully and aggressively supported through the release cycle of VB2008, not the marginalized nodding support that it received through the release of VB2008. Until that time, Microsoft should have more assertively supported extensions to forms and the ability to access .NET forms, workspaces, and code until that time (as it was, they did in fact do this, but no one in the VB6 community ever seemed to be aware of it), easing the VB6 transition from its procedural code platform roots to the more powerful Object Oriented world of .NET. Unlike the claims made by some critics, everything that can be done in VB6 can be easily accomplished in .NET as of VB2008, no longer requiring convoluted alternative solutions or intense, deep research to find a compatible alternative. However, many developers do not have a need for object-based programming, and VB6 still fills that procedural language niche for VB. Page –3–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Introduction When a Visual Basic 6.0 (VB6) application is upgraded to Visual Basic .NET (VB.NET; to at least VB2008) using the Visual Basic Upgrade Wizard (you can still download VB2008 Express to get this upgrade wizard), chances are that, once the upgraded application comes up in Visual Studio or Visual Basic Express, its Task List will present you with a number of, or typically quite a number of alerts, upgrade issues, to-dos, warnings, notes, global warnings, run-time warnings, and design issues. DO NOT PANIC! It is not as bad as you might at first think it is. Most of them are notices, and are only that: Notices. And virtually all of these added tagged comments can be safely reviewed, ignored, and then deleted without fanfare. Of those that actually do require attention; most can be solved in very short order using quick and easy edits. The numbered solution points provided in this document will solve almost every one of your upgrade woes. These solutions will work when you are upgrading to either the full Visual Studio.NET editions, or to the Visual Basic .NET Express Editions. But before diving into those solutions, you should also understand exactly why all these upgrade issues exist in the first place; why the current generation of Visual Basic has just a little bit of trouble upgrading from the previous generation (VB6). It is not, as many detractors will most vehemently (but without any merit) contrarily shout, because VB is now somehow broken (from my own personal point of view, I believe that it is the other way around – VB6 was a patchwork of hacks, fixes, and upgrades that did not seem to always follow uniform standards of syntax). But the best answer is that it is because VB.NET is now what those VB6 programmers desperately wished for VB to be (so be careful what you wish for). When the majority of VB6 developers demanded the ability to build VB code in a common language IDE (Integrated Development Environment), have 100% unfettered cross-language interoperability, and expecting no less than 100% unrestricted object oriented programming language capabilities, most of them did not have the first clue, not the slightest understanding, of the earth-shattering impact their passionate, relentless, spittle-laced howling would have on their beloved VB. An immense host of them, mostly amateur programmers and hobbyists, naively believed that after such a necessarily monumental undertaking, they could simply continue on their merry little way, writing VB code exactly as they had done before, using the very same often non-uniform syntax that they had been using before, and, oh yes, they expected there would be a few additional commands here and there to address full class inheritance, and also allow seamless access to methods whose source code was written in some other programming language, such as C++. They did not grok the fact that in order to provide them with exactly what they were keenly expecting would also clearly necessitate colossal changes to their beloved VB so that it would be a fully integrated, object oriented environment that would also interoperate with and act exactly like the other Visual Studio languages (or other .NET-compliant languages). This requires perfect synchronicity between all those languages; that each of them could clearly understand and use objects from each other without the slightest misconstruction. Though most VB6 users feel they are alone in this upgrade quandary, they most certainly are not. They have it easy compared to some language upgrades. Try translating between C (C89; K&R C) and C99, or C++85 and C++95, or better, between upgrades of the very first high level language, FORTRAN (Formula Translation), between FORTRAN57 and FOTRAN77, or even FORTRAN95. Upgrades bite everyone. Regardless of whether you write something in VB.NET, C#, C++, Delphi.NET, Google Chrome, or even IL (Intermediate Language – Microsoft’s brilliant cross-platform assembly language), their syntax must always sync up perfectly. In fact, code written in one language should be instantly translatable into one of the other languages based solely on the compiled IL instructions from any of those languages. For example, suppose you wrote the following VB.NET code within Form1 so that you could emulate the old VB6 command “ZOrder(Me, 0)”, to bring your form to the front of the window display stack: Public Sub ZOrder(ByVal frm As Form, ByVal Position As Integer) Page –4– If (Position = 0) Then frm.BringToFront()'use VB.NET function to bring the form to the top of the z-order Else frm.SendToBack() 'use VB.NET function send the form to the back of the z-order End If End Sub
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben This code is compiled into Microsoft Intermediate Language (MIL) as the following: method public instance void ZOrder(class [System.Windows.Forms]System.Windows.Forms.Form frm, int32 Position) cil managed { .maxstack 8 L_0000: ldarg.2 L_0001: ldc.i4.0 L_0002: bne.un.s L_000c L_0004: ldarg.1 L_0005: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::BringToFront() L_000a: br.s L_0012 L_000c: ldarg.1 L_000d: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::SendToBack() L_0012: ret Page –5– } The above machine independent language can then be immediately translated into C#: public void ZOrder(Form frm, int Position) { if (Position == 0) { frm.BringToFront(); } else { frm.SendToBack(); } } Or into C++: public: void __gc* ZOrder(Form __gc* frm, Int32 __gc* Position) { if (Position == 0) { frm->BringToFront(); } else { frm->SendToBack(); } } Or into Delphi.NET: procedure Form1.ZOrder(frm: Form; Position: Integer); begin if (Position = 0) then frm.BringToFront else frm.SendToBack end; Or into Google Chrome: method Form1.ZOrder(frm: Form; Position: Int32); begin if (Position = 0) then frm.BringToFront else frm.SendToBack end; Or into any other .NET-compliant language. But to achieve such a fluid transition, one cannot hide a lot of code. So much of the code that was previously hidden away from VB6 developers had to be brought out into the open. The fact that it had been hidden in the first place was an attempt to simplify the language to fully empower it as a RAD (Rapid Application Development) platform, which that end most certainly achieved, but it was also one of the main reasons why many C++ programmers looked down their noses at VB6, chiding it as a tinker toy language, because the total breadth, the total power, and the total unreserved system control that VB developers could otherwise wholly exploit was withheld from their grasp, which in my view severely handicapped VB developmental capabilities, limiting them far beyond that which most might venture to imagine, and, as Microsoft later agreed, this tactic also severely inhibited VB’s RAD potential.
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Having said that, am I a fan of VB6? Yes, I am. But will I continue to use it? I doubt it, except for maintenance purposes, to support my customer base who have invested heavily in VB6 software and either have no desire to upgrade, or do not currently wish to incur the expense or can afford the time of an upgrade. But I like too much the colossal power and the magnificent freedom I enjoy while using VB.NET to want to turn back to VB6. In fact, I have become so comfortable in VB.NET that I may now have trouble writing original code in VB6 again, mainly because of the conflicting syntax between controls and commands that VB6 permitted, or even required, is more complicated than the smoothly consistent syntax of VB.NET. I have always stressed the point that, because VB6 was a serious RAD platform, a developer could write in two hours what would take two weeks to match it in robustness using C++. I stand firm that this declaration is now even more true of VB.NET (this explains why I have finally made a full migration from C++). By the time VB2005 came out, VB.NET was clearly coming into its own as the new RAD platform of choice. The fact that its free version, VB2005 Express, was a full VB.NET compiler, with its only limitations being that it did not support direct interoperability with other .NET languages (which most developers do not bother with, anyway), it was missing a few professional-level templates, and it lacked support of a number of other features made available only to the full Visual Studio environment, such as the free Code Rush! Express editor enhancements (see www.devexpress.com/Products/Visual_Studio_Add-in/CodeRushX/). It also featured much stronger compatibility to VB6 code than VB2003 offered. VB2008 and VB2010 exhibit far greater compatibility to VB6, and they are in fact the only VB.NET platforms that I will recommend to VB6 users who have yet to migrate to the .NET domain. Like VB2005, free VB2008 and VB2010 Express editions are also available (see www.microsoft.com/express/download), plus Microsoft has also provided a number of excellent, previously pricey books regarding VB6 to VB.NET migration on hand for free download (see the end of this document for details and their web links). On top of that, the many numbered points listed in this document will make addressing pesky warnings and upgrade issues a snap to deal with. Not only will these points help you address those issues, but they will also hopefully give you a glimpse into the VB.NET approach to application development, and with any luck I will also be able to convey my excitement for this extremely potent and much more empowering rendition of Visual Basic, which is now a true force to be reckoned with; it finally being just as capable, just as powerful, but can be developed much faster and can be executed just as fast as the other contenders in the development world, such as VC++. The lists of notes in this document were initially compiled from personal references I had jotted down as I made my own migration over to VB.NET from VB6. It started out as a 6-page hodge-podge of terse cuff notes, but continued to grow as I learned new and better ways of adapting VB6 code to the .NET environment, first to VB2002 (a painful experience), then VB2003 (a little less painful), VB2005 (a rather pleasant upgrade), VB2008 (a very easy upgrade), and now VB2010. Prior to the release of VB2005, transitioning VB6 code to the .NET platform was in most cases, except for the simplest of applications, a dreadfully rocky path, fraught with aggravation and dead ends that often required major re-writing for segments of code. When VB2005 arrived on the scene, I noticed that massive pages of personal upgrade notes were now no longer required, and so they were duly discarded. When VB2008 was released, far greater amounts of my upgrade notes were no longer relevant. Indeed, VB2008 is probably the very first edition of VB.NET that I would even consider recommending to people who were thinking about making the transition over from VB6. VB2005 and VB2008 were focused strongly on the language itself and upon VB6 compatibility. VB2010 is geared mostly toward ADO.NET, web development, and business transactions. With just a little more tweaking, and I think these notes will shrink back to their original 6 pages, if not eliminate their need entirely. We can only dream. And now: on to those solutions… VERY IMPORTANT NOTICE: Every solution involving GCHandles, embracing angle brackets denoting Attributes “<>”, or have Marshaling instructions will simply assume that you will have also placed the following line in the heading of any file they are involved in; below any Option statements, if any, but before the Class or Module declaration: Page –6– Imports System.Runtime.InteropServices
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 1.. Dealing with structures passed to P/Invokes containing strings If a Structure (a VB6 User-Defined Type) is passed to a P/Invoke (a Processor Invocation; in VB6 parlance – an API Call) and it contains at least one string member that is expected to be of 8-bit ANSI Chars (as are most string-handling Win32 P/Invokes used by VB6), always be sure to marshal the structure by preceding it with the following special attribute: “<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>”. This ensures that all structure members are actually 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 actually done by default under VB6 (most Win32 DLLs process 8-bit ANSI text, but VB, both VB6 and VB.NET, plus newer Win32 DLLs, actually locally 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 'force what was done for VB6 by default byhind the scenes 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) Page –7– 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 more than 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 merged; 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 you want to set it to transparent, use something like “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 behind 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, within 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 Label1’s location to be relative to its new parent .Top = .Top - .Parent.Top End With
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 4.. Dealing with passing strings directly to P/Invokes If a string is passed as a parameter to a P/Invoke, you should usually be sure to also include the Auto verb after Declare in the P/Invoke 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 otherwise double-convert the text; 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, and will return 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 Mode. 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 Page –8– End Function 'VB.NET version of VB6 ObjPtr Public Function ObjPtr(ByVal o As Object) As Integer 'note that these additional methods actually simply use VarPtr() Return VarPtr(o) 'use VarPtr -- it will return the address of the VB.NET object End Function 'VB.NET version of VB6 StrPtr 'also works for VarPtrStrArray Public Function StrPtr(ByVal o As String) As Integer Return VarPtr(o) 'use VarPtr -- it will return the address of the VB.NET string End Function End Module 6.. More on StrPtr If your VB6 code used the StrPtr() function to pass the 32-bit address of the string, be aware that strings are no longer stored in the ancient BSTR format. Yet, like under VB6, if one passes a string ByVal to a function, you are actually performing the function of StrPtr(). Hence, in most cases the StrPtr() function is not actually required under VB.NET, just as it was seldom needed under VB6. Even so, if you do need to pin down and pass or manipulate the actual address of the string as a 32- bit Integer value , then of course refer back to the previous point. NOTE: This additional VB6-compatible feature of ByVal was introduced in VB2005 in order to enhance compatibility to VB6 and to simplify program 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 discussed in the next point.
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 7.. Dealing with passing strings ByRef to P/Invokes If a string must be passed ByRef to a non-CLR-managed Win32 interop P/Invoke in VB.NET because that process will need to alter that text string or needs to assign a string of text to it, most gurus instruct you to marshal it with VBByRefStr. 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 passing it 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 was 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) Microsoft suggests an alternative to using a ByRef string, and that is to use a StringBuilder; an object introduced with VB2008 that is one of the fastest (200 times faster at standard string manipulations), most powerful, and most amazing string handling 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 is still able to 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 ingrained rules, but we pass a StringBuilder ByVal to the P/Invoke, 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" ( _ Page –10– 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 Function 8.. Dealing with fixed-length strings If the VB6 code uses fixed-length strings, it will be upgraded in the context of their use. Specifically, a Field or a Local Variable will be upgraded differently than a Member Variable of a Structure or Class. By default, the Upgrade Wizard will take the following VB6 Local Variable or Field: Dim FixedLengthTest As String * 128 'Test Code It will be upgraded for VB.NET’s 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, but it is also 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 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 structure encapsulating this variable will stipulate a Charset.Auto Attribute (see point 1), as in “<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>”): Dim szCSDVersion As String * 128 'Fixed-length maintenance version string declared within a Structure or Class You may notice that the VB.NET upgrade to the above VB6 code will have been rewritten, and converted into the following Char array (note that you can trim off the highlighted command path qualifiers if you have 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 this line much further and still provide the very same functionality and ignore the 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 converted Char array, you can also use the following: <VBFixedString(128)>Public szCSDVersion() As Char 'Fixed-length maintenance version string
  • 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 is: No, we will not. We must understand that when the term “allocate” is used here, it really 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 simply 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 necessary (unless we need to pre-initialize it), what follows are four solutions for doing just 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 a reference object), 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. Pass it the structure 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) Page –11– 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), 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 classes that you are likely familiar with from VB6, an Abstract Class can only be assigned to one variable that can reference its actual data. When assigning an Abstract Class to another variable, it passes a copy of itself, giving the target variable a replica of the original (much like using the Clone() method, discussed much later in Point 41). This is juxtaposed to a Concrete Class, which is the standard class type in VB.NET that can have instances instantiated, and a single instance can be pointed to by multiple reference variables (copying a reference variable to another reference variable would simply copy the referenced address in the absence of the Clone method (which would in turn simply instantiate another referenced object)).
  • 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 any such imbedded helper method to 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... Page –12– 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 pass this structure to a P/Invoke. When 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 that would have been used here instead of Len()), you will find that the returned length would report 136 (bytes). This is the size of 2 Integer variables (32-bit/4-byte), allocating 8 bytes, plus the length of the fixed-length string, which is 128 bytes long, yielding a total of 136 bytes. Notice further that I stated that the length of the string was 128 bytes. This is something very important to keep in mind! It is because most Win32 P/Invokes use 8-bit ANSI characters instead of VB’s 16-bit Unicode characters. Even more, the documentation for the GetVersionEx P/Invoke I extracted this example from is actually the GetVersionExA (ANSI) version (as declared in its Alias modifier). Further, if you re-read the previously-stated documentation, the very last line states: “The VBFixedStringAttribute specifies the length of a string in bytes, not characters.” Had I been using the Unicode (Wide) version of the Kernel32.DLL function, GetVersionExW, I would have had to have stated 256 (bytes) as the designated width of the VBFixedArray attribute, in order to fully accommodate 128 16-bit Unicode characters. However, even so, do not be wracked with dread because we had initialized our string to just 128 bytes, yet we, within our code, may be, by proxy anyway, associating it with a string of 128 Unicode characters. This is because the CharSet.Auto property will tell the VB.NET interop marshal operator to automatically handle the Unicode/ANSI conversion for us, providing the required 128 8-bit ANSI characters needed during interop from the structure by the P/Invoke. This material appeared complicated the first time I ran into it, but after using it for a while, it quickly became quite simple and normal. My own personal solution is to only apply special attributes to the body of the structure when I will be performing any Win32 interop processes with it, which is to say that I will be invoking methods declared in unmanaged or non-.NET space. Also, I prepend any structure strings that must be of a fixed size with “<VBFixedString(xxx)>”, specifying the number of bytes the string will occupy, or doubling it if I am dealing with Unicode, and I typically declare structure variables using the “New” verb so that I will know that its data fields will be initialized. When specifying fixed-size arrays in my structures, I must also perform the additional easy step of adding a simple initialization method to my structure, which is outlined in the following point.
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 9.. Dealing with fixed-length arrays If a Structure requires at least one fixed-length array (VB.NET only allows un-initialized dynamic arrays to be declared within a Structure), then an upgrade will modify the structure to define an un-initialized dynamic array in place of the fixed-size array, but unlike Point 8, above, in order 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 also invoke the Initialize() method after assigning the structure reference to a variable for field. This special method is meant to be used to ensure that the array can be easily set to its mandatory startup dimensions after assignment. 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() Page –13– 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 should leave this code alone and simply invoke the Initialize() method after we assign the structure to a reference variable. If we need to supply this structure to a P/Invoke, then some think you might not be able to safely keep the Initialize() method within the structure. 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 squeamish about it, you might try something really unfortunate, like simply ignoring it or, worse, even deleting the Initialize() method. Is it OK to not bother with or to delete the Initialize() method, assuming that the 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 wisely retained the Initialize() method, as I hope you will, 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, such as in the following form: “ReDim lfLogFont.lfFaceName(LF_FACESIZE)”.
  • 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 sets aside space for the pointers, 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 Page –14– ... 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…
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 11.. Dealing with recovering the LenB() function A VB6 program would use LenB() to obtain the actual byte length of a string or structure, usually to insert the length of a structure into its first member (some variable-sized structures require this). However, during an upgrade to VB.NET, code using LenB() is always flagged as not being supported by VB.NET. Although Microsoft’s help is a bit lacking in providing a clear resolution to this important issue, the 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 Page –15– 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 fixed string 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
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 12.. Dealing with passing parameters to P/Invokes “As Any” If a P/Invoke has parameters tagged “As Any”, you are actually much better off using type-safe overloaded methods in VB.NET. With it you can declare more than one type-safe method with the same name, but using different parameters. This simplifies coding because you do not need to be concerned about which method to invoke. This is a feature 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, though one that was absolutely necessary for the pre-VB.NET platforms, for 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 assigned to 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 usually sent as lParam. Using “As Any” allowed both text and values to be passed by the very 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 method bodies, but provide them with the very 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 would 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 modify 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 exactly like VB6 Page –16–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 13.. Dealing with StrConv: converting between Unicode and ANSI strings Normally you can use the StrConv() method, as you had used it under VB6, and the upgrade will adjust parameter-naming changes. But when converting to/from ANSI and Unicode text, you will have to do this differently because Unicode/ANSI conversion is not presently supported by VB.NET’s StrConv, which is now geared more toward much-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 Convert this VB6 command line 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 driving 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 member, 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, _ Page –17– 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 to a BrowseInfo structure 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 "Select Source Path" and include a 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 ...
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben What 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 developers automatically placed an Auto verb in the declaration of the CopyMemory() P/Invoke, remembering only that we will be processing a string (Private Declare Auto Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory"), just as all the online examples I have seen do. To clarify, refer to Point 4 concerning my warning against including both the Auto and Alias verbs in a P/Invoke declaration. Further, because VB.NET has special knowledge of RtlMoveMemory(), it will enforce Unicode to ANSI text translation if any string is used in this method. What results is our Unicode string remains intact, and so, in binary terms, the string begins with the Unicode character “S” (x’0083’), which is actually stored internally in memory as x’83’ and then x’00’ (the ‘high’ byte x’00’ is listed after the low x’83’ because, internally, memory progresses, relatively, from left to right; from low to high – even bit sequences are reversed from how we typically imagine them as right to left – but this is all hidden from you), and because text is processed internally as ANSI by the P/Invoke, it finds an 8-bit null text terminator following the “S”. NOTE: Even though the Auto verb had been present, be aware that if it was retained, but no parameter contained a string type, then the method would have worked 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 the CopyMemory() declaration, those developers apparently did not figure that out. Even so, we can still explore the useful alternative method they developed for passing 8-bit ANSI strings to P/Invokes. One idea they came up with was to pass MoveMemory() a pre-converted ANSI string as a Byte array instead of a Unicode string. I will also assume that the declared CopyMemory() P/Invoke will not be used elsewhere in the current module, so I will simply redefine it to a version that will accept a Byte array (I could have easily just added an overloaded method). Since the P/Invoke now does not contain strings, we will remove the Auto verb (but you watching at home know that we just fixed the actual bug in the program, and needing no other changes, because apparently those developers were not aware of the rule warning against using both Auto and Alias in a P/Invoke declaration). 'copy memory API (note that if Auto were still present in this type of declaration, it ' (would still work perfectly, because string parameters are not present) Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal pDest As Integer, _ Page –18– ByVal pSource() As Byte, _ ByVal dwLength As Integer) And to pass ANSI text to the CopyMemory() method, I will need to translate the Unicode text to a Byte Array. I would therefore update my code with the following darker shaded segments: With udtBI .hwndOwner = hwndOwner 'owner handle (Me.Handle, or the handle of the form to 'parent' the dialog) .pidlRoot = 0 'Address of an ITEMIDLIST structure, or use NULL (0) if it is not used Dim Pmt As String = sPrompt & Chr(0) 'set local prompt copy and include a trackable null terminator in its size Dim Byt() As Byte = System.Text.Encoding.ASCII.GetBytes(Pmt) 'convert Unicode String to ANSI Byte Array lPmt = LocalAlloc(Lptr, Len(Pmt)) 'set aside space for text (use char count of the prompt for its 8-bit ANSI length) CopyMemory(lPmt, Byt, Len(Pmt)) 'copy byte array (Byt()) to set aside space (lPmt) for the char count of Pmt .lpszTitle = lPmt 'set address of string stored in unmanaged memory to structure member ... 'other initializations go here... End With NOTE: By the way, on NEW projects that involve folder and file selection dialogs, you should take the E-Z route and simply employ the FolderBrowserDialog or OpenFileDialog controls available in the VB.NET IDE Toolbox. With these you can set custom prompts and starting paths easily and quickly, and they require absolutely no interop code from you. For reference, what follows is a module that will provide Unicode/ANSI string conversion: Module modUnicodeANSI '****************************************************** ' StrConv_Uni2ANSI ' Provide conversion from Unicode string to ANSI string '****************************************************** Public Function StrConv_Uni2ANSI(ByVal StrUni As String) As Byte() If Len(StrUni) = 0 Then Return Nothing Return System.Text.Encoding.UTF8.GetBytes(StrUni) 'convert Unicode string to ASCII bytes End Function '****************************************************** ' StrConv_ANSI2Uni ' Provide conversion from ANSI string to Unicode string '****************************************************** Public Function StrConv_ANSI2Uni(ByVal Bytes() As Byte) As String Return System.Text.Encoding.UTF8.GetChars(Bytes) 'convert ASCII bytes to Unicode string End Function End Module
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 14.. Dealing with AddressOf/Missing Delegate issues When a warning about the use of the AddressOf() function indicates that the object needs a Delegate, this is easy to remedy, but the solution is a 2-step process. First, a Delegate (a prototype 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 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 unique 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”, where the address is to be assigned to, and then select “Go To Definition”. For this example, it took me to the following line that is located within a structure definition: 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 missing delegate problem. NOTE: If the above were not a member of a structure, but a parameter provided directly to a P/Invoke; simply change its type to the name of the Delegate, just as we did above. You may also want to go back to your book-marked location and toggle that bookmark off, as well as deleting the UPGRADE_WARNING comment inserted by the Upgrade Wizard). NOTE: Be aware that the Delegate will be internally interpreted as an IntPtr, which is an Integer Pointer, being the exact same size as an Integer. All addresses returned by AddressOf also return an Intptr (treaded by VB6 as a Long). Page –19–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben In another case, in instances where you hook and unhook window/form subclasses, intercepting the Windows Message Queue, this requires using the SetWindowLong P/Invoke. You may want to simplify the delegate handling process by defining multiple versions of this P/Invoke. Consider the following code clipping, which must invoke SetWindowsLong in two different ways; one using a Delegate (notice the use of AddressOf), and the other using a simple Integer value, as shown below: '************************************** ' Declare our P/Invoke to hook/unhook our code to the Windows Message Queue '************************************** Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Integer, _ Page –20– ByVal nIndex As Integer, _ ByVal dwNewLong As Integer) As Integer '*********************************************** ' HookWin(): Subclass hwnd to szWndProc: Insert a hook in the Windows Message Queue to divert queue processing to szWndProc '*********************************************** Public Sub HookWin(ByVal hWnd As Integer, ByRef PrvhWnd As Integer) 'UPGRADE_WARNING: Add a delegate for AddressOf SzWndProc. Click for more: BLAH BLAH PrvhWnd = SetWindowLong(hWnd, GWL_WNDPROC, AddressOf szWndProc) 'insert our hook, and save the previous one End Sub '*********************************************** ' UnhookWin(): remove subclass hook to szWndProc: remove our function diversion and stuff the old saved invocation back in '*********************************************** Public Sub UnhookWin(ByVal hWnd As Integer, ByRef PrvhWnd As Integer) If CBool(PrvhWnd) Then 'if somethinging (the old windproc) to unhook... Call SetWindowLong(hWnd, GWL_WNDPROC, PrvhWnd) 'reset previous hook by over-writing ours PrvhWnd = 0 'indicate no held hooks set aside End If End Sub '*********************************************** ' Our custom subclassing method to insert into the Windows Message Queue (only the header is shown below). '*********************************************** Private Function szWndProc(ByVal hWnd As Integer, _ ByVal uMsg As Integer, _ ByVal wParam As Integer, _ ByVal lParam As Integer) As Integer ... 'rest of program and function was here After defining the needed Delegate using the technique previously outlined, say szWndProcDelegate, to prototype our subclass method, szWndProc, some creative people have attempted to dance the Delegate name around, which resolves to an IntPtr/Integer, and declared PrvhWnd in HookWin, the PrvHwnd parameter for UnhookWin, and so on, to this Delegate. We might try fraught things like this as we blink excessive sweat from our eyes while struggling desperately to stop VB.NET from incessantly barking at us after each compile attempt with it badgering us about improper data typing; this stemming from our myopic attempts to resolve common addressing to our P/Invoke, SetWindowLong. But the above is making a simple problem a lot more complicated than it needs to be. Instead of trying to adjust variable typing to suit our P/Invoke, why not think broadly and simply massage our P/Invoke into addressing the two different ways that HookWin and UnhookWin need to invoke it? So, after thinking peaceful Zen thoughts about fluffy puppies and kittens, and having the full power of object oriented programming at hand, we should realize that all we really need to do is take advantage of VB.NET’s capacity to support overloaded methods. With that in our bag of tricks, all we must do to correct our problem is 1) create the needed Delegate for szWndProc, and then 2) define another, but overloaded, SetWindowLong method to support the Delegate. This way we will have one version of SetWindowLong with an Integer version to handle parameter dwNewLong, and another version of SetWindowLong that uses our Delegate as the type for dwNewLong. With just that, suddenly this otherwise potentially very complicated problem (well, it was complicated if we think too narrowly) is completely solved, as the darker shaded areas demonstrate below: '*********************************************** ' Declare our Delegate – declare it before it is referenced. This delegate resolves to a simple Integer/IntPtr '*********************************************** Public Delegate Function szWndProcDelegate(ByVal hWnd As Integer, _ ByVal uMsg As Integer, _ ByVal wParam As Integer, _ ByVal lParam As Integer) As Integer ... '*********************************************** ' Declare our overloaded P/Invokes to hook/unhook our code to/from the Windows Message Queue '*********************************************** Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Integer, _ ByVal nIndex As Integer, _ ByVal dwNewLong As szWndProcDelegate) As Integer ' "dwNewLong As szWndProcDelegate" will be treated at the machine level as simply an Integer/IntPtr Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Integer, _ ByVal nIndex As Integer, _ ByVal dwNewLong As Integer) As Integer
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 15.. Dealing with Dir() function warnings If you are warned that the Dir() method has changed behavior, this concerns the sequencing of folder shortcuts “.” (Current) and “..” (Parent) during a directory scan. In VB6 these two folders were always the first two entries gathered. Some programmers got goofy and just skipped checking the first two entries based on this assumption. This is no longer true, nor should it have 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 under VB.NET it is now a more versatile type Object (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 new RemoveAt() method, which will allow you to specify an index. NOTE: Remember, the warning about an Item’s default property will probably be the most frequently encountered warning in an upgrade if the VB6 project did much of anything with collections. Keep in mind that using the Item object’s ToString() property will be the safest, easiest solution, especially if you are instead using a ListBox or a ComboBox, but which can also use custom classes for data instead of just strings (as it had been for VB6). However, be mindful that these new collections are zero-based, not 1-based as the default ‘classic’ Collection. If you need it to be 1-based, stuff a dummy entry as the first item, but remember that the Count property will always be one higher. Page –21–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 17.. Dealing with late-bound Object references You may surmise from the last point that using late-binding (supposedly a bonus feature of Visual Basic) can in turn be confusing to the Upgrade Wizard. This is because sometimes it cannot be sure what kind of objects it is working with. As a rule, you should only take advantage of late-binding as a last resort; for it can take a heavy toll in time during execution, because internally, the processing engine must scan the object for supported functionality, which is stored internally not as fast indexes, but as a text data that must be parsed against internal tables also stored within the DLL (OCX – ActiveX – files simply redirect you to a DLL). Once the entry is found (if it exists), then an invocation address and a delegate (prototype) of the method must be matched to any parameters. Using late-binding is very much like using slow interpreted DOS BATCH programs (to be fair, I should have said VBScript or JavaScript). It should only be used when you have absolutely no other choice, because it is slow and cumbersome. Consider the following simple VB6 code segment: Dim o As Object o = Me.Label1 o.Caption = "SomeText" When this is upgraded to VB.NET, the wizard will not know, when processing the last line, that the object “o” is associated with a Label control, because its scope of knowledge of its surroundings is confined strictly to each statement as it is being upgraded. As such, even though in the second line a Label control is assigned to “o”, the Upgrade Wizard has discarded that knowledge as it rolls up its sleeves and clears the table to begin work on the next line. The only thing it knows for sure that is that “o” is declared as type Object. Hence, during the upgrade, it will not upgrade Caption to the new and now-uniform property Text in VB.NET. There is no sure way that it can assume that the actual object stored within “o” is a Label control, because it might be a user-defined class that will in fact continue to use a Caption property. These will have to be manually, though easily repaired. 18.. Dealing with VB6 parameterless defaults You will quickly discover that VB.NET does not support parameterless default properties. A lot of warnings will be issued in your upgraded code if you were one prone to using them. The decision 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 requires an index or key property, which fully qualifies it to be declared a default property. You can specify “Items.Item(Index)” or “Items(Index)”, but its intent is still clear. But having said all that, keep in mind that default properties are often processed late-bound, meaning that processing them and determining them is a process-slowing, time-consuming practice. For the fastest possible program execution, always early bind your code as much as possible. Also, as is probably made evident in the previous example, you should avoid using default properties with the Object and Variant data types in your VB6 code, because these can be difficult to resolve during an upgrade to VB.NET, and you will likely have to further modify the code yourself after the upgrade. Page –22–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 19.. Dealing with VB6 Null Propagation One obscure area that may run you into trouble is in regard to Null Propagation. In VB6 and previous, Null propagation supported the premise that wherever Null is used in an expression, the result of the expression will itself be Null. Consider the following VB6 code: Dim V V = 1 + Null V = Null + Right$("SomeText", 1) V = Right("SomeText", 0) Each of the above expressions yields a NULL result under VB6. However, under VB.NET, the statement 1+Null generates a type mismatch. Also, where VB6 had two versions of the Right function — Right$ returning a string, and Right returns a variant that could be Null — VB.NET only has one version, Right, that always returns a string. Further, V is declared as a variant by default under 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 by 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 = Nothing” 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 Goben 22.. Dealing with renamed properties Often you will find that form controls such as Listboxes and ComboBoxes now have renamed members in VB.NET from what they were in VB6. Most of the time it is because you may have accessed the object’s “Hwnd” (Windows Handle) property. For reasons of cross-language uniformity, VB.NET changed all Window Handle properties to “Handle”. Simply edit the properties from hwnd to Handle and they will work correctly. Why do upgrades not automatically fix this? 23.. Dealing with the loss of the ListCount property If form controls contain lists of any sort, then they likely implement Collections.IList. As such, upgraded VB6 properties that generate warning errors such as an unsupported ListCount property can be fixed by addressing the Items collection within the control (all collection-type lists now use the Items collection, and all with a Default Property of Item – there is no longer a List collection or ListItem property). For example, if a ListBox control named LstBox used the now-unrecognized ListCount property, edit it to instead use “LstBox.Items.Count”. Why do upgrades not fix this? 24.. Dealing with changed MousePointer warnings You may encounter warnings that the Screen property Screen.MousePointer has a new behavior. It has been my experience that in almost all cases you can safely ignore them, except in 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 to 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, under 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 to store a copy of the cursor file locally. To add a local copy of the cursor to your project (if it is not added to your project already), in the Soultion Explorer, right-click where you want to store the resource (I create folders named Classes, Forms, Modules, and Resources – using the AddAdd New Folder option – to keep my projects tidy), or the project name itself if you simply want to store in in the local pool of files. Select AddAdd Existing Item, and browse to and select your cursor file (even it is stored locally). It will be added to your project and you will see it listed in the target storage list. Next, we need to ensure that it is actually embedded within our application executable. To do that, 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 ProjectPropertiesResources, Select either Files or Other (it does not matter which) from the first dropdown, then Add Existing File from the Add Resource dropdown, then select your cursor. Next, in order to load your Cursor resource, you will need to read it as a stream. However, because it is actually stored in the resources as an 1-dimensional array of Bytes, we cannot cast it directly into a stream. However, what we can do is first read these bytes into a memory stream (rather than writing it first to a file). A memory stream exists wholly in-memory and we can dispose of it when we are finished with it, or, as I will demonstrate, we will let the Garbage Collector handle that for us. Thus, once we have the item stored, we can use a command sequence like the following to get it: Cursor = New Cursor(New System.IO.MemoryStream(My.Resources.Dilbert)) 'read cursor resource as a stream 25.. Dealing with changes to RECT structures I did a lot of VB6 programming with the Rect structure, typically defined as the following: Type RECT Page –25– 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”.
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 26.. Dealing with the loss of the Initialized and Terminate events If you have classes containing Initialized and/or Terminate events, then after they are upgraded you will find that the Initialized event is renamed Initialized_Renamed, and the Terminate event is renamed Terminate_Renamed. You will also notice that a New (constructor event), and a Finalize (destructor event) have been added to your class code. The New event will in turn invoke the Initialized_Renamed code, and the Finalize event will invoke the Terminate_Renamed code. Regardless of the fact that this is a workable strategy for class code upgrades, to me it looks a bit goofy, and is too much work than just leaving the original function names alone and invoking them from New and Finalize. As a bit of extra self-imposed work, I simply cut the code from within the body of the Initialized_Renamed code and paste it over the top of its invocation line (replacing it) for Initialized_Renamed within the Sub New code. I do likewise for Terminate_Renamed and Sub Finalize. I then of course delete the empty bodies Initialize_Renamed and Terminate_Renamed. For Example, the following VB6 Initialized method: Public Sub Initialized() LnkPrev = Nothing 'init links to nothing for now LnkNum = 0 'assume base of new array list (at least for now) Init() 'reset potential variable data Page –26– End Sub Is upgraded to VB.NET to: Public Sub New() MyBase.New() Initialized_Renamed() End Sub Public Sub Initialized_Renamed() LnkPrev = Nothing 'init links to nothing for now LnkNum = 0 'assume base of new array list (at least for now) Init() 'reset potential variable data End Sub But I further combine these two methods by changing it to: Public Sub New() MyBase.New() LnkPrev = Nothing 'init links to nothing for now LnkNum = 0 'assume base of new array list (at least for now) Init() 'reset potential variable data End Sub 27.. Dealing with changes to Enumeration references Various states are often checked against constants. In VB6 you check the form’s WindowState property for the constants vbNormal, vbMaximized, or vbMinimized. Although an upgrade sets these constants to FormWindowState.Normal, FormWindowState.Maximized, and FormWindowState.Minimized, it may have trouble determining from the program context what property the term should be upgraded to. After all, VB.NET also recognizes vbNormal to be a property constant for file attributes. As such, you can usually simply glance at the code in a wider context and determine what property should be checked, such as WindowState, FileAttributes, Constants, or other constant enumerators.
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 28.. Dealing with VB6 Namespace Twips conversions Nearly all individual command upgrades thankfully do not warrant warnings. Most-times these silent fixes will efficiently redress code, but once in a while these fixes will leave something to be desired in-as-far as efficiency is concerned. It is not that they do not operate correctly; logically, they work perfectly, but the code they generate can sometimes be a bit of over-kill. Most times this over-kill involves twips. Twips (1440 dots per logical inch) were the default unit of graphical measurement in VB6 and previous, allowing fast integer conversions between pixels (96 dots per logical inch) and points (72 points per logical inch), but it made the code incompatible as-is with API functions, which only operated in terms of pixels. Now that VB.NET has eliminated this confusion and by default uses display/printer-compatible and universally-accepted pixel measurements, we now have to suffer with VB6 upgrades that still need to deal translating the VB6 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 now 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 Goben 30.. Speeding code by removing references to the VB6 Compatibility Library It would be a really good idea to search for “VB6.” throughout your code and see if you need to dress the code back down a bit to using only native VB.NET methods. I prefer to eliminate all VB6 namespace usage so I can remove the compatibility reference. Although Microsoft has stated that they “implemented the VB6 namespace because the conversion of some VB6 code is impossible due to syntactical or architectural differences, and for this reason the functions in the VB6 Compatibility library are used to allow the code to run in VB.NET,” I disagree. Perhaps it is “impossible” in terms of duplicate syntax, but it is nothing much more than that, except, I cede, that some VB6 functions address COM-based (registered DLL) features; a virtual codeword in .NET Framework lingo for the possibility for the dreaded DLL Hell2. But I do not agree that invoking objects in unmanaged space makes them impossible to run in VB.NET without the VB6 namespace functions, even though it is probably the absolute best solution for absolute 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 not check versioning and overwrite newer COM-based DLLs with older, less feature-filled versions of the DLL. Page –28–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 32.. Dealing with changed Date/Time shortcut format options Prior to VB2008, some date/time formats were not recognized, such as "Short Date" and "Long Time", but that did not mean that their functionality was not still built into VB.NET. Although they are once more recognized, the VB6 statement “S = Format(Now, "Short Date")”, for example, is still upgraded to VB.NET as “S = VB6.Format(Now, "Short Date")”, even though it is no longer necessary as of VB2008, and the original statement is syntactically perfect. However, regardless of this, it would still be much faster to take advantage of VB.NET’s own built-in functionality and upgrade either statement to “S = Now.ToShortDateString”. A slicker, more versatile means to express dates and times is to take advantage of a Date object’s ToString() method (Search help for “DateTimeFormatInfo” and “DateTime.ToString Method”), where you can optionally specify date and time formats with very little typing. For example, we could change the above “S = Now.ToShortDateString” assignment to “S = Now.ToString("d")” instead. The “d” format tag, a shortcut tag representing “M/d/yyyy”, is one among many tags that VB.NET has reserved to express dates and times in just about any format you wish, to include, of course, custom formats. Following are some sample formats (Note that CultureInfo is En-US): Format Comment (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'-'dd'T'HH':'mm':'ss (Sortable Date Time Pattern) 2010-01-22T09:53:21 "u" yyyy'-'MM'-'dd HH':'mm':'ss'Z' (Universal sortable) (invariant) 2010-01-22 09:53:21Z "U" Universal sortable Friday, January 22, 2010 2:53:21 PM "Y" MMMM, yyyy (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:00 33.. Dealing with Date value conversions Another issue concerning dates is the ability of VB6 to store Date information not only in Date-type variables, but also in Doubles. This was a fluke simply because VB6 and earlier had used a Double as the general storage format for its Date type. I must admit that I had taken advantage of it when using VB6, because it was simply too easy to do. In VB.NET they are no longer stored as simple doubles, but have additional functionality stored along with them, making them more powerful. However, if you have VB6 code that does manipulate the date data that is assumed to be stored in a Double, use the Date object function ToOADate() to convert the VB.NET Date data to a Double, and FromOADouble() to convert from a Double to a VB.NET Date value. For example, often a double was used to strip the time (or date) from a Date variable, which stored both the date and time: Dim netDate As Date = Now 'get date and time of day Dim dblDate As Double = Fix(netDate.ToOADate()) 'get date without time of day netDate = netDate.Subtract(netDate.TimeOfDay) 'or, simply remove time factor from self The first two lines emulate the VB6 method. The third emulates this in VB.NET without any helper functions (I have also seen this same solution written in some on-line VB.NET code as “netDate = Date.FromOADate(Fix(netDate.ToOADate()))”, which accomplishes the same task, but eats more time). Page –29–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 34.. Speeding Format command use in VB.NET Statements using VB6’s Format command, like “myStr = Format(TotalFolders - FolderCnt, "#,##0")” will be upgraded in VB.NET to “myStr = VB6.Format(TotalFolders - FolderCnt, "#,##0")”. However, you can speed up this code by instead using “myStr = (TotalFolders – FolderCnt).ToString("#,##0")”. The integer result is a value-type, and as such it acts exactly like a variable or field; hence, embraced (encapsulated) expressions, even for strings or function results, have method and property members. By the way, VB.NET also fully supports the original statement, “myStr = Format(TotalFolders - FolderCnt, "#,##0")”, so you could simply remove the “VB6.” that the upgrade prepends to the Format statement, and it will continue to be operational, even though I prefer the isolation the ToString method provides the result’s format, on top of it being a more elegant solution. 35.. Dealing with Screen properties In VB6, you could access the Me.Screen object and obtain its dimension properties, such as Left, Right, Top, Bottom, Width, and Height. The new VB.NET Screen class is now loaded with functionality, and so for us to access the Screen’s Left, Right, Top, Bottom, Width, and Height properties, we have to drill a little deeper into the Screen object, going to its PrimaryScreen.Bounds structure, which exposes the desired properties. For example, if your VB6 code contained: Dim ScrHeight As Double ScrHeight = Me.Screen.Height It will be upgraded to VB.NET like this: Dim ScrHeight As Double ScrHeight = VB6.PixelsToTwipsY(System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height) However, we can simplify this code in 3 ways: 1) if we are in a form, then the System.Windows.Forms namespace is already loaded, so we can cut that from the code. 2) We are now working in Pixels, so we can remove all Twip conversions. 3) In VB.NET we can combine declaration with assignment. All these options give us this much shorter and simpler code: Dim ScrHeight As Double = Screen.PrimaryScreen.Bounds.Height 'you may want to set this to an Integer NOTE: In case you were not aware of it, adding or removing “System.Windows.Forms” in the above does not lengthen or shorten the final compiled code one bit. This framework mapping simply allows the compiler to zero in on target methods. Once a target is determined, the compiled code does not need to calculate the address of a method or class or enumerator each time it is accessed; it is already known, so when the preprocessing is finished and the actual output code is generated, absolute addressing has already been established. Page –30–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –31– 36.. Dealing with On Iexpr GOTO To handle upgrading “On Iexpr GOTO n1, n2, n3,...” one first cannot help but wonder why such code still existed 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 to the end of the block). 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):
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben This subroutine is simply the previous program flowchart translated to old DOS BASIC as much as possible. Back then, you could specify variables at a whim, never having to DIM them or define their type (the origin of Variants in VB), as we have emulated with Option Infer On. Lines beginning with “L” create labels for what would have been line numbers. Now you might appreciate why flowcharts were so popular back then, because the actual program code is convoluted and hard to follow without doing what I did back then, which was to take a pencil and draw dividing lines, as well as arrowed lines from the GOTO locations to their destinations. I have been told repeatedly that this routine cannot be structured because the GOTO instructions go in too many directions and places that cannot be logically blocked. But when I look at the flow chart, I am seeing loops and blocks and IF…ELSE segments of code. I first wrote the following structured translation long ago in FORTRAN, then in C++, then in QuickBASIC (predecessor to VB1), then in VB6, and finally in VB.NET. Following is a segment of a string sort, comparing the original with the current (some variables have already been declared by this time, which we will ignore for now): ' sort initialization Original Algorythm (1-Based) ' ---------------------------- NumberofItems = m_MyCount 'get number if items to sort (N=Number of Items) HalfDown = NumberofItems 'number of items to sort (M=N) ' ' perform the sort ' Do While CBool(HalfDown 2) 'while counter can be halved A: IF(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 Page –32– Loop Loop For completeness, following is my module to sort string arrays, ascending or descending: Module modSortStringArray 'Sort a String Array Alphabetically '******************************************************************************* ' modSortStringArray - Sort a string array in Ascending or Descending order using ' the Shell-Metzner Sort algorythm. This sort is extremely ' fast. Though longer than QuickSort, it sorts much faster ' with fewer replacments. 'EXAMPLE: ' Dim Test(3) As String 'or Dim Test() As String = {"Bob", "Zed", "Allen", "Rick"} ' Test(0) = "Bob" ' Test(1) = "Zed" ' Test(2) = "Allan" ' Test(3) = "Rick" ' If SortStringArray(Test) Then ' Dim S As String = "The array is sorted:" & vbCrLf ' For Index As Long = 0 To 3 ' S = S & " " & Test(Index) & vbCrLf ' Next Index ' MsgBox S ' Else ' MsgBox "The array was not sorted. The string is not an array" ' End If '******************************************************************************* Public Function SortStringArray(ByRef StrArray() As String, Optional ByVal SortDescending As Boolean = False) As Boolean ' ' get number of elements to do. Exit if this is not an array ' Dim NumberofItems As Integer Try NumberofItems = UBound(StrArray) + 1 'number of strings to do Catch Return False 'Array not dimensioned, so error End Try ' ' determine if we are sorting in Ascending or Descending order ' Dim AscDecFlag As Integer = 1 'default to ascending If SortDescending Then AscDecFlag = -1 'we will be doing descending End If '
  • 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 Page –33– Else IncIndex += 1 'else bump counter Exit Do End If Loop While IndexLo >= 0 'while more things to check Loop Loop End Function End Module NOTE: Most VB.NET lists and arrays already have a built-in sort method that employs the QuickSort algorythm that you can invoke by selecting Array.Sort(strArray), for example. The advantage here is that a sort method is already present and easy to access. However, the above Shell-Metzner sort is much faster than QuickSort, significantly so in large lists, and uses fewer replacements. The above version can also sort in descending order, if you wish it. For your amusement, here, though incomplete, is the guts of the QuickSort used by .NET: Friend Sub QuickSort(ByVal left As Integer, ByVal right As Integer) Do Dim low As Integer = left Dim hi As Integer = right Dim median As Integer = Array.GetMedian(low, hi) Me.SwapIfGreaterWithItems(low, median) Me.SwapIfGreaterWithItems(low, hi) Me.SwapIfGreaterWithItems(median, hi) Dim y As Object = Me.keys.GetValue(median) Do Try Do While (Me.comparer.Compare(Me.keys.GetValue(low), y) < 0) low += 1 Loop Do While (Me.comparer.Compare(y, Me.keys.GetValue(hi)) < 0) hi -= 1 Loop Catch exception1 As IndexOutOfRangeException Throw New ArgumentException(Environment.GetResourceString("Arg_BogusIComparer", _ New Object() {y, y.GetType.Name, Me.comparer})) Catch exception As Exception Throw New InvalidOperationException(Environment.GetResourceString("InvalidOperation_IComparerFailed"), exception) Catch obj1 As Object Throw New InvalidOperationException(Environment.GetResourceString("InvalidOperation_IComparerFailed")) End Try If (low > hi) Then Exit Do End If If (low < hi) Then Dim obj3 As Object = Me.keys.GetValue(low) Me.keys.SetValue(Me.keys.GetValue(hi), low) Me.keys.SetValue(obj3, hi) If (Not Me.items Is Nothing) Then Dim obj4 As Object = Me.items.GetValue(low) Me.items.SetValue(Me.items.GetValue(hi), low) Me.items.SetValue(obj4, hi) End If End If If (low <> &H7FFFFFFF) Then low += 1 End If If (hi <> -2147483648) Then hi -= 1 End If Loop While (low <= hi) If ((hi - left) <= (right - low)) Then If (left < hi) Then Me.QuickSort(left, hi) End If left = low Else If (low < right) Then Me.QuickSort(low, right) End If right = hi End If Loop While (left < right) End Sub
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –34– 37.. Dealing with On Iexpr GoSub To handle upgrading “On Iexpr GOSUB n1, n2, n3, ...” one once again cannot help but wonder why such code still existed in VB6 to begin with. The best resolution and fastest means of adapting this code to operate in VB.NET is to convert this to a Select…Case block, much as outlined in the previous point, but here it is more ideally suited to address the issue, breaking it down to this: Select Case IExpr Case n1 'Invoke subroutine associated with n1 Case n2 'Invoke subroutine associated with n2 Case n3 'Invoke subroutine associated with n3 ... End Select As you can see, invoking a selection of subroutines is a natural choice for a Select…Case block. When a subroutine returns, its control will not fall into the next Case block, but will go directly to the End Select, where program flow will continue. 38.. Dealing with updating VB6 error trapping Although VB.NET still supports “On Error Resume Next” and “On Error Goto 0” unstructured exception handling to support VB6-style error trapping, you really should consider upgrading it to the more controlled (encapsulated) Try…End Try structured exception handling statement. For example, consider the following VB6-style error trapped function as implemented (and still supported) in VB.NET: Private Function ReadFile(ByVal FilePath As String) As String() Dim fso As New FileSystemObject 'using COM object IWshRuntimeLibrary Dim ts As TextStream On Error Resume Next 'Reume on errors ts = fso.OpenTextFile(FilePath, IOMode.ForReading, False) 'open file If CBool(Err.Number) Then 'if error generated... MsgBox("Cannot open " & FilePath & ". It does not exist", _ MsgBoxStyle.OkOnly, _ "File Open Error") Return Nothing 'nothing for invoker to process End If On Error GoTo 0 'turn off error Trapping Dim TxtLines() As String = Split(ts.ReadAll(), vbCrLf) 'place each line in an array element ts.Close() 'close text stream Return TxtLines 'return array of text lines End Function The preceding can easily be adapted to the following Try…End Try block: Private Function ReadFile(ByVal FilePath As String) As String() Dim fso As New FileSystemObject 'use COM object IWshRuntimeLibrary. We should upgrade this to a StreamReader Dim ts As TextStream 'We really should upgrade all this to a faster System.IO.StreamReader Try 'try the following... ts = fso.OpenTextFile(FilePath, IOMode.ForReading, False) 'open file Catch ex As Exception 'cath errors (you can STILL check Err.Number) MsgBox("Cannot open " & FilePath & ". It does not exist", _ MsgBoxStyle.OkOnly, _ "File Open Error") Return Nothing 'nothing for invoker to process End Try 'end of error trapping Dim TxtLines() As String = Split(ts.ReadAll(), vbCrLf) 'place each line in an array element ts.Close() 'close text stream Return TxtLines 'return array of text lines End Function NOTES: You cannot mix VB6-style error trapping and Try…End Try error trapping within the same block of code (one type in one place, and the other in another place). Choose one or the other for the block. Also, the optional Finally block segment can precede the End Try statement, but following the last, or only Catch block, holding code that will follow Try and Catch, regardless of there being errors or not. There is a lot more to the Catch statement than meets the eye. You can also catch multiple exceptions by applying multiple Catch phrases, each Catch phrase encapsulating its own type of error. For example, by adding a ‘When’ clause to a Catch phrase we can narrow down error checks. 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:
  • 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", _ Page –35– 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 garbage collector can collect and destroy it (as of VB2010, the Garbage collector is no longer randomly starting, but runs continuously as a separate background task). 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 inevitable “however” part) However, if an object is set to Nothing and is then recreated within the same program code block, the first object may not yet be destroyed and a reference to the new object will 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 beast raising its ugly head in typical circumstances 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 its resources immediately, much 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.
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 40.. Dealing with changes to Common Dialogs Common Dialogs have changed significantly, and in my view, for the better. Still, you are going to find some interesting warnings that may at first confuse (or worse, panic) you. For example, on a form I have a CommonDialog control named CommonDialog1. With it I am going to open a text file and read it in. We will only look at the dialog interface for opening the file in the 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 Goben The next thing you should be aware of is that the single Flags property is replaced by individual Boolean properties; and each enumerator you had applied to it are now set on separate lines; the actual Flags property replaced by the fitting property allied with the control it is being used with. But if an applied flag is no longer recognized for that application of the control, then it is simply set to the now-non-existing Flag property, plus an error warning is issued for each one. If a property is still being assigned to Flags, it is a safe bet that you can just delete them. That clears another hurdle. You are next told that the properties CheckFileExists and CheckPathExists have been upgraded to a new name and a new behavior. The new names are obvious, but the only new behavior is that they are now individual Boolean properties and they are set to True or False instead of having an enumerated constant applied. You can ignore the warnings, so simply delete the warning comments. The next thing we look at is that the filter has a new behavior. This one originally had me confused, because the formatted text provide it is clearly the same as we used before in the VB6 code, so I simply ignore this warning and have yet to have trouble with it. By the way, in case you are new to dialog filters, we add filters in pairs, where the first half is purely descriptive, having nothing to do with actual filtering, such as “Text File (*.txt)”, and the second part is the actual filter pattern, such as “*.txt”. We combine these by separating them with a pipe “|” character, rendering “Text File (*.txt)|*.txt”. You can also select alternative selection filters, which the user can select from a dropdown list, by appending more Description | Filer pairs, separating them from the others with a pipe character as well. You can even combine multiple filters into a single choice, such as “Image Files (BMP, JPG, PNG)|*.bmp;*.jpg;*.png”. We separate the patterns in the filter pattern portion with a semicolon (the descriptive portion is as you choose, but a comma separator is traditional). The last warning we run into tells us that the CancelError properties is not supported in VB.NET. The way we deal with it in VB6 is pretty much as shown in our VB6 example. I use the On Error Resume Next, because when CancelError was set to True, if the user selected Cancel, a Cancel Exception Error was thrown. The On Error Resume Next prevented the program from aborting on this error, and then all we had to do is check if Err.Number was non-zero (under VB6, a non-zero value would be automatically cast to a Boolean, eliminating the need for Cbool(Err.Number), but that is actually a very bad habit to get into, and VB.NET especially does not like it with Option Strict turned on). We clear the error trap with On Error Goto 0. Although this type of error trapping is still accepted, one really should get into using VB.NET’s much superior Try…Catch…Finally format (I have been using it since VC++ in Visual Studio 6, and it is a treasure). However, we can completely eliminate the error trapping or setting the now unsupported CancelError flag. All we need to do is catch the returned value directly from the ShowDialog() function. If we were testing for multiple replies, such as checking for Abort, Cancel, Retry, Ignore, Yes, No, OK, or None, I might consider placing it in a Select…Case block, such as: Select Case .ShowDialog() Case Windows.Forms.DialogResult.Abort 'if we are doing this within a form, we can shorten this 'Do something here 'to DialogResult.Abort Case Windows.Forms.DialogResult.Retry 'can be shorted to DialogResult.Retry Page –37– 'Do another thing here Case Windows.Forms.DialogResult.Cancel 'can be shorted to DialogResult.Cancel Exit Sub End Select But since we are simply checking for a user Cancel, we can replace all this VB6 code: .CancelError = True On Error Resume Next .ShowDialog() If CBool(Err.Number) Then Exit Sub On Error GoTo 0 With this single VB.NET line of code: If .ShowDialog() = DialogResult.Cancel Then Exit Sub
  • 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 Page –38– End With 41.. Dealing with VB6.CopyArray One upgrade that was initially a little confusing to me was a ‘quiet’ one (no upgrade warning issued) that employs the VB6.CopyArray() helper function (from the Microsoft.VisualBasic.Compatibility namespace) that will convert an array of objects to a system array, happening when the VB6 code passes any array to another variable. This is because VB6 passed a copy of the array, whereas VB.NET passes a reference to the array, so the Clone() method is needed to actually copy the array. However, if you were to set Option Strict On, which I always do, then the return type from VB6.CopyArray() generates an error, because it returns type System.Array, so we must recast it. Suppose we had a Class named dynNodes that 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'”.
  • 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 Page –39– 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).
  • 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) Page –40– Me.New(ItemString, 0) End Sub '------------------------------- ' Provide a text data property to override the useless default in the Item() object. ' you can also use this to combine stored items when more than one text item is added. '------------------------------- Public Overrides Function ToString() As String Return Me.ItemString End Function End Class I would assign new text and indexes to my Listbox named ListBox1 using something like this: Me.ListBox1.Items.Add(New ListItem("David", 50159)) Extracting the text data can be done easily enough: Dim SName As String = Me.ListBox1.Items(Index).ToString ' you can also specify ItemString instead of ToString But extracting the index trades off with slightly more work, but it is still simple enough: Dim Idx As Integer = DirectCast(Me.ListBox1.Items(Index), ListItem).ItemData Because Items(Index) returns a generic object, we have to take the extra step of directly casting it to the type that we know it contains, which is a ListItem object. But if you have a lot of these to punch into the keyboard, this can result in a lot of typing. But even so, we can still employ less typing and much less code overhead than using VB6.GetItemData() and VB6.GetItemString() simply by writing our own little helper function, which we can add to a small module at the end of the class: Public Function ExtLI(ByRef Obj As Object) As ListItem If TypeOf Obj Is ListItem Then 'if the object is type ListItem Return DirectCast(Obj, ListItem) 'return the object as a ListItem End If Return Nothing 'else return a null object End Function Using the above helper function, you can now obtain the item data using the following: Dim Idx As Integer = ExtLI(Me.ListBox1.Items(Index)).ItemData And to get the item string, we can use either of the following two methods: Dim SName1 As String = Me.ListBox1.Items(Index).ToString 'Method 1 example Dim SName2 As String = ExtLI(Me.ListBox1.Items(Index)).ItemString 'Method 2 example 43.. Dealing with changes to Font manipulation One big change from VB6 to VB.NET is font manipulation. Under VB6 you could toggle Bold, Italic, Underline, and Strikethrough, and set Font Size and Name on the fly. Under VB.NET, these are set at development time, but setting them at runtime at first seems impossible. The only way to change fonts in VB.NET is to do it literally: change the font to a brand new font with different characteristics. This is actually what is done in VB6, but we 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.
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben When we upgrade a VB6 program to VB.NET, the VB6 Compatibility Library takes care of these processes for you. But you may actually be wondering how would you do it in a new program? It is surprisingly simple. All you have to really do is create a new font that can be based on the old one you are replacing, and then apply a new state to it. For example, suppose Label1 has a size of 10 points, and you want to simply set it to 12 points. You might do something like the following: Me.Label1.Font = New Font(Me.Label1.Font.Name, 12, Me.Label1.Font.Style) What we did was simply create a new font using the old font’s name, style, character set, as well as set it to using Points (default), and with a new size (12). Of course, you may not want to change it if it is already set to the desired size. We can accommodate this with a single function (be sure to also import the System.Drawing namespace in the heading of the file): '*************************************************************************************************************** ' FontChangeSize - Set/reset Selected Font Size ' CurrentFont = font reference to change ' NewSize = point size to set '*************************************************************************************************************** Public Function FontChangeSize(ByVal CurrentFont As Font, ByVal NewSize As Single) As Font If (Math.Round(CDbl(CurrentFont.SizeInPoints), 2) = Math.Round(CDbl(NewSize), 2)) Then Page –41– Return CurrentFont End If Return New Font(CurrentFont.Name, NewSize, CurrentFont.Style) End Function To use it, we simply issue the command “Me.Label1.Font = FontChangeSize(Me.Label1.Font, 12)”. NOTE: In case you did not notice it, we can borrow font settings from other controls that are similarly set, or already set to the desired format. We can even do this: “Me.Label1.Font = New Me.TextBox1.Font”. To change the fonts Bold, Italic, Underline, or Strikethrough state is even easier, due to other overloaded prototypes for Font. Suppose I wanted to set Label1 to have a Bold style, I might try this: Me.Label1.Font = New Font(Me.Label1.Font, FontStyle.Bold) 'base on an existing font, but alter style Or add more than one style simultaneously, like this: Me.Label1.Font = New Font(Me.Label1.Font, FontStyle.Bold Or FontStyle.Italic) 'apply bold + italic This works great, but toggling is a bit of a problem, especially if we want to turn on or off a single style option, but leaving others alone. The best approach is to test if a change is needed, and then if it is, to apply the appropriate state. We can do that with another simple function, like the one below: '**************************************************************************************************** ' FontChangeStyle - Support method for Bold, Italic, Underline, StrikeThrough set/reset '**************************************************************************************************** Private Function FontChangeStyle(ByVal CurrentFont As Font, _ ByVal StyleFlag As System.Drawing.FontStyle, _ ByVal SetStyle As Boolean) As Font 'mask desired style against current (do this in case multiple selections) Dim fntStyle As FontStyle = (CurrentFont.Style And StyleFlag) 'set flag to true if selected style is set, or if all selected styles are already set Dim flag As Boolean = (fntStyle = StyleFlag) 'if not EXACT match because something IS different, then FORCE change If Not flag AndAlso (fntStyle <> FontStyle.Regular) Then flag = Not SetStyle If (flag = SetStyle) Then 'if nothing will change, then simply return the current font Return CurrentFont End If 'define a new style value minus the current selection(s), based on current font style settings Dim newStyle As FontStyle = DirectCast(CurrentFont.Style And Not StyleFlag, FontStyle) If SetStyle Then 'are we setting the new style(s)? newStyle = (newStyle Or StyleFlag) 'yes, so set new style(s) to new font End If Return New Font(CurrentFont, newStyle) 'return new font based on current, w/selection set/reset End Function In the above function, we set the state of flag to True if the style we selected, such as FontStyle.Bold, is already set. We then check it against the SetStyle flag. If we want to set bold (SetStyle = True), but bold is already set (flag = True), then there is nothing to do and the old font is returned. If the state of SetFlag does not equal flag, then we have something to do. We first define a local variable that will contain the style flags without our selected style, in case we will be toggling it off (SetStyle = False). We then check the SetStyle value, and if it is set to True then we will apply the selected style to the flag value. Finally, we define a new font based on the old one, and apply the new style change to it. The great thing about this flag is that we can set multiple styles at once. Suppose we want to set both Bold and Italic styles to the label. We can do this by using this command “Me.Label1.Font =
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben FontChangeStyle(Me.Label1.Font, FontStyle.Bold Or FontStyle.Italic, True)”. To toggle a selected style off, we simply change the state of the SetStyle parameter to False. The last thing we might want to do is to change the actual font. Suppose we wanted to change the font of our label to Courier New. We could do this: Me.Label1.Font = New Font("Courier New", _ Me.Label1.Font.Size, _ Me.Label1.Font.Style) But this is a lot of work if we have to do a lot of it. We can instead create a simple little function that will do all the dirty work for us, such as this: '**************************************************************************************************** ' FontChangeName - Set/reset Selected Font Name ' CurrentFont = font reference to change ' NewName = new font family to change it to '**************************************************************************************************** Public Function FontChangeName(ByVal CurrentFont As Font, ByVal NewName As String) As Font Page –42– If (CurrentFont.Name = NewName) Then Return CurrentFont End If Return New Font(NewName, _ CurrentFont.SizeInPoints, _ CurrentFont.Style) End Function With this function, all we have to do to change the font to Courier New is execute this statement: Me.Label1.Font = FontChangeName(Me.Label1.Font, "Courier New") But suppose we want to set an entirely new font, or change more than one property, such as the font size and the style. We could write, as I have, a function to do all this, but in the end, probably the easiest method is to simply define a new font on the spot, just as had been demonstrated at the beginning of this point. For example, suppose I want to change the font of Label1 to Courier New, the point size to 12, and the style to Bold and Italic. All I would have to do is this: Me.Label1.Font = New Font("Courier New", 12, FontStyle.Bold Or FontStyle.Italic) Or, if we will be doing a lot of this, perhaps a full function is in order after all. Here is my complete module, which is based on the font support provided by the VB6 Compatibility Library: Imports System.Drawing Module modFontChanges '*************************************************************************************************************** ' FontChangeBold - Set/reset Selected Font Bold ' CurrentFont = font reference to change ' Bold = True:Set, else reset '*************************************************************************************************************** Public Function FontChangeBold(ByVal CurrentFont As Font, _ ByVal Bold As Boolean) As Font Return FontChangeStyle(CurrentFont, _ FontStyle.Bold, _ Bold) End Function '*************************************************************************************************************** ' FontChangeItalic - Set/reset Selected Font Italic ' CurrentFont = font reference to change ' Italic = True:Set, else reset '*************************************************************************************************************** Public Function FontChangeItalic(ByVal CurrentFont As Font, _ ByVal Italic As Boolean) As Font Return FontChangeStyle(CurrentFont, _ FontStyle.Italic, _ Italic) End Function '*************************************************************************************************************** ' FontChangeUnderline - Set/reset Selected Font Underline ' CurrentFont = font reference to change ' Underline = True:Set, else reset '*************************************************************************************************************** Public Function FontChangeUnderline(ByVal CurrentFont As Font, _ ByVal Underline As Boolean) As Font Return FontChangeStyle(CurrentFont, _ FontStyle.Underline, _ Underline) End Function
  • 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 Page –43– Return FontChangeStyle(CurrentFont, _ FontStyle.Strikeout, _ Strikeout) End Function '*************************************************************************************************************** ' FontChangeSize - Set/reset Selected Font Size ' CurrentFont = font reference to change ' NewSize = point size to set '*************************************************************************************************************** Public Function FontChangeSize(ByVal CurrentFont As Font, _ ByVal NewSize As Single) As Font If (Math.Round(CDbl(CurrentFont.SizeInPoints), 2) = Math.Round(CDbl(NewSize), 2)) Then Return CurrentFont End If Return New Font(CurrentFont.Name, _ NewSize, _ CurrentFont.Style) End Function '*************************************************************************************************************** ' FontChangeName - Set/reset Selected Font Name ' CurrentFont = font reference to change ' NewName = new font family to change it to '*************************************************************************************************************** Public Function FontChangeName(ByVal CurrentFont As Font, _ ByVal NewName As String) As Font If StrComp(CurrentFont.Name, NewName, CompareMethod.Text) = 0 Then Return CurrentFont End If Return New Font(NewName, CurrentFont.SizeInPoints, CurrentFont.Style) End Function '**************************************************************************************************** ' FontChangeStyle - Support method for Bold, Italic, Underline, StrikeThrough set/reset '**************************************************************************************************** Private Function FontChangeStyle(ByVal CurrentFont As Font, _ ByVal StyleFlag As System.Drawing.FontStyle, _ ByVal SetStyle As Boolean) As Font 'mask desired style against current (do this in case multiple selections) Dim fntStyle As FontStyle = (CurrentFont.Style And StyleFlag) 'set flag to true if selected style is (or all selected styles are) already set Dim flag As Boolean = (fntStyle = StyleFlag) 'if not EXACT match because but something IS different, then FORCE change If Not flag AndAlso (fntStyle <> FontStyle.Regular) Then flag = Not SetStyle If (flag = SetStyle) Then 'if nothing will change, then simply return the current font Return CurrentFont End If 'define a new style value minus the current selection(s), based on current font style settings Dim newStyle As FontStyle = DirectCast(CurrentFont.Style And Not StyleFlag, FontStyle) If SetStyle Then 'are we setting the new style(s)? newStyle = (newStyle Or StyleFlag) 'yes, so set new style(s) to new font End If Return New Font(CurrentFont, newStyle) 'return new font based on current, w/selection set/reset End Function '*************************************************************************************************************** ' ChangeFont - Support changing multiple properties '*************************************************************************************************************** Public Function ChangeFont(ByVal CurrentFont As Font, _ Optional ByVal NewName As String = vbNullString, _ Optional ByVal NewSize As Single = 0, _ Optional ByVal StyleFlag As System.Drawing.FontStyle = FontStyle.Regular, _ Optional ByVal SetStyle As Boolean = False) As Font Dim Changes As Boolean = False Dim flag As Boolean = False Dim Nam As String = CurrentFont.Name 'get current name Dim Siz As Single = CurrentFont.Size 'get current point size Dim Styl As FontStyle = CurrentFont.Style 'get current style flags Dim fntStyle As FontStyle = (Styl And StyleFlag) If CBool(Len(NewName)) Then 'if we will change the font family name... If StrComp(Nam, NewName, CompareMethod.Text) <> 0 Then 'if name actually changes Nam = NewName 'set new name Changes = True End If End If If NewSize <> 0 Then 'if we will change the point size, and diff. is big enough... If (Math.Round(CDbl(CurrentFont.SizeInPoints), 2) <> Math.Round(CDbl(NewSize), 2)) Then Siz = NewSize 'set new size Changes = True End If End If If StyleFlag <> FontStyle.Regular Then 'if we are setting a style flag 'set flag to true if selected style is (or all selected styles are) already set flag = (fntStyle = StyleFlag) 'if not EXACT match, but something IS different, then FORCE change If Not flag AndAlso (fntStyle <> FontStyle.Regular) Then flag = Not SetStyle If (flag <> SetStyle) Then 'if something will change...
  • 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 Page –44– End If Changes = True End If End If 'if there are changes, try returning new font If Changes Then Try Return New Font(Nam, _ Siz, _ Styl) Catch 'on error, fall through End Try End If Return CurrentFont 'return current if nothing to change, or error End Function End Module 44.. Dealing with changes to Form commands Some form commands have changed. You may notice that a few of the standard VB6 form processing commands you often used in VB6 no longer exist in VB.NET. Well, actually, they still do exist, but just in a form more open to your inspection than they were before, where most everything was hidden from you. These changes were not put in place to frustrate you; they were designed to be better tuned to a more focused and advanced RAD paradigm. Indeed, the whole reason code had been hidden from you before was to promote previous RAD principles. However, it has become quite clear that most VB developers are a fast, intelligent lot, and so the focus on maximum gizmo simplicity has changed focus to better sustain this hoard, bristling with creativity. When I first started using VB.NET, I felt a bit uncomfortable because some familiar VB6 form-processing commands seemed to be missing, such as Load, Unload, and the Query_Unload event. But as I researched them and experimented, I found that the way to handle these features was the way that the VB6 compiler had previously done it for us, but behind the scenes. Also, I noticed that events that I previously depended on were either not needed, or they fired in a different sequence. Under VB6, we used the I Love (or Loathe) RAP slogan to remember the order: Initialize, Load, Resize, Activate, and Paint. Under VB.NET, the Initialize process no longer fires an event (this is now taken care of when the Form New (constructor) method invokes the InitializeComponent method). The TextChanged and the Resize events first fire during that initialization process (which makes sense, because the 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).
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben The above VB6 code would be written in VB.NET like this: Dim Form2 As New Form2 'declare new instance of prototype form Form2.ShowDialog(Me) 'show new form, make current form its parent Form2.Close() 'close form, It will release resources if opened by the Show() method, but if you used ShowDialog()... Form2.Dispose() 'immediately deallocate form resources; add this if form is dialog or was instantiated but not displayed The Close() method is like VB6’s Unload. Although you probably could get by without invoking the Dispose() method, using only Close(), but if you know anything about any class that features a Dispose() method, then you would know that it is a very good idea to invoke it, because it causes all of its resource to be removed now, not later, when the randomly running garbage collector encounters it and figures out that it is not being used anymore. If you look at the documentation for the Close() method (search Help for Form.Close), it states “When a form is closed, all resources created within the object are closed and the form is disposed. You can prevent the closing of a form at run time by handling the Closing event and setting the Cancel property of the CancelEventArgs passed as a parameter to your event handler. If the form you are closing is the startup form of your application, your application ends. The two conditions when a form is not disposed on Close is when (1) it is part of a multiple-document interface (MDI) application, and the form is not visible; and (2) you have displayed the form using ShowDialog. In these cases, you will need to call Dispose manually to mark all of the form's controls for garbage collection.” Elsewhere, it states the point more directly: “Dispose will be called automatically if the form is shown using the Show method. If another method such as ShowDialog is used, or the form is never shown at all, you must call Dispose yourself within your application.” NOTE: If you invoke Dispose() before Close(), the FormClosing event will not fire. You can also invoke Dispose() from within the FormClosed event if you wish to (good idea). Further, you can invoke Dispose() after Close() even if it was not opened by ShowDialog(), though in this case it will do nothing, because the form had already been disposed. We can emulate the VB6 Show method easily, if we cannot live without it, using a simple module: Module modShowForm Page –45– Public Enum ShowFormConst As Integer Modal = 1 'as dialog box Modeless = 0 'as normal window End Enum '************************************************************************* ' ShowForm emulates the VB6 method of displaying a form, specifying a ' parent, and selecting a display mode (Modal or Modeless) '************************************************************************* Public Sub ShowForm(ByVal ThisFrm As Form, _ Optional ByVal Modal As ShowFormConst = ShowFormConst.Modeless, _ Optional ByVal OwnerFrm As Form = Nothing) ThisFrm.Owner = Nothing 'ensure no parent If (OwnerFrm IsNot Nothing) Then 'new owner exists ThisFrm.Owner = OwnerFrm 'set new owner End If If (Modal = ShowFormConst.Modeless) Then 'modeless? ThisFrm.Show() 'yes, so show normally (automatically invokes Dispose() method) Else ThisFrm.ShowDialog() 'else show as dialog ThisFrm.Dispose() '(we must invoke Dispose manually. It will not be invoked by ShowDialog because End If ' it may be returning a result to the invoker, which would be lost by Dispose) End Sub End Module To use it, we provide the method with our form, the optional modal setting (default is Modeless), and the optional parent window, in the form: “ShowForm(frm, FormShowConstants.Modal, Me)”. The weird thing about the Load command under VB6 was that it was not really necessary, unless you wanted to load a form without displaying it. This way you could load the form, initialize some control fields, and then display the form once it is set up. The documentation indicated that the Load command simply instantiated the form. So basically the less obvious VB6 “Load Form2” command is being used in place of the much clearer “Set Form2 = New Form2” command. This brings us to an issue I have with VB. During the Load event, we could initialize components, which is a handy place to do that sort of thing, but then we could also display it using the Show() method while we are still executing the Load event. That never settled well with me. It is still legal under VB.NET, sad to say. Everything must have an order to it. VB.NET should be more stringent in this situation. I just never saw the logic in the ILRAP/TRLAP sequences being able to be processed out of their defined orders. It makes sense to me in the new VB.NET TRLAP sequence that TextChange events fire before Resize because of the anchoring capabilities of VB.NET. A
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben dynamic label or textbox should reflect and resize its bounds before a form Resize event fires. And Activate and finally Paint, in perfectly logical order, should not fire until the Load process has finished doing its thing. The final thing to do is always painting. It is also in this last event that you should do any special shaping commands3, such as draw form-surface lines and circles and such. It makes much more sense to me if Microsoft had forced an LTRAP sequence, which would have resolved every sequencing frustration we have had with VB.NET form processing. But during the Load event, changes made to labels and fields can cause the TextChanged and Resize events to fire again, so that would frustrate an LTRAP sequence. So we are forced to dealing with these idiosyncrasies with a flag, such as using the Boolean mFormLoaded flag I outlined in Point 21. One event I once thought lacking in VB.NET was the VB6 QueryUnload event. This event had fired before the Unload event. I often used it to test if the user had chosen to close the form using either the window frame’s menu or had selected the “X” icon in the upper right corner of the frame, or they had pressed ALT-F4. I simply had to check the UnloadMode parameter for a value of vbFormControlMenu. If this test was true, then the user was blowing out. You could also check in a Multi-Document Interface form for a value of vbFormMDIForm to see if a child form was closing, or if the form owner was closing by testing for vbFormOwner. If I decided I did not want a form to close, I would set the Cancel parameter to a non-zero value (usually 1 or True). Finally, I took a much closer look at the FormClosing event (Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing). There was the answer in front of me. Apart from the “ByVal sender As Object” parameter, the second parameter, e, was of type FormClosingEventArgs. So in the body of the method I typed ‘e’ and then the dot. This offered me two properties: “Cancel”, a flag I could use to check or set if the form should close, and “CloseReason”, a read-only property that provides a value indicating to me why the form is being closed. These values are defined in the following enumeration within the System.Windows.Forms namespace: Public Enum CloseReason None 'the closing reason could not be defined WindowsShutDown 'the operating system is shutting down MdiFormClosing 'an MDI child form is closing UserClosing 'the user is closing the window TaskManagerClosing 'Windows Task Manager is closing the window FormOwnerClosing 'the form's owner is closing ApplicationExitCall 'Application.Exit() method was invoked End Enum Therefore, if I want to cancel the form closing because the user is trying to close the form (assuming that some data-critical application is running that must run to its end, otherwise data will be corrupted, for example), I might write my FormClosing event like this: Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing If e.CloseReason = CloseReason.UserClosing AndAlso CriticalProcessing = True Then e.Cancel = True Page –46– End Sub 45.. Dealing with VB6’s automatic Boolean conversions If you are one to use values in integer variables and fields as Boolean flags in conditional statements, such as using “If Idx Then” to test if the integer variable Idx contains a zero (False) or non-zero (True) value, if you have Option Strict turned On (which I highly recommend), you will need to properly cast them to Boolean, such as “If CBool(Idx) Then”. 3 VB.NET, prior to VB2010, lacked native shaping controls because VB.NET cannot work with windowless (lightweight) controls, but it can work around this using Microsoft’s free Visual Basic Power Packs (http://msdn.microsoft.com/en-us/ vbasic/bb735936.aspx), which deliver new controls for your IDE toolbox, featuring line and shape controls that can draw lines, rectangles and ovals at design time, eliminating the need to draw shapes manually in the form’s Paint event (this package comes installed as of VB2010). This pack also includes a printer control and collection, emulated printer I/O from VB6, a PrintForm component to allow you to print forms as you did in VB6, and a really neat data repeater that allows you to display rows of data in a scrollable container.
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 46.. Dealing with Option Strict On issues One thing that will crop up, not as an upgrade warning, but as a program error during compiling if you have Option Strict turned On (I always have this and Option Explicit turned On; more work for us during development, but worth its weight in video games if you want to have clear, concise code that is always early-bound, and therefore runs much faster), is that if you work with Short integers (former Integers in VB6) or Single precision floating point, you will often get compiler errors because Short values are being promoted to Integer, and Singles are promoted to Doubles. This is especially noticed when the receiving medium cannot accept these greater-precision values, such as method parameters that are declared as Short or Single. Consider these examples: Dim shValue As Short = 0 shValue += 1 'generates "Option Strict On disallows conversions from 'Integer' to 'Short'" Dim sngValue As Single = 0.0 sngValue += 1.0 'generates "Option Strict On disallows conversions from 'Double' to 'Single'" The reason the compiler errors occur is due to the fact that expressions cast their component parts to their equal or most senior member. If you let the mouse hover over any of the literal values, you will see why we run into problems. ‘0’ and ‘1’ are by default considered to be Integer values, and “0.0” and “1.0” are by default considered Double values. The values seem to work well enough during initial assignment, but they run into problems when they are in multi-type expression statements. We will always run into this problem when working with these smaller types because the default types in VB.NET are Integer for non-floating point values, and Double for floating point values. The way around this is simple. Just append the offending integer literals with “S”, which will cast them to type Short, and append “!” to the offending floating point literals to cast then to type Single. More, you should also do the same during the assignments, even though there is no error reported, because you are never really sure when the values are going to be demoted to the appropriate type. If they are demoted during run-time, then that requires extra computer cycles to convert those values to the proper type as a late-bound process. But even if the compiler automatically demoted them during compilation so that their conversion was early-bound, it still does not clear up the problem for someone reviewing your code and they see that the values are displayed in their promoted state. Here are some more Literal Type Characters appending flags, so that you can ensure literal values are what you would expect, or need them to be: Value Type VB6 Symbol VB.NET Symbol(s) VB.NET Example(s) Char Not Available c String(" "c, 128) Short Not Available S 123S Integer % % or I 123% or 123I Long & & or L 123& or 123L Single ! ! or F (Float) 123.45! or 123.45F Double # # or R (Real) 123.45# or 123.45R Decimal Not Available @ or D 123.45@ or 123.45D NOTE: It may seem redundant to use a ‘C’ tag because a single character of a string is a Char, it is actually an element in an array of 1 of type Char. For example, “Dim s As String = New String(" ", 128)” will generate the error “Option Strict On disallows conversions from 'String' to 'Char'.” Use instead Chr(32) or ChrW(32) or CChar(" ") or " "c. 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 Goben 47.. Dealing with Image and Picture Object upgrades Coping with not being able to copy a Picture property to an Image property is a big problem that has cornered most developers migrating over to VB.NET. Sometimes an upgrade will involve using VB6 or VB5 COM components right within the VB.NET upgraded application, such as VB5’s Common Control ComCtl32.dll, though the Upgrade Wizard will copy it locally and rename it axInterop.ComCtlLib.dll (a lot of us used this library in VB6 because it allowed us to display our windows using XP styles when we included a manifest file). There is nothing wrong with doing this and your application can even be stronger for it because additional resources, especially a huge reserve like all the COM-based code generated from Visual Studio 6, equals superior strength. However, I have also seen these controls used for importing image lists, and the problem with the VB5/6 ImageList control is that they only have a Picture property. That worked OK in VB5 and VB6, because a PictureBox control has both an Image and a Picture property, and so you could assign the Imagelist’s Picture property to the PictureBox’s matching Picture property like this: “Me.PictureBox1.Picture = Me.ImageList1.ListImages(4).Picture”. A VB.NET PictureBox control may have much more muscle, but it is also leaner, having only an Image property (fixing the persistence problem eliminated the need for two separate properties). And being that VB.NET does not support lightweight controls, the VB6 Image control went away, but it had also become mostly dead weight now that a PictureBox was able to serve both purposes. That is, except that an Image control could treat icon transparency colors as transparent. PictureBoxes cannot (well, directly, anyway). I think Microsoft 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 recall 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. Page –48– 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 your 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).
  • 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 and gnashed their teeth about the loss of control lists in VB.NET, such lists are sure easy to construct. 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: Page –49– Dim Image1(17) As PictureBox
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Alternatively, as of VB2008, you can instead use a strongly typed Generic List. Though I recommend using the above array, a Generic List might be handy when using a list of CheckBoxes. Besides, you can do a For…Each search through either implementation, when you might want to scan for those that are checked, or which RadioButton is selected. But, for me, working with arrays simply has less overhead. Here is how you would declare a Generic List, if you wish to use it instead: Dim Image1 As New System.Collections.Generic.List(Of PictureBox) 'A collection OF (Type) PictureBox In the Form_Load event, stuff the array with the controls (this sort of thing is usually done behind the scenes in the automatically generated code – to see such code yourself, be sure the “Show All Files” button is selected in the toolbar of the Solution Explorer and select the form’s file that ends in “.Designer.vb”). I cheat by entering the first complete line, then copying it and pasting copies, then editing their index value. This is how you would add them to the Image1() array of PictureBoxes: Image1(0) = _Image1_0 ' copy and paste, then edit sure makes this process a whole lot easier Image1(1) = _Image1_1 Image1(2) = _Image1_2 Page –50– ... Image1(17) = _Image1_17 Or, if you chose to use a strongly-typed Generic List, this is how you would add them to it: Image1.Add(_Image1_0) Image1.Add(_Image1_1) Image1.Add(_Image1_2) ... Image1.Add(_Image1_17) Unlike regular Collection objects that will return a generic Object and that you will have to cast to work with its result, Generic Collection objects are strongly typed and return the object type stored, just like using 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 loop Next MsgBox("You Selected " & RadioButtons(Index).Text) NOTE: All things considered, you may even want to instead consider placing the controls on a Panel, which is a borderless container class (hence runtime-invisible, unless you change its background color). You can loop through it with a For…Each, just like 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 as Image1_Click, that the images trigger), and after the command, add “Handles _Image1_0.MouseMove, _Image1_1. MouseMove, _Image1_2. MouseMove, etc.” Were we to actually do all this work, afterward this command line might 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.MouseMove If you do not want to do it this way (I don’t), which looks messy when it gets over a few handles, you can instead dynamically add handlers right after we filled the array/list using the command AddHandler (a runtime alternative to the Handles clause) to add the MouseMove, Click, or whatever event you want to process and assign the controls to the Address of the selected event method. So, if we were to strip the above line of all its trailing handlers (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 the following list, notice that we are taking each image defined, and notice further that by applying the AddHandler command to them, the controls expose event properties (technically Delegates), and we apply them to the address of the desired event method, such as the following:
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben AddHandler _Image1_0.MouseMove, AddressOf Image1_MouseMove 'Add the MouseMove event of our controls AddHandler _Image1_1.MouseMove, AddressOf Image1_MouseMove ' to the Image1_Mousemove list of AddHandler _Image1_2.MouseMove, AddressOf Image1_MouseMove ' supported events. ... AddHandler _Image1_17.MouseMove, AddressOf Image1_MouseMove '(NOTE: You can use RemoveHandler to remove, but not needed in most apps) Finally, at the beginning of the event code, if you upgraded, you will notice some now-dead (actually now error-marked) code that was used to get an Index of type Short (though you can use Integer if you wish) from the control (it was expressed as “Dim Index As Short = Image1.GetIndex(sender)”). I simply replace 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 all the heavy work for you. Consider the following function: '******************************************************************************* ' Subroutine Name : GetTagIndex ' Purpose : Return Index based on Tag value '******************************************************************************* Public Function GetTagIndex(ByVal eventSender As System.Object) As Short Page –51– If TypeOf eventSender Is Label Then Return CShort(DirectCast(eventSender, Label).Tag) 'Handle labels End If If TypeOf eventSender Is PictureBox Then Return CShort(DirectCast(eventSender, PictureBox).Tag) 'handle pictureboxes End If Return -1S 'flag error End Function With the above function, you need only enter the following line in an event to get its index like this: Dim Index As Short = GetTagIndex(sender) If you are using the Tag property of the controls for something else, which should be no big surprise, you might want to instead write a function that will accept the sender parameter and return an Index based on the naming of the control (I use the upgrade’s underscore and index naming method as a useful personal standard in my own VB.NET projects). Consider this function, which will also thankfully relieve you of the task of editing all the individual Tag properties of all the controls: Module modGetNameIndex '******************************************************************************* ' Subroutine Name : GetNameIndex ' Purpose : Return index based on Name (assume index value trails name after underscore) ' : Add as many type checks as you need. Example: Label1_13 for ID=13 '******************************************************************************* Public Function GetNameIndex(ByVal eventSender As System.Object) As Integer Dim Nam As String = vbNullString 'assign initial data so VB.NET will not accuse us of processing an uninitialized variable Select Case eventSender.GetType.Name 'Get the Object control type name Case "Label" Nam = DirectCast(eventSender, Label).Name 'Handle Labels, Get name Case "PictureBox" Nam = DirectCast(eventSender, PictureBox).Name 'Handle PictureBoxes. Get name Case "Button" Nam = DirectCast(eventSender, Button).Name 'Handle Button. Get name 'If you need them, you can add more Case tests here... End Select '--------------------------------------------------------------------------------------- If CBool(Len(Nam)) Then 'something to process? Dim I As Integer = InStrRev(Nam, "_") 'find trailing underscore (1-based index) If CBool(I) Then 'found one? Non-zero means Yes Return CInt(Nam.Substring(I)) 'yes, return trailing value (this works due to 0-based Substring method) End If '(for Substring, the index value is 1 higher than Instr's 1-based index) End If Return -1 'return a flag to indicate Unsupported Type Encountered End Function End Module And that is all there is to it. This simple technique will solve most old or new control array issues without a ton of extra overhead, and the most tedious part of it being the populating of Handles statements. But this type of thing was even done under VB6, though it was hidden from you.
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 49.. Dealing with changes to MouseMove parameter list changes You may discover that when using the VB.NET MouseMove event, several VB6 parameters are often assumed “missing”, which told you the button pressed, if any special keys were pressed (Shift, Control, Alt), and the coordinates of the cursor. However, if you were to look at an upgraded MouseMove event from VB6, you will see that the upgrade will provide you with the information you need to reacquire those “lost” parameters, even if you do not need them. Consider the following VB6 event heading: '******************************************************************************* ' Subroutine Name : Image1_MouseMove ' Purpose : Mouse is moving over a point. Display it's usability graphically '******************************************************************************* Private Sub Image1_MouseMove(ByVal Index As Integer, ByVal Button As Integer, _ ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) After it is upgraded to VB.NET, it looks like this: '******************************************************************************* ' Subroutine Name : Image1_MouseMove ' Purpose : Mouse is moving over a point. Display it's usability graphically '******************************************************************************* Private Sub Image1_MouseMove(ByVal eventSender As System.Object, _ ByVal eventArgs As System.Windows.Forms.MouseEventArgs) Handles Image1.MouseMove Dim Button As Short = eventArgs.Button &H100000 Dim Shift As Short = System.Windows.Forms.Control.ModifierKeys &H10000 Dim X As Single = eventArgs.X Dim Y As Single = eventArgs.Y Dim Index As Short = Image1.GetIndex(eventSender) NOTE: The backslash ‘’ performs an integer division, tossing away any remainder, so no Float results. Mind you, this is convenient, but most-times we will not 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 click 51.. Dealing with no AVI animation control in VB.NET and how to easily add a free one Many users have complained that VB.NET does not have an animation control to play AVI files. Although several online solutions exist, they normally require running the application with administrator permissions because they involve adding OCX interfaces. However, there is an even easier way to do this, and all without permission issues, or even running the AVI as a separate process. If you 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 all). First, if you have tried to install VB6 on a Windows Vista or later operating systems, you will have been told that there are known compatibility issues with this application. However, these issues are easy to surmount. If you have not done so already, and you need it, go ahead and install VB6. It will report incompatibilities, but just ignore them for now, and even tell the operating system, if it asked you, that it installed OK. The trick to getting past the incompatibilities issue is to right-click the installed VB6.EXE application, or any shortcut to it, select Properties, select the Compatibility tab, and then place a checkmark in the “Run this program as an administrator” checkbox. Afterward, any time you launch Page –52–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben VB6, you will have to hit the “Allow” option in a User Account Control dialog, but this is a small price for 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 for Visual 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 not install 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 should also 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 may still inform you that it might not have installed correctly, even though it actually did (this alert is in fact due to a problem it has trying to register OLEAUT32.DLL, which is already installed and registered), so select the “This program installed correctly” option if this message prompt comes up. If you are unsure about this, install it in Safe Mode (Select Start / Run (or -R), then enter msconfig, select the Boot tab, and select Safe Boot). You will have to undo this in Safe Mode to reboot normally. However, it will still report an error, but this time it will clearly inform you that it could not register OLEAUT32.DLL (it is because of heightened system protections that have been added that prevent this DLL file from being tampered with, and especially by an older version of it, which the VB6 version most certainly is). What is important for our main point, which is installing an animation control, is that the COM support library that provides this VB6 control is now loaded onto your computer and registered. But once it is loaded into your system, you are now able to easily add an animation control to your VB.NET Toolbox. To add the VB6 animation control to your Toolbox, and a new toolbox tab to contain it, do the following: 1)) With any, or a new VB.NET Window Application form up in the IDE, select the Toolbox tab. 2)) Right-click one of the Toolbox tabs and choose “Add Tab”. A new Tab will be added to the Toolbox, and it will wait for you to name it (this might not at first seem apparent, but notice a new blank tab with a cursor blinking in its heading). Name it “COM”, or to something of your own personal preference. And lock in the change by hitting Enter. 3)) Next, right-click the new COM Tab and select “Choose Items…” from the popup menu. After a rather long pause to gather its massive reserve of available system resources, a “Choose Toolbox Items” dialog window appears. 4)) Click the “COM Components” tab on the dialog window (there will be another delay to format its list). 5)) Locate “Microsoft Animation Control 6.0 (SP6)” in the displayed list and place a check in its checkbox. 6)) Select OK to apply the selection and close the dialog window. Notice that you now have an animation control in your new COM Toolbox Tab, which you can use just as you had done with VB6. Notice further that when you place an animation control on a form, the properties for the control report that it is of type AxMSComCtl2.AxAnimation. (Ax represents ActiveX) You will also notice that once you add an animation control to a form, that two new references are added to your project properties, both for “Microsoft Windows Common Controls-2 6.0 (SP6)”; one of them for Interop.MSComCtl2.dll, and another for AxInterop.MSComCtl2.dll, which are required to support the animation control. Notice further that their “Copy Local” properties are both set to True, meaning that these will be, and must be loaded into your program directory, being non-registration versions of the ActiveX MSComCtl2 registered controls (the system will copy them automatically for you). Developing this solution was like a mission for me. I first figured out how a VB6 to VB.NET upgrade did it, and then figured from that code-only solution that there had to be a way to add such a control to my toolbox, rather than instantiate everything programmically, which the upgrade code did. It may be simple enough to do it programmically, but it is not something that is going to be sitting at the top of my head every time I need to use it. On the other hand, a toolbox control is easy to find and even easier to apply to a form than it is to go about writing a bunch of instantiation and form-placement code (I say, let the background form designer do all of that programming for me). My only real question to Microsoft is why they could not have added a native animation control to VB.NET long before now? I would have though it essential, even for the release of VB2002. However, considering that AVI animation is a COM process, I think they hesitated to add it to .NET. Page –53–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 52.. Dealing with changes to Resources Management Hoards of VB6 users have grumbled loudly about the resource storage format used under .NET, though those complaints are mainly for the fact that .NET resources are stored differently than they were for VB6 (Win32). As a counterpoint, I firmly believe that they are stored better. What is more, two very annoying things have also been totally eliminated under .NET that drove me to frothing distraction under VB6: 1) With .NET, you no longer need to separately compile the resources (or figure out what to type into the resource compiler source file) as you had to do under VB6, and 2) .NET resources can be used immediately, even during development, such as sound, which did not work from the IDE at all under VB6. Just load and go. You can also do a whole lot more with them, particularly if you write multi-cultural apps, especially with the ResourceManager namespace. Audio Resources Probably the biggest complaint I hear is that embedded resources are supposedly more difficult to access under VB.NET. But from how I see it, I know that this process is a whole lot easier, though I think it really has something to do with their belief that they are only able to play audio files through the PlaySound() P/Invoke in WINMM.DLL, which cannot access the .NET-embedded resources (though its File I/O part will still work OK). My advice to those people is simple – abandon the PlaySound() P/Invoke entirely! Instead, use .NET’s My.Computer.Audio class methods, which are much easier to use than a P/Invoke. And, considering how easy it is to now add and access those embedded resources, I am glad for it, especially because I can now play those resources, extract them, check them, check for their existence, etc., all without giving them a great deal of thought. For example, what follows is typical VB6 P/Invoke code I had written for playing sounds: Private Declare Function PlaySound Lib "winmm.dll" Alias "PlaySoundA" (ByVal lpszName As String, _ Page –54– 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
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben This was a tried and true module that served me well for about a decade, both in VB6 and in the C++ (with my C++ version of it). However, with the introduction of the .NET Framework, the choice of options available to us has expanded substantially, rendering API invocations totally unnecessary. In VB.NET, from the My.Computer namespace, we now have a LOT of computer control only as far away as our fingertips. From the My.Computer.Audio namespace, we can easily play WAV files, stop them, or issue system sounds (the message beeps, such as alerts, warnings, etc.). From My.Resources, we have full access to all of our embedded resources. From My.Resources.ResourceManager, we can poke and prod and check our resource data to no end. One loud complaint I have heard regarding this is that many programmers cannot seem to use a string name to get access to an audio resource, so they have resorted to using a Select block to check a text name against a list, then play the associated embedded WAV resource. Well, I can understand that one. The first day I started playing with VB.NET resources, I thought the same. But being a software engineer, I knew that it could not be implemented so poorly. With all that raw power at my fingertips, Microsoft was going to just settle on some weak solution like that? So I took a look at the resource manager and between experiments (and stopping to actually read documentation), I quickly figured it all out. By simply paying attention to the popup tooltips, I learned that by providing the resource manager’s GetObject method with the text name of the desired resource, it would return an object that I could cast into the appropriate type (byte array, stream, or string), which comprises my audio data (byte array if from VB6, or stream if VB.NET-added). I could then take advantage of the Play method in My.Computer.Audio and play that resource, or from a file path, with equal ease. Following is my new VB.NET version of the previous VB6 PlayWav routines: '******************************************************************************* ' PlayWavFile(): VB.NET Play sound from a file '******************************************************************************* Public Function PlayWavFile(ByVal FileName As String, _ Optional ByRef PlayAsync As Boolean = False, _ Optional ByRef PlayLoop As Boolean = False, _ Optional ByRef NoWait As Boolean = False) As Boolean 'see if the audio file exists If Not System.IO.File.Exists(FileName) Then Return False 'nope; error 'process our flags Dim flags As AudioPlayMode = AudioPlayMode.Background If Not NoWait Then flags = AudioPlayMode.WaitToComplete If PlayLoop Then flags = AudioPlayMode.BackgroundLoop My.Computer.Audio.Play(FileName, flags) 'play audio file Return True 'success Page –55– End Function '******************************************************************************* ' PlayWavResource(): VB.NET Play sound from a resource file '******************************************************************************* Public Function PlayWavResource(ByVal SoundName As String, _ Optional ByRef PlayAsync As Boolean = False, _ Optional ByRef PlayLoop As Boolean = False, _ Optional ByRef NoWait As Boolean = False) As Boolean 'get data for sound from resource Dim obj As Object = My.Resources.ResourceManager.GetObject(SoundName) If obj Is Nothing Then Return False 'bad name, so return error 'process our flags Dim flags As AudioPlayMode = AudioPlayMode.Background If Not NoWait Then flags = AudioPlayMode.WaitToComplete If PlayLoop Then flags = AudioPlayMode.BackgroundLoop 'process file If TypeOf obj Is System.Array Then 'byte array (usually upgraded VB6 files) My.Computer.Audio.Play(DirectCast(obj, Byte()), flags) Return True 'all is good, so return success End If If TypeOf obj Is System.IO.Stream Then 'typical VB.NET resource file My.Computer.Audio.Play(DirectCast(obj, System.IO.Stream), flags) Return True 'all is good, so return success End If If TypeOf obj Is String Then 'typical external wave file Return PlayWavFile(DirectCast(obj, String), PlayAsync, PlayLoop, NoWait) End If Return False End Function '******************************************************************************* ' PlayWavStop(): VB.NET stop playing any sounds that might be playing '******************************************************************************* Public Sub PlayWavStop() My.Computer.Audio.Stop() End Sub
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Now this is definitely easy. True, I was initially disappointed that the PlaySound() P/Invoke no longer worked with .NET resource files, but what I got in trade has rendered the PlaySound() P/Invoke moot. AVI Resources AVI files are a source of frustration with VB6 users and VB.NET users alike. In VB6, it was a bit of a bother. To store the file FileCopy.avi in a VB6 resource file you had to: 1)) Add an entry, such as “101 CUSTOM "FileCopy.avi"” to a resource text file, such as myResources.rc. 2)) Compile the resource from a DOS prompt, using something like “rc myResources”. 3)) Finally, add the resulting myResources.RES file via Projects / Add File to your VB6 application. Why many VB6 users claim that this resource-adding hack is easier than adding resources under VB.NET can only be due to insanity. To add such a resource under VB.NET, all I need to do is go to Project / Properties, select the Resources tab, set the resource type to Files, hit the Add Resource button, and navigate the browser to and select my FileCopy.avi file, and it is added to my resources. Close the project properties and it is automatically compiled and associated to my application. Yet reading and playing the AVI resource file can be frustrating, because I have heard all sorts of tales of woe about how someone cannot figure out how to play it, or how they cannot convert the resource to a string so they can use a StreamWriter to save it as a file, and various variations of this dilemma. Unlike Audio files, which can be played directly from the resources, an AVI must currently be played from a file (actually, there is a way to play AVI files from a VB6 resource file by using something like “Lresult = SendMessage(Animation1.hwnd, ACM_OPEN, ByVal App.hInstance, ByVal aviResourceID)”, where ACM_OPEN is declared as WM_USER (&H400) + 100&). I was hoping that the new .NET architecture would enable the Animation control to read byte arrays (byte arrays exhibiting read/write/seek features, which is a byte array wrapped within a Stream interface) or streams directly, but sadly has yet to happen. Therefore, our first task is to copy the resource data to a file. I will assume that we have the FileCopy.avi file loaded in our resources. When it is added, it will automatically be saved in the resources by its file-defined name, such as FileCopy (you can alter this by renaming the resource). I like to play it as a file from wherever the executable program is running from, so I will do that, though you may require a different non-locked local resource, such as a temp folder on the user’s system if the application runs from a CD or DVD or some other similar write-protected source. Also, the properties box reports that FileCopy has a type of System.Byte[], which is saying that it is a Byte Array (C uses square braces to denote arrays). As such, instead of trying to convert the resource to a string, I will treat it as a byte array and use a FileStream object so that I can write a binary file. With VB6, all we had to do was issue the command “LoadResource 101, AviFile”, where AviFile is a string specifying the file 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 file Dim AviFile As String = Application.StartupPath & "FILECOPY.avi" 'first see if it already exists. If so, then we will not have to create it... 'if the AVI file does not already exist... If Not System.IO.File.Exists(AviFile) Then 'set up our FileStream object to create it Dim fs As New System.IO.FileStream(AviFile, System.IO.FileMode.Create) 'write byte array resource directly from resource to file from its beginning (0) for its length fs.Write(My.Resources.FILECOPY, 0, My.Resources.FILECOPY.Length) 'close the new local AVI file copy of the resource fs.Close()'this automatically invokes fs.Dispose(True) to release all used resources Page –56– End If The only thing left for us to do is to play it in our Animation control: With Me.Animation1 'start animation... .AutoPlay = True 'turn animation on .Open(AviFile) 'load the animation file .Visible = True 'show the animation AVI Application.DoEvents() 'let display catch up ' ' ... Do application processes, then finally... ' .Close() 'close AVI animation .AutoPlay = False 'turn off AVI animation .Visible = False 'and hide it End With
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben A final thing you may want to do when you are leaving your application is to perhaps delete the AVI file, although keeping it present will mean future runs will not have to start off saving another copy to a file, but, because I like to clean house afterward, I normally do this in my application exit code: System.IO.File.Delete(AviFile) 'delete the avi file resouce Other standard resources you can store are Icons, Images, and String. Other than the auxiliary Files, which I prefer to use, you may like to use Other, which is much the same as Files, but it is a way to separately catalogue your various data. You can even set the access modifiers of your resources as Public or Friend (default). 53.. Dealing with the loss of the App statement The App statement in VB6 is completely missing in VB.NET, and so we are left with extracting its many useful application-related values by other, sometime more complicated means. However, we can instantiate a globally-accessible class and obtain this information just as simply as we did under VB6. By instantiating the AppInfo Class using “Public App As New AppInfo”, we can, for example, get the running application’s version number exactly the same way we did under VB6, using: Dim Version As String = CStr(App.Major) & "." & CStr(App.Minor) & "." & CStr(App.Revision) Following is my AppInfo class, featuring most of the old App statement’s many useful features: Imports System.Reflection.Assembly, System.IO.Path, System.Diagnostics, System.Diagnostics.Process, System.Runtime.InteropServices '****************************************************************************** ' Class AppInfo (App) ' Used to create App class instance to access application information ' Declare this class globally using "Public App As New AppInfo" ' ' These function emulate the VB6 App command. For example, to get the current app's title, ' Dim S As String = App.Title '****************************************************************************** Public Class AppInfo Public Function FullPath() As String ' Support function Page –57– 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
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Page –58– Public Function PrevInstance() As Boolean Return (UBound(GetProcessesByName(GetCurrentProcess.ProcessName)) <> 0) End Function Public Function Instance() As Integer Return Marshal.GetHINSTANCE(GetCallingAssembly.GetModules()(0)).ToInt32 End Function End Class 54.. Dealing with updating default ByRef and ByVal method parameters flags Be sure to check your ByRef and ByVal references in your method parameter lists. When a program is updated from VB6 to VB.NET, parameter references that are not explicitly stated as flagged as ByRef or ByVal will default to ByRef, simply because VB6 had defaulted to ByRef (a policy I had always disagreed with, but understand due to its evolution from FORTRAN). But, most references should be tagged as ByVal, unless they are passing objects that will be modified. ByVal makes data safer, and so if 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.
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben The second step is to address the Build issue. One at a time, click on a now-included file and be sure the Build Action property is set to Content, meaning that the file will be part of the project’s content. You may notice the build actions for other files. Files that are already embedded in the final executable, such as resource objects, normally have a build action of None. Methods, classes, and forms have build actions of Compile. You will also want to check the “Copy to Output Directory” property while you are here. If it reports “Do not copy”, then it will not be sent to the destination folder. You may want to change this to “Copy always” or “Copy if newer” (copy if it is newer than an existing file of that name, or copy it if it does not yet exist). Another thing you may want to do is to determine how it should be supplied. If you go to the project properties, and select the PUBLISH tab, you will notice a button named “Application Files”. Select it and a list of application files that will be included in the publication will be listed. Depending on how you set the Target Framework version under the Advanced Compile Options (see the Compile tab in the Project Properties, under the Advanced Compile Options button), you may want to also check the Prerequisites list. For example, if you compiled your application to use .NET Framework 3.0, you may want to be sure that the person installing your application has the ability to load .NET Framework 3.0 if it is not already installed. In the Prerequisites list (see the Prerequisites button on the Publish tab). You can have the prerequisites loaded from the vendor website (Microsoft), or from the same location as your application (the prerequisites will be bundled right within your published installation), or you can specify the URL of a web address where they can be downloaded. If you are publishing your application for web distribution, it is usually easier to have prerequisites downloaded from the vendor. If you are building a CD/DVD installation, it is probably best to provide them yourself. Finally, once you have gone through the Publishing Wizard at least once and made your few choices (where to publish to, how the app will be installed, if updates can/should be checked, and from where), you can usually process subsequent builds by simply choosing the Publisher’s Finish button. 57.. Dealing with changes to Text Box SelStart and SelLen Properties This is a very minor change. The VB6 SelStart and SelLen properties of TextBox and RichTextBox controls has become SelectionStart and SelectionLength under VB.NET. Page –59–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 58.. Dealing with changes to ToolTips Tooltips have changed between VB6 and VB.NET. The VB6 ToolTipText property is no longer supported as it was. It is now handled by a ToolTip control. The purpose of the control is to help speed the execution of code. Having it built into each control ate a lot of time and resources. Fortunately, it translates very well during an automatic upgrade from VB6 to VB.NET. By simply dropping a ToolTip control onto a VB.NET form, tool tips are suddenly enabled for the other controls on the form, exposing a ToolTip property on each. For example, if you drop a fresh ToolTip control onto a form, it is named Tooltip1 by default, and the property is exposed on each of the form’s controls (through a process called Reflection) as “Tooltip on Tooltip1”. You can pre-fill the text of these tips at design time through the property interface just as you had done it in VB6. However, you now have a much more powerful control. You can even drop more than one ToolTip control on a form, which in turn exposes more than one ToolTip property. This may seem like a silly thing to do, but due to your now being able to customize the presentation of your tooltips, you can assign one set and style of ToolTips to certain controls, and assign another set and style to others. These customizations feature the ability to enable a balloon-type format, changing foreground and background colors, modify display timing, to include being always on, setting titles for the ToolTip windows, create owner-drawn ToolTips (user-drawing enabled through the ToolTip 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 its Clear method (in VB.NET, this would have to be upgraded to a RemoveAt(0) statement). Further, the Item property of the Items list (previously ListItems in VB6) is no longer a simple String, but rather an Object. Page –60–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 60.. Dealing with changes to Toolbar Button and Button Menu Clicks When a Toolbar program is upgraded from VB6 to VB.NET, a couple things happen. First, both normal and special dropdown button list click events must be handled differently than under VB6. Second, ToolBars, though still supported by VB.NET for reasons of backward compatibility, are automatically converted during an upgrade to the newer, more capable ToolStrip controls. These issues are quite easy to adapt to and resolve programmically, but you really should understand what is going on by examining the differences between the aging ToolBar and the newer ToolStrip, which are both featured in VB6 and VB.NET. Also, even though continuing to implement the old ToolBars might seem to be a more attractive choice from the perspective of a person upgrading from VB6 to VB.NET, you cannot just drop a ToolBar control onto a form from a fresh VB.NET install. The VB.NET Toolbox initially offers only ToolStrip controls. Even so, you are able to add a ToolBar control to the Toolbox, if you cannot seem to live without it, through its Choose Items option (right-click a desired Toolbox group header to access this option, and then in the dialog box select the Toolbar entry declared in the .NET tab’s Global Assemble Cache directory). When a normal button is clicked on a ToolBar under VB6, its ButtonClick event will provide you with a Button parameter that represents the clicked button on the ToolBar. If your processing code will identify these buttons by the key you assigned to it during development, you can acquire the Button object’s key through its Key property, or, if you process them by their assigned toolbar index number (beginning from 1), you should instead use the Button object’s Index property, or finally, if you process them by assigned text, you should use the Button’s Caption property. For example: Private Sub Toolbar1_ButtonClick(ByVal Button As MSComctlLib.Button) Select Case Button.Key 'Process by Key (use Button.Index for the button's 1-based index) Case "Exit" 'if Exit button, then simply unload the main form Page –61– Unload(Me) ... End Select End Sub When developing code for VB.NET’s ToolStrip buttons, this process is different. However, it is still very easy to use. By default, VB.NET generates a separate click event for each button. For example: Private Sub ToolStrip1_tbbExit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ToolStrip1_tbbExit.Click Me.Close() 'if Exit button, then simply unload the form (replaces VB6 Unload(Me)) Me.Dispose() 'also do this in case the form was opened as a Dialog or is an MdiChild and was not open End Sub However, you can consolidate all button code by creating event code named something like ToolStrip1_ButtonClick and adding a list of button event handlers at its end. Additionally, be sure to also define a Button object of type ToolStripItem in order to access each selected button, like so: Private Sub ToolStrip1_ButtonClick(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles ToolStrip1_tsiOpen.Click, ToolStrip1_tsiClose.Click, _ ToolStrip1_tsiOptions.Click, ToolStrip1_tsiExit.Click 'Define Button as a ToolStripItem object (DirectCast is faster than CType() -- no actual conversion taking place) Dim Button As ToolStripItem = DirectCast(sender, ToolStripItem) Select Case Button.Name 'Process by the button's Name (no Key available. Use Text if the 'ImageStyle does not display the Text field, otherwise try the Tag field) Case "tsiExit" 'if Exit button, then simply unload the main form Me.Close() 'Close the form Me.Dispose() 'also do this in case the form was opened as a Dialog or is an MdiChild ... End Select End Sub The above example generally covers how a VB6 to VB.NET upgrade also handles it, excepting that the original upgraded code will retain the old VB6 “Unload(Me)” command and mark it as unsupported and request editing (see Point 61 for an explanation). I simply replace it with “Me.Close()”, and then add “Me.Dispose()” if the form was handled as a dialog (though adding it even if it was not does no harm).
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben NOTE: During an upgrade from VB6, the upgrade automatically generates the code to declare the Button object. However, it will replace the original “Button.Key” with “Button.Name”, even though the VB6 button Name and Key values were seldom, if ever identical. This can be a potential problem because it will not provide an upgrade note informing you of this. So be aware that this will happen. I like to take the time to assign the upgraded button’s Tag property to reflect the old VB6 Key property, or, as I have done in the above snippet, simply update the test for the button name. Be aware that you could have also originally tested against the button’s Name property under VB6, in which case the code would have upgraded more smoothly. However, when developing the code under VB.NET, you can alternatively collectively process these clicks through the ToolStrip itself in a single subroutine, and from its click event you can extract the button information. Remember also that VB.NET button indexes begin at 0, not 1. For example: Private Sub ToolStrip1_ItemClicked(ByVal sender As Object, ByVal e As ToolStripItemClickedEventArgs) Handles ToolStrip1.ItemClicked 'The following is how you would get the clicked Button's Name property from this ToolStrip event: Dim ButtonName As String = e.ClickedItem.Name 'notice the type assigned to 'e', above. 'The following is how you would get the Button's definition index (from zero): Select Case ToolStrip1.Items.IndexOf(e.ClickedItem) + 1 'optionally add 1 (compensate 0 option base if 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 Page –62– ... End Select End Sub Button Menus (VB6 ToolBar Button style 5; tbrDropdown) are menus that drop down from toolbar buttons and offer a list of items to select from (you will notice a dropdown indicator on the right side of the button (▼). Under VB6, such selections could be handled through the ButtonMenuClick event, and you could acquire the index of the item in the button’s dropdown list from the menu button’s Index property, regardless of whether you predefined the dropdown button list or dynamically added them within the application code. Consider the following VB6 example: Private Sub Toolbar1_ButtonMenuClick(ByVal ButtonMenu As MSComctlLib.ButtonMenu) Call ShowListItem(ButtonMenu.Index) 'process according to the index value of the button End Sub But when you process a pre-defined dropdown button in VB.NET from a ToolStrip (style ToolStripDropDownButton), event handling takes two different approaches. First, it can provide an individual event code block for each dropdown button you care to declare code for, like this: Private Sub Option0ToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles Option0ToolStripMenuItem.Click 'Handle code for predefined dropdown button with displayed text "Option 0" End Sub Of course, the above is only practical when you pre-define dropdown button values in VB.NET. If you add them dynamically by adding entries to a DropDown button, we named, say, tsddbOptions, through its DropDownItems collection, such as we might have done in the following example: With Me.tsddbOptions.DropDownItems 'Add options to ToolStrip Drop-Down Button tsddbOptions .Add("Option 1") .Add("Option 2") .Add("Option 3") .Add("Option 4") End With You could handle selections to those dynamically added buttons, as well as through any that had already been predefined at development time through the dropdown button’s DropDownItemClicked event, much like the following: Private Sub tsddbOptions_DropDownItemClicked(ByVal sender As Object, ByVal e As ToolStripItemClickedEventArgs) _ Handles tsddbOptions.DropDownItemClicked 'The following is how you would acquire the dropdown Button's Text property (in lieu of a VB6 Key): Dim KeyName As String = e.ClickedItem.Text 'this is typically practical, because the text data is defined. ' The following is how you would acquire the Button's index (from zero): Select Case Me.tsddbOptions.DropDownItems.IndexOf(e.ClickedItem) + 1 'add 1 to compensate for a zero index in 1-based code Case 4 'Option 4 (4th 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 Select End Sub
  • 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 Goben 63.. Dealing with Process Handling In the KeyPress, KeyDown, and KeyUp Events One minor change that you may not notice is to the KeyPress, KeyDown, and KeyUp events. Unlike VB6, KeyPress, KeyDown, and KeyUp event handling under VB.NET gives you more control over what is done to key processing. A key down or up event is triggered when the user pressed or releases a keyboard key. A key press event is triggered when the user simply types a key on the keyboard. Typically, most developers concern themselves with just the KeyPress event. Usually, the developer wants to restrict what the user types into a TextBox. For example, suppose you wanted to ensure that the user entered only alphabetic or numeric characters into a TextBox, and further, the alphabetic characters should always be upper case. One might use code such as this: '******************************************************************************* ' Subroutine Name : txtName_KeyPress ' Purpose : Filter keyboard so that invalid data cannot creep in '******************************************************************************* Private Sub txtName_KeyPress(ByVal eventSender As System.Object, _ ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles txtName.KeyPress Dim KeyAscii As Integer = Asc(e.KeyChar) Select Case KeyAscii 'check the current key being entered into the txtName textbox Case 1 To 31 'ignore checking control keys (let them pass through) Case Else Dim C As String = UCase(Chr(KeyAscii)) 'get uppercase character from code Select Case C Case "A" To "Z", "0" To "9", "_" 'check against the range of allowed characters e.KeyChar = ChrW(AscW(C)) 'all is well for these, so be sure code reflects uppercase Page –64– 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.
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 64.. Dealing With Invoking Handled Events Under VB.NET Under VB6, you may have seen, or have even written, code that invoked another event method. For example, it was common to see code that forced a click event of an “ADD” button if the user typed text into a TextBox, and then hit the Enter key. This was, and is, a handy little shortcut. Suppose you have a TextBox named txtCommand, and a Button named btnAdd (with a caption of “&Add”, – which displays as “Add” – so that the user can also fire the Add button with Alt-A), which will add the user text to a Collection named colList. You might see VB6 code like this: Page –65– '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
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben But now that these events have parameters, many are wondering how to invoke them under VB.NET? One can squeeze by on invoking a button click by instead invoking the button’s PerformClick() method. But we would still be stuck with the GotFocus()event (now Enter()) for txtCommand. Actually, the solution is very simple. The first parameter of each event is the actual object being process; btnAdd for the btnAdd_Click() event code, and txtCommand for the txtCommand_Enter() event code. Both of these have a second argument object of an Eventargs type. As such you would simply specify the second parameter as “New System.EventArgs”. We use “New” because we are instantiating an new object. However, if the method we invoke will not modify or use the Enventargs parameter, we could sneak by cleanly by simply passing the EnventArgs parameter from the event we are invoking it from, if we are indeed invoking it from within an event. Following is the VB.NET upgrade of the above code: Page –66– Option Strict Off Option Explicit On Friend Class Form1 Inherits System.Windows.Forms.Form Dim colList As New Collection '****************************************************************** ' txtCommand_GotFocus (this becomes txtCommand_Enter in VB.NET -- txtCommand_LostFocus would become txtCommand_Leave) ' When textbox gets focus (the user selected it, for example), ' then select entire contents. '****************************************************************** Private Sub txtCommand_Enter(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtCommand.Enter With Me.txtCommand .SelectionStart = 0 'set selection to start of text .SelectionLength = Len(.Text) 'select entire text End With End Sub '****************************************************************** ' txtCommand_KeyPress ' When typing data into the textbox, if the user types ENTER, ' then automatically invoke the ADD button. '****************************************************************** Private Sub txtCommand_KeyPress(ByVal sender As System.Object, _ ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles txtCommand.KeyPress Dim KeyAscii As Short = Asc(e.KeyChar) Select Case KeyAscii Case 13 'CR? Call btnAdd_Click(btnAdd, New System.EventArgs()) KeyAscii = 0 End Select e.KeyChar = Chr(KeyAscii) If KeyAscii = 0 Then e.Handled = True End If End Sub '****************************************************************** ' btnAdd_Click '****************************************************************** Private Sub btnAdd_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAdd.Click colList.Add(Me.txtCommand.Text) Call txtCommand_Enter(txtCommand, New System.EventArgs()) End Sub End Class And sure enough, the upgrade performs the conversions of these invocations just as I described. All that is left here is to tighten up the code. For example, event txtCommand_KeyPress, although it works perfectly, could be seriously tightened up for much faster operation: Private Sub txtCommand_KeyPress(ByVal sender As Object, ByVal e As KeyPressEventArgs) Handles txtCommand.KeyPress If e.KeyChar = vbCr Then 'CR? btnAdd.PerformClick() 'yes, force adding the current data e.KeyChar = vbNullChar 'nullify the key e.Handled = True 'tell system not to handle this key code further End If End Sub
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 65.. 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: Page –67– 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 sent 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.
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 68.. 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 displayed Child form, or at least the one with focus. 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 Page –68– 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
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 71.. 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 only 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, 2 (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 Goben 75.. 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 mode. 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 Goben 77.. 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: Page –71– Me.Cursor = Cursors.Default 78.. 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
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Under VB.NET, the rules have changed a bit, but I think it is for the better. Consider the following example of a VB.NET version that merges the above VB6 KeyDown event: '******************************************************************************* ' Subroutine Name : txtName_KeyDown (VB.NET) ' Purpose : Allow DEL key to select Delete button '******************************************************************************* Private Sub txtName_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles txtName.KeyDown Page –72– Select Case e.KeyValue Case 46 'DEL key (we could have also checked ReadOnly e.KeyCode against Keys.Delete) If Me.cmdDelete.Enabled Then 'command button enabled? Me.cmdDelete.PerformClick() 'yes, press Delete key e.SuppressKeyPress = True 'disable further processing in KeyPress() event End If End Select End Sub Notice that there are 3 key differences noted. First, notice that we obtained the KeyValue (the VB.NET version of a VB6 KeyCode), from the “e” parameter, which is declared as a KeyEventsArg. Second, we invoked the Delete key using its PerformClick() method. Third, instead of setting e.KeyValue to 0 to supress the KeyPress event, as we might do if we were following the VB6 model, we instead supressed the KeyPress event (and further processing) by setting the “e” parameter’s Boolean SuppressKeyPress property to True. If we had tried to change e.KeyValue to 0, as we had done under VB6, an error would be generated, because under VB.NET, KeyValue is ReadOnly. Under VB.NET, there is a slight change to how the KeyPress event handles its code from the way VB6 did it. For example, the “e” parameter is now a KeyPressEventArg, which provides not an Integer KeyAscii code as VB6 did, but rather a KeyChar parameter that is of type Char. Also, to cancel further processing from the KeyPress event and to prevent an invalid code from being sent to the operating system for default processing, instead of setting the value to zero as we did under VB6, we set the boolean e.Handled parameter to True, which tells the operating system that we have handled the processing of the key and that no further processing is required. Consider the following rather involved, but useful syntax parsing example: '******************************************************************************* ' Subroutine Name : txtConstValue_KeyPress (VB.NET) ' Purpose : Filter keyboard so that invalid data cannot creep in ' : Process a command line such as "(BASE + 3) 'lower value" '******************************************************************************* Private Sub txtConstValue_KeyPress(ByVal sender As System.Object, _ ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles txtValue.KeyPress Dim KeyAscii As Integer = Asc(e.KeyChar) 'grab ASCII code of character Dim S As String = Trim(Me.txtConstValue.Text) 'get contents of assignment data (a Constant value assignment) Dim J As Integer = -1 'init location index Dim I As Integer = InStrRev(S, "'") 'comment tag present? If CBool(I) Then J = Me.txtConstValue.SelectionStart + 1 'if so, check for selection point End If Dim AllowLC As Boolean = J >= I 'set lowercase allowance flag if selection beyond comment tag Select Case KeyAscii Case 1 To 31 'ignore control keys Case Else Dim C As String = UCase(Chr(KeyAscii)) 'get text version of code If CBool(InStr(1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ &1234567890()'", C)) Then If Not AllowLC Then 'if not allowing lowercase data... e.KeyChar = CType(C, Char) 'update e.KeyChar to uppercase End If ElseIf CBool(InStr("+-", C)) Then 'if math involved, ensure enbraced by parenstheses S = Trim(Me.ConstValue.Text) 'grab trimmed text If VB.Left(S, 1) <> "(" Then 'no paren? S = "(" & S & ")" 'no, so enclose it With Me.ConstValue .Text = S .SelectionStart = Len(S) - 1 'set insert point immediately before ')' End With End If Else e.Handled = True 'cancel the KeyPress event, and prevent further OS handling of this key End If End Select End Sub
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 79.. 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 Page –73– Next Idx End Sub
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben As you can see, the Data object contains a 1-based Collection object that holds a list of one or more file paths, which we can easily loop through to pick up all of its entries. This, and single items that you could grab using the Drag Data object’s GetData() method were all the capability VB6 Drag and Drop had. Hmm. When you look at it that way, the VB6 interface was not very impressive. However, VB.NET comes along and it seems like a monkey wrench was thrown into the works (a monkey wrench, or spanner in Britain, is a large adjustable wrench with a long fulcrum arm that was popular in the nineteenth century, though now typically used in only really tough, tight situations, or plumbing, where you need to monkey around with a stuck pipe or large nut – in a pinch it also made a good, but sloppy hammer). When I upgraded my application, I discovered that the Drag and Drop code did not likewise upgrade. So I tried to take a look at what I needed to do. I dutifully enabled the EnableDrop property on my upgraded lstFiles object by setting it to True. I then added a DragDrop()event to the lstFiles object, but I was not quite sure what to do with it. I did notice that my “e” parameter was of type DragEventArgs, and that it had a Data object, but this Data object did not have a Files collection. So I took a look at the documentation to see what I could glean from that. What I gathered was that I needed to use the Data object’s GetData() method to acquire data (duh). All I had to do was provide GetData with a Format string. All the examples I saw were for basic types, like “String” and such. But I was not getting anywhere with that, so I thought I would use the GetFormats() method, which returned a string array, listing all the acceptable formats, and I put them into a message box. But I was still not getting any results. Plus, the mouse cursor looked like this when the mouse was dragged over my lstFiles control: More reading revealed that I had to parse the data being dragged and decide if it could be dropped onto my target object, and to set the proper copy effect for that object, which was accomplished through the object’s DragEnter()event. Though I was so far getting nowhere on the format, I really liked this new DragEnter()event idea a whole lot better than the really limited support we had in VB6. So I cheated, for the time being, by simply setting my Effects property to Copy, just so I could finally fiddle around in the DragDrop() event: Private Sub lstFiles_DragEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles lstFiles.DragEnter Page –74– e.Effect = DragDropEffects.Copy End Sub With that, my message box in my DragDrop event popped up with the list of formats available. Well, the last three formats looked promising, especially FileDrop and FileName. I recognized FileDrop from working with Clipboard I/O, using the DataFormats class, which featured a number of string members specifying, surprisingly enough, data formats. FileDrop was used in clipboard I/O to handle copying a number of files. As such, I assumed (rightly so, as it turned out) that I could use Filename or FileDrop interchangeably, though I prefer FileDrop simply because I can avoid misspellings by using the DataFormats enumeration. So with that, I changed my above DragEnter() event to: Private Sub lstFiles_DragEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles lstFiles.DragEnter If e.Data.GetDataPresent(DataFormats.FileDrop, True) Then 'Note that you can use "FileName" or even "FileDrop" in e.Effect = DragDropEffects.Copy 'place of DataFormat.FileDrop. Parameter TRUE allows other compatible Else 'formats to be converted to the specified target format. e.Effect = DragDropEffects.None 'Put up a no-entry sign End If End Sub This worked for file dragging, but then I ran into only a minor snag in my DragDrop event. But this ended up only helping me to better understand the special handling that Drag and Drop still affords files. As an experiment, I had placed the following line into my DragDrop event:
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben MsgBox(e.Data.GetData(DataFormats.FileDrop).ToString) 'I used ToString because the returned data is of type Object The result, instead of a filename, was “System.String[]”. This did not do very well as a filename, but it was informing me that the result was actually a string array (C# and C++ use square brackets instead of VB’s parentheses to indicate arrays). So, as a further experiment, I changed my test code to this: Dim DataFiles() As String = e.Data.GetData(DataFormats.FileDrop, True) 'grab file list Dim Nms As String = vbNullString 'init list accumulator For idx As Integer = 0 To UBound(DataFiles) 'loop through file list Nms &= CStr(idx) & ": " & DataFiles(idx) & vbCrLf & vbCrLf 'append each file Next MsgBox(Nms) 'display result And that did the trick. In reviewing all this, I have come to the conclusion that it would have been possible for the Upgrade Wizard to have upgraded the code. It would have had to add a DragEnter event, but it should have simply set “e.Effect = DragDropEffects.All”, but added an UPGRADE TODO notice that the developer should specify the formats that they want to allow, and the type of drop events they want to support. In upgrading the “Data.Files” VB6 collection, a generic “Dim DataFiles() As String = e.Data.GetData("FileName", Yes)” is easily possible, and instead of looping from 1 to the count of Collection items, you loop from 0 to the Ubound size of the DataFiles array. So in review, the following VB6 code: Private Sub lstFiles_OLEDragDrop(ByRef Data As Object, ByRef Effect As Integer, ByRef Button As Integer, _ ByRef Shift As Integer, ByRef X As Single, ByRef Y As Single) Dim Idx As Integer For Idx = 1 To Data.Files.Count 'parse through all possible files in the list Me.lstFiles.Items.Add(Data.Files(Idx)) 'append each to the end of the file list Page –75– Next Idx End Sub Can be upgraded to the following VB.NET Code: Private Sub lstFiles_DragDrop(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles lstFiles.DragDrop Dim DataFiles() As String = e.Data.GetData(DataFormats.FileDrop, True) 'get file list For idx As Integer = 0 To UBound(DataFiles) 'copy filepaths to list (you can also use a For...Each) Me.lstFiles.Items.Add(DataFiles(idx)) Next End Sub NOTE: If you have set Option Strict On, as I do, then you must also cast the data returned by GetData(), because it returns a generic Object type. We know that this type is actually a String Array, so we can use the DirectCast() command to cast it to string: Dim DataFiles() As String = DirectCast(e.Data.GetData(DataFormats.FileDrop, True), String()). Of course, you will also have to remember the DragEnter() event, which will allow you to specify what kinds of files your target control will allow, which is now handled during dragging, rather than generically by the enablement of a control parameter, which had invited the undesired possibility of invalidly formatted data being dropped under VB6: Private Sub lstFiles_DragEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles lstFiles.DragEnter If e.Data.GetDataPresent(DataFormats.FileDrop, True) Then 'Note that you can use "FileName" or even "FileDrop" in e.Effect = DragDropEffects.Copy 'place of DataFormat.FileDrop. Parameter TRUE allows other compatible Else 'formats to be converted to the specified target format. e.Effect = DragDropEffects.None 'Put up a no-entry sign End If End Sub NOTE: Just a reminder, I use the optional parameter “True” to get the GetData() and GetDataPresent() methods to allow compatible 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 use and easy to upgrade, and offers me much tighter control over what can or cannot be dropped on a control that accepts dropped items.
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben 80.. 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 Button Be aware that under VB6, you could display a Checkbox as a button by setting its Style property from Standard to Graphical. This was useful for buttons you wanted to display as toggling up or down on each click, and you could easily check its pushed state through its Value property. VB.NET also allows this, but by setting its Appearance property from Standard to Button. You check for it being toggled down 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 Goben 82.. Closing Remarks This concludes the assistance guide, “Navigating Your Way through Visual Basic 6.0 to Visual Basic .NET Application Upgrades”. Anything not covered by this document can usually be resolved with similar ease. Always remember that each warning inserted by the Upgrade Wizard also includes a link to the MSDN help, which offers you further assistance on the problem it either finds or, most often, simply cautions you to the per chance of a problem. Using these tools and this guide together, you should find upgrading to be much less of a chore. See the free companion manual, “Enhancing Visual Basic .NET Far Beyond the Scope of Visual Basic 6.0” (www.slideshare.net/davidrossgoben) for more detailed information of the differences between VB6 and VB.NET. It is also recommended that you download the free 719-page VB6 to VB.NET upgrade guide e-book, “Upgrading Visual Basic 6.0 to Visual Basic .NET and Visual Basic 2005” from Microsoft Developer Network (MSDN) at http://msdn.microsoft.com/en-us/library/aa480541.aspx. Pay particular attention to Chapter 7 through 9. You can also download the “Visual Basic 6.0 Upgrade Assessment Tool” from here, along with before and after sample files. This guide was developed jointly by the Microsoft patterns & practices team and ArtinSoft, (www.artinsoft.com) a company with vast experience in Visual Basic upgrades and the developer of the Visual Basic Upgrade Wizard and the Visual Basic Upgrade Wizard Companion. This guide provides valuable information for organizations who are considering upgrading their VB6-based applications and components to VB.NET. It provides proven practices to reach functional equivalence with a minimal amount of effort and cost, as well as guidance for common advancements after the application is running on the .NET framework. Another free 547-page e-book from Microsoft is “Upgrading Microsoft Visual Basic 6.0 to Microsoft Visual Basic .NET” at http://msdn.microsoft.com/en-us/vbrun/ms788236.aspx. It is the complete technical guide to upgrading Visual Basic 6 applications to Visual Basic .NET, covering all upgrade topics from APIs to ZOrders. It shows how to fix upgrade issues with forms, language, data access, and COM+ Services, and how to upgrade applications with XML Web services, ADO.NET, and .NET remoting. It also provides big-picture architectural advice, a reference of function and object model changes, and hundreds of before-and-after code samples. Still another free 274-page e-book from Microsoft is “Introduction to Microsoft Visual Basic 2005 for Developers” (http://msdn.microsoft.com/en-us/vbasic/ms788235). Even though it is designed around VB2005, it is worth its weight in gold. VB2005 is a giant step forward from earlier editions of VB.NET, and is in my view almost VB2008, which is the starting platform you need to get really serious in VB development. If you currently work with Visual Basic 6, these authors fully understand the adoption and code migration issues you'll encounter. They'll step you through a quick primer on .NET Framework programming, offering guidance for a productive transition. If you already work with .NET, you'll jump directly into what's new, learning how to extend your existing skills. From the innovations in rapid application development, debugging, and deployment, to new data access, desktop, and Web programming capabilities, you get the insights and code walkthroughs you need to be productive right away. Though some of the information provided in the above free Microsoft books are clearly dated, sometimes offering solutions using older, more convoluted command paths than those offered by VB2008 or VB2010 (often thanks to the new “My” namespace), those older solutions are still valid (I have tried to address those particular solutions in this document and its companion). To see how P/Invokes to the system are defined and used in .NET, visit PInvoke.net (www.pinvoke.net). This site is a massive knowledgebase of .NET P/Invoke information, providing examples for VB.NET and C#. This is an interactive, user-supported site. If you have a P/Invoke that is not yet documented, or is not presented for your development language, such as VB.NET, here is your chance to contribute your knowledge to the hive. Another place to get great upgrade information is VB Migration Partners. Although products and services are sold here, it also features a wealth of freely available information (www.vbmigration.com). David Ross Goben Last update: Saturday, August 30, 2014, 11:56:01 AM Contact: david.ross.goben@gmail.com Please feel free to inform me of any errors or omissions, or of topics that I should cover. Page –77–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben About the Author David Ross Goben is an independent researcher who is obsessive about details, a professional software engineer, and author, living his life in glorious anonymity. As a software engineer, he has been expected to think entirely out of the box and use intuitive perception to develop solutions to problems that were often assumed impossible. He says that he has been designing software solutions since dinosaurs walked the Earth, being a systems designer, language designer, and a compulsive developer (oh, and he has also painted houses, built them, and moved people's furniture across the country). He has written professional code in FORTRAN, C, C++, VB, Forth, COBOL, Pascal, various assembler languages, and others he wants to forget, and some he has successfully forgotten. Of Jewish descent, he has extensively explored Biblical history, ancient cultural thinking, and ancient slang for over three decades, which had resulted in his seminal work: A Gnostic Cycle: Exploring the Origin of Christianity. He has written numerous books, manuals, and magazine articles, many not credited, or authored under pen names. His other interests include Cosmology, Quantum Physics, Particle Physics, Astrophysics, Nuclear Physics, human-machine interaction, the Global Warming Myth, The Electric Universe, Expansion Tectonics, Perpetual Energy Technology, Quartz Technology, the real truth of history, exploring the glaring flaws in current Darwinian theory and Mendelian Inheritance, studying the bio-mechanical origins of life, and exploring the ancient practice of Dream Walking. His goal is to become as close as he can possibly be to a Universal Scholar. David Ross Goben Lady Lake, FL, USA david.ross.goben@gmail.com Page –78–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Free Online PDF Documents Available by David Ross Goben Four ready-to-read PDF documents are available for both online viewing and free downloading from Google Docs. They are: Open Letters Sent to Advocates for the Electric Universe and Expansion Tectonics Theories, and Exploring How These Theories Connect (152 pages). Since their initial proposals, gathered evidence has only strengthened the Electric Universe and the Expansion Tectonics theories. In spite of this, each time additional evidence for either of them surfaces, or another of a fast-growing body of scientists, especially those of important and augustly respected note, dares to declare favor for one or the other, select advocates for presumed ‘standard’ theories pop up, like South African meerkats alerted to an impending threat, and respond with typically scripted salvos of protest, too frequently droning the same old and practiced slogan-laced retorts like Gregorian Chants, as if their need to deny the public’s access to, or worse, their acceptance of such ideas was a matter of personal or professional survival. And it may be no wonder. In these last few decades, tenures, funding, and reputations have often been decided wholly upon which ideas, real or imagined, are accepted by the general public. We will compare the evidence between the Electric Universe and the Gravity-Based Universe, and between Expansion Tectonics and Plate Tectonics. We will also explore the long-held Prime Matter (Aether) theory that can strengthen the liquefacting sand upon which Particle Physics now finds itself, and which also strongly links the Electric Universe with Expansion Tectonics and makes these two models all the more plausible. So, prepare yourself for a Gnostic rollercoaster ride through an extremely thick ocean of information and history, a great deal of which had in the past often been intentionally obscured. View and download this PDF document for free at: https://docs.google.com/open?id=0B_Dj_dKazINlZTFjOWZmMWItNTg3ZS00MDcxLWE3NDctNDhhZGY2Y2JkOTRk Also available through web searches, and on Scribd.com and SlideShare.com. Page –79–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades (83 pages). This is powerful lap reference and resource for those needing to upgrade their old VB6 application to the object oriented universe of Microsoft Dot NET technology. When the majority of VB6 developers demanded the ability to build VB code in a common language IDE (Integrated Development Environment), have 100% unfettered cross-language interoperability, and expecting no less than 100% unrestricted object oriented programming language capabilities, most of them did not have the first clue, not the slightest understanding, of the earth-shattering impact their passionate, relentless, spittle-laced howling would have on their beloved VB. An immense host of them, mostly amateur programmers and hobbyists, naively believed that after such a necessarily monumental undertaking, they could simply continue on their merry little way, writing VB code exactly as they had done before, using the very same often non-uniform syntax that they had been using before, and, oh yes, they expected there would be a few additional commands here and there to address full class inheritance, and also allow seamless access to methods whose source code was written in some other programming language, such as C++. They did not grok the fact that in order to provide them with exactly what they were keenly expecting would also clearly necessitate colossal changes to their beloved VB so that it would be a fully integrated, object oriented environment that would also interoperate with and act exactly like the other Visual Studio languages (or other .NET-compliant languages). This requires perfect synchronicity between all those languages; that each of them could clearly understand and use objects from each other without the slightest misconstruction. This manual makes dealing with those many changes a snap. View and download this PDF document for free at: https://docs.google.com/file/d/0B_Dj_dKazINlMjViMGUzZTUtMWFiZS00ZGNhLWE1NjEtMDQ4NjcwNmNiOTFm/edit?pli=1 Also available through web searches, and on Scribd.com and SlideShare.com. Page –80–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Enhancing Visual Basic .NET Applications Far Beyond the Scope of Visual Basic 6.0 (370 pages). Transitioning from Microsoft Visual Basic 6.0 (VB6) to Microsoft Visual Basic .NET (VB.NET), at first glance, may look to be an intimidating endeavor. After all, you have likely heard or read through copious magazines and blogs that there are huge differences between these two developmental platforms. Though some of those differences are real, most others are simply imagined, engendered by nothing more than unapprised conjecture. Of the real platform deviations, most are simply due to them having to be expressed differently; plainly because VB.NET strictly follows a stringent pattern of uniform language syntax, which is something VB6 was not always good at. Hence, a programming language feature may have to be implemented under VB.NET using a radically different access conduit than the way it may have been realized under VB6. Other disparities, some seen as much more profound, actually end up being VB6 features that VB.NET does in fact support, but, again, due to tight .NET platform architectural specifications, VB.NET cannot support them in a like manner, but may by necessity have to utilize non-VB6-style invocation rules. Nevertheless, by employing some simple user-defined helper functions, such as will be demonstrated throughout this document, you can easily emulate “lost” VB6 commands, or, in most cases, make their functionality more accessible through simpler syntax. Regardless, you will find that, overall, VB.NET supports all these many differences, both major and minor, in but different forms, and in all it also implements much more robust techniques to apply their functionality. Many of the “major” differences bemoaned by many VB6 purists no longer exist; having existed only in Beta releases of VB.NET, but being addressed by the time of the initial product launch, or, in more complex cases, in later releases. The biggest problem here is that most new VB.NET developers still approach problems the same way as they may have faced them under VB6, or had been trained by VB6-savy developers. This book helps you to not only break yourself free of that mold, but to really make your VB.NET application shine by example after example of how to make what was thought to be difficult to be in fact very easy, and it also provides you with a toolkit you can build yourself, and customized to your needs so that you can really make your applications stand out from the rest of the pack. View and download this PDF document for free at: https://docs.google.com/file/d/0B_Dj_dKazINlRi1JWW42UXFzVG8/edit?pli=1 Also available through web searches, and on Scribd.com and SlideShare.com. Page –81–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Doom 3 Walkthrough and Strategy Guide (400 pages). This is an enhanced novelized exploration into the dark horrors of the 2004 Doom 3 adventure, perhaps one of the scariest and most densely detailed first-person-shooter escapades ever conceived, for both the original offering from Id Software and their later Doom 3 BFG Edition. This walkthrough takes you through the Doom 3 adventure at the Veteran Difficultly level, describing in intimate detail this quest and the strategies required to both beat this game easily and to explore areas and find treasures that you may have never before thought existed. Some of the most arduous battles that gamers often wail and gnash their teeth about can sometimes be accomplished by using some of the simplest solutions imaginable, and all without cheat codes, but with quick thinking and actual military techniques. For example, many players dread facing off with the six maggots (tall man-like demons with razor-sharp talons, two heads, and panther-like ferocity) at the end of the Alpha Labs – Sector 1 level, because they always seem to come to great harm unless, through sometimes pure luck or random fortune, they can hole up in the left far corner of that locked room and hold the massing maggot hoard at bay long enough for the player to take them all down in a blazing torrent of voluble gunfire, but did you know that it is actually stupidly easy to defeat them pain-free, and on top of that you can do it in perfect safety and at your leisure? Full descriptions of items, adversaries, strengths, weaknesses, locations, secrets and caches not mentioned in any other guide, a dense listing of useful console command codes, and custom modifications are covered, such as how to play at your monitor’s maximum resolution, add and/or alter custom keyboard commands and toggles, and how to easily play Doom 3 BFG Edition on otherwise uncooperative Windows 8.1 systems, offering two fast and very easy solutions that have worked on every system they have ever been tried. View and download this PDF document for free at: https://docs.google.com/file/d/0B_Dj_dKazINlY3pJTXVjd1FWWlE/edit?pli=1 Also available soon through web searches, and soon on Scribd.com and SlideShare.com. NOTE: Although the complete adventure is described, it is still a work in progress. Page –82–
  • Navigating Your Way Through Visual Basic 6.0 to Visual Basic .NET Application Upgrades – David Ross Goben Also Available From the Author A Gnostic Cycle: Exploring the Origin of Christianity (712 pages). In this book, you are going to explore the bare-boned facts behind a broad range of Biblical mysteries, digging deep beneath the thick layers of misunderstood traditions, semantic misconceptions, invented religious and political dogma, and breath-taking, world-changing events in history that had been willfully suppressed or misrepresented. Like any really good who-done- it, you will read tales of sinister deception, glorious victory, hateful murder, intimate love, rampaging armies, wholesale genocide, spiritual salvation, and, as you would anticipate in such mysteries, the heroes and villains are not always who they seem to be. Listed below is a short list of the hundreds of things you will herein unearth: • The Hebrew Bible was written to build self-esteem, not to record concise history. • King David was based upon Pharaoh Thutmosis III. • King Solomon was based upon Pharaoh Amenhotep III. • Moses was based upon Pharaoh Akhenaten (Amenhotep IV). • Joshua (Ye-ho-shua) was based upon Pharaoh Tutankhamun (Amenhotep V). • Joshua was the son of Moses, not of Nun, and lived only until he was 19, not 110. • Jesus was named for Joshua, because it was believed he was his Second Coming. • Jesus was a dynastic king; a direct descendant of the Scythian Anointed Kings. • Jesus married Mary Magdalene, a Royal Princess, and their offspring live today. • The Apostle John Mark was actually Mary Magdalene; the Beloved Disciple. • Simon Zealot was of the Magi, and was one of the most brilliant men of all time. • Lazarus raised Jesus from the grave after his crucifixion. • Judas Iscariot was hung on a tree (ancient slang for a crucifix), not from a rope. • Mary Magdalene was the first Pope of the Church Jesus personally established. • Christianity and Judaism are polytheists, worshipping to this day multiple Gods. • The Holy Spirit is the Jewish Shekinah (Presence of God); the Mother Goddess. • The Father in Heaven (Hebrew Adon) and Yahveh are two competing deities, from ancient Mesopotamia, and are separate to this day in the Page –83– Hebrew Bible. • Women are not half the value, but are in fact twice the value of a man. • The Jerusalem Church was actually located in Qumrân, by the Dead Sea. • Original Christian doctrine comes from Chapter 6 of Numbers, from the Hebrew Bible; rules governing the Qumrân Nazarite (pronounced “Nazareth”) Order. • The Apostolic Church has tried for centuries to exterminate Jesus’ Family Line. • True royal blood has different genetic markers than the common population. • The Eucharist is an ancient ceremony, using bread mixed with white powder gold. • The reason we think non-precious metals like gold and silver valuable is spiritual. • The Antichrist was born 8 years after Jesus, yet his sway is more powerful today. This book is available from your favorite book seller, or directly from Authorhouse or Authorhouse UK Ltd., for either $17.50USD or for £13.90UK: http://www.authorhouse.com/Bookstore/ItemDetail.aspx?bookid=33204 (ph: 1-888-519-5121) http://www.authorhouse.co.uk/Bookstore/ItemDetail.aspx?bookid=33204 (ph: 0800-1974150)