The Book of VB .NET: Migrating to Visual Basic .NET, Part 1
Excerpted with permission from The Book of VB .NET, by Matthew MacDonald, No Starch Press, Copyright 2002.
A traditional computer book usually discusses migration-thequestion of how to work with existing data files-early on. In manyprograms, including office productivity software such as MicrosoftWord, working with files from a previous version is just as easy as creatingnew documents. Unfortunately, Visual Basic .NET doesn't work thisway. As you've discovered in the preceding thirteen chapters of The Book of VB .NET, Visual Basic .NET introduces an entirely new programming framework called .NET. To programwell in .NET, you have to surrender many time-honored habits and adopt a new,object-oriented style. When it comes to migration, the question is not how youcan import your existing applications, but whether you should at all.
This article surveys the major changes between Visual Basic 6 andVB .NET, and in doing so, provides a nice summary of the .NET philosophy.We'll take a look at t he Upgrade Wizard, and see how a sample VB 6 projectweathers the transition to .NET. You'll also learn about how to integrate legacycode, which is useful when migration is simply too painful. Specifically, we'llexamine how you can access COM components and ActiveX controls-the "oldworld" of programming-in a .NET project. These techniques allow you to makemaximum use of your existing code, while pursuing new development inVisual Basic .NET. Integration and interaction may not always be convenient,but it will be an essential ingredient of .NET programming for the next fewyears. It will also be the best strategy in many cases where migration is not possibleand re-coding is just too time-consuming.
Introducing .NET Migration
Visual Basic .NET compromises backward compatibility in a number of troublesomeways. Hopefully, the advances you've seen over the course of this book willmake these complications worthwhile. Once you're in the .NET world, there'sreally no easy way back.
Visual Basic 6 in the .NET World
Visual Basic 6 is a mature, well-developed programming environment. There'sno immediate need to replace a VB 6 program (and there's no shame in maintaininga program in Visual Basic 6). Think of Visual Basic 6 as a well-worn, austerelanguage at the end of its evolution. It still has a nice autumn glow to it, butit will gradually fade into disuse. On the other hand, Visual Basic .NET is ayoung, dynamic upstart at the beginning of its life cycle. As with any new program,changes will abound in VB .NET over the next few years. However, it'sVisual Basic .NET, not Visual Basic 6, that will power the next generation of VBapplications.
A key theme in this article is breaking down the barriers between VB 6and VB .NET. There may not be an easy migration solution for many of yourprojects. Instead, you may need to start over again in .NET, and make heavy useof the COM compatibility layer that is built into .NET, in order to use your existingVB components.
There is no file compatibility between Visual Basic .NET and earlier versions. InChapter 3 of The Book of VB .NET, we explored the new file formats used in VB .NET projects. Thesefiles have different extensions (.vb instead of .mod or .frm, and .vbproj insteadof .vbp). VB .NET files also use a block structure that allows modules, forms,and classes to be combined in a single file, while Visual Basic 6 used a specialsyntax for its form files (as explained in Chapter 4 of The Book of VB .NET).
The syntax of the language of itself has been updated, and the alterationsrange from minor cosmetic changes to entirely new concepts such as namespaces.All these technical details have a single result: There is no way to open aVisual Basic 6 project in VB .NET. Instead, you have to migrate the project.
The Upgrade Wizard
Migration is a special procedure carried out by Visual Studio .NET's built- inUpgrade Wizard. Essentially, the Upgrade Wizard scans through every line ofevery file in your project. It examines the line, analyzes it for a variety of potentialproblems, and tries to assign an equivalent VB .NET statement. When dealingwith simple programs (for example, utilities that have only one window, orthat have the majority of their capabilities concentrated in a few core procedures),it does remarkably well. However, for complex programs that manage asophisticated user interface and a large amount of data, it works almost embarrassinglybadly. In these cases, migration usually isn't a feasible option.
Migrating a Simple Project
Here's a relatively simple VB 6 project using several concepts that are foreign toVB .NET. The full project is available with the samples for this article.
It starts with a simple startup procedure, contained in a module file:
' VB 6 code.Public Sub Main() frmSplash.Show vbModal frmMain.ShowEnd Sub
The first line launches a window modally-a splash screen with a companylogo. The window uses a timer that unloads itself automatically after a setamount of time:
' VB 6 code.Private Sub tmrClose_Timer() Unload MeEnd Sub
Then the code continues to the second line, launches the main program windownonmodally, and allows the Main subroutine to end. The main window consistsof a simple form with an MSFlexGrid control and a single button. When theuser clicks on the button, a short routine runs, retrieves information from a databasetable, and uses that information to fill the grid, as shown in Figure 14-1.
Figure 14-1: A VB 6 program
The code is quite straightforward. The Form_Load event handler configuresthe grid appropriately, and opens a database connection (using the form-levelvariable con):
' VB 6 code.Private con As ADODB.Connection
Private Sub Form_Load() grid.Cols = 2 grid.Rows = 0 grid.ColWidth(1) = 3000 grid.ColAlignment(0) = 1
Set con = New ADODB.Connection con.ConnectionString = "Provider=SQLOLEDB.1;Data Source=localhost;" & _ "Initial Catalog=Northwind;Integrated Security=SSPI" con.OpenEnd Sub
Of course, opening a database connection in the Form_Load event andclosing it in the Form_Unload event is an extremely bad design practice,because it ties up a limited database connection for an undetermined amount oftime. The individual using the computer could easily forget, leave the computerrunning, and go on holiday, tying up the connection indefinitely. However,there's nothing invalid in this code (and probably nothing unusual either).
The button event handler contains the following code:
' VB 6 code.Private Sub cmdFill_Click()
Me.MousePointer = vbHourglass grid.Rows = 0
Dim rs As ADODB.Recordset Set rs = con.Execute("SELECT * From Customers")
Dim i As Integer Do While rs.EOF <> True grid.AddItem (rs("CustomerID") & vbTab & rs("ContactName")) rs.MoveNext Loop
Me.MousePointer = vbDefault
Don't spend too much time analyzing this code; it uses the ADO library,which is the connection-based predecessor to ADO.NET. ADO works quite a bitdifferently, using a live connection and a MoveNext method to access all theinformation in a Recordset (instead of a Rows collection in a DataSet). This techniqueis similar to the way you use ADO.NET's special DataReader object.
You might also notice that this code uses the MousePointer property tothoughtfully turn the user's mouse pointer into an hourglass, indicating that adatabase operation is underway and that no other user action can be taken untilthe operation is finished.
One additional frill is the form's automatic resizing code:
' VB 6 code.Private Sub Form_Resize() grid.Width = Me.Width - 350 grid.Height = Me.Height - 1200 cmdFill.Top = Me.Height - 1000 cmdFill.Left = (Me.Width - cmdFill.Width - 60) \ 2End Sub
While this code can't stop the form from being made too small, like ourVB .NET code can, it still manages to ensure that the button and grid use theappropriate amount of space. The drawback is the introduction of hard-codedvalues, and generally ugly code, into the Form_Resize event handler. Interestingly,this code is the manual equivalent of two different VB .NET concepts wetake for granted. The grid's size changes, but its position does not; this is anexample of manual docking. The command button's position changes, but itssize is constant, which is an example of anchoring.
Clearly, this is an extremely simple program. However, it does have someaspects that can pose difficulty in the .NET world. They are:
- The use of ADO, which is a database technology built on COM.(Remember, COM isn't native to .NET.)
- The use of the MSFlexGrid control, which is an ActiveX control. Like allActiveX controls, it's also based on COM, and so there is no directequivalent in the .NET class library.
- The treatment of forms. In Visual Basic .NET, forms are classes, and youhave to create an instance of a form before using it. Clearly, the startuproutine in our example doesn't follow these rules. If you'd known that thisprogram was destined for VB .NET migration, you could have programmedaccordingly by dynamically creating forms, even in your VB 6 code.However, if you haven't specifically planned for this step, or if you aredealing with an older application, this technique probably hasn't been used.
Design-wise, there are a couple of other potential problems in this example,such as the way the database connection is held open. However, you can create apoorly designed program in VB .NET with the same ease that you could inVisual Basic 6. This example of poor programming should not affect the migrationprocess.
Importing the Project
To import this project into Visual Basic .NET, all you need to do is open the.vbp file. The Upgrade Wizard will automatically appear, as seen in Figure 14- 2.
Figure 14-2: The Upgrade Wizard
At this point, it's just a matter of clicking on Next several times, and theconversion will begin. Along the way, you will be prompted to choose a newdirectory where the .NET version of our project will be stored (see Figure 14- 3).
Figure 14-3: Creating the new .NET project
Remember, this a complex migration, not a simple File Open operation.The whole process is surprisingly slow, as you'll notice with any real-life application.Even with this simple program, Visual Studio .NET may still take a coupleof minutes to complete the migration.
Once the process is completed, the first thing you should do is read themigration report that has been created for you. You can find this as an HTMLfile (_UpdateReport.htm) in the Solution Explorer. If you double- click on it,you'll see an impressive file-by-file analysis of the project (Figure 14- 4). Eachsection lists migration problems and warnings, and can be expanded or collapsedindividually.
Figure 14-4: The upgrade report
In the case of the simple ADOTest project, no errors are reported, butthree warnings are flagged. You can read a full description of these issues byclicking the provided hyperlink, which takes you to a help topic. Usually, though,you'll want to investigate the code yourself. The appropriate area will be markedwith a comment and another hyperlink to the help topic:
' UPGRADE_WARNING: Form event frmMain.Unload has a new behavior.' Click for more: ms-help://MS.MSDNVS/vbcon/html/vbup2065.htmPrivate Sub frmMain_Closed(ByVal eventSender As System.Object, _ ByVal eventArgs As System.EventArgs) Handles MyBase.Closed con.Close()End Sub
In this case, the Wizard has changed the event handler for the Unload eventto the corresponding Close event. (The Wizard has also taken care of additionaldetails, such as adding the Handles clause and changing the event handler signatureto the .NET standard.)
The second warning is a similar false alarm that alerts us that the Resizeevent may occur when the form is first initialized. The third warning in themigration report informs us, rather cryptically, that the application will endwhen the Main subroutine ends. This warning highlights another differencebetween VB .NET and VB 6: In Visual Basic 6, a program wouldn't end untilevery window was closed. If you used a startup routine to begin your program,the startup routine could end and leave the other windows running to take careof the rest of the program. In VB .NET, applications work a little differently.
If you use a startup subroutine in Visual Basic .NET, the program will closeas soon as the subroutine ends, even if other windows are still open (somewhatlike using the End statement).
Module StartupModule Public Sub Main() frmSplash.DefInstance.ShowDialog()
' Program will end immediately after executing the next line. frmMain.DefInstance.Show() End SubEnd Module
This migration problem could have been avoided if the original programhad displayed both windows modally from the Main subroutine. In that case, thesubroutine would pause until the frmMain window had closed, rather than endingearly. To fix this minor problem, all you need to do is make this modification:
Module StartupModule Public Sub Main() frmSplash.DefInstance.ShowDialog() frmMain.DefInstance.ShowDialog() End SubEnd Module
Forms and the Default Instance
You may notice another unusual feature in this portion of the code: the referenceto DefInstance. A logical .NET startup routine would look more like this:
Public Sub Main() ' Create and show first window. Dim Splash As New frmSplash Splash.ShowDialog()
' Create and show second window. Dim Main As New frmMain Main.ShowDialog()End Sub
The .NET Upgrade Wizard doesn't have enough intelligence to make thischange. In fact, the problem is potentially a lot more complicated. In traditionalVB code, the Wizard really has no way of knowing when you are referring to aform, and when you are trying to create it. In VB 6, a form is loaded automaticallythe first time it is referred to in code, even if it isn't displayed. This systemallows the following kind of logic to work:
' This is VB6 code.frmMain.TextBox1.Text = "Hi" ' The form is created and loaded automatically.frmMain.Show ' Now the form is displayed.
To emulate this logic, the Upgrade Wizard adds a special block of code toevery form under the collapsed Upgrade Support region that works like this:
Private Shared m_vb6FormDefInstance As frmMainPrivate Shared m_InitializingDefInstance As Boolean
Public Shared Property DefInstance() As frmMain Get If m_vb6FormDefInstance Is Nothing _ OrElse m_vb6FormDefInstance.IsDisposed Then m_InitializingDefInstance = True m_vb6FormDefInstance = New frmMain() m_InitializingDefInstance = False End If DefInstance = m_vb6FormDefInstance End Get
Set m_vb6FormDefInstance = Value End SetEnd Property
The logic here is quite interesting. It works like this:
- Every form has a shared property called DefInstance. Because it is shared, itcan be accessed even without creating a form instance. (Also, because it isshared, every class uses the same code for this property, and returns thesame result.)
- When you retrieve the DefInstance property in your code, the Property Getprocedure checks the form's private m_vb6FormDefInstance sharedvariable. This variable is designed to hold a reference to the current form(in our example, frmMain). If this variable hasn't yet been initialized, theProperty Get procedure creates the form automatically, effectivelymimicking the VB 6 form behavior.
- The end result is that whenever a part of your program uses theDefInstance property, it gets the instance of the form stored in them_vb6FormDefInstance variable. If necessary, the form is loaded on thespot automatically.
This special block of "upgrade support" code is a trick that allows you touse forms in the VB 6 way, as long as you call the form's default instance insteadof just the form's class name. If you have the time, it may make sense to gothrough your code, remove the default instance logic, and recreate your formproperly. However, this isn't strictly necessary.
TIP Incidentally, this code violates an important recommendation of object-oriented programming: namely, that retrieving information from a property procedure should never change the state of the object. In this case, retrieving the property of anuninitialized form causes it to be created.