Deconstructing the Visual Basic Upgrade Wizard

 

Bill Sempf

March 2006

Applies to:
   Visual Basic 2005
   Visual Basic 6

Summary: Bill Sempf talks a look at the pros and cons of the Visual Basic upgrade wizard and shows it in action in a variety of different examples. (15 printed pages)

Contents

Introduction
Control Plus – User Interface Artifacts
Data Environment Guide – ADO
Printer Driver
Coffee – asynch Notification
VBMail – MAPI and Internet Mail
Conclusion

Introduction

Now that the Visual Basic 2005 honeymoon is over, more developers than ever are looking at their situation and realizing that we have millions of lines of Visual Basic code to convert to 2005 if we expect to utilize the new features of contemporary Windows versions. The Visual Basic Upgrade Wizard (VBUW) is a great place to start this process.

The strategy is simple: read any kind of Visual Basic program, and translate the Visual Basic code from Visual Basic 6 to Visual Basic 2005; if you think about it, that is a monster task for any program. The VBUW is very successful, however, with a line-by-line 96% success rate in the five programs I tested, and higher than that in some that didn't make the cut for this article.

Fact is, the VBUW is just another program, and just like pretty much any other program, it handles some situations better than others. My goal here is to put the tool through its paces, and make good determinations on a few specific project types based on what I find.

Improvements over Visual Basic .NET

The core of all of this is that the Upgrade Engine—the list of instructions that makes the decision to replace msgbox with Messagebox.Show()—is much improved over the previous version. The changes make it much more usable for the vast majority of small and midsized Windows Forms-type projects.

For instance, the 2003 version of VBUW would not decide to use the appropriate .NET Windows Forms control in place of an old OCX in Visual Basic. For instance, the CommonDialog control would just be put in place using Interop, using a Runtime callable wrapper around the old COM control. The 2005 version will determine if you are using FileOpen or FileSave or whatever and use the appropriate Windows Form control instead.

This is, of course, in addition to the new Visual Basic 2005 features that make the entire language more like Visual Basic 6. For instance, some of the more esoteric application-level properties versions are translated to the related My object properties. Also, the new direct access Form properties are translated.

ASP.NET and reality

The first question I was asked by my reviewer was, "Hey, there aren't any ASP projects in your examples. Why not?" Simple answer to that—you still can't upgrade ASP projects. They aren't written in Visual Basic, they are written in VBScript, and that is a whole different world. Additionally, Microsoft went a whole different way with ASP.NET, and the two aren't the least bit similar.

When ASP.NET first came out (back when it was ASP+), I tried to write a conversion engine. Trust me, even if there were a good wizard out there for ASP conversion, you would spend more time cleaning up that you would just rewriting it from scratch. For that reason, there is no ASP Upgrade Wizard.

A word about warnings

Of course, while 28 upgrade warnings might seem excessive on the first project, or on any of the projects I am reviewing, keep in mind an important fact: I selected these projects to specifically show what the VBUW might not handle so well. There are a handful of things that the Wizard doesn't upgrade, and I cover them all here.

I ask that you don't walk away thinking this is not a good product; in reality, it is truly an amazing piece of work. I recently upgraded a 12,650-lines-long Visual Basic 6 project that had just been upgraded from Visual Basic 4, and I received zero errors or warnings, and the app ran just fine. The whole process took me 5 minutes, and I looked like a hero.

Knowing all of that, let's look at some projects.

Control Plus – User Interface Artifacts

The Control Plus (controls.vbp) project is all about getting into the more sophisticated ActiveX control properties that came with Visual Basic 6, like Image and multi-line text boxes and labels. It is a good look at how the VBUW handles the intrinsics of controls.

I opened the project using Visual Studio 2005, and the Visual Basic Upgrade Wizard ran more or less without a hitch. The project type was set automatically, and there were no errors or warnings during the run. The project didn't run out of the box, though. A quick glance at the Upgrade Report told me the story.

Aa730876.vbuw2005_01(en-US,VS.80).gif

Figure 1. The Upgrade Report

From a usability perspective, the Upgrade Report is the heart of this tool. It very clearly states problems, suggestions, and issues it encounters as it revises your code. Compile Errors are problems that will prevent the application from compiling until they are dealt with. They are fairly rare, as you will see. Design Errors will prevent the application from running, though it usually will compile. This is usually in situations where the VBUW provides interop components for classes it doesn't have equivalencies for. Warnings are just information about changes in the way some .NET controls work.

As you can see, twelve properties or methods could not be upgraded. "Couldn't be upgraded?" you ask. Yes, that's what it says. When I looked at the details, it turns out that the PictureBox component no longer supports a Print or Cls method. Digging a little further, I discovered that a whole lot of objects have methods or properties that don't have Visual Basic 2005 equivalencies:

App Object

CheckBox Control

Clipboard Object

ComboBox Control

CommandButton Control

CommonDialog Control

Controls Collection

Data Control

DataGrid Control

DirListBox Control

DriveListBox Control

FileListBox Control

Font Object

Form Object

Forms Collection

Frame Control

HScrollBar Control

Image Control

ImageList Control

Label Control

Line Control

ListBox Control

ListView Control

MaskEdBox Control

MDIForm Object

Menu Object

OLE Container Control

OptionButton Control

PictureBox Control

Printer Object

Printers Collection

ProgressBar Control

RDO Data Control

RichTextBox Control

Screen Object

Shape Control

StatusBar Control

TextBox Control

Timer Control

ToolBar Control

TreeView Control

User Controls

VBControlExtender Object

VScrollBar Control

WebBrowser Control

WebClasses

Functionality not upgraded

There are three major categories of functionality that aren't upgraded: data, printing, and graphics. The PictureBox functionality falls into the Graphics category. Drawing on the screen, whether it is lines or text, is now the province of the System.Drawing namespace.

In Visual Basic 6, you would draw in a PictureBox, or many other ActiveX controls using the Print method. In Visual Basic 2005, you get a palette first, called a Graphics object, and draw on that. This is because all of the controls inherit from a base control that requires a certain level of graphics functionality. More advanced objects may inspire more functionality, but all objects require a certain level of functionality.

The code in question, from the Visual Basic 6 project, looks like this:

picStatus.Cls()
picStatus.Print("Selected: Club")

What this does is change the text to the parameter if you click on the club. Now, while this seems simple, and is in fact simple to code, it just isn't consistent. All of the ActiveX controls have a different way for you to 'write' on them. Furthermore, some, like the PictureBox, you shouldn't write on at all!!

In .NET there is a consistent, if more wordy, implementation of on-screen graphics. To actually set a picture in the PictureBox, you would create one using the Graphics object as follows:

theImage = New Bitmap("c:\picture.bmp")
picStatus.ClientSize = theImage.Size
picStatus.Image = CType(theImage, Image)

However, as is often the case in these situations in Visual Basic 6, the picStatus control isn't really a PictureBox at all; it should have been a status bar all along. So, we would have to go back into the form design, delete the PictureBox, and put in a StatusBar, then set the Text property of that StatusBar.

Data Environment Guide – ADO

This sample program demonstrates common techniques for runtime databinding using the DataEnvironment object that was so useful in ADO. For instance, one form dynamically creates a SQL statement that is then assigned to a Recordset object's Source property.

Every time the user clicks the ComboBox control, the recordset is closed and its Source property is reset to the new statement. The recordset is re-opened and bound to the controls again. Another creates several recordset object variables and sets the DataSource property of each to a recordset object. This project also demonstrates how to monitor events of the child and grandchild objects.

This project has an interesting problem because ADO, being ActiveX DATA objects, is one of the three things that are not upgraded. So what happens? Interop? Ewww! If you run the wizard and don't have Visual Basic 6 installed, you get an upgrade failure, because the ADO control DLLs aren't present.

Aa730876.vbuw2005_02(en-US,VS.80).gif

Figure 2. Upgrade failure

If you do have Visual Basic 6 installed (it is OK to run both on the same machine), then you get a project with a handful of "Method or Property Could not be upgraded" errors—quite a difference from the fatal error that those of us without Visual Basic 6 installed get.

ADO objects accessed through Interop

So what's the scoop? The VBUW just wrapped the old ADO objects in what is called a Runtime Callable Wrapper. This, unsurprisingly, allows the .NET Runtime to call an object that it normally couldn't call.

This is not exactly a best practice, but if you are in a hurry, it works. Generally, both Microsoft and I would recommend that you rewrite your data access in ADO.NET using DataAdapters and whatnot. There are already a whole host of articles on that topic.

If you are going to use the ADO objects, though, you need to know that databinding is different in Visual Basic 2005, and the VBUW doesn't make assumptions about what property of the TextBox you are trying to bind. In Visual Basic 2005, any of the properties that contain data are bindable, and you'll need to use the integrated development environment (IDE) to describe it, or use the DataBindings collection:

txtCustomer.DataBindings.Add("Text", _
deNwind, "CompanyName")

Printer Driver

One of my clients has a 1980s-era line printer with a Visual Basic COM+ component that runs it for their intranet. It is pretty straightforward—it takes a page of text and prints it. There is a LOT of code, because it does a lot of invocation of the Win32 API, but it works well.

As I mentioned before, printing is one of the "biggies" that isn't handled by the VBUW. This is true here as well, with 8 errors and 32 warnings, many of which concern printing. This project will take some rewriting. On the other hand, out of 2180 lines of code, I would have probably made more than 8 errors rewriting it by hand!

In Visual Basic 6, you print by creating a Printer object and using included methods to draw text and graphics onto a page after setting printer attributes like DeviceName, PrintQuality, or Copies. Then you use the EndDoc method to sends the output to the default printer for the application as defined in the Printers collection.

In Visual Basic 2005, the Printer is gone as a concept. Instead, you use a PrintDocument control to define the graphics and text with System.Drawing, a PrinterSettings object to define printer attributes like which printer to send to and which tray to use, and a PageSettings class to define page attributes like quality and aspect.

Printing sees a drastic change

Printing is no longer tied to a specific device, and the concept of a default printer for an application is no longer valid. Instead the PrintPage method of the PrintDocument component can be used to print to any device, and the default printer is system-wide.

Additionally, user interaction is available out of the box with Visual Basic .NET. The PrintDialog, PrintPreviewDialog, and PageSetupDialog components allow you to let the user select a printer and print options at runtime.

For example, the Line Printer program has a set of API calls to winspool.drv that gives access to the line printer. They use OpenPrinter to get a reference to the default printer, set a few values, and get ready to print.

Private Declare Function OpenPrinter Lib "winspool.drv" _
   Alias "OpenPrinterA"(ByVal pPrinterName As String, _
   ByRef phPrn As Integer, ByRef pDefault As Any) As Integer
Private Declare Function StartDocPrinter Lib "winspool.drv" _
   Alias "StartDocPrinterA"(ByVal hPrn As Integer, _
   ByVal Level As Integer, ByRef pDocInfo As DOC_INFO) As Integer
Private Declare Function StartPagePrinter Lib "winspool.drv" _
   (ByVal hPrn As Integer) As Integer
Dim px As Printer


Call OpenPrinter(Printer.DeviceName, hPrn, 0)
Printer.FontName = "courier"
Printer.FontSize = 12
lTemp = hPrn
If lTemp = 0 Then
   GoTo Exit_Err_Routine
End If
jobid = StartDocPrinter(hPrn, 1, di)
Call StartPagePrinter(hPrn)

When I run this code through the Upgrade Wizard, I get a warning about using As Any (which is no longer supported) in the Win32 declarations, but I don't care because I get to delete those. The .NET Framework handles printing now—I don't need winspool.drv.

More importantly, I get the unfortunate "UPGRADE_ISSUE: Printer object was not upgraded" above the instantiation of the Printer object. This is the problem, as I need that object to, well, print. Additionally, I get an "UPGRADE_ISSUE: Printer property Printer.DeviceName was not upgraded" before the line that uses that property, and similar messages above other uses of the Printer class.

We will have to rewrite all of the printer code. The goal would be to write a text string generated by the rest of the class to the printer. This is accomplished using DeawText in the System.Drawing namespace. I cover the PrintDocument object in my article, Moving Your Legacy Hardware Code to Visual Basic 2005.

Event log uses My object

In an aside, this component has a little logging feature that writes to the event log if the debug flag is set. It was gracefully upgraded to Visual Basic 2005 by the VBUW, formatted for logging using My, as shown below:

Visual Basic 6

Private Sub Log(msg As String)
Dim EventType As Integer
    If bDebugging Then
        EventType = vbLogEventTypeInformation
        If Len(msg) >= 5 Then
            'This will put a big red X in the event viewer for Errors
            If Mid(msg, 1, 5) = "ERROR" Then EventType = vbLogEventTypeError
        End If
        App.LogEvent msg, EventType
    End If

End Sub

Visual Basic 2005

Private Sub Log(ByRef msg As String)
  Dim EventType As Short
  If bDebugging Then
    EventType = _  TraceEventType.Information
    If Len(msg) >= 5 Then
      If Mid(msg, 1, 5) = "ERROR" Then
        EventType = TraceEventType.Error
      End If
My.Application.Log.WriteEntry(msg, _ EventType)
  End If

End Sub

Coffee – asynch Notification

Coffee consists of a client, CoffWat2, and two ActiveX components (OLE servers), Coffee2 and MTCoffee.

Together, these three projects demonstrate:

  • Asynchronous notifications using events (Coffee2)
  • Asynchronous notifications using callback methods (Coffee2)
  • Multithreading (MTCoffee)
  • XTimers.vbp is a helper project that provides a code-only timer used by Coffee2 and MTCoffee.

Project groups ignored

Here the Solutions / Project Groups problem raises its head. This project has 4 projects in it, and the Wizard doesn't really support the conversion of a group. This isn't a huge problem. I created a new Blank solution and added all the projects too it. The project references needed to be set up, and the appropriate namespaces referenced. (Check the ProgId that might have been set in the class declaration. It can make the namespaces hard to handle.) After that I was good to go.

There is, as one would expect, a little trouble with the Interfaces. Interfaces are handled very differently in Visual Basic .NET and Visual Basic 6. Essentially, the wizard didn't upgrade them into interfaces—it upgraded them into classes. This isn't going to cut it, but the changes are fairly straightforward.

At the beginning of my research, after cleaning up the obvious problems, I discovered that this like was causing a "Value of type" error:

Aa730876.vbuw2005_03(en-US,VS.80).gif

Figure 3. 'Value of type' error

This is the client implementation of the notification class—the TellMeReady method is expecting an ICoffeeNotify, so the NotifyMe class should implement that interface, right? Well, when I went to the file in Visual Studio—a blue squiggly appeared under the Implements statement. The error is "Implemented Type must be an Interface."

The original code is set as PublicNotCreatable, and looks like this:

Option Explicit

Public Sub CoffeeReady()
End Sub

Public Property Get NotifyID() As Long
End Property
Public Property Let NotifyID(ByVal NewValue As Long)
End Property

So I went to look at the ICoffeeNotify interface file in the Visual Basic 2005 project after the wizard had gotten to it, which is ICoffNot.vb, and found this source code:

Option Strict Off
Option Explicit On
<System.Runtime.InteropServices.ProgId("NotifyMe_NET.NotifyMe")> Public Class NotifyMe
    Implements ICoffeeNotify
   
   Private mlngNotifyID As Integer
   
   Private Sub ICoffeeNotify_CoffeeReady()
      With Form1.lstCallBacks
         .Items.Insert(0, VB6.Format(Now, "ddd hh:mm:ss"))
         If .Items.Count > 10 Then .Items.RemoveAt(10)
      End With
   End Sub
   
   Private Property ICoffeeNotify_NotifyID() As Integer
      Get
         ICoffeeNotify_NotifyID = mlngNotifyID
      End Get
      Set(ByVal Value As Integer)
         mlngNotifyID = Value
      End Set
   End Property
End Class

Interfaces are not upgraded

Hello, this isn't an interface! That might be our problem. The upgrade wizard failed to convert the file to an interface—there are probably not enough hints for the Upgrade Wizard to determine if it should make it an interface. I made the following edits, and things moved along smoothly:

  • Removed the ProgId
  • Changed the Class statement to an Interface declaration
  • Removed the Public and End Sub from CoffeeReady
  • Removed the Public and all of the internals code of NotifyId

The new code looks like this:

Option Strict Off
Option Explicit On
Public Interface ICoffeeNotify

    Sub CoffeeReady()

    Property NotifyID() As Integer
End Interface

Then I needed to match up the code in CWNotNe.vb, the client using the interfaces. The Implements statements weren't written, so I needed to clean that up. The original code looked like this after the wizard ran:

Option Strict Off
Option Explicit On
<System.Runtime.InteropServices.ProgId("NotifyMe_NET.NotifyMe")> _
Public Class NotifyMe
   Implements ICoffeeNotify
   
   Private mlngNotifyID As Integer
   
   Private Sub ICoffeeNotify_CoffeeReady()
      With Form1.lstCallBacks
         .Items.Insert(0, VB6.Format(Now, "ddd hh:mm:ss"))
         If .Items.Count > 10 Then .Items.RemoveAt(10)
      End With
   End Sub
   
   Private Property ICoffeeNotify_NotifyID() As Integer
      Get
         ICoffeeNotify_NotifyID = mlngNotifyID
      End Get
      Set(ByVal Value As Integer)
         mlngNotifyID = Value
      End Set
   End Property
End Class

And after I added the Interface code:

Option Strict Off
Option Explicit On
Public Class NotifyMe
    Implements ICoffeeNotify

    Private mlngNotifyID As Integer

    Private Sub CoffeeReady() _
Implements ICoffeeNotify.CoffeeReady
        With Form1.lstCallBacks
            .Items.Insert(0, VB6.Format(Now, "ddd hh:mm:ss"))
            If .Items.Count > 10 Then .Items.RemoveAt(10)
        End With
    End Sub

    Private Property NotifyID() As Integer_
 Implements ICoffeeNotify.NotifyID
        Get
            ICoffeeNotify_NotifyID = mlngNotifyID
        End Get
        Set(ByVal Value As Integer)
            mlngNotifyID = Value
        End Set
    End Property
End Class

VBMail – MAPI and Internet Mail

The MAPI session control establishes a MAPI session and signs off from a MAPI session. The MAPI messages control allows you to perform a variety of messaging systems functions after a messaging session has been established. These functions include accessing, downloading, and sending messages, displaying the details and address book dialog boxes, accessing data attachments, resolving recipient names during addressing, and performing compose, reply, reply all, forward, and deleting actions on messages.

The sample dynamically creates an ADO Recordset object to contain the list of unread messages. The DataSource property of a DataGrid control is set to the recordset object to display the list.

This project won't convert without Visual Basic 6 installed because msmapi32.ocx isn't installed. Mail and Internet working in general is very different in the .NET environment as compared to Win32 in general, and Visual Basic 6 in particular.

Mail in Visual Basic 6 is all about the MAPI controls. MAPI, or Messaging Application Program Interface, is sort of an API for e-mail systems. It isn't really that great, but Microsoft took a shot on it when e-mail was young, and Visual Basic 6 is largely based on it. Microsoft Exchange supports MAPI nicely, but most other mail servers fall short.

Visual Basic 2005 doesn't depend on MAPI; it talks POP and SMTP in native tongue using sockets. It means a little more code, but a lot more interoperability.

I ran the migration wizard on VBMail after I went to a machine that had Visual Basic 6 and Visual Basic 2005. Aside from the expected ADODB warnings, and other minor design errors, there was only one critical error. It is a Form Load statement that wasn't upgraded.

Load(frmRead)

Of source, you can now change that to

Dim MyfrmRead As frmRead = New frmRead
MyfrmRead.Show()

But that's just the beginning of the issues with this program. When I tried to go the form view for FrmLogOn, I got this rather unusual error message:

One or more errors encountered while loading the designer. The errors are listed below. Some errors can be fixed by rebuilding your project, while others may require code changes.

The designer cannot process the code at line 134: Me._tbrMail_Button3.CheckOnClick = False The code within the method 'InitializeComponent' is generated by the designer and should not be manually modified. Please remove any changes and try opening the designer again.

Hide Edit

at Microsoft.VisualStudio.Design.Serialization.CodeDom.XML.CodeDomXmlProcessor.CreateQuoteExpression(XmlElementData xmlElement)

…. [remainder clipped]

Some properties of controls upgraded incorrectly

This is somewhat unexpected. A little research discovers that there is a property that is moved by default but not commented or changed—CheckOnClick. This is a valid property of the CheckBoxList, but for some reason has been added to every ToolStripSeperator in the project. Probably just a small flaw in the Upgrade Wizard, but unusual no matter how you look at it.

The reason I brought this project to light was to talk about mail; however, it doesn't even show up in the Upgrade Report! What gives? As it turns out, the Upgrade Wizard just wrapped the MAPI controls in a Runtime Callable Wrapper. A quick look at the References confirms this:

Aa730876.vbuw2005_04(en-US,VS.80).gif

Figure 4. References list

Normally, I would recommend that this be rewritten, but a quick look at the project shows that this is more about reading than writing e-mail. An interesting quirk of the .NET Framework—writing e-mail is supported out of the box, and reading it is not.

.NET really doesn't do POP3

Writing e-mail is covered by the SMTP protocol, which is part of the System.Net.Mail namespace. Reading e-mail is covered by the POP3 protocol, which is NOT part of the .NET Framework. Admittedly, it is a lot more common to send an e-mail from a program than it is to receive one, but that still is a rather odd distinction.

If you do need to use the POP3 protocol, you will have to roll your own using System.Net.Sockets. Inheriting from TcpClient, then overloading the Connect method, gives you a StreamReader and a NetworkStream that you can read and write to the POP server. Or, you can use the MAPI control provided by Visual Basic 6.

Conclusion

The Upgrade Wizard is certainly the place to start when you have a Visual Basic 6 project to move to Visual Basic 2005. Large or small, every project will benefit by having the Upgrade Wizard involved. It's like having twenty Microsoft employees look over your code and make recommendations as to where it will upgrade well.

Nonetheless, there are still some major changes from Visual Basic 6 to Visual Basic 2005, which will require you to rewrite. Data access is probably the largest one, and graphics and printing make a large dent in the total as well. Even with these major changes, though, the Wizard is clearly the place to start.

There are also a few small holes, like the CheckOnClick property in the ToolBarSeperator. These discrepancies are going to occur whether you rewrite manually or use the wizard, but none of these are showstoppers.

The best advice I have is this:

  • Make your Visual Basic 6 code as clean as possible before you run the Upgrade Wizard. Use the Code Advisor to help you.
  • Don't expect to be able to deploy a new .NET version right away. There will be cleanup.
  • Be prepared to completely rewrite your data layer. Learn ADO.NET first before you convert your projects.
  • Upgrade soon. The next version of Visual Basic will not be better to upgrade to. Make the leap, and use the Visual Basic Upgrade Wizard.

© Microsoft Corporation. All rights reserved.