Cover Page

Copyright © 2002 by Microsoft Corporation


LOC Page

PUBLISHED BY
Microsoft Press
A Division of Microsoft Corporation
One Microsoft Way
Redmond, Washington 98052-6399

Copyright © 2002 by Microsoft Corporation

All rights reserved. No part of the contents of this book may be reproduced or transmitted in any form or by any means without the written permission of the publisher.

Library of Congress Cataloging-in-Publication Data
Robinson, Ed, 1967-
Upgrading Microsoft Visual Basic 6.0 to Microsoft Visual Basic .NET / Ed Robinson, Michael Bond,
Ian Oliver.
p. cm.
Includes index.
ISBN 0-7356-1587-X
1. Microsoft Visual BASIC. 2. BASIC (Computer program language). I. Bond,
Michael, 1965- II. Oliver, Ian, 1976- III. Title

QA76.73.B3 R62 2001
005.26'8--dc21 2001054695

Printed and bound in the United States of America.
1 2 3 4 5 6 7 8 9 QWT 7 6 5 4 3 2

Distributed in Canada by Penguin Books Canada Limited.
A CIP catalogue record for this book is available from the British Library.

Microsoft Press books are available through booksellers and distributors worldwide. For further information about international editions, contact your local Microsoft Corporation office or contact Microsoft Press International directly at fax (425) 936-7329. Visit our Web site at www.microsoft.com/mspress. Send comments to mspinput@microsoft.com.

ActiveX, IntelliSense, Microsoft, Microsoft Press, MSDN, the .NET logo, Visual Basic, Visual C++, Visual C#, Visual FoxPro, Visual InterDev, Visual Studio, Win32, Windows, and Windows NT are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries. Other product and company names mentioned herein may be the trademarks of their respective owners.

The example companies, organizations, products, domain names, e-mail addresses, logos, people, places, and events depicted herein are fictitious. No association with any real company, organization, product, domain name, e-mail address, logo, person, place, or event is intended or should be inferred.

Acquisitions Editor: Danielle Bird
Project Editor: Barbara Moreland
Technical Editor: Marzena Makuta
Manuscript Editor: Rebecca Pepper


Dedication

To my wife, Catherine, and in memory of my father, Michael Robinson (1930–1999)

—Ed

To Jane, for her love and support

—Mike

To my loving family and friends who have offered unconditional support throughout… and to Cathy

—Ian


Foreword: Upgrading to Visual Basic .NET

Just as the computing landscape has transformed itself over the last decade, so too has Microsoft Visual Basic. For more than ten years, Visual Basic has fueled the growth of client/server applications, 32-bit programming, and component development. It is the backbone of thousands if not millions of critical business systems throughout the world. Now, as the computing industry looks to address a new set of challenges focusing on Internet, mobile, and Web service development, a new version of Visual Basic—Visual Basic .NET—is once again at the forefront of technology, ready to fuel the growth of the next generation of application development.

Like Visual Basic 1, Visual Basic .NET is both evolutionary and revolutionary. It builds on the key strengths of previous versions of Visual Basic and extends rapid application development to the server and to the Web with XML Web services, Web Forms, trickle-down deployment of Windows applications, and the ability to write Windows services. Visual Basic .NET also delivers some of the most asked-for features: real inheritance and the ability to cleanly implement object-oriented designs, better integration with other languages, seamless deployment, and versioning.

These enhancements to the Visual Basic language, the shared Visual Studio .NET IDE, and the new underlying .NET Framework all introduce discontinuities between Visual Basic 6 and Visual Basic .NET. These discontinuities, while essential to the evolution and importance of Visual Basic as a programming tool, require you to use a particular set of technologies and strategies to facilitate a smooth upgrade to Visual Basic .NET.

In Upgrading Microsoft Visual Basic 6.0 to Microsoft Visual Basic .NET, Ed Robinson, Mike Bond, and Ian Oliver provide this vital information in clear, concise language. In addition to covering the many new concepts and constructs in the Visual Basic .NET language, they discuss the extensive capabilities of the Visual Basic .NET Upgrade Wizard as an essential instrument in your upgrade toolbox. Further, they describe strategies for addressing common upgrade issues, interoperating with existing Visual Basic 6 components, and adding value to your upgraded Visual Basic .NET applications. Finally, this book also serves as a handy reference guide, full of numerous before-and-after code and object model comparisons.

I’ve had the pleasure of working with Ed, Mike, and Ian for the past year on Visual Basic .NET. In this book, they bring you their unsurpassed knowledge of both product and customer in a highly digestible format. Armed with this knowledge and Visual Basic .NET, you will be well on your way to upgrading and enhancing your existing Visual Basic applications and taking the next big evolutionary step in application development.

Ari Bixhorn

Product Manager

Visual Basic .NET


Introduction

Welcome to the world of Microsoft Windows .NET, especially to Visual Basic .NET. Visual Basic is now a .NET development tool. .NET is a platform for developing both Windows and Web applications with seamless integration and universal connectivity through XML Web services.


What Is This Book About?

Many articles have appeared in the press about Visual Basic .NET being radically different from prior versions of Visual Basic. Unfortunately, most of the articles leave you with more questions than answers. What exactly are the changes in Visual Basic .NET? Will it be possible to upgrade existing applications? Why does it break compatibility with Visual Basic 6? Visual Basic .NET includes an Upgrade Wizard, which upgrades up to 95 percent of your project’s code, but what about the other 5 percent? What are the common upgrade issues? How do you resolve them and get your project working in Visual Basic .NET? What about XML Web services and ADO.NET data access—how can existing programs take advantage of these new features?

In this book, we answer these questions and more. We provide a complete guide to upgrading, with both technical and conceptual information. To upgrade your applications, you will need to learn some new skills. We teach these skills and discuss the hows, whats, and whys of upgrading: how to recognize the micro-issues that need a one-line fix, what to do about macro-issues that involve redesign of your application, and why Microsoft made the changes to Visual Basic.

Along the way, we’ll take a few interesting digressions. Because we worked on the team that created Visual Basic .NET, we’ll share a few behind-the-scenes stories about Visual Basic and the development of Visual Basic .NET. These stories are scattered throughout this book, which is divided into four parts:

  • Part I, Introduction to Upgrading What the differences are between Visual Basic 6 and Visual Basic .NET, how to prepare projects for upgrading, what approach to use, and when to leave applications in Visual Basic 6

  • Part II, Upgrading Applications How to use the Upgrade Wizard, what the wizard does and doesn’t do, what errors the Upgrade Wizard generates, and how to interoperate with COM components from VB.NET

  • Part III, Getting Your Project Working How to fix the common problems with forms, language, data access, MTS, and COM+ services; how to get your ActiveX controls and COM components to work; and how to deal with issues common to VB Application Wizard projects

  • Part IV, Techniques for Adding Value How to redesign multitiered applications to use .NET remoting or XML Web services, how to replace ActiveX controls with Windows Forms controls, how to integrate or replace ADO with ADO.NET

In addition to discussing how to upgrade applications, this book is also an upgrading reference. Chapter 8 includes the complete list of errors and warnings that the Upgrade Wizard generates. Chapter 19 maps the Microsoft Windows common controls to equivalent Windows Forms controls. Appendixes A and B give the complete Visual Basic function and object model mappings from Visual Basic 6 to Visual Basic .NET.


Who Should Read This Book

This book is written for the Visual Basic developer who wants to upgrade applications from Visual Basic 6 to Visual Basic .NET. Although we focus on Visual Basic 6, most of the information is also relevant for applications written in earlier versions of Visual Basic, since Visual Basic 6 is essentially a superset of Visual Basic versions 1 through 5.

The content of this book ranges from intermediate to advanced. For example, the chapter on fixing problems with VB Application Wizard projects will probably appeal to more intermediate developers, whereas the one on upgrading COM+ services will be of interest to highly experienced developers.

Two areas of upgrading that we don’t discuss are user controls and WebClasses. As this book went to press, Microsoft had not yet added this functionality to the Upgrade Wizard—it’s coming sometime in the future. If you’re upgrading these technologies, we hope you’ll still find the information in this book useful, since most of the forms material applies equally to user controls, and the chapters on language apply universally to every project you upgrade.


Using the Code Samples and CD

Throughout the text, we refer to code samples both in the book and on the companion CD. To get the most out of this book, make sure you try these samples out for yourself. To do this, we recommend that you use a computer with the following system configuration:

  • Pentium III, 500 MHz or faster

  • 256 MB of RAM or more

  • CD-ROM or DVD drive

  • Microsoft Windows NT 4, Windows 2000, or Windows XP

  • Microsoft Visual Basic 6, with Service Pack 5 or later

  • Microsoft Visual Basic .NET (Professional or Enterprise edition), or Visual Studio .NET. We recommend that you not use the standard edition of Visual Basic .NET, since it doesn’t include the Upgrade Wizard.

Notice that we recommend installing both Visual Basic 6 Service Pack 5 and Visual Basic .NET on the same machine. This is actually a requirement for many upgrades, since the Upgrade Wizard needs to refer to your project’s COM controls and type libraries during the upgrade. The simple rule of thumb is to make sure the program works in Visual Basic 6 on the same machine before upgrading.

This book presents before-and-after code samples in Visual Basic 6 and Visual Basic .NET. To help you tell the difference, they are formatted differently. Visual Basic 6 samples are in normal type:

MsgBox "Hello from Visual Basic 6"

Visual Basic .NET samples are in boldface type:

MsgBox "Hello from Visual Basic .NET"

The CD that accompanies the book also contains a searchable, electronic version of the text. Copy the e-book to your hard drive, and you’ll have a complete upgrade guide at your fingertips.


Let's Begin

Let’s Begin

We hope you enjoy reading this book and, more importantly, we hope it helps you with upgrading applications to Visual Basic .NET. We’ve aimed to keep every chapter as self-contained as possible, so if a chapter is not relevant to what you’re doing at the moment, feel free to jump forward to a later chapter that is more appropriate. Where should you begin? We recommend that you start by reading Chapter 1, as it lays much of the groundwork for the rest of the book and introduces you to the core concepts you will need to upgrade Visual Basic 6 to Visual Basic .NET.


Acknowledgments

The authors wish to thank the Visual Basic .NET and .NET Framework teams for creating such an awesome product for us to write about; our co-workers at Microsoft who helped resolve numerous issues; the team at ArtinSoft S.A. in Costa Rica for all their hard work and dedication in creating a world-class upgrade tool; Ari Bixhorn and the Visual Basic .NET product managers, who through their boundless energy helped us to see that there are no problems, only unfulfilled opportunities; Rob Copeland, Mike Blaylock, and Rick Nasci for agreeing to let us use our personal time to write this book; Danielle Bird, Barbara Moreland, Rebecca Pepper, Marzena Makuta, and Marc Young at Microsoft Press; and our friends and families, who supported us through the nights and long weekends we spent researching and writing.


Introduction to Upgrading

Part I
Introduction to Upgrading
Chapter 1
Visual Basic .NET Is More Than Visual Basic 6 + 1

If you’re familiar with Visual Basic but aren’t familiar with Visual Basic .NET, you may be wondering why we have written a book on upgrading. Surely Visual Basic .NET will open Visual Basic 6 projects as effortlessly as Visual Basic 6 opens Visual Basic 5 projects. “How different can Visual Basic .NET be?” you might ask. So before we start discussing the details of upgrading, let’s clear up any confusion: Visual Basic .NET, the latest version of Visual Basic, is not merely Visual Basic 6 with a few new features added on. Instead, Visual Basic has been thoroughly redesigned and restructured. The language has been modernized, with new, richer object models for data, forms, transactions, and almost everything else. The file formats have also changed.

Unfortunately, these changes mean that Visual Basic .NET is not entirely backward compatible with Visual Basic 6. Projects from previous versions need to be upgraded before they will compile and run in Visual Basic .NET. The Upgrade Wizard handles much of this work for you, but most real-world projects will require additional modifications before they can be run. Some people consider moving applications from Visual Basic 6 to .NET to be a migration rather than an upgrade, but the changes in the language are a logical step, and they make Visual Basic more powerful than ever before.

This is an exciting time for Visual Basic developers. Sure, upgrading applications takes some effort, but on the other hand Visual Basic .NET is incredibly capable, extending Visual Basic’s Rapid Application Development (RAD) model to the server and to the Web. Visual Basic .NET adds more features to Visual Basic than did Visual Basic 2, 3, 4, 5, and 6 combined. Microsoft made the changes because the focus of the language has shifted from previous versions. Whereas Visual Basic 6 was primarily a Windows development tool, Visual Basic .NET is designed to leverage the .NET platform, enabling the user to create Windows applications, console applications, class libraries, NT services, Web Forms applications, and XML Web services—all while allowing seamless integration with other programming languages.

Let’s look a little deeper at Visual Basic .NET to see where it differs from Visual Basic 6. We will look at the following three issues:

  • The development environment

  • The syntax of the language and object models of the classes

  • The run-time behavior of the compiled components

In each of these three areas, Visual Basic .NET departs from the conventions of Visual Basic 6. First, the integrated development environment (IDE) has been redesigned to house all of the Visual Studio languages: Visual Basic, C#, and Visual C++. Second, the language itself has been modernized, removing some keywords, such as GoSub; adding new keywords, like Inherits; and changing the meaning of other keywords, like Return and Integer. Finally, the run-time behavior of the compiled components is different. .NET applications are free-threaded; Visual Basic .NET projects are compiled to “assemblies” rather than to familiar Win32 applications, and variables and objects are garbage-collected, meaning that they lack a deterministic lifetime. A noticeable effect of this last change is that the class Finalize event is not triggered until sometime after the object is actually destroyed. We will talk more about these differences and about what .NET is in Chapter 2. For now, let’s continue to look at what the changes mean to the Visual Basic developer.

With so much that is new, how familiar will it all be to traditional Visual Basic users? To what extent can you leverage your existing Visual Basic skills when you move to Visual Basic .NET? To answer these questions, let’s take a quick look at the history of Visual Basic.

In 1991, Microsoft released Visual Basic 1, which opened the doors to Windows RAD. Visual Basic 1 was an instant success, and it’s easy to see why. Before Visual Basic, developers had to write WndProc handlers, work with pointers, and know when to apply the Pascal calling convention to methods. Visual Basic took over the handling of all of these details, allowing developers to concentrate on building business objects instead of writing the basic plumbing in every program.

In Visual Basic versions 2 though 6, Microsoft kept the underlying architecture of the product the same and simply added new features. Visual Basic 2 and 3 introduced the property grid, Data Access Objects (DAO) database programming, and object linking and embedding (OLE), resulting in a great set of features for Windows 3.1 programming. In 1995, Microsoft released Visual Basic 4, which enabled developers to write 32-bit EXEs, ActiveX controls, and class libraries. The year 1995 also saw the explosion of the Internet, and people began wanting to build Web sites. With versions 5 and 6, Visual Basic added its own flavor of Web development—WebClasses, ActiveX documents, and Dynamic HTML (DHTML) pages—yet for the most part, it still remained a Windows development tool.

It’s interesting to compare Visual Basic 1 with Visual Basic 6 to see how far the language has come. Visual Basic 1 had no IntelliSense, no support for open database connectivity (ODBC), no classes, limited debugging, no support for COM components, no Property Browser, no Web development features, and it created only EXEs. Visual Basic 6 had come a long way from the basic forms and modules development of version 1, yet it still had the spirit of Visual Basic. Visual Basic .NET also has that spirit: It has the same human-readable language, case insensitivity, support for late binding, automatic coercions, and familiar Visual Basic keywords, functions, and constructs, like Left$, MsgBox, and On…Error…GoTo. If you’re a Visual Basic 6 programmer, it’s a comfortable step to Visual Basic .NET. Yes, there are new concepts to learn, but your existing knowledge of Visual Basic is a great foundation.

After the release of Visual Basic 6, Microsoft was faced with a challenge. Developer needs were changing. More and more programmers were developing for the Web, and the Web development capabilities built into Visual Basic 6 were not addressing their needs. Visual Basic’s DHTML pages and ActiveX documents were client-side technologies, meaning that both the component and the Visual Basic runtime had to be installed on client machines. Visual Basic’s WebClasses, a server-based technology, stored state on the server and wasn’t scalable. In addition, the design experience for both WebClasses and DHTML pages could only be described as rudimentary! In short, the technologies were too limiting. Internet developers wanted “thin” clients, not Visual Basic downloads. They wanted code that ran on the server. They wanted security, and they needed scalability, since the more successful a Web site was, the more people would use it concurrently, and therefore the more capacity it had to have.

Clearly, a better architecture was needed. Programmers had also been asking for some significant new language features: inheritance, easier access to the underlying platform, and a solution for the many different component versioning problems that had collectively been labeled “DLL hell.” When looking for a solution to these problems, Microsoft also saw the opportunity to create a unified framework for developing applications. To understand why such a framework was desirable, consider that developers wanting to create forms for Windows in Visual Basic 6, Visual C++, and Microsoft Office Visual Basic for Applications (VBA) had to learn a different forms package for each language. If only there was a common forms package for all these products. Life would be so much simpler! This objective and others led Microsoft to develop a common framework available to all .NET languages.

One side effect of giving all languages access to a common framework is that each language must support the same data types. This support prevents the sort of headaches familiar to anyone who has tried to use Windows APIs from Visual Basic 6. Once all languages support the same data types, it’s simple to add cross-language interoperability: inheritance, debugging, security access, and an integrated compilation process. As you can see, the benefits of such a system would be amazing—and that system is exactly what the .NET platform is: a multiple-language system with a common forms package, set of base classes, and data types. For Visual Basic to be part of this revolution meant more than just changing the language—it meant reconceptualizing it from the ground up for the .NET platform.


Why Break Compatibility?

Why did Microsoft redesign and restructure the language? Why couldn’t it add these new features and still keep compatibility with Visual Basic 6? There are several reasons for this, as we discuss in the sections that follow.

Adding New Features

Some of the new features in Visual Basic .NET could not have been added without a redesign. Adding visual inheritance and accessibility support to the forms package required redesigning the forms object model. Adding Interface statements and attributes to the language made the language more powerful by enabling a greater degree of fine-tuning but required changing the language and file formats. Fixing “DLL hell” meant that versioning and deployment had to be redesigned.

By far the biggest reason for the changes, however, was the need to integrate Visual Basic with the .NET platform. Cross-language inheritance, debugging, and unfettered access to the underlying APIs required the standardization of data types across languages, which meant changing arrays to be zero based and removing fixed-length strings from the language. Redesigning the Web and data access classes to be more scalable meant even more changes from Visual Basic 6.

Fixing the Language

Visual Basic has grown over time, and as the language has been extended, some areas have become inconsistent and problematic. A good example of such an area is default properties. The rules for when an assignment is to be a default property and when it is to be an object have become inconsistent. Consider the following Visual Basic 6 example, where Form1 is a form in the current project:

Dim v As Variant
v = Form1

This code causes an error because Visual Basic 6 tries to assign the default property of the form (the controls collection) to the variable v. Contrast this behavior with the following Visual Basic 6 code:

Dim v As Variant
Set v = Form1

In this example, v is assigned the value Form1. In both examples, the right side of the expression is exactly the same, yet the value changes depending on the context. To anyone who didn’t write the code, it’s unclear from looking at the code what is being assigned: the object or the default property of the object. In Visual Basic .NET, parameterless default properties are not supported and must be resolved.

Another example of an inconsistent feature is the New statement. Consider the following Visual Basic 6 code:

Dim c1 As New Class1
Dim c2 As Class1: Set c2 = New Class1

At first glance, the two lines seem to do exactly the same thing. Both c1 and c2 are being set to new instances of Class1. Yet the two lines have quite different behavior. The statement

Dim c1 As New Class1

means that the variable will be re-created if it is set to Nothing and subsequently reused, whereas the effect of

Dim c2 As Class1: Set c2 = New Class1

is that c2 is created once. If c2 is set to Nothing, it will not be re-created automatically if it is referenced again. This subtle difference in behavior can lead to hard-to-find bugs. In Visual Basic .NET, both statements cause one instance of the class to be created. If the class is destroyed, it is not automatically re-created if it is referenced again.

Modernizing the Language

Another reason for breaking compatibility is to modernize the language. For example, the meaning of Long is now 64 bits, Integer is 32 bits, and the keyword Type has been changed to Structure. Some of these changes we can probably attribute to the “floodgate effect.” Once Microsoft opened the floodgates to new features and changes to fix the language, it became more acceptable to make other changes that were not quite as critical.


It Is Still Visual Basic

Despite the changes, programmers will still recognize the Visual Basic they know and love. Let’s now look at what changes you will expect to see moving to Visual Basic .NET.

Expect Subtle Differences

Visual Basic .NET has been rebuilt for the .NET platform. What does this statement mean? It means that the product has been rewritten from the ground up. One of the side effects of rewriting Visual Basic is that any similarities with previous versions of the language had to be added intentionally—you don’t get them for free, as you do when you simply add new features to an existing code base. A programming language is composed of a million subtle nuances: the behavior of the Format function, the order of events on a form, and the undocumented hacks that are possible, like subclassing a form’s message loop. Some of these subtleties are not exactly the same in Visual Basic .NET, and after upgrading an application, you may find small differences in the way the application works.

A good example is the Currency data type. In Visual Basic 6, the Currency data type has 4 digits of precision. In Visual Basic .NET, the Currency data type is renamed Decimal and has 12 digits of precision. If you run the following line of code in Visual Basic 6:

MsgBox( CCur(10/3) )

it produces 3.3333. If you run the equivalent line of code in Visual Basic .NET,

MsgBox( CDec(10 / 3) )

the result is 3.333333333333. This is not a huge change, but it underlies a principle of upgrading: Visual Basic .NET is subtly different from Visual Basic 6, and therefore upgraded applications will be different from their Visual Basic 6 counterparts in subtle ways. In most cases you will not notice the difference, yet it’s important to be aware of the changes and to test your applications thoroughly after upgrading them. Chapter 2 examines the differences between languages in greater depth. For now, let’s turn our attention to upgrading.

The Decision to Break Compatibility

When did Microsoft decide to break compatibility with Visual Basic 6? It was actually in early December 1999, during the development of Visual Basic .NET. Until that time, Visual Basic .NET was being developed to support the notion of “Visual Basic 6 sourced” projects that allowed you to edit and compile Visual Basic 6 projects in Visual Basic .NET. These projects would have a compatibility switch turned on, meaning that the language would be backward compatible with Visual Basic 6 and would even have access to the old Visual Basic 6 forms package.

By the end of 1999, it was obvious that this strategy wasn’t working. Little differences were slipping through: The old forms package could not be fully integrated into .NET, and the Visual Basic 6 sourced projects could not use some of the new features of the .NET platform. At that point Microsoft made the decision to break compatibility and instead concentrate on ensuring that people could upgrade their projects from Visual Basic 6 to Visual Basic .NET.

Plan for a 95 Percent Automated Upgrade

The effect of the changes and subtle differences in Visual Basic .NET is that, unlike previous versions of Visual Basic, most real-world projects cannot be upgraded 100 percent automatically. To understand why, consider that for a 100 percent upgrade there has to be a one-to-one correlation between every element of Visual Basic 6 and a corresponding element in Visual Basic .NET. Unfortunately, this correlation does not exist. The upgrade process is closer to 95 percent, meaning that the Visual Basic .NET Upgrade Wizard upgrades 95 percent of your application, and you modify 5 percent of the application to get it working. What does 5 percent mean? If it took you 100 days to write the original Visual Basic 6 application, you might expect to take 5 days to upgrade it. This number is not set in stone—some applications are easier to upgrade than others, and the experience of the person doing the upgrade is an important factor. To prepare yourself, make sure you familiarize yourself with Part IV. It discusses how to design your Visual Basic 6 applications to make the upgrade process much smoother.

Part III of this book is devoted to helping you learn how to make the 5 percent modifications, since this is the essential skill you’ll need for upgrading. Once you’ve gotten your application working, Visual Basic .NET has a bunch of exciting new features that you can add to your application straight away. Chapter 18 of this book discusses some common ways you can add value to your upgraded application. In fact, we encourage you to think of the upgrade as occurring in three steps:

  1. Use the Upgrade Wizard to bring your application into Visual Basic .NET.

  2. Make the modifications to get your application working.

  3. Start adding value with the great new features of Visual Basic .NET.


Why Should I Upgrade?

If it requires work to upgrade your applications from Visual Basic 6 to Visual Basic .NET, you may wonder, “Is upgrading worth the trouble? Why should I bother to upgrade an application that requires modifications when it works in Visual Basic 6 today?” The main reason for upgrading is to take advantage of the new features of Visual Basic .NET. What are these new features? Listing them all would be a book in itself. The following sections discuss some of the features that people commonly add to their upgraded applications.

New Language Features

Visual Basic .NET adds a number of new language features that make the language more powerful and will forever dispel the myth that Visual Basic is a “toy” programming language.

Inheritance

For years we developers had been asking Microsoft to add “real” inheritance to Visual Basic. Sure, Visual Basic 6 supports interface inheritance, but we wanted more; we wanted implementation inheritance. We wanted to benefit from code reuse and to truly implement object-oriented designs. Visual Basic .NET fully supports inheritance, via the new Inherits keyword. You can inherit classes from within your own application, from other applications, and from .NET components written in other languages. You can even use inheritance in forms to inherit the layout, controls, and code of another form. This is called visual inheritance. The following code illustrates the use of the Inherits keyword:

Public Class BaseClass
End Class

Public Class InheritedClass : Inherits BaseClass
End Class
Interfaces in Code

Along with “real” inheritance, Visual Basic .NET still supports interface inheritance and improves on it by providing the Interface keyword. The Interface keyword defines the interfaces in code. Your classes then implement the interfaces, as in the following example:

Interface myInterface
   Function myFunction()
End Interface
Public Class myImplementedClass
   Implements myInterface
   Function myFunction() _
   Implements myInterface.myFunction
      'Some Code
   End Function
End Class
Structured Exception Handling

In addition to supporting the familiar On Error GoTo error catching, Visual Basic .NET provides a Try...Catch...End Try exception-handling block that adds error handling. This construct allows you to embed code within an error-handling block. A great use for this type of block is to create a global error handler for your application by including a Try…Catch block in the startup object such as Sub Main. In the following example, Sub Main opens a new instance of Form1 and will catch and report any errors that are thrown anywhere in the application:

Sub Main()
   Try
      Windows.Forms.Application.Run( _
      New Form1())
   Catch ex As Exception
      MsgBox(ex.Message)
   End Try
End Sub
Arithmetic Operator Shortcuts

All arithmetic operators in Visual Basic .NET now have shortcuts that let you operate on and assign the result back to a variable. For example, in Visual Basic 6, you might write

Dim myString As String
myString = myString & "SomeText"

In Visual Basic .NET, you can write this in a much more elegant format:

Dim myString As String
myString &= "SomeText"

These expression shortcuts apply to &=, *=, +=, -=, /=, \=, and ^=. Note that you can also use the old Visual Basic 6–style expressions.

Overloaded Functions

Visual Basic .NET introduces function overloading. With overloading, you can declare multiple functions with the same name, each accepting a different number of parameters or accepting parameters of different types. For example, suppose that you have a method that deletes a customer from a database. You might want to create two versions of the deleteCustomer method, one to delete a customer based on ID and one to delete a customer by name. You can do so in Visual Basic .NET as follows:

Sub deleteCustomer(ByVal custName As String)
   'Code that accepts a String parameter
End Sub
Sub deleteCustomer(ByVal custID As Integer)
   'Code that accepts an Integer parameter
End Sub
Attributes

Visual Basic .NET also now includes attributes. Attributes give one the ability to fine-tune how an application behaves. They modify the behavior of code elements and can be applied to methods, classes, interfaces, and the application itself. You can use attributes to explicitly declare the GUID for a class or to define how a variable should be marshaled when it is passed to a COM object. Suppose, for example, that you have written a common utility function that you want the debugger always to step over, rather than step into. The DebuggerHidden attribute allows you to do this:

<DebuggerHidden()> _
Function nToz(ByVal input) As Integer
   If Not IsNumeric(input) Then input = 0
   Return Input
End Function
Multithreading

By default, your Visual Basic .NET applications are single threaded, but the language has new keywords that allow you to spawn new threads. This ability can be very useful if you have processes that take a long time to complete and that can run in the background. The following example creates a new thread and uses it to run the subroutine loadResultsFromDatabase:

Sub Main()
   Dim myThread As New Threading.Thread( _
   AddressOf loadResultsFromDatabase)
   mythread.Start()
End Sub
Sub loadResultsFromDatabase()
   'Some Code
End Sub
Reduced Programming Errors

Visual Basic .NET helps you reduce programming errors by supporting stronger type checking. For example, if you use the wrong enum value for a property or you assign a variable type to an incompatible type, the compiler will detect and report it at design time. With ADO.NET, you can add strongly typed datasets to your application, and if you refer to an invalid field name, it will be picked up as a compile error instead of a run-time error. These features allow you to catch errors as you write your program. For the ultimate in strong type checking, you can use Option Strict On in your application, which prohibits late binding and ensures that you use conversion functions whenever you assign a variable from one type to another. This feature can be useful in applications that don’t use late binding, but it enforces a stricter and more verbose coding standard than many developers are used to.

The .NET Framework

In addition to the familiar VBA library of functions, such as Left$, Right$, and Command$, and the Win32 APIs, Visual Basic .NET has access to the .NET Framework, which is designed specifically for Visual Basic, C#, and the other .NET languages. The .NET Framework is a collection of more than 3800 classes and 28,000 methods for forms, graphics, XML, Internet development, file access, transactions, and almost everything else you can think of. The set of .NET classes you will most likely become familiar with first is the new forms package—Windows Forms—which looks a lot like the familiar Visual Basic 6 forms but which is implemented as a set of .NET classes available to all languages. The next section describes the features of this package.

Windows Forms

Windows Forms is a new forms development system that replaces the old Visual Basic forms. Along with features that make it powerful and easy to use, Windows Forms is available to all Visual Studio .NET languages.

Faster Development

Windows Forms in Visual Basic .NET has several features that speed up development. An in-place menu editor and visual editing of tab orders make form design easier. Control anchoring allows you to remove all your old resizing code and instead visually anchor the controls so that they remain a fixed length from the edge of the form and resize whenever the form resizes. Visual inheritance allows you to inherit the controls, properties, layout, and code of a base form. A good use for this feature is to define a standard form layout for an application, with a standard size, header, footer, and close button. You can then inherit all forms from this standard form.

GDI+

GDI+ allows you to add rich visual effects to your application. For example, to make a form semitransparent, you would place the following line of code in the form load event:

Me.Opacity = 0.5

Figure 1-1 shows the effects of visual inheritance and GDI+ semitransparency in a Windows application.

Figure 1-1
Visual inheritance and GDI+ semitransparency.
Internationalization

Windows Forms has built-in support for internationalization. To add support for other languages, you set the Localizable property of the form to True, set the Language property to the desired language, and then change the Font and Size properties of the form and controls. Every change that you make is saved as specific to the current locale. For example, you can have different-sized controls with different text for both Spanish and English.

New Web Development Features

Visual Basic .NET offers many enhancements to Web development. Two of the most significant involve XML and Web Forms.

Better Support for XML

Visual Basic .NET has designers that allow visual editing of HTML documents, XML documents, and XML schemas. In addition, there are .NET Framework classes that support serializing and deserializing any .NET class to and from XML. Visual Basic .NET can create XML Web services that use HTTP to pass XML backward and forward to other applications. If your application uses XML, Visual Basic .NET has great support for it. If you’re looking to add XML support to your application (or to learn how to use XML), Visual Basic .NET is a great tool for doing so.

Web Services and Web Forms

Visual Basic .NET allows you to add Web services to your application. As you will see later in this book, in many cases you can actually convert your business objects to Web services. You can also easily add a Web Forms presentation layer that leverages your Visual Basic 6 or upgraded business objects. Visual Basic .NET makes Web development as easy as Windows development.

Better Development Environment

Along with language, form, and Web development enhancements, the IDE in Visual Basic .NET has a number of new features.

Cross-Language Interoperability

Visual Basic .NET is designed for cross-language interoperability. Because .NET unifies types, controls and components written in one language can easily be used in another. Anyone who has struggled to get a Visual Basic 6 user control to work in a Visual C++ project will immediately recognize the benefits of this. You can inherit from a class written in any other language built on the .NET platform or create components in other languages that inherit from your Visual Basic classes. Visual Studio .NET provides a single environment for developing and compiling multilanguage applications. Using the unified debugger, you can step from a component written in one language into another. You can also debug COM+ services and step into SQL Server stored procedures.

Background Compiler and Task List

Visual Basic .NET has a compiler that continually works in the background. Compilation errors are flagged in code in real time with blue squiggle underlines. The Task List updates in real time with the list of compilation errors, and it also shows any ToDo comments you add to your code. ToDo comments are a great way to keep track of what you still need to do. For example, if you add a button to a form, and plan to finish the click code later, you can add a comment like

   'TODO: Finish the code later

and the statement appears in the Task List. You can filter the Task List by task type and even choose what sort of comments are shown using the Task List pane. Figure 1-2 shows the Visual Basic .NET Task List.

Figure 1-2
ToDo comments in the Task List.
Is Visual Basic Still the Best Choice for Visual Basic Developers?

If you are faced with learning the new features of Visual Basic .NET, you may ask yourself, “Why should I stick with Visual Basic? Why don’t I choose another language instead? Why not move to C# (a new language in Visual Studio .NET derived from C++)?” Although the choice is yours, we should point out that Visual Basic .NET is now as powerful as C#, Visual C++, or any other language. All the .NET languages have access to the same .NET Framework classes. These classes are powerful, and they allow Visual Basic .NET to smash through the “glass ceiling” of previous versions.

Visual Basic .NET also keeps the spirit of Visual Basic alive. It is designed by Visual Basic developers for Visual Basic developers, whereas a language like C# is designed for C developers. Each language has some unique features that are not available in other languages. Let’s compare C# with Visual Basic to see what makes each language unique.

Features Found in C# but Not in Visual Basic .NET

The C# language supports pointers. Pointers allow you to write “unsafe” code that modifies memory locations directly. The following code shows how to use pointers in C#:

unsafe static void Main(string[] args)
{
   int myInt = 5;
   int * myptr =  & myInt;
   * myptr = 55;
   Console.WriteLine(myInt.ToString() );
}

Although Visual Basic .NET doesn’t support pointers in the language itself, as you will see in Chapter 16, you can still access memory locations using methods of the .NET Garbage Collector class. C# also supports document comments that allow you to embed self-documenting comments in your source code. These comments are compiled into the metadata of the component and can be extracted and built into help files.

Features Found in Visual Basic .NET but Not in C#

Visual Basic .NET’s most appealing feature is the human-readable Visual Basic language, which is case insensitive with great IntelliSense. If you declare a variable as MyVariable and then later change the case to myVariable, Visual Basic .NET automatically changes the case of all occurrences in the code. C# and Visual C++ don’t do this. In fact, C# treats MYVariable and myVariable as two separate variables. Most Visual Basic programmers have grown to know and become comfortable with case-insensitive behavior and will find Visual Basic .NET the most natural language to use.

Visual Basic .NET also supports late binding and optional parameters. C# supports neither of these. In addition, Visual Basic supports automatic coercions between types. For example, in C#, you cannot assign a Long to an Integer without using a conversion method (since it may cause an overflow). Visual Basic allows narrowing conversions like this one, since in most cases overflows don’t occur. If you want to prevent automatic coercions, you can use a new compiler option, Option Strict On, that enforces C-like strict type coercion.

Visual Basic has richer IntelliSense and better automatic formatting than any other language. It automatically indents code, corrects casing, and adds parentheses to functions when you press Enter.

The result is a true Visual Basic experience, enhanced by background compilation as you type. For example, if you misspell the keyword Function, as in

Funtion myFunction

as soon as you move off the line, the compiler parses it and puts a compiler error in the Task List. It also underlines the word “Funtion” with a blue squiggle indicating the location of the compile error. As soon as you correct the line, the compiler removes the Task List item and erases the underline. Background compilation helps you write better code and is unique to Visual Basic—no other language has a background compiler.

The language you use is a matter of choice. However, if you enjoy programming in Visual Basic, you will find Visual Basic .NET a great experience and the best upgrade ever.


Conclusion

Whew—it’s time to breathe. We’ve covered a lot of ground in this chapter. First we established that Visual Basic .NET is not 100 percent backward compatible with Visual Basic 6. We then took a lightning tour of the history of Visual Basic and saw that, although it is redesigned and restructured, Visual Basic .NET is part of the natural progression of Visual Basic. We looked at some of the differences between Visual Basic 6 and Visual Basic .NET and discussed some of the new features you can add to your upgraded applications. We also covered how you can add value to your upgraded applications and why you should continue to use Visual Basic.

The next chapter takes a deeper look at what the .NET platform is and outlines the significant differences in Visual Basic .NET. Later chapters go further into the upgrading options and describe what you can do to prepare your application for the upgrade to Visual Basic .NET. If you feel like jumping straight into upgrading, you may want to skip to Part II, which starts with a walkthrough of upgrading an application. Welcome to Visual Basic .NET, the future of Visual Basic.


Visual Basic 6 and Visual Basic .NET: Differences

Chapter 2
Visual Basic 6 and Visual Basic .NET: Differences

More than three years ago, the Microsoft Visual Basic team set out to create Visual Basic .NET. At that time managers would kid the development team by saying that they were making only three “simple” changes to Visual Basic 6: a new runtime system, a new development environment, and a new compiler. The Visual Basic development team spent the next three years working on one of these changes: the new compiler. Two other teams provided the development environment and runtime. As we pointed out in Chapter 1, the end result is not a new version of Visual Basic 6 but an entirely new product: Microsoft Visual Basic .NET. The name is important for two reasons. First, Visual Basic is still Visual Basic. Second, Visual Basic .NET is not Visual Basic 7.

This chapter describes the three “simple” changes made to create Visual Basic .NET, including changes to the runtime, the development environment, and the compiler. Microsoft also added other features to Visual Basic .NET along the way, including a new forms package and a new debugger, and these are also discussed in this chapter.


.NET Framework vs. ActiveX

As a Visual Basic developer, you will normally not be concerned with the run-time systems that underlie your Visual Basic applications. Visual Basic 6, for example, makes the details of how ActiveX works largely transparent. The Visual Basic 6 runtime handles all of the messy details that come with implementing an ActiveX-compliant component or application. Licensing, persistable objects, Microsoft Transaction Server (MTS) transaction awareness, and binary compatibility are exposed as simple settings that you can turn on or off. In the same vein, Visual Basic .NET does a good job of hiding the details of what happens under the hood. For example, you do not need to know that you are creating or using a .NET component. A .NET component is just like any other component. It has properties, methods, and events just as an ActiveX component does. Why should you care about the differences between ActiveX and .NET if everything basically looks the same?

On the surface, it doesn’t matter whether you’re using ActiveX, .NET, or your best friend’s component model—they all look about the same. When you dig into the details, however, you need to understand the machine that lies beneath.

If you have ever created an ActiveX control in Visual Basic 6, you may have found that it behaves slightly differently from other ActiveX controls that you bought off the shelf. For example, if you add a BackColor property to your control, you’ll notice when you test it that the color picker is not associated with your control. Digging deeper, you’ll find that you need to change the type of the property to OLE_COLOR and set the Property ID attribute on the property to BackColor. Only then will the property behave like a BackColor property. In solving this problem, you needed to cross over from pure Visual Basic into the world of ActiveX. Although Visual Basic attaches different terminology to options and language statements, you end up being directly or indirectly exposed to ActiveX concepts such as dispatch IDs (DISPIDs), what Visual Basic refers to as property IDs, and OLE types such as OLE_COLOR. Visual Basic, as much as it tries, cannot hide this from you. The more properties, events, methods, and property pages you add to your Visual Basic 6 ActiveX control, the more problems you encounter that require an ActiveX-related solution.

Visual Basic .NET works in much the same way. Most of the time, you are just dealing with Visual Basic. However, when you need your application or component to behave consistently with other types of applications, whether they be standard Windows applications or Web service server objects, you will need a detailed understanding of the environment in which you want your application to run. In the case of .NET applications, you will need to understand how .NET works. The more you know about the target environment, the better equipped you are to create a component or application that behaves well in that environment. So let’s dig a bit and uncover the machine that will run your upgraded Visual Basic .NET application: the .NET Framework.

.NET Framework

The .NET Framework is composed of two general parts: the common language runtime and the Framework class library. The runtime is the foundation upon which the .NET Framework is based. It provides the basic services on which all .NET applications depend: code execution, memory management, thread management, and code security. The Framework class library provides building blocks for creating a variety of .NET applications, components, and services. For example, the Framework class library contains the base classes for creating ASP.NET Web applications, ASP.NET XML Web services, and Windows Forms. It defines all of the value types, known as System types, such as Byte, Integer, Long, and String. It gives you complex structure classes such as Collection and HashTable, as well as the interfaces such as ICollection and IDictionary so you can define your own custom implementation of a standard Collection or HashTable class.

The .NET Framework as a whole, since it works across all .NET languages, can be thought of as an expanded version of the Visual Basic 6 runtime. The common language runtime corresponds to the Visual Basic Language Runtime in Visual Basic 6, which includes the byte code interpreter and memory manager. The counterparts of the .NET Framework class library in Visual Basic 6 include the Visual Basic forms package, the Collection object, and global objects such as App, Screen, Printer, and Clipboard.

The main difference between the two environments is that Visual Basic 6 is a closed environment, meaning that none of the intrinsic Visual Basic types, such as Collection, App, Screen, and so on, can be shared with other language environments, such as C++. Likewise, Microsoft Visual C++ is largely a self-contained language environment that includes its own runtime and class libraries, such as MFC and ATL. The MFC CString class, for example, is contained within the MFC runtime and is not shared with other environments such as Visual Basic.

In closed environments such as these, you can share components between environments only when you create them as ActiveX components, and even then there are a number of limitations. ActiveX components need to be designed and tested to work in each target environment. For example, an ActiveX control hosted on a Visual Basic 6 form may work wonderfully, but the same control may not work at all when hosted on an MFC window. You then need to add or modify the interfaces or implementation of your ActiveX component to make it work with both the Visual Basic 6 and MFC environments. As a result, you end up duplicating your effort by writing specialized routines to make your ActiveX component work in all target environments.

The .NET Framework eliminates this duplication by creating an environment in which all languages have equal access to the same broad set of .NET types, base classes, and services. Each language built on the .NET Framework shares this common base. No matter what your language of choice is—Visual Basic .NET , C#, or COBOL (for .NET)—the compiler for that language generates exactly the same set of .NET runtime instructions, called Microsoft Intermediate Language (MSIL). With each language distilled down to one base instruction set (MSIL), running against the same runtime (the .NET common language runtime), and using one set of .NET Framework classes, sharing and consistency become the norm. The .NET components you create using any .NET language work together seamlessly without any additional effort on your part.

Now that you have seen some of the differences between the Visual Basic 6 ActiveX-based environment and the Visual Basic .NET environment, let’s focus on various elements of the .NET Framework and see how each element manifests itself in Visual Basic .NET. The elements we will be looking at are memory management, type identity, and the threading model. Each of these areas will have a profound impact on the way you both create new Visual Basic .NET applications and revise upgraded Visual Basic 6 applications to work with Visual Basic .NET.

Memory Management

Visual Basic .NET relies on the .NET runtime for memory management. This means that the .NET runtime takes care of reserving memory for all Visual Basic strings, arrays, structures, and objects. Likewise, the .NET runtime decides when to free the memory associated with the objects or variables you have allocated. This is not much different from Visual Basic 6, which was also responsible for managing the memory on your behalf. The most significant difference between Visual Basic 6 and Visual Basic .NET in terms of memory management involves determining when an object or variable is freed.

In Visual Basic 6, the memory associated with a variable or object is freed as soon as you set the variable to Nothing or the variable falls out of scope. This is not true in Visual Basic .NET. When a variable or object is set to Nothing or falls out of scope, Visual Basic .NET tells the .NET runtime that the variable or object is no longer used. The .NET runtime marks the variable or object as needing deletion and relegates the object to the Garbage Collector (GC). The Garbage Collector then deletes the object at some arbitrary time in the future.

Because we can predict when Visual Basic 6 will delete the memory associated with a variable, we refer to the lifespan of a variable in that language as being deterministic. In other words, you know the exact moment that a variable comes into existence and the exact moment that it becomes nonexistent. The lifespan of a Visual Basic .NET variable, on the other hand, is indeterministic, since you cannot predict exactly when it will become nonexistent. You can tell Visual Basic .NET to stop using the variable, but you cannot tell it when to make the variable nonexistent. The variable could be left dangling for a few nanoseconds, or it could take minutes for the .NET Framework to decide to make it nonexistent. In the meantime, an indeterminate amount of your Visual Basic code will execute.

In many cases it does not matter whether or not you can predict when a variable or object is going to be nonexistent. For example, a simple variable such as a string or an array that you are no longer using can be cleaned up at any time. It is when you are dealing with objects that things get interesting.

Take, for example, a File object that opens a file and locks the file when the File object is created. The object closes the file handle and allows the file to be opened by other applications when the object is destroyed. Consider the following Visual Basic .NET code:

Dim f As New File
Dim FileContents As String
f.Open("MyFile.dat")
FileContents = f.Read("MyFile.dat")
f = Nothing
FileContents = FileContents & " This better be appended to my file! "
f.Open("MyFile.dat")
f.Write(FileContents)
f = Nothing

If you run this application in Visual Basic 6, it will run without error. However, if you run this application in Visual Basic .NET, you will encounter an exception when you attempt to open the file the second time. Why? The file handle associated with MyFile.dat will likely still be open. Setting f to Nothing tells the .NET Framework that the File object needs to be deleted. The runtime relegates the object to the garbage bin, where it will wait until the Garbage Collector comes along to clean it up. The File object in effect remains alive and well in the garbage bin. As a result, the MyFile.dat file handle is still open, and the second attempt to open the locked file will lead to an error.

The only way to prevent this type of problem is to call a method on the object to force its handle to be closed. In this example, if the File object had a Close method, you could use it here before setting the variable to Nothing. For example,

f.Close
f = Nothing
Dispose: Determinism in the Face of Chaos

Despite all of the benefits that a garbage-collected model has to offer, it has one haunting side effect: the lack of determinism. Objects can be allocated and deleted by the hundreds, but you never really know when or in what order they will actually terminate. Nor do you know what resources are being consumed or locked at any given moment. It’s confusing, even chaotic. To add some semblance of order to this system, the .NET Framework offers a mechanism called Dispose to ensure that an object releases all its resources exactly when you want it to. Any object that locks resources you need or that otherwise needs to be told to let go should implement the IDisposable interface. The IDisposable interface has a single method, Dispose, that takes no parameters. Any client using the object should call the Dispose method when it is finished with the object.

One More Thing to Worry About

If you’ve been using Visual Basic 6, you’re not accustomed to calling Dispose explicitly on an object when you write code. Unfortunately, when it comes to Visual Basic .NET, you will have to get accustomed to doing so. Get in the habit now of calling Dispose on any object when you are done using it or when the variable referencing it is about to go out of scope. If we change the File object shown earlier to use Dispose, we end up with the following code:

Dim f As New File
Dim FileContents As String
f.Open("MyFile.dat")
FileContents = f.Read("MyFile.dat")
f.Dispose
f = Nothing
FileContents = FileContents & " This better be appended to my file! "
f.Open("MyFile.dat")
f.Write(FileContents)
f.Dispose
f = Nothing
note | The Visual Basic Upgrade Wizard does not alert you to cases in which you may need to call Dispose. We advise you to review your code after you upgrade to determine when an object reference is no longer used. Add calls to the object’s Dispose method to force the object to release its resources. If the object—notably ActiveX objects that do not implement IDisposable—does not support the Dispose method, look for another suitable method to call, such as Close. For example, review your code for the use of ActiveX Data Objects (ADO) such as Recordset and Connection. When you are finished with a Recordset object, be sure to call Close.
When You Just Want It All to Go Away

While your application runs, objects that have been created and destroyed may wait for the Garbage Collector to come and take them away. At certain points in your application, you may need to ensure that no objects are hanging around locking or consuming a needed resource. To clean up objects that are pending collection, you can call on the Garbage Collector to collect all of the waiting objects immediately. You can force garbage collection with the following two calls:

GC.Collect
GC.WaitForPendingFinalizers
note | The two calls to Collect and to WaitForPendingFinalizers are required in the order shown above. The first call to Collect kicks off the garbage collection process asynchronously and immediately returns. The call to WaitForPendingFinalizers waits for the collection process to complete.

Depending on how many (or few) objects need to be collected, running the Garbage Collector in this manner may not be efficient. Force garbage collection sparingly and only in cases where it’s critical that all recently freed objects get collected. Otherwise, opt for using Dispose or Close on individual objects to free up needed resources as you go.

Type Identity

Mike once played on a volleyball team where everyone on his side of the net, including himself, was named Mike. What a disaster. All the other team had to do was hit the ball somewhere in the middle. Someone would yell, “Get it, Mike!” and they would all go crashing into a big pile. To sort things out, they adopted nicknames, involving some derivation of their full names. After that, the game went much better.

Like names in the real world, types in Visual Basic can have the same name. Instead of giving them nicknames, however, you distinguish them by using their full name. For example, Visual Basic has offered a variety of data access models over the years. Many of these data access models contain objects with the same names. Data Access Objects (DAO) and ActiveX Data Objects (ADO), for instance, both contain types called Connection and Recordset. Suppose that, for whatever reason, you decided to reference both DAO and ADO in your Visual Basic 6 project. If you declared a Recordset variable, the variable would be either a DAO or an ADO Recordset type:

Dim rs As Recordset

How do you know which type of Recordset you are using? One way to tell is to look at the properties, methods, and events that IntelliSense or the event drop-down menu gives you. If the object has an Open method, it is an ADO Recordset. If instead it has an OpenRecordset method, it is a DAO Recordset. In Visual Basic 6, the Recordset you end up with depends on the order of the references. The reference that appears higher in the list wins. In Figure 2-1, for example, the Microsoft ActiveX Data Objects 2.6 Library reference occurs before the reference to the Microsoft DAO 3.6 Object Library, so ADO wins and the Recordset is an ADO Recordset type.

Figure 2-1
ADO 2.6 reference takes precedence over DAO 3.6.

If you change the priority of the ADO reference by selecting it and clicking the down arrow under Priority, the DAO reference will take precedence. Clicking OK to apply the change transforms your Recordset type to a DAO Recordset.

Suppose you want to use both types of Recordset objects in your application. To do so, you need to fully qualify the type name as follows:

Dim rsADO As ADODB.Recordset
Dim rsDAO As DAO.Recordset

As you can see, Visual Basic 6 is quite flexible when it comes to using types. Indeed, you could argue that it is too flexible, since you could mistakenly change the type for variables in your code simply by changing the order of a reference.

Visual Basic .NET is stricter about the use of the same types in an application. The general rule is that you need to fully qualify every ambiguous type that you are using. If you are referencing both ADO and DAO, for example, you are forced to fully qualify your use of the types just as you would in Visual Basic 6:

Dim rsADO As ADODB.Recordset
Dim rsDAO As DAO.Recordset
Using Imports

To help you cut down on the number of words and dots that you need to type for each reference, Visual Basic .NET allows you to import a namespace. You can think of it as a global With statement that is applied to the namespace. (A namespace is similar to a library or project name in Visual Basic 6.) For example, type references can become quite bothersome when you are dealing with .NET types such as System.Runtime.Interopservices.UnmanagedType. To simplify the qualification of this type, you can add an Imports statement to the beginning of the file in which it is used:

Imports System.Runtime

This statement allows you to reference the type as Interopservices.UnmanagedType. You can also expand the Imports clause to

Imports System.Runtime.Interopservices.

and then simply refer to Unmanaged Type in your code.

Managing Conflicts

Imports works great until there is a conflict. As we indicated earlier, in Visual Basic 6, the rule is that the type library that is higher in the precedence list takes priority. Visual Basic .NET is different in that all conflicts are irreconcilable. You have to either change your Imports clause to avoid the conflict or fully qualify each type when it is used. Suppose that you add Imports statements for ADO and DAO as follows:

Imports ADO
Imports DAO

Now suppose that you want to declare a variable of type Recordset. As in the volleyball game described earlier, it’s as if you yelled out, “Recordset!” Both ADO and DAO jump in. Crash! Big pile. Any attempt to use the unqualified type Recordset will lead to an error that states, “The name ‘Recordset’ is ambiguous, imported from Namespace ADO, DAO.” To resolve the problem, you need to either fully qualify the type or remove one of the Imports statements.

No More GUIDs

Each ActiveX type, whether it is a class, an interface, an enumerator, or a structure, generally has a unique identifier associated with it. The identifier is a 128-bit, or 16-byte, numeric value referred to as UUID, GUID, LIBID, CLSID, IID, or <whatever>ID. No matter what you call it, it is a 128-bit number.

Rather than make you think in 128-bit numbers, Visual Basic (and other languages) associates human-readable names with each of these types. For example, if you create a Visual Basic 6 class called Customer, its type identifier will be something like {456EC035-17C9-433c-B5F2-9F22C29D775D}. You can assign Customer to other types, such as LoyalCustomer, if LoyalCustomer implements the Customer type with the same ID value. If the LoyalCustomer type instead implements a Customer type with a different ID value, the assignment would fail with a “Type Mismatch” error. In ActiveX, at run time, the number is everything; the name means little to nothing.

In .NET, on the other hand, the name is everything. Two types are considered the same if they meet all of the following conditions:

  • The types have the same name.

  • The types are contained in the same namespace.

  • The types are contained in assemblies with the same name.

  • The assemblies containing the types are weak named.

Note that the types can be in assemblies that have the same name but a different version number. For example, two types called Recordset contained in the namespace ADODB are considered the same type if they live in an assembly such as Microsoft.ADODB.dll with the same name. There could be two Microsoft.ADODB.dll assemblies on your machine with different version numbers, but the ADODB.Recordset types would still be considered compatible. If, however, the Recordset types lived in different assemblies, such as Microsoft.ADODB_2_6.dll and Microsoft.ADODB_2_7.dll, the types would be considered different. You cannot assign two variables of type Recordset to each other if each declaration of Recordset comes from an assembly with a different name.

Threading Model

Visual Basic 6 ActiveX DLLs and controls can be either single threaded or apartment threaded; they are apartment threaded by default. Apartment threading means that only one thread can access an instance of your Visual Basic 6 ActiveX component at any given time. In fact, the same thread always accesses your component, so other threads never disturb your data, including global data. Visual Basic .NET components, on the other hand, are multithreaded by default, meaning that two or more threads can be executing code within your component simultaneously. Each thread has access to your shared data, such as class member and global variables, and the threads can change any data that is shared.

Visual Basic .NET multithreaded components are great news if you want to take advantage of MTS pooling, which requires multithreaded components. They are bad news if your component is not multithread safe and you wind up trying to figure out why member variables are being set to unexpected or random values in your upgraded component.

There is certainly more to cover on this topic, but we’ll leave that for the discussion of threading in Chapter 11where we discuss how to make your multithreaded Visual Basic .NET components multithread safe.


Differences in the Development Environment

Although Visual Basic 6 shipped as part of Microsoft Visual Studio 6, it did not share a common infrastructure with its siblings C++, Visual InterDev, and Visual FoxPro. The only sharing came in the form of ActiveX components and in designers such as the DataEnvironment. Although Visual Studio 6 shipped with a common integrated development environment (IDE) called MSDev, Visual Basic 6 did not participate in MSDev and instead came with its own IDE called VB6.exe.

Visual Studio .NET ships with a single IDE that all languages built on the .NET Framework share called Devenv.exe. The Visual Studio .NET IDE is a host for common elements such as the Windows and Web Forms packages, the Property Browser, Solution Explorer (also known as the project system), Server Explorer, Toolbox, Build Manager, add-ins, and wizards. All languages, including Visual Basic .NET and C#, share these common elements.

Although the Visual Studio .NET IDE provides a common environment for different languages, the various languages are not identical or redundant. Each language maintains its own identity in the syntax, expressions, attributes, and runtime functions you use. When you write code behind a form in a common forms package such as Windows Forms or Web Forms, the code behind the form is represented by the language you are using. If you use Visual Basic, the events for the form are represented using Visual Basic syntax and have event signatures almost identical to those you are accustomed to using in Visual Basic 6. If you use C#, all of the Windows Forms event signatures appear in the syntax of the C# language.

What happened to the common tools that you have grown to love or hate in Visual Basic 6? They have all been rewritten for Visual Studio. NET, as you’ll see next.

Menu Editor

Do you really want to keep using the same clunky Menu Editor that has been around since Visual Basic 1, shown in Figure 2-2? We doubt it. So you’ll probably be pleased to know that you won’t find it in the Visual Studio .NET environment. Instead, you create menus by inserting and editing the menu items directly on a Windows form.

Figure 2-2
Visual Basic 6 Menu Editor.

To insert a new menu in the .NET environment, you drag a MainMenu component from the Toolbox and drop it on the form. Then you select the MainMenu1 component in the component tray, below the form, and type your menu text in the edit box that says “Type Here” just below the title bar for your form. Figure 2-3 shows the Visual Basic .NET menu editor in action.

Figure 2-3
Visual Basic .NET’s in-place menu editor.
Toolbox

The Visual Studio .NET Toolbox is similar to the Visual Basic 6 Toolbox in appearance and use. A difference you will notice right away is that the Visual Studio .NET Toolbox contains the name of each Toolbox item in addition to the icon. Also, depending on the type of project selected, the Toolbox displays a variety of tabs containing different categories of controls and components that you can add to a form or designer. For example, when you are editing a Windows Forms project, the Toolbox will contain categories titled Data, Components Windows Forms, Clipboard Ring, and General. Each tab contains ADO .NET data components such as DataSet and OleDBAdaptor; system components such as MessageQueue and EventLog; and Windows Forms controls and components such as Button, TextBox, Label, and TreeView.

A subtle difference between the Visual Basic 6 Toolbox and the Visual Basic .NET Toolbox relates to references. In Visual Basic 6, any ActiveX control you add to the Toolbox is also added as a reference within your project. The reference exists whether you use the ActiveX control on a form or not. In Visual Basic .NET, the items you add to the Toolbox are not referenced by default. It is not until you place the control on a Windows form or designer that a reference to that component is added to your project.

Because a reference to an ActiveX control automatically exists when you place the control on the Toolbox in Visual Basic 6, you can use the reference in code. For example, suppose you add the Masked Edit ActiveX control to the Toolbox but don’t add an instance of the control to the form. You can write code to add an instance of the Masked Edit ActiveX control to a form at runtime, as follows:

Dim MyMSMaskCtl1 As MSMask.MaskEdBox
Set MyMSMaskCtl1 = Controls.Add("MSMask.MaskEdBox", "MyMSMaskCtl1")
MyMSMaskCtl1.Visible = True

If you attempt to place a Masked Edit ActiveX control on a Visual Basic .NET Toolbar, you will find that if you declare a variable of the ActiveX control type, the statement will not compile. For example, if you attempt to declare the Masked Edit control, using Visual Basic .NET equivalent syntax, the statement won’t compile, as follows:

Dim MyMSMaskCtl1 As AxMSMask.AxMaskEdBox

To declare a variable of the ActiveX control type, you need to place the ActiveX control on a form. You will then be able to dimension variables of the ActiveX control type.

note | After you place an ActiveX control on a Visual Basic .NET form, you will find that you can declare variables of the control type. However, you will not be able to use Controls.Add, as demonstrated in the Visual Basic 6 code above. Controls.Add is not supported in Visual Basic .NET.
Property Browser

The Visual Studio .NET Property Browser is, for the most part, identical in terms of appearance and use to the Visual Basic 6 Property Browser. One minor difference is that the default view for the Property Browser in Visual Studio .NET is Category view, meaning that related properties are grouped under a descriptive category. Alphabetical view is also supported. The Visual Basic 6 Property Browser, on the other hand, defaults to listing properties alphabetically, although it supports a categorized view.

The Visual Studio .NET Property Browser can list all of the properties associated with a control or component. This is not the case when you are using the Visual Basic 6 Property Browser. For example, the Visual Basic 6 Property Browser cannot list object or variant-based properties. It can display properties for a limited number of objects, such as Picture or Font, but it cannot represent an object property such as the ColumnHeaders collection of a ListView control. Instead the Visual Basic 6 Property Browser relies on an ActiveX control property page to provide editing for object properties such as collections.

The Visual Studio .NET Property Browser allows direct editing of an object property if a custom editor is associated with the property or the property type. For example, the Visual Studio .NET Property Browser provides a standard Collection Editor for any property that implements ICollection. In the case of the ColumnHeaders collection for a ListView control, a ColumnHeader Collection Editor, based on the standard Collection Editor, is provided for you to edit the ColumnHeaders collection for the ListView. Figure 2-4 shows an example of editing the ListView Columns property.

Figure 2-4
Visual Basic .NET ColumnHeader Collection Editor in action.
Tab Layout Editor

Your days of clicking a control, setting the TabIndex property, and then repeating the process for the several dozen controls on your form are over. Welcome to the Visual Studio .NET Tab Layout Editor. The Tab Layout Editor allows you to view and edit the tab ordering for all elements on the form at once. To view your tab layout for the current form, select Tab Order from the View menu. A tab index number displays for each control on the form. You can start with the control that you want to be first in the tab order, and then click the remaining controls in the tab order that you want. The tab index numbers will correspond to the order in which you click the controls. Figure 2-5 illustrates the Tab Layout Editor.

Figure 2-5
Visual Studio .NET Tab Layout Editor in action.


Forms Packages

The forms package that you use in Visual Basic 6 to create standard .exe projects or ActiveX control projects is essentially the same package that has been in existence since Visual Basic 1. Visual Basic .NET offers a brand new forms package called Windows Forms. In addition, Visual Basic .NET gives you a second forms package to help in creating Web applications: the Web Forms package.

A Single Standard for Windows Forms

A significant difference between Visual Basic .NET and Visual Basic 6 is that the forms you use with Visual Basic .NET can be used in any type of .NET project. For example, you can use the same forms with both a Visual Basic application and a C# application.

The forms package found in Visual Basic 6 is local to that environment. You can use Visual Basic 6 forms only in Visual Basic 6. Microsoft has tried in the past to create a single, standard forms package that could be shared across multiple products such as Visual Basic, C++, and Office. The initiative, called Forms3 (pronounced Forms Cubed), never realized this goal. Forms3 is alive and well in Office but was never made fully compatible with the Visual Basic forms package.

The Windows Forms package reignites some hope of having a single forms standard applied across various Microsoft products—at least for client applications based on the .NET platform. The ideal of having a single, universal forms package, however, will need to wait; Visual Studio .NET also introduces a separate forms package for Web applications.

Two Forms Packages for the Price of One

One of the appealing features of Visual Studio .NET is that you can create a Web application more quickly and easily than you ever have before. This ease stems from the marriage between the Web Forms package and Visual Basic .NET. For the first time, you can create a Web application in the same manner that you create a Windows client application. You drag and drop controls onto a Web form and then write code to handle the form and control events. All of the skills that you use to create Visual Basic Windows applications can now be used to create Web applications.

note | The Upgrade Wizard will upgrade your client-based applications to use Windows Forms and will upgrade your WebClasses-based applications to use Web Forms.


Language Differences

With each new version of Visual Basic, Microsoft has expanded the language by offering new keywords, new syntactical elements, new conditional statements or modifiers, new attributes, and so on. Visual Basic .NET is no exception. It makes the same types of additions to the language as previous versions have, but on a much grander scale than before. Table 2-1 gives a complete list of keywords that have been added to the Visual Basic .NET language.

Table 2-1 New Keywords in Visual Basic .NET
Visual Basic .NET Keyword | Description
AddHandler and RemoveHandle | Dynamically adds or removes event handlers at runtime, respectively
AndAlso and OrElse | Short circuited logical expressions that complement And and Or, respectively
Ansi, Auto, and Unicode | Declare statement attributes
CChar, CObj, CShort, CType, and DirectCast | Coercion functions
Class, Interface, Module, and Structure | Type declaration statements
Default | Attribute for indexed property declarations
Delegate | Declare pointer to instance method or shared method
GetType | Returns Type class for a given type
Handles | Specifies event handled by a subroutine
Imports | Includes given namespace in current code file
Inherits | Optional statement used with a class to declare classes that inherit from another class
MustInherit | Optional statement used with a class to declare the class as an abstract base class
MustOverride | Optional subroutine attribute that specifies an inherited class must implement the subroutine
MyBase | Refers to base class instance
MyClass | Refers to the current class instance. Ignores a derived class.
Namespace | Defines a namespace block
NotInheritable | Optional statement used with Class to indicate the class cannot be inherited
NotOverridable | Optional subroutine attribute which specifies that a subroutine cannot be overridden in a derived class
Option Strict | Allows you to turn strict type conversion checking on or off. Default is off.
Overloads | Optional subroutine attribute that indicates the subroutine overloads a subroutine with the same name, but different parameters
Overridable | Optional subroutine attribute which specifies that a subroutine can be overridden in a derived class
Overrides | Optional subroutine attribute that indicates the subroutine overrides a subroutine in the base class
Protected | Class member attribute that limits member access to the class and any derived class
Protected Friend | Same as Protected, but expands the scope to include access by any other class in the same assembly
ReadOnly and WriteOnly | Attribute on a Property declaration to specify the property is read-only or write-only
Return* | Statement used to return, possibly with a value from a subroutine
Shadows | Attribute on class members to specify that a class member is distinct from a same-named base class member
Short | 16-bit type known as Integer in Visual Basic 6
SyncLock | Specifies the start of a thread synchronization block
Try, Catch, Finally, and When | Keywords related to structured error handling
Throw | Keyword to throw an exception
* Existing keyword with different behavior. |

Because the Upgrade Wizard generally does not modify or update your code to take advantage of new Visual Basic .NET features, only a subset of the new features come into play after an upgrade. Therefore, we will focus here on some of the general language differences that affect your upgraded Visual Basic 6 application. Chapter 11covers how to deal with these and other language changes in detail. The sections that follow describe the types of changes you will notice when you look at your upgraded Visual Basic .NET code.

All Subroutine Calls Must Have Parentheses

Parentheses are required on all subroutine calls. If you write code that does not use the Call keyword, as follows:

MsgBox "Hello World"

you are required to use parentheses in your Visual Basic .NET code, as follows:

MsgBox("Hello World")
ByVal or ByRef Is Required

In Visual Basic .NET, all subroutine parameters must be qualified with ByVal or ByRef. For example, instead of this Visual Basic 6 code:

Sub UpdateCustomerInfo(CustomerName As String)
End Sub

you will see the following Visual Basic .NET code:

Sub UpdateCustomerInfo(ByRef CustomerName As String)
End Sub

In this case, an unqualified Visual Basic 6 parameter has been upgraded to use the ByRef calling convention. In Visual Basic .NET, the default calling convention is ByRef.

Is That My Event?

Visual Basic 6 associates events by name, using the pattern <ObjectName>_<EventName>. For example, the click event associated with a command CommandButton is

Private Sub Command1_Click()

If you change the name of the Visual Basic 6 event to the name of a subroutine that does not match any other event, it becomes a simple subroutine. The name pattern, therefore, determines whether a subroutine is an event or not.

Handles Clause

Visual Basic .NET does not associate events by name. Instead, a subroutine is associated with an event if it includes the Handles clause. The name of the subroutine can be any name you want. The event that fires the subroutine is given in the Handles clause. For example, the click event associated with a Visual Basic .NET button has the following signature:

Private Sub Button1_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) _
   Handles Button1.Click

Because the event hookup is an explicit part of the event declaration, you can use unique names for your events. For example, you can change the name of your Button1_Click event to YouClickedMyButton as follows:

Private Sub YouClickedMyButton(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) _
   Handles Button1.Click
Event Parameters

Another interesting change related to events is that event parameters are different between Visual Basic 6 and Visual Basic .NET. In Visual Basic 6, the event subroutine contains the name and type of each parameter. In Visual Basic .NET, the parameters are bundled up in an EventArgs object and passed in as a reference to that object. Also, the event subroutine for a Visual Basic .NET event includes a reference to the object that fired the event.

As an example of the different handling of event parameters in the two versions of Visual Basic, consider a form with a Listbox control on it, for which you need to write code to show the checked item.

In Visual Basic 6, you would write the following code:

Private Sub List1_ItemCheck(Item As Integer)
   MsgBox "You checked item: " & Item
End Sub

The equivalent code in Visual Basic .NET is as follows:

Private Sub CheckedListBox1_ItemCheck(ByVal sender As Object, _
   ByVal e As System.Windows.Forms.ItemCheckEventArgs) _
   Handles CheckedListBox1.ItemCheck
   MsgBox("You checked item: " & e.Index)
End Sub

Observe how the item that is checked is passed directly as a parameter in Visual Basic 6. In Visual Basic .NET, it is passed as a member of the passed-in ItemCheckEventArgs object e.

Arrays Must Have a Zero-Bound Lower Dimension

You cannot declare an array in Visual Basic .NET to have a nonzero-bound lower dimension. This requirement also means that you cannot use Option Base 1. In fact, you cannot specify a lower dimension in an array declaration, since it must always be zero. The following types of declarations are no longer supported:

Dim MyIntArray(-10 To 10) As Integer   '21 elements
Dim  MyStringArray(1 To 100) As String   '100 elements

Option Base 1
Dim MyOptionBase1Array(5) As Long   '5 elements (1-5)

Instead, you must use zero-based lower bound arrays, and you need to adjust the bounds to create an array with the same number of elements, such as

Dim MyIntArray(20) As Integer   '21 elements (0-20)
Dim  MyStringArray(99) As String   '100 elements (0-99)

'Option Base 1      'Not supported by VB .NET
Dim MyOptionBase1Array(4) As Long      '5 elements (0-4)

Refer to Chapter 11 for more information on how you can change your array declarations in Visual Basic .NET to be compatible with your array declarations in Visual Basic 6.

Fixed-Length Strings Are Not Supported

Visual Basic .NET does not support fixed-length strings. For example, the following type of declaration is not supported:

Dim MyString As String * 32

Instead, you can dimension the string as a fixed-length array of characters, as follows:

Dim MyString(32) As Char

Or you can use a special class, VBFixedLengthString, defined in the Visual Basic .NET compatibility library. If you use the VBFixedLengthString class the declaration will be:

Imports VB6 = Microsoft.VisualBasic.Compatibility.VB6
   …
Dim MyFixedLenString As New VB6.FixedLengthString(32)

To set the value of a FixedLengthString variable you need to use the Value property as follows:

MyFixedLenString.Value = "This is my fixed length string"

Refer to Chapter 7 for more information about the Visual Basic .NET compatibility library.

Variant Data Type Is Eliminated

Visual Basic .NET eliminates the Variant data type. The main reason is that the underlying .NET Framework does not natively support the Variant type or anything like it. The closest approximation that the .NET Framework offers is the Object type. The Object type works somewhat like the Variant type because the Object type is the base type for all other types, such as Integer and String. Just as you can with a Variant, you can assign any type to an Object. However, in Visual Basic .NET, to get a strong type back out of a Variant to assign, for example, to an Integer or a String, you need to use a type-casting function, such as CInt or CString. With Visual Basic 6, you can write code such as the following:

Dim v As Variant
Dim s As String
v = "My variant contains a string"
s = v

When using Visual Basic .NET, however, you need to use type conversion functions such as CStr, as follows:

Dim v As Variant
Dim s As String
v = "My variant contains a string"
s = CStr(v)

Refer to Chapter 11 for more information on differences between the Visual Basic 6 Variant and Visual Basic .NET Object types.

Visibility of Variables Declared in Nested Scopes Is Limited

Variables that are declared in a nested scope, such as those occurring within an If…Then or For…Next block, are automatically moved to the beginning of the function. The Upgrade Wizard does this for compatibility reasons. In Visual Basic 6, a variable declared in any subscope is visible to the entire function. In Visual Basic .NET, this is not the case. A variable declared within a subscope is visible only within that subscope and any scope nested beneath it.

Take, for example, the following Visual Basic code:

Dim OuterScope As Long

If OuterScope = False Then
   Dim InnerScope As Long
End If

InnerScope = 3

This code works fine in Visual Basic 6, but it will lead to a compiler error in Visual Basic .NET. The compiler error will occur on the last line, InnerScope = 3, and will indicate that the name InnerScope is not declared.

note | The Upgrade Wizard will upgrade your code so that no compiler error occurs. It does this by moving the declaration for InnerScope to the top of the function along with all other top-level declarations. Moving the variable declaration to the top-level scope allows the variable to be used from any scope within the function. This move makes the behavior compatible with Visual Basic 6. It is one of the few cases in which the Upgrade Wizard changes the order of code during upgrade.


Changes in the Debugger

Visual Basic .NET shares the same debugger with all .NET languages in Visual Studio .NET. This debugger works much the same as the one in Visual Basic 6 in that you can step through code and set breakpoints in the same way. However, there are some differences that you should be aware of. These are discussed in the following sections.

No Edit and Continue

What percentage of your Visual Basic 6 application would you say is developed when you are debugging your application in what is commonly referred to as break mode? Ten percent? Forty percent? Ninety percent? Whatever your answer, the number is likely above zero. Any problems you encounter while debugging your Visual Basic 6 application are quite easy to fix while in break mode. This is a great feature that allows you to create applications more quickly. You will miss this ability in Visual Basic .NET.

The Visual Studio .NET common debugger does not allow you to edit your code while in break mode. Any time you encounter code that you want to change or fix, you need to stop debugging, make the change, and then start the application again. Doing so can be a real pain.

The Visual Basic .NET team recognizes that this is not what you would call a RAD debugging experience. The team hopes to offer an updated debugger that supports edit and continue in a future release of Visual Studio .NET. Until then, prepare to break, stop, edit, and rerun your application.

Cannot Continue After an Error

If an error or exception occurs while you are running your application, the Visual Basic .NET debugger will stop at the point where the exception occurred. However, unlike Visual Basic 6, in the Visual Basic .NET debugger you cannot fix your code or step around the code that is causing the error. If you attempt to step to another line, the application will terminate and switch to Design view. You will need to determine the source of the exception, fix your code, and then rerun the application.

No Repainting in Break Mode

In Visual Basic 6, the form and all controls on it continue to display even when you are in break mode. This happens because the Visual Basic 6 debugger lets certain events occur and allows certain code to execute when you are in break mode. For example, painting is allowed to occur.

When debugging your application using Visual Basic .NET, you will find that your form does not repaint. In fact, if you place another window over it while you are in break mode, you will find that the form image does not update at all. The Visual Basic .NET debugger does not allow any events or code to run while you are in break mode.

One benefit of the Visual Basic .NET debugger is that you can debug your paint code and watch your form update as each statement that paints the form executes. It allows you to pinpoint the exact statement in your code that is causing a problem with the display. Because the Visual Basic 6 debugger allows the form to repaint constantly, it is difficult to pinpoint painting problems using the Visual Basic 6 debugger.


Conclusion

As you can see, quite a bit is involved in the three “simple” changes that the teams made to create Visual Basic .NET. Despite all of these changes, you should find the development environment, compiler, and language familiar. The skills that you have acquired using Visual Basic 6 are not lost when you upgrade to Visual Basic .NET. The way you create, run, and debug a Visual Basic .NET application is nearly identical to the process you are already familiar with. After all, Visual Basic is still Visual Basic. The spirit is alive and well in Visual Basic .NET.


Upgrading Options

Chapter 3
Upgrading Options

This chapter provides an overview of your upgrading options and serves as a guide to evaluating your application portfolio. It will help you assess the reasons for and against upgrading applications to Visual Basic .NET. It also explains the role of the Upgrade Wizard and the issues that you need to be aware of when considering upgrading any project. The last part of the chapter describes the decision process for selecting projects to upgrade and criteria for evaluating the suitability of your applications.


Upgrading Is Optional

When evaluating your arsenal of applications, it is important to keep in mind that upgrading to Visual Basic .NET is purely optional. It is highly likely that you will have at least some applications that you will never upgrade. Not all applications will benefit from the new features in Visual Basic .NET. Some applications work perfectly already and there is no reason to change them. When evaluating an application for upgrading, the following four upgrade options are available to you:

  • Leave it in Visual Basic 6.

  • Perform a partial upgrade.

  • Perform a full upgrade.

  • Perform a partial or full upgrade with interoperability.

Don’t Upgrade

Leaving an application in Visual Basic 6 is by far the easiest option. Although you forgo the benefits of Visual Basic .NET and the .NET Framework, you still have an application that runs on a supported platform. This option can be a long-term or short-term one, depending on your business requirements. You may want to put off upgrading for the immediate future, or you may decide that there will never be a need to do it. The one important drawback, worth repeating, is that .NET is the future of the Windows platform. Keeping an application in Visual Basic 6 effectively relegates it to an obsolete platform.

Partial Upgrade

When your application consists of at least two distinct projects (an executable file and one or more ActiveX DLL projects, for example), you might perform a partial upgrade. A partial upgrade occurs when one or more components of an application are upgraded, but not the entire application. This partial upgrade is often a good first step toward a full upgrade, and it gets you many of the benefits without the effort of a complete upgrade.

A partial upgrade involves using the COM interop layer to communicate between the Visual Basic 6 COM and Visual Basic .NET managed components. An example would be a two-tier application using a client-side forms-based project with an ActiveX DLL component containing business logic and data access. You could upgrade the user interface while leaving the ActiveX DLL in Visual Basic 6. The components in the DLL are then accessed by a COM reference in Visual Basic .NET that looks like any other Visual Basic .NET component. Don’t let this fool you, though. COM interop does a lot under the covers to make this all possible.

What Is Managed Code?

Throughout this chapter we use the term managed. Managed is the common term used to describe any code that runs under the common language runtime. It refers to the fact that the runtime automatically “manages” memory, security, versioning, array bound checking, and other run-time services—a capability programming languages traditionally have not provided. For example, both Visual Basic .NET and C# create managed executables.

Unmanaged or native code is any code that runs outside the runtime. Visual Basic 6 creates “unmanaged” or “native” executables, that is, code that runs outside the common language runtime. C++ .NET is unique in that it can create managed executables, unmanaged executables, and executables that contain both managed and unmanaged code.

A partial upgrade is the most likely scenario for your large-scale legacy applications. You may have components that you do not want to change or that cannot easily upgrade to the managed world. By performing a partial upgrade, you get the benefit of improved performance and scalability in the components that require it. A partial upgrade is also the fastest upgrade path because you can choose to leave the difficult-to-upgrade code as is. This approach preserves your investment in existing code and minimizes churn in your application. It does have a cost, however, in that the performance improvements will not be as noticeable as in a fully managed upgrade because of the added overhead of the COM interop marshaling layer. COM interop between .NET components and COM components is slightly slower than managed components working together or COM objects working together. This performance impact is not noticeable in most applications—highly scalable Web sites and applications that do thousands of calls per second to COM methods are the most affected. Chapter 9 gives you more information on COM interop, including how it works and factors that affect its performance.

Applications with a dependency on COM components are harder to deploy and maintain than applications fully upgraded to .NET since COM objects need to be registered and have versioning issues that .NET objects do not.

Complete Upgrade

A complete upgrade is a purebred. It also requires the most effort. It entails not only upgrading code to Visual Basic .NET but also upgrading all of the technologies to their .NET equivalents. An application that qualifies as a complete (or fully managed) upgrade does not use any COM objects; it uses only the .NET Framework. This option is probably the most attractive for developers, and yet it is also the most elusive for larger, more complex applications. More often than not, with a large code base, portions of your application will need to be rewritten or accessed through the COM interop layer.

Upgrade with Interoperability

Interoperability is a good middle-of-the-road option. It usually means that your application has been upgraded to Visual Basic .NET, while core technologies remain in COM components. Whether the core code resides in a custom DLL of your own making or in other components, like ActiveX Data Objects (ADO) or Microsoft XML Parser (MSXML), the COM interop layer still comes into play. In other words, even though your entire application is in Visual Basic .NET, it is not a pure managed application, and, again, there are certain implications for performance and scalability.


Role of the Upgrade Wizard

The Upgrade Wizard imports your Visual Basic 6 applications into Visual Basic .NET. The wizard is activated when you open a Visual Basic 6 project from Visual Basic .NET. Unlike moving from previous versions of Visual Basic to Visual Basic 6, upgrading to Visual Basic .NET is hardly a simple operation involving only minor changes. The Upgrade Wizard has to make significant structural and functional changes to your code. These changes are necessary because of a number of differences in the structure of Visual Basic .NET. You should not expect the Upgrade Wizard to produce an application that works perfectly. In the vast majority of applications, you will need to make modifications and fixes to get everything compiled and running smoothly.

Many of the technologies used in your applications will upgrade easily to their equivalents in the .NET Framework via the Upgrade Wizard. Certain Visual Basic 6 project types are not supported, however. Two project types that do not map to Visual Basic .NET and are not supported by the Upgrade Wizard are ActiveX DHTML page projects and ActiveX document projects. You will have to either rewrite or manually convert these project types to another project type before upgrading.

The Upgrade Report

The Upgrade Wizard produces a report that highlights problem areas in your code that it encountered during the upgrade process. This report includes items that the wizard left alone because they could not be upgraded. The wizard also inserts comments into your code to help guide you through the remainder of the upgrade and provides pointers to additional documentation. Figure 3-1 shows a sample upgrade report.

Figure 3-1
An upgrade report.
Not Just a One-Trick Pony

Without a doubt, the Upgrade Wizard is the best way to move applications from Visual Basic 6 to Visual Basic .NET. Although it is possible to do the work by hand, the Upgrade Wizard can do most or all of the grunt work for you. It enables you, the developer, to focus on a smaller number of details rather than trying to figure out how to cram Visual Basic 6 classes, modules, and forms into a Visual Basic .NET equivalent.

In addition to making the upgrade process easier to manage, the Upgrade Wizard can also be used to analyze existing applications so that you can improve them beforehand, smoothing the eventual upgrade. The upgrade report highlights all of the issues that your application will face. The issues you discover will also help you determine the effort that will be required to complete the upgrade before you start down the upgrade path.


Testing

Testing is extremely important for any application. It is even more important when an application has been heavily modified, such as when you use the Upgrade Wizard. It is dangerous to assume that upgrading a functional Visual Basic 6 application to Visual Basic .NET will result in an application that behaves in exactly the same way after the upgrade. After all, the application is now running in a different environment, and significant changes have been made to the source code. Even though Visual Basic .NET controls and objects are similar to their Visual Basic 6 equivalents, there are differences (in both interfaces and behaviors). These differences place the onus on the developer to thoroughly test the upgraded application to ensure that the desired result has been achieved.

The key point here is to test, test, and test again.


Upgrading from Earlier Versions of Visual Basic

We’ve talked about Visual Basic 6. What about backward compatibility with versions 5 and earlier of Visual Basic? The answer is simple. If you are upgrading any projects from Visual Basic 5 and earlier, you’re on your own. The Upgrade Wizard only supports upgrading Visual Basic 6 projects. Interestingly, the format of Visual Basic 5 and Visual Basic 6 projects is virtually identical. Although the Upgrade Wizard does understand and will attempt to upgrade Visual Basic 5 projects, this is not a supported scenario—some Visual Basic 5 ActiveX controls will not work in Visual Basic .NET. The Upgrade Wizard team has focused solely on testing for Visual Basic 6 projects. If the Upgrade Wizard works well with your Visual Basic 5 project, consider it a bonus. Not everyone will be so lucky.

While it is possible to open Visual Basic 5 projects with the Upgrade Wizard, converting them to Visual Basic 6 first can make the move much easier. To do this, open the Visual Basic 5 project in Visual Basic 6, choosing the Update Controls option. This changes all of your controls to Visual Basic 6 controls (if they are available). Making this step also means that the Upgrade Wizard will generate better and more predictable results.

For projects older than Visual Basic 5, the best option is to upgrade them to Visual Basic 6 first. When you have them working, use the Upgrade Wizard to upgrade the Visual Basic 6 version of the project to Visual Basic .NET.


Selecting Projects to Upgrade

Selecting projects for upgrading is generally a straightforward process. Some applications are no-brainers (they will be upgraded no matter what); others are trivial enough that the upgrade process can be handled in a matter of days. On the other hand, more-complex applications can require much more thought and consideration. Multitiered applications using a wide variety of technologies or applications that are just really big can require an upgrade process that is accomplished in stages over an extended period of time.

For most applications, the selection process goes like this:

  1. Evaluate the benefits of upgrading the application to Visual Basic .NET. What features do you need or would you like to take advantage of?

  2. Evaluate the effort required by the upgrade. What will have to be rewritten or modified?

  3. Develop an upgrade plan. Will the upgrade be handled in stages, or will it be done all at once? Will some or all COM objects be upgraded to their Visual Basic .NET equivalents?

Figure 3-2 illustrates this process.

Figure 3-2
Upgrade decision process.
Evaluating the Benefits

To evaluate the benefits of upgrading, you must carefully examine your application and its architecture, components, and business requirements and determine an upgrade strategy. Ask yourself if it is worth just upgrading to Visual Basic .NET without moving from ADO to ADO.NET or moving from the SOAP toolkit to XML Web services, for example. Decide whether upgrading in stages makes sense.

Whatever governs the project selection process, it is important to keep “value” at the forefront of your mind. Ask yourself whether the upgrade will enable your application to support new features and technologies that provide benefits. Ultimately, if there is no value in either the upgrade experience or the result, you are probably dealing with a project that will live on perfectly happy as a Visual Basic 6 application until it is replaced or circumstances change. The five most common reasons to upgrade to Visual Basic .NET are

  • To standardize all your applications on a single platform

  • To upgrade a pet project

  • To take advantage of new language features and improved application architecture

  • To increase performance and scalability

  • To take advantage of the .NET Framework

The Value of Standardization

While using standardization as a justification for upgrading a project may seem somewhat facetious, it is often important to larger organizations. It’s not unrealistic to assume that a good portion of new Visual Basic development within your organization will be done using Visual Basic .NET. If this is the case, it makes sense to move applications to a single consistent platform. Savings on training is one of the most obvious reasons, but using one platform also enables developers to move between applications without having to switch between their Visual Basic 6 and Visual Basic .NET hats.

The Infamous Pet Project

Let’s face it: We all have pet projects. In fact, the majority of critical business applications started as somebody’s pet project at one point or another. Most of us want our “pets” to survive, grow, and thrive.

New Features, New Language

While it may have some detractors, Visual Basic .NET is pretty cool. It includes a whole host of features that finally make Visual Basic a player in the object-oriented world. Sometimes the benefits of upgrading to Visual Basic .NET are immediate and obvious. A case in point is inheritance. We are all familiar with the practice of implementing common interfaces and code within Visual Basic:

  1. Open a class file.

  2. Select all and copy.

  3. Create a new class file and paste.

  4. Make your changes to adapt this copied class to your needs.

The problem is that this approach generates a whole lot of duplicate code and is a maintenance headache. Inheritance in Visual Basic .NET offers a much better solution.

We found that implementing inheritance in one project containing a host of similar classes that represented various related functional tasks resulted in a 75 percent reduction in overall code size. The benefits were immediate and tangible. Bug fixes were simplified and required less work to implement. (They needed to be fixed in only one place instead of in many.) Upgrading also made it possible to implement a consistent error-handling scheme across all classes, resulting in a much cleaner project.

Performance and Scalability

Do you have a performance-critical application? Does your application need to be scalable, and is performance key to the success of the application? If so, you will definitely want to upgrade your application to Visual Basic .NET. Visual Basic .NET and the .NET Framework have features that can improve scalability and performance in most applications when used correctly. The ADO.NET data access object model, for example, provides in-memory local data caches and a data reader object that is optimized for speed. Also consider that ASP.NET pages are compiled and have improved caching mechanisms and dramatically enhanced session state management capabilities.

Two examples of major contributors to improved application performance are the SqlDataReader and StringBuilder utility classes. Let’s take a quick look at what these features can do for you.

Fast SQL Server Access

The SqlDataReader class is analogous to the Forward-Only cursor Recordset from ADO. It is a network-level data provider that offers a low-overhead object model and fast queries to Microsoft SQL Server databases. The result is that you can often double your data performance just by substituting SqlDataReader for a classic ADO Recordset. For more information on upgrading your existing ADO code to ADO.NET, see Chapter 20.

String Concatenation

Not everyone realizes that a major problem area for performance in Visual Basic 6 is string concatenation. This is a very wasteful operation with no real alternatives, other than rolling your own solution (usually as a C++-based COM component). In Visual Basic .NET, it is still possible to do the same old string concatenation, but there is a new utility object that provides fast and efficient string manipulation: StringBuilder. StringBuilder is a class found in the System.Text namespace.

How Fast Is Fast?

Here is an easy example that demonstrates the performance difference between string concatenation and StringBuilder:

Sub StringTest()
   Dim before, after As DateTime
   Dim diff As Double

   before = Now()
   SlowString()
   after = Now()

   diff = (after.Ticks - before.Ticks) / Math.Pow(10, 7)
   Debug.WriteLine("Standard concat: " & diff)
   before = Now()
   FastString()
   after = Now()

   diff = (after.Ticks - before.Ticks) / Math.Pow(10, 7)
   Debug.WriteLine("String Builder: " & diff)
End Sub

Const loopMax As Integer = 20000
Function SlowString() As String
   Dim str As String = "MyString "

   Dim i As Integer

   Dim result As String = ""
   For i = 0 To loopMax
      result &= str
   Next

   Return result
End Function

Function FastString() As String
   Dim str As String = "MyString "

   Dim i As Integer

   Dim result As New System.Text.StringBuilder()
   For i = 0 To loopMax
      result.Append(str)
   Next

   Return result.ToString()
End Function

When we ran this code on a laptop, the results showed that using StringBuilder was more than 200 times faster than simple string concatenation. In other words the FastString() method can be called approximately 200 times in the same time period it would take SlowString() to complete just once. An added benefit is that the FastString() method required less memory than the SlowString() method.

When you’re concerned about scalability and performance, Visual Basic .NET definitely has a lot to offer.

Plenty More Where That Came From

Of course, the performance improvements are not limited to the SqlDataReader and StringBuilder classes. They are merely two examples from a vast application framework that offers much, much more. Isolate the performance-critical areas of your application, list the technologies that are used, and then research the .NET equivalents. Most of the time, options exist that can dramatically improve the application’s performance.

note | Because of the architectural differences in the common language runtime, ASP.NET, and ADO.NET, it is possible, with a fully upgraded application, to see up to a fivefold increase in performance over an equivalent ASP/Visual Basic 6/ADO application based on real-world systems. A twofold gain is far more typical, however.
The .NET Framework

We’ve talked about the performance advantages of Visual Basic .NET, but the .NET Framework also has many compelling features that are not exclusively performance related. XML Web services are an excellent example of such a feature. Others are remoting, reflection, and a GDI+ graphics model, for starters.

Moving On

That sums up the benefits of upgrading to Visual Basic .NET. If you’re still planning to forge ahead, it’s time to move on to evaluate the effort that the upgrade will require.

Evaluating the Effort Required

Four main physical factors determine an application’s suitability for an upgrade:

  • Architecture

  • Code quality

  • Technologies

  • Size

The sections that follow discuss each of these factors in turn.

Application Architecture

The overall application architecture will play a large part in the determination of whether and how to upgrade an application. An application that is Web based, has a simple architecture, and demands high availability and responsiveness would be a good candidate for a complete upgrade. On the other hand, if the application is form based and has any or all of the following attributes, you might prefer either to take a tiered approach to the upgrade or to forgo the upgrade altogether:

  • Has a small user base

  • Uses the Visual Basic 6 drawing model

  • Contains many unmanaged Win32 API calls

  • Contains old code that uses obsolete language elements

It may be that some or all of your Visual Basic 6 applications can take advantage of the new features in Visual Basic .NET. You may even want to convert your legacy client/server applications to a thin Web-based client model with the improved Web controls.

Although many variations are possible, the following are the four general types of Windows application architectures.

ASP Web applications This type of application is usually performance driven and needs to be scalable to support hundreds or thousands of concurrent users across the Internet or intranets. It is one of the simplest kinds of applications to upgrade to ASP.NET because of the similarities between the two technologies. ASP components on the client end can, with some effort, be converted to ASP.NET using any of the managed languages. Middle-tier components written in Visual Basic 6 can be upgraded using the Visual Basic .NET Upgrade Wizard, the data access components can be converted to ADO.NET, and any third-party components can be accessed through the COM interop layer. By their nature, these applications depend on performance and scalability—two things the .NET Framework delivers. All other factors being constant, this type of application will probably see the biggest benefit from an upgrade. The one downside to upgrading ASP applications is that there is no upgrade tool to do the grunt work for you. The middle-tier components can take advantage of the Upgrade Wizard, but the ASP pages all need to be upgraded by hand.

Forms-based client/server applications A typical application that uses this architecture consists of a fat client, possible middle-tier COM components with business logic, a data access layer, and a connection to a data store. These applications are different from thin client/server applications in that some of the processing and business logic resides in the client. Fat-client systems come in all shapes and sizes, some with ActiveX controls and active documents embedded in a Web browser and others with client-installed executables, COM components, and third-party controls. Unless their design is fairly straightforward, these applications require more work to upgrade to the .NET Framework. Since the middle-tier components are much the same as in a thin-client system, the upgrade of the middle tier will not pose any more of a problem than upgrading a thin-client system. However, upgrading the client may be more difficult than the transition from ASP to ASP.NET for thin-client systems. The difficulty in upgrading the client lies in the fact that Visual Basic has been completely redesigned for Visual Basic .NET, and the functions and controls do not map exactly to their counterparts in Visual Basic 6.

Enterprise architecture with legacy or distributed systems Applications that contain back-end legacy systems, such as mainframes, can greatly benefit from Visual Basic .NET and its new features. Rewriting portions of the system to take advantage of the new XML Web services application architecture can improve code modularity and maintainability and can also increase accessibility to those legacy systems. With XML Web services, it is possible to expose and consume the functionality and data of the legacy system essentially by writing a wrapper component that exposes its interfaces through an XML Web service. This allows local and remote clients or components to easily interact with and program against the legacy system. The problem here is that any performance or scalability benefits would be hard to achieve, since the legacy systems are more often than not the performance bottlenecks. Although there might not be a huge performance benefit, your application can benefit architecturally by becoming more distributed and more flexible. XML Web services enable this by using SOAP as the underlying implementation, which is more flexible than DCOM in many ways.

Stand-alone applications Stand-alone applications are typically form-based applications such as games, graphics programs, controls, or general-purpose utilities. Simple applications are typically easy to upgrade.

Code Quality

Code quality will have a great deal of influence on an upgrade strategy. Some of the factors that will make your Visual Basic 6 application easier to upgrade include a modular, object-oriented design and good programming etiquette. This is not to say that applications that do not follow these principles are not upgradable, but the task will involve more effort. As a test, you can run the Upgrade Wizard and see what issues it discovers. See Chapter 4 for a more in-depth treatment of good coding practices.

Technologies

The technologies or Visual Basic 6 features that an application uses also affect the amount of effort required to upgrade an application. A typical area of difficulty involves the use of either many Win32 declared functions or the Visual Basic 6 graphics model. The Upgrade Wizard can’t really do anything with the declare statements, so it will leave them alone. But they should still work, with a few exceptions (which will be highlighted by the Upgrade Wizard). The Visual Basic .NET graphics model, on the other hand, is so different from what was available in Visual Basic 6 that there is no direct or indirect equivalent. Any code that uses the Visual Basic 6 drawing model will have to be rewritten. There is just no way around this.

Application Size and Scope

This point may be self-evident: a larger code base will take more time to upgrade. When you’re becoming familiar with the .NET Framework, it is best to start with smaller applications. That will give you a chance to become acquainted with the new constructs, and debugging problems will be easier. Take the code size into consideration when screening applications to upgrade.


Developing the Upgrade Plan

Once you have determined which projects to upgrade, the rest of the process becomes a whole lot easier. Keep one thing in mind when working out your upgrade plan: If your application consists of a number of projects, some of which depend on others, upgrade the client project first. Next upgrade the project the client depends on. If this project in turn depends on another project, upgrade the more fundamental one next, and so on. Doing the upgrade in this order means you are moving up the dependency hierarchy. Why choose this order? For the simple reason that it’s the easiest upgrade path. Using COM components from Visual Basic .NET is simpler than using .NET components from Visual Basic 6. The latter option requires changing both the .NET component and the COM component at the same time. It also means dealing with GUID and versioning issues that can make COM development a complicated process. Starting with the client and moving up the dependency hierarchy keeps all of the COM GUIDs the same and avoids COM versioning issues.

Keeping that rule in mind, you’ll find that the rest of the process is simple. Break your application down into its core components, and work out the dependencies. Starting from the client side, decide what components you will upgrade and in what order, and the extent to which each of them will be upgraded (none, partial, full, interop). Large applications will no doubt be handled in stages, since attempting to pull off a full upgrade of all components would be extremely disruptive and would lead to all sorts of testing problems and new bugs. Try to go step by step, testing as you go, to ensure that you maintain a high level of quality.


Conclusion

You have a lot of choices facing you. Not all applications are well suited to being moved to Visual Basic .NET. Others practically compel the move. What you should take away from this chapter is that you should consider applications for upgrading on a case-by-case basis. Of course, there are many good reasons for upgrading applications; just don’t feel that you are being forced to do so (unless, of course, you are). Upgrade only if and when it makes sense. When it does make sense, develop your upgrade plan and determine where you need to focus your efforts.


Preparing Your Project for the Upgrade to Visual Basic .NET

Chapter 4
Preparing Your Project for the Upgrade to Visual Basic .NET

Now that you have decided to upgrade to Microsoft Visual Basic .NET, it’s time to make the modifications necessary to smooth out the upgrade process. In doing so, you need to focus on issues that could get in the way of a straightforward upgrade. If you followed the guidelines in Chapter 3, you should already have a good idea of the potential problem areas in your application. This chapter will help you prepare for your upgrade project by identifying common coding practices that can cause upgrade issues, above and beyond the issues highlighted by the Upgrade Wizard’s upgrade report.

Keep in mind that rather than addressing how to upgrade, this chapter focuses on identifying changes that you can make to existing applications to help the upgrade go more smoothly. There are two parts to this topic. First we’ll discuss the use of up-to-date language and platform features. Then we’ll look at good coding practices and common traps that Visual Basic developers fall into. Where necessary, examples are provided for clarity.


Why Change Anything?

The quality of your upgrade depends greatly on the quality of the application you start with. It is therefore crucial to take the time to make your application more compatible with the upgrade process. You’ll need to replace obsolete language syntax and legacy (pre–Visual Basic 6) controls, modify your coding style, and watch for common traps. These steps are the best way to ensure that your code maintains its current functionality throughout the upgrade. The following sections demonstrate what you can do to improve your application and make it more suitable to an upgrade.


Cleaning Up Legacy Code

Visual Basic 6 is almost a wonder of compatibility; it maintains code compatibility for many language features going all the way back to Visual Basic 1. These features include elements like DefInt, VarPtr, and GoSub…Return that still work, and work reasonably well. But just because they work does not mean that you should use them. These features and others are deprecated in Visual Basic 6, and they have been removed completely from Visual Basic .NET. This section covers the obsolete and deprecated Visual Basic features that you should remove from or replace in your Visual Basic 6 application before upgrading.

VarPtr, DefInt, and Other No-Shows

As we just mentioned, certain language elements are no longer supported in Visual Basic .NET. Some were eliminated because they lead to confusing and cryptic coding styles and are not appropriate for the kind of large-scale distributed application development that is becoming dominant in today’s business environment. Others are just no longer meaningful in Visual Basic .NET. Take, for example, VarPtr. Pointers no longer have any meaning in the .NET world. In fact, pointers defeat the purpose of having a runtime environment like .NET in the first place. The runtime frees the developer from various tasks, and any Visual Basic language features that conflict with not only the runtime but also the goal of a scalable language for the enterprise have been eliminated.

These changes are intended to move the language (and the platform as a whole) forward in the most expeditious, and ultimately beneficial, way possible. The following Visual Basic language features are handled by the Upgrade Wizard but are not recommended for use in your applications:

  • DefInt, DefStr, DefObj, DefDbl, DefLng, DefBool, DefCur, DefSng, DefDec, DefByte, DefDate, DefVar

  • GoTo [LineNum]

  • Imp, Eqv

The following features are simply not handled, and eliminating them is left as a task for the developer:

  • GoSub…Return

  • LSet (for user-defined types)

  • VarPtr, ObjPtr, StrPtr

  • Null and Empty nonzero-lower-bound arrays

Given these changes, where should you start? First you must search out areas in your application that use these language elements and either replace them, or leave them as they are and resolve the issues after you use the Upgrade Wizard. Both approaches are valid. It is often more efficient to fix a problem area before upgrading than to try to sort out the mess afterwards. There are times, however, when no alternatives exist in Visual Basic 6 and the most effective approach is to wait and solve the problem using Visual Basic .NET. This decision is ultimately one that you, the developer, need to make on a case-by-case basis. If you are unsure, it doesn’t hurt to run the Upgrade Wizard just to see what it does, but that is a subject covered in a later chapter. (See Chapter 5)

DAO and RDO Data Binding

Over the years, Microsoft has introduced several data access technologies. Starting with Data Access Objects (DAO) and followed by Remote Data Objects (RDO), Microsoft started to bring data access to the masses with an accessible COM-based approach. The big breakthrough was the introduction of ActiveX Data Objects (ADO), with its flexible provider scheme. ADO was designed to provide a generic replacement for both DAO and RDO. It uses OLE DB to interface to various data providers, from comma-delimited text files to Microsoft Access databases to high-end database servers.

Visual Basic .NET introduces ADO.NET as the next generation of data access. Why does Microsoft continue to introduce new data access technologies? It reflects the fact that data access itself is evolving. Writing scalable Web sites is a different programming problem from querying a Microsoft Access database on the local machine. For this reason, ADO.NET’s n-tier disconnected data access is very different from DAO data binding to an Access database.

You’ll be pleased to know that Visual Basic .NET supports DAO, RDO, and ADO data access code. However, Visual Basic .NET does not support DAO or RDO data binding. What this means is that if your application has DAO or RDO data binding, you should either rewrite it as ADO data binding in Visual Basic 6 or rewrite it as ADO.NET data binding after upgrading the project to Visual Basic .NET.


Good Visual Basic 6 Coding Practices

Now that we have covered the deprecated features in Visual Basic 6, let’s move on to identify the areas where programming practices can interfere with a smooth upgrade. You may need to adjust your code to make it more compatible with the Visual Basic Upgrade Wizard. discusses how to use the Upgrade Wizard as a tool to identify and fix issues in your application. For now we will look at common problematic coding practices that you can correct prior to the actual upgrade. The Upgrade Wizard can identify most of these issues.

Variants and Variables

Visual Basic is an interesting language in that it has a default data type—the Variant type. What this means is that any variable, method parameter, or function return type that is not explicitly specified is considered a Variant. Although this default frees you from having to worry about types when coding—the Variant type can store all the intrinsic types as well as references to COM objects—using it is not the greatest programming practice.

One of the most significant impacts of relying on the Variant type is that you lose compile-time validation. Because Visual Basic cannot know what type you really wanted, it has to generate code to try to coerce your variable to the proper type at run time. It is not always successful, especially if the type you are using cannot be coerced to the expected data or object type.

Good programming practice aside, not explicitly declaring your variable types can generate a significant amount of upgrading work for the developer. This is unfortunate because given explicit types, the Upgrade Wizard can often upgrade variables and controls to their new Visual Basic .NET equivalents. If the meaning of your code is unclear, the Upgrade Wizard will not try to guess your intentions. It will simply leave the code as is and generate a warning. This leaves the task of upgrading the code exclusively to the developer. Why waste time, when the wizard will do the work for you?

Implicit vs. Explicit Variable Declaration

Look at the following code. Do you understand what is going on here?

Function MyFunction( var1, var2 )
   MyFunction = var1 + var2
End Function

Is this method doing string concatenation? Addition? If you can’t be sure, the Upgrade Wizard certainly won’t be. Testing the function shows that it is possible to use a variety of different inputs:

   Debug.Print MyFunction("This and", " that.")
   Debug.Print MyFunction(Now(), 1)
   Debug.Print MyFunction(4, 5)

The following results show in the Immediate window:

This and that.
7/23/2001 8:04:59 PM
 9

An alternative, and more proper, way to define the function would look like this:

Function MyFunction( var1 As String, var2 As String ) As String
   MyFunction = var1 & var2
End Function

or this:

Function MyFunction( var1 As Integer, var2 As Integer ) As Integer
   MyFunction = var1 + var2
End Function

With this explicit definition style, we can see exactly what the programmer intends to happen. We also get the benefits of compile-time validation: The compiler will generate an error if you pass an invalid type to the function (unless it is a Variant). In addition, the Upgrade Wizard will be able to generate the proper equivalent Visual Basic .NET code without a problem.

Abstraction

Abstraction is a fairly simple concept. The idea is to produce an implementation that separates the usage of an object, type, or constant from its underlying implementation. This concept is at the heart of every object-oriented programming environment. Abstraction insulates the developer from changes in the underlying implementation. When you violate this principle, you risk making your application more fragile and prone to errors. There are two common areas where Visual Basic developers get themselves into trouble: constants and underlying data types.

Constants and Underlying Values

Visual Basic 6 defines a whole host of constants. COM libraries make even more constants and enumerated types available through IntelliSense. The standard Visual Basic constants are really just pretty names that hide underlying integer values. Table 4-1 shows an excerpt from the Microsoft Developer Network (MSDN) documentation on mouse pointer constants.

Table 4-1 Mouse Pointer Constants
Constant | Underlying Value | Mouse Pointer
vbDefault | 0 |  | Default
vbArrow | 1 |  | Arrow
vbCrosshair | 2 |  | Cross
vbIbeam | 3 |  | I beam
vbIconPointer | 4 |  | Icon
vbSizePointer | 5 |  | Size
vbSizeNESW | 6 |  | Size NE, SW
vbSizeNS | 7 |  | Size N, S
vbSizeNWSE | 8 |  | Size NW, SE

You use these constants to change the mouse pointer, as in the following two examples:

Screen.MousePointer = vbIbeam
Screen.MousePointer = 3

These two examples achieve the same result. They are equivalent in that they both change the cursor to the I beam. Although they achieve the same result, the second line is hard to read. Unless you are intimately familiar with the MousePointer constants, it is hard to tell at a glance what result that line of code will actually produce. Using a named constant makes your code more readable and keeps the intent clear.

The Upgrade Wizard is unable to guess what you intended if you use an underlying value in place of a constant. When it encounters such a value, it leaves the code in place and inserts a special comment known as an upgrade warning (see Chapter 8 which you then have to resolve on your own. When constants are used properly, the Upgrade Wizard can upgrade code to the Visual Basic .NET equivalent (if it exists) with no warnings.

Use the Proper Constants

A side issue to the notion of underlying values is that constant values can conflict. Remember that the Visual Basic standard constants are actually implemented with integers, and thus constants with unrelated function may have the same value. The problem is that you can often use constants interchangeably. While Visual Basic 6 doesn’t really care—it has no notion of strict constant or enumerated type enforcement, and one integer is as good as another—the Upgrade Wizard will either change the constant definition to a possibly incompatible type in Visual Basic .NET or will not upgrade the offending line of code at all (leaving it in place with a warning).

The main lesson here is that when you use constants, make sure you are using them in the correct context. The following example illustrates this point:

Sub DefaultExample()
   Screen.MousePointer = vbIconPointer
   Screen.MousePointer = adCmdStoredProc
End Sub

Here vbIconPointer is defined as a MousePointer constant, and adCmdStoredProc is defined as an ADO Command object constant used for specifying stored procedures (totally unrelated to the MousePointer and not meaningful in this context). Both of these constants have a value of 4, and it is possible to use them interchangeably in Visual Basic 6. The Upgrade Wizard, however, will attempt to upgrade the constants to their .NET equivalent. Visual Basic .NET performs strict type checking for enumerated constants, and the upgraded code will not compile. Getting this right to start with will avoid this whole class of problems.

Underlying Data Types

Another problematic coding style involves the use of underlying (or implementation) data types instead of proper types. A case in point is the Date data type in Visual Basic 6. The Date data type is implemented as a Double. What this means is that you can coerce a Date to a Double and/or store the contents of a Date in a Double. Some developers may have used a Double directly instead of the Date data type. While this will work just fine in Visual Basic 6, it will cause problems for the Upgrade Wizard (and therefore for you) and will categorically not work in Visual Basic .NET. In the Microsoft .NET Framework resides a new Date object, and the implementation is significantly different (and hidden from the developer). The new Date object supports much larger date ranges and higher-precision time storage than the Visual Basic 6 Date type. The only way to ensure that your code upgrades properly is to always use the Date data type and the date/time manipulation functions. Resist using the underlying Double value.

How to Get a Double Date

Date variables are stored as IEEE 64-bit (8-byte) floating-point numbers (which is the definition of a Double). The Double is capable of representing dates ranging from January 1, 100, to December 31, 9999, and times from 0:00:00 to 23:59:59. The following diagram shows how a Date is stored within said Double.

Try out the following example in Visual Basic 6:

Dim dt As Date
Dim dbl As Double

dbl = 37094.776724537
dt = dbl

Debug.Print dt
Debug.Print dbl

The Immediate window displays the following:

7/22/2001 6:38:29 PM
37094.776724537

You can adjust the date by adding or subtracting any Integer value to the double, and you can alter the time by adding or subtracting any Decimal value less than 1.

Taking advantage of implementation details rather than using the abstracted types defeats the purpose of abstraction in the first place. Abstraction is of significant use to any application because it allows you to change implementation details over time without requiring interface or coding changes. When you violate this rule of abstraction and take advantage of the underlying implementation, you risk having your application crumble to pieces when the implementation changes (as it is likely to do over time).

Early Binding vs. Late Binding vs. Soft Binding

Binding refers to the way that objects and methods are “bound” by the compiler. You can think of the different binding types in the context of variable declaration and usage. Early binding is the most explicit form of variable declaration and usage. Late binding is the least explicit form of variable declaration. Soft binding is somewhere in the middle. Confused yet? Read on.

Early Binding

Early binding refers to the use of strongly typed variables. Strongly typed means that each variable type is defined explicitly and that the Variant type is never used. Specifying the type of variables explicitly relieves Visual Basic of the task of second-guessing your work when you compile your application. It can distinguish the variable types and thus generate more optimized code (code that is faster and less resource intensive). It also enables the programmer to catch programming problems at compile time because the compiler will generate errors when nonexistent object methods are called or when parameter and return types aren’t compatible.

Example of Early Binding

The following code shows an example of the use of early binding. Notice that all of the function’s parameters, the return type, and the local variables are explicitly typed. Nothing is explicitly or implicitly declared as Variant.

Function GetADORs(connStr As String, statement As String) _
   As ADODB.Recordset

   Dim conn As New ADODB.Connection
   conn.Open connStr
   Set GetADORs = conn.Execute(statement)
   conn.Close
End Function
Late Binding

Late binding occurs when the Variant type is used (either implicitly or explicitly). When you declare a variable as Variant, the compiler cannot know your exact intentions. The compiler then inserts additional logic to bind the method or property to the object during program execution. It becomes the responsibility of the runtime environment to catch any binding errors. Execution is less efficient because the runtime environment must resolve the variable types before it can perform an operation or method call. Take, for example, the following Visual Basic 6 method, which sets the Caption property value of a given label:

Sub SetLabel( lbl, value )
   lbl = value
End Sub

The compiler must insert code to evaluate whether lbl is an object and, if so, determine its default property. Furthermore, the runtime must check that the contents of value are valid for the default property of lbl. This step adds processing overhead to a single line of code. While the performance consequences are not obvious in a small application, large-scale enterprise or Web applications will notice the difference.

Late Binding and Upgrading

In preparation for using the Upgrade Wizard, you should strongly type anything that you can. Review your code and explicitly specify function parameters and return types. Inspect every instance that uses a Variant, and ask yourself whether it is possible to use a strict data type instead. For example, consider the following function:

Public Function Test(frm) As Integer
   Dim lbl
   Set lbl = frm.Label1
   lbl.Caption = "This is a test"
   frm.Label2 = "Another Test"
   Test = True
End Function

This function implicitly takes a Variant parameter (intended to be a form) and explicitly returns an Integer. From the standpoint of good programming practice alone, it is important to ensure that your code clearly identifies the expected variable types. It becomes even more important with an application like the Visual Basic Upgrade Wizard. Look at what the Upgrade Wizard does with the code:

Public Function Test(ByRef frm As Object) As Short
   Dim lbl As Object
   lbl = frm.Label1
   lbl.Caption Label2 = "Another Test"
   'UPGRADE = "This is a test"
   frm. _WARNING: Boolean True is being converted into a numeric.
   Test = True
End Function

The following errors and warnings resulted:

  • lbl.Caption should be lbl.Text.

  • frm.Label2 should be frmLabel2.Text.

  • An upgrade warning occurred, indicating that the wizard was converting Boolean to numeric.

Part of the problem would have been avoided if lbl were explicitly declared as an instance of a Label control. Doing so would have ensured that the Caption property upgraded to Text in Visual Basic .NET.

Simple modifications to the code make the result of the Upgrade Wizard more predictable:

  • Change Test to return Boolean.

  • Change the definition of lbl to a variable of type Label.

  • Explicitly type the frm parameter to be Form1.

The function looks like this after all of these changes:

Public Function Test(frm As Form1) As Boolean
   Dim lbl As Label
   Set lbl = frm.Label1
   lbl.Caption = "This is a test"
   frm.Label2 = "Another Test"
   Test = True
End Function

The upgraded function now looks like this:

Public Function Test(ByRef frm As Form1) As Boolean
   Dim lbl As System.Windows.Forms.Label
   lbl = frm.Label1
   lbl.Text = "This is a test"
   frm.Label2.Text = "Another Test"
   Test = True
End Function

The Test function has now upgraded cleanly with no errors.

The modifications to the original Visual Basic 6 code took very little work and also added a level of clarity to the original application. Granted, it is not always possible to make these modifications, but it is the ideal case.

Soft Binding

Soft binding is the last of the binding types and is often the most insidious. Imagine a situation involving a form (MyForm) with a label (Label1). Consider the following example, in which you pass the form and new text for your control:

Sub Form_Load()
   SetLabelText Me, "This is soft binding!"
End Sub

Sub SetLabelText( frm As Form, text As String )
   frm.Label1.Caption = text
End Sub

Notice that everything is strongly typed. There is certainly no obvious late binding going on, but something more subtle is happening. The parameter frm is declared as an object of type Form, while the form being passed to it is of type MyForm. The type Form does not have a property or a control called Label1. Visual Basic is implementing a form of late binding on a strongly typed variable. This is what we call soft binding. You have a couple of options for working around issues with soft binding:

Sub SetLabelText1( frm As MyForm, text As String )
   frm.Label1.Caption = text
End Sub

or this:

Sub SetLabelText2( frm As Form, text As String )
   Dim f as MyForm
   Set f  = frm
   frm.Label1.Caption = text
End Sub

SetLabelText1 is the preferred option because it will always prevent the wrong type of form from being passed to the function. SetLabelText2, while better than the original (and while it will upgrade properly), is not as robust because the assignment of frm to f could fail if the types are not compatible.

Watch Out for Null and Empty

Two keywords in Visual Basic 6, Empty and Null, could cause problems when you upgrade your application. Empty is typically used to indicate that a variable (including a Variant) has not been initialized. Null is specifically used to detect whether a Variant contains no valid data. If you ever test for either Null or Empty in your application, be sure to use the IsNull and IsEmpty functions, rather than the = operator. This is a minor change that should be easy to implement and will minimize upgrade headaches.

Another issue regarding Null involves the way in which various Visual Basic functions handle Null propagation. Take the following example:

Dim str As Variant
str = Null
str = Left(str, 4)

As you can see, a value of Null is passed to the Left function. The standard Left function in Visual Basic 6 is designed to propagate Null values. So the value of str after Left is called is Null. The problem is that in Visual Basic .NET, functions such as Left will not propagate Null. This disparity might introduce errors into your application without your realizing it. The Left$ function provides a way around this problem:

Dim str As Variant
str = Null
str = Left$(str, 4)

Running this code will cause a run-time error, however (just as its Visual Basic .NET equivalent would). You can resolve the conflict (without actually testing for Null) by using the string concatenation operator:

Dim str As Variant
str = Null
str = Left$(str & "", 4)

Concatenation with an empty string will cause Null to be coerced to an empty string (but not a string that is Empty), and Left$ will not cause a run-time error. Thus, you should use Left$ (and all of the associated functions—Right$, Mid$, and so on) and ensure that your application behaves correctly before upgrading. If you do so, you should not have to worry about a difference in functionality because the behavior of functions such as Left$ is equivalent to their Visual Basic .NET counterparts.

Implicit Object Instantiation

Many of you are probably familiar with the Visual Basic 6 As New syntax. It can be used to reduce coding by allowing the developer to write more compact code. Take, for instance, the following two examples:

' Example 1
   Dim c As ADODB.Connection
   Set c = New ADODB.Connection
   c.Open connStr
   Set c = Nothing
   c.Open connStr      ' Runtime error

' Example 2
   Dim c As New ADODB.Connection
   c.Open connStr
   Set c = Nothing
   c.Open connStr      ' No error

Example 2 shows how As New simplifies your code by taking care of the type declaration and object instantiation simultaneously. But the examples also demonstrate a behavioral difference between the two forms of variable declaration. The first example will produce an error; the second will not. What’s going on here? Although declaring variables in this fashion can be considered equivalent, these examples are by no means equal. We were surprised to find out how As New had worked in the past. Looking at Example 2, you would think that the first line performs two tasks: creating the variable and instantiating the object. In fact, it does only the former. It turns out that Visual Basic 6 tries to be smart about creating the object. The code in Example 2 will not actually instantiate the object until the object is accessed, while Example 1 explicitly instantiates the object. Also in Example 1, when the connection object is destroyed by setting c to Nothing, the object is gone, and no object will be created in its place unless the application does so explicitly. In the second example, the connection object is created only when the Open method is called. The connection object is destroyed when c is set to Nothing but is created again (by the Visual Basic runtime) when Open is called the second time.

This issue is not a problem in Visual Basic 6. After all, even if you didn’t know how it handles this situation, it would be safe to assume (as we did) that Visual Basic creates the object when told to. But it is important to note that Visual Basic .NET changes the behavior of As New to instantiate the object at the same time that the variable is defined. It also will not create new objects implicitly. If you set a reference to Nothing, you must create a new instance. Visual Basic .NET will not do it for you. This approach would seem to be the more logical (because it involves less uncertainty regarding object instantiation and lifetimes), but it could have consequences for your code. Example 3 demonstrates the behavior of As New in Visual Basic .NET.

' Example 3
   Dim c As New ADODB.Connection()
   c.Open( connStr )
   c = Nothing
   c.Open( connStr )     ' Runtime Exception

If you are using this syntax and relying on behavior specific to Visual Basic 6, you need to change your approach. Everyone else can forget about this scenario and move on—move on, that is, to a discussion of another form of the As New syntax:

Dim forms(0) As New Form
forms(0).Title = "Form 1"
ReDim Preserve forms(2)
forms(1).Title = "Form 2"
forms(2).Title = "Form 3"

This is where matters get tricky. Notice that we need to create this array of controls only once. If we resize the array, each additional element acts like the first. Visual Basic 6 will create a new instance of Form when we access the Title property for any index of this array (provided, of course, that it falls within the bounds of the array). While this ability can be convenient, it can also cause problems. Imagine a situation in which a bug accidentally causes you to access another element (which might otherwise be empty). Visual Basic 6 will merrily create the object for you and at the same time hide a bug in your application. Depending on how your application is written, the bug may not be easily discovered.

To make a long story short, it is best not to define arrays in this way (especially in large-scale applications). Instead, you can use the following syntax to define the array in a way that is compatible with Visual Basic .NET:

Dim forms(0) As Form
Set forms(0) = New Form
forms(0).Title = "Form 1"
ReDim Preserve forms(2)
For i = 1 To 2
   Set forms(i) = New Form
   forms(i).Title = "Form " & i
Next

Let’s look at the upgrade issue associated with this technique. Visual Basic .NET does support As New, but only for a single object declaration, in part because of one of the new features in Visual Basic .NET: construction. Every object in Visual Basic .NET has a constructor of some sort. When you create an object, that constructor is called. (Standard object-oriented programming concepts work here.) COM objects in Visual Basic 6 did not support construction, and as a result the runtime could create objects without any real worry; you still needed to do the work of initializing the object (by opening a connection, creating a file, or whatever). In Visual Basic .NET, those kinds of assumptions cannot be made. Some objects require parameterized constructors, which further impedes the use of As New in the array case.

What all this means is that in Visual Basic .NET, you must explicitly declare your objects. As New thus becomes an explicit form of variable declaration and loses any implicit behaviors. If you want to create an array of objects, you must first create the array and then manually populate it with the objects you need. Although this does remove a shortcut from the toolbox of the Visual Basic programmer, it increases the clarity of your code. No object will exist until you create it, which makes for greater predictability all round.


Conclusion

As you have seen in this chapter, there are very definite and predictable steps that you can take to improve the chances that your application will upgrade smoothly. While some of these steps require work up front on your part, they will save you time and effort over the course of a complete upgrade. Abandoning deprecated features and improving code quality not only will ensure a smooth upgrade but will reduce the barrier between your application and other managed framework classes.

For additional information, you can consult the MSDN white paper “Preparing a Visual Basic 6.0 Application for Upgrading.” You can find it by searching the Help files included with Visual Studio .NET.


Upgrading Applications

Part II
Upgrading Applications
Chapter 5
Your First Upgrade

In Part I, we explored the differences between Microsoft Visual Basic 6 and Visual Basic .NET, looked at the different upgrading options, and covered how to prepare your Visual Basic 6 application for the upgrade to Visual Basic .NET. Now it’s time to begin talking about the how-to of upgrading. This chapter walks you through the process of using the Upgrade Wizard to upgrade a simple project and examining what your new Visual Basic .NET project looks like. It also introduces you to techniques for fixing problems with the upgraded project and discusses some advanced upgrading techniques such as upgrading project groups and using the VB6 Snippet Upgrade add-in.


Upgrade Walkthrough

Let’s start by creating a simple project in Visual Basic 6. We will assume that you have Visual Basic 6 and Visual Basic .NET installed on the same machine. Having both versions installed is actually a recommended configuration. Visual Basic .NET installs to a different directory than Visual Basic 6. If Visual Basic 6 is already installed, it is left intact. Visual Basic .NET does not overwrite any Visual Basic 6 files—the two versions can be installed and run side by side. The order of installation doesn’t matter either; you can set up Visual Basic 6 after installing Visual Basic .NET or vice versa. The benefits of installing both products on the same machine are twofold:

  • It allows you to ensure that your project actually runs. (If the project doesn’t work in Visual Basic 6, it’s unlikely that it will work after upgrading.)

  • Visual Basic 6 Setup installs common ActiveX controls and libraries. These are not distributed with Visual Basic .NET. The easiest way to get them on the machine is to install Visual Basic 6.

We are going to create a simple Visual Basic 6 project, prjFirstUpgrade, and upgrade it to Visual Basic .NET. (You can find a completed version of this project on the companion CD that accompanies this book.) The project we are about to develop generates a new random number between 0 and 10 every second and shows the number in a TextBox. It continues to do this until you click a CommandButton to stop it. Let’s get started. Launch Visual Basic 6, and follow these steps to create the project:

  1. Create a new Standard EXE project.

  2. After Visual Basic 6 creates the project, you’ll see that Form1 is displayed in the integrated development environment (IDE). Add a TextBox, a CommandButton, and a Timer control to Form1.

  3. Select the Timer control. In the Properties grid, set the interval to 1000.

  4. Double-click the Command1 CommandButton, and add this code to the Command1_Click procedure:

    Private Sub Command1_Click()
   Me.Timer1.Interval = 0
End Sub

  5. Now return to the Form window, and double-click the Timer control to create the Timer1_Timer event. Add the following code to the event:

    Private Sub Timer1_Timer()
   Dim i As Integer
   i = Rnd * 10
   Me.Text1 = i
End Sub

  6. Save the project, using the default name for the form: Form1. We’ll name the project something meaningful: prjFirstUpgrade. Make sure you save the project into a directory on your local computer, rather than to a network share. This step ensures that Visual Basic .NET has enough security permissions to upgrade and run the project. The form and code should look similar to Figure 5-1.

    Figure 5-1
    Random number generator in Visual Basic 6.

When you run the project, you’ll see that the TextBox updates every second with a new random number. It stops when you click Command1.

Now that we’ve created a Visual Basic 6 project, let’s upgrade it to Visual Basic .NET. You can leave Visual Basic 6 running, with the project still loaded—doing so doesn’t interfere with the upgrade. Start Visual Basic .NET, and follow these steps to upgrade your project:

  1. In Visual Basic .NET, from the File menu, choose Open and then Project to display the Open Project dialog box. Navigate to the location where you saved your Visual Basic 6 project file, select prjFirstUpgrade.vbp, and click the Open button to upgrade prjFirstUpgrade using the Upgrade Wizard.

  2. The first page of the wizard contains welcome information, as shown in Figure 5-2. After you’ve read the welcome information, click Next to move to the second page.

    Figure 5-2
    Welcome screen of the Upgrade Wizard.

  3. The second page, shown in Figure 5-3, is where you set options for the upgrade. These options may be disabled depending on the project type, and in most cases, you will leave them at their default setting, but let’s examine them more closely to see when you would use them. There are two options. The first determines what type of Visual Basic .NET project to upgrade to. As you would expect, EXEs upgrade to EXEs, and DLLs upgrade to DLLs; for standard EXE and DLL projects, the upgrade type is determined automatically. With EXE server projects, however, you have some flexibility. These projects act like a combination of an EXE and a DLL. When upgrading these projects, you have the choice of converting them to a Visual Basic .NET EXE or to a Visual Basic .NET DLL. Since our project is a standard EXE, the first choice (EXE) is automatically selected. The second choice, DLL/Custom Control Library, is disabled, and for this reason the second option, Generate Default Interfaces For All Public Classes, is disabled also.

    This option is available only for DLL and EXE server projects. You should select this option only if other projects use the Implements keyword to implement classes from within your DLL or EXE server. Selecting this option causes the wizard to create an interface for every public class in your project.

    Click Next to move to page 3.

    Figure 5-3
    Choosing a project type.

  4. On the third page, shown in Figure 5-4, you specify the destination directory for the upgraded project. The project is not moved to this location. Instead, the Upgrade Wizard leaves your Visual Basic 6 project untouched and creates a brand new Visual Basic .NET project in the directory you specify here. By default, the wizard suggests placing the upgraded project in a new directory called <projectname>.NET. You can use the text box and the Browse button to change the location of this directory, but in most cases you should simply accept the default name. You can always move the directory later simply by copying it to a different location.

    Figure 5-4
    Specifying a destination directory.

    If the destination directory already contains files, these will be deleted to make room for the new project. With our soon-to-be-upgraded Visual Basic 6 project, let’s accept the suggested name, prjFirstUpgrade.NET. Click Next to move to page 4.

  5. Because the prjFirstUpgrade.NET folder doesn’t exist yet, a warning message appears asking you if you want to create it. Click Yes.

  6. On page 4, the wizard tells you that it is ready to perform the upgrade. Click Next to generate the upgraded project.

Status text and a progress bar show you what the Upgrade Wizard is processing and give you a visual indicator of the time left. The upgrade is not instantaneous; it can take anywhere from a minute (for a small project such as ours) to several hours (for a project with hundreds of forms and classes). After the wizard has finished, your project opens in Visual Basic .NET. The result will be similar to Figure 5-5, depending on what windows you already have open in Visual Basic .NET.

Figure 5-5
The upgraded project opens in Visual Basic .NET.
What Just Happened?

Let’s look at the upgraded project to see what the wizard did with our Visual Basic 6 project. You should see the Solution Explorer window in the top right corner of the IDE. (If the window is not visible, press Ctrl+Alt+L to see it.) Double-click Form1.vb to open the upgraded form. As you can see in Figure 5-6, the project looks exactly as it did in Visual Basic 6, with the exception that the Timer is now in the component tray beneath the form. Why has it moved? Nonvisual controls such as Timers, ToolTips, and Menus are now displayed in the component tray instead of on the form itself.

Figure 5-6
Upgraded form.

Now let’s try running the project. Press F5, just as you would in Visual Basic 6. The first time you run an upgraded project in Visual Basic .NET, you are prompted to save the solution file. A solution file is similar to a group file in Visual Basic 6. A solution contains one or more projects, just as a group can contain one or more projects. In Visual Basic .NET, since every project must be part of a solution, one is created for you. Press Enter to save it with the default name. The project then runs, and Form1 begins showing a new random number every second, as shown in Figure 5-7.

Figure 5-7
Generating random numbers in the upgraded project.

Click Command1 to stop the Timer. The Timer indeed stops, but not in the way we intended. Instead, the program breaks with an exception: “ ‘0’ is not a valid value for Interval. Interval must be greater than 0,” as shown in Figure 5-8.

Figure 5-8
Exception generated by the upgraded project.

Click Continue to stop the program and return to the IDE. The exception occurs at this line in the Command1_Click event:

Me.Timer1.Interval = 0

What happened? Let’s look at the upgraded code for the Command1_Click event:

Private Sub Command1_Click(ByVal eventSender As System.Object, _
ByVal eventArgs As System.EventArgs) Handles Command1.Click
   'UPGRADE_WARNING: Timer property Timer1.Interval cannot have a value
   'of 0. Click for more:
   'ms-help://MS.VSCC/commoner/redir/redirect.htm?key-
   'word="vbup2020.htm"
   Me.Timer1.Interval = 0
End Sub

The Upgrade Wizard has inserted a warning: “Timer Property Timer1.Interval cannot have a value of 0.” Warnings alert you to run-time differences between Visual Basic 6 and Visual Basic .NET. Click the underlined part of the comment, and a Help topic opens in the IDE with the title “Timer Interval Property Behavior Has Changed.” Figure 5-9 shows what the Help topic looks like.

Figure 5-9
Help topic for the Timer Interval property.

The Help topic says that Timer.Interval no longer disables the timer. Instead you have to use the statement Timer1.Enabled = False. Let’s change our code. Replace the line

Me.Timer1.Interval = 0

with

Me.Timer1.Enabled = False

Now press F5 to run the project. Notice that this time Command1 stops the timer just as it did in Visual Basic 6.

We’ve finished upgrading our project, but let’s not stop there. We’ll do something with our upgraded project that you couldn’t do in Visual Basic 6—make the form fade out. Add the following code to the Command1_Click event:

Dim i As Integer
For i = 99 To 0 Step -1
   Me.Opacity = i / 100
Next
End

After you add this code, the event should look like this:

Private Sub Command1_Click(ByVal eventSender As System.Object, _
ByVal eventArgs As System.EventArgs) Handles Command1.Click
   'UPGRADE_WARNING: Timer property Timer1.Interval cannot have a value
   'of 0. Click for more: ms-help://MS.MSDNVS/vbcon/html/vbup2020.htm
   Me.Timer1.Enabled = False
   Dim i As Integer
   For i = 99 To 0 Step -1
      Me.Opacity = i / 100
   Next
   End
End Sub

Press F5 to run the project. Now when you click Command1, the form becomes transparent and fades from view. As you can see, you may have to make some modifications to get your project working, but after you’ve done so, you can take advantage of the many features in Visual Basic .NET that weren’t available in Visual Basic 6. Upgrading enables you to add more value to your projects.

Language Changes

Now that we’ve got our project working, let’s take a few moments to review where the Upgrade Wizard changed the code, stepping through the original code line by line. Here is the original Visual Basic 6 code:

Option Explicit

Private Sub Command1_Click()
   Me.Timer1.Interval = 0
End Sub

Private Sub Timer1_Timer()
   Dim i As Integer
   i = Rnd * 10
   Me.Text1 = i
End Sub

The first line, Option Explicit, is upgraded to

Option Strict Off
Option Explicit On

Notice that On was added to Option Explicit. This statement has the same effect as the plain Option Explicit in Visual Basic 6—it means that all variables must be explicitly declared using the Dim, Public, or Private statement. As you might guess, you can also write Option Explicit Off, which lets you use variables without explicitly declaring them. Option Strict is a new option. When it is set to On, you will get compile errors if your code does either of the following:

  • Uses late binding

  • Performs a narrowing conversion—that is, converts a variable of one type to another, where an overflow or type mismatch might occur (like assigning a Date to a String or an Integer to a Long)

When your project is upgraded, Option Strict Off is put at the top of each file to turn off strict type checking, since setting it to On would cause compile errors in most Visual Basic 6 code.

The Different Option Statements

Visual Basic .NET has three types of Option statements. Along with Option Strict and Option Explicit, there is Option Compare. As it did in Visual Basic 6, Option Compare sets the string comparison mode. By default, Strict is set to Off, Explicit is set to On, and Compare is set to Binary. You can change the defaults for each option in the Build section of the project’s properties. If you change the default for, say, Option Compare to Text, all forms, modules, and classes will use Option Compare Text, unless they have their own Option Compare statements that override the default setting.

The wizard also inserts a Class statement. In Visual Basic .NET, all source files have a .vb file extension. Whether a file is a module, a class, or a form depends on the code inside the file. Why does the wizard insert a Class statement instead of a Form statement? Because all forms are actually classes that inherit from the base class System.Windows.Forms.Form. Here is what the Class statement looks like. (Event Code) indicates where your events are placed.

Friend Class Form1
   Inherits System.Windows.Forms.Form
   (Event Code)
End Class

If you want to, you can put several forms or classes into a single file, provided each has its own Class statement.

Just beneath the Class statement, you’ll see two collapsed regions: Windows Form Designer Generated Code and Upgrade Support, as shown in Figure 5-10.

Figure 5-10
Collapsed code.

This is “system-generated code,” or code used by Windows Forms to store the design layout of the form and by the Upgrade Wizard to store instancing information. We’ll discuss these sections in Chapter 6 and in Chapter 12For now, let’s move on and look at your upgraded event code. The Command1_Click event code is upgraded from

Private Sub Command1_Click()
   Me.Timer1.Interval = 0
End Sub

to

Private Sub Command1_Click(ByVal eventSender As System.Object, _
ByVal eventArgs As System.EventArgs) Handles Command1.Click
   'UPGRADE_WARNING: Timer property Timer1.Interval cannot have a value
   'of 0. Click for more: ms-help://MS.VSCC/commoner/redir/
   'redirect.htm?keyword="vbup2020.htm"
   Me.Timer1.Interval = 0
End Sub

The signature of the Click procedure has changed from Visual Basic 6. In Visual Basic .NET, most events are passed two parameters: eventSender, an object that is set to the control (in this case CommandButton1); and eventArgs, an object that contains extra arguments (such as mouse coordinates). The contents of eventArgs change depending on the type of event. For Click events, it contains nothing of interest. At the end of the Sub statement, you’ll see the Handles keyword. This keyword associates the event procedure with the control event. Each event procedure can be associated with multiple controls and multiple events. For example, the following Handles clause would connect a procedure to the Command1.Click, Command2.Click, and Command1.TextChanged events:

Handles Command1.Click, Command2.Click, Command1.TextChanged

The line after the Sub statement contains the following upgrade warning:

'UPGRADE_WARNING: Timer property Timer1.Interval cannot have a value
'of 0. Click for more: ms-help://MS.VSCC/commoner/redir/
'redirect.htm?keyword="vbup2020.htm"

The Upgrade Wizard inserts warnings whenever it encounters a statement that may not work the same in Visual Basic .NET. It inserts the warning on one line before the statement with the problem. You can filter the Task List to show the upgrade warnings. For information on using the Task List, see Chapter 6.

The final statement in the Command1_Click event is

Me.Timer1.Interval = 0

This statement is exactly the same as it was in Visual Basic 6.

Timer1_Tick has also undergone some changes during the upgrade. The Visual Basic 6 code

Private Sub Timer1_Timer()
   Dim i As Integer
   i = Rnd * 10
   Me.Text1 = i
End Sub

is upgraded to

Private Sub Timer1_Tick(ByVal eventSender As System.Object, _
ByVal eventArgs As System.EventArgs) Handles Timer1.Tick
   Dim i As Short
   i = Rnd() * 10
   Me.Text1.Text = CStr(i)
End Sub

Just as we saw with Command1_Click, the Timer1_Tick procedure now has a new signature. The line after the Sub statement has also changed. In Visual Basic .NET, the Integer data type has grown in size from 16 bits to 32 bits, and a new data type, Short, has been added to the language for 16-bit numbers. For this reason, the line

Dim i As Integer

is upgraded to

Dim i As Short

This ensures that the variable i is the same size as it was in Visual Basic 6. Let’s move on to the next line:

i = Rnd * 10

is upgraded to

i = Rnd() * 10

In Visual Basic .NET, all calls to functions must have parentheses, so the Upgrade Wizard has added them to the Rnd function. Now let’s look at the final line of code in this event procedure.

Me.Text1 = i

is upgraded to

Me.Text1.Text = CStr(i)

This line has undergone two changes. Because Visual Basic .NET does not support default properties, the Upgrade Wizard resolves the default property of Me.Text1 and expands the statement to Me.Text1.Text. Because the Text property is being assigned to a number, the wizard also explicitly converts i to a string, using the CStr function.

Whew! The upgrade resulted in quite a number of changes, but looking over the upgraded code you’ll see that it still looks familiar. The overarching impression is that upgraded code is more explicit than the Visual Basic 6 version. That, in a nutshell, is the real difference between Visual Basic 6 and Visual Basic .NET. Visual Basic 6 concealed a lot of details, like forms being classes, the design layout, and default properties. In Visual Basic .NET, these are all exposed for you to see.

Other Files in Your Project

Now close the Code Editor and Windows Form Designer windows. Let’s turn our attention to the Solution Explorer for a second. The Solution Explorer shows all the files in your upgraded project. Notice that the Upgrade Wizard has added two extra files to your project: _UpgradeReport.htm and AssemblyInfo.vb. Go ahead and double-click AssemblyInfo.vb to open it in the Code Editor. This file contains attributes. Attributes don’t perform any action; they are used to store additional information about a method, form, class, or project. The attributes in this file allow you to change information about your project such as the assembly title and assembly description. Generally, unless you have a specific need, you can leave these values at their default settings. Close the AssemblyInfo.vb Code Editor and we’ll look at the upgrade report.

A discussion of the assembly attributes in AssemblyInfo.vb is beyond the scope of this book. These attributes can be used to specify the identity of your Visual Basic .NET application. To learn more about attributes, search the Visual Basic .NET Help for “Attributes” and “Setting Assembly Attributes.”

The upgrade report is added to your project with the filename _UpgradeReport.htm. It is an HTML file. Double-click the file in the Solution Explorer to open it. Click the + sign next to Form1 to display all of the issues with Form1, and you’ll see that the difference with Timer.Interval has been recorded. The report should look similar to Figure 5-11.

Figure 5-11
An upgrade report.

The upgrade report captures in one place the issues found while upgrading the project to Visual Basic .NET. Like the comments in code, each issue is also a hyperlink that navigates to a Help topic describing how to fix the problem. For more on upgrade issues, see Chapter 7.

You now have your Visual Basic .NET project. The Upgrade Wizard keeps the form layout the same, makes necessary syntax changes, and warns you about any issues found during the upgrade. After you get your project working, you can enhance it using some of the exciting new features of Visual Basic .NET. We’ll put off a discussion of these features until later, because we still need to look at how to upgrade project groups and snippets of code.


Upgrading Project Groups

The Upgrade Wizard upgrades only projects. But what about project groups? In Visual Basic 6, you could create a project group that contained several projects. When you pressed F5, all of the projects in the group were compiled together, and the debugger could step from code in one project into code in another. Project groups are useful when one project references another.

The Visual Basic .NET equivalent of a project group is a solution. Each solution contains one or more projects. As in Visual Basic 6, they are all compiled together when you press F5, projects can reference other projects in the same group, and the debugger can step from code in one project into code in another. Not all projects in a solution have to be Visual Basic .NET projects. A solution can contain projects written in different languages, such as Visual Basic .NET, C#, and Visual C++.

To upgrade a project group to Visual Basic .NET, you need to upgrade one project at a time, starting with the most “downstream” project and working your way up the dependency hierarchy. For example, if your project group contains an EXE project that references a DLL project, you should upgrade the EXE first, followed by the DLL. Let’s walk through an example to see how this is done. First we will create a project group in Visual Basic 6, and then we will upgrade it one project at a time to Visual Basic .NET. Our project group will be simple: it will show “Hello World” in a message box when you click a CommandButton on a form.

  1. Open the project you created earlier in Visual Basic 6 (prjFirstUpgrade.vbp). Add a new DLL project by choosing Add Project from the File menu and selecting ActiveX DLL from the Add Project dialog box. A DLL project called Project1 is added to the Project Explorer. The two projects are now part of a project group.

  2. Visual Basic 6 automatically adds a new class in Project1, Class1. Add the following code to the class:

    Sub sayHelloWorld()
   MsgBox "Hello World"
End Sub

  3. Close the Class1 Code Editor, and save all the files by choosing Save Project Group from the File menu. Make sure you save all the files in the same directory as the first project. Accept the default filenames; the project group will be called Group1.vbp, and the new project will be called Project1.vbp.

  4. Now we will make prjFirstUpgrade reference Project1. Select prjFirstUpgrade in the Project Explorer, and choose References from the Project menu. In the References dialog box, select Project1, and click OK. Figure 5-12 shows the References dialog box.

    Figure 5-12
    References dialog box.

  5. Let’s use the Project1 Class1 from prjFirstUpgrade. Open prjFirstUpgrade.Form1 in the Form window, and double-click the Command1 CommandButton to edit the Click event code. Change the Click event code to the following:

    Private Sub Command1_Click()
   Dim c As New Class1
   c.sayHelloWorld
End Sub

    After you’ve added this code, the project group will look like Figure 5-13.

    Figure 5-13
    Project group in Visual Basic 6.

  6. Let’s test it out. Press F5 to run the project group. When you click the CommandButton Command1, a message box pops up with the text “Hello World.”

  7. We’ll do two more things before we upgrade the group. First, save the group by choosing Save Project Group from the File menu. Next, compile the group by choosing Make Project Group from the File menu. Compiling is necessary because the Upgrade Wizard checks the timestamp of the Project1 project against the timestamp of the Project1 DLL. If the project file is newer than the DLL, the wizard assumes that the DLL is out of date and asks you to recompile it.

Now that we’ve created a project group in Visual Basic 6, let’s upgrade it to Visual Basic .NET. First we’ll upgrade the EXE prjFirstUpgrade. Then we’ll upgrade the DLL Project1. Finally, we’ll change prjFirstUpgrade so that it references the upgraded Project1.

  1. Start Visual Basic .NET, and upgrade prjFirstUpgrade. (If you need a recap on how to upgrade a project, see the section “Upgrade Walkthrough” earlier in this chapter.) You won’t need to make any modifications to the upgraded code, since we replaced Timer.Interval = 0 with code that shows a message box.

  2. Now let’s upgrade Project1 to be part of this solution. In Visual Basic .NET, from the File menu choose Add Project and then Existing Project. This opens the Add Existing Project dialog box. Select Project1.vbp and click the Open button to upgrade Project1. Accept the default upgrade options, as you did with prjFirstUpgrade.

  3. Is that all there is to do? Not quite. The project prjFirstUpgrade still references the Visual Basic 6 version of Project1. We need to delete the reference to the Visual Basic 6 Project1 DLL and add a reference to the upgraded Visual Basic .NET Project1. First let’s remove the reference. Expand the prjFirstUpgrade references node and remove the reference Interop.Project1_1_1 by right-clicking it and selecting Remove. Note that in your project, this reference might be called something slightly different. Look for the reference with “Project1” in the name. For information on adding and removing references, see Chapter 6.

  4. Add a reference to the upgraded Project1 by right-clicking References, selecting Add Reference, and then selecting Project1 in the Add Reference dialog box.

  5. That’s it! Press F5 to run the solution. When you click the Command1 button, you will see a message box, as shown in Figure 5-14.

    Figure 5-14
    Upgraded project group.

You now know the basic procedure for upgrading project groups. If your Visual Basic 6 project group contains many projects, you simply repeat these steps for every project in the group. Start with the most downstream project, and work your way up the dependency hierarchy.


Using the VB Snippet Upgrade Add-In

What is a snippet? A snippet is a fragment of code, such as a procedure or even just two or three lines of code. A Visual Basic 6 snippet is a fragment of Visual Basic 6 code. It may be sample code that you received by e-mail, or it might be a function you use all the time. The VB Snippet Upgrade add-in is designed for upgrading snippets. It is useful when you want to upgrade only a few lines of code, not an entire project. To use it, you must have a Code Editor window open in Visual Basic .NET. Choose VB Snippet Upgrade from the Tools menu. The VB Snippet Upgrade window opens, as shown in Figure 5-15.

Figure 5-15
The VB Snippet Upgrade add-in.

Simply type or paste Visual Basic 6 snippet code into this window, and click the Upgrade button. Let’s try upgrading a simple example. Type the following into the window:

Dim x As Integer
Debug.Print x

Now click the Upgrade button. The snippet is upgraded and inserted into the Code Editor at the current insertion point:

Dim x As Short
System.Diagnostics.Debug.WriteLine(x)

The VB Snippet Upgrade add-in is a great way to familiarize yourself with how code upgrades from Visual Basic 6 to Visual Basic .NET. The add-in can upgrade several lines of code, a function, or even a whole module. The only limitation is that the snippet can’t refer to functions or objects that are not included as part of the snippet—the VB Snippet Upgrade add-in needs to examine all of the objects and functions you use in that snippet. If they are defined elsewhere, the add-in won’t know how to interpret them. You can add references to the snippet, however. A list box on the References tab allows you to add or remove COM libraries that your snippet references. Any libraries you add here are also added to your Visual Basic .NET project.

Getting Updates

As this book went to press, the VB Snippet Upgrade add-in was still in development, and Microsoft had not yet decided whether it would be ready to ship with Visual Basic .NET or be made available later as a download. If it is not installed with Visual Basic .NET, you will be able to get it from the Visual Basic Web site, http://msdn.microsoft.com/vbasic/. From time to time this Web site is updated with new versions of the Upgrade Wizard, the VB Snippet Upgrade add-in, and other resources for upgrading. These updates may contain bug fixes, whitepapers, or new upgrade capabilities.


Upgrading Using the Command Line

Those of you who prefer using command-line tools over graphical wizards will be pleased to know that Visual Basic .NET ships with a command-line upgrade tool: VBUpgrade.exe. If you accept the default file locations during installation, you will find it installed to the following directory:

<drive>:\Program Files\Microsoft Visual Studio .NET\Vb7\VBUpgrade\

The command-line upgrade tool produces exactly the same result as the Upgrade Wizard—in fact, they share the same underlying upgrade engine. Let’s see it in action. Assuming that the tool resides on drive C, open a Command Prompt window and change the current directory to the location of the tool, using the CD command, as follows:

CD "C:\Program Files\Microsoft Visual Studio .NET\Vb7\VBUpgrade"

Now type the following to see the command-line options:

vbupgrade /?

The upgrade tool shows you its available options:

Microsoft (R) Visual Basic.NET Upgrade Tool Version 7.00.9238.0
Copyright (C) Microsoft Corp 2000-2001.
Portions copyright ArtinSoft S.A.
All rights reserved.
Usage: VBUpgrade <filename> [/Out <directory>] [/NoLog │ /LogFile <filename>] [/Verbose] [/GenerateInterfaces]
/?                      Display this message
/Out                    Target directory (default is ".\OutDir")
/Verbose                Outputs status and results
/NoLog                  Don't write a log file
/LogFile                Log file name (default is
                        "<ProjectFileName>.log")
/GenerateInterfaces    Generates interfaces for public classes

To upgrade a project, you need to specify the project filename and the destination directory for the upgraded project. If the destination directory doesn’t exist already, the upgrade tool will create it for you. For example, the following statement upgrades C:\Project1.vbp to the C:\Project1.NET directory:

vbupgrade c:\Project1.vbp /Out c:\Project1.NET

Because it is a command-line tool, the upgrade tool doesn’t show any wizard pages while upgrading. The one difference between the command-line tool and the Upgrade Wizard is that the command-line tool does not delete any files in the destination directory. If the destination directory already contains files, the command-line tool stops with an error.


Conclusion

This chapter has covered the basic mechanics of upgrading projects, project groups, and code snippets. Doing each is easy, once you know how. The next chapter takes a more detailed look at how to work with your upgraded project in Visual Basic .NET.


Common Tasks in Visual Basic .NET

Chapter 6
Common Tasks in Visual Basic .NET

In the previous chapter, we walked through how to upgrade an application. If you’re new to Visual Basic .NET, some parts of the integrated development environment (IDE) may seem a little foreign. The purpose of this chapter is to familiarize you with some of the basics of working with applications in Visual Basic .NET. We’ll start by building a simple Visual Basic .NET application from scratch. We’ll use this application to introduce the new IDE and explain how to create new Visual Basic projects, and then we’ll follow with a discussion of troubleshooting and debugging techniques and tactics. If you’re already familiar with Visual Studio .NET, you might want to skip to the section on problem solving later in this chapter.


A Guide to Working in Visual Basic .NET

Microsoft Visual Studio .NET has a somewhat different development experience than Visual Basic 6 developers are used to. In fact, the new IDE combines features from Visual Basic 6, Microsoft Visual C++, and Microsoft Visual InterDev. The single most powerful aspect of the new IDE is that it is shared across all development languages. The Visual Basic editing experience is not all that different from the C# or C++ editing experience. The menus change slightly between project types, but the core interface, illustrated in Figure 6-1, remains the same.

In this section, you’ll develop a simple Windows application to get a feel for the new IDE. The application will contain two buttons, a TextBox control and a ListBox control. The first button will add text from the TextBox control to the ListBox control. The second button will remove the selected item from the ListBox control. Nice and simple. Let’s rock.

Figure 6-1
New Visual Basic .NET IDE.
Creating a Visual Basic .NET Project

Creating a Visual Basic .NET Windows Forms project is simple: under the File menu, select New, and then select Project. In the New Project dialog box, shown in Figure 6-2, select Visual Basic Projects as the project type and Windows Application as the project template. Leave all settings at their defaults and click OK to finish creating your project. Figure 6-3 shows the new project.

Figure 6-2
New Project dialog box.
Visual Basic .NET Project Types

Visual Basic .NET offers a sizable list of projects that you can create by using the New Project dialog box. The notion of a multilanguage “solution” is evident to anyone who examines this list. The dialog box does not change, whether you’re creating a new project or adding a new project to an existing solution. Here are the Visual Basic .NET project types that are supported out of the box:

  • Windows application

  • Class library

  • Windows control library

  • ASP.NET Web application

  • ASP.NET Web service

  • Web control library

  • Console application

  • Windows service

  • Empty project

  • Empty Web project

  • New project in existing folder

Figure 6-3
Your new project.
Getting to Know the Visual Studio .NET IDE

In Visual Basic .NET you still have projects, but instead of project groups (as you had in Visual Basic 6) you now have solutions. Each solution can contain multiple projects, even projects created in different programming languages (for example, Visual C# and C++). Visual Basic .NET now supports additional project types, including console applications, Web applications, Web services, and Windows services, all of which can be included in your solutions. You use the Solution Explorer to organize and manipulate your projects and build settings. By default, you will find the Solution Explorer in the upper right corner of the screen.

Figure 6-3 demonstrates several differences between Visual Basic .NET and Visual Basic 6. In the Solution Explorer you can see that references are now accessible from the project tree, and Visual Basic no longer uses file extensions to identify forms, classes, and code modules. All Visual Basic files now have the same extension, .vb.

On the left side of the IDE is the Toolbox. Moving your mouse over the Toolbox tab causes the drawer to slide out from the side of the screen. The Toolbox view defaults to the Windows Forms tab, which lists all the controls available to your application.

Now we are going to walk through creating a simple Windows application. To create a Windows Forms project, do the following:

  1. Drag a TextBox control from the Toolbox and position it in the upper left corner of the form.

  2. Drag a Button control from the Toolbox and position it to the right of the TextBox.

  3. Drag another Button from the Toolbox and position it to the right of the first button.

  4. Drag a ListBox control to the form and position it below the TextBox and buttons.

  5. Position the controls so that the form looks roughly like Figure 6-4.

Now let’s set some properties for the controls we just added to the form.

  1. Click the Button1 control. The property page should be visible on the right. If it’s not, right-click the control and select Properties from the shortcut menu.

    Figure 6-4
    Newly added controls.

  2. In the property page set the Text property to &Add and the (Name) property to AddButton.

  3. Click the second button and set the Text property to &Remove and the Name property to RemoveButton.

  4. Click the ListBox1 control and find the Anchor property. (It is in the group of properties that specify layout, toward the bottom of the properties list.) Click the down arrow and make sure the top, left, right, and bottom sides of the control are selected. Setting these options will ensure the correct resizing behavior of the control.

You now have a Microsoft .NET application that doesn’t do much but look pretty and resize nicely. Let’s add some functionality. Double-click the AddButton control. This takes you to the Code Editor in the AddButton_Click method. Add the following line of code:

ListBox1.Items.Add(TextBox1.Text)

From the left drop-down menu directly above the editor, select RemoveButton. In the drop-down menu on the right, select the Click event. This creates a RemoveButton_Click event and positions the cursor inside the event. Now you should add the following line of code:

ListBox1.Items.RemoveAt(ListBox1.SelectedIndex)

Not only have we implemented the desired functionality, but we’ve also explored two ways of associating event handlers with controls in Visual Basic .NET. Now that we have the features we want, let’s run the application.

Running Your Project

Compiling in Visual Basic .NET is different from compiling in Visual Basic 6. In Visual Basic 6, you could run your application with coding errors. The IDE would break in and notify you of problems before it would execute problematic code. In Visual Basic .NET, your entire application has to be compiled before you can test anything at run time.

To run your application, press F5. This step will cause Visual Basic .NET to compile the application before launching it under the debugger. At this point we shouldn’t have any errors. Everything should work just fine and look something like Figure 6-5 (after the user has added a few listbox entries).

Figure 6-5
Your new Visual Basic .NET application.

It turns out that everything is not working just fine. Try selecting an item in the listbox (add one if necessary), and then click the Remove button twice. You will get the error shown in Figure 6-6.

Figure 6-6
The application is broken.

So it’s broken. Click Break and let’s dive into the debugger.

A Quick Introduction to Debugging

Debugging in Visual Basic .NET is not exactly what traditional Visual Basic developers are used to. Probably the most surprising missing feature is Edit And Continue. However, Visual Basic .NET offers a host of new features that greatly enhance the debugging experience, including service debugging, cross language debugging, XCopy deployment, structured exception handling, and a more sophisticated debugging API.

Where Is Edit And Continue?

The developers of the initial version of Visual Basic .NET decided not to include Edit And Continue. Dropping a feature that Visual Basic developers have come to rely on was not done lightly, but it had to be done. Visual Basic .NET and the .NET Framework are totally new platforms developed from the ground up. The challenge was to provide all the features of previous platforms that had come about during a decade of refinements. Unfortunately, including Edit And Continue would have meant delaying the initial release of Visual Basic .NET. Although it is a sadly missed feature, don’t let this omission detract from the other powerful debugging features available in Visual Basic .NET.

In the preceding section, we learned that the application is throwing an exception on the following line of code, and we need to find out why. The exception dialog box has already told us that the error was an ArgumentOutOfRange exception. Looking at the code, we can see that the problem is located in the SelectedIndex property of the ListBox1 control:

ListBox1.Items.RemoveAt(ListBox1.SelectedIndex)

The Autos window in the lower left side of the IDE displays the value for SelectedIndex. When no item is selected, the SelectedIndex property is –1. You can also use the Command window to inspect the SelectedIndex property. Just type ?ListBox1.SelectedIndex to print out the value in the Command window.

One possible solution is to add code that will check for this condition before trying to remove an item from the ListBox control. You must end the debugging session before you can make any changes to the source file. The current version of Visual Studio .NET locks the source files during debug sessions. Moving to the Code Editor, replace the body of the RemoveButton_Click event with the following code:

If ListBox1.SelectedIndex <> -1 Then
   ListBox1.Items.RemoveAt(ListBox1.SelectedIndex)
End If

This code prevents the application from attempting to remove any items from the ListBox unless an item is selected. It will avoid the exception previously encountered at this point. There is, however, another way to do this—possibly a more proper way from a user-interface perspective. It makes sense that the user should not be able to click the Remove button unless it will actually perform a valid action. To specify this behavior, do the following:

  1. In the Form Designer, select the Remove button and change its Enabled property to False.

  2. In the Code Editor, select the ListBox1 control from the left drop-down menu. Select the SelectedIndexChanged event from the right drop-down menu. Add the following code to the new event handler:

    If ListBox1.SelectedIndex = -1 Then
   RemoveButton.Enabled = False
Else
   RemoveButton.Enabled = True
End If

The end result is that by default, the Remove button is disabled at startup. As soon as an item is selected in the listbox, the Remove button is enabled. If the user clicks the button, the selected item is removed, and the button is again disabled.


Miscellaneous Items

The Visual Basic .NET IDE has a whole host of new features. It also introduces some new behaviors that will take time for Visual Basic developers to get used to. This section walks you through some of those behaviors.

Handling Build Errors

If your project contains compile errors, when you build it Visual Basic .NET shows the dialog box seen in Figure 6-7.

Figure 6-7
Your reward for trying to run a project containing errors.

The only purpose of this dialog box is to let you choose between running a stale build of your solution and fixing the problems and trying again. We can’t really see why you’d want to make changes and then run a previous build. The vast majority of the time you are going to try to fix the problems first. And that’s why you have the Task List.

Using the Task List

The Task List has several different uses. First and foremost, it reports compilation errors. Unlike compiling in Visual Basic 6, you can now view a list of all the errors and fix them in whichever order you prefer, instead of having the order dictated by the compiler, and you can do so without having an annoying dialog box pop up for every error that the compiler comes across. Figure 6-8 displays the Task List for a sabotaged sample application. Double-clicking any of these items brings you to the related line of code. When the issue is fixed, the item in the Task List automatically goes away.

Figure 6-8
Task List.

Of course, the Task List has many other purposes. In the Options dialog box, displayed by choosing Options from the Tools menu, you can select Environment and then select Task List. This dialog box, shown in Figure 6-9, allows you to customize what is displayed in the Task List and gives you the option of adding custom tokens. This feature can be a useful way of marking parts of your application and prioritizing feature areas. For example, you can create tokens that indicate the phase in which features will be added. You could create tokens like Beta1, Beta2, and Beta3 and filter based on the phase of the project you are currently in. You could also create tokens like Bill, Bob, or Janice that indicate areas that individuals on your team need to address. This approach is often useful when implementing large applications.

Figure 6-9
Visual Studio .NET Options dialog box.

These comments would look like this in your code:

'TODO: Do this
'Beta2: This feature needs to be implemented later
'Janice: Can you deal with this?
Using Breakpoints

Breakpoints work the same way they did in Visual Basic 6. Find a line of code you want to break into the debugger on, and either click to the far left of the line in the editing window or right-click the line and select Insert Breakpoint from the shortcut menu. You can remove breakpoints by clicking the red circle or right-clicking the line of code and selecting Remove Breakpoint from the shortcut menu.

References

Visual Basic .NET has two types of references that you can add to your project: a standard reference and a Web reference. A standard reference is used to import the namespace of either a COM type library or a .NET reference. Web references are used exclusively for importing a Web service.

Standard References

As Figure 6-10 shows, three kinds of standard references are available to you through the Add Reference dialog box: COM references, .NET references, and project references. Use .NET references and project references for referencing managed components in your project. Adding a COM reference causes the COM type library to generate a managed type library, thereby enabling the use of COM objects within your Visual Basic .NET project as if they were managed classes.

To open the Add Reference dialog box, right-click the References node in the Solution Explorer, and choose the Add Reference menu item. This opens the Add Reference dialog box as seen in Figure 6-10.

Figure 6-10
Add Reference dialog box.

To remove a reference, open the References node in the Solution Explorer, right-click the reference you want to remove, and choose the Remove menu item.

Web References

To enable your application to consume Web services, you add a Web reference. Adding a Web reference generates a local proxy class that enables you to call methods on the Web service. You add Web references by right-clicking the Reference node in the Solution Explorer and choosing the Add Web Reference menu item. This opens the Add Web Reference dialog box, as seen in Figure 6-11.

Figure 6-11
Add Web Reference dialog box.


Problem-Solving Techniques

You need to master a number of skills to become effective at debugging. This section explores some of those skills.

Using the System.Diagnostics Library

The System.Diagnostics namespace has three important classes: Debug, Trace, and Debugger. The Debug and Trace classes are related in that you can use both of them to generate debugging information at run time, which is helpful in diagnosing various application problems. Although their functionality is slightly different, both classes support similar interfaces, making it easy to move back and forth between them. However, you might also be confused about when to use one instead of another. The official word from the Microsoft Developer Network (MSDN) documentation is that the Trace class can be used to instrument release builds. Instrumentation allows you to monitor the health of your application running in real-life settings, isolate problems, and fix them without disturbing a running system. You can use the Debug class to print debugging information and check your logic with assertions; you can make your code more robust without affecting the performance and code size of your shipping product.

The Debugger class is somewhat different. It enables communication between your application and an attached debugger. This can be useful for inserting code into your application that assists in debugging complex problems or error scenarios. In addition, some application types (such as Windows services) can be notoriously difficult when you’re trying to trace the root of failures. You might find these two particular methods of the Debugger object useful:

  • The Log method enables the application to post messages to the attached debugger.

  • The Break method enables the application to signal the debugger to break into that line of code. Using this method is analogous to using conditional breakpoints in Visual Basic .NET, except that Break will work with any attached debugger.

Using CorDbg

CorDbg is a managed command-line debugger. It can be extremely useful for handling problems with deployment machines that don’t have the Visual Basic .NET debugger installed. We don’t recommend this debugger to the casual developer, but the simple commands explained in this section will provide a useful introduction to command-line debugging.

Where Do I Get CorDbg?

CorDbg is located in the %SYSVOL%\Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Bin directory when the Framework Software Development Kit (SDK) is installed. You can use the Visual Basic .NET installer to install the SDK, or you can download the SDK from the MSDN Web site.

First you need to start the debugger. If you add the path to the directory containing CorDbg.exe to your environment, you can start the debugger by typing cordbg at the command prompt. The following sequence of commands is typical:

  1. Type pro to get a list of managed processes and process IDs.

  2. Type at [pid] to attach to the process.

  3. Type ca e to catch all exceptions.

  4. Type g to continue the program execution.

When an exception occurs, CorDbg breaks to the prompt and reports the exception type. At this stage you can evaluate where the exception occurred, get stack trace information, and inspect variable values.

If command-line debuggers aren’t your style, the Framework SDK also provides a GUI debugger (DbgCLR, in the %SYSVOL%\Program Files\Microsoft Visual Studio .NET\FrameworkSDK\GuiDebug directory) that provides a similar debugging experience to the Visual Basic .NET debugger.

Simplifying Complex Expressions

Trying to figure out the cause of an exception in a large compound statement can be a real challenge. When you encounter obtuse errors (either compile or run-time errors), it can be helpful to break down the offending line of code into smaller, isolated statements. There’s no penalty for increasing the line count of your application, so why worry? Breaking down complex single-line expressions into several separate expressions can also improve your ability to add run-time error handling to your application.

Let’s look at an example of simplifying complex expressions. The following Visual Basic .NET line is a complex expression because there are several statements on one line:

MsgBox(Format(CDate(myString & "/2002")))

If a line like this causes errors and you can’t figure out where the error occurs, try simplifying it by putting each statement on a separate line and storing the results of each statement in a temporary variable. The following sample shows how to do this:

Dim tempString, formattedString As String, tempDate As Date
tempString = myString & "/2002"
tempDate = CDate(tempString)
formattedString = Format(tempDate, "d-mmm-yyyy")
MsgBox(formattedString)

If an error occurs after you’ve made the change, it’s simple to determine which statement actually caused the error. We are not suggesting you do this with all your code, but it’s a useful technique for tracking down the cause of an error when all you know is that it happens somewhere on a particular line.


Conclusion

This chapter covered a lot of information, from the bare debugging essentials to more sophisticated troubleshooting methods. We hope this chapter has provided you with a basic understanding of how to work with the IDE and how to approach debugging applications in Visual Basic .NET. The Visual Basic .NET debugging experience is significantly different from what you’re familiar with in Visual Basic 6. This helps emphasize the importance of using the techniques discussed in this chapter to ensure that you can work effectively with your applications.


Upgrade Wizard Ins and Outs

Chapter 7
Upgrade Wizard Ins and Outs

This chapter focuses on the Visual Basic .NET Upgrade Wizard. Specifically, we’ll look at the approach the Upgrade Wizard takes to upgrade your Visual Basic 6 application to Visual Basic .NET. This chapter will address questions such as, What will my code look like after it has been upgraded? What types of projects are upgraded? How will my forms, classes, modules, controls, and ActiveX controls be upgraded? What happens when a component, property, method, event, or language statement can’t be upgraded?


Upgrade Philosophy

When the Visual Basic .NET team decided to not support 100 percent Visual Basic 6 compatibility, the challenge to create a tool to upgrade any arbitrary Visual Basic 6 project to Visual Basic .NET was enormous. There was much debate within the team about what approach to take. Ultimately, the Visual Basic team adopted two overriding goals that can be summed up with the following statements:

“It’s your code.”

“Just make it work.”

It’s Your Code

The first goal of the Upgrade Wizard is to make the changes to your Visual Basic 6 forms and code as unobtrusive as possible. You should be able to recognize your code after the wizard has upgraded it. If the wizard transforms your code into an unintelligible pile of junk, you’ll probably grab the closest person, point at your code, and say, “Look what Microsoft did to my code. I’m better off rewriting this whole thing myself.” To avoid this situation, the Upgrade Wizard was designed with the goal of preserving your code as much as possible.

All of your comments should be preserved and in the same place they were before the upgrade. If you include #If…Then directives, these will also be included in your upgraded project, and you should find them exactly where you put them, relative to everything else. The wizard tries to upgrade each element of your code—language statements, controls, components, and ActiveX references—to the elements in Visual Basic .NET that most closely match.

If, for example, the MsgBox statement exists in both Visual Basic 6 and Visual Basic .NET, the wizard will upgrade your code to use MsgBox. If there is no direct match, the wizard finds a language statement or component that most closely matches the element you are using. For example, all references to a CommandButton Caption property in code are upgraded to the .NET Framework equivalent, Button.Text property. If no element in Visual Basic .NET represents the same element in Visual Basic 6, the code is left as is, an UPGRADE_ISSUE comment is inserted above the nonupgraded code, and an issue is logged to the upgrade report.

Just Make It Work

The Upgrade Wizard strives to upgrade all your code from Visual Basic 6 to Visual Basic .NET in a way that allows you to run the upgraded application immediately after upgrade. This works for simple applications or applications that make use of only Visual Basic features that the wizard can upgrade. For larger, more complex applications, you’ll need to fix some compiler errors and run-time issues that the Upgrade Wizard can’t resolve.

This goal fits hand in hand with the “It’s your code” objective. The best way to guarantee that your application will run the same after upgrade is to upgrade the application to use language statements, controls, and objects that behave identically in both Visual Basic 6 and Visual Basic .NET. For example, the Upgrade Wizard could choose to replace all instances of the Microsoft ListView ActiveX control in your application with the .NET Framework ListView control, but it doesn’t. The wizard doesn’t make this upgrade because by reusing the exact same component, that component—and thus the application—has a better chance of behaving the same as before. If the wizard were to replace your 20 ActiveX controls with 20 noncompatible .NET Framework controls, you would spend more time changing code to run your application. You can get your application up and running more quickly when the upgraded code contains language statements, controls, and objects that are compatible and most familiar to you. Once you get the application running, you can choose to incrementally replace code or components with .NET equivalents, and then test each change as you go.


Compatibility Library

To help your upgraded applications work, Visual Basic .NET includes a compatibility library. This library enables support for features such as control arrays, ADO data binding, and loading resources from a .res file. You can also use some of the functions in the compatibility library to convert between twips and pixels or between System.Drawing.Image and IPicture. All objects and functions contained in the compatibility library can be found under the namespace Microsoft.VisualBasic.Compatibility.VB6. The Upgrade Wizard automatically adds a reference to the compatibility library for all upgraded applications.


Upgrade Wizard Capabilities and Limitations

If you own Visual Studio .NET, the best way to get a sense of what the Upgrade Wizard can do for you is to run it. To start the wizard, run Visual Studio .NET and open the .vbp file for your favorite Visual Basic 6 project. The wizard will greet you. Make your way through each step, selecting the appropriate option (defaults work fine in most cases), and then sit back and watch the status for each upgraded item. After the upgrade is complete, look through the upgraded Visual Basic .NET application. As you look over your code, see if you can match up the changes that were made as described in the following sections.


Wizard Methodology

Before we get into the thick of specific changes that the Upgrade Wizard applies to your code, let’s take you up to 39,000 feet and look down across the broad landscape of what the Upgrade Wizard does. The wizard will upgrade the following items:

  • Your Visual Basic 6 project to the equivalent Visual Basic .NET project type

  • Your Forms to .NET Framework Windows Forms equivalents

  • Your intrinsic controls to .NET Framework Windows Forms equivalent controls

  • Your ActiveX control and object references to use the same ActiveX control and object references

  • Your Visual Basic language statements to use the same or equivalent language statements provided by Visual Basic .NET or the .NET Framework (or in some cases the Visual Basic .NET compatibility library)

Figure 7-1 illustrates how the elements of a form are upgraded. Figure 7-2 illustrates how code is upgraded.

Figure 7-1
How a form is upgraded.
Figure 7-2
How code is upgraded.

The lack of support for certain features in Visual Basic .NET—such as Dynamic Data Exchange (DDE) and ActiveX Documents—means that certain types of Visual Basic 6 applications aren’t well suited to a Visual Basic .NET upgrade. These types of applications you can consider in the “out” category when it comes to upgrade. However, just because an application is in the “out” category doesn’t necessarily mean that you can’t use it in Visual Basic .NET. You might still be able to communicate with your Visual Basic 6 application from Visual Basic .NET without changing it. For example, rather than upgrading your ActiveX DLL project you may want to leave it as is. You can create a Visual Basic .NET application that interoperates with the existing ActiveX DLL.

By reusing existing ActiveX components in your Visual Basic .NET applications you can create the Visual Basic .NET applications more quickly. The general ability of Visual Basic .NET applications to communicate with ActiveX components is known as COM interop. You may want to take advantage of COM interop when upgrading your applications so that you don’t need to upgrade the application and all of its COM—generally known as ActiveX—dependencies at once. See Chapter 9 for more information on using COM interop.

The Upgrade Wizard will either not upgrade or provide only partial support for the following items:

  • ActiveX EXE projects

  • Designers such as Add-In Designer, DHTML Page Designer, DataReport, and DataEnvironment

  • Certain ActiveX controls: SSTab and UpDown

  • ActiveX document–based applications

  • Extensibility model–based applications

  • OLE container control

  • Drag and drop

  • Graphics statements and graphical controls

  • Dynamic Data Exchange

Project Upgrade

Visual Basic 6 and Visual Basic .NET have the same basic concept of a project: a repository in which you can group all the needed source and resource files to create a specific output file, such as an EXE or a DLL. This section focuses on three project-related areas—types, attributes, and filenames—and discusses how the Upgrade Wizard handles each.

Project Types

Where possible, the Upgrade Wizard will upgrade your Visual Basic 6 project to an equivalent project type in Visual Basic .NET, as shown in Table 7-1. Equivalent Visual Basic .NET project types exist for common project types such as Standard EXE, ActiveX DLL, and UserControl.

note | The version of the Upgrade Wizard that shipped with Visual Basic .NET doesn’t include the ability to upgrade UserControl or WebClass projects. However, future versions of the Update Wizard that have this capability will be available for download from the Visual Basic Web site, http://msdn.microsoft.com/vbasic/.
Table 7-1 Visual Basic 6 and Equivalent Visual Basic .NET Project Types
Visual Basic 6 Project Type | Visual Basic .NET Project Type
Standard EXE | Windows application
ActiveX EXE | No equivalent (Choose between upgrade to a Windows application or a Class Library project.)
ActiveX DLL | Class Library
ActiveX control | Windows control library
WebClass-based project | XML Web Forms
Project Attributes

A Visual Basic 6 project contains a number of attributes, including Name, command-line arguments, conditional compilation constant settings, Help settings, version information, and compiler settings. Let’s look at how the Upgrade Wizard handles each of these attributes.

Project name

The project name for a Visual Basic 6 application is upgraded to be the Assembly Name and Root Namespace name for the Visual Basic .NET application. The Assembly Name is the name that other applications or tools use to load the EXE or DLL containing your project. The Root Namespace name is the name that other applications and tools use to refer to your Visual Basic .NET application or library. The Root Namespace name is roughly equivalent to the project name found in Visual Basic 6. Since the Visual Basic 6 project name is applied to your Visual Basic .NET project, anywhere in your code that you fully reference a type by prefacing it with the project name, the code will continue to work as is, without change.

Command-line arguments

If you create a Visual Basic 6 EXE project that relies on command-line arguments, you can test your application in the Visual Basic 6 debugger by setting command-line arguments on the Make tab for Project Properties, as shown in Figure 7-3.

Figure 7-3
Visual Basic 6 command-line setting in the Project Properties dialog box.

The command-line settings aren’t upgraded with your project. To edit them in Visual Basic .NET, select the project in the Solution Explorer, and then select Properties from the Project menu. Within the Properties dialog box, select Debugging under the Configuration Properties folder, as shown in Figure 7-4.

Figure 7-4
Visual Basic .NET command-line setting in Configuration Properties, Debugging.
Conditional compilation constant settings

If you use #If…Then conditional statements in your code, you can set the value for the condition as a global project setting. In Visual Basic 6 the setting is located on the Make tab of the Project Properties dialog box, as shown in Figure 7-5.

Figure 7-5
Visual Basic 6 conditional compilation statement setting in the Project Properties dialog box.

The constant values that you set will be upgraded to Visual Basic .NET. To edit your settings, select the project in the Solution Explorer and then choose Properties from the Project menu. Within the Properties dialog box, select Build under the Configuration Properties folder, as shown in Figure 7-6.

Figure 7-6
Visual Basic .NET custom constants setting under Configuration Properties, Build.
Help settings

In Visual Basic 6 you were allowed to have one Help file per project. You specify the Help file name on the General tab of the Project Properties dialog box, as shown in Figure 7-7. You could also set a project-level Help context ID.

Figure 7-7
Visual Basic 6 Help file setting in the Project Properties dialog box.

Creating Help to go along with your Visual Basic .NET application is different. A Visual Basic .NET application has no concept of a project-level Help file. Instead, you request Help on a per-form basis by including a HelpProvider component on each Windows Form that you want to support Help. Because no top-level Help file is associated with a Visual Basic .NET application, the Upgrade Wizard doesn’t upgrade this project setting.

For more information on how to add Help to your Visual Basic .NET application, refer to the following Help topic: ms-help://MS.MSDNVS/vbcon/html/vbconHelpSupportChangesInVisualBasic70.htm. You can find the Help topic by expanding the following nested Help topics:

Visual Studio .NET; Visual Basic and Visual C#; Upgrading Applications; Upgrading from Visual Basic 6.0; Introduction to Visual Basic .NET for Visual Basic Veterans; Forms Changes in Visual Basic .NET; Help Support Changes in Visual Basic .NET.
Version information

The Upgrade Wizard upgrades most version information associated with your Visual Basic 6 application. As illustrated in Figure 7-8, the following attributes are available on the Make tab of the Visual Basic 6 Project Properties dialog box:

  • Comments

  • Company Name

  • File Description

  • Legal Copyright

  • Legal Trademarks

  • Product Name

  • Major Version Number

  • Minor Version Number

  • Revision

    Figure 7-8
    Visual Basic 6 version settings in the Project Properties dialog box.

The attributes are upgraded to a separate file in your project called AssemblyInfo.vb, which contains project-level attributes for your project. For example, after you upgrade a Visual Basic 6 project, the version-related attributes are set in the AssemblyInfo.vb file as follows:

<Assembly: AssemblyTitle("Wow! My upgraded application")>
<Assembly: AssemblyDescription("I love upgrading to VB.Net")>
<Assembly: AssemblyCompany("Microsoft")>
<Assembly: AssemblyProduct("MyUpgradedApp")>
<Assembly: AssemblyCopyright("2001")>
<Assembly: AssemblyTrademark("")>
note | The revision number that appears in the version number is not upgraded, because Visual Basic 6 and Visual Basic .NET have different revision number formats. In Visual Basic 6 the revision number is a single part of the version number. The Visual Basic .NET version number is a two-part entry composed of a revision number and a build number.
Compiler settings

The Upgrade Wizard doesn’t upgrade any Visual Basic compiler options to Visual Basic .NET. Because the Visual Basic 6 and Visual Basic .NET compilers generate different types of instructions, the compiler options for each product have nothing in common. The native code optimization settings, such as Remove Array Bounds Checks, don’t make sense for the Visual Basic .NET compiler. The Visual Basic .NET compiler is required to generate secure code. Therefore, you don’t have the option of turning off certain safety checks, such as array bound checks; all safety checks need to be in place.

Project Filenames

The filename of your Visual Basic 6 .vbp project file is applied to your new Visual Basic .NET .vbproj file. For example, if the name of your Visual Basic 6 project is MyProject.vbp, the name of your Visual Basic .NET project will be MyProject.vbproj.

Individual item filenames

Each file contained within a Visual Basic 6 project retains the same base filename after upgrade. The file extension for all upgraded files is set to .vb. For example, if you have a form file in your project called Form1.frm, it will be upgraded to Form1.vb. What if you have two files with the same base filename, such as Main.frm and Main.bas? Since both files can’t upgrade to the same filename (Main.vb, in this case), the Upgrade Wizard avoids the conflict by renaming one of the files with its internal name. For example, if the name of module Main.bas is modMain, the file will be renamed modMain.vb to avoid conflicting with the form file Main.vb.

Forms and Intrinsic Controls

The Upgrade Wizard upgrades forms and intrinsic controls, such as CommandButton, TextBox, OptionButton, and CheckBox, contained on the form to the new .NET Framework Windows Forms package equivalent. After the upgrade process is complete, you’ll find that the size and layout of your forms are preserved. If a control cannot be upgraded, a red label is inserted on the upgraded form to alert you of the problem. Table 7-2 shows the mapping of Visual Basic 6 intrinsic controls to their Windows Forms equivalents.

Table 7-2 Mapping Between Visual Basic 6 Intrinsic Controls and .NET Windows Forms Controls
Visual Basic 6 Control Class | Visual Basic .NET Control Class | Upgrade Notes
VB.CheckBox | System.Windows.- Forms.Checkbox |
VB.ComboBox | System.Windows.- Forms.ComboBox |
VB.CommandButton | System.Windows.- Forms.Button |
VB.Data | No equivalent | Because the Data control supports DAO data binding—which is not supported by Windows Forms—the control doesn’t upgrade. You can use the ADO data control in its place, update your code to use ADO data binding, and the ADO control will upgrade.
VB.DirListBox | DirListBox in Microsoft.Visual- Basic.Compatibility.VB6 |
VB.DriveListBox | DriveListBox in Microsoft.Visual- Basic.Compatibility.VB6 |
VB.FileListBox | FileListBox in Microsoft.VisualBasic.- Compatibility.VB6 |
VB.Frame | System.Windows.- Forms.GroupBox or System.Windows.Forms.- Panel | If the frame has a border, the control upgrades to a GroupBox; it upgrades to a Panel if there is no border.
VB.HScrollBar | System.Windows.- Forms.HScrollBar |
VB.Image | System.Windows.- Forms.Picture | The Visual Basic 6 image control is a graphical control, which can be transparent. Windows Forms doesn’t support transparent controls, so transparency will be lost after upgrade.
VB.Label | System.Windows.- Forms.Label | Visual Basic 6 labels are graphical and can be transparent. Visual Basic .NET labels have a window and cannot be transparent. Transparency will be lost after upgrade.
VB.Line | No equivalent or System.Windows.- Forms.Label | For vertical and horizontal lines a label is used. The label is set to the width of the line. The back color of the label is set to the line color. Diagonal lines do not map.
VB.ListBox | System.Windows.- Forms.ListBox |
VB.OLE | No equivalent | In some cases you can replace the control with the Microsoft Web Browser control and use the Navigate property.
VB.OptionButton | System.Windows.- Forms.OptionButton |
VB.PictureBox | System.Windows.- Forms.PictureBox or System.Windows.- Forms.Panel | If the PictureBox has child controls, it upgrades to a Panel; otherwise, it upgrades to a PictureBox.
VB.Shape | No equivalent | Shape is a graphical control; Windows Forms doesn’t support graphical controls. You can use the .NET Framework graphics commands in place of the control.
VB.TextBox | System.Windows.- Forms.TextBox |
VB.Timer | System.Windows.- Forms.Timer | Setting the Interval property of a Visual Basic .NET timer to 0 will throw an exception. You need to set the Enabled property of a Timer to False to disable this behavior.
VB.VScrollBar | System.Windows.- Forms.VScrollBar |
Layout and Design-Time Property Settings

The design-time layout and properties you set on a Visual Basic 6 form and the controls contained on the form are saved in a special section of the .frm file. The form and controls are saved in property description blocks, as illustrated in Figure 7-9.

Figure 7-9
Visual Basic 6 .frm file in text view.

Windows Forms stores layout and property settings differently. All settings are saved as part of your code in a hidden block called Windows Form Designer Generated Code. The design-time settings for a Windows form can be found in the InitializeComponent subroutine within this hidden block.

The Upgrade Wizard maps your Visual Basic 6 design-time settings to generated code in InitializeComponent. For example, suppose you have a Visual Basic form containing a button named MyButton, as shown in Figure 7-10. (You can find the MyButton.vbp project on the companion CD.)

Figure 7-10
Visual Basic 6 form with a button on it.

The Visual Basic .frm file for the form will contain this description:

Begin VB.Form Form1
   Caption         =   "Form1"
   ClientHeight    =   2085
   ClientWidth     =   3750
   ClientLeft      =   60
   ClientTop       =   345
   Begin VB.CommandButton cmdMyButton
      Caption         =   "MyButton"
      TabIndex        =   0
      Default         =   -1  'True
      Left            =   1080
      Top             =   720
      Width           =   1455
      Height          =   495
   End
End

If you upgrade the preceding Visual Basic .frm, the generated Visual Basic .NET code will be

Private Sub InitializeComponent()
   Me.cmdMyButton = New System.Windows.Forms.Button

   Me.Text = "Form1"
   Me.ClientSize = New System.Drawing.Size(250, 139)
   Me.Location = New System.Drawing.Point(4, 23)

   Me.AcceptButton = Me.cmdMyButton

   Me.cmdMyButton.Text = "MyButton"
   Me.cmdMyButton.TabIndex = 0
   Me.AcceptButton = Me.cmdMyButton
   Me.cmdMyButton.Size = New System.Drawing.Size(97, 33)
   Me.cmdMyButton.Location = New System.Drawing.Point(72, 48)
   Me.cmdMyButton.Name = "cmdMyButton"
   Me.Controls.Add(cmdMyButton)
End Sub
note | We’ve removed superfluous settings from the .frm and InitializeComponent listings to focus attention on how the design-time settings of a form and control are generally upgraded. We’ve selected properties that demonstrate the more complex mappings that the Upgrade Wizard supports. Most other settings are one-to-one mappings from (for example) Property X to Property X.

Table 7-3 shows how the original .frm description and the generated Visual Basic .NET code match up line for line.

Table 7-3 Mapping Between Visual Basic 6 .frm Settings and Visual Basic .NET Code
Visual Basic 6 .frm Setting | Visual Basic .NET Code | Upgrade Notes
Caption = "Form1" | Me.Text = "Form1" | Caption property is upgraded to Text property in Windows.- Forms. Generally you’ll see Caption properties mapped to Text properties in your code.
ClientHeight = 2085 ClientWidth = 3750 | Me.ClientSize = New System.- Drawing.Size(250, 139) | Form Height and Width settings are mapped to the Visual Basic .NET form’s ClientSize property. Sizes are automatically converted from twips to pixels.
ClientLeft = 60 ClientTop = 345 | Me.Location = New System.Drawing.- Point(4, 23) | Left and Top settings are mapped to the Location property. Positions are automatically converted from twips to pixels.
Begin VB.CommandButton cmdMyButton | Me.cmdMyButton = New System. Windows.Forms.- Button | In Visual Basic 6, the run time takes care of creating the form and controls spelled out in the .frm file. In Visual Basic .NET, declaration and allocation of each control is explicit.
Caption = "MyButton" | Me.cmdMyButton.- Text = "MyButton" | Caption is upgraded to Text.
Default = -1 "True" | Me.AcceptButton = Me.cmdMyButton | Default and Cancel are no longer properties of individual buttons in Visual Basic .NET. Instead, you set the AcceptButton and CancelButton properties of the form to point to the respective Default and Cancel buttons. In this case the Default design-time property setting is automatically upgraded to the AcceptButton setting on the current form.

After the form is upgraded, it will look nearly identical to its Visual Basic 6 counterpart, as shown in Figure 7-11.

Figure 7-11
Upgraded Visual Basic .NET form.

Let’s take a look at some cases in which the design-time settings don’t upgrade. We’ll place a Data control on the Visual Basic 6 form with the MyButton control. We’ll also set the UseMaskColor property of the MyButton control to True, resave the Visual Basic 6 application, and upgrade it again. The first issue is that the Data control (as listed in Table 7-2 on page 128) can’t be upgraded. A red label on the form represents the Data control. Red labels on your form indicate that the form didn’t fully upgrade.

An interesting characteristic of this upgraded project is that if you build it, you won’t get any compiler errors. How can this be true if there is no property equivalent to the CommandButton’s UseMaskColor property? If the Upgrade Wizard emitted the design-time property setting in the InitializeComponent section, it would lead to a compiler error. Any compiler errors that occur within InitializeComponent will prevent you from viewing the design-time form. Since it is important that you be able to view your form after upgrade, the wizard intentionally omits any design-time properties that don’t map. Instead, it logs an error to the upgrade report.

If you open the _UpgradeReport.htm file included with your upgraded project, you’ll see a summary of the properties that weren’t upgraded for each control. In this example, you’d see an entry for the CommandButton’s UseMaskColor property. Figure 7-12 shows the upgrade report that is produced. The report that documents the issue provides a link to Help, and, where possible, it offers suggestions on how to work around or solve the issue.

Figure 7-12
Visual Basic .NET upgrade report.
Control Arrays

Control arrays are a long-standing Visual Basic feature. A control array allows several controls to share the same events and initial property settings, allowing you to apply the same behavior to the controls in the control array. You can also create control arrays in Visual Basic .NET. However, the way you do so is very different from the way you create them in Visual Basic 6. Think of a control array in Visual Basic .NET as a standard array containing controls. To create a control array in Visual Basic .NET, you need to write code to declare an array of the particular control type, and then add the controls to the array. You write additional code to hook the same set of events to each member of the control array. If you’re dealing with a dozen or more control array elements on your form, writing all that code is a time-consuming process.

The Visual Basic .NET compatibility library introduced earlier in this chapter includes support for control arrays. The compatibility library contains an array class for each control type. For example, the control array class for Button is called ButtonArray. The control array class includes methods such as Item and Load that allow you to access control array elements or add new members to the control array. In addition the control array class includes all of the events for the underlying control. The events are shared for all elements of the Visual Basic .NET control array, as you would expect based on how Visual Basic 6 control arrays behave.

The Upgrade Wizard upgrades your Visual Basic 6 control arrays to target control array classes defined in the Visual Basic .NET compatibility library. Control arrays and all associated code are upgraded as is, with minor syntactical changes in some cases, as illustrated in the following example.

Consider a Visual Basic 6 form that has two command buttons in a control array called Command1. The following Visual Basic 6 code loads Command1(2) as a new control array element:

Private Sub Command1_Click(Index As Integer)
   MsgBox "Command1 (" & Index & ") clicked"
End Sub

Private Sub Form_Load()
   Load Command1(2)
   Command1(2).Left = Command1(1).Left + _
      Command1(1).Width
   Command1(2).Visible = True
End Sub

Here’s the Visual Basic .NET code after the Upgrade Wizard has upgraded it:

Private Sub Command1_Click(ByVal eventSender As System.Object, _
ByVal eventArgs As System.EventArgs) _
Handles Command1.Click
   Dim Index As Short = Command1.GetIndex(eventSender)
   MsgBox("Command1 (" & Index & ") clicked")
End Sub

Private Sub Form1_Load(ByVal eventSender As System.Object, _
ByVal eventArgs As System.EventArgs) _
Handles MyBase.Load
   Command1.Load(2)
   Command1(2).Left = _
      VB6.TwipsToPixelsX(VB6.PixelsToTwipsX(Command1(1).Left) + _
      VB6.PixelsToTwipsX(Command1(1).Width))

   Command1(2).Visible = True
End Sub

Focus your attention on the usage of Command1, and ignore the pixel-to-twips conversions; you’ll see that the code you need to use control arrays is nearly identical, with a couple of notable differences:

  • The Load statement becomes a Load method on the base control array element Command1—for example, Command1.Load(2).

  • The control array index is not passed directly as a parameter to the Command1_Click event. The event parameter is instead obtained from the base control array object Command1 by calling GetIndex and passing GetIndex the source of the event. The event source will be the member of the control array that you clicked.

Despite these differences, the good news is that Visual Basic .NET and the Upgrade Wizard both support control arrays.

ActiveX Controls and ActiveX References

The general approach to upgrading ActiveX controls and references is to reuse the exact same controls and references in your upgraded Visual Basic .NET application. Although in theory your ActiveX components should work the same as they did prior to the upgrade, you might encounter some issues. These issues stem from differences between the Visual Basic 6 and Visual Basic .NET ActiveX control hosting environments. Issues can also stem from the fact that .NET supports ActiveX components by communicating with them through a COM interop layer. (These issues are discussed in Chapter 9 and Chapter 13.) This section focuses on some of the exceptions to the rule that ActiveX controls and references are upgraded to use the same component. In some cases, an ActiveX control or reference is upgraded to use a native .NET component replacement.

ADO Data Control

You can place the ActiveX ADO Data control (ADODC) shipping with Visual Basic 6 on a Windows form, but you can’t use it to bind to Windows Forms controls. For example, you can’t place an ADODC control on a Windows form and hook a TextBox up to it because the ADODC control has no way to communicate the bound data to the Windows Forms TextBox control. The ADODC control can communicate only with other ActiveX controls, not with .NET Windows Forms controls.

To resolve this issue, the Visual Basic .NET compatibility library contains a .NET version of the ADODC control. You can use the ADODC .NET control to bind data to Windows Forms controls and other ActiveX controls. The Upgrade Wizard automatically upgrades any instances of ADODC ActiveX controls to ADODC .NET controls. The ADODC .NET control was designed to be a compatible replacement for the ADODC ActiveX control.

How does this help you? If you use the ADODC ActiveX control in your Visual Basic 6 applications, you should expect your ADO data-bound applications to continue working after you upgrade to Visual Basic .NET.

Microsoft Data Binding Collection

The Visual Basic .NET compatibility library provides an MSBind .NET class as a compatible replacement for the ActiveX MSBind object found in Visual Basic 6.

Visual Basic 6 allows you to link a control such as a TextBox with the ADODC control by setting the DataSource property of the TextBox control. Although the DataSource property appears on the TextBox control, the property is added and managed by Visual Basic and not by the control itself. In fact the control does not even know that it has a DataSource property. When you set the DataSource property, Visual Basic creates an instance of MSBind and associates the TextBox with a field in a database. The database field is specified by the DataField property, which is also added to the TextBox control and managed by Visual Basic.

The problem is that Windows Forms doesn’t provide any support for the DataSource and DataField properties. In fact, when you place a TextBox (or any other control) on a Windows form, you won’t find a DataSource or DataField property. How do you get data binding to work in Visual Basic .NET? You use an instance of a binding collection (MSBind).

You can add items to the binding collection to associate a property on a control with a field in a database. To bind a control’s property to a field in a database, you add the control and the property where you want the database data to be stored to the MSBind collection. If you want to implement custom data binding or binding to nontraditional properties on a control, use MSBind in your Visual Basic 6 application.

Because there is no DataSource or DataField property, the Upgrade Wizard translates the binding information stored in the DataSource and DataField properties to binding information set into an MSBind .NET binding collection. Suppose you’re upgrading an application with a TextBox (Text1) bound to an ADODC (Adodc1) control. The TextBox displays the CompanyName contained in the Customers table. The Upgrade Wizard automatically generates the following code to hook up data binding:

Private ADOBind_Adodc1 As VB6.MBindingCollection

Public Sub VB6_AddADODataBinding()
   ADOBind_Adodc1 = New VB6.MBindingCollection()
   ADOBind_Adodc1.DataSource = CType(Adodc1, msdatasrc.DataSource)
   ADOBind_Adodc1.Add(Text1, "Text", "CompanyName", Nothing, "Text1")
   ADOBind_Adodc1.UpdateMode = _
      VB6.UpdateMode.vbUpdateWhenPropertyChanges
   ADOBind_Adodc1.UpdateControls()
End Sub

Public Sub VB6_RemoveADODataBinding()
   ADOBind_Adodc1.Clear()
   ADOBind_Adodc1.Dispose()
   ADOBind_Adodc1 = Nothing
End Sub
note | In Visual Basic 6, the binding collection is called BindingCollection. In Visual Basic .NET, the binding collection is called MBindingCollection. The M stands for managed, meaning the managed code version of the BindingCollection.

VB6_AddADODataBinding is called from Sub New so that the data bindings are set up as soon as the form is created. VB6_RemoveADODataBinding is called when the form is disposed. Data bindings are removed when the form is destroyed.

In general, if you have an ADODC-based data-bound application in which all the data binding is set up at design time, the Upgrade Wizard takes care of generating the necessary Visual Basic .NET code to make it work. Because there are no properties you can set in the Property Browser, updating the application after upgrade requires considerably more work. You’ll need to update VB6_AddADODataBinding to add or remove bound controls and associated database fields.

SSTab Control

The Upgrade Wizard treats the SSTab control as a special case because the SSTab ActiveX control requires a special interface called ISimpleFrame. This interface enables the control to contain other controls. Without this interface, you can’t place other controls within the SSTab. If you’ve used the SSTab control, you know that it would be pretty useless if you couldn’t place other controls inside it. The control reaches the height of uselessness when you place it on a Windows form. Windows Forms doesn’t support the ISimpleFrame interface, so you can’t place any controls on the tabs within the SSTab.

The Upgrade Wizard recognizes this situation and rectifies it by automatically upgrading your instances of SSTab controls to instances of Windows Forms TabControls. The Windows Forms TabControl is similar—but not identical—in behavior to the SSTab control. You’ll find that after upgrade, the TabControl has a similar appearance to the SSTab control. All the controls contained in the SSTab control will be found on their respective tabs in the Windows Forms TabControl. However, you’ll notice differences when you write code to operate the TabControl. Most of these differences stem from the TabControl having a TabPage collection object. You need to manually update the code that sets array properties, such as TabCaption, to instead set the caption on the specific tab within the TabPage collection.

UpDown Control

The UpDown control uses internal interfaces supported by Visual Basic 6 to buddy up with another control. These interfaces aren’t supported in Windows Forms. Furthermore, because the control is an ActiveX component, it can’t link to a non-ActiveX component such as a Windows Forms Button control. If you place an ActiveX UpDown control on a Windows form, you should set the BuddyControl property to another control on the form, and then run the application. The UpDown control doesn’t update the buddy control.

The Upgrade Wizard won’t upgrade UpDown controls found on a form. Instead, you must replace each UpDown control with the Windows Forms NumericUpDown control.

Visual Basic Code

The Visual Basic .NET language is largely a superset of the Visual Basic 6 language. You use the same keywords, conditional statements, and expressions that you use when writing a Visual Basic 6 application. The Upgrade Wizard will upgrade most of your code that relates to the core Visual Basic language without making any changes to it. Chapter 11 covers issues related to upgrading code in more detail. This section focuses on how the Upgrade Wizard upgrades your Visual Basic 6 code.

General Types of Code Conversions

The Upgrade Wizard upgrades your Visual Basic 6 language statements using a variety of means, which are explained in the following sections.

Mapping to native language statement with the same name

Most Visual Basic core language statements, such as If…Then, For…Next, Select Case, And, Or, Do…Loop, On Error GoTo, and MsgBox fall into this category. The code you see in Visual Basic 6 is the code you get in Visual Basic .NET.

Mapping to native language statement with a different name

The functions IsEmpty, IsNothing, and IsObject fall into this category. Each of these functions operates on Variants, which aren’t supported in Visual Basic .NET. These functions are upgraded to functions that operate on Object types. The following Visual Basic 6 code:

Dim b As Boolean
Dim v As Variant

b = IsEmpty(v)
b = IsObject(v)
b = IsMissing(v)
b = IsNull(v)

upgrades to the following Visual Basic .NET code:

Dim b As Boolean
Dim v As Object
'UPGRADE_WARNING: IsEmpty was upgraded to IsNothing and has
'a new behavior.
b = IsNothing(v)
'UPGRADE_WARNING: IsObject has a new behavior.
b = IsReference(v)
'UPGRADE_NOTE: IsMissing() was changed to IsNothing()
b = IsNothing(v)
'UPGRADE_WARNING: Use of Null/IsNull() detected.
b = IsDbNull(v)
Mapping with coercion

Visual Basic .NET disallows direct assignments of incompatible types to each other without an explicit conversion. For example, you can’t directly assign an integer a String value or a Date to a Double. You need to do an explicit conversion using a function such as CInt or CShort. Some types contain conversion methods, such as ToString or ToOADate. As an example of how conversion functions are added after upgrade, consider the following Visual Basic 6 code:

Dim i As Integer
Dim TodaysDate As Date
Dim d As Double
i = "7"
TodaysDate = Now
d = TodaysDate

This code upgrades to the following Visual Basic .NET code:

Dim i As Short
Dim TodaysDate As Date
Dim d As Double
i = CShort("7")
TodaysDate = Now
d = TodaysDate.ToOADate   'Converts to an OLE Automation compatible
                          'Date value
note | If you need to convert between types in your Visual Basic .NET code, look at the methods implemented by the .NET System.Convert class.
Mapping to an equivalent .NET Framework function

Math functions such as Abs, Atn, Cos, and Sqrt fall into this category. Certain properties on the App object, such as PrevInstance, upgrade to .NET equivalents as well. For example, take a look at the following Visual Basic 6 code:

Dim i As Integer
i = Abs(-1)

This code upgrades to the following Visual Basic .NET code, using the equivalent function from the System.Math class:

Dim i As Short
i = System.Math.Abs(-1)
Mapping to a function provided by the Visual Basic .NET compatibility library

The LoadResString, LoadResData, LoadResPicture, SendKeys, and some App object methods fall into this category. The following Visual Basic 6 code:

Dim strPath As String
strPath = App.Path

upgrades to

Dim strPath As String
strPath = VB6.GetPath
No mapping available

This category includes cases that can’t be automatically upgraded. Instead, the wizard generates an upgrade issue and possibly an UPGRADE_TODO item. The binary string functions, such as AscB, LeftB, MidB, and RightB fall into this category. For example, the following Visual Basic 6 code:

Dim iCharCode As Integer
Dim BinaryString() As Byte
BinaryString = StrConv("This is my binary string", vbFromUnicode)
iCharCode = AscB(BinaryString)

upgrades to the following Visual Basic .NET code:

Dim iCharCode As Short
Dim BinaryString() As Byte
'UPGRADE_ISSUE: Constant vbFromUnicode was not upgraded.
'UPGRADE_TODO: Code was upgraded to use
'System.Text.UnicodeEncoding.Unicode.GetBytes() which may not have the
'same behavior.
BinaryString = System.Text.UnicodeEncoding.Unicode.GetBytes(StrConv( _
   "This is my binary string", vbFromUnicode))
'UPGRADE_ISSUE: AscB function is not supported.
iCharCode = AscB(BinaryString)

Several UPGRADE_ messages are emitted in this case. The UPGRADE_ISSUE messages relate to byte-related support not being available. The UPGRADE_TODO message warns you that the .NET conversion function used might not have the behavior you desire.

Option Explicit On Included Automatically

When you use the Option Explicit statement in Visual Basic 6, the compiler forces you to declare all variables with a Dim, Private, Public, or Static statement (for example). If you’re not using the Option Explicit statement in Visual Basic 6, the Upgrade Wizard includes the Option Explicit On statement in all forms and modules and takes care of declaring any undeclared variables for you. If you’ve been planning to go through and explicitly declare all of your variables, or you’ve avoided doing it because of time constraints, your wait has been rewarded. The Upgrade Wizard takes care of this for you.

DefType Is Upgraded

If you use the DefType statement to determine the variable type for unqualified variable declarations, your DefType declarations will be upgraded to the appropriate type. For example, the following Visual Basic 6 code:

DefStr A-Z				'All unqualified variable declarations will be Strings
Sub Main
   Dim s
   s = "I'm a string"
End Sub

upgrades to the following Visual Basic .NET code:

Sub Main
   Dim s As String
   s = "I'm a String"
End Sub
Data Types Are Upgraded to Fit

The storage size for Visual Basic intrinsic types such as Integer and Long has changed in Visual Basic .NET. In Visual Basic 6 an Integer is 16 bits and a Long is 32 bits. In Visual Basic .NET an Integer is 32 bits and a Long is 64 bits. Most of the time it doesn’t matter whether you use an Integer instead of a Long. If you have a loop index that goes from 0 to 10, you can nitpick over the performance implications of using one size or another, but generally it makes no difference. The program works the same way.

Sometimes, however, the size difference matters. For example, if you’re calling Windows API functions that require a 32-bit integer argument, you had better use a Declare statement that declares the argument as 32 bits.

To keep your application running smoothly after upgrade, the Upgrade Wizard declares all of your numeric types to use the correct Visual Basic .NET equivalent based on size. This means that an Integer variable is upgraded to Short. A variable of type Long is upgraded to type Integer. In the case of the Variant type, the Upgrade Wizard maps to the closest equivalent type found in Visual Basic .NET: Object. Table 7-4 gives a mapping of types between Visual Basic 6 and Visual Basic .NET. The table provides mappings where the name of the type is different between Visual Basic 6 and Visual Basic .NET. All other types, such as Byte, Single, Double, and String, map as is.

Table 7-4 Mapping of Types Between Visual Basic 6 and Visual Basic .NET
Visual Basic 6 Type | Upgrades to Visual Basic .NET Type
Integer | Short
Long | Integer
Variant | Object
Currency | Decimal
ByRef Added to Unqualified Parameters

The default calling convention in Visual Basic 6 is to pass all unqualified parameters as ByRef. Visual Basic .NET, on the other hand, requires you to qualify all parameters as ByVal or ByRef. Because the Visual Basic 6 default calling convention is ByRef, the Upgrade Wizard upgrades and qualifies all unqualified parameters with the ByRef keyword.

For example, the following Visual Basic 6 code:

Sub Test(MyParam As Long)
   MyParam = 7
End Sub

upgrades to the following Visual Basic .NET code:

Sub Test(ByRef MyParam As Integer)
   MyParam = 7
End Sub
Code-Behind Forms

Code-behind forms refers to code that makes reference to a form, control, property, method, or event. Although this is normally code you write behind a form, you can also reference controls from other project items, such as a class module or .bas module. We will limit this discussion to code that references a property, method, or event for any form or control in the project.

Code that references a form or control property is upgraded in a similar fashion to a design-time property setting, as discussed earlier in the section “Layout and Design-Time Property Settings.” The main difference is that for properties you refer to in code, the Upgrade Wizard will leave any code that cannot be upgraded as is. In many cases this will lead to a compiler error. In addition, a comment that begins with UPGRADE_ (such as UPGRADE_ISSUE) will be inserted on the line immediately above the nontranslated statement. For example, let’s say you attempt to upgrade the following code:

cmdMyButton.UseMaskColor = True

The upgraded code will appear as follows:

'UPGRADE_ISSUE: CommandButton property cmdMyButton.UseMaskColor
'was not_upgraded. Click for more:
''ms-help://MS.VSCC/commoner/redir/redirect.htm?keyword="vbup2064"'
cmdMyButton.UseMaskColor = True

The Upgrade Wizard leaves the code in as is—without omitting it or commenting it out—because doing so makes the issue stand out like a sore thumb. You are forced to recognize and deal with the problem before you can run your upgraded application. The philosophy here is that it’s best to get all the bad news up front. It’s generally much easier to resolve a problem as soon as you discover it, rather than finding it down the road after you’ve made a bunch of code changes. If the Upgrade Wizard were simply to emit the UPGRADE_ISSUE command and avoid the compiler error by commenting out the offending line, you might never pay attention to the issue. If a problem related to the issue surfaced months later, you might end up spending a significant amount of time tracking down the line of code that the Upgrade Wizard conveniently commented out for you.

Global Objects

The Upgrade Wizard provides minimal support for upgrading global objects such as App, Printer, Clipboard, and Screen. In fact, the wizard upgrades some methods for the App and Screen objects, and that’s all. You will need to manually upgrade the code related to the other global objects. For example, to upgrade your Printer object–related code, use the .NET System.Drawing.Printing.PrintDocument class. Chapter 12 contains more information on upgrading global objects.

Class Modules and User Controls

Each class module and user control upgrades to an equivalent class module and user control in Visual Basic .NET. Table 7-5 lists the upgrades for class attributes of class modules and user controls.

Table 7-5 Class Attribute Mappings
Class Attribute | Upgrades To
Private | Friend
Public noncreatable | Public
Single use | Public
Global single use | Public
Multiuse | Public
Global multiuse | Public
Persistable Classes: ReadProperties, WriteProperties

The ReadProperties and WriteProperties events for persistable classes and user controls are not upgraded because the .NET Framework doesn’t support the same notion of data persistence that ActiveX supports. The .NET Framework requires that clients of a .NET server persist the properties of the server by generating code to set server properties to initial property values. Clients persist ActiveX servers, on the other hand, by passing either a PropertyBag or a binary stream to the ActiveX server, requesting that the server save the state of its properties to the PropertyBag or stream. When the server is a Visual Basic–authored ActiveX component, the client will invoke the ReadProperties and WriteProperties events of the component. After the server has been upgraded to a .NET server, the client directly obtains the property values for the ActiveX component without invoking events such as ReadProperties and WriteProperties.

The bottom line is that you don’t need ReadProperties and WriteProperties to create a persistable Visual Basic .NET server class. You can remove this code after upgrade.

DataSource Classes

For a Visual Basic 6 class in which the DataSourceBehavior property is set to vbDataSource, the class is upgraded to implement the DataSource interface. Your code contained within the GetDataMember event is upgraded to the GetDataMember method of the implemented DataSource interface. You can test your upgraded class by assigning an instance of it to the DataSource property of a control such as the Visual Basic 6 DataGrid. The DataGrid should display the data returned by your upgraded Visual Basic .NET DataSource class.

Objects for Accessing Data

If you’ve written a Visual Basic 6 application based on ADO, it will upgrade as is, including code related to ADO data binding. In addition, when upgrading your ADO-based application, the Upgrade Wizard will promote all references to ADO versions 2.6 or earlier to ADO version 2.7. If you based your application on DAO or RDO, the DAO- and RDO-related elements of your application will also upgrade as is as long as you are not using DAO or RDO data binding.

Data Binding

To support data binding, any Microsoft ADODC ActiveX controls you use will automatically be upgraded to use a .NET ADODC control. The .NET version is designed to be compatible with the ActiveX version.

Visual Basic 6 provides internal support for DAO and RDO data binding. There are neither internal supports nor public classes in Visual Basic .NET to support DAO or RDO data binding. If your application requires data binding, you should manually replace your DAO- or RDO-related elements with ADO-related elements. You can make the changes to your Visual Basic 6 application, test the changes, then upgrade your ADO-based Visual Basic 6 application to Visual Basic .NET.

DataEnvironment

If you use a DataEnvironment designer in your Visual Basic 6 application, the wizard will create a Visual Basic .NET DataEnvironment class—contained in its own .vb file—and add it to your upgraded Visual Basic .NET project. The DataEnvironment class will contain all settings and objects found in the original Visual Basic 6 DataEnvironment. Any code that references the DataEnvironment will continue to work. You should be able to get your upgraded application to work with the generated DataEnvironment class at run time.

Visual Basic .NET doesn’t provide a way for you to edit DataEnvironment properties at design time. To change property settings, you need to edit the code contained in the generated DataEnvironment class file.

Designers

Visual Basic .NET doesn’t include designers such as the DHTML, WebClass, DataEnvironment, and DataReport. In fact, none of the designers are included with Visual Basic .NET, nor is there any support to load a designer in Visual Studio .NET.

In some cases the code and design-time settings related to a designer are upgraded to work in the upgraded Visual Basic .NET application. For example, code associated with your WebClass is upgraded to Web Forms; DataEnvironment-related settings and code are upgraded to a generated DataEnvironment class. The CrystalReports package in Visual Studio .NET supports reading reports stored in DataReport designer format.


Conclusion

In terms of upgrade potential, is your application in or out? As discussed in this chapter, the situation really depends on the type of Visual Basic application you’ve written. If your application drives the Visual Basic 6 extensibility model, uses an ActiveX designer such as the DHTML page designer, or relies on Dynamic Data Exchange (DDE), it’s in the out category. You’ll need to do a significant amount of rewriting to make your application work in the .NET environment. In some cases, it might make more sense to leave your application running in Visual Basic 6 and interoperate with it from Visual Basic .NET.

If, on the other hand, your application is a more typical three-tier application with a presentation layer that communicates with a server component, it’s most definitely in the in category. You’ll find that the Upgrade Wizard does a good job of upgrading applications that fall into this category. The Upgrade Wizard will strive to upgrade your application in the least intrusive way possible that will allow you to get it up and running quickly. Using the Upgrade Wizard, you will be able to get your Visual Basic 6 application up and running in Visual Basic .NET more quickly than by porting your Visual Basic 6 application by hand.


Errors, Warnings, and Issues

Chapter 8
Errors, Warnings, and Issues

This chapter looks at the different errors, warnings, and issues the Upgrade Wizard generates when you upgrade your Visual Basic 6 projects to Visual Basic .NET.

Each time you upgrade a project, the wizard upgrades most of the code and objects to Visual Basic .NET, but some items are not automatically changed. These items require manual modifications after the wizard finishes. To understand why, consider this example. In Visual Basic .NET, the Caption property of a label is now called the Text property. Label.Caption maps to Label.Text. The Upgrade Wizard knows about this and changes all Label.Caption references to Label.Text. But what happens if the wizard can’t determine whether a particular object is indeed a label? When the wizard isn’t able to determine what data type an object is, it inserts a warning into your code. The following code is a good example. Assume Form1 is a form, and Label1 is a Label control:

Dim o As Variant
Set o = Form1.Label1
o.Caption = "VB Rocks"

In this example, the variable o is declared as a variant and then assigned to Label1. The Caption property is then set to the string “VB Rocks.” Because o is a variant and is late-bound, Visual Basic 6 doesn’t know what type of object it is at design time. (This is why you don’t get IntelliSense for variants at design time.) Like the Visual Basic 6 Code Editor, the Upgrade Wizard also has a design-time view of the code. We’ll follow what the wizard does as it walks through the three lines of code in our simple example:

Dim o As Variant

The first line is easy; the wizard knows to change Variant data types to Object, so it upgrades the first line as follows:

Dim o As Object

The second line is also upgraded automatically:

Set o = Form1.Label1

This is a Set statement, so it has to be an object assignment rather than a default property assignment. The variable o is assigned to the control Form1.Label1 and upgraded as follows:

o = Form1.DefInstance.Label1

The first two lines were upgraded automatically. The final line, however, causes problems:

o.Caption = "VB Rocks"

Because o is late-bound, the Upgrade Wizard doesn’t know what to do with the Caption property. Perhaps o is a label, but perhaps it is another type of control. When the Upgrade Wizard finds code that can’t be resolved at design time, it inserts a warning. The final line upgrades to the following:

'UPGRADE_WARNING: Couldn't resolve default property of object o.
Caption. 'Click for more: ms-help://MS.MSDNVS/vbcon/html/vbup1037.htm
o.Caption = "VB Rocks"

What do you do now? If you run the code, it causes a run-time exception. Often, the best solution is to change the variable declaration to be strongly typed. In this example, change the first line to

Dim o As Label

After this change, the compiler will report that Caption is not a property of Label by generating a Task List entry and by highlighting the code with a green underline. You should then change the property to Text. Another good solution that avoids the problem entirely is to declare o as a label in Visual Basic 6. Declaring it as a label makes it strongly typed, and the wizard will have no trouble changing Caption to Text. Here is how to rewrite the code in Visual Basic 6 to be strongly typed:

Dim o As Label
Set o = Form1.Label1
o.Caption = "VB Rocks"

After upgrading, the code becomes

Dim o As System.Windows.Forms.Label
o = Form1.DefInstance.Label1
o.Text = "VB Rocks"

This code works perfectly in Visual Basic .NET.

This is a simple example; we can figure out from looking at the code that o is a label. However, the wizard doesn’t know this. It has a set of rules for converting Visual Basic 6 to Visual Basic .NET that it follows for each property, method, and event of a particular object type. When a variable is late-bound, the wizard doesn’t know how to interpret its properties.

Couldn’t the Upgrade Wizard simply guess the object type? Other examples show the futility of this strategy. In the following example, even the original author of the code wouldn’t be able to determine what type of object o will be set to at run time. Let’s assume that Label1 is a label and Text1 is a text box:

Dim o As Object
If someVariable = 1 Then
   Set o = Me.Text1
Else
   Set o = Me.Label1
End If

It’s impossible to know whether o will be a label or a text box until the code actually executes.

EWI is the common term for the errors, warnings, and issues the Upgrade Wizard inserts into your code. EWIs alert you to problems like the preceding one. EWI comments are inserted on the line before the problem. The comment text is in two parts: the message and the link to Help. In the following code, the underlined part of the text is actually a hyperlink to the appropriate Help topic:

'UPGRADE_WARNING: Couldn't resolve default property of object o.
Caption. 'Click for more: ms-help://MS.MSDNVS/vbcon/html/vbup1037.htm

All EWIs are associated with Help topics that show you how to fix the problem. Because the EWI is inserted as a comment, it doesn’t affect the compilation or execution of your project.

Identifying All the Problems

Does the Upgrade Wizard detect every problem during an upgrade? Is every problem marked in your code with easy-to-follow guidance? Unfortunately, it’s not quite as simple as that. Some problems might have only a 0.01 percent chance of occurring, so what should the wizard do—mark every occurrence of the code, generating false alarms in 99.99 percent of the cases, or ignore a potential problem and let it generate a compile or run-time error? The Upgrade Wizard walks a fine line in determining what it should treat as an error and what it shouldn’t. It warns about most problems it finds, but as you’ll see later in this chapter, there are some that it doesn’t warn about.


The Different Kinds of EWIs

There are six types of EWIs. Let’s look at the most serious type first.

Upgrade Issues

Upgrade issues are inserted into your code whenever the wizard meets code that will cause a compile error. Upgrade issues mark items that you need to fix before the program will run. A good example is the OLEDrag method. This method has no direct equivalent in Windows Forms, so the following code

Text1.OLEDrag

upgrades to

'UPGRADE_ISSUE: TextBox method Text1.OLEDrag was not upgraded
Text1.OLEDrag

Because the OLEDrag method is not a member of the TextBox object, the Text1.OLEDrag statement causes a compile error. The compile error shows in the Task List. The upgrade issue does not. Why not? If it did, you would have two Task List entries for every compiler error instead of one. As you’ve learned, at the end of every EWI comment is a link to a Help topic describing how to fix the problem. (In this case, you need to reimplement drag-and-drop capability using the new Windows Forms objects.) We’ve removed the hyperlinks from most of the other examples in this book to save space.

Upgrade ToDos

Upgrade ToDos let you know when code has been partially upgraded and needs finishing before it will run. This type of issue commonly arises when you declare a variable of a type that contains a fixed-size array. For example, the following code defines a type called myType that contains a fixed-size array. It then creates an instance of myType called myVariable.

Type myType
   myArray(10) As String
End Type
Sub Main()
   Dim myVariable As myType
End Sub

In Visual Basic 6, this code works without a problem. In Visual Basic .NET, the Type keyword is renamed Structure; arrays defined with the Structure keyword aren’t initialized—you have to initialize them yourself. When upgrading this example, the wizard does what it can: It changes the Type keyword to Structure and upgrades the variables within the structure to Visual Basic .NET. It creates a Sub to initialize the arrays. In Visual Basic .NET, structures can now have methods, so the Upgrade Wizard also creates an Initialize method that initializes the arrays for you. The hard work is done. The only step remaining is to write code that calls the Initialize method. This is where you come in. The Upgrade Wizard inserts a ToDo EWI that tells you what you need to do. The upgraded code looks like this:

Structure myType
   Dim myArray() As String
   'UPGRADE_TODO: "Initialize" must be called to initialize instances
   'of this structure.
   Public Sub Initialize()
      ReDim myArray(10)
   End Sub
End Structure
Public Sub Main()
   Dim myVariable As myType
End Sub

To make the code work, you need to add a line in Sub Main to call the Initialize method. The following code shows the modified Sub Main:

Public Sub Main()
   Dim myVariable As myType
   myVariable.Initialize()
End Sub

After you make the modification, the code runs as it did in Visual Basic 6. Like upgrade issues, code marked with ToDo comments must be fixed before the program will run.

Run-Time Warnings

Run-time warnings alert you to behavior differences between Visual Basic 6 and Visual Basic .NET. A behavior difference is code that doesn’t cause a compile error but might act differently at run time. For example, the Dir function is used to return the list of files or directories inside a particular directory. In previous versions of Visual Basic, Dir also returned . and .. (indicating current directory and parent directory). In Visual Basic .NET, the Dir function doesn’t return the . and .. directories. When the following code is upgraded:

Dim myFilename As String
myFilename = Dir("C:\Temp\*.*", vbDirectory)

the Dir function is marked with an upgrade warning:

Dim myFilename As String
'UPGRADE_WARNING: Dir has a new behavior.
myFilename = Dir("C:\Temp\*.*", FileAttribute.Directory)

Some programs will work perfectly; some will have problems. If you have code that relies on Dir returning . and .., you need to make modifications. As with other EWIs, a link to Visual Basic .NET help explains the difference and tells you what to do next. Upgrade warnings show up in the Task List.

Design Issues

Design issues identify differences in the design of a form. For example, suppose you have a project with a form, and you’ve set the form’s OLEDropMode property to Manual. When you upgrade the project, the OLEDropMode property is not upgraded, since there is no direct equivalent in Windows Forms. What happens to OLEDropMode? It is simply ignored and doesn’t appear in the upgraded form. Because the code has no appropriate place to put this EWI, the Upgrade Wizard puts the following entry in the Form1 section of the upgrade report: “Form property Form1.OLEDropMode was not upgraded.” The EWI is associated with a Help topic that explains how to implement drag-and-drop capability in Windows Forms. Because design issues are recorded only in the upgrade report, they do not appear in the Task List.

Upgrade Notes and Global Warnings

We’ve just looked at the four most common types of EWI. There are two other less common types of EWI, neither of which shows up in the Task List.

  • Upgrade notes are inserted as friendly memos whenever code is substantially changed.

  • Global warnings are inserted into the upgrade report to alert you to major issues, such as differences in data binding.


Understanding the Upgrade Report

The upgrade report summarizes any problems found during the upgrade. It is added to every project during an upgrade, whether or not there were any problems. Suppose you upgrade a project containing a form with a text box that implements drag and drop, and a module with a type that has a fixed-size array in it. The upgrade report might look something like Figure 8-1.

Figure 8-1
An upgrade report.

Let’s look at the different parts of this report:

  • Project name and time of upgrade The first line of the report gives the project name; the second line shows the date and time the project was upgraded.

  • Project items The body of the report contains a table summarizing the project items (forms, classes, modules, and designers) and the issues found for each item during the upgrade. This table has seven columns.

    The first and second columns show the upgraded filename and the original filename, respectively. The third column gives the type of project item—form, class, module, and so on. The fourth column summarizes what happened during upgrade: Upgraded (no issues found), Upgraded with Issues, or Not Upgraded. If the project item was not upgraded, the original Visual Basic 6 file will simply be added to the project as a related document. The fifth and sixth columns show the count of errors and warnings for each project item, and the seventh column adds these two numbers to give the total number of issues.

    The table has a row for each project item, plus an extra row at the top of the table labeled (Global Issues) that notifies you of any project-level issues (such as general differences in data binding). Each row has a plus symbol on the left. If you click the plus symbol beside Form1, you’ll see the individual issues, as shown in Figure 8-2.

    Figure 8-2
    Expanded view of Form1 issues.

    Form1 has one design error. The OLEDragMode property of TextBox1 was not upgraded. In the report, the description of each error has a second purpose. The description is also a hyperlink that navigates to Help for the EWI, just as the EWI comments in code do.

  • Troubleshooting link This hyperlink navigates to general Help on troubleshooting upgrading. The Help topic also links to pages on the Internet offering the latest troubleshooting information.

  • Upgrade settings The final section of the report shows the Upgrade Wizard settings, locations of the Visual Basic 6 and Visual Basic .NET projects, and any options that were set.

Estimating Fix Time

How long will it take to get your upgraded project working in Visual Basic .NET? Unfortunately, this chapter cannot give you a guaranteed length of time that a given project will take. What we can give you is a mechanism for loading the list of issues into Microsoft Excel and the reassurance that after doing a few upgrades, you’ll be faster at solving issues and more accurate at knowing which issues are quick to solve and which take time.

The upgrade report is formatted to be easy to read. For large projects, in which you want to manage the distribution and status of issues within a group of developers, loading the issues into a spreadsheet might make more sense. Doing so allows you to add comments against each issue, such as who the issue is assigned to and its status. Let’s look at how to do this.

The upgrade report is actually based on an underlying XML log file. The Upgrade Wizard creates a log file for every upgraded project. The log—called <projectname>.log—is placed in the same directory as the new Visual Basic .NET project. This log contains all the information in the upgrade report. In fact, the upgrade report is actually generated from the log file. The XML log file is easy for software to read but awkward for humans. Using a simple Excel macro, we can load the list of issues into a spreadsheet. Follow these steps:

  1. Start Excel, and create a new workbook.

  2. Add a new CommandButton to the worksheet. To do this, select View, Toolbars, Control Toolbox. Draw a CommandButton onto the spreadsheet.

  3. Right-click the CommandButton, and choose Properties from the context menu.

  4. In the Properties dialog box, change the name of the CommandButton to LoadUpgradeReport. Change the caption to read Load Upgrade Report.

  5. Double-click the button to open the VBA Macro Editor. Enter the LoadUpgradeReport code.

  6. Choose References from the Tools menu. In the References dialog box, select Microsoft XML, v3.0, and click OK.

  7. Return to the worksheet. Click the Exit Design Mode button, and then click the Load Upgrade Report button. The macro prompts you for the filename of the log file. Enter the path to the log file. The macro populates the worksheet with the contents of the log file, as shown in Figure 8-3.

    Figure 8-3
    Loading the upgrade log into Excel.

Comments in the Excel code also show other columns that you can add to the spreadsheet, such as object type, object name, and the underlying EWI number.


Working with EWIs

Now that we’ve discussed the types of EWI that will be generated during upgrade, let’s look at how to use EWIs to get your project working. At this point, you might have a few questions: How many issues should I expect to see in my upgraded project? How do I know when I’ve made the right fix? How do I keep track of what is left to fix?

The number of issues depends entirely on your programming style and the size of the project items. If you’ve followed all the recommendations in Chapter 4 you might minimize the number of EWIs to fewer than 10, or perhaps 0, per file. As a rule of thumb, forms usually have more issues than classes and modules, since forms have a rich object model and must be changed significantly when upgraded to Windows Forms.

The best idea is to get your project running as quickly as possible, leaving the run-time differences, noncritical work, and improvements until later. Fix the compile errors and ToDo items first. That way you’ll be working with a project that is “runnable” as quickly as possible. You can filter the Task List to show all the EWIs and compile errors or to show only the compile errors. Start by filtering the Task List to show only compile errors. For information on filtering the Task List, see Chapter 6.

A good method for working with EWIs is to fix a problem and then remove the EWI in the code so it stops showing up in the Task List. This ensures that the Task List shows only the issues left to resolve. Some compile errors can also be postponed. Your application might have code that performs a noncritical function, such as drawing a picture onto a PictureBox. You could decide to comment this out and add a remark such as the following:

'TODO: Fix Paint Code later

These ToDo comments will show up in the Task List when you change the filter to show all tasks. You can take care of noncritical functionality later, after you’ve done the important work. The main advantage of getting the project into a runnable state as soon as possible is psychological: it’s a satisfying feeling knowing that your project is basically working, even if you have a few modifications left to make.

How do you know which modification you need to make for each EWI? The Visual Basic .NET Help will assist you with this. In the beginning, the modifications might take quite some time to fix, since you’re learning about upgrading and Visual Basic .NET at the same time. The good news is that after you’ve fixed a problem the first time, you’ll find that fixing the same problem again is much quicker, since you know what you’re doing.

Performing a Two-Pass Upgrade

Open an old Visual Basic 6 project. Which EWIs do you think would be generated if you upgraded it to Visual Basic .NET? If only you had a way to know before upgrading! Perhaps you would change late-binding code or other easy-to-fix problems before upgrading. In these situations a two-pass upgrade is useful. The concept of a two-pass upgrade is simple: upgrade the project twice. The purpose of the first upgrade is simply to generate an upgrade report to identify where the problems are. You discard the first upgraded project after examining the report. Using the upgrade report, you fix some of the issues in Visual Basic 6 before upgrading a second time. The second pass is the real upgrade. When you do the second upgrade, you replace the first upgraded project, and you should have fewer issues to fix than in the first pass. In many cases, the two-pass approach significantly speeds upgrading.


The Different Upgrade EWIs

The Upgrade Wizard can generate 50 different EWIs. Let’s examine each one, grouped by EWI type.

Upgrade Issues

  • <statement> <statementname> is not supported Marks the Calendar statement, Load <formname> statement, GoSub, or On <expr> GoSub. The workaround for Calendar is to create a class that inherits from the System.Globalization.Calendar class. You can replace Load <formname> with code that creates a new instance of the form. GoSub is best fixed by reimplementing the routines as procedures.

  • <functionname> function is not supported Added when the following functions are used: CVErr; IMEStatus; AscB, MidB, ChrB, and the other B functions; VarPtr, ObjPtr, and StrPtr.

  • DoEvents does not return a value Added when code uses the return value of DoEvents. (In Visual Basic 6 it returns the number of open forms.) Although .NET provides no way to get the number of open forms, you can implement your own forms collection to do this. See Chapter 15 for more details.

  • Declaring a parameter As Any is not supported Added when an API has a parameter with a type of As Any. See Chapter 11for tips on fixing API problems.

  • Declaration type not supported: <declaration type> Added to variable declarations not supported in Visual Basic .NET: arrays of fixed-length strings, references to a private enumerator from within a public enumerator, enumerator values assigned to color constants, arrays with more than 32 dimensions, arrays with negative upper bounds, and Font objects declared WithEvents.

  • Constant <constantname> was not upgraded Some constants can’t be upgraded to Visual Basic .NET.

  • Unable to determine which constant to upgrade <constantname> to Added when a constant could upgrade to one of several Visual Basic .NET constants and the wizard can’t determine from the context which constant is appropriate.

  • LSet cannot assign one type to another Added when code uses LSet to assign a user-defined type from one type to another. The solution is to add a custom CopyTo method to the structure that copies the property in the source structure to corresponding properties in the destination structure.

  • COM expression not supported: Module methods of COM objects Added when code uses a module method. This issue is discussed in Chapter 13

  • Objects of type vbDataObject are not supported Added to the VarType function when the Upgrade Wizard detects that you’re testing for VbVarType.vbDataObject (value 13). Objects that don’t support the IDispatch interface can’t be used in Visual Basic .NET. This is a rare issue.

  • <objecttype> object <object> was not upgraded Added to code that uses the Printers collection, Forms collection, or Scale-modeConstants.

  • Property <object>.<property> was not upgraded Added when a property cannot be upgraded automatically. See Appendix A for a complete list of properties and their mappings from Visual Basic 6 to Visual Basic .NET.

  • <objecttype> <object> could not be resolved because it was within the generic namespace <namespace> Added when code uses members of a variable declared as Form or Control. For information on fixing this type of soft-binding issue, see Chapter 10.

  • Unload <object> was not upgraded Added when Unload is used with a soft-bound form or control.

  • A string cannot be used to index the <variablename> control collection In Visual Basic 6, all collections could be indexed by a name or by a numeric key. In Visual Basic .NET, only hash tables and the VB collection can be indexed by a name. Most other collections, such as the Windows Forms controls collection, have only a numeric key index.

  • Event parameter <parameter name> was not upgraded Added when a form has an Unload or QueryUnload event. Windows Forms doesn’t support the Cancel property for Unload or the UnloadMode property for QueryUnload.

  • Form property <formname>ScaleMode is not supported In Visual Basic .NET, you can’t set the scale mode at run time.

  • <objecttype> property <variable>.<property> is not supported at run time Added when code sets a property that is read-only at run time.

  • ListBox│ComboBox property <control>.NewIndex was not upgraded Added when code uses the NewIndex property of a ComboBox or ListBox control. NewIndex is not supported in Windows Forms.

  • <control> was upgraded to a Panel, and cannot be coerced to a PictureBox PictureBoxes are upgraded to Panels if they contain child controls. This EWI is added when the Panel is then passed to a method that accepts a PictureBox parameter.

  • <controltype> Property <control>.<property> does not support custom mouse pointers Added when code sets a custom mouse pointer. Custom mouse pointers are not automatically upgraded to Visual Basic .NET. You can rewrite your custom mouse pointer logic to use cursors. You can then load a cursor from a .cur file or a .resx file and set the cursor for a particular control.

Upgrade ToDos

  • Uncomment and change the following line to return the collection enumerator Added to collection classes. The Upgrade Wizard upgrades most of the collection class code, but you need to finish the upgrade by adding the collection enumerator. In most cases, this means simply uncommenting one line.

  • Code was upgraded to use <function> which may not have the same behavior Added to code that assigns a ByteArray to a string. In Visual Basic .NET, strings with an odd number of bytes will cause an exception because strings are stored in Unicode and therefore need two bytes per character. You should modify your code to ensure that the byte array is of an even length.

  • “Initialize” must be called to initialize instances of this structure As we discussed previously, this EWI is added to types (upgraded to structures) when the type contains a fixed-size array. When declaring a variable of this type, you will need to call the Initialize method to initialize the array.

  • Add a delegate for AddressOf <methodname> Added to code that uses AddressOf. See Chapter 11for more information on using AddressOf in Visual Basic .NET.

  • LoadResStrings method may need to be replaced The Visual Basic 6 AppWizard often generated a LoadResStrings function. This function is not automatically upgraded to Visual Basic .NET. If your code uses the unmodified AppWizard function, you can replace it with the suggested code in Help.

Upgrade Warnings

  • As <variable type> was removed from ReDim <variable> statement Arrays can be redimensioned using the ReDim statement only as the type they were originally defined as.

  • Arrays can’t be declared with New You can’t declare arrays of classes with New. Variable declarations of the form Dim x(10) As New Class1 are not allowed in Visual Basic .NET. The workaround is to initialize the classes when they are declared or as they are needed.

  • Structure <variable> may require marshalling attributes to be passed as an argument in this Declare statement Added to APIs when a structure is being passed. For information on marshalling attributes, see Chapter 11.

  • Arrays in structure <structure variable name> may need to be initialized before they can be used If a COM interface defines a structure with a fixed-size array, the array might need to be initialized before it can be used.

  • Array ‘xxx’ may need to have individual elements initialized If an array of structures has been declared or redimensioned using ReDim, and the structure contains a fixed-length array, you will need to call Initialize for each member of the array.

  • Lower bound of array <variable> was changed from <lowerbound> to 0 Arrays in Visual Basic .NET must have a lower bound of 0. If the array has a lower bound of anything other than zero, it is changed to 0 and the wizard generates this EWI.

  • Can’t resolve the name of control <controlname> When the Upgrade Wizard needs to resolve the name of a control within a control array and it can’t because the index is a variable, this warning is added.

  • ParamArray <parameter> was changed from ByRef to ByVal In Visual Basic .NET, ParamArrays can be passed only by ByVal.

  • Use of Null/IsNull detected Null is not supported in Visual Basic .NET, so it is upgraded to the closest equivalent, System.DBNull.Value. IsNull is changed to IsDBNull. You can’t rely on DBNull having the same behavior as Null in Visual Basic 6. Also, Visual Basic 6 functions that accepted Null as a parameter and could return Null—such as Left and Right—now work only with strings. Null propagation is no longer supported in Visual Basic .NET. If Null is used in arithmetic expression, it will cause a run-time exception.

  • <functionname> has a new behavior Some functions have different behaviors in Visual Basic .NET, and this EWI warns you of the difference. Here are two of the key differences:

    IsObject(<object>) is upgraded to IsReference(<object>) and, unlike its behavior in Visual Basic 6, returns True if the object is empty and True if the variable is a string. The Dir function no longer returns the directory entries . and ..

  • Class instancing was changed from <instancing type> to public Single-use classes are changed to public classes during an upgrade.

  • Sub Main won’t be called at component startup If a DLL has a Sub Main in it, it won’t be called when the first class is created.

  • Application will terminate when Sub Main() finishes In Visual Basic 6, the application finishes when the End statement is called, or when all forms and objects are unloaded or destroyed. In Visual Basic .NET, the application ends when the End statement is called or when the startup object stops running. If your application has Sub Main as its startup object, the application will end when Sub Main ends. Several workarounds exist, including the use of System.Windows.Forms.Application.Run to create forms from within Sub Main. Forms loaded in this manner keep the application running once Sub Main finishes.

  • <object> event <variable>.<event> was not upgraded Some events can’t be upgraded to Visual Basic .NET. These events are changed into procedures and won’t be fired as they were in Visual Basic 6. You have to call them yourself if you want to run the code. This applies to the OLEDrag and OLEDragOver events and to all Font events.

  • Event <object>.<event> may fire when form is initializedSome events will be fired as the component is initialized. For example, if a check box has a default state of being selected, when the form is initialized, the CheckBox.CheckStateChanged event will fire. This leads to subtle differences if your event code refers to other controls that aren’t fully created when this event is fired. You should modify your code to cope with this new behavior. This EWI is added for the following events:

OptionButton.Click | upgraded to CheckChanged
Form.Resize | upgraded to Resize
TextBox.Change | upgraded to TextChanged
ComboBox.Change | upgraded to TextChanged
CheckBox.Click | upgraded to CheckStateChanged
ComboBox.Click | upgraded to SelectedIndexChanged
DriveListBox.Click | upgraded to SelectedIndexChanged
ListBox.Click | upgraded to SelectedIndexChanged

  • Value <value> for <controltype> property <control>.<property> could not be resolved Some properties and property enumerators have changed from Visual Basic 6 to Visual Basic .NET. This EWI is generated if the Upgrade Wizard can’t map a property or property enumerator. It usually occurs because the property is being assigned to a variable or return value of another function that can’t be resolved.

  • Couldn’t resolve default property of object ‘<objectname>’ In some projects, this is the most common EWI you will see. It is added whenever a late-bound variable is set to a value. The Upgrade Wizard can’t determine whether the assignment is to the variable or to the default property of the variable. Because it is late-bound, the wizard knows nothing about the variable and therefore generates this EWI.

  • <function> parameter ‘<parameter>’ is not supported, and was removed The Wait parameter of AppActivate, the HelpFile and Context parameters of MsgBox, and InputBox are not supported in Visual Basic .NET. These parameters are removed during an upgrade.

  • Assignment not supported: KeyAscii to a non-zero value In Visual Basic .NET, the keypress parameter KeyAscii (now KeyPressEventArgs.KeyChar) can’t be changed. If you have code that sets KeyAscii to 0, this is upgraded to KeyPressEventArgs.Handled = True. Code that sets it to any value other than 0 is marked with this warning.

  • Timer property <controlname>.Interval cannot have value of 0Timer.Interval = 0 deactivated a timer control in Visual Basic 6. In Visual Basic .NET, use the Enabled property to enable or disable the timer. Setting Interval to 0 causes an exception.

Design Errors

Design errors apply to forms and controls and are usually inserted only into the upgrade report. We’ve noted the EWIs that can be inserted into code.

  • <controltype> control <controlname> was not upgraded This EWI is added to the report for controls that aren’t supported in Windows Forms. The control is replaced with a red label placeholder. This EWI will occur with the following controls: OLE Container, Non horizontal/vertical lines, DAO Data, RDO Data, UpDown ActiveX, and the Shape control with the shape set to Oval or Circle.

  • <controltype> property <controlname>.<propertyname> was not upgraded Added when a property or method does not map from Visual Basic 6 to Visual Basic .NET. This EWI is also added to code if the property or method is used at run time.

  • <controltype> property <controlname>.<propertyname> has a new behavior Added to the report when a property, method, or event behaves differently in Visual Basic .NET. This issue is added for the following properties, methods, and events:

    • Control.Keypress event

    • ComboBox.Change event

    • Form.Activate event

    • Form.Deactivate event

    • Form.Picture property

    • Form.QueryUnload event

    • Form.Terminate event

    • Form.Unload event

    • Form.ZOrder property

    • HScrollBar.Max property

    • Image.Stretch property

    • ListBox.Columns property

    • ListBox.ItemCheck event

    • OptionButton.Click event

    • PictureBox.AutoSize property

    • Screen.MousePointer property

    • TextBox.TextLength property

    • UserControl.Picture property

    • VScrollBar.Max property

  • PictureBox property <controlname>. Picture will be tiled Added for a PictureBox with child controls. If a PictureBox contains child controls, it is upgraded to a panel because PictureBoxes in Windows Forms are not control containers. The background image of a Panel is always tiled.

  • ListBox│ComboBox property <control>. ItemData was not upgraded Added when ListBoxes and ComboBoxes have ItemData set at design time. The ItemData property is not upgraded. (Note that run-time use of ItemData is upgraded.) The solution is to set the ItemData information for the ListBox or ComboBox at run time.

  • Only TrueType and OpenType fonts are supported in Windows Forms Windows Forms doesn’t support raster fonts. If a control has its Font property set to a raster font, the font is changed to the default Windows Forms font (8-point Microsoft Sans Serif). This issue doesn’t apply to ActiveX controls, which maintain their own fonts.

Global Warnings

  • <objectname> is not a valid Visual Basic 6.0 file and was not upgraded The project item (form, module, or other item) has an invalid file format—perhaps it is corrupt or a Visual Basic 4 file. This file is ignored during the upgrade.

  • Could not load referenced component <reference>. It is recommended you install Visual Basic 6.0, with all referenced components, and ensure the application compiles and runs before upgrading. Added when a reference, such as an ActiveX control or class library, cannot be found. The best way to avoid this issue is to ensure that the project runs on the machine before upgrading. The Upgrade Wizard needs to load all your project’s references to examine the type libraries, create wrappers, and instantiate ActiveX controls. If this problem occurs, the upgrade is canceled.

  • Could not find file: <projectitemtype> <filename>. Please make sure this file is present before upgrading this project. Added when a file can’t be found. This warning can be issued if a file is located on a network share that is not available. If this problem occurs, the upgrade is canceled.

  • <projectitemtype> <filename> is a Visual Basic 5.0 file. For best results, upgrade the <projectitemtype> to Visual Basic 6.0 before upgrading it to Visual Basic. NET. Added to the upgrade report whenever a project with a Visual Basic 5 form, class, and so on is upgraded.

  • The referenced component <reference> is missing a design time license Added when the design-time license for a control is not installed. This warning usually occurs because a control is installed as part of a setup, without its design-time license. The Upgrade Wizard needs the design-time license because it instantiates ActiveX controls during the upgrade so that they can retrieve their properties for a Visual Basic 6 form. The Visual Basic .NET installation includes a registry file with the licenses for all the Windows Common controls. These licenses are stored in a registry file in the Extras directory; they are not installed with Visual Basic .NET.

  • The referenced project <projectname> is either not compiled or out of date. Please re-compile the project. Occurs when a project references another project that hasn’t been compiled. The solution is to compile the referenced project to a DLL. If this problem occurs, the upgrade is canceled.

  • MTS/COM+ objects were not upgraded If a project has references to the Microsoft Transaction Server Type Library or the COM+ Services Type Library, they are removed during upgrade. These references will not work in Visual Basic .NET and should be replaced with .NET COM+ services objects. See Chapter 16 for more details.

  • The following line couldn’t be parsed Added to a code module when a line could not be upgraded because of syntax errors. The line is copied as is into the Visual Basic .NET module and marked with this EWI.

  • <objecttype> <objectname> was not upgraded Applies to ActiveX documents and property pages. These project items can’t be automatically upgraded.

Upgrade Notes

  • There may be differences in databinding behavior Added to the upgrade report whenever an application has ADO data binding. The warning has a hyperlink to a Help topic that gives general advice about fixing issues with ADO data binding.

  • Def <variabletype> statement was removed. Variables were explicitly declared as type <type>. Added to the declaration section of modules that had statements such as DefInt and DefStr. The variables are changed to be explicitly declared. This message is purely informative.

  • <variable> was changed from a Constant to a Variable A constant was changed to a variable because the data type can’t be stored as a constant in Visual Basic .NET. This message is added for constants declared as colors.

  • NewEnum property was commented out When collection classes are upgraded, the Upgrade Wizard comments out the old NewEnum property and inserts a ToDo comment describing how to implement the collection enumerator.

  • Object <variable> may not be destroyed until it is garbage collected Added to code that sets an object to Nothing. Objects set to Nothing aren’t actually destroyed until the .NET Garbage Collector performs its next collection.

  • IsMissing(<variable>) was changed to IsNothing(<variable>) The IsMissing function was replaced with IsNothing.

  • #If #EndIf block was not upgraded because the expression <expr> did not evaluate to True or was not evaluated If your code has conditional compilation statements, code in the False branch will not be upgraded. The Visual Basic 6 code will be copied as is into your upgraded application.

  • Remove the next line of code to stop form from automatically showing Added to multiple document interface (MDI) child forms when the Visual Basic 6 MDIForm has the property AutoShowChildren = True. The Upgrade Wizard inserts the line Me.Show into the child form to give it the same behavior as in Visual Basic 6.

  • The following line was commented to give the same effect as VB6 Code that does nothing in Visual Basic 6—but would have an effect in Visual Basic .NET—is commented out. This applies to code that sets Form.BorderStyle at run time.

  • <control>.<event> was changed from an event to a procedureAdded to these events: HScrollBar.Change, HScrollBar.Scroll, VScrollBar.Change, VScrollBar.Scroll, and MenuItem.Click. The event was changed to a procedure and called from another event. This change is made because Windows Forms combines some events that used to be separate events into a single event.

  • <statement> was upgraded to <statement> Added when a statement is upgraded to a corresponding statement with a different name. This note is added for the Erase statement, which is upgraded to System.Array.Clear.


Which Problems Are Not Detected?

Perhaps you’re asking yourself, “Which errors doesn’t the Upgrade Wizard catch?” The Upgrade Wizard doesn’t warn you about five important errors:

  • The wizard doesn’t warn about the New statement. The following code has a different behavior in Visual Basic 6 than in Visual Basic .NET (see Chapter 1for a full explanation):

    Dim x As New y

    The wizard doesn’t output an EWI for this because in 99.99 percent of the cases you won’t notice the difference. If the wizard did create a warning for every occurrence, some projects would be overcome with EWIs.

  • The wizard doesn’t detect all coercion problems (in which a variable of one type is assigned to another type). Some coercions are allowed in Visual Basic 6 but not in Visual Basic .NET. Generally these errors are easy to fix, since they almost always result in compile errors.

  • It is common to pass variables as parameters to functions. Often these parameters are defined as ByRef, which means that if the function changes the value of the parameter, the underlying variable also changes. For example, in the following code snippet, myVariable is passed to a function as myParameter, and the function changes myParameter to 5, which also changes myVariable to 5, since it is passed as a ByRef parameter.

    Sub Main()
   Dim myVariable As Integer
   myFunction myVariable
   MsgBox myVariable
End Sub

Function myFunction(myParameter As Integer)
   myParameter = 5
End Function

    Visual Basic 6 has one exception to this behavior: if you pass a property as a ByRef parameter, it will not be changed. For example, suppose that our application has a form with a TextBox on it called Text1. The following line of code passes the Text property of Text1 to the function myFunction.

    myFunction Form1.Text1.Text

    In Visual Basic 6, because the Text property is a property, it is not changed when the function explicitly changes the value. In effect, properties are always passed ByVal. Visual Basic .NET does not have this limitation; properties passed as ByRef parameters are treated as ByRef parameters. If the example just given was upgraded to Visual Basic .NET, the Text property would be changed.

    In some cases, this may lead to subtle differences in your project’s run-time behavior. You can force Visual Basic .NET to pass properties as ByVal parameters by adding parentheses around the variable. For example, this code passes the Text property ByRef:

    myFunction(Me.Text1.Text)

    whereas this code passes the Text property ByVal (just as Visual Basic 6 did):

    myFunction((Me.Text1.Text))

  • In Visual Basic 6, you can use ReDim to redimension a variable to have a different number of indexes. For example, the following code is valid in Visual Basic 6 but causes a compile error in Visual Basic .NET:

    Dim x()
ReDim x(5, 5)

    The Upgrade Wizard doesn’t warn you about this, but the compile error makes it easy to detect and easy to fix. In Visual Basic .NET, each time a variable is redimensioned, it must always have the same number of indexes. In Visual Basic .NET, if you change the Dim statement to dimension the variable with two indexes (using a comma), it works perfectly:

    Dim x(,)
ReDim x(5, 5)

  • The wizard warns you about problems it detects with ActiveX controls. However, there are subtle differences between hosting ActiveX controls in Visual Basic 6 and hosting them in Visual Basic .NET. The wizard doesn’t know about all these differences, and therefore it can’t warn you. For a discussion of using ActiveX controls in Visual Basic .NET, see Chapter 9 and Chapter 13.

It’s important to be conscious of these issues. Learn the methods that will allow you to handle each of them just as you handle issues that the Upgrade Wizard detects.


Conclusion

EWIs are a valuable part of your upgrade. They alert you to issues in your code and show you how to fix the problems. In a perfect world we wouldn’t need them—everything would upgrade without problems. The next best thing is to be aware of problems and know how to fix them. Looking over the list, you’ll see that many errors can be avoided entirely by writing your Visual Basic 6 code in a way that prepares it for Visual Basic .NET. Doing a two-pass upgrade is a great way to see what types of EWIs your Visual Basic 6 program will have when it is upgraded to Visual Basic .NET.


Using Visual Basic 6 with Visual Basic .NET: COM Interop

Chapter 9
Using Visual Basic 6 with Visual Basic .NET: COM Interop

This chapter focuses on making your Visual Basic 6 and Visual Basic .NET applications work together. The mechanism that makes interoperability between the two products possible is known as COM interop. We’ll start by looking at the various ways you can create or use existing components that communicate across COM and .NET component boundaries. We’ll also show you how to debug across calls between Visual Basic 6 and Visual Basic .NET authored components. Finally, we’ll discuss the role of binary compatibility in Visual Basic .NET.

If you’ve been creating multitiered applications using Visual Basic 6, your application has likely evolved into a large system spanning multiple components. Let’s say, for example, that you have an application composed of a Visual Basic standard EXE front end containing ActiveX controls talking to a middle-tier Visual Basic DLL. The Visual Basic DLL in turn talks to a back-end SQL Server database. Upgrading such an application to Visual Basic .NET in one shot is nearly impossible. This is where COM interop swoops in to save the day.

COM interop allows you to upgrade one component at a time while keeping the system alive. For example, you can upgrade your Visual Basic 6 middle-tier component to Visual Basic .NET independently of the user interface (UI) component. Once you have tested your new Visual Basic .NET component with your Visual Basic 6 UI client, you can update the client to take advantage of the new Visual Basic .NET server component. At a later date you may decide to upgrade your Visual Basic 6 client components to Visual Basic .NET components. An ActiveX control vendor may offer a .NET upgrade to your favorite ActiveX control, leading you to replace all ActiveX versions of the control in your application with the .NET version. Eventually your entire system evolves to .NET, smoothly and without interruption.

Visual Studio .NET Is Built on COM Interop

You do not need to look far for an example of COM interop at work. If you’re running Visual Studio .NET, COM interop is right under your nose. The Property Browser is written in C#, a language built on the .NET Framework. Most of the designers you will find, such as the Windows Forms designer, are written in a language supported by .NET. All of the wizards are written in either C# or Visual Basic .NET. The Visual Studio .NET environment is a traditional client application written in C++ that interoperates with these other .NET components using COM interop. The Upgrade Wizard relies heavily on COM interop to accomplish its tasks. The wizard is a .NET component that calls out to the upgrade engine, an out-of-process COM EXE server, to upgrade your Visual Basic 6 project. The upgrade engine in turn calls back to the wizard to provide status. As your application is being upgraded, the status text and progress bar updates you see are brought to you by way of COM interop.

We look forward to the day when 100 percent of our Visual Studio .NET components are written in Visual Basic .NET. Until that day, COM interop will be silently at work keeping Visual Studio .NET humming along.

It would be nice to be able to upgrade your entire application to .NET, but in some cases it may not be feasible. For example, what if your application relies on a COM component for which there is no .NET equivalent? A good example is a Visual Basic 6 ActiveX document or DHTML page designer for which there is no equivalent component in .NET. In such cases COM interop can help keep things running without hindering you from moving other parts of your system forward to Visual Basic .NET.

Although we’ve been talking about interoperation among Visual Basic components, the concept of interoperation applies to all COM components. For example, your application may be composed of a Visual Basic front end talking to a C++ authored middle-tier component. As long as the components that make up your application are based on COM, your Visual Basic .NET application can continue to talk to them. Similarly, your Visual Basic 6 application can continue to talk to a .NET component—authored in any language supported by .NET—as if it were a COM component, without any changes in your Visual Basic 6 application.


Where COM Interop Comes into Play

There are three common situations in which you will encounter COM interop: when using ActiveX controls, when calling a COM component from a .NET application or component, and when calling a .NET component from a COM application or component. Let’s take a look at each of these situations.

ActiveX Controls

Visual Basic .NET allows you to place either .NET or ActiveX controls on a Windows form. Chapter 13covers ActiveX control hosting in more detail. Suffice it to say that the way you use ActiveX controls in Visual Basic .NET is nearly identical to the way you use them in Visual Basic 6. You add the control to the Toolbox, place the control on the form, set design-time property values, and then write code behind it. No sweat.

Communication Between a .NET Client and a COM Server Component

The .NET Framework enables .NET clients to communicate with COM components. It accomplishes this by wrapping the COM component in a runtime callable wrapper (RCW) to make it look like a .NET component, as illustrated in Figure 9-1. Exposing a COM component for use with .NET requires no additional work. You simply add the COM component as a reference, and Visual Studio .NET takes care of generating the RCW for you. Later in this chapter, we demonstrate how to call a COM server from a .NET Web application.

Figure 9-1
Runtime callable wrapper that enables a COM component to look like a .NET component.
Communication Between a COM Client and a .NET Server Component

In addition to allowing .NET clients to communicate with COM components, the .NET Framework allows COM applications to communicate with .NET components by making a .NET component look like a COM component. It accomplishes this by creating a wrapper around the .NET component called a COM callable wrapper (CCW), as illustrated in Figure 9-2. To expose a .NET component to COM, you must register your .NET component as being COM callable. Visual Studio .NET provides a way for .NET components to be registered automatically for COM. The next section demonstrates how to upgrade a Visual Basic 6 component to .NET and then expose that .NET component to COM.

Figure 9-2
COM callable wrapper that enables a .NET component to look like a COM component.


Upgrading a Visual Basic 6 Client/Server Application

Suppose you have a Visual Basic 6 ActiveX DLL that acts as a language translator, translating a phrase in one language to another language. Your component is currently limited to rudimentary Spanish. The companion CD includes two Visual Basic 6 sample applications: TranslatorServer and TranslatorClient. In the following section we will demonstrate how to call the TranslatorServer component from a Visual Basic .NET client application. First, however, you need to build the Visual Basic 6 server component by following these steps:

  1. Run Visual Basic 6.

  2. Open TranslatorServer.Vbp provided on the companion CD.

  3. Open TranslatorServer.Cls, and you will find that it contains the following code:

    Public Function Translate(ByVal SentenceFrom As String, _
   ByVal LanguageFrom As String, _
   ByVal LanguageTo As String) As String

   ' Note to self: Find someone who speaks Spanish and VB who
   ' is willing to expand this component to translate any    ' common English phrase
   If LCase(LanguageFrom) = "english" And _
      LCase(LanguageTo) = "spanish" Then
      Select Case LCase(SentenceFrom)
         Case "hello world"
            Translate = "hola mundo"
      End Select
   End If

End Function

  4. From the File menu, choose Make TranslatorServer.dll and make the .DLL file into a directory on your hard drive.

To test your application in Visual Basic 6, run Visual Basic 6 and open the Visual Basic 6 TranslatorClient application provided on the companion CD. Open Module1.Bas and you’ll find the following code to call the TranslatorServer component.

Sub Main()

   Dim Translation As String
   Dim Ts As New TranslatorServer.Translator
   Translation = Ts.Translate("hello world", "English", "Spanish")
   MsgBox Translation

End Sub

Now select References from the Project menu, click the Browse button, and locate the TranslateServer.Dll that you built by following the previous steps. Press F5 to run the project. You should see "hola mundo" displayed, as shown in Figure 9-3.

  1. Figure 9-3
    “Hola mundo” translation successfully received from Visual Basic 6 server.

You may be wondering what the point of all this is. So far, all we’ve done is call a Visual Basic 6 component from Visual Basic 6. Now, however, we’ll upgrade each component separately to see how to call a Visual Basic 6 component from Visual Basic .NET and vice versa. Finally, we’ll see how to tie the upgraded client and server components together to form a complete Visual Basic .NET solution.

Creating a .NET Client That Talks to a COM Server

Let’s create a .NET client that talks to a COM server. Since the whole point of Visual Studio .NET is to help you create applications quickly for the Web, let’s create a Web client that calls our COM server. You might want to do this, for example, if you have business logic stored internally—say, a Visual Basic 6 ActiveX DLL function that returns a list of your company’s products—that you want to make available for viewing by external partners or customers.

  1. Run Microsoft Visual Studio .NET.

  2. Choose New Project from the File menu.

  3. Select Visual Basic ASP.NET Web Application, name the application MyAmazingTranslator, and click OK.

  4. Right-click References on the Solution Explorer tab, and choose Add Reference.

  5. Select the COM tab.

  6. Select TranslatorServer from the list, and click OK.

  7. Open WebForm1.aspx.

  8. Drag and drop the following controls from the Toolbox to WebForm1: A Label, a TextBox, and a Button.

  9. For the text of the label, enter My amazing English-to-Spanish translator.

  10. For the text of Button1, enter Translate.

  11. Double-click the Translate button and insert the following code in the Button1_Click event handler:

    Dim ts As New TranslatorServer.Translator()
TextBox1.Text = ts.Translate(TextBox1.Text, "English", _
   "Spanish")

  12. Choose Start from the Debug menu.

  13. Type hello world into the text box, and click the button. See Figure 9-4 for an example of the output.

You have now taken a simple desktop application and made it available to the world. Hola mundo indeed.

Figure 9-4
My amazing English-to-Spanish translator at work on the Web.
Debugging Between the Visual Basic .NET Client and Visual Basic 6 Server

When upgrading Visual Basic 6 code to Visual Basic .NET, it is critical that you be able to debug the changes that you made yourself or that were made by the Upgrade Wizard. The Microsoft Visual Studio .NET development environment makes it possible to debug between Visual Basic 6 applications and Visual Basic .NET. However, you need to make a few changes to your Visual Basic 6 code in order to debug it using the Visual Studio .NET debugger.

Preparing Your Visual Basic 6 Project to Debug Using Visual Studio .NET

To be able to set breakpoints in your Visual Basic 6 source code, you need to build your Visual Basic 6 project with debugging symbols turned on. To do so, perform the following steps:

  1. Run Visual Basic 6 and load the application you want to debug. In this case, load TranslatorServer.vbp.

  2. From the Project menu, choose TranslatorServer Properties.

  3. Click the Compile tab and select Create Symbolic Debug Info.

  4. For best results, select No Optimization. See Figure 9-5.

    Figure 9-5
    Recommended Visual Basic 6 options for debugging.

    At this point it’s a good idea to turn on binary compatibility so that you do not need to update your .NET client application.

  5. Click the Component tab.

  6. Change the Version Compatibility setting from Project Compatibility to Binary Compatibility, as shown in Figure 9-6.

  7. Click OK to close the Project Properties dialog box.

Now rebuild your application. Choose Make TranslatorServer.dll from the File menu, click OK, and click Yes to replace the existing file, if prompted to do so.

Figure 9-6
Setting binary compatibility in Visual Basic 6.

Now you’re ready to debug.

  1. Run Visual Studio .NET and open the MyAmazingTranslator Web project you created earlier.

  2. Select the MyAmazingTranslator project in Solution Explorer.

  3. From the Project menu, choose Properties.

  4. Expand the Configuration Properties folder and select Debugging.

  5. Under Debuggers, click Unmanaged Code Debugging.

  6. Click OK to close the dialog box.

  7. Open WebForm1.aspx, and double-click the Translate button.

  8. Insert a breakpoint on the following line:

    TextBox1.Text = ts.Translate(TextBox1.Text, "English", _
   "Spanish")

  9. From the File menu, choose Open File, and open TranslatorServer.cls, saved as part of the Visual Basic 6 TranslatorServer project.

  10. Right-click the first line of code in the Translate function, and select Insert Breakpoint.

  11. Choose Start from the Debug menu.

  12. Click the Translate button. Execution should break on the following line:

    TextBox1.Text = ts.Translate(TextBox1.Text, "English", _
   "Spanish")

  13. Step over the line, and execution should break in your Visual Basic 6 TranslatorServer.cls code file. You can now step through your Visual Basic 6 code to ensure that everything is working properly.

    note

    In order to debug an ASP.NET application on your local machine, you need to be added as a member of the Debugger Users group. If you do not have administrative privileges on the machine, you also need to change the Machine.config file for Aspnet _wp.exe to run Aspnet_wp.exe with User privileges rather than System account privileges. See Visual Studio .NET’s Help system for more details.
Exposing a Visual Basic .NET Component to Be Called by a Visual Basic 6 Client

In some cases, you will want to upgrade your Visual Basic 6 server and make it available to your Visual Basic 6 or other COM client applications. For example, since Visual Basic .NET allows you to create multithreaded components, you may want to upgrade your Visual Basic 6 component to a .NET component. You can then register the component to take advantage of a multithreaded environment such as Microsoft Transaction Server (MTS) in order to use object pooling, for example.

Let’s take the Visual Basic 6 TranslatorServer component located on the companion CD and upgrade it to Visual Basic .NET by performing the following steps:

  1. Run Visual Studio .NET.

  2. From the File menu, choose Open Project and open TranslatorServer.vbp.

  3. Step through the Upgrade Wizard by clicking Next and selecting the default options as you go.

    Let’s change the name of the .NET server so that it doesn’t conflict with the server name of its COM predecessor.

  4. Select the TranslatorServer project in the Solution Explorer.

  5. From the Project menu, choose Properties.

  6. Change the Assembly Name from TranslatorServer to TranslatorServer.net.

  7. Click OK to close the Project Properties dialog box.

  8. View the code for TranslatorServer.vb:

    Option Strict Off
Option Explicit On
Public Class Translator

Public Function Translate(ByVal SentenceFrom As String, _
   ByVal LanguageFrom As String, ByVal LanguageTo As String) _
   As String

      ' Note to self: Find someone who speaks Spanish and VB
      ' who is willing to expand this component to translate
      ' any common English phrase
      If LCase(LanguageFrom) = "english" And _
         LCase(LanguageTo) = "spanish" Then
         Select Case LCase(SentenceFrom)
            Case "hello world"
               Translate = "hola mundo"
         End Select
      End If

   End Function
End Class

Note that the upgraded code, specifically the contents of the Translate function, is exactly the same as the Visual Basic 6 code. The point is that you can create a Visual Basic .NET server in exactly the same way that you create a Visual Basic 6 ActiveX DLL server. Now for the gotcha. Although you can build the upgraded Visual Basic .NET server, you cannot call it from Visual Basic 6. Why not? Because by default a Visual Basic .NET server is meant to be called by other .NET components, not by a COM component. To call a Visual Basic .NET server from a COM application such as Visual Basic 6, you need to register the server for COM. The simple way to expose the component to COM is to turn on the Register For COM Interop attribute by doing the following:

  1. Select the TranslatorServer project in the Solution Explorer.

  2. From the Project menu, choose Properties.

  3. Under the Configuration Properties folder, select Build.

  4. Select the Register For COM Interop check box, as shown in Figure 9-7, and click OK to close the dialog box.

  5. From the Build menu, choose Build Solution, and click the Save button to save the Solution file. This step creates the Visual Basic .NET DLL and also registers it for COM.

    Figure 9-7
    Registering the component for COM interop.
note | Registering a .NET component for COM interop does not necessarily mean that only COM clients can use the component. Instead, it opens that component up to the world of both COM and .NET. Exposing your .NET server to COM allows you to upgrade old clients or to create new clients in .NET that use the COM server. At the same time you can make a quick modification to other COM clients to allow them to work with your .NET server.

Now you are ready to call the Visual Basic .NET TranslatorServer component from Visual Basic 6. This process involves the same steps used to create the Visual Basic 6 TranslatorClient application located on the companion CD. To call the Visual Basic .NET TranslatorServer component from Visual Basic .NET, follow these steps:

  1. Run Visual Basic 6 and open the TranslatorClient application located on the companion CD.

  2. Choose References from the Project menu.

  3. Deselect TranslatorServer, the Visual Basic 6 server component.

  4. Select TranslatorServer_net, the Visual Basic .NET server component.

  5. Open TranslatorClient.bas.

  6. Change the following declaration from

    Dim Ts As New TranslatorServer.Translator

    to

    Dim Ts As New TranslatorServer_net.Translator

  7. Run the application.

You should see the results shown in Figure 9-3.

note | If you want to add features to your upgraded Visual Basic .NET component, you should add COM attributes to ensure binary compatibility. Binary compatibility causes your Visual Basic .NET component to look, act, and smell like existing COM clients no matter what new features you add to the component. If you break compatibility, your COM clients will not be able to find the .NET component. You will be forced to recompile your COM client against the updated .NET server and redistribute it. For an example of how to add COM attributes to ensure binary compatibility, see “Replacing COM with .NET: Binary Compatibility” later in this chapter.
Debugging Between the Visual Basic 6 Client and .NET Server

Earlier we showed you how to debug both Visual Basic 6 and Visual Basic .NET code using the Microsoft Visual Studio .NET debugger. Now we’re going to debug our Visual Basic 6 client application and .NET server, using both the Visual Basic 6 debugger and the Visual Studio .NET debugger to step across the call from Visual Basic 6 code to Visual Basic .NET code. Let’s start in the Visual Studio .NET development environment.

  1. Run Visual Studio .NET and open the TranslatorServer application you upgraded to Visual Basic .NET earlier.

  2. Open TranslatorServer.vb and place a breakpoint within the Translate function on the following line:

    If LCase(LanguageFrom) = "english" And _
   LCase(LanguageTo) = "spanish" Then

  3. Select the TranslatorServer project in Solution Explorer.

  4. From the Project menu, choose Properties.

  5. Under the Configuration Properties folder, select Debugging.

  6. Select Start External Program, click the Browse (“…”) button, and search for VB6.exe.

  7. From the Debug menu, choose Start. Visual Basic 6 will launch.

  8. From Visual Basic 6, open TranslatorClient.vbp.

  9. Place a breakpoint on the following line:

    Translation = Ts.Translate("hello world", "English", _
   "Spanish")

  10. Run the Visual Basic 6 client application.

Execution will break in the Visual Basic 6 application. Step over the line of code.

The Visual Studio .NET debugger will appear, and execution will break in the Visual Basic .NET server application. You can step through and debug your Visual Basic .NET server application code. When execution returns from the Visual Basic .NET server, function execution resumes in the Visual Basic 6 debugger. Pretty cool, eh?

Tying It All Together

As you have seen, COM interop enables you to upgrade your application one piece at a time. You can choose to upgrade one part to .NET while keeping other parts based on COM. If your goal is to move your application to the Web or an intranet environment, plenty of options are available to you. For example, you can create a Web front end that uses the same back-end logic currently in use by your traditional Windows client applications. You can also make your back-end functions—your business logic—available to remote clients on the Internet. To do so, you can expose your functions as Web services. In the case of the Translator class, you can expose the Translate function over the Web by adding a Web service class to the project, copying the contents of the Translator class to the Web service class, and marking the Translate function as a WebMethod. We leave this task as an exercise for you to complete.


Replacing COM with .NET: Binary Compatibility

In creating Visual Basic versions 4, 5, and 6, the Microsoft Visual Basic development team worked hard to allow you to create Visual Basic COM components that are backward compatible. This feature was enabled by an innocent-looking check box on the Component tab of the Project Properties dialog box, shown earlier in Figure 9-6. Binary compatibility in Visual Basic 6 enabled you to create version 2 of a COM component that is a compatible replacement for version 1 of that component. What this means is that the version 2 component contains exactly the same public objects, properties, methods, and events as the version 1 component. In addition, it means that the properties, methods, and events appear in exactly the same order as in the version 1 component. The version 2 component also understands how to initialize itself using property settings saved by the version 1 component.

Although the version 2 component needs to look, act, and smell like a version 1 component, the version 2 component can include additional objects, properties, and methods that improve upon the version 1 component. No matter the improvements, however, in order to keep clients of the version 1 server component working with the version 2 component, the version 2 component must expose some subset of itself as a version 1 component. If the version 2 component doesn’t expose itself as a version 1 component, you need to do one of the following:

  1. Invest in fixing the version 2 component to behave identically to the version 1 component and then invest further in testing the component to ensure that no existing version 1 clients are broken by the changes.

  2. Rename the version 2 component and change its attributes in such a way that it exposes itself as a completely new component. Clients that are using the version 1 component continue to use that component and are not disturbed by the distribution of the version 2 component.

  3. Recompile all clients that use the version 1 component to use the version 2 component and then distribute the client and a version 2 server component as a matched pair.

Which option makes more sense? Option B is generally the recommended approach if you have widely distributed clients that rely on version 1 of the server component. If there is any doubt in your mind about whether you can update an existing component or create a new component as a direct replacement of a version 1 component, don’t do it. Instead, go with option B. It’s the safe bet.

When it comes to creating Visual Basic .NET components that are direct replacements of your Visual Basic 6 components, Visual Basic .NET adopts the option B approach. Simply put, you can’t do it, although if the truth be told, it’s possible but not easy. You’ll sleep much better if you simply assume that you can’t directly replace a Visual Basic 6 component with a Visual Basic .NET one.

The Visual Basic team deliberately chose not to include features that would make it easy for you to mark a Visual Basic .NET component as being a compatible replacement for a Visual Basic 6 component. We’ll let the following Visual Basic code speak for itself:

Const Old_compiler = "VB6.EXE"
Const New_compiler = "VBC.EXE"
Const Old_runtime = "MSVBVM60.DLL"
Const New_runtime = ".NET Framework and Microsoft.VisualBasic.Dll"

If New_compiler <> Old_compiler And New_runtime <> Old_runtime Then
   MsgBox "Minor behavioral differences that are expensive to " & _
          "find and fix"
End If
Indirect Replacement Model

Visual Basic .NET takes an indirect approach in creating Visual Basic .NET components that are replacements of your Visual Basic 6 COM components. This approach means that you can create a component that looks and smells like a Visual Basic .NET component but doesn’t necessarily act 100 percent like the Visual Basic 6 component it’s “replacing.” Since to take advantage of new version 2 component features you need to update your existing clients, why not live a little and roll those features into a Visual Basic .NET component? Putting the new features in a separate component has the advantage of not disturbing the version 1 component. Clients that are running against the version 1 component continue to run against it, unaffected by the changes in the version 2 component. When you are ready to move your clients to the version 2 component, you can update and redistribute them as needed.

By using the Visual Basic Upgrade Wizard, you can quickly upgrade your Visual Basic 6 components to Visual Basic .NET components. Although the wizard doesn’t give you a full, 100 percent binary-compatible replacement for your Visual Basic 6 component, what you end up with is pretty close. All properties, methods, and events are brought forward, preserving the public interface of your component. Once you have worked out all of the upgrade-related comments and issues in the upgrade report, you can effectively replace your Visual Basic 6 component with the .NET version by making a quick change to the Visual Basic 6 client application so that it uses your .NET component instead of the Visual Basic 6 component. You then need to rebuild and redeploy your Visual Basic 6 application to take advantage of the new Visual Basic .NET server component.

Enabling Binary Compatibility in Visual Basic .NET Classes

If you want to expose your upgraded Visual Basic .NET server component to a COM client and you plan on making changes to the Visual Basic .NET server component over time, we strongly suggest that you do the following to ensure compatibility:

  • Declare all the public class properties and methods in an interface.

  • Declare all the public events in a separate event interface.

  • Add attributes to the class to declare ID attributes for the class, interface, and event interface.

  • Change the class declaration so that it implements both the programmable and event interfaces.

Let’s step through an example that demonstrates how to enable binary compatibility in a Visual Basic .NET class. Doing so will ensure that a COM client that uses the class will continue to work with it even after you have added new functionality to the class.

We’ll start with a Visual Basic .NET class that was upgraded from Visual Basic 6. Assume that the filename for the class is Class1.vb.

Option Strict Off
Option Explicit On

Public Class Translator

   Public Event TranslationError(ByVal ErrorMessage As String)

   Public Function Translate(ByVal SentenceFrom As String, _
                             ByVal LanguageFrom As String, _
                             ByVal LanguageTo As String) As String

      Dim fSuccess As Boolean

      ' Note to self: Find someone who speaks Spanish and VB who
      ' is willing to expand this component to translate any common
      ' English phrase
      If LCase(LanguageFrom) = "english" And _
         LCase(LanguageTo) = "spanish" Then
         Select Case LCase(SentenceFrom)
            Case "hello world"
               Translate = "hola mundo"
               fSuccess = True
         End Select
      End If

      If Not fSuccess Then
         RaiseEvent TranslationError( _
            "Translation not implemented for " & SentenceFrom)
      End If
   End Function

End Class

  1. Choose Add New Item from the Project menu and select COM Class. This gives you a template class for creating binary compatible objects.

  2. Copy and paste the public event TranslationError and the public method Translate to the new COM class. Paste the code after Sub New.

  3. Right-click Class1.vb in the Solution Explorer and delete it. We no longer need it.

  4. View the code for ComClass1 and expand the #Region “COM GUIDs,” since you will need to use these values later.

  5. Change the Public Class declaration for ComClass1 to Translator as follows:

    Public Class Translator

  6. Add the following Imports clause to the top of the file as follows. COM attributes such as Guid and ComVisible will come from this namespace.

    Imports System.Runtime.InteropServices

  7. Replace the ComClass attribute in the Translator class with the following attributes:

    <Guid(ClassId), ComVisible(True)> _
Public Class Translator

  8. Define the programmable interface containing the Translate method. Define the interface after the #End Region for “COM GUIDs” as follows:

    <Guid(InterfaceId)> _
Interface ITranslate
   <DispId(1)> _
   Function Translate(ByVal SentenceFrom As String, _
                      ByVal LanguageFrom As String, _
                      ByVal LanguageTo As String) As String
End Interface

  9. Immediately following the programmable interface, insert the declaration for the event interface as follows:

    <Guid(EventsId)> _
Interface ITranslateEvents
   Event TranslationError(ByVal ErrorMessage As String)
End Interface

  10. Add an Implements clause after the Public Class declaration to implement the programmable and event interfaces you just declared as follows:

    Public Class Translator
   Implements ITranslate, ITranslateEvents

  11. Modify the Public Event declaration to implement the interface event as follows:

    Public Event TranslationError(ByVal ErrorMessage As String) _
   Implements ITranslateEvents.TranslationError

  12. Modify the public Translate method to implement the ITranslate.Translate interface method as follows:

    Public Function Translate(ByVal SentenceFrom As String, _
                          ByVal LanguageFrom As String, _
                          ByVal LanguageTo As String) _
                             As String _
                          Implements ITranslate.Translate

When completed, you should have the following class with all the necessary COM attributes defined. You can rebuild the class without breaking compatibility with existing COM clients. The only way you will break compatibility is if you consciously change GUIDs, change a method or event signature, or change the order of events or methods defined in an interface. To maintain compatibility from this point on, you must restrict yourself to adding new methods to the end of an interface. You can never remove or change an existing method, nor can you can change the order of the methods.

Imports System.Runtime.InteropServices

<Guid(ClassId), ComVisible(True)> _
Public Class Translator
   Implements ITranslate, ITranslateEvents

#Region "COM GUIDs"
   ' These  GUIDs provide the COM identity for this class
   ' and its COM interfaces. If you change them, existing
   ' clients will no longer be able to access the class.
   Const ClassId As String = "4B33F612-69C0-4270-A7F3-1B460EBDE978"
   Const InterfaceId As String = _
      "D1C5A219-A6C9-46CA-939F-8CF2D22FE441"
   Const EventsId As String = "86215D08-D4AD-4CA8-9BAC-01E8A592812D"
#End Region

   <Guid(InterfaceId)> _
   Interface ITranslate
      <DispId(1)> _
      Function Translate(ByVal SentenceFrom As String, _
                         ByVal LanguageFrom As String, _
                         ByVal LanguageTo As String) As String
   End Interface

   <Guid(EventsId)> _
   Interface ITranslateEvents
      Event TranslationError(ByVal ErrorMessage As String)
   End Interface

   ' A creatable COM class must have a Public Sub New()
   ' with no parameters; otherwise, the class will not be
   ' registered in the COM registry and cannot be created
   ' via CreateObject.
   Public Sub New()
      MyBase.New()
   End Sub

   Public Event TranslationError(ByVal ErrorMessage As String) _
      Implements ITranslateEvents.TranslationError

   Public Function Translate(ByVal SentenceFrom As String, _
                             ByVal LanguageFrom As String, _
                             ByVal LanguageTo As String) As String _
                             Implements ITranslate.Translate

      Dim fSuccess As Boolean

      ' Note to self: Find someone who speaks Spanish and VB who
      ' is willing to expand this component to translate any common
      ' English phrase
      If LCase(LanguageFrom) = "english" And _
         LCase(LanguageTo) = "spanish" Then
         Select Case LCase(SentenceFrom)
            Case "hello world"
               Translate = "hola mundo"
               fSuccess = True
         End Select
      End If

      If Not fSuccess Then
         RaiseEvent TranslationError( _
            "Translation not implemented for " & SentenceFrom)
      End If

   End Function

End Class
note | In order to control the binary compatibility of components, many of you asked for a Visual Basic feature to expose COM attributes such as Guid and method IDs in a Visual Basic class. Visual Basic 6 offers a simple way to enable binary compatibility—the Binary Compatibility option—but does not allow you to define or edit the COM attributes that it automatically generates for you. Visual Basic .NET does not offer any simple solution to binary compatibility; there is no binary compatibility option that you can turn on. Instead, you must write code manually defining the interfaces and COM attributes to enforce binary compatibility. The ability to specify COM attributes comes at a cost: more code. After you have “beautified” your code by manually adding these attributes, you may ask what you have gotten yourself into. Be careful what you ask for—particularly if you’ve requested more control in specifying COM attributes; you just might get it.


Conclusion

You can mix and match COM with .NET in any number of ways, depending on the needs of your business. There are economic and practical reasons for choosing to have one part of your application based on .NET and the other part based on COM. The beauty of COM interop is that it gives you a choice. When upgrading your applications, you don’t have to standardize on .NET immediately to take advantage of the new features. You can start using those new features now and continue to use your existing Visual Basic 6 code and components as needed to accomplish your goals more quickly and effectively.


Getting Your Project Working

Part III
Getting Your Project Working
Chapter 10
Ten Common Upgrade Problems

Chapter 4 looked at common issues with Microsoft Visual Basic 6 code and discussed how you could resolve them before upgrading. This chapter also focuses on resolving common upgrade issues—but this time we look at the issues found after the wizard has finished upgrading your project. To this end, we describe ten very common upgrade problems and how to fix them. Some of these problems are due to language differences and new control behaviors. Others are related to the different run-time behavior of the .NET Framework. You’ll learn how to avoid these issues in the first place and how to resolve them if you encounter them after you begin upgrading. You’ll also find sample applications on the companion CD that illustrate some of the situations discussed here.


Default Properties

Default properties are no longer supported in the common language runtime. If the Upgrade Wizard can determine a default property, it will properly modify your code to state the property explicitly in Visual Basic .NET. If it cannot determine the property being referenced (in the case of late binding), the wizard will leave your code as is and insert a warning highlighting the issue for you to resolve. In those cases, the offending line of code will be preserved through the upgrade and tagged to bring it to your attention. While it is possible (and often more desirable) to go back to your original code and modify it, there are definitely cases in which it is not possible to make those changes anywhere but in the upgraded application.

The following code samples demonstrate the differences in upgrade results when default properties are accessed using both late-bound and early-bound objects. The code is contained in the DefaultProperties sample included on the companion CD.

Private Sub CopyButton_Click()
   EarlyBoundCopy Text1, Text2
   LateBoundCopy Text1, Text3
End Sub

' This method's parameters are explicitly defined as TextBoxes
Private Sub EarlyBoundCopy(sourceCtrl As TextBox, destCtrl As TextBox)
   destCtrl.Text = sourceCtrl
End Sub

' This method's parameters are variants (the default type)
Private Sub LateBoundCopy(sourceCtrl, destCtrl)
   destCtrl.Text = sourceCtrl
End Sub

Although this code runs just fine in Visual Basic 6, the Upgrade Wizard will insert two run-time warnings in your code. This type of warning means that the code will compile after upgrading but will generate an exception at run time. Specifically, the LateBoundCopy method will generate an InvalidCastException. The exception says the following: “Cast from type ‘TextBox’ to type ‘String’ is not valid.”

Here is the code the Upgrade Wizard generates:

Private Sub CopyButton_Click(ByVal eventSender As System.Object, _
   ByVal eventArgs As System.EventArgs) Handles CopyButton.Click
   EarlyBoundCopy(Text1, Text2)
   LateBoundCopy(Text1, Text3)
End Sub

' This method's parameters are explicitly defined as TextBoxes
Private Sub EarlyBoundCopy(ByRef sourceCtrl As  _
   System.Windows.Forms.TextBox, ByRef destCtrl As _
   System.Windows.Forms.TextBox)
   destCtrl.Text = sourceCtrl.Text
End Sub

' This method's parameters are variants (the default type)
Private Sub LateBoundCopy(ByRef sourceCtrl As Object, _
   ByRef destCtrl As Object)
   'UPGRADE_WARNING: Couldn't resolve default property of object
   'destCtrl.Text.
   'UPGRADE_WARNING: Couldn't resolve default property of object
   'sourceCtrl.
   destCtrl.Text = sourceCtrl
End Sub

When you encounter these kinds of problems, you will need to modify your code. Two options are available. First, you can change the definition of the variable to be strongly typed in Visual Basic 6 and run your application through the Upgrade Wizard again. Your other option is to explicitly specify the desired property in Visual Basic .NET. This last option results in the LateBoundCopy method looking like this:

Private Sub LateBoundCopy(ByRef sourceCtrl As Object, _
   ByRef destCtrl As Object)
   destCtrl.Text = sourceCtrl.Text
End Sub
note | The second option involves modifying your code to explicitly use the property you want to read or modify. Notice that this is exactly what the Upgrade Wizard does for you when it knows what type it is dealing with. This example illustrates the advantage of always using early binding. The Upgrade Wizard will know what the default properties are and will take care of the work for you. Using late binding puts you in the position of going through all of the upgrade warnings and manually entering the desired properties.

The fact that you can upgrade an application that contains these kinds of run-time problems should lead you to two conclusions. First, you need to pay close attention to the issues raised by the Upgrade Wizard. Second, you must test your upgraded application thoroughly to make sure that the original functionality has been maintained. The next section examines how using default properties can also lead to additional and more subtle run-time differences.


AddItem and ToString with COM Objects

In Visual Basic 6, the AddItem method is used to add a new entry to a ListBox or ComboBox control. In Visual Basic .NET, the equivalent operation involves calling the Add method of the Items collection for the ComboBox or ListBox Windows Forms controls. This method takes an object as a parameter, instead of a string. Because only strings can be displayed in a ListBox, the Add method implicitly calls the ToString method on the passed object. For variables that have a primitive value, such as a string or a number, the method returns the value as a string. For other objects, however, the ToString method’s behavior varies significantly from class to class.

note | Officially, Visual Basic .NET no longer supports implicit default properties. Unofficially, however, this is not universally true. All classes in the .NET Framework derive from System.Object. System.Object defines several methods, one of the most important of which is the ToString method. In some ways, ToString is emerging as a de facto default property. For instance, ComboBoxes display strings in their lists, yet the ComboBox.Items.Add method accepts an object, not a string. How does the Add method populate the list? It calls the object’s ToString method with the expectation that the ToString method of any object returns the actual information that is desired.

Consider the following example of adding items to a ListBox from a TextBox in Visual Basic 6. This example is contained in the ListBoxExample sample project on the companion CD. It adds the contents of a TextBox control to a ListBox control. The key here is that we are relying on the default method of the TextBox control (Text) being called when the control is passed to the AddItem method.

Option Explicit

' Button Click Event Handler
Private Sub AddButton_Click()
   AddText Text1
End Sub

' Adding the control to the ListBox
Private Sub AddText(text)
   ' We are relying on the default property here
   List1.AddItem text
End Sub

The problem with this code is its use of the default property of the TextBox object when the object is late-bound. When a TextBox is used in this context, Visual Basic assumes that you want the default property specified in the COM object’s type library. In this case, the Text property of the TextBox object is defined as its default property. This means that the following two lines of code are functionally equivalent:

List1.AddItem Text1
List1.AddItem Text1.Text

As we mentioned previously, the use of a Variant for handling the TextBox object in the AddText method prevents the Upgrade Wizard from resolving the default property. The following example shows what the code looks like after upgrading:

' Button Click Event Handler
Private Sub AddButton_Click(ByVal eventSender As System.Object, _
   ByVal eventArgs As System.EventArgs) Handles AddButton.Click
   AddText(Text1)
End Sub

' Adding the control to the ListBox
'UPGRADE_NOTE: Text was upgraded to text_Renamed.
Private Sub AddText(ByRef text_Renamed As Object)
   ' We are relying on the default property here
   'UPGRADE_WARNING: Couldn't resolve default property of
   'object text_Renamed.
   List1.Items.Add(text_Renamed)
End Sub

What happens? The code runs as is and does not generate an exception at run time. However, the TextBox’s ToString method is called. Unfortunately, the TextBox does not return just the displayed text; it also prepends the string with its class path. So, for example, if the TextBox displays the string “Hello World,” the ToString method returns “System.Windows.Forms.TextBox, Text: Hello World.” To fix the problem, you need to modify your code to eliminate the use of default properties. Doing so would change the line in which the items are added to the ListBox to the following:

' This explicitly passes the contents of the Text property to the
' ListBox Add method.
List1.Items.Add(text_Renamed.Text)

It is important to emphasize that if the original sample had not used late binding, no modifications would have been required.


Deterministic Finalization and Garbage Collection

Garbage collection is a new concept to Windows platform development. It is quite a departure from the COM memory-management mechanism of reference counting and offers significant benefits. In COM, objects use reference counting to keep track of the number of active references to determine their lifetime. When an object’s reference count reaches zero, the object is destroyed. Reference counting presents several problems within COM because of issues with potential reference leaks or circular references. However, it is, for the most part, predictable. Reference counting makes it possible to determine when and where an object will be destroyed and, as a result, to manipulate the lifetime of the object in a deterministic way—hence the term deterministic finalization.

Visual Basic 6 made COM programming much simpler because it hid a great deal of the COM implementation details. But you could rely on Visual Basic’s behavior with respect to COM object lifetimes. The following example illustrates how reference counting in Visual Basic 6 can influence an object’s lifetime:

Dim x As Connection, y As Connection
Set x = new Connection 'Create an object and set refcount = 1
Set y = x       'set refcount = 2
Set x = Nothing 'set refcount = 1
Set y = Nothing 'set refcount = 0; and destroy the object

It is not necessary to explicitly dereference your objects in Visual Basic 6. When a variable goes out of scope, Visual Basic 6 automatically dereferences the object for you:

Sub MySub()
   Dim x As Connection, y As Connection
   Set x  = New Connection 'Create an object and set refcount = 1
   Set y = x   'set refcount = 2
End Sub

In Visual Basic .NET, the picture is drastically different. For the most part, it is no longer possible to determine the exact lifetime of a given object because Visual Basic .NET does not use reference counting. Instead, it handles memory management through garbage collection. Putting aside the implementation details, this change produces distinct behavioral differences that will have implications for your applications. First of all, setting a reference to Nothing will not cause that object to be immediately destroyed. Garbage collection guarantees that the object will be destroyed at some point, but there is no guarantee as to when it will be destroyed.

The effects of this change are best illustrated by the following code in Visual Basic .NET:

Dim conn As New ADODB.Connection
conn.Open( ... )
Dim rs = conn.Execute( ... )
conn = Nothing  ' The object is not destroyed here and the
                ' connection is still open
note | You might ask why Microsoft chose to make this change from a deterministic memory-management scheme to a nondeterministic one such as garbage collection. The general consensus was that too much of COM programming was related to memory-management issues. Garbage collection frees the developer from many of these arduous tasks, enabling the developer to focus more on designing program logic then on memory management. It does require a slightly different programming approach when dealing with objects that represent physical system resources. For the most part, though, you don’t really need to worry about it. Garbage collection is quite a positive change and should result in fewer problems with memory leaks in your applications.

In Visual Basic 6, we could rely on setting the conn reference to Nothing to close the connection and destroy the underlying Connection object. In Visual Basic .NET, setting conn to Nothing simply marks the object for collection. The underlying physical connection to the database remains open, consuming both memory and network resources. Thus, it is imperative when working with objects that represent real physical resources (such as files, graphics handles, and database connections) that you explicitly release those resources before you dereference the object (or allow it to go out of scope). A proper way to handle this would be as follows:

Dim conn As New ADODB.Connection
conn.Open( ... )
Dim rs = conn.Execute( ... )
conn.Close()
conn = Nothing

The Close method ensures that the database connection is released immediately. This is by far the best way to handle any physical resource. Whenever you deal with a physical resource, follow this general rule: open the resource as late as possible and release it as soon as possible. This practice will ensure the most efficient use of system resources.

Bringing a Little Determinism to the Party

It is possible to ensure that your objects are destroyed before your application proceeds. To do so, you need to cause a garbage collection to occur and then wait for the collection to complete. You can accomplish these tasks by calling the following two methods.

GC.Collect()
GC.WaitForPendingFinalizers()

It is pretty obvious that the first method causes a collection to occur. You need to realize, however, that the Collect method is an asynchronous call (hooray, free threading!), meaning that your application will not wait for the collection to complete before continuing. That is why it is important to call WaitForPendingFinalizers, which will block your application until the collection has completed.

Now that you’ve seen how forcing garbage collection can be done, here is why you should almost never do it: Garbage collection is, by definition, an expensive operation. For this reason, it takes place relatively infrequently, instead of whenever and wherever an object is dereferenced. If you have code that needs to work no matter what, then by all means take advantage of Collect and WaitForPendingFinalizers. Ultimately, however, this technique is just a quick fix. For the long term, you should probably investigate how to eliminate this dependency.


Generic Objects (Control/Form/Screen)

Generic objects are a form of soft binding (discussed in Chapter 4). A problem arises when you use methods such as ActiveForm or ActiveControl that return object types (Form or Control) that you use as though they were a more specific type. For example, suppose that you have created a form in your application. If you add a TextBox to that form (call it Text1), you have a distinct class derived from the Form object with a property (Text1) that does not exist in the parent Form class.

When you call the method ActiveForm, it returns a Form object that does not have a Text1 property. In Visual Basic 6, you can use that strongly typed object in a late-bound fashion:

Screen.ActiveForm.Text1.Text = "This is a test"

Visual Basic 6 allows you to access the properties and methods of these objects. Visual Basic .NET, on the other hand, has more strict type requirements. While it allows both early and late binding, soft binding is no longer supported. When you upgrade this kind of code, you need to cast the result from ActiveForm to either an object (forcing late binding) or to the specific class that contains the method or property you are attempting to invoke (forcing early binding). The upgraded code can take one of the following forms:

' Forcing late binding by casting to Object
CType(ActiveForm, Object).Text1.Text = "This is a test"

' Forcing early binding by casting to Form1
CType(ActiveForm, Form1).Text1.Text = "This is a test"

Although it is up to you to decide what form you prefer, we recommend that you use early binding whenever possible. There is nothing intrinsically wrong with using late binding, but early binding provides the developer with a level of compile-time validation. Late binding can result in errors that are exposed only at run time. It is also slower than early binding because the run-time environment has to bind the methods and properties to the object at run time (hence the term late binding). In an informal test, early-bound property access was more than five times faster than late-bound access. (See the Visual Basic .NET project LateBinding Performance on the companion CD. As you run it, watch the Output window in the debugger.) Why make life more difficult for yourself when you can have the best of both worlds? Early binding is best, unless there is no feasible alternative.


Dim?As New

Dim…As New

You will encounter upgrade issues with the As New syntax in two types of cases. The first is when you use As New to declare a variable. The second is when you use it to declare an array. The use of As New to declare a variable is still supported in Visual Basic .NET, but it no longer supports implicit declaration. Array declaration with As New is not supported at all. The following code shows how the Upgrade Wizard handles both types of declarations.

Here are the declarations in Visual Basic 6:

Dim x As New Class1
Dim y(5) As New Class2

Here’s how the Upgrade Wizard handles the declarations:

Dim x As New Class1()
'UPGRADE_WARNING: Arrays can't be declared with New.
Dim y(5) As New Class2()

Notice that the Upgrade Wizard leaves the array declaration alone. When you first upgrade the project, there will be a compiler error on this line because As New is no longer supported for arrays. The best way to deal with this situation is fairly simple: initialize your array in a loop.

Dim y(5) As Class2
For i = 0 To 5
   y(i) = New Class2()
Next

This solution does not address the other side of the As New behavior: implicit instantiation. For example, the following code will fail in Visual Basic .NET with a NullReferenceException at run time:

Dim x As New Object1()
x.AnyMethod()
x = Nothing
x.AnyMethod() ' This will cause a NullReferenceException

The Upgrade Wizard will help you isolate these problems. First it will insert a warning wherever you have declared an array with As New. It will also generate another warning wherever you set an object to Nothing. You can use these warnings to track down spots in your code where you might run into difficulties. The only way to resolve these problems is to explicitly create a new instance of Object1 or to test for Nothing whenever you access a variable that might have been set to Nothing.

Dim x As New Object1()
x.AnyMethod()
x = Nothing
x = New Object1()
x.AnyMethod()

As New is an example of a language feature that differs significantly in functionality from Visual Basic 6 to Visual Basic .NET. It can present a host of issues in an application that relies too closely on its previous behavior. Watch yourself.


Sub Main (or Default Form)

In Visual Basic .NET, your application’s lifetime is now defined differently than it was in Visual Basic 6. When the startup object finishes, the application ends. In Visual Basic 6, an application remained active as long as there were dialog boxes or windows open. This represents a change in how your application’s life cycle is determined. For the most part, however, you probably will not have to worry about this change, unless your application closes its main form but expects to continue running. The application model assumes the use of a main form that runs on the main thread. When that form terminates, your application terminates by default.

There are two main startup scenarios for Visual Basic applications. The first is when the startup object is a form. The second is when the startup object is a Sub Main method. Sub Main is typically used in two situations. You may implement a Sub Main method if you need to customize the loading process or if you used the Application Wizard to create your application.

note | The Upgrade Wizard attempts to treat Application Wizard projects as a special case, but if the Sub Main method is too heavily modified, it will not be able to do much for you. See Chapter 17for more on upgrading VB Application Wizard projects.

As a general rule, when you are opening a form from Sub Main, use the System.Windows.Forms.Application.Run method to start your application. This method begins running a standard Windows application messaging loop on the current thread and makes the specified form visible. If you’re creating another form that may replace the original form, you should create it on a separate thread to ensure that your application does not terminate prematurely. Otherwise, when the specified form is closed, the application will terminate immediately. The following example demonstrates the use of the Application.Run method in a fictional Sub Main.

' This is how to start your application from Sub Main
System.Windows.Forms.Application.Run(New Form1())


Font Disparities

In Visual Basic 6, it is possible to use two different types of fonts: raster and TrueType. Although TrueType fonts will upgrade to Visual Basic .NET, raster fonts are no longer supported. This change can present a problem for your application. What happens to your carefully laid-out forms? Table 10-1 shows how the Upgrade Wizard translates various Visual Basic 6 fonts during the upgrade process.

Table 10-1 Visual Basic Fonts and How They Are Upgraded
Font Name | Type | Converted Font | Style Preserved?
Arial | TrueType | Arial | Yes
Comic Sans MS | TrueType | Comic Sans MS | Yes
Courier | Raster | Microsoft Sans Serif | No: 8pt Plain
Courier New | TrueType | Courier New | Yes
FixedSys | Raster | Microsoft Sans Serif | No: 8pt Plain
Microsoft Sans Serif | TrueType | Microsoft Sans Serif | Yes
Modern | Raster | Microsoft Sans Serif | No: 8pt Plain
MS Sans Serif | Raster | Microsoft Sans Serif | Yes
MS Serif | Raster | Microsoft Sans Serif | Yes
Roman | Raster | Microsoft Sans Serif | No: 8pt Plain
Script | Raster | Microsoft Sans Serif | No: 8pt Plain
Small Fonts | Raster | Microsoft Sans Serif | No: 8pt Plain
System | Raster | Microsoft Sans Serif | No: 8pt Plain
Terminal | Raster | Microsoft Sans Serif | No: 8pt Plain

If you used the default font MS Sans Serif in a Visual Basic 6 form, your formatting will be preserved in Visual Basic .NET but the font will be changed to the Microsoft Sans Serif TrueType font. You may encounter a problem with layout disparities owing to the slight differences between the two fonts. Controls that are laid out with very tight constraints on your forms might exhibit a wrapping effect.

Non-TrueType fonts will lose their formatting when upgraded. The best way to ensure that your application preserves its look is to modify your Visual Basic 6 application to use TrueType fonts, thus ensuring that your upgraded forms will require minimal control repositioning. Figures 10-1 and 10-2 demonstrate what happens when raster fonts are upgraded. Figure 10-1 shows a sample application (FontTest, included on the companion CD) prepared in Visual Basic 6. Notice how both raster and TrueType fonts are represented.

Figure 10-1
Raster and TrueType fonts in Visual Basic 6.

As Figure 10-2 shows, the fonts can change dramatically after the application is upgraded to Visual Basic .NET with the Upgrade Wizard. This example demonstrates some of the challenges you will face in your form layouts.

Figure 10-2
Raster and TrueType fonts after upgrading.

Notice that all the raster fonts, with the exception of MS Sans Serif, lost their formatting and ended up in plain 8-point Arial. As you can see, you have no choice but to fix your fonts manually. Stick with MS Sans Serif, and you should have few problems. (With luck, only spot fixes will be required.) The other choice is to use TrueType fonts, which will always maintain formatting, although they too may require some tweaking.


Bad Constants

In Visual Basic 6, you can create a set of named constants, known as an enumeration. Each member of the enumeration is assigned an integer value; in code, the member name evaluates to its assigned integer. Because enumerations in Visual Basic 6 represent integer values instead of distinct types, the process can verify only their underlying values. Unfortunately, this arrangement allows developers to use constants intended for different purposes interchangeably and also to use simple integers (representing the underlying values) in place of the constants. Visual Basic .NET is more strict and requires the correct enumeration constants because enumerations are now actual types, not just collections of friendly names for integers. The most common example of the use of bad constants is in relation to the Screen.MousePointer property. Constants such as vbNormal are frequently used in place of vbDefault (a proper constant for the MousePointer property). It is also fairly common for developers to use the underlying values for constants. The following example from Visual Basic 6 demonstrates three equivalent statements that work just fine:

' Correct MousePointer constant
Screen.MousePointer = vbDefault

' Incorrect constant, same underlying value
Screen.MousePointer = vbNormal

' The same underlying value
Screen.MousePointer = 0

When you upgrade your project, however, the Upgrade Wizard will not be able to upgrade the vbNormal constant because it is not defined for the MousePointer and you will be left with an upgrade warning and a compile error. On the other hand, the Upgrade Wizard can work with an underlying constant, if used, and can translate it to a proper constant in Visual Basic .NET. The following results from the Upgrade Wizard illustrate this scenario:

' Correct MousePointer constant
'UPGRADE_WARNING: VB.Screen property Screen.MousePointer has a
'new behavior.
System.Windows.Forms.Cursor.Current = _
   System.Windows.Forms.Cursors.Default

' Incorrect constant, same underlying value
'UPGRADE_ISSUE: Unable to determine which constant to upgrade
'vbNormal to.
'UPGRADE_ISSUE: VB.Screen property Screen.MousePointer does not
'support custom mousepointers.
'UPGRADE_WARNING: VB.Screen property Screen.MousePointer has a
'new behavior.
System.Windows.Forms.Cursor.Current = vbNormal

' The same underlying value
'UPGRADE_WARNING: VB.Screen property Screen.MousePointer has a
'new behavior.
System.Windows.Forms.Cursor.Current = _
   System.Windows.Forms.Cursors.Default

If you have used an incorrect constant, you will need to fix it on your own. The Upgrade Wizard cannot help you, other than highlighting the problem in the upgrade report. Thankfully, the solution is very simple, thanks to IntelliSense. Simply delete the offending constant, and you get an IntelliSense menu listing the proper constant options. Pick the appropriate constant, and you are on your way.


Drag and Drop

Drag-and-drop capability is an important function of most modern applications. There are a number of important differences between Visual Basic 6 and Visual Basic .NET with respect to drag and drop. To better understand how these changes will affect your application, let’s take a quick overview of this feature.

Drag and Drop in Visual Basic 6

Visual Basic 6 supported two kinds of drag-and-drop operations. Standard drag and drop is intended to support drag and drop between controls within a single form. OLE drag and drop was designed to support drag-and-drop capability between applications and is the focus of this section.

The standard Visual Basic 6 controls have varying degrees of support for OLE drag-and-drop operations. Table 10-2 lists the standard Visual Basic 6 controls and their support for manual and automatic drag-and-drop operations.

Table 10-2 Visual Basic 6 Controls Grouped According to Their Support for OLE Drag and Drop
Controls | OLEDragMode | OLEDropMode
TextBox, PictureBox, Image, RichTextBox, MaskedBox | VbManual, vbAutomatic | vbNone, vbManual, vbAutomatic
ComboBox, ListBox, DirListBox, FileListBox, DBCombo, DBList, TreeView, ListView, ImageCombo, DataList, DataCombo | VbManual, vbAutomatic | vbNone, vbManual
Form, Label, Frame, CommandButton, DriveListBox, Data, MSFlexGrid, SSTab, TabStrip, Toolbar, StatusBar, ProgressBar, Slider, Animation, UpDown, MonthView, DateTimePicker, CoolBar | Not supported | vbNone, vbManual

For the sake of brevity, this section deals exclusively with a manual drag-and-drop application. To better illustrate how the upgrade process affects OLE drag-and-drop operations, we have provided a sample application named OLEDragAndDrop on the companion CD. It permits the user to drag and drop image files to and from the application and implements OLE drag and drop in full manual mode. This sample should give you a better idea of exactly what changes you will need to make in your own application. Figure 10-3 shows an image from the sample OLEDragAndDrop application.

Figure 10-3
OLEDragAndDrop application.

Figure 10-4 outlines the life cycle of an OLE drag-and-drop operation in Visual Basic 6. The next section moves on to how things are done in Visual Basic .NET.

Figure 10-4
Life cycle of a Visual Basic 6 drag-and-drop operation.
Drag and Drop in Visual Basic .NET

In Visual Basic .NET, the drag-and-drop operations have been consolidated into a single framework. Drag and drop between controls is handled in exactly the same manner as drag and drop between applications. This change simplifies the programming burden for new development but requires the rewriting of drag-and-drop code for existing applications. Figure 10-5 outlines the life cycle of a drag-and-drop procedure in Visual Basic .NET. Note that this cycle applies to both control-to-control and application-to-application drag and drop.

Figure 10-5
Life cycle of a Visual Basic .NET drag-and-drop operation.

The OLEDragAndDrop sample application contains the code necessary to implement the drag-and-drop operation in Visual Basic 6. The following is an excerpt from the Form1.frm file in that project:

Private Sub PictureDisplay_MouseDown(Button As Integer, _
   Shift As Integer, X As Single, Y As Single)
   PictureDisplay.OLEDrag
End Sub

Private Sub PictureDisplay_OLEDragDrop(Data As DataObject, _
   Effect As Long, Button As Integer, Shift As Integer, X As Single, _
   Y As Single)
   If Data.GetFormat(vbCFFiles) Then
      Dim i As Integer
      For i = 1 To Data.Files.Count
         Dim file As String
         file = Data.Files(i)

         If Not EntryExists(file) Then
            Select Case UCase(Right(file, 4))
               Case ".tif", ".jpg", ".gif"
                  FileList.AddItem file
                  FileList.ListIndex = FileList.ListCount - 1

                  Set PictureDisplay.Picture = LoadPicture(file)
            End Select
         Else
            SelectListItem file
         End If
      Next
   End If
End Sub

Private Sub PictureDisplay_OLEStartDrag(Data As DataObject, _
   AllowedEffects As Long)
   Data.Files.Clear

   Data.Files.Add FileList
   Data.SetData , vbCFFiles
   AllowedEffects = vbDropEffectCopy
End Sub

The changes necessary to make the drag-and-drop code work in Visual Basic .NET are fairly significant. Due to the changes in the drag-and-drop programming model, the Upgrade Wizard cannot automatically make the modifications for you. It simply leaves the methods alone, leaving it to you to implement the logic in the Visual Basic .NET drag-and-drop event model.

Instead of upgrading the Visual Basic 6 code, we have provided a new Visual Basic .NET implementation of the same features. This helps demonstrate how the drag-and-drop event model has changed and how to properly pass information back and forth. The following code shows how to implement the equivalent drag-and-drop operation from the OLEDragAndDrop sample application in Visual Basic .NET. (This code is also included on the companion CD.)

Private Sub Form1_Load(ByVal sender As Object, _
   ByVal e As System.EventArgs) Handles MyBase.Load
   PictureDisplay.AllowDrop = True
End Sub

Private Sub PictureDisplay_DragDrop(ByVal sender As Object, _
   ByVal e As System.Windows.Forms.DragEventArgs) _
   Handles PictureDisplay.DragDrop
   ' Clear out the existing image and ensure that any resources
   ' are released
   If Not PictureDisplay.Image Is Nothing Then
      PictureDisplay.Image.Dispose()
      PictureDisplay.Image = Nothing
   End If

   Dim files() As String = e.Data.GetData(DataFormats.FileDrop, True)

   Dim i As Integer
   For i = 0 To files.Length - 1
      If Not EntryExists(files(i)) Then
         FileList.SelectedIndex = FileList.Items.Add(files(i))
      Else
         SelectListItem(files(i))
      End If
   Next

   ' Set the picture to the last image added
   PictureDisplay.Image = Image.FromFile(FileList.SelectedItem)
End Sub

Private Sub PictureDisplay_DragEnter(ByVal sender As Object, _
   ByVal e As System.Windows.Forms.DragEventArgs) _
   Handles PictureDisplay.DragEnter
   If e.Data.GetDataPresent(DataFormats.FileDrop) Then
      Dim file() As String = e.Data.GetData(DataFormats.FileDrop, True)

      Select Case UCase(Microsoft.VisualBasic.Right(file(0), 4))
         Case ".tif", ".jpg", ".gif"
            e.Effect = DragDropEffects.Copy
         Case Else
            e.Effect = DragDropEffects.None
      End Select
   Else
      e.Effect = DragDropEffects.None
   End If
End Sub

Private Sub PictureDisplay_MouseDown(ByVal sender As Object, _
   ByVal e As System.Windows.Forms.MouseEventArgs) _
   Handles PictureDisplay.MouseDown
   Dim file(0) As String
   file(0) = FileList.SelectedItem

   Dim d As New DataObject(DataFormats.FileDrop, file)
   PictureDisplay.DoDragDrop(d, DragDropEffects.Copy)
End Sub

Use the life-cycle diagram in Figure 10-5 to help gain an understanding of how to integrate drag and drop into your Visual Basic .NET applications. This example should be a good introduction to the issues you will encounter when dealing with drag and drop.


Collection Classes

Upgrading collections built with the Visual Basic Class Builder utility is fairly straightforward. In the typical case, you will need to uncomment only a single line of code to get the collection to work in Visual Basic .NET. Of course, the ease of the upgrade depends a great deal on what modifications were made to the collection and how closely it resembles the originally generated collection class. In most cases, the Upgrade Wizard will insert a simple ToDo comment that requires a minor modification to get your collection to work as expected. The following example is a simple string collection class implemented in Visual Basic 6 using the Class Wizard Utility:

'Local variable to hold collection
Private mCol As Collection

Public Function Add(Key As String, Value As String) As String
   'Set the properties passed into the method
   mCol.Add Value, Key
   Add = Value
End Function

Public Property Get Item(vntIndexKey As Variant) As String
   'Used when referencing an element in the collection
   'vntIndexKey contains either the Index or Key to the collection,
   'which is why it is declared as a Variant
   'Syntax: Set foo = x.Item(xyz) or Set foo = x.Item(5)
   Set Item = mCol(vntIndexKey)
End Property

Public Property Get Count() As Long
   'Used when retrieving the number of elements in the
   'collection. Syntax: Debug.Print x.Count
   Count = mCol.Count
End Property

Public Sub Remove(vntIndexKey As Variant)
   'Used when removing an element from the collection
   'vntIndexKey contains either the Index or Key, which is why
   'it is declared as a Variant
   'Syntax: x.Remove(xyz)

   mCol.Remove vntIndexKey
End Sub

Public Property Get NewEnum() As IUnknown
   'This property allows you to enumerate
   'this collection with the For...Each syntax
   Set NewEnum = mCol.[_NewEnum]
End Property

Private Sub Class_Initialize()
   'Creates the collection when this class is created
   Set mCol = New Collection
End Sub

Private Sub Class_Terminate()
   'Destroys collection when this class is terminated
   Set mCol = Nothing
End Sub

Essentially, what will happen when you upgrade this code is that the NewEnum property on the collection will be commented out and replaced with a GetEnumerator method. The following example is the complete Visual Basic .NET version of the previous collection class as created by the Upgrade Wizard. Notice both the NewEnum property and the GetEnumerator method.

Friend Class StringCollection
   Implements System.Collections.IEnumerable
   'Local variable to hold collection
   Private mCol As Collection

   Public Function Add(ByRef Key As String, _
                       ByRef Value As String) As String
      'Set the properties passed into the method
      mCol.Add(Value, Key)
      Add = Value
   End Function

   Default Public ReadOnly Property Item(ByVal vntIndexKey As Object) _
      As String
      Get
         'Used when referencing an element in the collection
         'vntIndexKey contains either the Index or Key to the
         'collection,
         'which is why it is declared as a Variant
         'Syntax: Set foo = x.Item(xyz) or Set foo = x.Item(5)
         Item = mCol.Item(vntIndexKey)
      End Get
   End Property

   Public ReadOnly Property Count() As Integer
      Get
         'Used when retrieving the number of elements in the
         'collection. Syntax: Debug.Print x.Count
         Count = mCol.Count()
      End Get
   End Property

   'UPGRADE_NOTE: NewEnum property was commented out.
   'Public ReadOnly Property NewEnum() As stdole.IUnknown
      'Get
         'This property allows you to enumerate
         'this collection with the For...Each syntax
         'NewEnum = mCol._NewEnum
      'End Get
   'End Property

   Public Function GetEnumerator() As System.Collections.IEnumerator _
      Implements System.Collections.IEnumerable.GetEnumerator
      'UPGRADE_TODO: Uncomment and change the following line to return
      'the collection enumerator.
      'GetEnumerator = mCol.GetEnumerator
   End Function

   Public Sub Remove(ByRef vntIndexKey As Object)
      'Used when removing an element from the collection
      'vntIndexKey contains either the Index or Key, which is why
      'it is declared as a Variant
      'Syntax: x.Remove(xyz)

      mCol.Remove(vntIndexKey)
   End Sub

   'UPGRADE_NOTE: Class_Initialize was upgraded to
   'Class_Initialize_Renamed
   Private Sub Class_Initialize_Renamed()
      'Creates the collection when this class is created
      mCol = New Collection
   End Sub

   Public Sub New()
      MyBase.New()
      Class_Initialize_Renamed()
   End Sub

   'UPGRADE_NOTE: Class_Terminate was upgraded to
   'Class_Terminate_Renamed.
   Private Sub Class_Terminate_Renamed()
      'Destroys collection when this class is terminated
      'UPGRADE_NOTE: Object mCol may not be destroyed until it is
      'garbage collected.
      mCol = Nothing
   End Sub

   Protected Overrides Sub Finalize()
      Class_Terminate_Renamed()
      MyBase.Finalize()
   End Sub
End Class

To enable your collection class, you need to uncomment a single line of code in the GetEnumerator method (the Visual Basic .NET equivalent of the NewEnum property) and make any necessary additional modifications. The vast majority of cases will not require additional modifications, but the Upgrade Wizard does not take any chances. By requiring you to uncomment the line in the GetEnumerator method, it forces you to evaluate whether you do in fact need to make any additional changes. Otherwise, just returning the Enumerator from the underlying collection class will be perfectly sufficient. To see this sample in action, check out the Visual Basic .NET version of the CollectionSample project on the companion CD.


Conclusion

This chapter focused on ten common problems involving features within your upgraded application. Hopefully, the discussion has given you a better handle on how to resolve similar issues in your own applications. The samples provided on the companion CD should allow you to see how isolated feature areas can be upgraded. The next chapter covers changes in the Visual Basic language and describes how to fix problems arising from those changes.


Resolving Issues with Language

Chapter 11
Resolving Issues with Language

The Basic programming language has been around for a long time in a variety of forms—GW-BASIC, QuickBasic, Visual Basic, and Visual Basic for Applications (VBA), to name just a few Microsoft PC-based varieties. Each product provides its own brand of the Basic programming language, supporting or not supporting certain types and language constructs. Each product has also made an attempt to clean up or simplify the language. Microsoft Visual Basic .NET offers yet another version of the Basic language, adding its own types and language constructs and omitting others.

In Visual Basic .NET you will not find Type…End Type, GoSub…Return, While…Wend, nonzero-based arrays, the Currency type, static subroutines, or fixed-length strings. You will, however, find new language elements such as Structure…End Structure, Inherits, Overloads, Try…Catch, SyncLock, the Char type, the Decimal type, and assignment shortcuts such as += and -= to increment or decrement a value. New statements such as Inherits, Overloads, Try…Catch, and SyncLock provide support for new features—inheritance, structured exception handling, and multithreading—not found in previous versions. Other changes such as the removal of GoSub…Return, the change from Type…End Type to Structure…End Structure, and the addition of the Char type are intended to modernize the language. Still other changes—removal of support for nonzero-based arrays and fixed-length strings—are the result of trade-offs needed to make Visual Basic .NET work with other .NET languages.

This chapter discusses upgrade issues related to your code. In particular, it focuses on language elements in Visual Basic 6 that the Upgrade Wizard doesn’t automatically upgrade (or upgrades only partially) for you.


Language Elements

Let’s look first at language-related upgrade issues, setting aside for now issues related to data structures and user-defined types. We’ll cover control-of-flow statements such as While…Wend and GoSub…Return, precompiler statements such as #If…#End If, and language statements such as Abs, Open, and Close.

#If…#End If Precompiler Statements

All conditional compilation statements are maintained after an upgrade. All code within all paths of an #If…#End If block will be found in the upgraded project, but only the code in the execution path—the path that evaluates to True  —is upgraded. This happens because the Upgrade Wizard cannot guarantee that code in other execution paths is valid. For example, you can include any text you want in an #If…#End If block that evaluates to False, such as

#Const COMMENT = False

#If COMMENT Then
This is my invalid code. There is no comment. This will only confuse
the Upgrade Wizard if it tries to upgrade me.
#End If

You should make sure that you have turned on all the conditional compilation arguments in your Visual Basic 6 project for the code paths you want to upgrade. Also be sure that your code compiles and runs in Visual Basic 6. The wizard will not be able to upgrade invalid code contained in your project. This includes code contained within an #If…#End If block that evaluates to True.

Constants and Constant Expressions

If you define a constant (in Visual Basic 6) to represent a property on a Windows form or control, you may find that the code does not compile after you upgrade the project. For example, suppose that your Visual Basic 6 project contains the following code:

Const COLOR_RED = vbRed

The Upgrade Wizard will upgrade the code to the following:

'UPGRADE_NOTE: COLOR_RED was changed from a Constant to a Variable.
Dim COLOR_RED As System.Drawing.Color = System.Drawing.Color.Red

Why was the Const declaration changed to a Dim? The reason is that System.Drawing.Color.Red, although it looks like a constant, is not; it is a nonconstant expression. This means that, rather than being a constant value, such as 255 (the value of vbRed), it is a property that returns a System.Drawing.Color object. To see the declaration for System.Drawing.Color.Red within the upgraded Visual Basic .NET project, right-click Red in the Visual Basic .NET code window and choose Go To Definition. The Object Browser will display the definition of System.Drawing.Color.Red, as follows:

Public Shared ReadOnly Property Red As System.Drawing.Color

In some cases, you may encounter a compiler error in your upgraded code. For example, suppose that in Visual Basic 6 you create an enumerator containing values for red, green, and blue called RGBColors. You use the enumerator values in code to combine red and green to create a yellow background for your form, as follows:

Enum RGBColors
   Red = vbRed
   Green = vbGreen
   Blue = vbBlue
End Enum

Private Sub Form_Load()
   BackColor = RGBColors.Red + RGBColors.Green
End Sub

The resulting Visual Basic .NET code after upgrade will be as follows:

'UPGRADE_ISSUE: Declaration type not supported: Enum member
'of type Color.
Enum RGBColors
   Red = System.Drawing.Color.Red
   Green = System.Drawing.Color.Lime
   Blue = System.Drawing.Color.Blue
End Enum

Private Sub Form1_Load(ByVal eventSender As System.Object, _
                       ByVal eventArgs As System.EventArgs) _
                       Handles MyBase.Load
   BackColor = System.Drawing.ColorTranslator.FromOle(RGBColors.Red + _
   RGBColors.Green)
End Sub

Because the declarations for the color objects Red, Lime, and Blue are not constant, each line results in the compiler error “Constant expression is required.”

There are a couple of ways that you can fix up the code to work. You can define your own constant values, or you can use nonconstant values directly, as described in the sections that follow.

Define Your Own Constant Values

The easiest way to fix your code is to use constant values in place of the Red, Lime, and Blue objects in the code just given. For example, you can declare constants for vbRed, vbGreen, and vbBlue so that you can define the enumerator members as these constant types, as was done in the Visual Basic 6 code.

Const vbRed As Integer = &HFF
Const vbGreen As Integer = &HFF00
Const vbBlue As Integer = &HFF0000

Enum RGBColors
   Red = vbRed
   Green = vbGreen
   Blue = vbBlue
End Enum

Private Sub Form1_Load(ByVal eventSender As System.Object, _
                       ByVal eventArgs As System.EventArgs) _
                       Handles MyBase.Load
   BackColor = System.Drawing.ColorTranslator.FromOle(RGBColors.Red + _
   RGBColors.Green)
End Sub
Use Nonconstant Values Directly

Another way to solve the problem is to replace any instances in which a member of RGBColors, such as RGBColors.Red, is used with the matching System.Drawing.Color, such as System.Drawing.Color.Red. When you make this change, a new question arises: How do you manage calculations that involve two objects? For example, in the example above, how do you add the values of two color objects together to arrive at a new color value? You can’t directly add two objects together to get a result. If you can use a method or a function that will give you a meaningful numeric value for the object, you can use the numeric values in your calculation. Using a numeric-to-object conversion function allows you to turn the result of the numeric calculation back into an object representing the calculated value.

In the case of color objects, you can obtain a meaningful numeric (color) value by using the ToOle method of the System.Drawing.ColorTranslator class. The ToOle method takes a color object—such as System.Drawing.Color.Red—and obtains an RGB color value for it. ToOle(System.Drawing.Color.Red), for example, will return the RGB value 255, or FF in hexadecimal. Not by coincidence, the vbRed constant in Visual Basic 6 has the same value. Once you have calculated the new color, you can convert the result back to a color object by using the ColorTranslator.FromOle method. The following is an example of how you can change the original upgraded Visual Basic .NET code to use the System.Drawing.Color values directly:

'Include the Imports statement at the top of the Form file
Imports System.Drawing
…
Private Sub Form1_Load(ByVal sender As System.Object, _
                       ByVal e As System.EventArgs) Handles MyBase.Load
   BackColor = _
      ColorTranslator.FromOle(ColorTransltor.ToOle(Color.Red) _
      + ColorTranslator.ToOle(Color.Lime))

End Sub

It’s up to you whether you want to define your own constants or use the nonconstant values directly in your code. If, in your original code, you are using the constant values in combination with other constant values—say, in a numeric calculation—it would make sense to define your own constant values and use the constants in your code. Otherwise, if you are making a simple assignment of a constant value to a property, it would make more sense to use the nonconstant value—in other words, the object—directly.

Control Flow

If you never knew that Visual Basic supported GoSub…Return, On…GoTo, and On…GoSub, and you don’t use these statements in your Visual Basic 6 code, you’re ahead of the game. Visual Basic .NET drops support for these three statements.

GoSub…Return

GoSub…Return is a holdover from earlier generations of the Basic language that did not support subroutines, Sub and Function. Visual Basic .NET does not support the GoSub statement. It does, however, support the Return statement, but its meaning is different from that in Visual Basic 6. Return is used to return from a subroutine. You can use the Return statement to return a value in a function.

If you are using the GoSub statement in your code, we recommend copying the code associated with the GoSub label to its own subroutine. Any local variables that the GoSub label handler uses should be passed as parameters to the subroutine that you create. The following code uses GoSub in two places within Sub Main to display a summary of the number of times an animal shows up in a sorted list of animals. It is a good example of when you would use GoSub in a subroutine to perform a suboperation that reuses local variable values.

Sub Main()

   Dim Animals(2) As String
   Dim ctAnimal As Long
   Dim PrevAnimal As String
   Dim i As Long

   Animals(0) = "Alligator"
   Animals(1) = "Monkey"
   Animals(2) = "Monkey"

   For i = 0 To UBound(Animals)

      ' Detect break in sequence and show summary info
      If PrevAnimal <> "" And PrevAnimal <> Animals(i) Then
         GoSub ShowDetail
      End If

      ctAnimal = ctAnimal + 1
      PrevAnimal = Animals(i)

   Next

   ' Show summary info for last animal in list
   GoSub ShowDetail

   Exit Sub

ShowDetail:
   MsgBox PrevAnimal & " " & ctAnimal
   ctAnimal = 0
   Return
End Sub

The Upgrade Wizard upgrades the code as is, inserting UPGRADE_ISSUE comments for each occurrence of GoSub and UPGRADE_WARNING comments for each occurrence of Return. The comments tell you that GoSub is not supported and that Return has a new behavior in Visual Basic .NET.

You can replace the GoSub statements with calls to a subroutine called ShowDetail. You need to pass the local variables as parameters to the ShowDetail subroutine. In the previous example, you would pass PrevAnimal and ctAnimal to ShowDetail. You need to pass ctAnimal ByRef so that the value gets reset to 0 when execution returns from ShowDetail. The following code demonstrates how you can use a function—ShowDetail—in place of a GoSub label of the same name (found in the code shown previously):

Public Sub Main()

   Dim Animals(2) As String
   Dim ctAnimal As Integer
   Dim PrevAnimal As String
   Dim i As Integer

   Animals(0) = "Alligator"
   Animals(1) = "Monkey"
   Animals(2) = "Monkey"

   For i = 0 To UBound(Animals)
      ' Detect break in sequence and show summary info
      If PrevAnimal <> "" And PrevAnimal <> Animals(i) Then
         ShowDetail(PrevAnimal, ctAnimal)
      End If

      ctAnimal = ctAnimal + 1
      PrevAnimal = Animals(i)
   Next

   ' Show summary info for last animal in list
   ShowDetail(PrevAnimal, ctAnimal)
End Sub

Sub ShowDetail(ByVal Animal As String, ByRef ctAnimal As Integer)
   MsgBox(Animal & " " & ctAnimal)
   ctAnimal = 0
End Sub
On…GoTo

On…GoTo is an outdated version of Select Case. On…GoTo evaluates a given numeric expression for a value between 1 and 255 and then jumps to the label given by the nth parameter after GoTo.

The following Visual Basic 6 example demonstrates the use of On…GoTo.

   Dim Mode As Integer

   Mode = 2   'WriteMode
   On Mode GoTo ReadMode, WriteMode

   MsgBox "Unexpected mode"
   Exit Sub

ReadMode:
   MsgBox "Read mode"
   Exit Sub

WriteMode:
   MsgBox "Write mode"

The Upgrade Wizard upgrades the code to use Select Case and GoTo as follows:

Dim Mode As Short

Mode = 2 'WriteMode
Select Case Mode
   Case Is < 0
      Error(5)
   Case 1
      GoTo ReadMode
   Case 2
      GoTo WriteMode
End Select

MsgBox("Unexpected mode")
Exit Sub

ReadMode:
MsgBox("Read mode")
Exit Sub

WriteMode:
MsgBox("Write mode")

End Sub

Although the wizard produces correct code, you will probably want to move the code contained under each label to each Case statement. This step will eliminate the need for GoTo and will also condense your Visual Basic .NET code as follows:

Dim Mode As Short

Mode = 2 'WriteMode
Select Case Mode
   Case 1
      MsgBox("Read mode")
   Case 2
      MsgBox("Write mode")
   Case Else
      MsgBox("Unexpected mode")
End Select
On…GoSub

On…GoSub works in much the same way as On…GoTo, except that when you jump to the label, execution can return back to the next statement after the On…GoSub statement when you call Return. The following example, in particular the UpdateAccountBalance subroutine, demonstrates the use of On…GoSub to jump to a transaction—deposit or withdrawal—depending on the value of the passed-in Transaction variable. The Transaction variable can either be 1 for deposit or 2 for withdrawal. If, for example, the value is 1, the On…GoSub statement jumps to the first label specified in the list—in this case Deposit:

Option Explicit
Private m_AccountBalance As Currency

Private Sub Main()

   Const Deposit = 1
   Const Withdrawal = 2

   m_AccountBalance = 500
   UpdateAccountBalance Deposit, 100

End Sub

Private Sub UpdateAccountBalance(ByVal Transaction As Integer, _
                                 ByVal Amount As Currency)

   On Transaction GoSub Deposit, Withdrawal
   MsgBox "New balance: " & m_AccountBalance
   Exit Sub

Deposit:
   m_AccountBalance = m_AccountBalance + Amount
   Return

Withdrawal:
   m_AccountBalance = m_AccountBalance - Amount
   Return

End Sub

As in the On…GoTo example given earlier, you can change your On…GoSub code to use a Select Case statement.

Select Case Transaction
   Case Deposit
      m_AccountBalance = m_AccountBalance + Amount
   Case Withdrawal
      m_AccountBalance = m_AccountBalance - Amount
End Select
MsgBox "New balance: " & m_AccountBalance
File Functions

Visual Basic 6 includes a number of language statements—such as Open, Close, Put #, Get #, and Print # —that allow you to read and write text and binary files. Visual Basic .NET provides a set of functions that are compatible with the Visual Basic 6 file statements. The Upgrade Wizard will upgrade your Visual Basic 6 file statements to the equivalent Visual Basic .NET functions. Table 11-1 illustrates those relationships.

Table 11-1 File Function Mapping from Visual Basic 6 to Visual Basic .NET
Visual Basic 6 Statement | Visual Basic .NET Function
ChDir | ChDir
ChDrive | ChDrive
Close | FileClose
FileCopy | FileCopy
Get | FileGet
Input # | Input
Kill | Kill
Line Input # | LineInput
Lock | Lock
MkDir | MkDir
Open | FileOpen
Print # | Print, PrintLine
Put | FilePut
Reset | Reset
RmDir | RmDir
SetAttr | SetAttr
Unlock | Unlock
Width # | FileWidth
Write # | Write, WriteLine
File Format Compatibility

When you upgrade an application from Visual Basic 6 to Visual Basic .NET, not only do you need to worry about getting your Visual Basic .NET application working in a compatible way, but you also need to be concerned about being able to read data that was written by a Visual Basic 6 application. Because Visual Basic .NET offers you a full set of compatible file functions, you can read files created by Visual Basic 6 applications. For example, you can read and write each of the file types that Visual Basic 6 supports—Text, Binary, and Random. In order to maintain compatibility, however, you need to be aware of some subtleties in the way that you read and write files.

Print, PrintLine, Write, and WriteLine

The Visual Basic .NET Print and PrintLine functions are equivalent to the Visual Basic 6 Print # function. Which function you use depends on how Print # is used in your Visual Basic 6 application. If the text you are printing is terminated by a semicolon, use Print. If the text you are printing is not terminated by a semicolon, use PrintLine. The same sort of mapping applies to the Visual Basic 6 Write # function. For example, the following Visual Basic 6 code:

Print #1, "This is a full line of text"
Print #1, "This is a partial line of text";
Write #2, "This is a full line using write"
Write #2, "This is a partial line using write";

is equivalent to the following Visual Basic .NET code:

PrintLine(1, "This is a full line of text")
Print(1, "This is a partial line of text")
WriteLine(2, "This is a full line using write")
Write(2, "This is a partial line using write")

The Upgrade Wizard automatically upgrades your Visual Basic 6 Print # and Write # code to use the appropriate function, based on whether the text is terminated by a semicolon. It is when you start editing your upgraded Visual Basic .NET code or writing new code that you need to be aware that there are two separate functions that give you full-line versus partial-line file printing.

When Reading or Writing Variables, Size Matters

If you are reading or writing information to a binary file, you need to pay attention to the variables you are using. For example, the byte size of Integer and Long variables is different between Visual Basic 6 and Visual Basic .NET. An Integer is 16 bits in Visual Basic 6 and 32 bits in Visual Basic .NET; a Long is 32 bits in Visual Basic 6 and 64 bits in Visual Basic .NET. If, in Visual Basic .NET, you are reading data from a binary file that was written as a Visual Basic 6 Integer, you will need to use a variable of type Short (16 bits) to read the data.

If you use the Upgrade Wizard to upgrade your application, this change will not be an issue because the wizard automatically maps all your variables that are type Integer to Short and maps type Long to Integer. However, when you edit the upgraded code or write new code, you need to be careful to use the correct data type—based on size, not name—when reading and writing binary files. This includes files that you open using Random mode.

Random-Access Files and Dynamic Arrays

If you create a random-access binary file in Visual Basic 6, additional header information relating to the format of your data may be written to the file. This header information tells Visual Basic how much data to read back in when it reads the file. The information is written when you use a dynamic data type such as a dynamic array or a variable-length string. For example, header information associated with a dynamic array includes the count of the array dimensions, the size of the array, and the lower bound. Header information associated with a variable-length string includes the length of the string. If you use a fixed-length array or a fixed-length string, no header information is written.

Dynamic Array Refresher

Not sure what a dynamic array is? You are probably not alone. The difference between a dynamic array and a fixed-length array involves a subtle variation in syntax. If your declaration for an array does not specify the size, it is considered to be a dynamic array. For example, the following statement declares a dynamic array:

Dim MyDynamicArray() As Integer

Once the array is declared as a dynamic array, it is always considered a dynamic array, even after you redimension it to a known size, as in the following statement:

ReDim MyDynamicArray(100)

If you specify the size of the array as part of the declaration, it is considered to be a fixed-length array. For example,

Dim MyDynamicArray(100) As Integer

Once you have dimensioned a fixed-length array, you cannot use the ReDim statement to change its size.

The Upgrade Wizard does not handle situations that involve writing the contents of a dynamic array to a random-access file. Consider the following Visual Basic 6 code, which initializes the contents of a dynamic array, OutputData, with its own index values.

Dim OutputData() As Byte
ReDim OutputData(100)

Dim i As Integer

For i = 0 To UBound(OutputData)
   OutputData(i) = i
Next

Open Environ("TEMP") & "\ArrayData.Dat" For Random As #1 Len = 120
Put #1, , OutputData
Close #1

If we use the following Visual Basic 6 code to read in the contents of the array, everything works fine:

Dim OutputData() As Byte
Dim i As Integer

Open Environ("TEMP") & "\ArrayData.Dat" For Random As #1 Len = 120
Get #1, , OutputData
Close #1

For i = 0 To UBound(OutputData)
   If i <> OutputData(i) Then
      MsgBox "Error: Data element (" & i & ") is incorrect. " & _
         "Value=" & OutputData(i)
      Exit For
   End If
Next

If you upgrade this Visual Basic 6 code to Visual Basic .NET, the resulting code is as follows:

Dim OutputData() As Byte
Dim i As Short

FileOpen(1, Environ("TEMP") & "\ArrayData.Dat", _
   OpenMode.Random, , , 120)
'UPGRADE_WARNING: Get was upgraded to FileGet and has a new behavior.
FileGet(1, OutputData)
FileClose(1)

For i = 0 To UBound(OutputData)

   If i <> OutputData(i) Then
      MsgBox("Error: Data element (" & i & ") is incorrect. " & _
         "Value=" & OutputData(i))
      Exit For
   End If
Next

If you run this upgraded code, the first problem you will encounter is a “Cannot determine array type because it is Nothing” exception on the line FileGet(1, OutputData). This exception occurs for exactly the reason that it indicates: the OutputData array is uninitialized, so its value is Nothing. It is caused by a difference between Visual Basic 6 and Visual Basic .NET. In Visual Basic 6, although the array is not initialized, the Visual Basic Get statement can still determine the type of the array at run time. The type is needed so that the Get statement can redimension the array with the correct type and fill it with the data contained in the file.

To fix this problem, you need to redimension the array with at least one element so that it is initialized to a value other than Nothing. Include the following statement after the Dim OutputData() As Byte statement:

ReDim OutputData(0)

Run the code again, and you uncover another problem. A message box pops up telling you “Error: Data element (0) is incorrect. Value=1.” This message occurs because the Visual Basic .NET FileGet statement assumes that the data it is reading was written from a fixed-length array, not a dynamic array. The value of 1 that leads to the error is actually a value contained within the header information for the dynamic array. It is the number of dimensions contained in the dynamic array, not the dynamic array data itself.

To fix this problem, you need to tell the FileGet statement that the data it is reading is coming from a dynamic array. You do this by specifying the optional FileGet parameter called ArrayIsDynamic. Change the following line in the Visual Basic .NET upgraded code:

FileGet(1, OutputData)

to

FileGet(1, OutputData, ArrayIsDynamic:=True)

Run the code again, and it will work as expected. The FileGet function will expand the OutputData array to have an upper bound of 100 and will read in the array contents, following the array header information, from the file.

Random-Access Files and Fixed-Length Strings

A problem similar to the dynamic array problem discussed in the previous section will occur if you attempt to read a fixed-length string from a random-access file. Consider the following upgraded Visual Basic .NET code to read in a fixed-length string:

Dim OutputData As New VB6.FixedLengthString(9)
Dim i As Short

FileOpen(1, Environ("TEMP") & "\StringData.Dat", _
   OpenMode.Random, , , 15)

'UPGRADE_WARNING: Get was upgraded to FileGet and has a new behavior.
FileGet(1, OutputData.Value)
FileClose(1)

For i = 1 To Len(OutputData.Value)
   If CStr(i) <> Mid(OutputData.Value, i, 1) Then
      MsgBox("Error: Character (" & i & ") is incorrect.  Value=" & _
         Mid(OutputData.Value, i, 1))
      Exit For
   End If
Next

This situation is different from the dynamic array case in that the FileGet function assumes that the data contained within the file is a variable-length string, not a fixed-length string. This means that the FileGet function expects the file to contain a string header giving the length of the string. If you execute the code, you will encounter a “Bad record length” exception. The FileGet function is getting tripped up because it thinks the first 4 bytes of data is the string length when in fact it is the string data. The function attempts to use the first 4 bytes of string data as the length, comes up with a large number—larger than the record size—and throws an exception.

To address this problem, the FileGet function provides an optional parameter called StringIsFixedLength. Set the parameter to True, and the code will work as expected. To fix the Visual Basic .NET code, change the following line:

FileGet(1, OutputData.Value)

to

FileGet(1, OutputData.Value, StringIsFixedLength:=True)


Types and Type Operations

Visual Basic .NET offers most of the same types as Visual Basic 6. You will find the same basic types, such as Byte, Integer, Single, and String. You can also define the same types, such as Enum, user-defined types (called Structure in Visual Basic .NET), and Class. Visual Basic .NET also offers some new types that you can use—Short, Char, and Decimal. It also gives you a new Interface type that you can define or use. This section focuses on Visual Basic 6 types for which support has changed in Visual Basic .NET.

Object Replaces Variant

The Variant data type has existed in the Visual Basic language since Visual Basic 2. It is part of the Visual Basic franchise. If you do not specify a type for a variable, you can always rely on Variant being the default type. The beauty of the Variant data type is that you can assign any numeric, string, array, or object reference value to it. It can also be in various states of nothingness—missing, Null, or Nothing.

The same characteristic that makes the Variant data type easy to use is also the one that will get you into trouble. The compiler will not and cannot step in to save you if you assign variants containing incompatible types to each other. For example, if you attempt to assign a variant containing an object to a variant containing a simple type such as an Integer, you will encounter a run-time error.

Visual Basic .NET introduces the Object type. It is a combination of the Object and Variant types found in Visual Basic 6. Like the Visual Basic 6 Object type, a Visual Basic .NET object can be assigned to an object instance, such as a control, and you can make late-bound calls to the object. The Variant-like behavior of the Object type is that you can assign any type to it, ranging from Integer, Array, and Structure to class objects.

IsMissing Is Lost

One characteristic that the Visual Basic .NET Object type lacks compared to the Visual Basic 6 Variant type is a value that means missing. Other than being set to an actual value, the only other state a Visual Basic .NET Object type can be set to is Nothing. The following Visual Basic 6 code demonstrates how this can be a problem if you are testing for a missing optional parameter:

Private m_Info As String
Private m_Picture As StdPicture

Public Sub SetData(Optional ByVal Info As String, _
                   Optional ByVal Picture As Variant)

   If Not IsMissing(Info) Then
      m_Info = Info
   End If

   If Not IsMissing(Picture) Then
      Set m_Picture = Picture
   End If

End Sub

Because a Visual Basic .NET object cannot be set to a value representing “missing,” the IsMissing function is not available in Visual Basic .NET. The closest approximation is IsNothing. The Upgrade Wizard will replace all of your calls to IsMissing with calls to IsNothing.

Visual Basic .NET requires that you specify a default value for all optional parameters to be used if no argument is passed in. The wizard will automatically initialize parameters to reasonable initial values depending on the type; a string is set to an empty string, a numeric is set to 0, and an object parameter is set to Nothing.

Let’s suppose that in the code just given you wanted to pass in Nothing as the value for the Picture parameter. Your intent is to erase any current picture. Inspect the following upgraded code, and you will see that the logic cannot distinguish between a missing parameter and a parameter for which you explicitly pass in Nothing. The Picture parameter will always be set to Nothing in either case. If you explicitly pass in Nothing to erase the current picture, the Not IsNothing(Picture) test will fail and the picture member (m_Picture) will not be set to Nothing; in other words, the picture will not be erased.

Private m_Info As String
Private m_Picture As System.Drawing.Image

Public Sub SetData(Optional ByVal Info As String = "", _
                   Optional ByVal Picture As Object = Nothing)

   'UPGRADE_NOTE: IsMissing() was changed to IsNothing().
   If Not IsNothing(Info) Then
      m_Info = Info
   End If

   'UPGRADE_NOTE: IsMissing() was changed to IsNothing().
   If Not IsNothing(Picture) Then
      m_Picture = Picture
   End If
End Sub

How should you modify your code to cope with this situation? You need to reintroduce the concept of IsMissing into your code. One approach is to set the initialization value to a unique value that will not be passed in under normal circumstances. For example, in the previous code, you could set the parameter initialization values to “<Missing>”. If you see that a parameter value is set to “<Missing>”, you can assume that the caller did not specify the argument when making the call. The logic will be compatible with the way it was before. If the values are not specified, the current value is left intact, and if the caller passes in a value—including Nothing or an empty string—the current value will be replaced by the passed-in value. Applying “<Missing>” to the parameter initialization values and fixing up the code to test for “<Missing>” yields the following:

Private m_Info As String
Private m_Picture As System.Drawing.Image

Public Sub SetData(Optional ByVal Info As String = "<Missing>", _
                   Optional ByVal Picture As Object = "<Missing>")
   If Info <> "<Missing>" Then
      m_Info = Info
   End If

   If Picture <> "<Missing>" Then
      m_Picture = Picture
   End If
End Sub
Dealing with Null

As we discussed in the previous section, an Object can be in only one of two states—set to a value or to Nothing. A Visual Basic Variant, on the other hand, can contain other states, such as empty, Null, or missing. We can work around the absence of the missing state by using a special value that you create to mean “missing.” When dealing with Null, you do not need to create your own value. Rather, the .NET Framework provides the System.DBNull.Value object as the equivalent to a Visual Basic 6 Null value.

Why is the name of the value DBNull.Value? Why not just Null? The reason is that the DB in DBNull is short for database. Since it is most common to deal with null values when reading values from or writing values to database fields, DBNull.Value is intended to be used when you want to set or check for null values in a database table. When you are setting an object variable to a field contained in an ADO.NET dataset or to a field in a traditional ADO recordset, if the field value is Null, the object variable will be set to System.DBNull.Value by convention.

Null Propagation

Many Visual Basic 6 functions—such as Left, Mid, Chr, and Hex—deal with Variant values containing Null. These functions return a Variant containing Null. Returning a Null value in response to a passed-in Null value is known as null propagation. The null-propagating functions all accept Variant values and return Variant values. A set of sibling functions in the Visual Basic 6 language accepts strings and returns strings. These functions—such as Left$, Mid$, Chr$, and Hex$—will result in an error if you attempt to pass in a value of Null.

By supporting null propagation, Visual Basic 6 allows you to write working code that does not need to check for Null. For example, if you have code that converts a customer name—taken from a field in a database—to lowercase, the code will work even if the customer name is not provided and is Null. The following Visual Basic 6 code will execute without error even though vCustomerMiddleName is Null.

Dim vCustomerMiddleName As Variant
vCustomerMiddleName = Null
vCustomerMiddleName = LCase(vCustomerMiddleName)

Visual Basic .NET does not support null propagation. This means that although the same Visual Basic 6 functions—Left, Mid, Chr, Hex, and LCase—are available in Visual Basic .NET, the Visual Basic .NET functions do not accept Null—System.DBNull.Value to be exact—as a legal value. The Upgrade Wizard produces the following code from the previous Visual Basic 6 example:

Dim vCustomerMiddleName As Object
vCustomerMiddleName = System.DBNull.Value
vCustomerMiddleName = LCase(vCustomerMiddleName)

This code leads to a run-time exception: “No accessible overloaded ‘Strings.LCase’ can be called without a narrowing conversion.” This message is an overly technical, wordy way of saying that the LCase function does not accept System.DBNull.Value as a legal argument.

How can you fix this problem? The obvious fix is to insert a check before the call to every Visual Basic .NET function to make sure that the passed-in argument is not System.DBNull.Value. We could modify the code as follows:

Dim vCustomerMiddleName As Object
vCustomerMiddleName = System.DBNull.Value
If Not vCustomerMiddleName Is System.DBNull.Value Then
   vCustomerMiddleName = LCase(vCustomerMiddleName)
End If

This fix will work great if you are making a couple of calls to LCase in your code, but what if you are making hundreds or thousands of calls to the function? Adding checks for System.DBNull.Value before all calls to your Visual Basic .NET functions will be too time-consuming. Another solution is to declare your own LCase function that handles null propagation. For example, you can create the LCase function in a module—where it can be accessed from anywhere in your code—as follows:

Public Function LCase(ByVal obj As Object) As Object
   If Not obj Is System.DBNull.Value Then
      ' Value is not null so call Visual Basic .NET function
      obj = Microsoft.VisualBasic.LCase(obj)
   End If
   Return obj
End Function

If you place this function in a module named Module1, it is a simple matter to search for and replace all your calls to LCase with ones to Module1.LCase.

Arrays

Visual Basic .NET supports two array types—strongly typed and generic System.Array. Strongly typed arrays are the arrays you are accustomed to using in Visual Basic 6. Anytime you use the Dim statement to declare an array variable of a specific type, you are creating a strongly typed array. Visual Basic .NET—actually the .NET Framework—offers a new System.Array class. This class offers a set of methods so that you can define an array of any type and any number of dimensions at run time. An important difference between the two array types is that strongly typed arrays do not support nonzero lower bounds, whereas the System.Array class does.

Because Visual Basic .NET offers primary support for strongly typed arrays, it is not possible to declare a strongly typed array with a nonzero lower bound. Visual Basic .NET in turn does not support nonzero-bound array-related features such as array declarations including the To statement and Option Base 1. It does support LBound, but when LBound is used on a strongly typed array, it will always return 0.

The Upgrade Wizard will upgrade your arrays to strongly typed Visual Basic .NET arrays. If you are using Option Base 1, the wizard will issue an UPGRADE_WARNING message: “Lower bound of array <ArrayName> was changed from 1 to 0.” The UPGRADE_WARNING is added for each array declaration found that does not specify a lower bound. If you are using a positive value for the array’s lower bound, the wizard will remove the lower bound from the declaration and issue the same UPGRADE_WARNING. If you specify a negative value for the lower bound in your array declaration, the wizard will leave the declaration as is and will include an UPGRADE_ISSUE comment: “Declaration type not supported: Array with lower bound less than zero.” The following code samples demonstrate how the wizard upgrades your Visual Basic 6 array declarations. The Visual Basic 6 code

Option Base 1
…
Dim OptionOneBasedArray(10) As Integer
Dim PositiveLBoundArray(10 To 20) As Long
Dim NegativeLBoundArray(-10 To 10) As Variant

upgrades to the following Visual Basic .NET code:

'UPGRADE_WARNING: Lower bound of array OptionOneBasedArray
'was changed from 1 to 0.
Dim OptionOneBasedArray(10) As Short

'UPGRADE_WARNING: Lower bound of array PositiveLBoundArray was
'changed from 10 to 0.
Dim PositiveLBoundArray(20) As Integer

'UPGRADE_ISSUE: Declaration type not supported: Array with
'lower bound less than zero.
Dim NegativeLBoundArray(-10 To 10) As Object

If you are using positive lower bounds in your array declaration, the upgraded declaration will compile and run. However, if your code uses hard-coded references to the lower bound of the array, the array will have more elements than you need or will use. For example, suppose that you had code that accessed each element of the PositiveLBoundArray, as follows:

Dim i As Integer
For i = 10 To 20
   PositiveLBoundArray(i) = i
Next

This code will work without any problem. The only issue is that there are now 10 elements of the array, 0 through 9, that are not being used.

If, in Visual Basic 6, you declared your array with a negative lower bound, the wizard will leave the declaration as is. The declaration will result in a compiler error in Visual Basic .NET, however. You will need to change your array to use a 0 lower bound, and you may need to adjust your code to work with the new array bound. For example, you could change the NegativeLBoundArray declaration to use a 0 bound and the same number of elements as follows:

Dim NegativeLBoundArray(20) As Object

You would then need to update your code that accesses the elements of NegativeLBoundArray. Suppose, for example, that you have code such as the following:

Dim i As Integer
For i = LBound(NegativeLBoundArray) To UBound(NegativeLBoundArray)
   NegativeLBoundArray(i) = i
Next

The For…Next loop is acceptable because you are using LBound and UBound, and so the code automatically picks up the new lower and upper bounds for the array. The value that is being set into the array, however, is incorrect. The intent was to seed the array with values from –10 through 10, but the upgraded code is now seeding the array with values from 0 through 20.

To fix this problem, you could introduce an adjustment factor into your code and apply it to any code that is using hard-coded values to access an array element. The example above uses a calculated value i based on the array index. We could adjust i by subtracting 10. If we define our adjustment value in a constant, the updated code is

Dim i As Integer
Const NegativeLBoundArray_Adjustment = 10
For i = LBound(NegativeLBoundArray) To UBound(NegativeLBoundArray)
   NegativeLBoundArray(i) = i - NegativeLBoundArray_Adjustment
Next

With the adjustment in place, the code once again seeds the array with values from –10 through 10.

There is another way to solve this problem. If it’s easier for you to use negative array bounds, or simply any nonzero lower bound, consider using the .NET Framework System.Array class. This class includes a method called CreateInstance, which allows you to create a generic System.Array with nonzero lower bounds. If we start with the original Visual Basic 6 array declarations given earlier:

Dim OptionOneBasedArray(10) As Integer
Dim PositiveLBoundArray(10 To 20) As Long
Dim NegativeLBoundArray(-10 To 10) As Variant

we can convert, in Visual Basic .NET, the declarations to System.Array and include calls to CreateInstance to initialize the type and dimensions of the arrays as follows:

Dim OptionOneBasedArray As System.Array  ' 1 to 10 As Short
Dim PositiveLBoundArray As System.Array  ' 10 to 20 As Integer
Dim NegativeLBoundArray As System.Array  ' –10 to 10 As Object

' Arrays to define the array length and LBound for each dimension
' Since we are only dealing with single dimension arrays, we only
' need one element
Dim ArrLens(0) As Integer
Dim LBounds(0) As Integer

ArrLens(0) = 10
LBounds(0) = 1
OptionOneBasedArray = System.Array.CreateInstance(GetType(Short), _
   ArrLens, LBounds)

ArrLens(0) = 11
LBounds(0) = 10
PositiveLBoundArray = System.Array.CreateInstance(GetType(Integer), _
   ArrLens, LBounds)

ArrLens(0) = 21
LBounds(0) = -10
NegativeLBoundArray = System.Array.CreateInstance(GetType(Object), _
   ArrLens, LBounds)

Dim i As Integer
For i = LBound(NegativeLBoundArray) To UBound(NegativeLBoundArray)
   NegativeLBoundArray(i) = i
Next

A caveat when using System.Array is that you cannot assign a System.Array variable containing a nonzero lower bound to a strongly typed array. For example, the following code will result in a “Specified cast is not valid” exception. The exception occurs on the assignment of the strongly typed array variable StrongTypeArray to the System.Array variable NegativeLBoundArray.

Dim StrongTypeArray() As Object
StrongTypeArray = NegativeLBoundArray
Structures

You will not find the Visual Basic 6 Type…End Type, commonly referred to as a user-defined type, in Visual Basic .NET. Instead you will find Structure…End Structure, a superset of Type…End Type. Within a structure, you can define members of any type. In addition, you can define methods and properties. You will find that a Visual Basic .NET structure works much like a class. One difference is that you cannot create instances of a structure, and all structure members implicitly include the Shared attribute. This means that a structure can access shared members of the class or module where it is defined, but it cannot access instance members.

Member Initialization

A difference between Visual Basic 6 and Visual Basic .NET user-defined types is that Visual Basic 6 supports fixed-length array declarations within a user-defined type. In Visual Basic .NET, however, you cannot specify the size of the array contained within a user-defined type. To work around this limitation, you can define a constructor for your user-defined type in which you initialize the array to a fixed size. Consider the following Visual Basic 6 code:

Private Type MyType
   MyStringArray(10) As String
End Type

Sub Main()
   Dim mt As MyType
   MsgBox (mt.MyStringArray(0))
End Sub

The Upgrade Wizard upgrades this code to

Private Structure MyType
   <VBFixedArray(10)> Dim MyStringArray() As String

   'UPGRADE_TODO: "Initialize" must be called to initialize
   'instances of this structure.
   Public Sub Initialize()
      ReDim MyStringArray(10)
   End Sub
End Structure

Public Sub Main()
   'UPGRADE_WARNING: Arrays in structure mt may need to be
   'initialized before they can be used.
   Dim mt As MyType
   MsgBox(mt.MyStringArray(0))
End Sub

The wizard has done a few things here to create the equivalent Visual Basic .NET code. It has added the VBFixedArray attribute to the structure, specifying the size of the array. This attribute is useful if you are passing the structure to a Visual Basic file function such as Get or Put. The Visual Basic file function will use the attribute to determine whether it needs to read or write additional header information associated with the array. Another change the wizard has made is to insert a public method within the structure called Initialize. The code contained within the Initialize method includes ReDim calls to initialize fixed-length arrays contained within the structure. Finally, the wizard has inserted some comments to let you know that you should include a call to the Initialize method before attempting to use the structure.

If you run the code as upgraded, you will encounter a NullReferenceException, “Object reference not set to an instance of an object,” on the following line:

MsgBox(mt.MyStringArray(0))

The reason is that the structure member MyStringArray does not contain any array elements. To initialize the array with elements, you need to call the Initialize method on the structure. The best place to call Initialize is right after you declare a structure variable. In the example above, you should call Initialize right after the declaration of mt. After you apply this change, the updated code will be as follows:

Private Structure MyType
   <VBFixedArray(10)> Dim MyStringArray() As String

   Public Sub Initialize()
      ReDim MyStringArray(10)
   End Sub
End Structure

Public Sub Main()
   Dim mt As MyType
   mt.Initialize()
   MsgBox(mt.MyStringArray(0))
End Sub
LSet

The LSet statement can be used in Visual Basic 6 to assign the contents of one user-defined type to another. The assignment will work even if the two user-defined types do not contain the same fields. LSet works by copying the memory for the source user-defined type to the target user-defined type.

An example of where you might use LSet is to provide two different ways to view or access the same data. For example, suppose you want to store a version number in a 32-bit integer. The version number consists of 4 parts, each 1 byte in length—major, minor, revision, and build version numbers. To simplify creating the 32-bit integer, you can create a user-defined type containing each part of the version number. For the purpose of storing the version in a 32-bit integer, you can use LSet to assign a user-defined type containing a 32-bit integer to the user-defined type containing the version parts. The following Visual Basic 6 code demonstrates how this can be done.

Type VersionPartsType
   BuildVer As Byte
   RevisionVer As Byte
   MinorVer As Byte
   MajorVer As Byte
End Type

Type VersionType
   Version As Long
End Type

Sub Main()
   Dim vp As VersionPartsType
   Dim vt As VersionType

   vp.MajorVer = 7
   vp.MinorVer = 0
   vp.RevisionVer = 1
   vp.BuildVer = 2

   LSet vt = vp
   MsgBox "32-bit version number is " & Hex(vt.Version)
End Sub

Visual Basic .NET does not support the LSet statement to assign one type to another. The Upgrade Wizard will place the following comment before calls to LSet in the upgraded code.

'UPGRADE_ISSUE: LSet cannot assign one type to another.

To solve this problem, you can create a copy function that you use to copy each member of one user-defined type to another user-defined type. In this case, you will need to write a copy function that combines four members of VersionPartsType into a single member of VersionType. Here is an example of a copy function you can use to take the place of the LSet statement:

Sub CopyVersion(ByVal vp As VersionPartsType, _
                ByRef vt As VersionType)

   ' Calculate the 32-bit version number by placing each version part
   ' within each byte of the 32-bit integer. Major version goes into
   ' the top byte and build goes into the lowest byte.
   vt.Version = vp.MajorVer * 2 ^ 24 + vp.MinorVer * 2 ^ 16 + _
      vp.RevisionVer * 2 ^ 8 + vp.BuildVer
End Sub
note | Note The LSet statement plays two roles: it left-aligns a string within a string, and it also sets a type to another type. The first role, left-aligning a string within a string, is supported in Visual Basic .NET.


Making Your Code Thread-Safe

Threading issues may affect Visual Basic 6 ActiveX DLLs or UserControls that you upgrade to Visual Basic .NET, especially since the Visual Basic Upgrade Wizard does not make your code thread-safe when upgrading it. You will need to make manual changes to your code—as explained later in this section—to make it thread-safe.

Such threading issues are uncommon, since components created in Visual Basic 6 are thread-safe by nature. Thread safety is accomplished in Visual Basic 6 by letting only a single thread access a component. Even if multiple threads are attempting to gain access to the component, all requests are synchronized through the single accessor thread associated with the component.

Visual Basic .NET components (including UserControls), on the other hand, are not thread-safe. Multiple threads are allowed to access the component simultaneously. There is no synchronization of threads on behalf of a Visual Basic .NET component. As a result, code that is thread-safe in Visual Basic 6 becomes unsafe after being upgraded to Visual Basic .NET. This change will affect you if you are running your component in a multithreaded environment such as Internet Explorer, Internet Information Services, or COM+. If you are not sure whether your component will be run in a multithreaded environment, it is always best to err on the side of caution and ensure that your component is thread-safe.

When more than one thread is executing your code, each thread has access to the same shared data: member variables and global variables. Allowing more than one thread to manipulate shared data at the same time can lead to problems. For example, if two threads are attempting to increment a count of items in a collection simultaneously, the count may be end up being incremented by 1 and not 2. This will happen if both threads obtain the current count at about the same time and add 1. Neither thread will see what the other thread is doing. We call this a synchronization problem, since it involves a situation in which only one thread at a time should be allowed to perform the operation.

For example, a synchronization problem can occur when one thread caches a global or member variable value in a local variable and performs a calculation on that variable. Let’s take a look at the following Visual Basic .NET code.

Public Class GreedyBank

   Private m_Balance As Decimal

   Public Sub New()
      m_Balance = 20
   End Sub

   Public Function Withdraw(ByVal Amount As Decimal) As String

      'Protect against negative balance
      If m_Balance >= Amount Then
         m_Balance = m_Balance - Amount
      Else
         Return "The amount you requested will overdraw " & _
            "your account by $" & Amount – m_Balance
      End If

   End Function

End Class

This example is pretty straightforward. A client application calls Withdraw. Withdraw takes the current customer balance and deducts the requested withdrawal amount. To ensure that the customer’s account does not become overdrawn by the transaction, the function checks to make sure that the customer has enough money to cover the withdrawal amount.

This works great when only one thread at a time is executing the function. However, you can run into problems if two threads are executing the function simultaneously.

Let’s suppose that the first thread has executed up to the following line:

m_Balance = m_Balance - Amount

The customer’s balance checked out, so the thread is ready to deduct the Amount. Assume that the customer has just enough money ($20) to cover the withdrawal request of $20. Just before the first thread executes the above statement, a second thread comes along, requesting an amount of $20 and completing the following statement:

        If m_Balance >= Amount Then

The second thread also sees that the bank balance, $20, is sufficient to cover the withdrawal request and proceeds to the next line:

m_Balance = m_Balance - Amount

At this point, the customer is rewarded with an account overdraft and the fees that accompany it. The first thread subtracts the withdrawal, leaving a balance of $0. The second thread, having successfully bypassed the sufficient-balance-to-cover-withdraw-amount check, then executes the same line, leaving a net balance of –$20. Here code that on the surface appears to be perfectly legitimate leads to unanticipated results—although the customer would need to submit two withdrawal requests simultaneously to encounter the problem.

Using a SyncLock Block

To fix this synchronization problem, you need to make sure that only one thread at a time has access to your shared member or global variables. In other words, you need to synchronize access to your data, letting only one thread at time execute certain blocks of code that manipulate shared data. You can do this by using the new Visual Basic .NET keyword SyncLock.

A SyncLock block ensures that only one thread at a time is executing code within the block. In the earlier code example, we can fix our negative balance problem by placing a SyncLock around the contents of the Withdraw function:

SyncLock GetType(GreedyBank)

   'Protect against negative balance
   If m_Balance >= Amount Then
      m_Balance = m_Balance - Amount
   Else
      Return "The amount you requested will overdraw " & _
         "your account by $" & Amount – m_Balance
   End If

End SyncLock

If there is any chance that your Visual Basic .NET component or UserControl will run in a multithreaded environment, we highly recommend using SyncLock blocks to protect your shared data. Taking the extra time to do this work up front can save you from grief down the road.


Windows API

The foundation upon which every Windows application is built is the Windows API. Visual Basic applications are no exception. Visual Basic, through the language and forms package, abstracts the Windows API to a set of easy-to-use statements, components, and controls. In many cases, you can build a Visual Basic application without needing to call a Windows API function directly. However, because the Visual Basic language and forms package does not represent every Windows API function available, Visual Basic supports calling Windows API functions directly. It has done so since version 1, enabling you to add capabilities to your application that cannot be implemented in the Visual Basic language. Visual Basic .NET carries this capability forward by enabling you to declare and call Windows API functions in the same way as before.

This section focuses on the language changes that affect the way you create Declare statements for API functions. It also shows how you can use classes contained in the .NET Framework to complement or replace the Windows API calls you make in your application.

Type Changes

Two changes that affect almost every Windows API function declaration involve the numeric types Integer and Long. In Visual Basic 6, an Integer is 16 bits and a Long is 32 bits. In Visual Basic .NET, an Integer is 32 bits and a Long is 64 bits. Visual Basic .NET adds a new type called Short, which, in terms of size, is the replacement for the Visual Basic 6 Integer type.

In Visual Basic .NET, when you create a new Declare statement for a Windows API function, you need to be mindful of this difference. Any parameter type or user-defined type member that was formerly a Long needs to be declared as Integer; any member formerly declared as Integer needs to be declared as Short.

Take, for example, the following Visual Basic 6 Declare statement, which contains a mix of Integer and Long parameters:

Declare Function GlobalGetAtomName Lib "kernel32" _
   Alias "GlobalGetAtomNameA" (ByVal nAtom As Integer, _
   ByVal lpBuffer As String, ByVal nSize As Long) _
   As Long

The equivalent Visual Basic .NET declaration is as follows:

Declare Function GlobalGetAtomName Lib "kernel32" _
   Alias "GlobalGetAtomNameA" (ByVal nAtom As Short, _
   ByVal lpBuffer As String, ByVal nSize As Integer) _
   As Integer

The Upgrade Wizard will automatically change all the variable declarations in your code to use the right size. You need to worry about the size of a type only when you are creating new Declare statements or modifying existing statements.

As Any No Longer Supported

Because in some cases you need to pass either a string or a numeric value to a Windows API function, Visual Basic 6 allows you to declare parameter types As Any. This declaration allows you to pass an argument of any type, and Visual Basic will pass the correct information when the call is made. Although this gives you greater flexibility, no type checking is performed on the argument when you call the API function. If you pass an incompatible argument type, the application may crash.

Visual Basic .NET does not support declaring Windows API parameters As Any. Instead, it allows you to declare the same API function multiple times, using different types for the same parameter.

The Windows API function SendMessage is an example of an instance in which you would use As Any in the API function declaration. Depending on the Windows message you are sending, the parameter types required for the message will vary. The WM_SETTEXT message requires you to pass a string, whereas the WM_GETTEXTLENGTH message requires you to pass 0, a numeric value, for the last parameter. In order to handle both of these messages, you create the Visual Basic 6 Declare statement for SendMessage as follows:

Private 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

The last parameter has been declared As Any. The following code is an example of setting the caption of the current form and then retrieving the length of the caption set:

Dim TextLen As Long
Const WM_SETTEXT = &HC
Const WM_GETTEXTLENGTH = &HE

SendMessage Me.hwnd, WM_SETTEXT, 0, "My text"
TextLen = SendMessage(Me.hwnd, WM_GETTEXTLENGTH, 0, 0&)

To implement the equivalent functionality in Visual Basic .NET, you create multiple Declare statements for the SendMessage function. In the case of the SendMessage API, you create two Declare statements, using two different types for the last parameter—String and Integer—as follows:

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

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
note | You also need to change the other parameter types from Long to Integer, as discussed in the previous section.

Although having to create multiple Declare statements for the same function is more work, you benefit by getting both the flexibility of using the same function name and type checking when the call is made.

AddressOf Changes

Certain API functions, such as EnumWindows, require a pointer to a callback function. When you call the API function, Windows calls the callback function you provided. In the case of EnumWindows, Windows will call the function for each top-level window contained on the screen.

Visual Basic 6 allows you to declare Windows API functions that take callback function pointers by declaring the function pointer parameter as Long, to represent a 32-bit pointer. You call the API function passing the subroutine to serve as the callback function, qualified by the AddressOf keyword.

Visual Basic .NET still supports the AddressOf keyword, but instead of returning a 32-bit integer, it returns a delegate. A delegate is a new type in Visual Basic .NET that allows you to declare pointers to functions or to class members. This means that you can still create Declare statements for Windows API functions that take a callback function pointer as a parameter. The difference is that instead of declaring the parameter type as a 32-bit integer, you need to declare the function parameter as a delegate type.

The following Visual Basic 6 code demonstrates how to use AddressOf to pass a pointer to a callback function:

Declare Function EnumWindows Lib "USER32" _
   (ByVal EnumWindowsProc As Long, _
    ByVal lParam As Long) As Boolean

Declare Function GetWindowText Lib "USER32" Alias "GetWindowTextA" _
   (ByVal hwnd As Long, ByVal TextBuffer As String, _
    ByVal TextBufferLen As Long) As Long

Dim TopLevelWindows As New Collection

Sub Main()

   Dim WindowCaption As Variant

   ' Enumerate all top-level windows, EnumWindowsCallback will
   ' be called
   EnumWindows AddressOf EnumWindowsCallback, 0

   ' Echo list of captions to Immediate window
   For Each WindowCaption In TopLevelWindows
      Debug.Print WindowCaption
   Next

End Sub

Function EnumWindowsCallback(ByVal hwnd As Long, _
ByVal lParam As Long) _
As Boolean
   Dim TextBuffer As String
   Dim ActualLen As Long

   ' Get window caption
   TextBuffer = Space(255)
   ActualLen = GetWindowText(hwnd, TextBuffer, Len(TextBuffer))
   TextBuffer = Left(TextBuffer, ActualLen)

   ' Put window caption into list
   If TextBuffer <> "" Then
      TopLevelWindows.Add TextBuffer
   End If

   ' Return True to keep enumerating
   EnumWindowsCallback = True

End Function

When you use the Visual Basic .NET Upgrade Wizard to upgrade the above code, you get the following result:

Declare Function EnumWindows Lib "USER32" _
   (ByVal EnumWindowsProc As Integer, ByVal lParam As Integer) _
      As Boolean

Declare Function GetWindowText Lib "USER32" Alias "GetWindowTextA" _
   (ByVal hwnd As Integer, ByVal TextBuffer As String, _
    ByVal TextBufferLen As Integer) As Integer

Dim TopLevelWindows As New Collection

Public Sub Main()

   Dim WindowCaption As Object

   'UPGRADE_WARNING: Add a delegate for AddressOf EnumWindowsCallback
   EnumWindows(AddressOf EnumWindowsCallback, 0)

   For Each WindowCaption In TopLevelWindows
      System.Diagnostics.Debug.WriteLine(WindowCaption)
   Next WindowCaption

End Sub

Function EnumWindowsCallback(ByVal hwnd As Integer, _
                             ByVal lParam As Integer) As Boolean

   Dim TextBuffer As String
   Dim ActualLen As Integer

   TextBuffer = Space(255)
   ActualLen = GetWindowText(hwnd, TextBuffer, Len(TextBuffer))
   TextBuffer = Left(TextBuffer, ActualLen)

   If TextBuffer <> "" Then
      TopLevelWindows.Add(TextBuffer)
   End If

   ' Return True to keep enumerating
   EnumWindowsCallback = True

End Function

The upgraded code contains a single compiler error: “‘AddressOf’ expression cannot be converted to ‘Integer’ because ‘Integer’ is not a delegate type.” The error occurs on the following statement:

EnumWindows(AddressOf EnumWindowsCallback, 0)

To fix the error, you need to change the Declare declaration for EnumWindows to accept a delegate type for the EnumWindowsProc parameter instead of an Integer type. The easiest way to create a delegate declaration for the EnumWindowsCallback function is to perform the following steps:

  1. Copy and paste the subroutine declaration in your code that is to serve as the callback function.

  2. Insert the Delegate keyword at the beginning of the declaration.

  3. Change the function name by appending the word Delegate to it. The delegate name must be unique.

In the previous example, we can create the delegate declaration by copying and pasting the following function signature to the section of code containing the Declare statements:

Function EnumWindowsCallback(ByVal hwnd As Integer, _
                             ByVal lParam As Integer) As Boolean

Insert the keyword Delegate at the beginning of the function declaration given previously, and change the name by appending Delegate to the original function name—EnumWindowsCallback becomes EnumWindowsCallbackDelegate. You end up with a delegate declaration for a function with the form—name and parameters—of EnumWindowsCallback:

Delegate Function EnumWindowsCallbackDelegate(ByVal hwnd As Integer, _
   ByVal lParam As Integer) _
   As Boolean

Now that the delegate declaration for the callback function is in place, you need to change the parameter type for the EnumWindows Declare statement from Integer to the delegate type EnumWindowsCallbackDelegate, as follows:

Declare Function EnumWindows Lib "USER32" _
   (ByVal EnumWindowsProc As EnumWindowsCallbackDelegate, _
    ByVal lParam As Integer) As Boolean

The code will compile and run without error, producing the same results as the original Visual Basic 6 code.

Passing User-Defined Types to API Functions

When you pass a Visual Basic 6 user-defined type to an API function, Visual Basic passes a pointer to the memory containing the user-defined type; the API function sees the members of the user-defined type in the same order as they were declared in Visual Basic. This is not the case for Visual Basic .NET, however. If you declare a user-defined type, the order of the members is not guaranteed to be the order in which they appear in the code. The .NET runtime may reorganize the members of a user-defined type in a way that is most efficient for the user-defined type to be passed to a function. To guarantee that the members are passed exactly as declared in the code, you need to use marshalling attributes.

Marshaling Attributes

A marshaling attribute is an attribute that you attach to a structure member or subroutine parameter declaration to specify how the member or parameter should be stored in memory and passed to the function. For example, if you are calling an API function named MyFunction, written in C or C++, that takes a VARIANT_BOOL—a 2-byte Boolean type—parameter, you can use the MarshalAs attribute to specify the parameter type that the API function expects. Normally, a Boolean parameter is passed using 4 bytes, but if you include UnmanagedType.VariantBool as a parameter to the MarshalAs attribute, the parameter is marshaled—in other words, passed—as a 2-byte VARIANT_BOOL argument. The following code demonstrates how to apply the MarshalAs attribute to your API function declarations:

Declare Sub MyFunction Lib "MyLibrary.dll" _
   (<MarshalAs(UnmanagedType.VariantBool)> ByVal MyBool As Boolean)

The MarshalAs attribute is contained in the System.Runtime.InteropServices namespace. In order to call MarshalAs without qualification, you need to add an Imports statement at the top of the module for System.Runtime.InteropServices, as follows:

Imports System.Runtime.InteropServices

For structures passed to API functions, declare the structure using the StructLayout attribute to ensure compatibility with Visual Basic 6. The StructLayout attribute takes a number of parameters, but the two most significant for ensuring compatibility are LayoutKind and CharSet. To specify that the structure members should be passed in the order in which they are declared, set LayoutKind to LayoutKind.Sequential. To ensure that string parameters are marshaled as ANSI strings, set the CharSet member to CharSet.Ansi.

Suppose you are calling an API function named MyFunction in a DLL named MyLibrary, which takes as a parameter a structure called MyStructure. MyStructure contains a string and a fixed-length string type, declared in Visual Basic 6 as follows:

Type MyStructure
   MyStringMember As String
   MyFixedLenStringMember As String * 32
End Type

An approximately equivalent Visual Basic .NET declaration is

Structure MyStructure
   Dim MyStringMember As String
   Dim MyFixedLenStringMember As String
End Structure

The above declaration is approximate because it is missing attributes to make it fully equivalent with the Visual Basic 6 declaration. It is missing attributes on the structure to specify layout and is also missing an attribute on MyFixedLenStringMember to mark it as a fixed-length string.

Because you cannot guarantee that the .NET runtime will pass the string members in the order declared—for example, the .NET runtime may reorganize the structure in memory so that MyFixedLenStringMember comes first—you need to add marshalling attributes to the structure as follows:

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
Structure MyStructure
   Dim MyStringMember As String
   Dim MyFixedLenStringMember As String
End Structure

Visual Basic .NET does not natively support fixed-length strings. However, when you pass a structure containing a string to an API function, you can change the string member to a character array and include a MarshalAs attribute to tell the .NET runtime to pass a string of a fixed size. To do this, declare the structure member—MyFixedLenStringMember—as a Char array instead of a String. Include the UnmanagedType.ByValArray and SizeConst arguments to the MarshalAs attribute. If the structure is going to be used with any of the Visual Basic file functions—for example, if the structure serves as a record in a random access file—you need to add the VBFixedString attribute. You include or omit the MarshalAs and VBFixedString attributes depending on how the structure is used in your code. In this case, since we are passing the structure to a Windows API function, you need to add the MarshalAs attribute to the MyFixedLenStringMember structure member but omit the VBFixedString attribute, as follows:

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
Structure MyStructure
   Dim MyStringMember As String
   'Note: Change MyFixedLenStringMember to Char array.  You must
   '      pad this member to 32 characters before calling the API
   '      function.

   MarshalAs(UnmanagedType.ByValArray, SizeConst:=32) _
   Dim MyFixedLenStringMember() As Char
End Structure

In addition to the ByValArray and SizeConst attributes, the code contains a comment to warn you that the ByValArray attribute requires you to pass a string padded out to exactly the size specified by SizeConst. This means that if you want to pass 3 characters you must append 29 spaces to the end of the string to create a buffer 32 characters in length. You can use the FixedLengthString class provided in the compatibility library to create strings that are automatically padded out to the desired size. For example, if you want to set the MyFixedLenStringMember to a 32-character fixed-length string containing the characters “123,” here’s how you would do it:

Dim ms As MyStructure
ms.MyFixedLenStringMember = New VB6.FixedLengthString(32, "123")

With both the StructLayout and MarshalAs attributes in place, the structure will be passed in memory to an API function in the same way as the equivalent Visual Basic 6 Type…End Type structure is passed.

note

Note The Upgrade Wizard incorrectly upgrades fixed-length strings contained in structures. Instead of declaring the string as a Char array, the wizard declares the upgraded fixed-length string as a String. The wizard applies the ByValTStr attribute instead of the ByValArray attribute. The upgraded code will compile and run. However, there is a subtle problem that you need to be aware of: the last character in the fixed-length string buffer is set to Null when passed to an API function. If, for example, you are passing a 5-character fixed-length string (as part of a structure to an API function) containing “12345”, only “1234” will be passed. The last character, “5”, is replaced with a Null character. To fix this problem, you need to change the member type from String to Char array and change the MarshalAs attribute from ByValTStr to ByValArray. Review your code to make sure you are using the FixedLengthString class to create strings that you assign to the member variable. Keep in mind that if the structure is not passed to an API function you can remove the MarshalAs attribute, and you don’t need to review your code in this case.
ObjPtr and StrPtr Not Supported

Visual Basic 6 includes helper functions that return a 32-bit address for an object (ObjPtr) or a string (StrPtr). Visual Basic .NET does not support these functions. Moreover, it does not allow you to obtain the memory address for any type.

In cases where you must have control over underlying memory, the .NET Framework provides a pointer type known as System.IntPtr. The System.Runtime.InteropServices.Marshal class contains a number of functions that you can use to obtain a pointer type for a given type. For example, the functions AllocHGlobal, GetComInterfaceForObject, and OffSetOf all return pointers to memory. You can also use the GCHandle structure to obtain a handle to an element contained in Garbage Collector–managed memory. In addition, you can obtain a pointer to the memory by having the Garbage Collector pin the memory to a single memory location and not move it.

The following Visual Basic 6 example calls CopyMemory to copy a source string to a destination string. It uses StrPtr to obtain the memory addresses of the source and destination strings.

Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
   (ByVal lpDest As Long, ByVal lpSource As Long, _
    ByVal cbCopy As Long)

Sub Main()
   Dim sourceString As String, destString As String

   sourceString = "Hello"
   destString = "      World"

   CopyMemory ByVal StrPtr(destString), _
              ByVal StrPtr(sourceString), 10

   MsgBox destString
End Sub

Because there is no direct equivalent for StrPtr in Visual Basic .NET, you must use a different approach. To obtain a memory address to a string in Visual Basic .NET, you call GCHandle.Alloc to associate a handle with the string. Calling AddrOfPinnedObject on the handle allows you to obtain an IntPtr to the string buffer. Finally, you can obtain the memory address by calling IntPtr.ToInt32 to get the address value. It’s more work than using StrPtr, but it achieves the same result. Here is the equivalent Visual Basic .NET code for the Visual Basic 6 code just given:

Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
   (ByVal lpDest As Integer, ByVal lpSource As Integer, _
    ByVal cbCopy As Integer)

Public Sub Main()
   Dim sourceString As String, destString As String

   sourceString = "Hello"
   destString = "      World"

   Dim sourceGCHandle As GCHandle = _
      GCHandle.Alloc(sourceString, GCHandleType.Pinned)
   Dim sourceAddress As Integer = _
      sourceGCHandle.AddrOfPinnedObject.ToInt32

   Dim destGCHandle As GCHandle = _
      GCHandle.Alloc(destString, GCHandleType.Pinned)
   Dim destAddress As Integer = _
      destGCHandle.AddrOfPinnedObject.ToInt32

   CopyMemory(destAddress, sourceAddress, 10)
   sourceGCHandle.Free()
   destGCHandle.Free()

   MsgBox(destString)
End Sub


Conclusion

Visual Basic .NET embodies the spirit of the Basic language by supporting most of the Visual Basic 6 language as is and by extending the language to embrace new features such as inheritance, structured exception handling, and free threading. As this chapter has demonstrated, alternate solutions exist for each language construct that has been retired or changed in meaning. You can address each language change by adding new attributes, by creating helper functions or overloaded functions that are equivalent to the Visual Basic 6 functionality, or by using the .NET Framework to achieve equivalent behavior. Ultimately, you can take your upgraded code and make it behave as it did before. Once you have your upgraded application working, you can update it to take advantage of the expansive set of features offered by the Visual Basic .NET language and the .NET Framework.


Resolving Issues with Forms

Chapter 12
Resolving Issues with Forms

In 1988, Alan Cooper demonstrated a prototype called Ruby to Bill Gates. Ruby provided a form designer that allowed you to drag and drop controls, then known as gizmos, to quickly and easily create composite forms—such as dialog boxes, entry forms, and report forms. Microsoft took Cooper’s Ruby product and combined it with Basic to create Microsoft Visual Basic 1. Microsoft has since shipped a version of Ruby with every version of Visual Basic, versions 1 through 6. With every version, that is, until Visual Basic .NET.

Visual Basic .NET provides a new forms package called Windows Forms. Although the Windows Forms package was designed using the same basic principle as Ruby—it is a form designer that allows you to drag and drop controls and set properties—it was never meant to be an extension of, nor to be compatible with, Ruby. Therefore, there are fundamental differences between the two forms packages that affect the way you create Visual Basic applications.

This chapter focuses on some of the fundamental differences between the Ruby and Windows Forms packages. Specifically, it discusses issues that the Upgrade Wizard does not handle for you. Before we get into the differences, however, let’s look at what Windows Forms and Ruby have in common.


Similarities in Form Structure

When you create a new project in Visual Basic .NET, you will find yourself at home in the environment. The way you create and design forms is the same in Visual Basic .NET as it is in Visual Basic 6. Although the names of some of the properties, methods, and events may have changed, you should be able to quickly find the one you need to use. In fact, you will find that you can create the exact same form in both Visual Basic 6 and Visual Basic .NET, using largely the same actions, the same controls, and the same property settings.

The Upgrade Wizard can re-create your Visual Basic 6 forms in Visual Basic .NET. This is possible because the essential pieces of Visual Basic .NET are, for the most part, similar to those in Visual Basic 6: equivalent controls and equivalent properties, methods, and events. Figures 12-1 and 12-2 demonstrate a Visual Basic 6 form before and after being upgraded to Visual Basic .NET.

Figure 12-1
Visual Basic 6 form in design view.
Figure 12-2
Upgraded Visual Basic .NET form in design view.

As for the differences between forms in Visual Basic 6 and Visual Basic .NET, most of them are subtle, many related to the renaming or restructuring of components. It is these subtle differences, however, that can give you the most grief. We will spend the remainder of the chapter talking about them.


General Issues

Whether you are editing an upgraded application or writing a new Visual Basic .NET application from scratch, you will encounter certain general issues. These issues apply across forms, controls, and components. For example, you need to be aware of some basic differences between the Visual Basic 6 and Visual Basic .NET property, method, and event (PME) models. Understanding these fundamental differences should give you a good base to start from when you are trying to understand issues you encounter involving a specific form or control.

Differences in Properties, Methods, and Events

The component models for Visual Basic 6 and Visual Basic .NET are similar in the sense that objects are composed of the same pieces: properties, methods, and events. You use properties, methods, and events in exactly the same way in Visual Basic .NET as you do in Visual Basic 6. The main difference is that the specific properties, methods, and events that make up any given object do not always match up. Let’s explore some of the general PME model differences.

Some Properties, Methods, and Events Have Been Renamed

One difference between the Visual Basic 6 and Visual Basic .NET PME models is that in some cases they use different names to refer to the same thing. For example, Visual Basic .NET does not contain a Caption property. Instead, every .NET control and component uses the Text property. In many other cases, properties, methods, or events have been renamed. For example, SetFocus in Visual Basic 6 is Focus in Visual Basic .NET, and the Visual Basic 6 GotFocus and LostFocus events are known as Enter and Leave in Visual Basic .NET.

If you are upgrading an application, you do not need to be too concerned about property naming differences. The Upgrade Wizard will automatically upgrade your code to use the new names. Your challenge will be to recognize and use the renamed properties when you start to modify your upgraded project or write new code. Appendix A contains a complete mapping of all Visual Basic 6 properties, events, and methods to their counterparts in Visual Basic 6.

Multiple Properties Can Be Mapped into a Single Object Property

In some cases, the Upgrade Wizard will map more than one property to constructor parameters on a object. For example, an equivalent way of setting a form’s Left and Top properties is to use the Location property. To set the Location property, you must first create an instance of the System.Drawing.Point object and initialize System.Drawing.Point with the left and top position of the component. For example, you can replace the following Visual Basic 6 code:

Command1.Left = 10
Command1.Top = 20

with the following Visual Basic .NET code:

Button1.Location = New System.Drawing.Point(10, 20)

In a similar manner, the Visual Basic 6 Width and Height properties can be represented by the Size property in Visual Basic .NET.

note | The Left, Top, Width, and Height properties are available in Visual Basic .NET. We use Location and Size as an example because if you look in the code generated by the Windows Forms Designer in the “Windows Form Designer generated code” #Region section of the file, you will see that the designer uses the Location and Size properties to set the position and size of controls on the form. The Upgrade Wizard will automatically map properties such as Left and Top to constructor parameters on an object such as Point.
Some Properties, Methods, and Events Are Not Supported

In some cases you will find that there is no equivalent Visual Basic .NET counterpart for a particular property, method, or event in Visual Basic 6. This will be the case for one of two reasons: support is provided in a different manner, or the feature is considered out of date.

Support Available in a Different Manner

You won’t find the Visual Basic 6 properties and methods related to drawing and graphics in Visual Basic .NET. At least, you won’t find them supported in the same way. These properties and methods, such as AutoRedraw, Line, and Circle, do have equivalents in Visual Basic .NET. The equivalent functions are provided in the .NET Framework System.Drawing.Graphics class.

In addition, global objects such as Clipboard and Printer do not have matching objects in Visual Basic .NET. However, you will find a Clipboard class and a PrintDocument class in the .NET Framework that you can use to accomplish the same task. Table 12-1 provides a list of Visual Basic 6 statements and objects for which equivalent functionality can be found in the .NET Framework.

Table 12-1 User Interface Components and Their Visual Basic .NET Equivalents
Visual Basic 6 | Visual Basic .NET
Graphics statements (such as Line and Circle) | System.Drawing.Graphics class
OLE drag and drop | Windows Forms drag-and-drop properties, methods, and events to implement manual drag and drop
Clipboard object | System.Windows.Forms.Clipboard class
Printer object | System.Drawing.Printing.PrintDocument class

The Visual Basic Upgrade Wizard does not upgrade objects, properties, methods, or events for which there is no equivalent in Visual Basic .NET. It therefore will not upgrade any properties, methods, or events related to the areas listed in Table 12-1. The wizard simply preserves your Visual Basic 6 code related to these areas as is. You need to upgrade your code manually to take advantage of the equivalent functionality provided by Visual Basic .NET or the .NET Framework.

We’ll discuss graphics statements in more detail shortly. For information on how to add replacements for the Clipboard object, see Chapter 15. OLE drag and drop was discussed in Chapter 10.

Out-of-Date Features

Features such as Dynamic Data Exchange (DDE) and Visual Basic drag and drop, not to be confused with OLE drag and drop, are considered to be out of date. Neither Visual Basic .NET nor the .NET Framework supports these features. The Upgrade Wizard will preserve any Visual Basic 6 code you have written related to out-of-date objects, properties, methods, or events as is. You need to either change your code to adopt a more up-to-date technology or implement support for the technology in .NET by calling the Windows API functions that relate to the technology. For example, you can replace your DDE code with COM, or you can implement DDE support in your Visual Basic .NET application by declaring and calling the DDE-related API functions. The next section talks in more detail about ways to move code that uses out-of-date technologies to Visual Basic .NET.

Technology Differences

Since its inception in 1991, the focus of Visual Basic has been to enable Windows technologies for the developer. It allows you to quickly create Windows applications that use data, graphics, COM, and a host of other technologies. At the same time, Visual Basic has emphasized compatibility. This means that Visual Basic support—language statements and object models—for various technologies has accumulated over the years. Visual Basic 6 supports all technologies, regardless of their relevance in today’s world. Objects and statements to support old technology coexist alongside the new: DDE with COM and the data objects DAO and RDO with ADO.

Visual Basic .NET, on the other hand, starts anew, wiping the technology slate clean. Visual Basic .NET and the .NET Framework provide first-class .NET support for the latest technologies, such as the .NET component model and ADO.NET. You can still use some of the existing technologies via COM interop, but you will not find .NET classes representing existing technologies such as ADO and DDE. Visual Basic .NET and the .NET Framework also offer renovated object models for technologies such as graphics and forms.

What does this all mean to you? It means that the Visual Basic code you write to take advantage of a particular technology is different from before. New .NET component models represent these technology areas. In many cases, you will find that you can accomplish the same tasks as before, but you will need to use different components, properties, methods, and events to do so. This section discusses changes to technology areas related to components and forms.

Graphics

Visual Basic 6 supports a number of graphics methods on the form, such as Line and Circle, and it supports PictureBox and Image controls that allow you to draw lines, circles, and text on a form or control. Visual Basic .NET does not support any methods on the form or PictureBox controls that let you draw. Instead you must respond to the Paint event, where you are passed a System.Drawing.Graphics object. You can use the System.Drawing.Graphics object and related objects to draw exactly as you have drawn before.

The Visual Basic .NET Upgrade Wizard does not automatically upgrade your code to respond to the Paint event because the model for drawing in Visual Basic 6 is dramatically different from that in Visual Basic .NET. Visual Basic 6 allows you to call a drawing method from any event or function in your code. Visual Basic .NET, on the other hand, requires any drawing code to be located in the Paint event or in subroutines called from the Paint event. It is therefore impossible for the wizard to pull calls to drawing methods, scattered about in various parts of your code, and arrange the code so that it will continue to work. Even if the wizard were able to pull off such a feat, you probably wouldn’t recognize the resulting code. Therefore, the wizard leaves the calls to the Visual Basic 6 graphics methods in your upgraded Visual Basic .NET code as is. You need to manually port your graphics-related code to use the .NET equivalent graphics functions given in Table 12-2. For examples of manually upgrading Visual Basic 6 graphics statements such as Line and Circle to Visual Basic .NET, see Chapter 15.

Table 12-2 .NET Framework Equivalents for Visual Basic 6 Graphics Statements
Visual Basic 6 | Visual Basic .NET
Circle | System.Drawing.Graphics.DrawEllipse method
Cls | System.Drawing.Graphics.Clear (color) method
CurrentX | X parameter to various graphics methods, such as System.Drawing.Graphics.DrawString
CurrentY | Y parameter to various graphics methods, such as System.Drawing.Graphics.DrawString
DrawMode | Attributes of System.Drawing.Pen passed to a Draw method on System.Drawing.Graphics
DrawStyle | Attributes of System.Drawing.Pen passed to a Draw method on System.Drawing.Graphics
DrawWidth | Width property of System.Drawing.Pen passed to a Draw method on System.Drawing.Graphics
FillColor | One of the colors contained in System.Drawing.Brushes passed to either System.Drawing.Graphics method FillEllipse or FillRectangle
FillStyle | HatchBrush class and HatchStyle enumeration in the System.Drawing.Drawing2D namespace
HDC | System.Drawing.Graphics.GetHdc method
Image | System.Drawing.Image class
Line | System.Drawing.Graphics.DrawLine method
PaintPicture | System.Drawing.Graphics.DrawImage method
Point | System.Drawing.Bitmap.GetPixel method
Print | System.Drawing.Graphics.DrawString method
PSet | System.Drawing.Bitmap.SetPixel method
TextHeight | System.Drawing.Graphics.MeasureString method
TextWidth | System.Drawing.Graphics.MeasureString method
Dynamic Data Exchange

Dynamic Data Exchange (DDE) was at one time one of the few ways you could pass data between applications without using a shared file. Visual Basic support for DDE dates back to Visual Basic 1, which included support for DDE on Windows 3. Visual Basic support for DDE, and the underlying Windows support for DDE, has remained largely unchanged since 1990. DDE, however, has since been replaced by COM.

COM allows you to share data and invoke methods between applications in a more efficient and scalable manner. Since most Windows applications that expose public data and methods do so via COM, DDE is generally not the primary way that you retrieve public data and invoke public methods on a Windows application. You use COM as a means of communicating with most Windows applications.

After upgrading your application to Visual Basic .NET, it is highly recommended that you replace all DDE communication with COM. For example, if you have code that calls LinkExecute to invoke a method on a DDE server, look at the documentation or type library for the server and see whether there is a COM equivalent for the same method. If there is, add a COM reference to the Windows application and write Visual Basic code to call the method.

Neither Visual Basic .NET nor the .NET Framework provides support for DDE. If you absolutely must communicate with another application using DDE, you will need to implement DDE in your Visual Basic .NET application in one of two ways: by creating and interoperating with an ActiveX EXE server built in Visual Basic 6 that manages the DDE conversation or by declaring and implementing the DDE-related Windows API functions.

If you want to reuse your Visual Basic 6 DDE code, you can create a Visual Basic 6 ActiveX EXE project and add a public class to exchange the DDE-related information you need with your Visual Basic .NET application. For example, if you want to perform a DDE LinkExecute operation in your Visual Basic .NET code, you create a DDE helper class written in Visual Basic 6 as follows:

  1. Create a Visual Basic 6 ActiveX EXE project.

  2. Add a public method to Class1 called LinkExecute that takes two parameters: ServerTopic and ExecCommand.

  3. Add a form—called Form1—to the ActiveX EXE project.

  4. Add a TextBox—called Text1—to the form.

  5. Add the following code to the LinkExecute method in Class1:

    Dim f As New Form1

f.Text1.LinkMode = 0                'None
f.Text1.LinkTopic = ServerTopic
f.Text1.LinkMode = 2                'Manual
f.Text1.LinkExecute ExecCommand

  6. Build the ActiveX EXE server as Project1.dll.

To perform the LinkExecute operation in Visual Basic .NET, add a COM reference to Project1.dll to your Visual Basic .NET project References list and enter the following code:

   Dim DDEClientHelper As New Project1.Class1Class()
   DDEClientHelper.LinkExecute("MyDDEServer│MyTopic", "MyCommand")

If you want to implement DDE in your Visual Basic .NET application by calling the DDE-related API functions, you will, at a minimum, need to declare and call DdeInitialize and DdeUninitialize. To call DdeInitialize, you need to implement DdeCallbackProc. However, this is just a start. You will also need to call a number of other DDE-related API functions to establish a connection and send data. To implement functionality to perform LinkExecute, for example, requires calling at least eight other DDE-related API functions. This task also requires a deep understanding of Windows messaging architecture and memory management, not to mention a knowledge of how to represent Windows types—such as handles and structures—in Visual Basic code. This undertaking is not for the faint of heart. To learn more about the DDE-related Windows API, search for “Dynamic Data Exchange Management Functions” in the Microsoft Developer Network (MSDN). For more information on issues related to using Declare statements in your code, refer to Chapter 11.

Visual Basic Drag and Drop

Visual Basic drag and drop has been available since Visual Basic 1. This feature allows you to drag and drop a control at run time onto a form or onto any other control on a form. The result of the drag-and-drop action depends on the code you write for the DragDrop event for a form or control. For example, you could write code for a CommandButton that would check to see whether the control being dropped was a TextBox and, if so, copy the TextBox text to the CommandButton caption. At run time, you drag and drop the TextBox onto the CommandButton, and the button caption changes to the TextBox text.

Although Visual Basic drag and drop is useful in creating a rich user interface, it has some serious limitations. Its functionality is limited to dragging and dropping forms within the same application; you cannot drag and drop between applications. In addition, if you set DragMode to automatic, there is no result for the drag-and-drop operation unless you write code to define the drop action. This means that you need to write code in the DragDrop event for the form and all controls that are to support drag and drop. Finally, when a control is dragged over a particular form or control, there is no concept of a no-drop icon. You need to write code in the DragOver event and set the DragIcon property to a no-drop icon for all controls that you do not want to allow to be dropped.

Visual Basic OLE drag and drop was introduced in Visual Basic 5. It provides several advantages over Visual Basic drag and drop. First, OLE drag and drop is a COM standard, meaning that you can drag and drop data between any applications that support OLE drag and drop and the data that you are dragging. In addition, OLE drag and drop is automatic. You can enable it by setting the OLEDragMode and OLEDropMode properties of the form and controls that you want to have support OLE drag and drop. You are not required to write any code to enable basic drag-and-drop operations. Finally, in OLE drag and drop, the concept of a no-drop icon is built in. This means that by default the form and controls do not advertise themselves as targets of a drag-and-drop operation. You set the OLEDropMode property or write code for the controls that you want to have support OLE drag and drop.

Because Visual Basic drag and drop is considered an outdated feature, the Upgrade Wizard does not upgrade your DragDrop-related code. Instead, it preserves the Visual Basic 6 drag-and-drop code as is in the upgraded Visual Basic .NET project. The code will not compile, so you need to either delete it and remove the drag-and-drop functionality or change the code to use OLE drag and drop. For information on how to implement drag and drop in Windows Forms, see Chapter 10.


Issues Involving Forms

Let’s start our discussion of forms by looking at the differences between a Visual Basic 6 form and a Visual Basic .NET form. Fortunately, these differences are relatively minor. The basic concept of a form, for example, is the same between Visual Basic 6 and Visual Basic .NET. The form serves as a surface where you can place controls to create a composite form. The way in which you create a form, set properties, and add controls to it using the Visual Basic .NET designer is also essentially the same as in Visual Basic 6. You should find yourself at home when you create and edit Visual Basic .NET forms.

Event Firing Differences

A subtle difference between Visual Basic 6 and Visual Basic .NET is that some events do not fire in the same relative order. For example, in Visual Basic 6 the Load event is the first event you expect to fire. This is not true for Visual Basic .NET, however, where the Resize event occurs before the Load event. Also, depending on the control properties you set at design time, a number of other events can occur before the Load event. Table 12-3 lists the events that can occur before the Form.Load event.

Table 12-3 Visual Basic .NET Events That Can Occur Before the Form.Load Event
Object | Event
Form | Move
Form | Resize
CheckBox | Click
ComboBox | Change
OptionButton | Click
TextBox | TextChanged

Problems generally occur when you write your code in such a way that code in one event relies on values or objects being initialized or set in another event. For example, suppose that you create an element of a control array in the Form.Load event and write code to position the control array element in the Form.Resize event. If the Resize event fires after the Load event, everything works as planned. If, on the other hand, the Resize event fires before the Load event, an error will occur in your Resize code. The reason is that you will be attempting to access a control array element that does not yet exist.

To illustrate this problem, consider the following code for a Visual Basic 6 standard EXE application, in which Form1 contains a CommandButton with the Index property set to 0.

Private Sub Form_Load()
   ' Load a new element of the Command1 control array
   Load Command1(1)
   Command1(1).Visible = True
End Sub

Private Sub Form_Resize()
   ' Position control array element 1 below control array element 0
   Command1(1).Top = Command1(0).Top + Command1(0).Height + 3
End Sub

If you use the Upgrade Wizard to upgrade the application to Visual Basic .NET, the upgraded code will be as follows:

Private Sub Form1_Load(ByVal eventSender As System.Object, _
                       ByVal eventArgs As System.EventArgs) _
                       Handles MyBase.Load
   ' Load a new element of the Command1 control array
   Command1.Load(1)
   Command1(1).Visible = True
End Sub

'UPGRADE_WARNING: Event Form1.Resize may fire when form is initialized.
Private Sub Form1_Resize(ByVal eventSender As System.Object, _
                         ByVal eventArgs As System.EventArgs) _
                         Handles MyBase.Resize
   ' Position control array element 1 below control array element 0
   Command1(1).Top = _
      VB6.TwipsToPixelsY(VB6.PixelsToTwipsY(Command1(0).Top) + _
      VB6.PixelsToTwipsY(Command1(0).Height) + 3)
End Sub

When you run the upgraded Visual Basic .NET application, an error occurs on the assignment of Command1(1).Top. The error is “Control array element 1 doesn’t exist.” It occurs because the Resize event takes place before the Load event.

You can fix this problem, and others like it, by using a class-level Boolean variable to keep track of whether the Load event has been executed. If it has not executed and is not in the process of executing, you should not execute any code in the current event. In the example just given, you can declare a new form-level variable called m_Loaded as follows:

Private m_Loaded As Boolean = False

In the Form1_Resize event procedure, check m_Loaded and do not execute any code unless it is True:

If m_Loaded Then
   ' Position control array element 1 below control array element 0
   Command1(1).Top = _
      VB6.TwipsToPixelsY(VB6.PixelsToTwipsY(Command1(0).Top) + _
      VB6.PixelsToTwipsY(Command1(0).Height) + 3)
End If

In the Form1_Load event procedure, set m_Loaded to True as follows:

m_Loaded = True

' Load a new element of the Command1 control array
Command1.Load(1)
Command1(1).Visible = True

Because the Resize event fires after the Load event in Visual Basic 6, you need to emulate this behavior in Visual Basic .NET. You do this by calling the Resize event before exiting the Form1.Load event procedure, as follows:

m_Loaded = True

' Load a new element of the Command1 control array
Command1.Load(1)
Command1(1).Visible = True

' Emulate Visual Basic 6 behavior by invoking the Resize event
Form1_Resize(Me, New System.EventArgs())
Heed Upgrade Warnings

The Form1_Resize event procedure for the upgraded Visual Basic .NET code includes the comment “UPGRADE_WARNING: Event Form1.Resize may fire when form is initialized.” The Upgrade Wizard includes this type of warning for any upgraded event that is known to fire before the Load event. To be safe in situations such as this, you may want to implement the same types of changes that we applied to the Resize event in the previous example.

The Default Form: DefInstance

Visual Basic 6 supports the notion of a default form. A default form is the default instance of the form that Visual Basic creates for you automatically. Having a default form instance allows you to write code such as the following:

Form1.Caption = "Caption for my default form"

Visual Basic .NET does not support a default form instance. If you write the equivalent code in Visual Basic .NET, as follows:

Form1.Text = "Caption for my default form"

you will run smack into a compiler error informing you that there is no shared member called Text on Form1. One way to solve this problem is to add a shared method to your form that returns the default instance—in other words, the first created instance—of the form. For example, for a default Visual Basic .NET Windows application project, you could add the following code to Form1:

Public Shared m_DefInstance As Form1
Public Shared ReadOnly Property DefInstance() As Form1
   'If no form instance exists, create one
   'Note: if the form is the startup form it will
   'already exist
   If m_DefInstance Is Nothing Then
      m_DefInstance = New Form1()
   End If
   Return m_DefInstance
End Property

This code sets up the DefInstance property but does not initialize the m_DefInstance variable to the first form instance created. To accomplish this initialization, you need to initialize the variable in the Sub New constructor for the form. The Sub New constructor exists on every form and can be found in the #Region code block labeled “Windows Form Designer generated code.” Expand the #Region code block and insert the following assignment in Sub New at the end of the subroutine:

If m_DefInstance Is Nothing Then
   ' Initialize m_DefInstance to the first form created
   m_DefInstance = Me
End If

Now you can write code that accesses the default instance of a form as follows:

Form1.DefInstance.Text

The default form is an example of a Visual Basic 6 semantic that does not exist in Visual Basic .NET. However, as we have demonstrated here, you can write code to cause your Visual Basic .NET forms to adopt the same semantic.

It is important to understand the purpose of the DefInstance property because you will likely see it used in your upgraded application. It is used when you access properties or controls using the name of the form. The Upgrade Wizard automatically implements the DefInstance shared method on all upgraded forms. However, the implementation that the wizard applies to your upgraded form is much more complicated than the simple example given here. The generated code takes into account whether the form is a startup or not. It also contains logic to initialize the default instance variable correctly when the form is a multiple document interface (MDI) form or an MDI child form. You can examine and even modify the code that the wizard generates. The DefInstance-related code is generated within the #Region blocks “Windows Form Designer generated code” and “Upgrade Support.”

Application Lifetime and Forms

Chapter 10 introduced differences in application lifetime between Visual Basic 6 and Visual Basic .NET. Let’s look into this area in more detail. An application in Visual Basic 6 application terminates when the last form is unloaded. A Visual Basic .NET application, on the other hand, terminates when the startup form associated with the application is unloaded. This means that your Visual Basic .NET application can terminate when one or more forms is still showing.

note | The way you specify the startup form in Visual Basic .NET is similar to the way you do so in a Visual Basic 6 application. To set the startup form in Visual Basic .NET, right-click the project name in the Solution Explorer and select Properties from the shortcut menu. You are presented with the project settings dialog box, which groups various settings under a number of categories. Select the General category under Common Properties, and choose the desired startup form from the Startup Object list. Figure 12-3 shows where you can find the Startup Object list.
Figure 12-3.
Startup Object list in the Visual Basic .NET project settings dialog box.

When you create a new Visual Basic .NET application or upgrade a Visual Basic 6 application, the lifetime of the startup form determines the lifetime of the application. If you unload the startup form, the application terminates no matter what other forms are currently open. If the startup form is your main form, this termination will most likely not be an issue. If, on the other hand, the form that determines the application lifetime is a secondary form, such as an MDI child form, you will probably need to make some changes to your application.

The simplest way to deal with this issue is to identify a form in your application that you consider to be the main form. For example, if your application is composed of an MDI form with a number of MDI child forms, you would identify the MDI form as the main form. You should set this form as the startup form for the application.

If you want your application to terminate when the last form is closed, you need to write code that manages the lifetime of your application. Let’s look at a Visual Basic 6 application that has two forms: Form1 and Form2, in which Form2 is shown from Form1’s Load event. In Visual Basic 6, the application terminates when you close both forms, regardless of the order in which you close them. Form1 contains the following code in its Form_Load event procedure to show Form2:

Private Sub Form_Load()
   Form2.Show
End Sub

If you upgrade the project to Visual Basic .NET, the application will terminate as soon as you close Form1, even if Form2 is still showing. Suppose you want to change the behavior of the Visual Basic .NET application so that it behaves like the Visual Basic 6 application. The reason Form1 determines the lifetime for the Visual Basic .NET application is that the compiler includes an invisible piece of code that starts up your application and shows the startup form. The code the compiler includes is

System.Windows.Forms.Application.Run(New Form1())

Application.Run is a .NET Framework function that loops internally processing messages and waits for the form instance that is passed to it to terminate. If you do not want your application to be associated with any particular form, you can substitute a version of Application.Run that takes no arguments as follows:

System.Windows.Forms.Application.Run()

This version loops until you call Application.Exit from somewhere in your program. You can control the execution of your application by calling this version of Application.Run from Sub Main. For example, you could show Form1 and then call Application.Run from Sub Main as follows:

Sub Main()
   Form1.DefInstance.Show()   ' Equivalent to Form1.Show
                              ' in Visual Basic 6
   System.Windows.Forms.Application.Run()
End Sub

Create Sub Main in a new code module called Module1, and then set Module1 as the startup object for the project.

Since the application is no longer tied to the lifetime of any particular form, you need to add code that calls Application.Exit when the last form is unloaded. One way to do this is to have each form increment a global form count every time a form instance is created and then decrement the form count when the instance is destroyed. When the application form count is decremented and reaches 0, meaning that no more forms are loaded, call Application.Exit. To increment and decrement the global form count, you can add two functions to Module1 called AddForm and RemoveForm. You also need to declare a module-level variable that will keep track of the form count. Here is an example of the code you need to add:

Dim FormCount As Integer
Sub AddForm()
   FormCount += 1  'Equivalent to FormCount = FormCount + 1
End Sub

Sub RemoveForm()

   FormCount -= 1  'Equivalent to FormCount = FormCount - 1

   'At least one form remains, keep going
   If FormCount > 0 Then
      Exit Sub
   End If

   'No more forms – end the application
   'Note: Make sure this call is the last statement in the Sub
   'System.Windows.Forms.Application.Exit()

End Sub

You then need to add calls to AddForm and RemoveForm to both Form1 and Form2. You can add the call to AddForm to the Form_Load event procedure and a call to RemoveForm to the Form_Closed event procedure. Here is the resulting code for Form1:

Private Sub Form1_Load(ByVal eventSender As System.Object, _
                       ByVal eventArgs As System.EventArgs) _
                       Handles MyBase.Load
   AddForm()
   Form2.DefInstance.Show()
End Sub

Private Sub Form1_Closed(ByVal sender As Object, _
                         ByVal e As System.EventArgs) _
                         Handles MyBase.Closed
   RemoveForm()
End Sub

Here is the code you need to add to Form2:

Private Sub Form2_Load(ByVal sender As System.Object, _
                       ByVal e As System.EventArgs) _
                       Handles MyBase.Load
   AddForm()
End Sub

Private Sub Form2_Closed(ByVal sender As Object, _
                         ByVal e As System.EventArgs) _
                         Handles MyBase.Closed
   RemoveForm()
End Sub

As long as Module1 is set as your startup object, the application will behave like the Visual Basic 6 application. You can close Form1 and Form2 in any order. When the last form is closed, Application.Exit is called and the application terminates.

MDI Forms

Both Visual Basic 6 and Visual Basic .NET support MDI parent and child forms. The difference between the two environments lies in how you create the MDI parent form and show the MDI child form. To create an MDI application in Visual Basic 6, you add a special MDIForm to your application to act as the MDI parent form. Visual Basic .NET has no concept of an MDIForm as a separate form type. Instead, you set the IsMdiContainer property of a Visual Basic .NET form to True.

In Visual Basic 6, you specify MDI child forms by setting the MDIChild property of a form to True. When the MDI child form is shown, it always displays within the MDI parent form. Visual Basic .NET does not work this way. In Visual Basic .NET, there is no MDIChild property. Instead, you must write code to set the MdiParent property of a form to the MDI parent form. For example, if you want Form2 to be an MDI child form of MDI parent Form1, you need to write the following code:

Dim MyForm2 As New Form2
MyForm2.MdiParent = Me
MyForm2.Show

In the code example given here, assume that the code is written in the module for Form1. For example, we can include this code in the Form1_Load event procedure to show the MDI child Form2 from Form1.

Lifetime of MDI Form Applications

As we discussed in the previous section, the lifetime of a Visual Basic .NET application is tied to the startup form. The same is true of MDI form applications, but with an interesting twist. Visual Basic 6 allows you to specify an MDI child form as the startup form in an MDI form application. Specifying an MDI child form as the startup form has the advantage that you don’t have to write any code to show the MDI parent and MDI child form by default. When the MDI startup child form is shown, it checks to see whether the MDI parent form is showing. If it is not, the MDI parent is shown automatically and the MDI child form is displayed within the MDI parent.

If you upgrade an application in which an MDI child form is the startup form, you will find that it will terminate when you close the MDI child form. To prevent this, you need to specify the MDI parent form, not the child form, as the startup form.


Conclusion

This chapter has covered the general types of differences between Visual Basic 6 and Visual Basic .NET that you will encounter. For example, it looked at how property model differences and differences in technology play out in your upgraded Visual Basic .NET application. In addition, it looked at how form-related issues such as event ordering and lifetime can lead to subtle differences in the execution of your upgraded application. We have offered solutions to help you move your Visual Basic 6 code to Visual Basic .NET. Solutions are available even when the technology is no longer supported or the components and language statements that provide the support have changed drastically.

The characteristics that make up a Visual Basic form have remained largely constant since the inception of Visual Basic in 1988. The form is based on a Windows window in which a form designer allows you to drag and drop controls and set properties. The way you create a form and the behavior you expect from your form in a compiled application have also remained unchanged over the years. The Visual Basic .NET form is no exception. You create and interact with Visual Basic .NET forms in pretty much the same way as you do Visual Basic 6 forms. Differences become noticeable when you start using Visual Basic .NET forms on a more granular level. For example, properties do not always jive between Visual Basic 6 and Visual Basic .NET.

Figuring out the mapping between Visual Basic 6 and Visual Basic .NET properties is a matter of learning such details as that Caption always maps to Text or that OLEStartDrag maps to DoDragDrop. Most of these mappings can be looked up or memorized from a table. Appendix A of this book provides such a table. It should serve as your technical reference for mapping Visual Basic 6 objects and statements to their Visual Basic .NET equivalents.


Upgrading ActiveX Controls and Components

Chapter 13
Upgrading ActiveX Controls and Components

If you’re on old hand at Microsoft Visual Basic programming, you likely have vivid memories of the last great migration—the one between Visual Basic 3 and 4. It involved the great leap forward from 16 bits to 32 bits and the introduction of OLE components now known as ActiveX components.

The transition from Visual Basic 6 to Visual Basic .NET is reminiscent of those days, but on a much grander scale. Once again Microsoft is introducing a new component model to open doors to a whole new world of applications: Web applications and XML Web services. This new component model is the foundation for the .NET Framework.

This is all fine and wonderful, you might say, but are you kidding me? Do I need to update all of my ActiveX controls to .NET controls, just as I was required to replace all of my VBXs in Visual Basic 4 32 bit? Fortunately, the answer is no to both questions. You can still use your existing ActiveX components with Visual Basic .NET.


ActiveX Controls Are Still Supported?Yes!

ActiveX Controls Are Still Supported—Yes!

One difference between the transition from Visual Basic 6 to .NET and that from Visual Basic 3 to 4 is that Visual Basic .NET still supports the old component model. This was not true of Visual Basic 4. Migrating your VBX components to ActiveX was an all-or-nothing proposition. If you wanted to continue to use VBXs, you had to keep your application in the 16-bit world. If the vendor for your favorite control did not have a 32-bit ActiveX control replacement for your favorite VBX control, you were out of luck. You had to either wait it out and hope the vendor released a compatible control or use an ActiveX component provided by a different vendor. In any case, you were likely to run into issues when upgrading your application, whether the ActiveX component claimed full compatibility or not.

In Visual Basic .NET, you have the choice of using either an ActiveX component or an equivalent .NET component in its place. Unlike Visual Basic 4, which had a control migration feature that automatically upgraded your VBX controls to ActiveX controls, Visual Basic .NET has no such feature. Furthermore, Microsoft is not introducing any of the new .NET controls and components as direct replacements of similarly featured ActiveX controls. For example, even though there is a .NET TreeView control, it is not fully compatible with the ActiveX TreeView control. In most cases, you will need to make modifications to your code to use the new .NET controls. We’ll discuss how to replace ActiveX controls with Windows Forms controls in Chapter 19. For now, let’s concentrate on using ActiveX controls in Visual Basic .NET.

ActiveX Upgrade Strategy

Since one of the overriding goals of the Upgrade Wizard is to ensure compatible behavior after you upgrade your application to Visual Basic .NET, the Visual Basic team decided to allow your project to use the same ActiveX controls and components as before. There are a couple of exceptions to this rule, but in general your Visual Basic .NET application will run against the same set of ActiveX components after the upgrade. This strategy greatly increases the chances that your application will perform exactly as it did in Visual Basic 6. Once you are assured that everything is behaving the way you expect it to, you can replace ActiveX controls and components with equivalent .NET components as you see fit.


Limitations of ActiveX Control Hosting

If you have ever used an ActiveX control in an environment outside Visual Basic, such as Microsoft Internet Explorer or Microsoft Office, you might have encountered differences or limitations in behavior for that control. Likewise, a .NET Windows form is a unique control host environment with its own capabilities and limitations. Since a .NET Windows form is primarily the host container for .NET components, some interesting magic is cast on an ActiveX control to make it act as if it were a .NET component. Unfortunately, this magic has limited power and cannot be applied to all types of ActiveX controls. Therefore the Windows Forms environment offers no support for the following types of ActiveX controls and components:

  • Container controls

  • Windowless controls

  • DAO-based data-bound controls

  • Controls that make use of internal Visual Basic 6 interfaces

  • Components that hook into the Visual Basic 6 extensibility model

  • ActiveX designers

We discuss each of these in turn in the sections that follow.

Container Controls

The SSTab ActiveX control is an example of a container control. Although you can place an SSTab control on a Windows form, you cannot place other controls within the SSTab. Part of the problem is that the SSTab control needs to communicate with the controls placed inside it. The communication happens using ActiveX interfaces that are not supported by a .NET Windows form. The end result is that the SSTab cannot find the child controls. Moreover, it cannot associate each child control with the tab to which it belongs. The ability to display contained controls on separate tabs—an essential feature of the SSTab control—is lost.

note | The Visual Basic .NET Upgrade Wizard automatically replaces each instance of the SSTab control with a .NET Windows TabControl. This is one of the few cases in which the Upgrade Wizard replaces an ActiveX control with an equivalent .NET control. Since the SSTab control is dead weight on a Windows form, the Visual Basic team decided to give you a head start. You will find that all of the child controls on each SSTab tab are moved to the appropriate TabControl tab. You are left to focus on changing top-level properties of the TabControl and tweaking some code. Otherwise, you would be spending hours meticulously re-creating and placing each child control on each TabControl tab exactly as it appeared in the SSTab control.
Windowless Controls

The Windows Forms environment provides support only for controls that display within a standard window. To support windowless controls, a host needs to support painting the control directly within the form window. One nice feature is that windowless controls can be made transparent, since the host manages the painting for the control and the form on the same window surface. A side effect of Windows Forms not supporting windowless controls is that you cannot create transparent labels, as you could in Visual Basic 6.

In most cases the lack of support for windowless controls will not prohibit you from placing a windowless control on a Windows form. Most windowless controls have a split personality of being either windowed or windowless, depending on the capabilities of the host. If the host supports windowless controls, the control will take on its preferred windowless form. If the host does not support windowless controls, the control will take on its less desirable, but workable, windowed appearance. In the case of Windows Forms, all windowless controls will be asked to create a window and render as a windowed control.

DAO-Based Data-Bound Controls

DAO-based data-bound controls include the Visual Basic 5 DBGrid and RDO Data control. Visual Basic 6 links these controls to their data source using an internal data binding manager object. Visual Basic .NET contains no such internal data binding manager object and so provides no such support for DAO-based controls. Therefore, you will find that you cannot bind a DBGrid control to an RDO Data control in Visual Basic .NET.

ADO data binding works just fine. The data binding manager object for ADO is contained in an ActiveX component called MSBind. This component enables you to bind ADO components such as the Visual Basic 6 DataGrid to ADO data sources such as an upgraded DataEnvironment class.

Controls That Make Use of Internal Visual Basic 6 Interfaces

A limitation of the ActiveX architecture is that it does not provide a standard mechanism permitting controls to find one another on the same form or document. It is left up to the host to define interfaces that allow controls to hook up with one another. Visual Basic 6 defines interfaces such as IVBGetControl that allow ActiveX controls to find their parent, children, or siblings. Visual Basic .NET does not provide full support for these interfaces. As a result, controls that depend on other controls will not work properly. For example, the UpDown ActiveX control becomes more or less a paperweight in the Visual Basic .NET environment. You can attempt to buddy the control to another control, but you will find that the values for the buddy control will not update when you click the UpDown control. In this case, it is recommended that you use the .NET Framework UpDown control instead.

Components That Hook into the Visual Basic 6 Extensibility Model

Don’t expect to be able to use a Visual Basic 6 add-in component in the Visual Basic .NET environment. Add-in components such as wizards generally interact with the development environment to create controls, change code, and update project settings. The component interacts with the environment through a set of extensibility interfaces. The problem is that Visual Basic .NET supports a completely different set of extensibility interfaces than Visual Basic 6 does. Visual Basic 6 add-in components simply will not work in the new environment.

ActiveX Designers

ActiveX designers such as the DataEnvironment, WebClass, DHTML, and DataReport Writer are not supported in Visual Basic .NET. The Microsoft Visual Studio .NET environment supports a completely different approach to designers known as packages. None of these designers are provided as Visual Studio .NET packages. In the case of the DataEnvironment and WebClass designers, the Upgrade Wizard will upgrade your Visual Basic 6 DataEnvironment and WebClass projects to take advantage of run-time support classes for these designers. This means that your projects based on DataEnvironment and WebClass can be made to work at run time, but you will not get a fancy designer that you can use to change settings at design time. All settings must be changed by writing code.


ActiveX .NET Controls: Best of Both Worlds

When you place an ActiveX control on a Windows form, something interesting happens. The control becomes part ActiveX, part .NET—something we call an ActiveX .NET control. The ActiveX portion is the core control that defines the behavior of the control you know and love. The .NET part is a translation layer that is wrapped around the control to help make it fit in with other .NET controls and objects. This section describes how ActiveX controls are supported in .NET.

ActiveX Interop Ax Wrapper: The Windows Forms Wrapper

For Windows Forms to be able to position and display your ActiveX control, additional properties, events, and methods are added to the control. You can think of these as extended properties, methods, and events (PMEs) of your control. Figure 13-1 illustrates how an ActiveX control is extended to include properties such as Location, Tag, and Visible. These extended PMEs are combined with the public PMEs of your ActiveX control to form a new wrapper class. When you write code against the control, you are actually writing it against the wrapper class. The wrapper class in turn delegates to the ActiveX control. If you are setting a public property that is available on the ActiveX control, the wrapper class simply passes the property setting straight through to the control. If you are setting an extended property not found on the ActiveX control, the wrapper takes care of performing the operation on the control, such as toggling visibility or changing its size.

Figure 13-1
An Ax wrapper encapsulates and extends an ActiveX control.

The Ax (short for ActiveX) wrapper is officially known as the Windows Forms Wrapper (WFW). It is called this because the wrapper is specifically generated when the control is placed on a Windows form. If you place an ActiveX control on any other type of .NET form, such as a Web form, the Ax wrapper is not generated.

The concept of a wrapper class is not new. Visual Basic 6 wraps ActiveX controls in a similar fashion. However, the way controls are wrapped in the two environments is quite different. This leads to a number of interesting issues when the code is hosted on a Visual Basic .NET form.

note | If you create a new Visual Basic .NET Windows application project and place an ActiveX control on the form, the control name is prefixed with Ax. For example, if you place an ActiveX TreeView control on the form, the resulting name of the control is AxTreeView. As the name implies, when you write code against AxTreeView you are actually programming against the Ax wrapper class for the control, not the control itself.
Property and Parameter Type Mappings

One purpose of the Windows Forms ActiveX control wrapper is to expose ActiveX properties in such a way that they can be naturally assigned to other .NET component properties. For example, in Visual Basic 6, the Picture property is based on the StdPicture object, which implements the IPicture and IPictureDisp interfaces. In Visual Basic .NET, however, the Picture property for .NET components is type System.Drawing.Image. The problem is that you cannot directly assign a property of type StdPicture to a System.Drawing.Image property. To help alleviate this problem and avoid nasty-looking conversion code around every property assignment, the wrapper automatically exposes your ActiveX control’s Picture property as System.Drawing.Image. Figure 13-2 illustrates how the Ax wrapper exposes an ActiveX property such as Picture as a .NET type such as System.Drawing.Image.

Figure 13-2
Picture property translated to System.Drawing.Image.

In a similar manner, the Windows Forms Ax wrapper exposes other common ActiveX properties, such as Font and Color, as the respective .NET type. Table 16-1 lists the ActiveX property types that the Windows Forms Ax wrapper exposes as the appropriate .NET type.

Table 16-1 Ax Wrapper Mapping of ActiveX Types to .NET Types
ActiveX Control Property Type | Maps to Ax Wrapper Property Type
OLE_COLOR | System.Drawing.Color
Font | System.Drawing.Font
Picture (bitmap) | System.Drawing.Image

Take, for example, the following Visual Basic 6 code, which assigns a Picture to the Picture property of a ListView ActiveX control:

ListView1.BackColor = vbRed
Set ListView1.Picture = _
   LoadPicture(Environ("WINDIR") & "\Prairie Wind.bmp")
Set Picture1.Picture = ListView1.Picture

The code is upgraded to the following Visual Basic .NET code:

ListView1.BackColor = System.Drawing.Color.Red
ListView1.Picture = _
   System.Drawing.Image.FromFile(Environ("WINDIR") & _
   "\Prairie Wind.bmp")
Picture1.Image = ListView1.Picture
Variant and Object Types: Mapping—Not!

As you can see, you can assign native .NET types such as System.Drawing.Color.Red or System.Drawing.Image to ActiveX control properties such as BackColor or Picture, respectively. This strategy works great when the ActiveX control property or method parameter is strongly typed. In this case Windows Forms sees that the property type for BackColor is OLE_COLOR and the type for Picture is IPictureDisp. Seeing these types, Windows Forms automatically maps the properties to their .NET equivalents when creating the wrapper. What happens when Windows Forms cannot tell the property type? For example, what happens when a property or method parameter is of type Variant or Object?

If an ActiveX control property or method parameter is not strongly typed—that is, if it is a generic type such as Variant or Object—the type is mapped to a .NET Object type in the wrapper. This commonly occurs when the ActiveX control exposes a property or a method that contains optional parameters. In order for a parameter to be optional it must also be of type Variant. If, for example, you are calling an ActiveX control method that takes an optional Picture, Color, or Font, you must explicitly pass the underlying ActiveX type as the parameter value. This means that you will need to convert a .NET type to the equivalent ActiveX type before making the call. Doing so can lead to some ugly-looking, but necessary, code.

Consider the following Visual Basic 6 code, which adds an image to an ActiveX ImageList control:

ImageList1.ListImages.Add , , _
   LoadPicture(Environ("WINDIR") & "\Zapotec.bmp")

If you right-click the line of code in Visual Basic 6 and choose Object Browser and then navigate to the ListImages type, you will see the following declaration:

Function Add([Index], [Key], [Picture]) As ListImage

Each parameter contained in square brackets is an optional parameter of type Variant. Although the third parameter is named Picture, it is of type Variant, so Visual Basic .NET does not know to map this property to System.Drawing.Image. If you upgrade this code to Visual Basic .NET, you end up with the following:

ImageList1.ListImages.Add( ,  , _
   VB6.ImageToIPictureDisp(System.Drawing.Image.FromFile( _
   Environ("WINDIR") & "\Zapotec.bmp")))

If the Picture parameter had been treated as a System.Drawing.Image type, the call to System.Drawing.Image.FromFile would be sufficient. In this case, however, the Picture parameter is mapped to Object and requires the underlying ActiveX type IPictureDisp. The compatibility library function ImageToIPictureDisp is required to convert the Image object to an IPictureDisp—the underlying interface type for Picture—and the resulting IPictureDisp is passed as the Picture argument to the Add method. Without this conversion you will encounter a run-time COMException returned from the ImageList control, containing the error “Invalid picture.”

Fortunately, as in the case just described, the Upgrade Wizard handles most of these conversions for you. In rare cases you will need to call a conversion helper function to convert a .NET type to the equivalent ActiveX type.


Standard Component Wrappers and ActiveX Control Subobjects

ActiveX controls that contain one or more subobjects have a split personality. The top-level set of control properties, methods, and events are wrapped and handled by an Ax wrapper. A simple component wrapper wraps all subobjects and other types defined by the control. A simple component wrapper is also applied to all standard ActiveX components that are not ActiveX controls. For example, if you reference ADO, a simple component wrapper is applied to expose the properties, methods, and events of all ADO objects. The objects are exposed using the .NET types that most closely represent the underlying type contained in the ActiveX component.

In the case of the TreeView control, the Ax wrapper is applied to the TreeView’s top-level properties, methods, and events. Properties such as Appearance, Font, and Style are wrapped and handled by the Ax wrapper. The Ax wrapper exposes the Font property as a System.Drawing.Font, for example. If TreeView had a BackColor property, it would be exposed as a System.Drawing.Color property.

Now let’s take a look at subobjects that the TreeView control exposes, such as Nodes and Node. The BackColor property of a Node gets exposed to you as a System.UInt32 type. But isn’t BackColor type OLE_COLOR? Why UInt32  ? UInt32 is chosen for two reasons:

  • The type most closely matches the type OLE_COLOR, which itself is a UInt32.

  • Neither the simple component wrapper nor the Ax wrapper supports type aliases. In the original COM type library, OLE_COLOR is an alias for UInt32.

Consider the following Visual Basic 6 example, which sets the ForeColor property on both the ListView ActiveX control and one of its ListItem subobjects:

Dim li As ListItem
Set li = ListView1.ListItems.Add(, "Item1Key", "Item1")
ListView1.ForeColor = vbRed
li.ForeColor = vbRed

After upgrade, the Visual Basic .NET code is as follows:

ListView1.ForeColor = System.Drawing.Color.Red
li.ForeColor = _
   System.Convert.ToUInt32(System.Drawing.ColorTranslator.ToOle( _
   System.Drawing.Color.Red))

Although ForeColor is the same exact type for ListView and the ListItem object, the code needed to assign the value of Red is radically different. The ListView ForeColor property is exposed by the Ax wrapper as type System.Drawing.Color, so it’s quite natural to assign a System.Drawing.Color.Red object to the property. The ListView Ax wrapper takes care of translating the System.Drawing.Color.Red object value to the equivalent UInt32 color value.

In the case of the ListItem.ForeColor property, the simple component wrapper exposes the type as System.UInt32, leaving it up to you to figure out how to convert a System.Drawing.Color.Red object value to a numeric UInt32 color value. To make this conversion, you first need to convert the color object to a numeric value by using System.Drawing.ColorTranslator.ToOle. The ToOle function returns a signed long integer or System.Int32. You then need to convert the Int32 value to a UInt32 value by using the System.Convert class.

Fortunately, the Upgrade Wizard handles most of these conversions for you. The downside is that you may end up with some rather unfamiliar-looking conversion code. Table 16-2 provides a list of conversion helper functions so that you’ll understand how the conversion works when you see one of these in code. You can also use these functions when writing new code to help make assignments between ActiveX and .NET types.

Table 16-2 Useful Conversion Functions for Common ActiveX Object Types
Conversion Function(s) | Description
System.Convert methods | Allow you to convert from virtually any .NET type to another .NET type. Use System.Convert to convert 32-bit signed integers (System.Int32) to 32-bit unsigned integers (System.UInt32).
System.Drawing.ColorTranslator methods | Allow you to convert between OLE color, Windows color, and .NET color types.
FontToIFont, IFontToFont | Located in the Microsoft.VisualBasic.Compatibility.VB6.Support class. You use these functions to convert between .NET and ActiveX Font types. They are used primarily when you are trying to get or set an ActiveX component property that is exposed as Object, IFont, IFontDisp, or as a component-specific class that implements IFont.
CursorToIPicture, IconToIPicture | Allow you to convert a System.Windows.Forms.Cursor or System.Drawing.Icon type directly to an ActiveX IPicture.
ImageToIPicture, ImageToIPictureDisp, IPictureToImage, IPictureDispToImage | Allow you to convert between the .NET System.Drawing.Image and ActiveX IPicture types. These functions are intended for situations in which you are trying to get or set an ActiveX component Picture property that is defined as Object, IPicture, IPictureDisp, or as a component-specific class declaration that implements IPicture.
Common Exceptions That Require Type Conversions

Certain lines of your upgraded code may contain type mismatch assignments. For example, you may find code in which the MousePointer property of an ActiveX control is being assigned to a System.Windows.Forms.Cursors property. The problem is that an ActiveX MousePointer property is usually a MousePointerConstants enumeration type defined by the control—in other words, a numeric type. Each System.Windows.Forms.Cursors property, such as IBeam, returns a Cursor object. Attempting to assign a Cursor object to a numeric type is a recipe for a compiler error. In this case, you receive a descriptive compiler error telling you that you cannot assign a Cursor to a numeric type.

Compiler errors are not the type of errors that you need to worry about. Although annoying at times, they are in-your-face error messages that point directly to a problem in your code. You can easily locate the problem and, in most cases, especially with the help of IntelliSense, find a quick fix. A more insidious problem that will give you fits is an exception that occurs at run time. The two most common exceptions that relate to the assignment of incompatible types at run time are InvalidCastException and COMException.

InvalidCastException

The InvalidCastException is a standard .NET exception that occurs when the .NET Framework is unable to cast one type to another at run time. This exception commonly occurs when you attempt to assign a .NET type to an ActiveX type. For example, if you attempt to assign a .NET collection to an ActiveX component method that returns a Visual Basic 6 Collection object, this exception will occur.

COMException

A COMException can occur any time a property or method of an ActiveX control or component is called. The exception generally occurs because the Visual Basic .NET code is passing a .NET object when it should be passing an ActiveX object. For example, if you attempt to assign an Ax wrapped ImageList control to the ImageList property of an Ax wrapped TreeView control, as in the following line of code, it will bark back at run time with a COMException.

AxTreeView1.ImageList = AxImageList1

The problem happens in this case because you need to assign the underlying ActiveX control object to the ImageList property. You can obtain the underlying ActiveX control object by calling GetOcx on the Ax wrapped control. The following code works and does not bark at you:

AxTreeView1.ImageList = AxImageList1.GetOcx()


Name Collisions

Name collisions happen when the Ax wrapper class includes a property, method, or event that has the same name as the ActiveX control. Take, for example, an ActiveX control that has a property called Size. The ActiveX control’s Size property will be in conflict with the Size property that the Ax wrapper adds to the control. To manage this type of conflict, the Ax wrapper will rename the ActiveX control property to CtlSize. If you need to set the ActiveX control’s size property, you should set CtlSize. To set the size property managed by Windows Forms on behalf of the ActiveX control, you would set the Size property. Fortunately, name conflicts will not occur for most ActiveX controls. When the Upgrade Wizard detects a name conflict, it will upgrade your code to use the correct property or method even if the property or method has been renamed.

Naming conflicts are not new to Visual Basic .NET. Visual Basic 6 also manages name conflicts for you. As we mentioned earlier in this chapter, Visual Basic 6 creates a wrapper class for ActiveX controls. If there is a conflict with a property or method name, the wrapper hides the ActiveX control property or method in favor of the wrapper’s property or method. To access the underlying ActiveX property, you need to use the ActiveX control’s Object property. For example, the following Visual Basic 6 code:

MyFavoriteControl.Object.Tag = "My control's internal tag"
MyFavoriteControl.Tag = "The Tag property given to me by Visual Basic"

upgrades to the following Visual Basic .NET code:

MyFavoriteControl.CtlTag = "My control's internal tag"
MyFavoriteControl.Tag = "The Tag property given to me by Visual Basic"
Event Name Collisions

If an event name conflicts with another property or base class event name, the Upgrade Wizard renames the event. It does so by appending the word Event to the end of the event name. One ActiveX control that exhibits this behavior is the Microsoft WinSock control. If you place a WinSock control on a Windows form and view its events, you will see that the Close and Connect events have been renamed CloseEvent and ConnectEvent, respectively. Since most controls contain unique event names, you will seldom encounter this issue. Even if you do, you probably will not notice. If a name conflict does occur, the renamed event is easy to find, since the event name is mostly preserved. The Upgrade Wizard automatically upgrades your code to use the renamed event name, so you should not encounter any issues after upgrading your Visual Basic 6 application.


Using ActiveX Components from .NET

This section discusses common issues that you may encounter when using an ActiveX control or component in your Visual Basic application after upgrading to Visual Basic .NET.

When ByRef Bites

The Visual Basic .NET compiler introduces a new semantic when passing properties ByRef to a function. If the property has write access, it is set to the return value of the ByRef parameter. In Visual Basic 6 this is not the case. If you pass a property ByRef, the property is never set.

As an example, consider the following Visual Basic 6 code contained in Form1 of a default standard EXE project:

Private Sub Form_Load()
   SetString Me.Caption
   MsgBox Me.Caption
End Sub

Private Sub SetString(ByRef str As String)
   str = "String changed by SetString"
End Sub

When this code is run, the message box displays “Form1.” Me.Caption is not changed by calling the function SetString.

Consider the following code that has been upgraded to Visual Basic .NET:

Private Sub Form1_Load(ByVal eventSender As System.Object, _
                       ByVal eventArgs As System.EventArgs) _
                       Handles MyBase.Load
   SetString(Me.Text)
   MsgBox(Me.Text)
End Sub

Private Sub SetString(ByRef str_Renamed As String)
   str_Renamed = "String changed by SetString"
End Sub

If you run the Visual Basic .NET version, you will see that the Text property of the form gets set to “String changed by SetString” as a result of calling SetString.

This is both good and bad. It’s good that Visual Basic .NET offers this new semantic so that properties passed ByRef are set the way you would expect. It’s bad, however, in that this change could lead to subtle, hard-to-find problems in your upgraded code.

This issue does not end with your own code. You may run into it when attempting to call an ActiveX component that takes ByRef parameters. Consider the following Visual Basic .NET code written against a ListView control located on Form1 in a default Windows application project:

Dim lvItem As MSComctlLib.ListItem
AxListView1.ListItems.Add(, , "Item1")
lvItem = AxListView1.ListItems(AxListView1.ListItems.Count)

Run this code and you will encounter a COMException on the last line, telling you that “Property is read-only.” What property is read-only? you might ask. How am I going to figure this one out?

The problem in this case is that the ListItems default property Item takes a ByRef Variant argument. AxListView1.ListItems.Count is passed as an argument to this ByRef parameter. Shouldn’t this be okay? Isn’t the Count property read-only, so the compiler won’t try to set it? Yes and no. The Count property is implemented with both a Get and a Let. Therefore, the compiler thinks the property can be written. When it tries to write to the property, however, the Let implementation for the Count property throws an exception.

The reason the ListView ListItems subobject has a settable Count property is a long, twisted story stretching back three versions of Visual Basic. To work around these types of issues, you can include parameters that you don’t want to be set in parentheses. Using parentheses tells the compiler to pass the property read-only. For example, if you change the code in the previous example to the following, it will work. Note the extra parentheses around AxListView1.ListItems.Count:

' Pass AxListView1.ListItems.Count ByVal to avoid Set being called
lvItem = AxListView1.ListItems((AxListView1.ListItems.Count))
When a Collection Is Not a Collection

Just because a .NET collection looks like a collection does not mean that it really is a collection—at least not in the Visual Basic 6 sense. A .NET collection is almost the same as a Visual Basic 6 collection. It has the same methods, such as Add, Remove, and Item, but you cannot assign a .NET collection to a Visual Basic 6 collection. Why would you need to use a Visual Basic 6 collection? Isn’t this .NET? Don’t you want all of your types to be declared in .NET? Isn’t the .NET collection a new and improved Collection object? Yes on all counts, but there is one case in which you will need to use a Visual Basic 6 collection: when you call a COM object that takes a Visual Basic 6 Collection object as a parameter or that returns a Visual Basic 6 Collection object. A .NET collection will not do in this case.

Take, for example, the following Visual Basic 6 code declared in a public class module of an ActiveX DLL called RetCollection.dll:

Option Explicit
Private m_Collection As New Collection
Public Function ReturnCollection() As Collection
   Set ReturnCollection = m_Collection
End Function

Private Sub Class_Initialize()
   m_Collection.Add "MyItem1", "MyKey1"
   m_Collection.Add "MyItem2", "MyKey2"
End Sub

Now consider the following Visual Basic .NET code, which calls the ReturnCollection function:

Dim c As Collection
Dim rc As New RetCollectionLib.RetCollection()

c = rc.ReturnCollection
MsgBox(c.Item(1))

The code compiles, runs—up to a point, at least—and throws an InvalidCastException on the attempt to assign c to rc.ReturnCollection. The problem is that a .NET collection—represented by the variable c—cannot be assigned to the Visual Basic 6 Collection object returned by the function ReturnCollection.

To fix this problem, you need to add a reference to the Visual Basic 6 runtime Msvbvm60.dll. Follow these steps to do so:

  1. Right-click the References list in Solution Explorer, and choose Add Reference.

  2. Select the COM tab.

  3. Select Visual Basic For Applications Version 6.0 from the list.

  4. Change your Visual Basic .NET code to use the Visual Basic 6 Collection object as follows:

            Dim c As VBA.Collection

The variable c now matches the type returned by the ReturnCollection function, so everything will now work as expected.

Nonzero-Bound Arrays

You may encounter problems when attempting to call a COM object method that returns an array defined with a nonzero-bound lower dimension, such as –10 or 1. Take, for example, the following code defined in a Visual Basic 6 ActiveX server DLL:

Option Base 1
Public Function RetStringArray() As String()
   Dim i As Long
   'Array is 1-based. We're using Option Base 1
   Dim s(10) As String

   For i = LBound(s) To UBound(s)
      s(i) = i
   Next

   RetStringArray = s
End Function

Suppose that you are trying to call the RetStringArray function, using the following Visual Basic .NET code:

Dim rs As New RetStringArrayLib.RetStringArray
Dim s() As String

s = rs.RetStringArray
MsgBox(s(1))

The code will compile, but you will encounter an InvalidCastException at run time when trying to assign s the return value of rs.RetStringArray. The problem occurs because you are attempting to assign a nonzero-based string array to a zero-based string array variable. To fix this, you need to change the declaration of s from a strongly typed String array—always zero based—to a generic System.Array type, as follows:

Dim s As System.Array

The generic System.Array type can represent a nonzero-based array but cannot be assigned to a strongly typed array unless it is zero bound.

Alias Types Are Not Supported

Type aliasing is declaring a new type based on an existing type. A common example is the OLE_ types defined in the Standard OLE Automation type library (StdOle2.tlb). The type OLE_COLOR, for example, is an alias for an unsigned 32-bit long integer. When you reference a COM object in .NET, type aliases are lost. Instead you need to use the base type. For example, you need to use the .NET System.UInt32 type instead of OLE_COLOR.

note | Neither Visual Basic 6 nor Visual Basic .NET allows you to declare a type that is an alias of another type. In the case of Visual Basic 6, however, you can use aliased types from other type libraries. For example, you can create a Visual Basic 6 component with a public BackColor property of type OLE_COLOR. If you attempt to use the component in .NET, the property type will show up as System.UInt32.
Module Methods Are Not Supported

If you have a Visual Basic 6 project that references a COM component that exports module methods, the module methods are not available to be called by a .NET client. For example, the DirectX Visual Basic type library Dx8vb.dll contains a large number of module methods that can be called from Visual Basic 6. When you upgrade Visual Basic 6 code that calls module methods, the calls are left in the code, but the code doesn’t compile.

Since module methods delegate to exported functions within a DLL, the trick to solving this problem is finding the DLL-exported function that the module method calls. To do this, you can dump the list of exported functions for the DLL, but first you need to find the DLL containing the module method you want to call. The easiest way to find it is to load the original Visual Basic 6 project in Visual Basic 6 and find the DLL reference within the References list. To view the References list, choose References from the Project menu. Once you have located the reference within the list—for example, DirectX 8 For Visual Basic Type Library—note the location of the DLL for that reference. Open a DOS command window and dump the exported functions contained in the DLL by executing the following command:

DumpBin /Exports DX8VB.DLL > Exports.Txt

Launch Notepad and open Exports.txt. Look for a function that matches the name of the module method that your Visual Basic 6 code calls. For example, if your Visual Basic 6 code calls the DirectX function D3DXColorAdd, you will find the following entry in the DLL exports list:

        105   15 0002DB8F VB_D3DXColorAdd

You can use this information to declare the API entry point in your Visual Basic code. The most useful information in this cryptic entry is the entry ID contained in the first column and the name of the function. In this case the entry ID is 105 and the function name is VB_D3DXColorAdd.

Suppose, for example, that you have upgraded a Visual Basic 6 function that calls the DirectX function D3DXColorAdd and you end up with the following Visual Basic .NET code:

Dim COut As DxVBLibA.D3DCOLORVALUE
Dim CRed As DxVBLibA.D3DCOLORVALUE
Dim CBlue As DxVBLibA.D3DCOLORVALUE

CRed.r = 255
CBlue.b = 255
'UPGRADE_ISSUE: COM expression not supported: Module methods of
'COM objects. Click for more: 'ms-help://MS.VSCC/commoner/redir/
'redirect.htm?keyword="vbup1060"'
DxVBLibA.D3DXMATH_COLOR.D3DXColorAdd(COut, CRed, CBlue)

This code leads to a compiler error, since D3DXColorAdd, as suggested by the UPGRADE_ISSUE comment, is not available. Based on the Exports.txt DLL export information obtained earlier, you can declare the function as follows:

Private Declare Function D3DXColorAdd Lib "dx8vb.dll" _
   Alias "#105" (ByRef COut As DxVBLibA.D3DCOLORVALUE, _
   ByRef C1 As DxVBLibA.D3DCOLORVALUE, ByRef c2 As _
   DxVBLibA.D3DCOLORVALUE) As Integer

In this case we’re using the entry ID 105 as the name of the function. You could also declare the function to use the API function name VB_D3DXColorAdd in the Alias clause. To obtain the full declaration for the function, you can load your original Visual Basic 6 project in Visual Basic 6 and start the Object Browser. With the Object Browser running, you can search for the module method declaration for D3DXColorAdd. The module method declaration will match the API declaration you need to create using the Declare statement.


Conclusion

The good news is that ActiveX controls and components are supported in Visual Basic .NET. Although there are some limitations in the support that is provided, you should not be hindered in upgrading Visual Basic 6 applications that are good candidates for Visual Basic .NET. For example, if you have a Visual Basic 6 middle-tier component that makes heavy use of ActiveX-based ADO objects, you can upgrade your component to a Visual Basic .NET class library component. All of your code that talks to the ActiveX ADO components will continue to work as is. You can then quickly take advantage of Visual Basic .NET by exposing some of your methods as WebMethods to be called over the Internet, for example.

If Visual Basic .NET did not allow you to make use of your existing ActiveX components, you would be devoting all of your energy to finding or creating .NET replacements for everything you are using. You would get bogged down just trying to get your application working again in the .NET environment. The ability to use ActiveX components frees you to focus only on those parts of your application that are critical to get working in .NET. This ability enables you to get up and running in .NET quickly.


Resolving Data Access Issues

Chapter 14
Resolving Data Access Issues

Most business applications have some sort of data access. This fact is not surprising when you consider that conceptually, most businesses are themselves based around data: customers and orders, inventory and purchase information—all of this business information needs to be stored in such a way that it can be easily retrieved, edited, and stored again. Countless Microsoft Visual Basic applications have been developed to provide a friendly face for information in databases. Each new version of Visual Basic has delivered enhancements to data access over previous versions. Visual Basic .NET is no exception. With this version, we see continued support for ActiveX Data Objects (ADO) code and data binding. Visual Basic .NET also supports Data Access Objects (DAO) and Remote Data Objects (RDO) code, but it does not support data binding for DAO and RDO. Finally, it introduces a new disconnected type of data access called ADO.NET. This chapter looks at upgrading the three major data access technologies, concentrating on ADO.

The examples in this chapter are all included on the companion CD. They use the Northwind Microsoft Access database. This database is distributed with Microsoft Access, Microsoft Office XP Professional, Visual Basic 6, and various other Microsoft products. The examples in this chapter assume that this database is in the location C:\NWind.mdb. If it is somewhere else, you will need to edit the projects to point to the correct location or move the database to this location.


Data Access in Visual Basic

Data access is made up of three components: code that manipulates data objects, data binding, and design-time tools such as the ADO data environment. The users of your applications may regard the run-time behavior as another component of data access; but we will consider the run-time behavior as an element of each of the three components. Let’s look quickly at the three components and see where they differ between Visual Basic 6 and Visual Basic .NET.

Code

DAO, RDO, and ADO are implemented as COM libraries, so most code works exactly the same in Visual Basic .NET as it did in Visual Basic 6. For example, the following ADO code opens the Northwind database and then executes a Select statement that returns the list of employees. The first name and last name of the employee is then shown in a message box:

Dim cn As New Connection
Dim rs As Recordset
cn.Open "Provider=Microsoft.Jet.OLEDB.3.51;Data Source=c:\temp\nwind.mdb"
Set rs = cn.Execute("Select * From Employees")
MsgBox rs!FirstName & " " & rs!LastName
rs.Close
cn.Close

This code upgrades to the following:

Dim cn As ADODB.Connection = New ADODB.Connection()
Dim rs As ADODB.Recordset
cn.Open("Provider=Microsoft.Jet.OLEDB.3.51;Data Source=c:\temp\nwind.mdb")
rs = cn.Execute("Select * From Employees")
MsgBox(rs.Fields("FirstName").Value & " " & rs.Fields("LastName").Value)
rs.Close()
cn.Close()

As we’ve seen in other examples elsewhere in this book, after it is upgraded the code looks essentially the same, just more explicit. It works perfectly in Visual Basic .NET. Likewise, most RDO and DAO code works perfectly after upgrading, with a few differences that we will discuss later in this chapter.

One major difference across all the data access technologies is how you access fields of a Recordset (or Resultset for RDO). In Visual Basic 6, it is common to access fields using the shorthand coding convention RecordsetName!FieldName. In Visual Basic .NET, this code needs to be expanded in order to resolve the default properties. The Upgrade Wizard does this for you, but the code looks a little different after upgrading. Let’s look at an example:

Dim rs As Recordset
Dim myString As String
myString = rs!Lastname

This code assigns the string myString to the field Lastname of the Recordset rs. It upgrades to the following:

Dim rs As ADODB.Recordset
Dim myString As String
myString = rs.Fields("Lastname").Value

Notice that rs!Lastname was expanded to rs.Fields(“Lastname”).Value. The Upgrade Wizard will handle all of these cases for you.

Data Binding

Data binding allows you to bind the value of a control on a form to a field in a database. Visual Basic 6 supports data binding using ADO, DAO, and RDO. You can bind a control to an ADO data environment, an ADO Data control, a DAO Data control, or an RDO Data control. Visual Basic .NET, however, supports ADO data binding only, not DAO or RDO data binding. This distinction comes from the way in which the different types of data binding are implemented.

DAO and RDO are both older data access technologies. DAO data binding was introduced in Visual Basic 3, and RDO data binding debuted in Visual Basic 4. When the Visual Basic development team first implemented these older forms of data binding, they built them into the forms package. This implementation allowed a seamless integration, but it also tied the data binding technology to Visual Basic Forms. In Visual Basic .NET, Visual Basic Forms has been replaced with Windows Forms—a redesigned forms package. The designers of Windows Forms decided not to build DAO and RDO data binding into Windows Forms, and therefore DAO and RDO data binding are not supported. ADO data binding is supported because it is not built into the forms package; instead it is implemented in the COM library MSBind.dll. This library manages ADO data binding in Visual Basic 6, and the updated library, called Microsoft.VisualBasic.Compatibility.Data, manages data binding in Visual Basic .NET. The Upgrade Wizard upgrades data binding to both the ADO Data control and the ADO data environment.

ADO Data Environment

The Visual Basic 6 ADO data environment lets you visually design database connections and database commands using the ADO Data Environment Designer. Although Visual Basic .NET does not support the ADO data environment, the Upgrade Wizard upgrades the connections, commands, and Recordsets to a class that has the same run-time behavior as the Visual Basic 6 data environment.

Components That Don’t Upgrade

The components that can’t automatically be upgraded to Visual Basic .NET cause errors in the upgraded project. For example, if a Visual Basic 6 form has a DAO Data control, the control is removed during upgrade and EWIs are inserted into the upgrade report. Any code that references the control will cause a compile error.

What should you do if your project uses RDO or DAO data binding? The only solution is to reimplement the data binding in Visual Basic .NET, using ADO or ADO.NET data binding. The best choice is generally ADO.NET. As we discuss in the next section, the design-time experience for ADO.NET is better than for ADO.

Figure 14-1 gives an overview of the different data access technologies in Visual Basic 6 and shows how they upgrade to Visual Basic .NET. Technologies on the left side without an arrow do not automatically upgrade to Visual Basic .NET.

Figure 14-1
How data access technologies upgrade.


ADO.NET Is the Future

The future of data binding and data access is with ADO.NET, not with DAO, RDO, or ADO. In Visual Basic .NET Windows Forms and Web Forms, ADO.NET offers a rich editing experience, with designers for connections, schemas, and data binding. Unfortunately, the ADO editing experience is not as rich as that of either ADO.NET in Visual Basic .NET or ADO in Visual Basic 6. Although ADO data binding is useful for maintaining existing applications, we advise you to use ADO.NET for new applications that use data access, especially those that use data binding.

If ADO.NET is the future, you may ask, why doesn’t the Upgrade Wizard upgrade DAO, RDO, and ADO to ADO.NET? Unfortunately, there is no one-to-one mapping to ADO.NET. ADO.NET is a disconnected architecture, and it is distinct enough that automatic upgrading to it is not possible. For example, the Recordset object in ADO has the concept of a current row; this is a cursor that points to the current row as you step through a Recordset using the MoveFirst, MoveNext, MoveLast, and MovePrevious methods. The closest equivalent to a Recordset in ADO.NET is a DataTable (or DataSet, a collection of DataTables). A DataTable does not have a current row, and there is no equivalent to the Move method of a Recordset. For this reason, an automatic upgrade from DAO, RDO, and ADO is not possible. For an introduction to working with ADO.NET, see Chapter 20.


General Issues with Data Access Code

This section looks at some of the changes that are made to your data access when it is upgraded from Visual Basic 6 to Visual Basic .NET. It also discusses solutions to problems you may encounter when upgrading data access code.

DAO and RDO Module Methods

Both DAO and RDO expose module methods. As we discussed in Chapter 13, module methods are properties and functions of the COM library itself that are made globally available. They can be used without creating an instance of any class. For example, the following DAO code opens a database using the OpenDatabase module method:

Dim db As Database
Set db = OpenDatabase("c:\temp\NWind.mdb")

When Visual Basic 6 executes this code, it recognizes that OpenDatabase is a method of the DAO COM library rather than of a particular class within the library. Visual Basic .NET does not support module methods; every method must be qualified with a class.

Because module methods are actually methods of the COM library’s underlying AppID class, the Upgrade Wizard locates the AppID class within the COM component’s type library. It then creates a global variable of this class and places it in a module called UpgradeSupport.vb. If this module doesn’t already exist, the wizard creates it for you. The name of the variable it creates is DAODBEngine_definst for DAO, RDOrdoEngine_definst for RDO. When the previous example is upgraded, the wizard creates an UpgradeSupport.vb module with the following code:

Module UpgradeSupport
   Friend DAODBEngine_definst As New DAO.DBEngine
End Module

The code that uses the OpenDatabase method is upgraded to be a method of this default instance:

Dim db As DAO.Database
db = DAODBEngine_definst.OpenDatabase("c:\temp\NWind.mdb")

Default instances are a good solution for module methods. If you’re continuing to write DAO and RDO code, you should be aware of this difference and use the global variable to access the module methods. For example, you could add the following code to the upgraded project that uses the DAO BeginTrans and CommitTrans methods:

DAODBEngine_definst.BeginTrans()
DAODBEngine_definst.CommitTrans()

Similarly, you can add the following DAO methods and properties:

BeginTrans | CommitTrans | CompactDatabase
CreateDatabase | CreateWorkspace | Idle
OpenConnection | Properties | RegisterDatabase
RepairDatabase | Rollback | SetOption
SystemDB | Version | Workspaces

You can also add the following RDO methods and properties:

rdoCreateEnvironment

rdoRegisterDataSource

If you are writing a new Visual Basic .NET project that uses module methods, you will need to add a global default instance variable to your application to access these methods.

ADO Version 2.7

ADO comes in a number of versions: 2.1, 2.2, 2.5, 2.6, and now version 2.7. You can use any of these versions in your application. The different versions are merely different type libraries that point to the same DLL: Msado15.dll. Each time you install a new version of Microsoft Data Access Components (MDAC), it updates the underlying Msado15.dll and installs a new type library for the latest version. ADO 2.7 is installed with Visual Basic .NET. When you upgrade your project, the Upgrade Wizard automatically changes any references to earlier versions of ADO to ADO 2.7. These changes don’t cause any problems with upgraded code, since ADO 2.7 is backward compatible with previous versions.

Errors in Events

Most data access applications use events in one way or another. For example, if your project uses the ADO Data control, you may have an event handler for the ADODC_MoveComplete event. Another common usage is to declare an ADO.Connection variable using the WithEvents keyword and to define one or more event handlers for the variable. In Visual Basic .NET, if a COM object raises an event, and an untrapped error occurs in the event, the error will be silently ignored. Let’s look at an example—the Visual Basic 6 project prjADOEvent (included on the companion CD). This project has a TextBox bound to an ADO Data control that accesses the Employees table in the Northwind database. When you run this project from the Visual Basic 6 IDE and click the Raise Error button, an error occurs in the ADODC1_MoveComplete event, as shown in Figure 14-2.

Figure 14-2
Error occurring in the Visual Basic 6 project prjADOEvent.

Try upgrading the application to Visual Basic .NET. Now when you click the Raise Error button, no error occurs. Did Visual Basic .NET somehow fix the error? Unfortunately not. It is simply ignoring the error, as it does all errors occurring in events raised by COM objects. The solution is to put error trapping code in the event. In this example, the following Visual Basic 6 code for the MoveComplete event:

Private Sub Adodc1_MoveComplete(ByVal adReason As ADODB.EventReasonEnum, _
   ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, _
   ByVal pRecordset As ADODB.Recordset)
   Dim x As Integer, y As Integer
If m_RaiseError Then
   x = x / y
End If
End Sub

is upgraded to

Private Sub Adodc1_MoveComplete(ByVal adReason As _
   ADODB.EventReasonEnum, _
   ByVal pError As ADODB.Error, ByRef adStatus As _
   ADODB.EventStatusEnum, _
   ByVal pRecordset As ADODB.Recordset) Handles Adodc1.MoveComplete
   Dim x, y As Short
   If m_RaiseError Then
      x = x / y
   End If
End Sub

Let’s change this event to include Try...Catch error trapping:

Private Sub Adodc1_MoveComplete(ByVal adReason As _
   ADODB.EventReasonEnum, _
ByVal pError As ADODB.Error, ByRef adStatus As _
   ADODB.EventStatusEnum, _
ByVal pRecordset As ADODB.Recordset) Handles Adodc1.MoveComplete
   Try
       Dim x, y As Short
       If m_RaiseError Then
           x = x / y
       End If
   Catch ex As Exception
       MsgBox(ex.Message)
   End Try
End Sub

Now Visual Basic .NET catches and displays the error, as shown in Figure 14-3. You should add error trapping code to all DAO, RDO, and ADO events that may raise errors.

Figure 14-3
Error caught and displayed by Visual Basic .NET.
RDO Connection

A common technique when programming with RDO in Visual Basic 6 is to let the user configure the database connection at run time, rather than requiring it to be set up at design time. To do this, you write code that opens an rdoConnection with a blank Connect argument, as in the following code:

Dim cn As rdoConnection
Dim en As rdoEnvironment
Set en = rdoEnvironments(0)
Set cn = en.OpenConnection("")

When this code runs, it brings up the Select Data Source dialog box, as shown in Figure 14-4.

Figure 14-4
Select Data Source dialog box.

The code upgrades to the following:

Dim cn As RDO.rdoConnection
Dim en As RDO.rdoEnvironment
en = RDOrdoEngine_definst.rdoEnvironments(0)
cn = en.OpenConnection("")

Whether this upgraded code works or not in Visual Basic .NET depends on where it is located. If it is located in a Button_Click event, it will work. However, if it is in Form_Load, it will fail with the error shown in Figure 14-5.

Figure 14-5
RDO connection error in Visual Basic .NET.

The reason that it fails in Form_Load is that the dialog box tries to retrieve a handle for the parent window. In Visual Basic 6 that handle exists, but in Visual Basic .NET it hasn’t been created yet. The solution is to move the code from Form_Load to another event, such as a Button_Click event.

Null, vbNullString, and vbNullChar

As we discussed in Chapter 11, the Null value works differently in Visual Basic .NET than it does in Visual Basic 6. The use of Null can sometimes lead to subtle, hard-to-find errors. You may find one such error with vbNullString. It is a common practice in Visual Basic 6 to use vbNullString to reset a Recordset filter, as in the following code:

Dim rs As New ADODB.Recordset
rs.Filter = vbNullString

This code sets the Recordset filter to an empty string. After upgrading, it becomes the following:

Dim rs As New ADODB.Recordset()
rs.Filter = vbNullString

When run, this code causes a COM exception in Visual Basic .NET because vbNullString has the value Nothing in Visual Basic .NET, whereas in Visual Basic 6 it has the empty string value "". The Recordset filter is expecting a string value rather than a Null, and hence the error. The solution is to change the code to use an empty string:

Dim rs As New ADODB.Recordset()
rs.Filter = ""

This code works perfectly.


ADO Data Environment

Visual Basic 6 introduced the ADO data environment, which provides a visual way to add and edit data objects. With the ADO Data Environment Designer, you can add connections, commands, and child commands. At run time, the ADO data environment acts like a class, with collections of connections, commands, and Recordsets.

Because Visual Basic .NET does not support the ADO data environment, the Upgrade Wizard upgrades data environments to a class that has the same run-time behavior. Since it’s a class, not an ADO data environment, the editing experience is less rich than in Visual Basic 6. The programming model for the upgraded class is almost identical to Visual Basic 6. For example, suppose your project has a data environment named myDataEnvironment, with a command called myCommand. The following code would assign a Recordset to the result of the command:

Dim rs As Recordset
Set rs = myDataEnvironment.rsmyCommand

After upgrading, this code looks like the following:

Dim rs As ADODB.Recordset
rs = myDataEnvironment.rsmyCommand

With a few exceptions that we will discuss now, the upgraded data environment behaves like the original Visual Basic 6 version.

Calling Dispose

When your application shuts down, it’s important to dispose of the upgraded ADO data environment. This closes all Recordsets, commands, and connections. To dispose of the ADO data environment, call the Dispose method. For example, suppose your application has an ADO data environment named DataEnvironment1. You would dispose of it with the following line of code:

DataEnvironment1.Dispose

You should put this line somewhere in your application shutdown code.

Initialize Event

In Visual Basic 6, the ADO data environment supports an Initialize event and a Finalize event. When your application is upgraded, the code in these events is upgraded, but as procedures. The events won’t be fired when the application is run. You should move the code in the Initialize procedure into the New event and the code in the Finalize procedure into the Dispose event.

Cursor Location with Microsoft Access Databases

In Visual Basic 6, you could set the cursor location of an ADO connection object to adUseServer or adUseClient, signifying a server-side or client-side cursor location, respectively. If the database is a Microsoft Access database, the property is ignored and a client-side cursor is always used. In Visual Basic .NET, trying to use a server-side cursor with a Microsoft Access database causes a run-time exception, since the value is invalid. This situation may cause problems in upgraded applications if you have inadvertently set the cursor to be server-side. The solution is to remove the line of code in the Visual Basic .NET project that sets the cursor location. For example,

Me.Adodc1.CursorLocation = ADODB.CursorLocationEnum.adUseServer

This rule applies to the ADO data environment, the ADO Data control, and connections created in code.


ADO Data Binding

With ADO data binding, you can bind controls on forms to fields in a database. In Visual Basic 6, you can bind a control to one of three ADO objects:

  • ADO data environment

  • ADO Data control

  • ADO data source classes

The Upgrade Wizard can upgrade the first two data binding technologies, but it cannot upgrade data binding to ADO data source classes. Before we look at how data binding is upgraded, let’s take a minute to review how it works in Visual Basic 6.

Each Visual Basic 6 control supports either complex or simple binding. Complex binding occurs when a control binds to an entire Recordset. The control manages its own data binding. The Microsoft DataGrid ActiveX control is a good example of complex binding. Complex binding is the simplest to upgrade, since it’s only a matter of setting the control’s DataSource to a Recordset and then letting the control manage its own binding. The Upgrade Wizard manages this without problems.

Simple binding is more involved. Simple binding occurs when a control binds its value to the current record in a Recordset. Binding a TextBox Text property to an ADO Data control is a good example of simple binding. Simple binding in Visual Basic 6 is controlled by four properties of the control. Table 14-1 lists these properties. In Visual Basic 6, these four properties are “magic” properties. When you set them to valid entries, Visual Basic 6 adds an entry to an MSBind binding collection. If you change the properties at run time, Visual Basic 6 removes the old entry from the binding collection and adds a new entry.

Table 14-1 Properties Controlling Simple Binding in Visual Basic 6
Property | Description
DataSource | The name of the ADO Data control or ADO data environment to bind to
DataMember | For ADO data environments only, the name of the command to bind to
DataField | The field of the Recordset to bind to
DataFormat | The format in which the data is to be displayed

Windows Forms controls do not have these properties, so the Upgrade Wizard adds code to the form to manage the binding collection. For example, suppose that a form has a TextBox that is bound to a field of an ADO Data control. When it upgrades the form, the Upgrade Wizard does the following:

  • Inserts a form variable that manages the control binding. The variable is declared in the form’s declarations section. If the ADO Data control is called Adodc1, the binding variable is named ADOBind_Adodc1.

  • Inserts a method that sets up the binding collection. This method is named VB6_AddADODataBinding and is called from the Form.New event.

  • Inserts a method that removes the bindings and cleans up the objects when the form closes. This method is named VB6_RemoveADODataBinding and is called from the Form.Dispose event.

There are some data binding elements that the upgrade can’t handle automatically. Let’s look at them now.

Control Arrays of ADO Data Controls

Control arrays of ADO Data controls cannot automatically be upgraded from Visual Basic 6 to Visual Basic .NET. If you have a form with a control array of ADO Data controls, the control arrays will lose their settings during the upgrade, and the controls will not be bound. The best solution is to remove the controls from the control array in Visual Basic 6 before upgrading.

Setting Data Binding Properties at Run Time

Many projects that use data binding have code that adjusts data binding properties at run time. There are several reasons for this: you may need to change the database location, depending on whether the application is being run against a development database or a production database; or you may need to dynamically change the field a control is bound to. Code that changes data binding properties at run time may need some modifications after upgrading. The reason for requiring these modifications is that the underlying data binding mechanism has changed—you no longer use the DataSource and DataField properties to set up data binding. Instead, you add and remove binding entries of a data binding variable.

Let’s walk through an example to see how to upgrade code that adjusts binding properties at run time. On the companion CD, you will find a project called prjADORuntime. This Visual Basic 6 project has a TextBox bound to an ADO Data control. The binding is changed at run time to point to the Northwind database. In the Form_Load event, the code changes the connection string for the ADO Data control and then changes the data binding for the TextBox. When it is run, the form looks like Figure 14-6.

Figure 14-6
Setting up data binding dynamically at run time.

Here is the relevant code in Form_Load that changes the data binding:

'Set up connection string and recordsource for the ADODC
Me.dcEmployees.ConnectionString = _
   "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & _
   m_NorthwindDatabase
Me.dcEmployees.CommandType = adCmdTable
Me.dcEmployees.RecordSource = "Employees"
'Set up binding for the textbox
Set Me.txtLastname.DataSource = Me.dcEmployees
Me.txtLastname.DataField = "Lastname"

After upgrading, this section of code becomes the following:

'Set up connection string and recordsource for the ADODC
Me.dcEmployees.ConnectionString = _
   "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & _
   m_NorthwindDatabase
Me.dcEmployees.CommandType = ADODB.CommandTypeEnum.adCmdTable
Me.dcEmployees.RecordSource = "Employees"
'Set up binding for the textbox
'UPGRADE_ISSUE: TextBox property txtLastname.DataSource was not 'upgraded.
Me.txtLastname.DataSource = Me.dcEmployees
'UPGRADE_ISSUE: TextBox property txtLastname.DataField was not 'upgraded.
Me.txtLastname.DataField = "Lastname"

Changing the properties of the Data control is fine, but the code that sets up the binding for a particular control also needs to be modified. This code should be changed from

'UPGRADE_ISSUE: TextBox property txtLastname.DataSource was not 'upgraded.
Me.txtLastname.DataSource = Me.dcEmployees
'UPGRADE_ISSUE: TextBox property txtLastname.DataField was not 'upgraded.
Me.txtLastname.DataField = "Lastname"

to the following:

ADOBind_dcEmployees.Remove("txtLastname")
ADOBind_dcEmployees.Add(txtLastname, "Text", "Lastname", _
   Nothing, "txtLastname")

The ADOBind_dcEmployees variable is the global binding variable. This code removes the entry for the TextBox txtLastname and then adds a new entry to bind txtLastname to the Lastname field. Once this modification has been made, the code works in Visual Basic .NET.

If you want, you can go one step further and adjust the architecture of the form so that binding is set up only after the ADO Data control properties have been set at run time. If you look inside the Form_New event, you will find a line that reads

VB6_AddADODataBinding()

Remove this line of code and add it to the frmMain_Load event, so that the Load event looks like the following:

Private Sub frmMain_Load(ByVal eventSender As System.Object, _
ByVal eventArgs As System.EventArgs) Handles MyBase.Load
   If DoesDatabaseExist(m_NorthwindDatabase) Then
      'Set up connection string and recordsource for the ADODC
      Me.dcEmployees.ConnectionString = _
      "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & _
      m_NorthwindDatabase
      Me.dcEmployees.CommandType = ADODB.CommandTypeEnum.adCmdTable
      Me.dcEmployees.RecordSource = "Employees"
      VB6_AddADODataBinding()
   Else
      MsgBox("Northwind database can't be found at " & _
      m_NorthwindDatabase, MsgBoxStyle.Critical)
      End
   End If
End Sub

What we have done here is set up the correct connection string for the ADO Data control before adding the binding. The Visual Basic .NET data binding code may be more verbose than that in Visual Basic 6, but it also offers more control over how and when the data binding is set up.

If you are adjusting the data binding at run time, it is important to set some binding at design time first. The Upgrade Wizard adds the binding variables and procedures only if it detects that a form has data binding. If the binding is set only at run time, the Upgrade Wizard will not create the binding variable and procedures.


Conclusion

This chapter has looked at how to upgrade data access to Visual Basic .NET. DAO, RDO, and ADO code upgrades easily. ADO data binding also upgrades well, but Visual Basic .NET does not support DAO and RDO data binding. If your application relies heavily on data binding, it may be useful to reimplement the data binding in ADO.NET, since Visual Basic .NET has a rich design-time experience for ADO.NET data binding. For more information on moving ADO to ADO.NET, see Chapter 20.


Problems That Require Redesign

Chapter 15
Problems That Require Redesign

As you get used to upgrading applications and become more familiar with the modifications needed to get them working in Microsoft Visual Basic .NET, you start noticing that some problems can be remedied with a one-line fix, whereas others require you to recode the problem area or even redesign it. This chapter looks at the most common of the redesign problems and shows you how to get each working in Visual Basic .NET.

If you give 50 different programmers the same problem to solve, you’ll get 50 different solutions. We all do things differently, and any given programming problem can be solved in many different ways. For each of the problems in this chapter, we provide a sample solution that you can use as is in your own code. However, the Microsoft .NET Framework often provides several different mechanisms that have the same effect. You may find that another mechanism is more suitable for the particular problem you’re trying to solve. Where possible, we try to make you aware of these mechanisms and direct you to sources where you can learn more about them.


Replacing the OLE Container Control

The OLE Container control was first introduced in Visual Basic 3 and is used to dynamically add objects to a form at run time. In Visual Basic 6, the OLE Container control is commonly used in three ways.

  • To create a control that contains an embedded object, such as a Microsoft Word document. When your application is compiled, its state (the contents of the object) is compiled into your program. If the user changes the object at run time, you can programmatically save the updated state to a file using the SaveToFile method and subsequently reload it using the ReadFromFile method.

  • To create a control that contains a linked object, such as a Word document. The difference between a linked object and an embedded object is that a linked object is linked to a file on disk instead of being compiled into the application. In the case of a linked document, any changes you make are immediately reflected in the document file, which can subsequently be loaded by Word.

  • To bind to an OLE object in a database—typically to a picture. The OLE Container control can be data-bound to a field in a database using the DAO Data control or the RDO Remote Data control. The most common usage is to display a picture.

Visual Basic .NET does not support the OLE Container control. What happens to the control during an upgrade? Let’s look at an example. Figure 15-1 shows a Visual Basic 6 form at design time, with an OLE Container control that has a link to a Word document stating, “VB Rocks!!”

Figure 15-1
OLE Container control with linked object in Visual Basic 6.

When we upgrade the project, the OLE control is replaced with a bright red label, indicating that the control could not be upgraded. Figure 15-2 shows the upgraded form. If you look at the upgrade report, you will find an entry saying that the control was not upgraded, and any code that manipulates the control will be marked with an upgrade warning similar to the following:

'UPGRADE_ISSUE: Ole method OLE1.CreateLink was not upgraded.
Figure 15-2
OLE Container control after upgrade.

Let’s work on replacing the OLE control and getting our upgraded application to show the Word document as it did in Visual Basic 6. In many cases, we can use a WebBrowser ActiveX control to produce the same effect as a linked object within an OLE Container control. The WebBrowser resembles Microsoft Internet Explorer and can display documents, spreadsheets, and HTML pages using the Navigate method. It also allows you to edit these objects and save them to their original files. The WebBrowser is a lot like the linking part of OLE (which, you’ll recall, is short for “object linking and embedding”). Using the WebBrowser control, we can open the Word document and display it on the form, as shown in Figure 15-3.

Figure 15-3
Word document displayed using the WebBrowser control.

Here are the steps required to replace an OLE control with the WebBrowser control:

  1. Remove the red label from the form.

  2. Add a WebBrowser control to the form. Finding this control can be a little confusing. For starters, it is contained in the obscurely named Shdocvw.dll. In the Customize Toolbox component picker, you need to select Microsoft WebBrowser Control on the COM Components tab. In the Toolbox it is called an Explorer control, and when you add it to your form it is called a WebBrowser control.

  3. In Form_Load (or wherever you want to bind to the document), add the following code:

    Me.AxWebBrowser1.Navigate("C:\SampleCode\LinkedDoc.doc")

    You may want to substitute the filename in this example for the location of the object to which you are linking.

  4. Press F5, and that should do the trick—the document should appear in the form. This method works for most objects that Internet Explorer can display, such as HTML files, documents, spreadsheets, GIFs, and JPEG files.

The other common use for the OLE Container control is to bind a control to an image in a database. In Visual Basic 6, this is done using the DAO and RDO Data controls. When such a project is upgraded to Visual Basic .NET, you’ll see the red control mentioned previously, since neither the OLE Container control, the DAO Data control, nor the RDO Data control is supported in Visual Basic .NET.

Let’s look at how you can achieve the same effect using ADO and a helper function in your Visual Basic .NET program. The following steps show how to bind a PictureBox to the Photo field of the Employees table in the Northwind database. It assumes that the NWind.mdb database is located in the root C:\ directory. If it is located somewhere else, you will need to change the file location in the following source. Here are the steps:

  1. Create a new Visual Basic .NET Windows application.

  2. Add a reference to ADODB.

  3. Add the adoHelper helper class to the application. For the source code of the helper class, see the Bind OLE Picture application on the companion CD.

  4. Add a PictureBox to the form.

  5. In the Form1_Load event, add this code:

    Dim cn As New ADODB.Connection()
Dim rs As New ADODB.Recordset()
Dim c As New adoHelper()
cn.Open("Provider=Microsoft.Jet.OLEDB.4.0;" & _
   "Data Source=C:\NWIND.MDB")
rs.Open("Employees", cn)
Me.PictureBox1.Image = c.rsFieldToBitmap(rs.Fields("photo"))

That’s it! Press F5 and notice that the PictureBox retrieves the photo from the database, as shown in Figure 15-4.

Figure 15-4
Binding a PictureBox to an OLE image in a database.


Replacing Painting Functions

Remember how, in Visual Basic 6, you could draw a circle on a form using the Form.Circle method? Remember also how you could make the line stay on the form by setting the Form.AutoDraw property to True? Well, things have changed a little in Visual Basic .NET. The graphics methods Line, Circle, Print, PaintPicture, and Cls have been removed from Form and PictureBox. Instead, Windows Forms has access to the .NET GDI+ object library, which has a much richer set of graphics functions. While all this is very exciting, you’ve probably guessed that the graphics methods you’re used to are not supported in Windows Forms.

For example, suppose the Click event of a Button contains the following Visual Basic 6 code, which draws a line and a circle on the current form:

Me.Line (0, 200)-(2000, 200)
Me.Circle (300, 300), 100

The two methods cannot automatically be upgraded to Visual Basic .NET, so after upgrading, these statements become the following:

'UPGRADE_ISSUE: Form method Form1.Line was not upgraded
Me.Line(0, 200) To (2000, 200)
'UPGRADE_ISSUE: Form method Form1.Circle was not upgraded
Me.Circle(300, 300, 100)

The line and circle methods cause compile errors in Visual Basic .NET because they are not available as methods of the Form object. Where have the graphics methods gone? you may ask. They have been moved to the GDI+ graphics object. To use a GDI+ graphics object, you create a graphics object, draw onto the graphics object, and then dispose of the graphics object. For example, the following code paints a line and a circle on the form. Try it out by putting it in a Button_Click event in a Visual Basic .NET project.

Dim g As Graphics = Me.CreateGraphics
g.DrawLine(Pens.Black, 0, 20, 100, 20)
g.DrawEllipse(Pens.Black, 24, 24, 30, 30)
g.Dispose()

Figure 15-5 shows the effect of running this code.

Figure 15-5
Drawing a line and a circle in Visual Basic .NET.

This code draws a line of length 100 and a circle with a diameter of 24. Notice that graphics coordinates are in pixels. In Visual Basic 6, the graphics unit was twips by default. Also notice that, as in Visual Basic 6, the origin is the top left corner. In this example, we put the graphics code in a Click event to ensure that the form is visible when the code is run. There is no AutoRedraw property in Visual Basic .NET. If the window is hidden, minimized, or moved off the screen, the line and circle you worked so hard to draw are erased. Luckily, Windows Forms gives you a way to redraw your objects every time the form is repainted. You do this using the Form.Paint event. This event is called every time a portion of the form’s background has to be repainted. The following code shows how to paint a line and a circle using the Paint event. Notice that the system passes a graphics object to the event, so you don’t need to create one yourself.

Private Sub frmLineAndCircle_Paint(ByVal sender As Object, _
ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
   e.Graphics.DrawLine(Pens.Black, 0, 20, 100, 20)
   e.Graphics.DrawEllipse(Pens.Black, 24, 24, 30, 30)
End Sub

In most applications, Form.Paint is the best place to put graphics code, since the graphics are redrawn each time the form is repainted.

Now let’s look at something else you can do in the Paint event using GDI+. The following Paint event makes the form background fade from black to white:

Private Sub frmGradient_Paint(ByVal sender As Object, _
ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
   Dim rectForm As New Rectangle(New Point(0, 0), Me.ClientSize)
   Dim l As New Drawing2D.LinearGradientBrush(rectForm, Color.Black, _
      Color.White, Drawing.Drawing2D.LinearGradientMode.Horizontal)
   e.Graphics.FillRectangle(l, rectForm)
End Sub

Figure 15-6 shows the form with the gradient background. Notice that this form also has a TextBox and a PictureBox on it, and that these controls haven’t been painted with the gradient. This brings us to an important point: the graphics object passed to the Form.Paint event paints only the form background. Each control on the form also has a Paint event, which you can use to draw on that particular control.

Figure 15-6
Black to white background on a form.

In addition to circles, lines, and gradient backgrounds, the graphics object also has all the methods that were available in Visual Basic 6. To erase the background, use the Clear method; to paint a picture, use the PaintImage method; and to print text onto a form, use the DrawString method. These methods are incredibly flexible. For example, the following code draws the string “VB Rocks” onto the background of a form, using the contents of a PictureBox as a brush:

Dim bitmapBrush As New Drawing.TextureBrush(PictureBox1.Image)
Dim f As New Font("Arial", 60, FontStyle.Bold, GraphicsUnit.Pixel)
e.Graphics.DrawString("VB Rocks", f, bitmapBrush, 20, 20)

As you can see, the GDI+ methods are quite powerful.

Learn More About GDI+

What we’ve shown here just scratches the surface. GDI+ is a fully featured set of graphics classes. You can draw paths, rectangles, icons, and pictures. You can rotate and transform these objects, and you can use alpha blending to fade one color into another. You will find GDI+ in the System.Drawing namespace. To learn more, search for “Introduction to GDI+” and “Using GDI+ Managed Classes” in the Visual Basic .NET Help system.


Rewriting Clipboard Code

Visual Basic 6 has a Clipboard object that allows you to store and retrieve text and pictures to and from the Windows Clipboard. In Visual Basic .NET, you manipulate the Clipboard using the System.Windows.Forms.Clipboard namespace. The new Clipboard classes are more flexible than those in Visual Basic 6, in that they allow you to set and retrieve data in a particular format and query the contents of the Clipboard to see what formats it supports. Unfortunately, Clipboard code cannot be upgraded automatically. However, you’ll find it straightforward to implement the same functionality in Visual Basic .NET.

Let’s look at an example. Suppose you have a Visual Basic 6 form with two PictureBox controls on it, sourcePictureBox and destinationPictureBox. The following code sets and retrieves the text “VB Rocks” and then copies a picture from one PictureBox to the Clipboard and into a second PictureBox:

'Set and retrieve text
Dim s As String
Clipboard.SetText "VB Rocks"
s = Clipboard.GetText
'Set and retrieve a picture
Clipboard.SetData Me.sourcePictureBox.Picture
Me.destinationPictureBox.Picture = Clipboard.GetData

When this code is upgraded, the Clipboard code is left as is and is marked with upgrade warnings:

'UPGRADE_ISSUE: Clipboard method Clipboard.SetText was not upgraded.Clipboard.SetText("VB Rocks")

The upgraded code causes compile errors in Visual Basic .NET. To get the same functionality, we have to delete the upgraded code and replace it with code that uses the System.Windows.Forms.Clipboard objects:

'Set and retrieve text
Dim s As String
Dim stringData As IDataObject
Dim getString As New DataObject(DataFormats.StringFormat)
Clipboard.SetDataObject("VB Rocks", True)
stringData = Clipboard.GetDataObject()
If stringData.GetDataPresent(DataFormats.Text) Then
   s = stringData.GetData(DataFormats.Text, True)
End If
'Set and retrieve a picture
Dim pictureData As IDataObject
Clipboard.SetDataObject(Me.sourcePictureBox.Image)
pictureData = Clipboard.GetDataObject
If pictureData.GetDataPresent(DataFormats.Bitmap) Then
   Me.destinationPictureBox.Image = _
      pictureData.GetData(DataFormats.Bitmap, True)
End If

The Visual Basic .NET code improves upon the Visual Basic 6 code by checking for the existence of a compatible Clipboard format before setting the value of the string and the PictureBox image.


Using the Controls Collection

To add and remove controls at run time in Visual Basic 6, you can use the controls collection. For example, the following code adds a TextBox dynamically at run time:

Dim myControl As Control
Set myControl = Me.Controls.Add("VB.TextBox", "myControl")
myControl.Visible = True

This code adds a TextBox to the form and makes it visible. Figure 15-7 shows the result.

Figure 15-7
Adding a TextBox to a Visual Basic 6 application dynamically at run time.

It is also easy in Visual Basic 6 to remove a control dynamically. The following line of code removes the TextBox we just added:

Me.Controls.Remove "myControl"

One of the annoying things about the Visual Basic 6 model is that the controls collection has no IntelliSense, so you have to remember the methods, parameters, and ProgID of the control to add. There is no automatic upgrade for the controls collection, but it is easy to add and remove intrinsic controls in Visual Basic .NET. Here is the code for adding a TextBox to a form:

Dim c As Control
c = New TextBox()
c.Name = "myControl"
Me.Controls.Add(c)

In Windows Forms, controls are indexed by number, not by name. To remove a control by name, you have to iterate through the controls collection, find the control, and remove it. The following code shows how to remove the newly added control by name:

Dim c As Control
For Each c In Me.Controls
   If c.Name = "myControl" Then
      Me.Controls.Remove(c)
      Exit For
   End If
Next

Adding ActiveX controls dynamically at run time is a bit more work. As we discussed in Chapter 13, Visual Basic .NET creates wrappers for ActiveX controls. These wrappers must exist before a control can be added. In addition, most ActiveX controls have design-time licenses that must be present before the control can be created on the form. Visual Basic .NET compiles the license into the executable. These factors mean that the control must already be present in the project before it can be added dynamically at run time. One way to do this is to add a dummy form to the project and put all ActiveX controls that will be added dynamically onto this form. After you’ve created this dummy form, you can add the ActiveX control dynamically to any form in your project. The following code shows how to add a Windows Common Controls TreeView control dynamically to a form (the project already has a dummy form with a TreeView added):

Dim c As New AxMSComctlLib.AxTreeView()
c.Name = "myTreeView"
Me.Controls.Add(c)

The control can be removed dynamically in a manner similar to removing an intrinsic control:

Dim c As Control
For Each c In Me.Controls
   If c.Name = "myTreeView" Then
      Me.Controls.Remove(c)
      Exit For
   End If
Next


Using the Forms Collection

The forms collection in Visual Basic 6 is a collection of all open forms in the project. The most common uses of the forms collection are to determine whether a form is open, to iterate through the forms collection, and to close a form by name. The following Visual Basic 6 example shows how to iterate through the forms collection, determine whether a form called Form1 is open, and then unload Form1 if it is open:

Dim f As Form
For Each f In Forms
   If f.Name = "Form1" Then
      Unload f
      MsgBox "Form unloaded"
   End If
Next

The forms collection cannot be upgraded automatically, so if the previous code is upgraded, the collection is marked with an upgrade issue:

'UPGRADE_ISSUE: Forms collection was not upgraded.
For Each f In Forms

The only way to achieve the same effect in Visual Basic .NET is to implement your own forms collection. Luckily, this is easier than it sounds. First create a new Visual Basic .NET Windows application. Add a new module, and in the module put the following code (you can find this code on the companion CD in the file FormCollectionClass.vb):

Module FormsCollection

Public Forms As New FormsCollectionClass()
End Module
Class FormsCollectionClass : Implements IEnumerable
   Private c As New Collection()
   Sub Add(ByVal f As Form)
      c.Add(f)
   End Sub
   Sub Remove(ByVal f As Form)
      Dim itemCount As Integer
      For itemCount = 1 To c.Count
         If f Is c.Item(itemCount) Then
            c.Remove(itemCount)
            Exit For
         End If
      Next
   End Sub
   ReadOnly Property Item(ByVal index) As Form
      Get
         Return c.Item(index)
      End Get
   End Property
   Overridable Function GetEnumerator() As _
      IEnumerator Implements IEnumerable.GetEnumerator
      Return c.GetEnumerator
   End Function
End Class

This is the collection that will maintain your collection of forms. You need to make sure that each form is added to the collection when it is created and removed from the collection when it is disposed of. In the New event for Form1, insert the following line:

Forms.Add(Me)

In the Disposed event, insert this line:

Forms.Remove(Me)

That’s all you need to do to implement your forms collection. If your application has many forms, you’ll have to put the New and Dispose code in every one.

Let’s see how to use this collection in code. The following Visual Basic .NET example does exactly what the first Visual Basic 6 example in this chapter did: loop through the forms collection, determine whether a form called Form1 is open, and unload it if it is open.

Dim f As Form
For Each f In Forms
   If f.Name = "Form1" Then
      f.Close()
   End If
Next

You can use this forms collection to unload forms, loop through the collection of open forms, or simply check whether a particular form is open.

Do You Really Need a Forms Collection?

Before adding a forms collection to your project, you may want to ask yourself whether you really need it. One drawback is that you need to remember to add the code to every form in your application. If you forget to add it to a single form, that form will not be added to the collection. If you do use the forms collection, and you use it a lot, consider adding the New and Dispose code to the template from which every new form is created.

Some applications created with the Visual Basic Application Wizard contain code that loops through the forms collection and closes every form in the application. In many cases you can safely remove this code from the project and avoid having to create your own forms collection.


Upgrading PrintForm Code

Visual Basic 6 offers a simple way to print forms using the Form.PrintForm method. PrintForm is not perfect: some controls don’t print well, and the quality is not always picture-perfect. But for many people, it’s good enough. Unfortunately, Visual Basic .NET has no direct equivalent of PrintForm, and thus the Upgrade Wizard can’t upgrade PrintForm code. Don’t worry, though; we’ve found a replacement for you, using a PrintForm helper class. (For the source code to this class, see the PrintForm application on the companion CD.) Here are the steps to implement PrintForm behavior in Windows Forms:

  1. Create a new Visual Basic .NET Windows application.

  2. Add the PrintForm helper class.

  3. Add a Button to the default form, and in the Button_Click event insert the following code:

    Dim c As New PrintForm()
c.Print(Me)

  4. Run the application, and click the button. Bingo! Your form will be sent to the printer.

You should be aware of one limitation: the form to be printed must be the topmost form and must be entirely visible, since the class takes a “snapshot” of the form and prints it. In many cases this technique will be good enough, especially when you simply want to print the visible layout of the current form. As a bonus, the helper class also contains another function, getImageFromForm. This method copies the image of the form to an image object. You can use this to, say, copy the image from one form to another. Let’s see how this works by extending the previous example:

  1. Add a second form, Form2, to your application.

  2. Add a second Button to Form1, and in the Button2_Click event insert the following code:

    Dim c As New PrintForm()
Dim i As Image
i = c.getImageFromForm(Me)
Dim f2 As New Form2()
f2.Show()
f2.CreateGraphics.DrawImage(i, New Point(0, 0))

  3. Run the application, and click Button2. The application paints the image of Form1 onto Form2, as shown in Figure 15-8. The controls painted onto Form2 won’t work, since we’ve simply painted the form image. Nevertheless, it’s a convincing illusion.

    Figure 15-8
    Which is the real Form1?
Where Are the Print Functions?

The printing model in the .NET Framework is quite different from that in Visual Basic 6. If you’re looking for the printing functions, you’ll find them in the System.Drawing.Printing namespace. For help with printing programmatically from your application, search for the topic “Printing with the PrintDocument Component” in the Visual Basic .NET Help system.


Replacing Property Pages

Suppose you’re writing an image list user control, and you want to give users a way to add a collection of images. How do you do it? In Visual Basic 6, you would add a property page to your control. In the property page, you would write code that manages the collection. Property pages are useful because they allow you to work around the limitations of the Visual Basic 6 Property Browser, which can edit only strings, enums, numbers, colors, pictures, and fonts. Editing a collection of bitmaps is out of the question for the poor old Visual Basic 6 Property Browser. You’ll be pleased to know that Visual Basic .NET has a Property Browser that can edit any .NET variable type or class—property pages are no longer needed.

Property pages in user controls are not upgraded automatically. To enable your control to edit the values that used to be exposed in the control’s property pages, you need to add properties to your user control. The good news is that the Visual Basic .NET Property Browser automatically recognizes the types of your control’s properties and allows users to edit them in the Property Browser. In the case of a bitmap collection, you simply add the property to your user control, and the Visual Basic .NET Property Browser provides a bitmap collection editor. Let’s try this out. Add the following code to a Windows Forms user control:

Dim m_bitmapCollection() As Bitmap
Property BitmapCollection() As Bitmap()
   Get
      Return m_bitmapCollection
   End Get
   Set(ByVal Value As Bitmap())
      m_bitmapCollection = Value.Clone
   End Set
End Property

If you add the control to a form, you will notice that it has a new property, bitmapCollection, and that you can edit this property using a collection editor. Figure 15-9 shows this editor. It’s that’s simple.

Figure 15-9
Adding a bitmap collection to your user control.

There are property page editors for every .NET class. For example, if you add a targetDate property of type Date to your user control, the Property Browser automatically provides a date editor. Try putting this property code into your user control:

Private m_targetDate As Date
Public Property targetDate() As Date
   Get
      Return m_targetDate
   End Get
   Set(ByVal Value As Date)
      m_targetDate = Value
   End Set
End Property

The Property Browser automatically uses the date editor for the property, as shown in Figure 15-10.

Figure 15-10
Date editor provided by the Property Browser.

The advantage of showing properties in the Property Browser instead of in property pages is that in the Property Browser all of the properties for your control are visible and easily discoverable. Compare this with Visual Basic 6 property pages, which hide functionality from people who use your controls.

Writing Your Own Property Editors

The Windows Forms Property Browser can edit any .NET class or variable type. But what happens if you write a new class, say a Customer class, and you want users to be able to edit it in the Property Browser? Windows Forms gives you a way to provide this option by allowing you to write your own custom property editor that will be used whenever people edit properties of type Customer. To learn more about writing custom property editors, search for the following two topics in the Visual Basic .NET Help system: “Attributes and Design-Time Support” and “Implementing a UI Type Editor.”


Eliminating ObjPtr, VarPtr, and StrPtr

Visual Basic 6 supports the undocumented functions ObjPtr, StrPtr, and VarPtr. These functions return the memory address of the location where the corresponding object, variable, or string is stored. This memory address, also known as a pointer, can then be passed to Windows APIs that accept memory addresses as parameters. Not surprisingly, these functions are rarely used, and also not surprisingly, programmers can get into real trouble using them, since they bypass the memory management and safety features of Visual Basic 6.

There is no automatic upgrade for ObjPtr, VarPtr, and StrPtr, but there is a way to achieve the same functionality in Visual Basic .NET. Before continuing, we must warn you that manipulating memory is not a good practice. You shouldn’t need to do it; there is almost always a more appropriate workaround that uses safer techniques.

Okay, okay, if you really want to know how to get the memory address in Visual Basic .NET, we’ll show you. But remember, this stuff is dangerous! Let’s look at some examples. Create a new Visual Basic .NET console application. Our examples will use the default module. We will be using the System.Runtime.InteropServices namespace, so at the top of the module, add the following line of code:

Imports System.Runtime.InteropServices

Memory pointer functions are commonly used with the CopyMemory API, so let’s add a Declare statement for the API in the declarations section of our module:

Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(ByVal lpDest As Integer, ByVal lpSource As Integer, _
ByVal cbCopy As Integer)

After adding this statement, your module should look like the following:

Imports System.Runtime.InteropServices
Module Module1
    Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
    (ByVal lpDest As Integer, ByVal lpSource As Integer, _
    ByVal cbCopy As Integer)

    Sub Main()

    End Sub
End Module

We will be working with our examples inside the Sub Main method. In Visual Basic .NET, variables are garbage-collected and can in theory be moved around in memory arbitrarily by the Garbage Collector. As a result, getting the memory address of a variable poses a problem, since if the Garbage Collector decides to move the variable in memory, the memory address we just retrieved becomes invalid. Luckily, the Garbage Collector provides a method to “pin” the object in memory so that it won’t be moved around. The GCHandle.Alloc method gets a handle to the variable and pins it in memory. The handle’s AddrOfPinnedObject method then returns the memory address. Let’s see this in action. The following code creates an integer, pins it in memory, displays the memory address of the variable, and then unpins the handle. (You should always unpin the handle when you’re finished playing around with the memory address.)

Sub Main()
   Dim myInteger As Integer
   Dim handle As GCHandle = _
      GCHandle.Alloc(myInteger, GCHandleType.Pinned)
   Dim address As Integer = handle.AddrOfPinnedObject.ToInt32
   MsgBox("The memory address of myInteger is " & address)
   handle.Free()
End Sub

Let’s look at another example. This code creates two strings, gets the memory addresses of each, and then uses the CopyMemory API to copy one string into another:

Sub Main()
   Dim src, dest As String
   src = "VB Rocks"
   dest = "            "
   Dim srcHandle As GCHandle = _
      GCHandle.Alloc(src, GCHandleType.Pinned)
   Dim srcAddr As Integer = srcHandle.AddrOfPinnedObject.ToInt32
   Dim destHandle As GCHandle = _
      GCHandle.Alloc(dest, GCHandleType.Pinned)
   Dim destAddr As Integer = destHandle.AddrOfPinnedObject.ToInt32
   CopyMemory(destAddr, srcAddr, Len(src) * 2)
   srcHandle.Free()
   destHandle.Free()
   MsgBox(dest)
End Sub

If you run this code, you’ll see that the source string is copied into the destination string, which is then shown in a message box. Something to be aware of is that variables are stored differently in Visual Basic .NET than in Visual Basic 6. Notice that when you use the CopyMemory API, the third parameter is the number of bytes to copy, which is double the length of the string. In Visual Basic .NET, strings are stored as Unicode, so each character is stored in 2 bytes. Thus, when copying a string using APIs, you have to specify a number of bytes that is double the string length.


Conclusion

This chapter has looked at eight problems that require some redesign work when moving from Visual Basic 6 to Visual Basic .NET, and it has shown you how to make the necessary changes to your code. In all of the cases, the redesign is straightforward—you just have to know how to do it. The next chapter looks at more situations that require redesign, all related to upgrading MTS and COM+ services applications.


Upgrading COM+ Components

Chapter 16
Upgrading COM+ Components

COM+ services and Microsoft Transaction Server components are the backbone of most major Microsoft Visual Basic business applications. This chapter starts with an introduction to implementing COM+ services in Microsoft .NET and then moves on to describe how to upgrade basic COM+ components. Although the emphasis is on transactional objects and object construction, the information here applies to the upgrading of all kinds of COM+ components. From this chapter you should take away a basic understanding of how COM+ components are implemented in Visual Basic .NET, and you should have an idea of the effort required to upgrade your existing components.

note | This chapter is definitely not for someone who is not familiar with COM+. It makes no attempt to introduce the concepts behind the code. For that, a wealth of resources is available through Microsoft Press publications and the MSDN documentation, both online and included with Visual Basic .NET.
COM+ Application Types

To frame the discussion, let’s start by taking a look at COM+ applications. According to the Microsoft Developer Network (MSDN), there are three basic types of user-creatable COM+ applications. Each has particular features, advantages, and restrictions that make it appropriate for specific application architectures. The three types are as follows:

  • Server application A COM+ application that runs in its own process. Server applications can support all COM+ services.

  • Library application A COM+ application that runs in the process of the client that creates it. More specifically, the components in a library application are always loaded into the process of the creator. Library applications can use role-based security but do not support remote access or queued components.

  • Application proxy A set of files containing registration information that allows a client to remotely access a server application. When run on a client computer, an application proxy file writes information about the COM+ server application—including its CLSID, ProgID, RemoteServerName, and marshaling information—to the client computer. The server application can then be accessed remotely from the client computer.

    note

    Server applications differ from library applications in how they are instantiated. A library application’s components are created in the same process as the calling application. A server application’s components are created out of process. When you attempt to instantiate an object remotely, it is not possible to create that component in the caller’s process; therefore, only server applications are capable of instantiation by remote applications, due to their out-of-process nature.

The vast majority of COM+ applications fall into the first two categories, and those will be the focus of the development content in this chapter. The third, application proxy, is a specialization of the server application and can be treated as a logical extension of the discussion contained here.


Using COM+ in Visual Basic .NET

Before getting into the how-to of upgrading, it’s important to take a step back and investigate how COM+ services are implemented in .NET. For those of you who are curious as to what has happened to COM+ in .NET, fear not. COM+ is still the same sprightly beast that it was before you installed Visual Studio .NET. The .NET Framework exposes all of the existing COM+ services to Visual Basic .NET applications. Table 16-1 lists the COM+ services available to .NET developers.

Table 16-1 COM+ Services Supported in Visual Basic .NET
Service | Description
Automatic transaction processing | Applies declarative transaction processing features.
BYOT (bring your own transaction) | Allows a form of transaction inheritance.
COM Transaction Integrator (COMTI) | Encapsulates Customer Information Control System (CICS) and Internet Mail Service (IMS) applications in Automation objects.
Compensating Resource Manager | Applies atomicity and durability properties to nontransactional resources.
Just-in-time activation | Activates an object on a method call and deactivates it when the call returns.
Loosely coupled events | Manages object-based events.
Object construction | Passes a persistent string value to a class instance on construction of the instance.
Object pooling | Provides a pool of ready-made objects.
Private components | Protects components from out-of-process calls.
Queued components | Provides asynchronous message queuing.
Role-based security | Applies security permission based on role.
SOAP services | Publishes components as XML Web services.
Synchronization | Manages concurrency.
XA interoperability | Supports the X/Open transaction processing model.

Despite implementation differences, underneath the .NET Framework the stalwart heart of COM+ still beats. The .NET Framework COM+ services are built on top of COM+, rather than being a substitute for it. These are the same services that you are already familiar with; they are just implemented differently.

The fact that COM+ is a set of native application services (it does not run on the common language runtime with Visual Basic .NET) raises interesting questions: Is this COM interop, and is there a performance penalty? The short and simple answer is no. COM+ services are able to call directly into the runtime environment and vice versa without COM interop wrappers or variable marshaling overhead. The technical details as to why are too involved to go into here (this is not to say that the authors don’t need to be reminded of them periodically), but rest assured—you will not pay a performance penalty for choosing to use COM+ services through the .NET Framework instead of through Visual Basic 6. In fact, thanks to the new power of Visual Basic .NET, more COM+ services are available to you than ever before, and you have more leverage to use them. In addition, Visual Basic .NET makes implementing COM+ applications far easier than in Visual Basic 6 and gives the developer a significant amount of control over component deployment issues.

note | At this point, you might be wondering why COM interop doesn’t come into play with COM+. Isn’t COM+ the same as COM? Yes and no. COM+ was designed as an application infrastructure for developing COM components for the enterprise. While there is a high level of integration between the two technologies, COM+ is not really COM. This difference enables COM+ to call directly into the runtime (and vice versa) without the overhead and marshaling associated with a COM interop method call.

What about the differences between COM+ in Visual Basic 6 and COM+ in .NET? While they do exist, COM+ is COM+ no matter how you look at it. On the other hand, an ActiveX DLL is by no means a managed assembly (a Visual Basic .NET DLL), and this has specific benefits (see the discussion of attributes later in this chapter). Visual Basic .NET is designed to provide more integrated support for COM+, above and beyond that found in Visual Basic 6. To help illustrate the differences, let’s take a look at how COM+ applications are built in Visual Basic .NET.

COM+ Requirements in Visual Basic .NET

By now you should be familiar with the namespace structure, if not the content, of the .NET Framework. Support for COM+ services is implemented in the System.EnterpriseServices namespace. Any component wishing to take advantage of COM+ (aside from adding a reference to the System.EnterpriseServices.dll assembly) must meet all of the following requirements:

  • It must inherit from the ServicedComponent class or from another class derived from ServicedComponent.

  • It must apply attributes specifying the COM+ services supported by the class.

  • The assembly containing the COM+ component must have a strong name.

The sections that follow discuss each of these requirements.

Inheriting from the ServicedComponent Class

We have mentioned that there are differences between the implementation of COM+ in Visual Basic 6 and its implementation in Visual Basic .NET. One of these differences is that in Visual Basic 6 any class can implement a COM+ service by implementing specific interfaces from the COM+ services type library. The downside to this approach is that you have to implement a great deal of boilerplate code yourself. In .NET, the situation is quite different. All components that want to leverage COM+ services need to inherit their objects from the ServicedComponent class. This class provides a framework for your custom component that enables context sharing between COM+ and .NET Framework classes. In addition, it provides a host of features that enable self-registration and support for new .NET features like remoting and XML Web services.

This change means that your COM+ classes take on the following structure. (Note that this is by no means a complete implementation.)

Imports System.EnterpriseServices

' A trivial COM+ Class
Public Class COMPlusClass
   Inherits ServicedComponent ' Required to support COM+

   Public Sub New()
      MyBase.New()
   End Sub
End Class

Instead of implementing external interfaces to handle events such as Construct, Activate, and Deactivate, you use member functions of the ServicedComponent class. To implement specific features that you need, you override the base class functionality. This makes your class look like the following:

Imports System.EnterpriseServices

' A trivial COM+ Class
Public Class COMPlusClass
   Inherits ServicedComponent ' Required to support COM+

   Public Sub New()
      MyBase.New()
   End Sub

   Protected Overrides Sub Construct(ByVal ConstructionString _
      As String)
   End Sub

   Protected Overrides Sub Activate()
   End Sub

   Protected Overrides Sub Deactivate()
   End Sub
End Class

Starting to look familiar yet? It’s still not even close to being complete, however. We have created the base framework for our COM+ class, but we need to provide more information about what we intend it to do and what services it supports. That is where attributes come into play.

Working with Attributes

Attributes are a new feature in Visual Basic .NET. Attributes are specialized classes that are applied to code elements, allowing you to provide descriptive metadata about your component. At compile time, attributes are emitted into metadata that is available to the common language runtime or to custom tools and applications through the System.Reflection namespace. You can explicitly mark objects and methods with attributes that affect their behavior.

You attach an attribute to a component by preceding the component with a reference to the attribute and providing any relevant parameters or flags. This call to the constructor is placed within angle brackets (<>) in Visual Basic. Whenever you create a new application in Visual Basic .NET, a file called AssemblyInfo.vb is created. This file contains a set of common attributes for your project. The file typically looks like this:

Imports System.Reflection
Imports System.Runtime.InteropServices

' General Information about an assembly is controlled through
' the following
set of attributes. Change these attribute values to
' modify
 the information  associated with an assembly.

' Review the values of the assembly attributes

<Assembly: AssemblyTitle("")>
<Assembly: AssemblyDescription("")>
<Assembly: AssemblyCompany("")>
<Assembly: AssemblyProduct("")>
<Assembly: AssemblyCopyright("")>
<Assembly: AssemblyTrademark("")>
<Assembly: CLSCompliant(True)>

' The following GUID is for the ID of the typelib if this project
' is exposed to COM

<Assembly: Guid("17465E40-15E9-4F52-8954-CF931AC54747")>

' Version information for an assembly consists of the following
' four values:

'      Major Version
'      Minor Version
'      Build Number
'      Revision

' You can specify all the values or you can default the Build and
' Revision Numbers by using the '*' as shown below:

<Assembly: AssemblyVersion("1.0.*")>

In addition to this standard set of attributes, the System.EnterpriseServices namespace defines a host of attributes specific to COM+. These attributes provide a combination of services. Some specify feature support; others specify your application’s default COM+ settings. Table 16-2 contains a list of COM+ specific attributes.

Table 16-2 COM+ Specific Attributes
Attribute | Scope | Unconfigured Default Value | Configured Default Value
ApplicationAccessControl | Assembly | False | True
ApplicationActivation | Assembly | Library | No default
ApplicationID | Assembly | Generated GUID | No default
ApplicationName | Assembly | Assembly name | No default
ApplicationQueuing | Assembly | No default | No default
AutoComplete | Method | False | True
ComponentAccessControl | Class | False | True
COMTIIntrinsics | Class | False | True
ConstructionEnabled | Class | False | True
Description | Assembly Class Method Interface | No default | No default
EventClass | Class | No default | FireInParallel = False AllowInprocSubscribers = True PublisherFilter = Null
EventTrackingEnabled | Class | False | True
ExceptionClass | Class | No default | No default
InterfaceQueuing | Class Interface | False | True
JustInTimeActivation | Class | False | True
LoadBalancingSupported | Class | False | True
MustRunInClientContext | Class | False | True
ObjectPooling | Class | False | True
PrivateComponent | Class | No default | Private
SecureMethod | Assembly Class Method | No default | No default
SecurityRole | Assembly Class Interface | No default | No default
Synchronization | Class | False | SynchronizationOption.Required
Transaction | Class | False | TransactionOption.Required TransactionIsolationLevel.Serializable Timeout = infinite

Let’s see how our sample COM+ class looks with some of these important attributes:

Imports System.EnterpriseServices

<Assembly: ApplicationName("Sample COMPlus Application")>
<Assembly: ApplicationActivation(ActivationOption.Library)>

' A trivial COM+ Class with attributes

<Transaction(TransactionOption.Required), _
   ConstructionEnabled([default]:=" This is a test"), _
   ObjectPooling()> _

Public Class COMPlusClass

   Inherits ServicedComponent ' Required to support COM+

   Public Sub New()
      MyBase.New()

   End Sub

   Protected Overrides Sub Construct(ByVal ConstructionString _
      As String)
      Trace.WriteLine("Construct() : """ & ConstructionString & """")
   End Sub

   Protected Overrides Sub Activate()
      Trace.WriteLine("Activate()")
   End Sub

   Protected Overrides Sub Deactivate()
      Trace.WriteLine("Activate()")
   End Sub

   <AutoComplete()> Public Sub DoTasks()

   End Sub
End Class

This example demonstrates the use of all three scopes (method, class, and assembly) of attributes and shows how you can specify support for various COM+ features. Some of these attributes are fairly obvious and are not too big a leap from Visual Basic 6 (the Transaction attribute, for example). Others highlight the ability of attributes to better control deployment. An example of the latter is the ConstructionEnabled attribute. This particular attribute allows the developer to specify a default construction string that will be visible (and modifiable) in the Component Services Microsoft Management Console (MMC).

While other COM+ attributes provide only default settings for use during self-registration, the AutoComplete attribute provides a fundamentally new twist on programming with transactions. Marking a method with AutoComplete causes SetCommit to be called automatically if the method terminates normally. This saves the developer a fair bit of work. If an unexpected error occurs (in the form of an unhandled exception), SetAbort is called automatically. You can still call SetAbort on your own based on a logical violation, but it is no longer necessary for you to explicitly commit or abort the transaction in every circumstance. This attribute reduces a great deal of repetition by requiring code only for the extraordinary cases. The following example shows how simple implementing transactions now becomes with the AutoComplete attribute:

Imports System.EnterpriseServices
<Transaction(TransactionOption.Required)>
Public Class Sample COMPlusClass

   Inherits ServicedComponent

   <AutoComplete()>Public Function DoTransactionTask() As Boolean

     ' Do Your stuff.
     ' Calls set complete automatically if no exception is generated

   End Function
End Class

All COM+ settings can be set using attributes on a class or method, instead of using the property pages found in Visual Basic 6. This approach makes settings more explicit and less prone to accidental change.

We’ve only been able to scratch the surface of the topic of attributes here, but hopefully this discussion has given you a decent idea of how to use them. Attributes are used extensively throughout Visual Basic .NET to implement all sorts of functionality—not only COM+ features but also newer features like XML Web services.

Creating a Strong Name for Your Assembly

If you are registering a component as part of a COM+ application, you must provide a strong name for the assembly. A strong name consists of an assembly’s identity—its simple text name, version number, and culture information (if provided)—strengthened by a public key and a digital signature generated over the assembly. Assemblies with the same strong name are expected to be identical.

Generating Key-Pair Files

To sign an assembly with a strong name, you must have a public/private key pair. This public and private cryptographic key pair is used during compilation to create a strong-named assembly. You can create a key pair using the Strong Name tool (Sn.exe). Key-pair files usually have an .snk extension.

note | The Sn.exe tool is provided with the .NET Framework SDK and Visual Basic .NET, but it is not normally available from the standard command-line prompt. To use the tool, on the Programs menu, point to Microsoft Visual Studio .NET and then to Visual Studio .NET Tools, and choose Visual Studio .NET Command Prompt. This command-line tool has all of the necessary environment variables set to run all of the Framework SDK and Visual Studio .NET tools.

To create a key pair, type the following at the command prompt:

sn –k <filename>

In this command, filename is the name of the output file containing the key pair. The following example creates a key pair called COMPlusClass.snk.

sn –k COMPlusClass.snk

Once you create the key pair, you must put the file where Visual Basic can find it. When signing an assembly with a strong name, Visual Basic looks for the key file in the directory containing the Visual Studio solution. Put the key file in the appropriate project directory, and add the following assembly attribute near the top of the COMPlusClass.vb file:

<Assembly: AssemblyKeyFile("COMPlusClass.snk")>
note | Although we chose to name the key file after the class it was created for, the key file itself has nothing specifically to do with the class. The key file is applied at the assembly level to ensure that the application has a unique signature. That is, after all, why the strong name feature exists. It allows for the definition of unique applications regardless of any possible naming conflicts. This uniqueness becomes very important when dealing with applications that have machine scope.
Registering COM+ Applications

Now that you know all of the requirements for COM+ components, it’s time to talk about how to register the components with COM+. There are two ways to register .NET COM+ applications. The first is easier: dynamic registration. This method requires the least amount of work on your part and will be done for you as long as you specify the appropriate attributes in your assembly manifest. The second option is manual registration. It requires you to do a little more work but is not substantially more difficult than dynamic registration.

Dynamic Registration

Dynamic registration is a process whereby COM+ components are self-registered. Other than specifying a set of assembly attributes, you need only make sure that your COM+ application (whether it is in a separate assembly or not) resides in your application’s bin directory. Fusion will find the assembly and automatically register the COM+ application, using all of the attributes you have specified.

What Is Fusion?

Fusion is the mechanism that enables the common language runtime to handle run-time assembly binding. It is responsible for finding an appropriate assembly that contains the objects required by your application. This is the essential tool that enables side-by-side application deployment and eliminates the need for registering assemblies and type libraries. While fusion is a complex concept, all you really need to know is that it is responsible for binding your application’s assemblies at run time and makes it possible to eliminate DLL conflicts from Visual Basic .NET.

As an example, we have provided the COMPlusClass project on the companion CD. This project contains one form and one COM+ component. Running the project brings up the main form, shown in Figure 16-1.

Figure 16-1
COMPlusClass sample application.

Clicking the Go button causes the COMPlusClass to be instantiated. When this happens, the component is automatically registered in COM+. You can see this in the Component Services MMC, which should look something like Figure 16-2.

Figure 16-2
Installed COMPlusClass application in COM+.

It can’t get much easier than that, can it? If you check the properties of the COM+ application in the MMC, you will see the effects of the attributes you specified in the application itself. Replacing the application defined in COM+ is as simple as deleting it from the Component Services console and running the application again.

Manual Registration

Manually registering an assembly can be done for both library and server applications. You register an application manually by running the RegSvcs.exe utility on your application’s assembly. Here is the general syntax of this command:

regsvcs [assemblyname]

This utility does the following:

  1. Loads the assembly.

  2. Registers the assembly.

  3. Generates a type library.

  4. Registers the type library.

  5. Installs the type library into the request application.

  6. Configures the class.

You can use the RegSvcs.exe utility to customize the registration process and also as a general maintenance utility that you use to remove COM+ applications.


Upgrading COM+ Components

Unfortunately, because a Visual Basic .NET COM+ services application has to be implemented in quite a different manner than a Visual Basic 6 COM+ services application, the Upgrade Wizard cannot do much for your Visual Basic 6 COM+ components. It upgrades the business logic, database code, and other parts of your application, but it doesn’t upgrade the transaction attributes. The implementation differences are not a one-to-one mapping, so upgrading the parts dealing with COM+ support is best left to the developer. This leaves you with a fair bit of work to do on your own. To better illustrate what is going on here, let’s look at a Visual Basic 6 sample called SimpleTransaction, included on the companion CD. This is an ActiveX DLL project with a single class, SimpleClass, that is marked as RequiresNewTransaction. The SimpleClass.cls code looks like this:

Implements ObjectControl
Implements IObjectConstruct

Dim ctxt As ObjectContext
Dim connStr As String
Const m_module As String = "SimpleTransaction.SimpleClass"

Private Sub IObjectConstruct_Construct(ByVal pCtorObj As Object)
   connStr = pCtorObj.ConstructString
End Sub

Private Sub ObjectControl_Activate()
   Set ctxt = GetObjectContext(m_module)
End Sub

Private Function ObjectControl_CanBePooled() As Boolean
   ObjectControl_CanBePooled = True
End Function

Private Sub ObjectControl_Deactivate()
End Sub

Public Sub DoTasks()
   On Error GoTo HandleError
   Dim conn As Connection
   Set conn = New Connection

   Dim sqlCmd As String
   sqlCmd = "This is a sample query"

   conn.Open connStr
   conn.Execute sqlCmd

   ctxt.SetComplete
   Exit Sub

HandleError:
   ctxt.SetAbort
End Sub

After running this sample project through the Upgrade Wizard, you have a Visual Basic .NET class that looks like this:

Option Strict Off
Option Explicit On
Public Class SimpleClass
   Implements COMSVCSLib.ObjectControl
   Implements COMSVCSLib.IObjectConstruct

   Dim ctxt As COMSVCSLib.ObjectContext
   Dim connStr As String

   Const m_module As String = "SimpleTransaction.SimpleClass"

   Private Sub IObjectConstruct_Construct(ByVal pCtorObj As Object) _
     Implements COMSVCSLib.IObjectConstruct.Construct
     ' UPGRADE_WARNING: Couldn't resolve default property of object
     ' pCtorObj.ConstructString...
     connStr = pCtorObj.ConstructString
   End Sub

   Private Sub ObjectControl_Activate() Implements _
     COMSVCSLib.ObjectControl.Activate
     ctxt = COMSVCSLibAppServer_definst.GetObjectContext(m_module)
   End Sub

   Private Function ObjectControl_CanBePooled() As Boolean _
     Implements COMSVCSLib.ObjectControl.CanBePooled
     ObjectControl_CanBePooled = True
   End Function

   Private Sub ObjectControl_Deactivate() _
         Implements COMSVCSLib.ObjectControl.Deactivate
   End Sub

   Public Sub DoTasks()
      On Error GoTo HandleError
      Dim conn As ADODB.Connection
      conn = New ADODB.Connection

      Dim sqlCmd As String
      sqlCmd = "This is a sample query"

      conn.Open(connStr)
      conn.Execute(sqlCmd)

      ctxt.SetComplete()
   Exit Sub

HandleError:
     ctxt.SetAbort()
   End Sub
End Class

As you can see, the Upgrade Wizard leaves the work of implementing the COM+ parts of the class to you. While this task is quickly accomplished for this simple example, you should be aware that bigger applications will take time if the classes have a large set of transactional components. You will need to refer to the Visual Basic 6 version of each class to ensure that you transfer the correct transactional attributes to Visual Basic .NET. Otherwise, you may see unexpected and potentially undesirable behavior.

Using the previous example, let’s walk through the steps necessary to get the class working:

  1. Add a reference to System.EnterpriseServices.

  2. Change the class to inherit from EnterpriseServices.ServicedComponent, and remove all COMSVCS Implements statements.

  3. Add the necessary COM+ attributes to the class (Transaction, ConstrutionEnabled, ObjectPooling, and so on).

  4. Change the COM+ interface methods Activate, Construct, and Deactivate to be member methods that override base methods on ServicedComponent.

  5. Add the AutoComplete attribute to the DoTasks method. Remove the explicit commit and abort code.

After you’ve completed all of these steps, the class looks like this:

Option Strict Off
Option Explicit On

Imports System
Imports System.EnterpriseServices

<Transaction(TransactionOption.RequiresNew), ConstructionEnabled(), ObjectPooling()> Class SimpleClass
   Inherits ServicedComponent

   Dim connStr As String
   Const m_module As String = "SimpleTransaction.SimpleClass"

   Protected Overrides Sub Construct(ByVal constructionString _
      As String)
      connStr = constructionString
   End Sub

   <AutoComplete()> Public Sub DoTasks(ByVal fail As Boolean)
      Dim conn As ADODB.Connection
      conn = New ADODB.Connection()

      Dim sqlCmd As String

      sqlCmd = "This is a sample query"

      conn.Open(connStr)
      conn.Execute(sqlCmd)

       If fail Then ContextUtil.SetAbort()
   End Sub
End Class

Notice that the DoTasks method does not explicitly commit or abort the transaction except when a logical condition is true. This helps to demonstrate that you can still explicitly commit or abort your transactions. If you encounter a method in which the commit and abort logic is too tangled to reasonably remove, you can forgo using the AutoComplete attribute altogether and commit your transactions manually.


Making .NET and COM Components Work Together

Previous chapters have discussed issues regarding COM interop and how best to handle it in your applications. Although these discussions were directed at standard COM components, much of the information also applies to using COM+ applications from .NET. It is possible to import server, library, and proxy applications into your Visual Basic .NET projects. The imported objects are used just as they are in any other COM interop scenario. In addition, if your application has a ServicedComponent class that calls into the imported COM+ application, the context will propagate transparently across the interop boundary. You need to take into account some special considerations if you intend for your .NET serviced components to work with COM clients:

  • Avoid using parameterized constructors.

  • Avoid using static methods.

  • Define event-source interfaces in managed code.

  • Include HRESULTs in user-defined exceptions.

  • Supply GUIDs for types that require them.

  • Expect inheritance differences.


Conclusion

Upgrading your COM+ application to Visual Basic .NET is not a totally automatic process. The Upgrade Wizard does the hard work, upgrading your application structure and business logic, but legwork on your part is necessary to add the ServicedComponent Inherits statement, the COM+ attributes, and calls to the SetComplete and SetAbort methods.

We’ve covered how to upgrade COM+ (and Microsoft Transaction Server) transactional components; however, there is a lot more to COM+ services than simply transactions. So in a way, we are just scratching the surface of what is possible. To learn more about using COM+ services, see the topics “Serviced Component Overview,” “Writing Serviced Components,” and “Summary of Services” in the Visual Basic .NET Help.


Upgrading VB Application Wizard Projects

Chapter 17
Upgrading VB Application Wizard Projects

This chapter looks specifically at upgrading projects created with the VB Application Wizard. Because the wizard generates the same forms and modules for the applications it creates, each project will have the same problems that can be fixed in the same way. Of course, the wizard creates shell projects that are used as a basis for development. Here we’ll discuss only the issues common to these shell projects generated by the wizard—not ones that may occur with code you have subsequently added.

When you create a new project in Microsoft Visual Basic 6, the New Project dialog box gives you a number of choices as to the type of project to create. The most common choice is Standard EXE, followed by ActiveX DLL. These create empty projects, to which you have to add menus, toolbars, and the corresponding logic. Since many projects have the same basic components, Visual Basic 6 provides a VB Application Wizard that generates many of these components for you. This wizard starts when you select VB Application Wizard as the project type in the New Project dialog box, as shown in Figure 17-1.

Figure 17-1
Starting the VB Application Wizard from the New Project dialog box.

The VB Application Wizard asks you a number of questions about the project you want to create, such as the type of interface you want the project to have, the menus and submenus you need, whether strings will be stored in a resource file, and what data access forms the application will have. After you answer these questions, the wizard creates a project that is prepopulated with commonly used forms and classes. This project then becomes a template for your application. You create more forms and classes and add the business logic. Table 17-1 lists the project items the wizard creates.

Table 17-1 Project Items Created by the VB Application Wizard
Project Item | Description
frmAbout form | frmAbout is the form that opens when users choose About from the Help menu.
frmLogin form | frmLogin is a simple username/password login form that opens when the application first starts.
frmMain form | Each project created with the VB Application Wizard has a form called frmMain. This is the primary form of the application.
frmSplash form | frmSplash is a splash screen that shows after the frmLogin form but before frmMain opens.
frmBrowser | An application for which you specify Internet connectivity has a frmBrowser form. This form contains a WebBrowser ActiveX control.
Data forms | Data forms are added to display and edit information from a database.
Module1 module | Each project created with the VB Application Wizard has a module called Module1. This module has a Sub Main method, the startup object that opens the frmMain form.
frmDocument form | Each MDI interface project has a form called frmDocument; this is the MDI child form.
frmOptions form | frmOptions is a tabbed dialog form for setting application options. Because this form upgrades without issues, we won’t discuss it any further in this chapter.

Your project created with the VB Application Wizard may have all or some of the project items listed in Table 17-1, depending on the options you selected in the wizard. Each of these project items has its own unique set of upgrade issues. Let’s look at these items and see what you have to do to get them working in Visual Basic .NET.


App.Revision

Although App.Revision isn’t actually a project item, the VB Application Wizard projects use it in several places, so we’ll discuss it right at the beginning. In Visual Basic 6, it is common to concatenate the three App object properties—App.Major, App.Minor, and App.Revision—together to create a version number for the application. For example, in frmAbout, you will find the following line of code in Form_Load:

lblVersion.Caption = "Version " & App.Major & "." & App.Minor & _
   "." & App.Revision

Visual Basic .NET does not have an App object, so the Upgrade Wizard chooses the most appropriate upgrade for each property. App.Major is upgraded to

System.Diagnostics.FileVersionInfo.GetVersionInfo( _
   System.Reflection.Assembly.GetExecutingAssembly.Location _
   ).FileMajorPart

App.Minor is upgraded to

System.Diagnostics.FileVersionInfo.GetVersionInfo( _
   System.Reflection.Assembly.GetExecutingAssembly.Location _
   ).FileMinorPart

However, there is no equivalent for App.Revision, so the Upgrade Wizard leaves it as is, which creates a compile error in Visual Basic .NET. For example, the Visual Basic 6 line

lblVersion.Caption = "Version " & App.Major & "." & App.Minor & _
   "." & App.Revision

upgrades to

lblVersion.Text = "Version " & _
   System.Diagnostics.FileVersionInfo.GetVersionInfo( _
   System.Reflection.Assembly.GetExecutingAssembly.Location _
   ).FileMajorPart _
   & "." & System.Diagnostics.FileVersionInfo.GetVersionInfo( _
   System.Reflection.Assembly.GetExecutingAssembly.Location _
   ).FileMinorPart & "." & App.Revision

There are two ways to fix the compile error. The first and easiest is simply to remove the App.Revision part of the line:

& "." & App.Revision

After this change, only the major and minor parts of the version will be shown. Here is what the code just shown looks like after the App.Revision part is removed:

lblVersion.Text = "Version " & System.Diagnostics.FileVersionInfo.GetVersionInfo( _
   System.Reflection.Assembly.GetExecutingAssembly.Location _
   ).FileMajorPart _
   & "." & System.Diagnostics.FileVersionInfo.GetVersionInfo( _
   System.Reflection.Assembly.GetExecutingAssembly.Location _
   ).FileMinorPart

While this is a good option, Visual Basic .NET also has a property that returns the entire version number of the application in 0.0.0.0 format. This property is the best way to create a version number in Visual Basic .NET, since it gives the full version information. Here is the replacement code:

lblVersion.Text = "Version " & Application.ProductVersion

You’ll also notice that this line is shorter than the Visual Basic 6 version. You can use this line as a replacement for any code that concatenates together App.Major, App.Minor, and App.Revision to generate a version number.

What’s Up with Versioning in Visual Basic .NET?

Visual Basic 6 has three versioning properties: App.Major, App.Minor, and App.Revision. You can choose to make App.Revision auto-increment so that the revision number increases each time you compile the project. Although this is a simple model, it is out of step with Windows versioning, which supports four versioning properties: Major, Minor, Revision, and Build. When a Visual Basic 6 project is compiled, App.Revision is mapped to the Windows Build property and the Windows Revision property is left blank. Visual Basic .NET supports the four Windows versioning properties. The version number is set using the AssemblyVersion attribute in the AssemblyInfo.vb file. By default, the revision and build are set to be auto-incrementing based on the time of compilation—when combined they are a timestamp giving the date and time of compilation.


frmAbout Form

The VB Application Wizard adds the frmAbout form to your project when you choose to add an About box standard form to the application. The About box opens when the user chooses About from the Help menu. It shows the version information for the application and has a command button that opens the System Information application. There is one compile error that you need to fix after upgrading. It is in the Form_Load event, and it occurs because the wizard uses the App.Revision property to generate the version information shown in the box. As we discussed in the previous section, you should replace the code

lblVersion.Text = "Version " & _
   System.Diagnostics.FileVersionInfo.GetVersionInfo( _
   System.Reflection.Assembly.GetExecutingAssembly.Location _
   ).FileMajorPart _
   & "." & System.Diagnostics.FileVersionInfo.GetVersionInfo( _
   System.Reflection.Assembly.GetExecutingAssembly.Location _
   ).FileMinorPart & "." & App.Revision

with the following:

lblVersion.Text = "Version " & Application.ProductVersion

In the StartSysInfo procedure, you’ll also find a warning that Dir has a new behavior. This behavior difference applies to the Dir method when it is used to return a list of directories. In this case, since Dir is being used to determine whether the file Msinfo32.exe exists, the warning can be ignored, since it applies only to directories.


frmLogin Form

The frmLogin form is added to your project when you choose the option to add a login dialog box to the application. This form works perfectly after upgrading.


frmMain Form

The application’s primary form is frmMain. For multiple document interface (MDI) applications, it is an MDIForm; for single document interface (SDI) and Explorer applications, it is a standard form. Most of the modifications you have to complete for upgraded VB Application Wizard projects involve this form.

API Declare Statements

All projects generated by the VB Application Wizard contain an API Declare statement for OSWinHelp. MDI applications also contain a Declare statement for the SendMessage API. Here are the two Declare statements in Visual Basic 6:

Private 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

Private Declare Function OSWinHelp% Lib "user32" Alias "WinHelpA" _
   (ByVal hwnd&, ByVal HelpFile$, ByVal wCommand%, dwData As Any)

After upgrading, both of these Declare statements cause compile errors, since each of them passes variables as As Any. Here are the upgraded Declare statements:

Private Declare Function SendMessage Lib "user32"  Alias _
   "SendMessageA"(ByVal hwnd As Integer, ByVal wMsg As Integer, _
   ByVal wParam As Integer, ByVal lParam As Any) As Integer

Private Declare Function OSWinHelp Lib "user32"  Alias "WinHelpA"( _
   ByVal hwnd As Integer, ByVal HelpFile As String, _
   ByVal wCommand As Short, ByRef dwData As Any) As Short

The SendMessage API is not used in VB Application Wizard projects, so you can simply remove the declaration for SendMessage. The As Any parameter for OSWinHelp should be changed to String, since it is used to pass the name of the Help file. After removing the SendMessage API and changing the OSWinHelp API, the declare statement for the API looks like this:

Private Declare Function OSWinHelp Lib "user32" Alias "WinHelpA" ( _
   ByVal hwnd As Integer, ByVal HelpFile As String, _
   ByVal wCommand As Short, ByRef dwData As String) As Short
note

Although not discussed here, the correct Visual Basic .NET declaration for SendMessage is:

Private 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
mnuHelpAbout_Click Event Procedure

If you haven’t added an About box to the application, the event for the About menu item, mnuHelpAbout_Click, has code that opens a message box with the application version:

MsgBox "Version " & App.Major & "." & App.Minor & "." & App.Revision

After upgrading, this code causes an error because it uses App.Revision to generate the application version. It upgrades to

MsgBox("Version " & _
   System.Diagnostics.FileVersionInfo.GetVersionInfo( _
   System.Reflection.Assembly.GetExecutingAssembly.Location _
   ).FileMajorPart _
   & "." & System.Diagnostics.FileVersionInfo.GetVersionInfo( _
   System.Reflection.Assembly.GetExecutingAssembly.Location _
   ).FileMinorPart & "." & App.Revision)

As we discussed in the section on App.Revision, you should replace this line with the following:

MsgBox("Version " & Application.ProductVersion)
App.HelpFile

App.HelpFile is used in several places in the frmMain form. Windows Forms has a new system for displaying Help that is not compatible with Visual Basic 6. Because of this incompatibility, the App.HelpFile property is not upgraded and causes a compile error after upgrading. If your application uses context-sensitive Help, you will have to reimplement it in Visual Basic .NET. If your application simply shows Help contents and a Help index, you can modify your application to show them. Simply replace the instances of App.HelpFile with the name of the Help file.

For example, in the mnuHelpSearchForHelpOn_Click event handler, the line

nRet = OSWinHelp(Me.hwnd, App.HelpFile, 261, 0)

is upgraded to

nRet = OSWinHelp(Me.Handle.ToInt32, App.HelpFile, 261, 0)

App.HelpFile generates a compile error. Suppose that your application’s Help file is called C:\MyProject\MyProject.hlp. You would modify the line to read as follows:

nRet = OSWinHelp(Me.Handle.ToInt32, "c:\MyProject.hlp", 261, 0)

If your application doesn’t contain a Help file, you can replace all instances of App.HelpFile with an empty string. For example, you would modify the line just shown to the following:

nRet = OSWinHelp(Me.Handle.ToInt32, "", 261, 0)

An easy way to make this change is to use the Find And Replace dialog box to replace all instances of “App.HelpFile” with “”, the empty string.

Implementing context-sensitive help in Visual Basic .NET applications is outside the scope of this book. If you want to learn more, search for the following topics in the Visual Basic .NET help: “Help and User Assistance in Windows Applications” and “Introduction to the Windows Forms HelpProvider Control.”
ActiveMdiChild in MDI Projects

In MDI applications, code in event procedures in the frmMain form, such as tbToolBar_ButtonClick, often uses soft binding to access controls on the active MDI child form. Visual Basic .NET detects and generates compile errors for controls that are accessed in this way.

Before discussing the solution, let’s look a little closer at the reason that soft binding is used. When a user clicks a toolbar button or chooses a menu command from the MDI parent form, the code often acts upon the currently selected MDI child form. If an MDI application has many child forms, the active form is determined only at run time; therefore, it is common to use the ActiveForm object to access controls on the current form. For example, the following extract from the tbToolBar_ButtonClick event procedure shows how to change the text alignment of the selected text inside a control called rtfText on the active form:

Select Case Button.Key
   Case "Align Right"
      ActiveForm.rtfText.SelAlignment = rtfRight

For MDI applications, the Visual Basic .NET equivalent of Visual Basic 6’s ActiveForm is ActiveMdiForm. The code just shown upgrades to the following:

Select Case eventArgs.button.Key
   Case "Align Right"
      'UPGRADE_WARNING: Control rtfText could not be resolved because
      'it was within the generic namespace ActiveMdiChild.
      ActiveMdiChild.rtfText.SelAlignment = _
         RichTextLib.SelAlignmentConstants.rtfRight

Notice that the Upgrade Wizard has inserted an EWI because it cannot resolve rtfText. Because the active form is defined dynamically at run time, the wizard has no way of knowing what control rtfText actually refers to. Visual Basic .NET has stronger type checking than Visual Basic 6, so it generates a compile error for this line of code. All the compiler knows is that ActiveMdiChild is a Form object, and rtfText is not a valid control or property of a standard Form object. To fix this error, you should either strongly type the form or force the code to use late binding. When possible, you should strongly type the form because doing so gives you IntelliSense and type checking at compile time. Here is how to modify the code to use strong type checking.

First of all, add a property called GetActiveMdiChild to the form:

ReadOnly Property GetActiveMdiChild() As frmDocument
   Get
      Return Me.ActiveMdiChild
   End Get
End Property

Then change all occurrences of ActiveMdiChild to use this property:

Select Case eventArgs.button.Key
   Case "Align Right"
      GetActiveMdiChild.rtfText.SelAlignment = _
         RichTextLib.SelAlignmentConstants.rtfRight

Notice that since our property is of type frmDocument (the MDI child form), code can access properties, methods, and controls of the MDI child form. The compiler knows that rtfText is a control of frmDocument, so this code compiles and runs.

You will have to make this change for every procedure that uses controls of ActiveMdiChild. The easiest way to do this is to use the Find And Replace dialog box to replace all instances of “ActiveMdiChild” with “GetActiveMdiChild”. When you’re doing the find and replace, be careful not to rename the newly added GetActiveMdiChild property.

What about MDI applications that have several different form types? What if the active child form could be one of a number of different forms? In most cases, the different child forms won’t all have controls with the same name, but you can still fix the code to work. Defining GetActiveMdiChild as type Object will force the code to use late binding, and the control will be resolved only at run time. To do this, change the property to the following:

ReadOnly Property GetActiveMdiChild() As Object
   Get
      Return Me.ActiveMdiChild
   End Get
End Property

Where possible, however, you should avoid late binding and instead use the strongly typed version of the property. When the property is strongly typed, you get IntelliSense and type checking, and the code executes quicker.

Forms Collection in frmMain_Closed

In the Form_Unload event of SDI and Explorer applications, the VB Application Wizard generates code that unloads each open form:

'Close all sub forms
For i = Forms.Count - 1 To 1 Step -1
   Unload Forms(i)
Next

This code upgrades to the following. Not shown here is that Form_Unload is renamed frmMain_Closed during the upgrade.

'Close all sub forms
'UPGRADE_ISSUE: Forms collection was not upgraded.
'UPGRADE_WARNING: Couldn't resolve default property of object Forms.Count.
For i = Forms.Count - 1 To 1 Step -1
   'UPGRADE_ISSUE: Forms collection was not upgraded.
   'UPGRADE_ISSUE: Unload Forms(i) was not upgraded.
   Unload(Forms(i))
Next

As we discussed in Chapter 15, the forms collection cannot be upgraded automatically. Fixing this problem is simple; you can remove this entire section of code, since Visual Basic .NET applications automatically unload all their child forms when the main form closes.

Clipboard in MDI Projects

MDI applications have three event procedures for the Clipboard object: Cut, Copy, and Paste. They are created as follows:

Private Sub mnuEditCut_Click()
    On Error Resume Next
    Clipboard.SetText ActiveForm.rtfText.SelRTF
    ActiveForm.rtfText.SelText = vbNullString
End Sub

Private Sub mnuEditCopy_Click()
    On Error Resume Next
    Clipboard.SetText ActiveForm.rtfText.SelRTF
End Sub

Private Sub mnuEditPaste_Click()
    On Error Resume Next
    ActiveForm.rtfText.SelRTF = Clipboard.GetText
End Sub

To make looking at the upgraded Clipboard code simpler, let’s focus on the two lines of Clipboard code that set and get the Clipboard text, respectively:

Clipboard.SetText ActiveForm.rtfText.SelRTF

ActiveForm.rtfText.SelRTF = Clipboard.GetText

After the upgrade, these two lines cause errors in Visual Basic .NET, since the Clipboard object cannot be upgraded automatically (see Chapter 15).

'UPGRADE_ISSUE: Clipboard method Clipboard.SetText was not upgraded.        Clipboard.SetText(ActiveMdiChild.rtfText.SelRTF)
'UPGRADE_ISSUE: Clipboard method Clipboard.GetText was not upgraded.
ActiveMdiChild.rtfText.SelRTF = Clipboard.GetText

You can fix the problem by adding two helper methods to Module1. These helper methods get and set the Clipboard text:

Function ClipboardSetText(ByVal newText As String)
   Try
      Clipboard.SetDataObject(newText, True)
   Catch ex As Exception
      MsgBox("Exception setting clipboard text: " & ex.Message)
   End Try
End Function

Function ClipboardGetText()
   Try
      Dim sClipText As String
      Dim stringData As IDataObject
      Dim getString As New DataObject(DataFormats.StringFormat)
      stringData = Clipboard.GetDataObject()
      If stringData.GetDataPresent(DataFormats.Text) Then
         sClipText = stringData.GetData(DataFormats.Text, True)
         Return sClipText
      End If
   Catch ex As Exception
      MsgBox("Exception getting clipboard text: " & ex.Message)
   End Try
End Function

Now you just need to modify the set and get code to call these functions:

ClipboardSetText(GetActiveMdiChild.rtfText.SelRTF)

GetActiveMdiChild.rtfText.SelRTF = ClipboardGetText()

Notice that we’ve also changed ActiveMdiChild to GetActiveMdiChild, as we recommended earlier.


frmSplash Form

The form frmSplash is a splash screen. It is added to the project if you specify a splash screen at application startup in the VB Application Wizard. After being upgraded, this form has two problems. First, as the frmAbout form does, frmSplash inserts the application version into a label. As we discussed in the earlier section on App.Revision, you should replace the version code in Form_Load. The following Visual Basic 6 code

lblVersion.Caption = "Version " & App.Major & "." & App.Minor & "." _
   & App.Revision

upgrades to

lblVersion.Text = "Version " & _
   System.Diagnostics.FileVersionInfo.GetVersionInfo( _
   System.Reflection.Assembly.GetExecutingAssembly.Location _
   ).FileMajorPart _
   & "." & System.Diagnostics.FileVersionInfo.GetVersionInfo( _
   System.Reflection.Assembly.GetExecutingAssembly.Location _
   ).FileMinorPart & "." & App.Revision

Replace this code with the following:

lblVersion.Text = "Version " & Application.ProductVersion

The second problem is something new. Applications with a splash screen perform a series of steps in Sub Main to

  1. Show the frmSplash form.

  2. Refresh the frmSplash form (forcing the form to be fully painted).

  3. Load the frmMain form.

  4. Unload the frmSplash form.

  5. And finally, show the frmMain form.

Here is the code that performs this series of steps:

Sub Main()
   frmSplash.Show
   frmSplash.Refresh
   Set fMainForm = New frmMain
   Load fMainForm
   Unload frmSplash
   fMainForm.Show
End Sub

After upgrading, this code looks like the following:

Public Sub Main()
   frmSplash.DefInstance.Show()
   frmSplash.DefInstance.Refresh()
   fMainForm.DefInstance = New frmMain
   'UPGRADE_ISSUE: Load statement is not supported.
   Load(fMainForm)
   frmSplash.DefInstance.Close()
   System.Windows.Forms.Application.Run(fMainForm.DefInstance)
End Sub

This code causes a compile error because the Load statement is not supported in Visual Basic .NET. To fix the error, remove the Load(fMainForm) statement; the application will work normally. The updated Sub Main event will look like this:

Public Sub Main()
   frmSplash.DefInstance.Show()
   frmSplash.DefInstance.Refresh()
   fMainForm.DefInstance = New frmMain
   frmSplash.DefInstance.Close()
   System.Windows.Forms.Application.Run(fMainForm.DefInstance)
End Sub


frmBrowser Form

When creating a project with the VB Application Wizard, if you indicate that you want your users to be able to access the Internet from your application, the wizard adds the frmBrowser form to the application. Choosing Web Browser from the View menu then opens the form. The code generated in the mnuViewWebBrowser_Click event in frmMain looks like this:

Private Sub mnuViewWebBrowser_Click()
   Dim frmB As New frmBrowser
   frmB.StartingAddress = "http://www.microsoft.com"
   frmB.Show
End Sub

This code upgrades to the following:

Public Sub mnuViewWebBrowser_Click(ByVal eventSender As _
   System.Object, _
   ByVal eventArgs As System.EventArgs) _
   Handles mnuViewWeb Browser.Click
   Dim frmB As frmBrowser = New frmBrowser
   frmB.StartingAddress = "http://www.microsoft.com"
   frmB.Show()
End Sub

If this is an MDI project, you need to adjust the behavior of the frmBrowser form. Open the code window for frmBrowser and search for the line Me.Show. This statement is in the Sub New method within the “Windows Form Designer generated code” hidden region. Remove the Me.Show line.

The reason for removing the line is to avoid an event ordering issue. This line causes the Form_Load event to fire before the mnuViewWebBrowser method has assigned the StartingAddress property of the form. After you remove the Me.Show line, the frmBrowser form will navigate to the default Web site when it is first opened.


Data Forms

The VB Application Wizard has an option to add data access forms to the project. Each data access form allows users to add, update, and delete data in a database table. The forms use ADO data binding and can be set up to use binding with the ADO Data control, code, or a data class.

The Upgrade Wizard can upgrade only projects with ADO Data control data binding. Forms that accomplish data binding via code will not work in Visual Basic .NET because the controls do not support the DataSource property. Data binding by using a data class cannot be upgraded for two reasons: the controls do not support the DataSource property, and the DataSource behavior of classes cannot be upgraded automatically. If your application uses either of these two types of data binding, you will need to reimplement the data binding, using ADO or ADO.NET.

Forms with ADO Data control data binding are upgraded to Visual Basic .NET. Data binding has its own set of issues, and these are covered in Chapter 14.


Module1 Module

The VB Application Wizard adds a module called Module1 to your project. This module contains the Sub Main startup method and will also contain the LoadResStrings method if you chose to store the project’s strings in a resource file.

LoadResStrings Method

If you choose Yes when the VB Application Wizard asks if you would like to use a resource file for the strings in your application, the wizard stores strings (such as text for menu items) in a resource file. The wizard adds a method named LoadResStrings to the application to load the strings at run time. This procedure loads the strings for menu captions, ToolTips, control captions, and fonts. After the upgrade, this procedure causes 19 compile errors, mainly because it uses soft binding. The Upgrade Wizard adds an EWI to the method, advising you to replace the function with the following code supplied in Help:

Sub LoadResStrings(ByRef frm As System.Windows.Forms.Form)
   On Error Resume Next
   Dim ctl As System.Windows.Forms.Control
   Dim obj As Object
   Dim fnt As System.Drawing.Font
   Dim sCtlType As String
   Dim nVal As Short

   ' Set the form's caption.
   frm.Text = VB6.LoadResString(CShort(frm.Tag))

   ' Set the font.
   Dim FontName As String, FontSize As Double
   FontName = VB6.LoadResString(20)
   FontSize = CShort(VB6.LoadResString(21))
   frm.Font = New System.Drawing.Font(FontName, FontSize)

   ' Set the controls' captions using the Caption
   ' property for menu items and the Tag property
   ' for all other controls.
   For Each ctl In frm.Controls
      ctl.Font = fnt
      sCtlType = TypeName(ctl)
      If sCtlType = "Label" Then
         ctl.Text = VB6.LoadResString(CShort(ctl.Tag))
      ElseIf sCtlType = "AxTabStrip" Then
         For Each obj In CObj(ctl).Tabs
            obj.Caption = VB6.LoadResString(CShort(obj.Tag))
            obj.ToolTipText = VB6.LoadResString _
               (CShort(obj.ToolTipText))
         Next obj
      ElseIf sCtlType = "AxToolbar" Then
         For Each obj In CObj(ctl).Buttons
            obj.ToolTipText = VB6.LoadResString _
               (CShort(obj.ToolTipText))
         Next obj
      ElseIf sCtlType = "AxListView" Then
         For Each obj In CObj(ctl).ColumnHeaders
            obj.Text = VB6.LoadResString(CShort(obj.Tag))
         Next obj
      Else
         nVal = 0
         nVal = Val(ctl.Tag)
         If nVal > 0 Then ctl.Text = VB6.LoadResString(nVal)
         nVal = 0
         nVal = Val(CObj(frm).ToolTip1.GetToolTip(ctl))
         If nVal > 0 Then
            CObj(frm).ToolTip1.SetToolTip(ctl, VB6.Load-
               ResString(nVal))
         End If
      End If
   Next ctl

   Dim mnu As System.Windows.Forms.MainMenu = CObj(frm).MainMenu1
   If Not mnu Is Nothing Then
      LoadMenuResStrings(mnu)
   End If
End Sub

Public Sub LoadMenuResStrings(ByVal mnu As System.Windows.Forms.Menu)
   Dim mnuItem As System.Windows.Forms.MenuItem
   For Each mnuItem In mnu.MenuItems
      On Error Resume Next
      mnuItem.Text = VB6.LoadResString(CInt(mnuItem.Text))
      On Error Goto 0
      If mnuItem.MenuItems.Count > 0 Then
         LoadMenuResStrings(mnuItem)
      End If
   Next
End Sub

This code is a replacement for the standard LoadResStrings method generated by the VB Application Wizard. If you haven’t modified the method, simply copy and paste this procedure into your application, replacing the existing LoadResStrings method.


Conclusion

This chapter has focused on upgrading projects created with the VB Application Wizard. It’s important to note that the applications generated by this wizard are not complete. They are a starting point from which you continue to add your own business logic and project items. We’ve examined how to fix problems with the initial project items and code as they are generated by the wizard. Obviously, there will be other issues that occur as a result of code you add to these base projects. You’ll find solutions to these general upgrade issues in other chapters of this book.


Techniques for Adding Value

Part IV
Techniques for Adding Value
Chapter 18
Adding Value to Your Applications

Parts I, II, and III of this book discussed how to prepare applications for upgrading, introduced the Microsoft Visual Basic .NET upgrade technologies, and looked at how to fix problems in your upgraded application. The final four chapters of this book depart from the subject of upgrading and instead look at how you can continue developing your application in Visual Basic .NET and how you can add value by incorporating new features of Visual Basic .NET into your application.

In this chapter, we’ll demonstrate a number of new features that you may want to add to your upgraded applications. You’ll see how to load objects dynamically from other DLLs, create rich graphics that were out of the reach of Visual Basic 6 programming, access the registry with less effort than ever before, and work with the powerful new Microsoft .NET file classes.

Note that the features we discuss in this chapter are all optional additions to your projects. We introduce some new ways of doing familiar things, plus some techniques that were not possible in Visual Basic 6. This chapter does not provide an exhaustive list of new capabilities in Visual Basic .NET; instead, it simply describes some cool features you can add to upgraded projects.

Throughout this chapter we’ll refer to the sample application DynamicApp.sln, located on the companion CD.


Overview of the Sample Application

DynamicApp is a Microsoft Windows application with three forms. The primary form, Dashboard, is a dashboard from which you can launch the other features of the application: a registry editing form and a snappy graphics form, as seen in Figure 18-1.

Figure 18-1
Dashboard opens the registry editing form and the snappy graphics form.

What is interesting is the architecture of the application. Each form is in a different application; the dashboard is in the main EXE, while the graphics form and registry editing form are in two separate DLLs. When the dashboard opens, it searches its directory for DLLs and then examines each DLL it finds for Windows forms. It dynamically builds a list of available Windows forms and shows them as buttons on the dashboard. When you click a button on the Dashboard form, the appropriate DLL is loaded dynamically and the form is shown. These actions are performed using some of the new file functions in Visual Basic .NET.


New File Functions

As we mentioned before, the Dashboard form handles three tasks of interest:

  • It obtains a list of DLLs in a directory.

  • It examines each of the DLLs for Windows forms.

  • It dynamically loads and shows the form.

Let’s see how it performs each of these actions.

Reading the Contents of a Directory

In Visual Basic 6, it is awkward to read the contents of a directory: you have to call the Dir$ function repeatedly. This function returns the name of the first file in the directory, and then the next, and so on. You can’t examine two directories at the same time because Dir$ tracks only the file it has enumerated for the current directory. In Visual Basic .NET, getting the list of files in a directory is much simpler. Use the System.IO.Directory.GetFiles method to return an array of filenames. The following sample code is from the Dashboard.GetSubForms method in the DynamicApp project:

Dim filePathArray() As String
filePathArray = System.IO.Directory.GetFiles(myDirectory(), "*.dll")

After this code runs, the filePathArray variable is filled with an array of file paths, such as

C:\DynamicShell\bin\GraphicsFeatures.dll

The System.IO namespace contains many other useful functions as well. For example, System.IO.Path.GetDirectoryName returns the directory name from a file path. The code

MsgBox(System.IO.Path.GetDirectoryName( _
   "C:\DynamicShell\bin\GraphicsFeatures.dll"))

shows C:\DynamicShell in the message box. In a similar vein, the System.IO.Path.GetFileName method returns the filename of a file path. For example, the code

MsgBox(System.IO.Path.GetFileName( _
  "C:\DynamicShell\bin\GraphicsFeatures.dll"))

shows GraphicsFeatures.dll in the message box. Another useful function is System.Reflection.Assembly.GetExecutingAssembly. This last method returns the file path of the application. DynamicApp passes the file path to System.IO.Path.GetDirectoryName to get the application’s directory, which is how it knows where to look for DLLs.

Finding All the Forms in a DLL

Once an application has found a DLL, how can it dynamically determine what forms it contains? The enabling technology is called reflection. Reflection encompasses a set of classes that allow you to examine the contents of a DLL at run time. Let’s see how it works, using a simplified version of the Dashboard.GetSubForms method in DynamicApp. The following sample shows how to list all of the types (type is the .NET term for class, module, or form) in the current application. Try it out. Create a new Windows application, and enter the following code in the Form_Load event:

Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
   Dim asm As Reflection.Assembly
   Dim typeArray() As Type
   Dim typeCounter As Integer
   asm = asm.LoadFrom( _
      System.Reflection.Assembly.GetExecutingAssembly.Location)
   typeArray = asm.GetTypes()
   For typeCounter = 0 To typeArray.Length - 1
      MsgBox(typeArray(typeCounter).Name)
   Next
End Sub

There is only one type in this application: Form1. So when you run this code, Form1 is shown in the message box. What’s happening here? Remember that assembly is the .NET term for an EXE or a DLL. We declare a variable of type Assembly and then use it to load the current application. Next we retrieve all of the types in the assembly into an array. We then loop through the array and show the name of each type in a message box. Simple, isn’t it?

DynamicApp uses this mechanism to get the list of forms inside every DLL in the bin directory where the application is located. Instead of showing the form name in a message box, it stores the list of forms in an array and uses that array to create a set of buttons on the Dashboard form. When the user clicks a button, DynamicApp shows the appropriate form.

Loading Forms Dynamically

Once you know the name of the DLL and the name of the form you want to show, the rest is quite simple. You create an instance of the form, using the Assembly.CreateInstance method, and then call the Show method on the form. The following code snippet is a simplified version of the code in the DynamicApp Dashboard.Button_Click_Handler method. It demonstrates how to load a DLL called MyDll.dll, create an instance of a form within the DLL Form1, and show the form.

Dim asm As Reflection.Assembly
Dim f As Form
asm = asm.LoadFrom("MyDll.dll")
f = CType(asm.CreateInstance("MyDll.Form1"), Form)
f.Show()

Notice with the CreateInstance method that we have to refer to the form as MyDll.Form1, using the <namespace>.<formname> format. Also be aware that when you write code that loads a DLL dynamically, all names, including the filename and type name, are case sensitive.

Reading and Writing to Files

While we’re on the subject of the new file functions, let’s take a short digression and quickly discuss how to write to and read from files using the new .NET Framework methods. You can still use the existing Visual Basic file functions, but the .NET Framework file methods, although a little more complicated, offer more flexibility when working with files.

We’ll create a simple console application that creates a file, writes “Hello World” to it, closes the file, and then reopens it and shows the contents in a message box. This sample is on the companion CD, and it is called FileReadAndWrite.sln. The project contains one module. Here are the contents of that module:

Sub Main()
   '* Create a file and write to it
   Dim outFile As System.IO.FileStream
   outFile = New System.IO.FileStream("C:\tempFile.txt", _
      IO.FileMode.Create, IO.FileAccess.Write)
   Dim fileWriter As New System.IO.StreamWriter(outFile)
   fileWriter.WriteLine("Hello World")
   fileWriter.Close()
   outFile.Close()

   '* Open a file and read from it
   Dim inFile As System.IO.FileStream
   inFile = New System.IO.FileStream("C:\tempFile.txt", _
      IO.FileMode.Open, IO.FileAccess.Read)
   Dim fileReader As New System.IO.StreamReader(inFile)
   While fileReader.Peek > -1
      MsgBox(fileReader.ReadLine)
   End While
   fileReader.Close()
   inFile.Close()
 End Sub

To open a file for writing, you have to perform two steps: create a stream object and then create a StreamWriter object to write to the stream. You can then write to the file using the StreamWriter object’s Write and WriteLine methods. Reading from a file involves a similar process: create a stream object, and then create a StreamReader to read from the stream. To determine whether there is anything to read from the file, use the StreamReader object’s Peek method, which returns the value of the next byte in the file, or –1 if there is nothing left to read. The StreamReader object’s Read, ReadBlock, ReadLine, and ReadToEnd methods are used to read the contents of the file.

Using Dynamic Properties

Let’s take one more digression and look at dynamic properties. With Windows Forms you have the ability to change the properties of the form at run time, using a configuration file. Figure 18-2 shows the DynamicApp solution open in Visual Basic .NET. Notice that the solution has a file called App.config, and notice also that in the Property Browser, the Form.Text property has a small blue icon next to the name. This indicates that the Form.Text property is retrieved at run time from the App.config file.

Figure 18-2
The App.config file and the Form.Text dynamic property.

If you double-click the App.config file to open it, you will see that it is an XML file and that it has the following line in the body:

<add key="Dashboard.Text" value="DynamicApp" />

This is the value of the Text property, which is used to set the title bar text of the application. When the project is compiled, this file is deployed alongside the EXE, with the name DynamicApp.exe.config. If you open this file in Notepad and change the setting to something like

<add key="Dashboard.Text" value="Hello World" />

“Hello World” will show in the title bar the next time the application is run. It is called a dynamic property because the value is not stored in the compiled application but instead in an editable XML file that the application reads at run time.

Why is the Text property dynamic? Are all properties dynamic? When you create a form, by default none of the properties are dynamic. To make a particular property dynamic, you use the (Dynamic Properties) property in the Property Browser; you use the (Advanced) builder to select the properties that you want to make dynamic, as shown in Figure 18-3.

Figure 18-3
Making properties dynamic.

Dynamic properties are valuable for database connection strings, Internet addresses, directory names, and any other property that might need to be changed after the application is compiled.


New Windows Capabilities

Now let’s look at what each of the subforms in the sample application does.

Accessing the Registry

Accessing the registry is awkward in Visual Basic 6. You have to use APIs like RegCreateKey and RegDeleteKey, which are cumbersome. The .NET Framework provides a much easier mechanism for accessing the registry. The registry editing form in the RegistryFeatures project (included on the companion CD) shows how to use the .NET Framework classes to create and delete keys and values. Figure 18-4 shows the registry editing form.

Clicking the Create button in the MyRegistryKey Key group box creates the registry key HKEY_CURRENT_USER\Software\MyRegistryKey. Clicking the Delete button deletes this key. Clicking the Create button in the MyRegistryKey\MyValue Value box adds a new string value to the key called MyValue. Clicking the Delete button deletes the value. The contents of the HKEY_CURRENT_USER\Software key are shown in the tree view on the right of the form.

Figure 18-4
Registry editing form.

The following code shows how the form adds the registry key. First it tries to open the registry key, and if the registry key doesn’t already exist, the code creates it.

Dim rk As Microsoft.Win32.RegistryKey
rk = Registry.CurrentUser.OpenSubKey("Software\MyRegistryKey", True)
If rk Is Nothing Then
   Registry.CurrentUser.CreateSubKey("Software\MyRegistryKey")
Else
   rk.Close()
End If

To create a registry key, we first create a Microsoft.Win32.RegistryKey variable called rk. We then use rk to attempt to open a registry key within CurrentUser. The CurrentUser property maps to the HKEY_CURRENT_USER registry hive. If the registry key doesn’t exist, it is created using the CreateSubKey method.

Deleting a key is even simpler. It involves only one line of code.

Registry.CurrentUser.DeleteSubKeyTree("Software\MyRegistryKey")

This line deletes the MyRegistryKey registry key and any registry subkeys and values it may have.

To add a value, we open the registry key and use the SetValue method. In the following example, we add a string value “MyValue” with the contents of the newValue.Text TextBox.

Dim rk As Microsoft.Win32.RegistryKey
rk = Registry.CurrentUser.OpenSubKey("Software\MyRegistryKey", True)
rk.SetValue("MyValue", CStr(Me.newValue.Text))
rk.Close()

If the value already exists, it is overwritten with this new value. To delete the value, use the DeleteValue method, as in the following example:

Dim rk As Microsoft.Win32.RegistryKey
rk = Registry.CurrentUser.OpenSubKey("Software\MyRegistryKey", True)
rk.DeleteValue("MyValue")
rk.Close()

Notice in all these examples that each time we open a registry key, we also close it using the Close method. The registry editing form acts upon the HKEY_CURRENT_USER hive. In addition, you can access the HKEY_LOCAL_MACHINE, HKEY_CLASSES_ROOT, HKEY_USERS, and HKEY_CURRENT_CONFIG registry hives using the Registry object’s LocalMachine, ClassesRoot, Users, and CurrentConfig properties. This is certainly much easier than working with the Visual Basic 6 registry APIs.

Control Anchoring

The registry editing form also demonstrates automatic resizing using control anchoring. When you resize the form, the tree view also resizes—without your writing a single line of resize code! Using the Anchor property of the TreeView control, you can bind the edges of the control to the edges of the form. The TreeView control is anchored to each side of the form so that when the form resizes, the control also resizes, keeping the top, bottom, left, and right edges the same distance from the edges of the form. This method is much easier than in Visual Basic 6, where you have to write lines and lines of code in the Form_Resize event to adjust the size of the control.

Graphics Features

If you enjoy adding some “sharpness” to your applications with graphics, you’ll be pleased to know that the .NET Framework has a set of dedicated graphics classes called GDI+, making it easier to add graphics than in any previous version of Visual Basic. We discussed creating gradient backgrounds and drawing shapes in Chapter 15; now we’ll look at three other features: opacity, regions, and alpha blending. These features are all demonstrated in the Graphics Effects form in the Graphics Features project on the companion CD. This form has three check boxes and a button. Each one applies a different graphics effect.

The first check box is Form Opacity. Setting this option makes the form semitransparent, as shown in Figure 18-5.

Figure 18-5
Semitransparent form.

The opacity property is a measure of opaqueness. It can take any value between 0 and 1. Setting the property to 1 makes the form solid, setting it to 0 makes the form invisible, and setting it to 0.5 makes the form semitransparent. A fun use of this property is to fade a form from invisible to fully solid. Click the Fade In button on the Graphics Effects Form to see a demonstration of this. Here is the code that fades the form from invisible to solid:

Private Sub FadeInButton_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles FadeInButton.Click
   Dim d As Double
   For d = 0 To 1 Step 0.01
      Me.Opacity = d
      Me.Refresh()
      System.Threading.Thread.CurrentThread.Sleep(5)
   Next
   Me.HasOpacity.Checked = False
End Sub

The For…Next loop increments the variable d from 0 to 1. The code then sets the opacity to the value of d: 0.00, 0.01, 0.02, 0.03, and so on. The Refresh method forces the form to completely repaint, and the System.Threading.Thread.CurrentThread.Sleep method waits 5 milliseconds before continuing to fade the form in. While opacity may not be instrumental in solving any real-world business problems, it’s a fun effect to add.

The second graphics feature this form demonstrates is regions. Setting the Donut Shape option changes the form from rectangular to a doughnut shape, as shown in Figure 18-6.

Figure 18-6
Form displayed using the Donut Shape option.

To change the region of a form, create a region from a set of graphics paths and apply this to the form’s Region property. The following code shows how to do this:

Dim myPath As New Drawing2D.GraphicsPath()
Dim bigDiameter As Integer = Me.ClientSize.Height
Dim donutWidth As Integer = 80
myPath.AddEllipse(0, 0, bigDiameter, bigDiameter)
myPath.AddEllipse(donutWidth, donutWidth, _
   bigDiameter - (donutWidth * 2), _
   bigDiameter - (donutWidth * 2))
Me.Region = New Region(myPath)

This code creates a GraphicsPath variable myPath. This variable is used to store one or more vector shapes. The sample above adds two ellipses to the variable. The first ellipsis is the outer ring of the doughnut. The second is the inner ring, the hole of the doughnut. Finally, the Region property of the form is set to the newly created path. This shape is a true doughnut—if you click in the middle of the hole, the mouse clicks are sent to the window behind the doughnut form. This technique is very flexible. The number of different graphics paths and different shapes you can create are limited only by your imagination. For example, the following code changes the form’s shape to the string “VB.NET Rocks.”

Dim myPath As New Drawing2D.GraphicsPath()
myPath.AddString("VB.NET Rocks", Me.Font.FontFamily, Font.Bold, _
80, New Point(0, 0), StringFormat.GenericDefault)
Me.Region = New Region(myPath)

The third graphics feature this form demonstrates is alpha blending. This technique allows you to paint text or pictures semitransparently. Figure 18-7 shows the effect of setting the Alpha Blending option on the Graphics Effects form.

Figure 18-7
Alpha blending.

Setting the check box enables a timer on the form. In the Timer.Tick event, two strings are painted onto the form: a blue VB.NET is painted first, and a red Rocks!! is painted over the top. Each is painted with a different alpha value that sets the transparency. The alpha value cycles from 0 to 256 and back to 0 each time the Timer.Tick event fires. The net effect is that the text on the form fades from VB.NET to Rocks!! to VB.NET again. Here is the code from the Timer.Tick event that does the alpha blending:

Static alpha As Integer = 0, upDown As Integer = 2
Dim g As Graphics
Dim f As New Font("Arial Black", 60, FontStyle.Regular, _
   GraphicsUnit.Pixel, 0)
Dim tempBitmap As New Bitmap(300, 100)
alpha += upDown
If alpha <= 0 Then
   upDown = -upDown
   alpha = 0
ElseIf alpha >= 255 Then
   upDown = -upDown
   alpha = 255
End If
g = Graphics.FromImage(tempBitmap)
g.Clear(Me.BackColor)
g.DrawString("VB.NET", f, New SolidBrush(Color.FromArgb(255 - alpha, _
   Color.DarkBlue)), New RectangleF(0, 0, 300, 100))
g.DrawString("Rocks!!", f, New SolidBrush(Color.FromArgb(alpha, _
   Color.Red)), 0, 0)
g.Dispose()
g = Me.CreateGraphics
g.DrawImage(tempBitmap, 200, 10)
g.Dispose()
tempBitmap.Dispose()
f.Dispose()

The alpha and upDown variables are static—they retain their old value each time the event is called. Each time the event is called, alpha increases by 2 until it reaches 256, and then it decreases by 2 until it reaches 0 and starts all over again. A temporary bitmap called tempBitmap is created for drawing the string. The reason for drawing to a temporary bitmap first is to avoid screen flicker. The two strings are drawn with the opposite alpha values—when VB.NET is drawn as solid, Rocks!! is drawn as transparent. Valid alpha values range from 0 (transparent) to 255 (opaque). Finally, the bitmap is drawn onto the form.

Notice that to draw onto the bitmap or form, we first have to create a graphics object:

g = Me.CreateGraphics
g.DrawImage(tempBitmap, 200, 10)

This graphics object is the GDI+ graphics drawing surface that supports all of the advanced graphics methods. A new graphics object has to be created for each bitmap or form you want to draw onto. When you finish using the graphics object, it is important to destroy it using the Dispose method:

g.Dispose()
Windows XP–Style Controls

The Windows XP user interface has a new look. The Windows common controls, such as buttons, group boxes, and tabbed dialog boxes, are softer in appearance—they have curved edges and are highlighted with a pleasant orange outline when the mouse moves over them.

Although you can’t add or edit Windows XP–style controls using the Visual Basic .NET Windows Forms designer, you can configure your application to use Windows XP controls when it runs. In effect, these are normal controls that simply display differently at run time. The XPTheme sample on the companion CD shows how to use Windows XP–themed controls, and the following steps describe how to add the Windows XP theme to a new application:

  1. Create a new Windows application. Add some controls.

  2. For every control that has a FlatStyle property, set the FlatStyle property to System, and Windows XP will use the default system style for the control.

  3. Compile your application as usual.

  4. Create a manifest file in the same directory as the application’s executable (usually the bin directory). A manifest file is an XML file that contains run-time information about the application. Using Notepad, create a text file with the following constants:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
manifestVersion="1.0">
   <dependency>
      <dependentAssembly>
         <assemblyIdentity
            type="win32"
            name="Microsoft.Windows.Common-Controls"
            version="6.0.0.0"
            processorArchitecture="X86"
            publicKeyToken="6595b64144ccf1df"
            language="*"
         />
      </dependentAssembly>
   </dependency>
</assembly>

  5. Save the file with the name <exename>.manifest. For example, if your application name is MyApp.exe, this file should be called MyApp.exe.manifest. The manifest works on an application-by-application basis rather than a file-by-file basis. Thus, you need to create only one manifest for the main EXE of the application, even if it contains many DLLs. When your application runs, Windows XP looks at the application manifest and uses the information it finds there to alter the application’s execution. In this case, the manifest instructs Windows XP to use the latest version of the common controls library—the version with the Windows XP–style controls in it.

Figure 18-8 shows a form with standard controls on the left and the same forms with Windows XP controls on the right.

Figure 18-8
Normal controls (above) and Windows XP controls (below).


XCopy Deployment

Do upgraded applications have any limitations? Most upgraded applications contain COM controls and ActiveX references. Most of these controls have to be registered before they can be used. Visual Basic .NET components don’t have this limitation—they are self-describing. The information that would go into the registry for a COM component is compiled into the .NET application.

It is often said that .NET components support XCopy deployment (referring to the DOS multiple copy XCopy command). What this means is that you can install any Visual Basic .NET application simply by copying the EXE and any dependent DLLs from a directory on one machine to a directory on another machine (assuming that the .NET Software Development Kit [SDK] is already installed on the client machine). No registration, no extra installation steps. Plus, the application is isolated—the DLLs it installs do not overwrite DLLs used by other applications.

XCopy deployment is extremely useful for client applications that are to be installed on perhaps thousands of client machines. With Visual Basic 6 applications, it is usually during one of these installations that something goes wrong—your application installs a DLL that overwrites another DLL or another DLL’s registration, which then breaks another application. These types of DLL conflicts are frustrating for both you, the developer, and your users.

Visual Basic .NET applications don’t have this problem because an installation doesn’t overwrite other files (unless you explicitly decide to overwrite an existing file), and other installations don’t affect your application’s files. However, if your application uses COM components—ActiveX controls or COM libraries—these components must be installed and registered, just as they must be in Visual Basic 6. This means two things. First, for each Visual Basic .NET application that contains COM components you must register the COM components, and second, if one of these COM components is overwritten, your application will stop working. If easy XCopy deployment is important to you, it’s a good idea to remove the COM components from your application and replace them with .NET components. This step is especially relevant for applications that will be installed to multiple client machines.

How do you replace COM components with .NET components? In the next chapter, you’ll see how you can do this by replacing common ActiveX controls with Windows Forms controls.


Conclusion

This chapter has looked at several ways you can extend your upgraded applications. These are important techniques because most of them would be either impossible or very difficult to achieve in Visual Basic 6. This point underscores one of the important concepts of upgrading: when you move your application to Visual Basic .NET, the real benefit comes when you add value with some of the great features of Visual Basic .NET and the .NET Framework.

The next three chapters look at more ways to add value to your applications. In addition to learning how to replace ActiveX controls with Windows Forms controls, you’ll see how to integrate your ADO data access code with ADO.NET data access code, and you’ll learn techniques for redesigning distributed applications.


Replacing ActiveX Controls with Windows Forms Controls

Chapter 19
Replacing ActiveX Controls with Windows Forms Controls

If you’ve been using Microsoft Visual Basic long enough—since before Visual Basic 4, to be exact—you will have fond memories of upgrading your controls from one control model to another: from VBX to OCX. When Visual Basic pole-vaulted to 32 bits, VBX—short for Visual Basic extension—controls didn’t make it over the bar and were left behind. Visual Basic 4 32-Bit Edition offered OCX—short for OLE control extension—replacements for most of the VBX controls delivered with Visual Basic 4 16-Bit Edition. In order to move your Visual Basic application to 32 bits, you were forced to upgrade your VBX controls to OCX controls. Visual Basic 4 provided a feature that would automatically replace the VBX controls in your project with equivalent OCX controls—commonly referred to as ActiveX controls today.

Visual Basic .NET offers a new control model called Windows Forms controls. Although there are many Windows Forms controls that are equivalent to the ActiveX controls you find in Visual Basic 6, you are not forced to replace all of your ActiveX controls with Windows Forms equivalents; Visual Basic .NET supports ActiveX controls as is. In fact, as we discussed in Chapter 13, the Upgrade Wizard—except in limited situations—does not replace your ActiveX controls with equivalent Windows Forms controls, even when equivalent controls exist.

This chapter discusses how you can manually replace your ActiveX controls with equivalent Windows Forms controls. In addition, it discusses the benefits of making this transition.


Benefits of Upgrading Controls

Upgrading from ActiveX controls to Windows Forms controls has certain benefits. As you’ll see in this section, these benefits include 100 percent Microsoft .NET compatibility, improved versioning, and simpler deployment.

100 Percent .NET Compatibility

Windows Forms controls have an inherent advantage over ActiveX controls in that they are native to the .NET environment, and thus they are more tightly integrated into that environment. For example, a Windows Forms control can support custom property editors—such as a TreeView nodes collection editor—for use with the Property Browser.

Because ActiveX controls are based on COM and are not native to the .NET environment, you may encounter some issues with them. For example, custom property editors are not available for ActiveX controls. An ActiveX control may depend on functionality provided by the Visual Basic 6 environment, which does not exist in Windows Forms. Thus, the capabilities of the ActiveX control may degrade when placed on a Windows form. For example, the ActiveX SSTab control depends on a specific host interface in order to support child controls. Windows Forms does not support the host interface SSTab is looking for, so the control cannot accept child controls when it exists on a Windows form.

Improved Versioning

You can think of ActiveX controls and components as being global across all applications. When an ActiveX control is registered on your machine, all applications in which the control is used will share the control from the same location. If you download an update for the ActiveX control, all of your applications automatically use the updated control. This global use of controls can be good and bad. It’s good if the updated control fixes bugs that affect one or more of your applications, but it’s bad if the updated control introduces new bugs or incompatibilities that break your applications. Because accepting an update for an ActiveX control is an all-or-nothing proposition, the potential for doing more harm than good is high. The updated control might benefit some applications while breaking others.

Applications that are built with Windows Forms controls do not risk being broken when you download an update for a control. The reason for this is that Windows Forms controls are local to each application, meaning that each application is bound to a particular version of the control. If an updated control becomes available on a system, the application does not automatically pick it up. Instead, it continues to use the version of the control that it was originally built against. If you want the application to pick up the latest version of the control automatically, you can change the policy for the application to cause it to do so. You can do this on an application-by-application basis to ensure that applications that will benefit from the updated control will use it, whereas ones that won’t benefit or that risk being broken continue to use the version of the control known to work with the application.

This change means that if you have an application based purely on Windows Forms controls that you have built, tested, and deployed, you can count on the application to run on a machine without disruption. No future control updates installed on the machine can affect it. The application will continue to run with the controls that it was tested against.

Simpler Deployment

When you deploy an application based entirely on Windows Forms controls and .NET components, you will find that fewer files are needed. If your application contains ActiveX controls, more files must be deployed, since an extra DLL is generated for each ActiveX control. The extra DLL is the COM interop assembly needed to facilitate the communication between Windows Forms and the ActiveX control.

If you are using Windows Forms controls that are equivalent to the ActiveX controls provided by Visual Basic 6 in your application—such as RichText, ProgressBar, StatusBar, Toolbar, TreeView, and ListView—the controls are provided as part of the Windows Forms assembly. No additional DLLs are required when you use these controls. If you are using controls provided by an outside vendor, you can deploy the control by copying the control assembly (DLL) to your application directory—the directory where your EXE file lives. No registration of the Windows Forms control is required.


Process of Replacing Controls

Because the Visual Basic .NET Upgrade Wizard does not automatically replace your ActiveX controls with Windows Forms equivalents, the process of replacing the ActiveX controls in your upgraded project with equivalent controls provided in the Windows Forms package is a manual one. This process involves the following general steps:

  1. Copy the design-time property settings for the ActiveX control.

  2. Delete the ActiveX control from the form.

  3. Place the equivalent Windows Forms control on the form.

  4. Rename the Windows Forms control to match the name of the ActiveX control.

  5. Paste the design-time settings for the control.

  6. Fix up the design-time settings.

  7. Resolve errors in the code.

  8. Remove the ActiveX control library reference.

The example in this section demonstrates each of these steps in turn.

Manually Upgrading a Control

Let’s take a look at a simple example of replacing the ActiveX ProgressBar control with the Windows Forms ProgressBar control. First you need to start with a Visual Basic 6 application that uses the ProgressBar control.

The companion CD includes a sample Visual Basic 6 application called PBar that serves as the basis for demonstrating how to manually upgrade an ActiveX control to its Windows Form equivalent control. Specifically, it demonstrates how to manually upgrade the ActiveX ProgressBar control to the Windows Forms ProgressBar control. The application consists of a form containing a ProgressBar ActiveX control and a command button. The command button click event (Command1_Click) contains the following code, which we’ll use to demonstrate how to upgrade code associated with an ActiveX control:

   Dim i As Integer
   Dim StartTime As Single

   For i = ProgressBar1.Min To ProgressBar1.Max

      StartTime = Timer
      Do While Timer - StartTime < 0.01
      Loop

      ProgressBar1.Value = i
      ProgressBar1.Refresh

   Next

The sample application also includes the following ProgressBar Click event for the purpose of demonstrating how to upgrade an event associated with an ActiveX control:

   Private Sub ProgressBar1_Click()
      MsgBox "You clicked my progress bar!"
   End Sub

First load the Visual Basic 6 sample PBar application and try it out. Then choose Start from the Run menu, and click the Show Progress button. You should see the progress automatically increment from minimum to maximum. Figure 19-1 shows an example of the application running.

Figure 19-1
Visual Basic 6 ProgressBar application.

Now you’ll upgrade the application to Visual Basic .NET. To do this, copy the PBar project from the companion CD to your hard drive, run Visual Basic .NET and open the project file for the Visual Basic 6 application—PBar.vbp. The Upgrade Wizard appears. Step through each of the wizard’s pages, selecting the default options. The upgraded application will be loaded in Visual Basic .NET. Test it by choosing Start from the Debug menu and clicking the Show Progress button. The application should run as it did in Visual Basic 6.

In the next section, we will replace the ProgressBar ActiveX control, following the steps outlined earlier.

Copy the Design-Time Property Settings for the ActiveX Control

Design-time settings are stored in two places: in the upgraded form file and in the original Visual Basic 6 form file. The upgraded form file contains the extended design-time settings for the control. These are the settings—common to all controls on the form—that define attributes such as the control’s name, its size and position on the form, and its tab order. The Visual Basic 6 form file contains custom property settings for the form. In the case of the ProgressBar, these settings include the Max and Min property settings.

Copy the extended property settings

Let’s start by copying the upgraded extended property settings found in the upgraded form file. To see these property settings, view the code for the Visual Basic .NET form file and expand the “Windows Form Designer generated code” region. The property settings for the ProgessBar control are located in the InitializeComponent subroutine as follows:

Me.ProgressBar1.Location = New System.Drawing.Point(8, 64)
Me.ProgressBar1.Name = "ProgressBar1"
Me.ProgressBar1.OcxState = _
   CType(resources.GetObject("ProgressBar1.OcxState"), _
   System.Windows.Forms.AxHost.State)
Me.ProgressBar1.TabIndex = 0
Me.ProgressBar1.Size = New System.Drawing.Size(297, 57)

Select all property settings and copy the code to the Clipboard. Run Notepad and paste the code into it. Notepad will serve as a temporary holding place for this code. Delete the line containing the OcxState setting. OcxState represents the internal, saved state of the ActiveX control and cannot be applied to the ProgressBar Windows Forms control. It is easier to get the OcxState information from the original Visual Basic 6 form file, as we will demonstrate in the next section.

Copy the control-specific property settings

Now let’s copy the property settings found in the original Visual Basic 6 form file. Run another instance of Notepad, and open PBar.frm (copied previously from the companion CD). Look for the following section in the FRM file:

   Begin MSComctlLib.ProgressBar ProgressBar1
      Height          =   855
      Left            =   120
      TabIndex        =   0
      Top             =   960
      Width           =   4455
      _ExtentX        =   7858
      _ExtentY        =   1508
      _Version        =   393216
      Appearance      =   1
      Min             =   1
      Max             =   200
   End

Let’s copy the ActiveX control-specific property settings. In this case, the control-specific property settings are Appearance, Min, and Max. Custom property settings appear in the FRM file’s property settings block for a control, after the extended property settings. Extended property settings relate to properties that Visual Basic 6 maintains on behalf of the ActiveX control. The following list contains the extended properties supported by Visual Basic 6. You do not need to copy these settings when replacing the ActiveX control with a Windows Forms control. The reason is that the equivalent Visual Basic .NET property settings can be found in the InitializeComponent subroutine of the upgraded form file. It is easier to copy the upgraded extended property settings found in the Visual Basic .NET form file than it is to copy the ones found in the original Visual Basic 6 FRM file, as we demonstrated in the previous section.

_ExtentY | DataFormat | Left
_ExtentX | DataMember | Name
_Version | DataSource | TabIndex
Extender properties | Default | TabStop
Align | DragIcon | Tag
Cancel | DragMode | TooltipText
CausesValidation | Enabled | Top
Container | Height | Visible
DataBindings | HelpContextID | WhatsThisHelpID
DataChanged | Index | Width
DataField |  |

Copy the following settings to the original instance of Notepad containing the extended property settings. Paste them after the extended property settings.

   Appearance      =   1
   Min             =   1
   Max             =   200

After you’ve copied the settings, Notepad should contain the following:

   Me.ProgressBar1.Location = New System.Drawing.Point(8, 64)
   Me.ProgressBar1.Name = "ProgressBar1"
   Me.ProgressBar1.TabIndex = 0
   Me.ProgressBar1.Size = New System.Drawing.Size(297, 57)
   Appearance      =   1
   Min             =   1
   Max             =   200

Modify the settings in Notepad by converting each of the settings you’ve just copied to Visual Basic code in the form Me.<controlname>.<propertyname> = <value>. Your code should appear as follows after modification:

   Me.ProgressBar1.Location = New System.Drawing.Point(8, 64)
   Me.ProgressBar1.Name = "ProgressBar1"
   Me.ProgressBar1.TabIndex = 0
   Me.ProgressBar1.Size = New System.Drawing.Size(297, 57)
   Me.ProgressBar1.Appearance = 1
   Me.ProgressBar1.Min = 1
   Me.ProgressBar1.Max = 200
Delete the ActiveX Control from the Form

The next step is an easy one. Switch to Design view for the form, and delete the ProgressBar ActiveX control from the upgraded Visual Basic .NET form, Form1.

Place the Equivalent Windows Forms Control on the Form

Select the Windows Forms ProgressBar control on the Windows Forms tab of the Toolbox. Place the control on Form1. You don’t need to worry about placing the control in exactly the same position as the previous control. The position and size will be restored later when you copy the code from Notepad.

Rename the Windows Forms Control to Match the Name of the ActiveX Control

Change the Name property of the control to ProgressBar1 to match the name of the original ActiveX control.

Paste the Design-Time Settings for the Control

All that monkeying around you did in Notepad will now pay off. Copy the property settings you created in Notepad to the InitializeComponent subroutine in Form1, and replace the existing property settings for ProgressBar that you find. The InitializeComponent subroutine is located within the hidden block labeled “Windows Form Designer generated code.”

Fix Up the Design-Time Settings

Compiler errors will display for any property settings that the compiler doesn’t recognize. For example, a compiler error will occur on the following three lines of code. It occurs because the property cannot be found in the Windows Forms ProgressBar control. You will need to either eliminate property settings for properties that are no longer supported or change the property name to the equivalent property found in the Windows Forms control.

   Me.ProgressBar1.Appearance = 1
   Me.ProgressBar1.Min = 1
   Me.ProgressBar1.Max = 200

Using IntelliSense, you can quickly get a feel for what properties the Windows Forms ProgressBar control does and doesn’t support. To see a list of all properties associated with the ProgressBar control, type the name of the control in the code window, followed by a period. A drop-down list of the available properties and methods displays. You will find that the Appearance property is no longer supported, Min maps to Minimize, and Max maps to Maximize. Making the appropriate changes causes the compiler errors related to design-time settings to disappear. The resulting InitializeComponent code is as follows:

   Me.ProgressBar1.Minimize = 1
   Me.ProgressBar1.Maximize = 200
Resolve Errors in the Code

Compiler errors are displayed for any code in which a property cannot be resolved. In the Command1_Click event, you will see three compiler errors related to the use of the properties and methods Min, Max, and CtlRefresh. In line with the changes you made to the design-time properties, change Min to Minimum and Max to Maximum. The CtlRefresh method for the ActiveX ProgressBar control corresponds to the Refresh method for the Windows Forms ProgressBar control. The ActiveX control property is renamed CtlRefresh in Windows Forms to avoid conflicts with the extended Refresh property applied to all controls. The Windows Forms ProgressBar control contains a single method called Refresh. Rename CtlRefresh to Refresh.

In addition to fixing up properties and methods, you need to update your event code. Specifically, when you delete the ActiveX control, the events for the control become disassociated and are converted to ordinary subroutines. You need to reassociate the events with the Windows Forms control. In our example, code is written in response to the ProgressBar Click event. When the ProgressBar ActiveX control is deleted, the following subroutine exists but is not associated with the Windows Forms ProgressBar control:

Private Sub ProgressBar1_ClickEvent( _
   ByVal eventSender As System.Object, _
   ByVal eventArgs As System.EventArgs)
   MsgBox("You clicked my progress bar!")
End Sub

The easiest way to reassociate the Click event with the ProgressBar control is to act as though you were writing new code for the Click event. Select the Click event for the ProgressBar from the Event drop-down list to create the ProgressBar1_Click event handler subroutine, as follows:

Private Sub ProgressBar1_Click(ByVal sender As Object, _
   ByVal e As System.EventArgs) Handles ProgressBar1.Click
End Sub

Cut and paste the body of the ProgressBar1_ClickEvent subroutine to the ProgressBar1_Click event subroutine, and then delete the original ProgressBar1_Click event subroutine, leaving the following code:

Private Sub ProgressBar1_Click(ByVal sender As Object, _
   ByVal e As System.EventArgs) Handles ProgressBar1.Click
   MsgBox("You clicked my progress bar!")
End Sub

Re-creating the event subroutines in this manner causes the correct event parameters to be created in turn. You then need to resolve any compiler errors that result. For example, you may have code that references a method of an ActiveX control’s event argument object, but the method may not exist or may have a different name in the event argument object of the corresponding Windows Forms control.

Remove the ActiveX Control Library Reference

The final step is optional and should be performed only if you have replaced all ActiveX controls for a given library with Windows Forms equivalents. In the case of the example demonstrated here, all controls contained in the Windows common controls library—the ProgressBar control—have been replaced with Windows Forms equivalents. Therefore, you can safely remove the reference to the ActiveX control library. To remove the reference, expand the References list for the project in the Solution Explorer. You will need to remove two references for each ActiveX control. Right-click AxMSComctlLib, and choose Remove from the shortcut menu. Then right-click MSComctlLib and choose Remove. Removing an ActiveX control library reference in this manner means that fewer files need to be deployed with the application.

With these changes in place, all compiler errors should be resolved. Run the application and test it out. It should behave exactly like the original Visual Basic 6 application.


Mappings for Visual Basic 6 ActiveX Controls

Visual Basic 6 provides a number of ActiveX controls, such as the RichText, Masked Edit, and Common Dialog controls, as well as the Windows common controls—ProgressBar, Slider, StatusBar, Toolbar, TreeView, and so on. Table 19-1 lists the Windows Forms controls that make suitable replacements for Visual Basic 6 ActiveX controls.

Table 19-1 Mapping of Visual Basic 6 ActiveX Controls to Visual Basic .NET Windows Forms Controls
Visual Basic 6 ActiveX Control | ActiveX Control Library | Visual Basic .NET Windows Forms Control
ADO Data control* | MSAdodc.ocx | ADODC control provided by the Visual Basic compatibility library
Common Dialog | ComDlg32.ocx | OpenFileDialog, SaveFileDialog, FontDialog, ColorDialog, and PrintDialog
DataGrid | MSDatGrd.ocx | No equivalent control; Windows Forms DataGrid does not bind to ADO data, just to ADO.NET data
Rich TextBox | RichTx32.ocx | RichTextBox
SSTab Denotes an ActiveX control that is automatically upgraded to the equivalent Windows Forms control. | TabCtl32.ocx | TabControl
ListView | MSComCtl.ocx | ListView
TreeView | MSComCtl.ocx | TreeView
ImageList | MSComCtl.ocx | ImageList
TabStrip | MSComCtl.ocx | TabControl
Toolbar | MSComCtl.ocx | ToolBar
StatusBar | MSComCtl.ocx | StatusBar
ProgressBar | MSComCtl.ocx | ProgressBar
Slider | MSComCtl.ocx | TrackBar
UpDown | MSComCt2.ocx | NumericUpDown
MonthView | MSComCt2.ocx | MonthCalendar
DTPicker | MSComCt2.ocx | DateTimePicker
ActiveX Controls vs. Windows Forms Controls

For the most part, the Windows Forms controls listed in Table 19-1 offer the same functionality as their ActiveX control counterparts. One of the main differences—as demonstrated by the ProgressBar upgrade example given earlier—is that features of a Windows Forms control are not exposed in the same way. For example, the same type of properties may exist but have different names. In the case of the ProgressBar control, the Windows Forms control’s Maximum and Minimum properties equate to the Max and Min properties of the ActiveX control. For properties such as these, a simple one-to-one mapping exists, and it is easy to find the equivalent property, method, or event when replacing an ActiveX control with a Windows Forms control.

We refer to controls that do not contain any subobjects, such as a collection of items or nodes, as having a flat object model. This means that all the properties and methods for the control exist directly on the control. You need to use only one dot (or period) to access any one of the properties or methods when writing code. Replacing a flat-model ActiveX control—such as a ProgressBar or Slider—with its Windows Forms equivalent is a relatively straightforward task. With both controls—the ActiveX version and the Windows Forms version—on a form side by side, you can use IntelliSense or the Object Browser to take a quick inventory of each control and compare how you map the properties, methods, and events from one control to the other.

We refer to controls that have properties that return objects such as collections or other complex structures as having a deep or rich object model. Controls such as TreeView and ListView fall into this category. The TreeView control, for example, has a Nodes property representing a collection of Node objects. The Nodes collection is itself an object having its own set of properties and methods. One of these methods—the Item method—returns a Node object that in turn has its own set of properties and methods. You can navigate from the control to a child object by using a dot (or period) to separate each successive object in the object hierarchy.

Replacing a rich-model ActiveX control with the equivalent Windows Forms control presents a unique challenge. The challenge stems from differences in how the object models allow you to interact with subobjects of ActiveX and Windows Forms controls. ActiveX controls take a more property-centered approach to adding or navigating a control’s subobjects. Windows Forms controls take a more object-oriented approach. This difference affects the way you write code to add or find a subobject for a control. The challenge is to figure out the code that you need to write to achieve the same results in a Windows Forms control as are achieved in its ActiveX counterpart.

Let’s take a look at adding child nodes to a TreeView control as an example. This example highlights the general differences between ActiveX and Windows Forms controls having rich object hierarchies, such as the ListView, ToolBar, StatusBar, and tabbed dialog controls.

The following is an example of code written to add a single parent and child node to a TreeView control. The code is associated with a Visual Basic 6 form containing a TreeView control and an ImageList control named TreeView1 and ImageList1, respectively. Assume that the ImageList control contains at least one image added at design time.

Set TreeView1.ImageList = ImageList1
TreeView1.Nodes.Add , , "Parent", "Parent node", 1
TreeView1.Nodes.Add 1, tvwChild, "Child", "Child node", 1

The ActiveX controls provided with Visual Basic 6 do not allow you to create a subobject directly. For example, you cannot dimension a variable of type Node using the New qualifier. Instead, you create a node indirectly by calling the Add method on the Nodes collection and passing in a number of arguments representing property settings for the newly created node. Because this type of object model directs you to specify the properties of the Node object rather than the Node object itself, it is a property-centered model for creating nodes.

To achieve the same result using the Windows Forms TreeView control, you must think in a more object-oriented fashion. In the object model used in Windows Forms, it is preferable to create Node objects and then add them to the Nodes collection. We should note that the Windows Forms TreeView control, and other Windows Forms controls for that matter, do allow you to create collection member objects in a property-centered way. For example, you can create a Node object by calling the Add method on the Nodes collection and passing the Text value for the node. However, the Add method on collections generally does not support a large number of arguments—unlike collections related to ActiveX controls, where the arguments represent properties for the new item. You must get back into the object-oriented mindset and set the property values on the object after it is created.

The following code is the equivalent of the previous example; it adds a single parent and child node to a Windows Forms TreeView control:

Dim nodeChild As TreeNode = New TreeNode("Child node", 0, 0)
Dim childNodes() As TreeNode = {nodeChild}
Dim nodeParent As TreeNode = New TreeNode("Parent node", _
   0, 0, childNodes)

TreeView1.ImageList = ImageList1
TreeView1.Nodes.Add(nodeParent)

Compared to the ActiveX TreeView control, the Windows Forms TreeView control requires you to take a more structured, bottom-up approach to creating the nodes within the TreeView. In the Windows Forms version of TreeView, you cannot add individual nodes relative to other nodes in an ad hoc fashion. Instead, you must add the child nodes for a parent node at the time you add the parent node. You do this by passing in an array of TreeNode child objects as an argument to the TreeNode constructor for the parent node. As this code demonstrates, you add nodes in a bottom-up manner by first allocating the child nodes and then creating the parent node and passing it an array of its children. The code has a more object-oriented feel to it, as you construct the Node objects and then pass the objects as arguments to other methods—such as Add—to be added to the Nodes collection.

We intentionally used the ImageList control in this example to point out another difference between ActiveX controls and Windows Forms controls that use the rich object model. The ActiveX controls provided in Visual Basic 6 assume the value of 1 as the index for the first item in a collection. Windows Forms controls, on the other hand, always assume an index value of 0 for all collections. That is why the code for the ActiveX TreeView control passes a value of 1 as the index for the first image in the ImageList ListImages collection, whereas the Windows Forms code example uses 0.


Conclusion

Although the Upgrade Wizard makes life easier by having your upgraded Visual Basic 6 applications use the same ActiveX controls, keeping the ActiveX controls means that you do not reap any of the benefits of an application that is based 100 percent on .NET controls and components. Unfortunately, the Upgrade Wizard does not give you the choice of upgrading the ActiveX controls in your project to the equivalent Windows Forms controls.

This chapter has demonstrated how you can manually replace ActiveX controls with equivalent Windows Forms controls after your project has been upgraded. If the ActiveX control you need to replace has a flat object model, it is a relatively straightforward process to map its properties, methods, and events to those of the equivalent Windows Forms control. If, on the other hand, you want to replace an ActiveX control that has a rich object model with the equivalent Windows Forms control, you will need to restructure your code so that it fits the object model defined by the Windows Forms control.


Moving from ADO to ADO.NET

Chapter 20
Moving from ADO to ADO.NET

Let’s face it—most Microsoft Visual Basic applications have some sort of data access. If your application uses ActiveX Data Objects (ADO), you probably want to know what improvements you can make now that you are upgrading to Visual Basic .NET. The .NET Framework offers a whole new set of controls and services, including ADO.NET. Naturally, you will want to take advantage of some or all of these new features. From the developer’s standpoint, you have three options available:

  • Adapt your usage of ADO to better interoperate with the .NET Framework objects.

  • Upgrade your ADO code to ADO.NET.

  • Do both.

This chapter introduces the concepts and class hierarchy of ADO.NET. Then it provides an overview of how to use your existing ADO code within your .NET application to bind data to controls and support ADO Recordsets with XML Web services. For the more adventurous among you, the last part of this chapter offers some guidance on how to migrate your existing ADO code to ADO.NET.


ADO.NET for the ADO Programmer

It is obvious from the get-go that ADO.NET isn’t the traditional ADO. The data object model provided by ADO.NET is substantially different from that of ADO and requires a different approach when designing and implementing solutions. While some of the concepts remain familiar to ADO developers (such as using Connection and Command objects to manipulate a data source), others will require some adjustment. This discussion gives only a short overview, so we can quickly move on to the more interesting stuff.

Overview of ADO.NET

While ADO.NET presents a new data object model, parts of it are quite similar to the ADO we all know and love. Think of ADO.NET as the result of the integration of both classic ADO and the Microsoft XML technologies in a single, coherent framework. Unlike ADO, ADO.NET was designed from the ground up to support a more general data architecture that is abstracted from an underlying database. In other words, ADO.NET was designed to work with a myriad of data sources and can function as a data store in and of itself. This framework provides a series of data container objects that support functions similar to those used in databases (indexing, sorting, and views) without requiring an actual physical database behind the scenes.

ADO.NET also provides an interface that is consistent with other .NET stream-based data classes. This is a huge step forward and a wonderful design win. In Visual Basic 6, ADO uses a totally different object model compared to the FileSystemObject or the Microsoft XML libraries. ADO.NET, on the other hand, is designed to be consistent with other .NET Framework classes. This new compatibility enables developers to move between vastly different kinds of classes with relative ease. Figure 20-1 provides an overview of the ADO.NET class structure.

Figure 20-1
Overview of ADO.NET classes.

While the Connection and Command objects are still required for most data access, the distinction between the two objects has been made literal, as the Connection object now solely represents a database connection, while the Command object handles all queries.

The classic ADO Recordset functionality has been split into smaller and more specialized objects: the DataSet and DataReader classes. Forward-only “firehose” Recordsets map directly to DataReaders, while disconnected Recordsets map best to DataSets. DataSet is a powerful new class designed solely to address the disconnected data model problem. It was designed specifically to fill the gaps in the way ADO handles disconnected data, which was never very satisfactory. Given the new kinds of applications that Visual Basic .NET targets, improving and enhancing data access was a primary goal. ADO.NET goes still further by providing strong integration with XML, for which ADO had only minimal support.

DataSet s

DataSets are probably the most foreign of the new objects in the ADO.NET arsenal and thus require a bit more of an introduction than the rest. A DataSet is an in-memory, named cache of data retrieved from a generic data container. What this means is that the original data source is not important and does not have to be a database. DataSets are extremely flexible for that reason. They can be populated from various sources, including data adapters, local data, or XML.

In essence, a DataSet contains collections of DataTables and DataRelations. The DataTable class conceptually corresponds closely to a database table, while the DataRelation class is used to specify relations between the DataTables. DataSets can also contain user-specific information with the ExtendedProperties collection. Contained data and the current schema (or both) can be directly extracted as XML streams, making the information highly mobile. Figure 20-2 illustrates the structure of the DataSet class and its relationship to the data-specific ADO.NET classes.

important | Any relationships between DataTables need to be specified by the programmer. The current version of ADO.NET does not support propagating preexisting relationships from the data source to the DataSet. Therefore, any relationships that are defined in your SQL database (or other relational databases) need to be redefined in your DataSet.
Figure 20-2
DataSet class hierarchy.

Using a DataAdapter in conjunction with a DataSet enables you to explicitly manage changes made to all of the tables contained within the DataSet and propagate those changes back to the originating data source. The DataAdapter is a new component in ADO.NET. It is designed to operate as an intermediary between disconnected forms of data (DataSets, DataTables) and the underlying data store. The simplest use of a DataAdapter is to fill a DataSet or DataTable with the results of a database query, but it is able to go much further than that. The DataAdapter is an intermediary in the truest sense of the word, in that it supports bidirectional data transfers. Using the DataAdapter, you can define how changes to a DataSet or DataTable are merged back into the source database and vice versa. In its most advanced use, the DataAdapter can specify how to handle conflict resolution when merging, including insertions and updates.

The .NET Framework ships with two types of DataAdapters: the OleDbDataAdapter and the SqlDataAdapter. Although these DataAdapters target different data sources, they can be used interchangeably, allowing you to move between data sources with minimal disruption. As you will see later in this chapter, the OleDbDataAdapter can also be used as a bridge between ADO and ADO.NET.

A Quick Note on Providers

You will see managed providers mentioned throughout this chapter. Two kinds of providers ship with Visual Basic .NET: the OleDb providers and the SqlClient providers, which live in the System.Data.OleDb and System.Data.SqlClient namespaces, respectively. The majority of the examples in this chapter use the OleDb managed provider because it maps the most directly to ADO. The OleDbConnection object can use the same connection string that ADO uses. The SqlClient managed provider is designed to talk directly to a Microsoft SQL server and uses a slightly different connection string. (There is no need to specify a provider in a connection string for the SqlConnection object.)


Integrating Your ADO Code into a Visual Basic .NET Application

Believe it or not, ADO has certain capabilities that enable it to be compatible with .NET applications and the .NET Framework. It is possible to preserve your existing investment in ADO while still taking advantage of .NET controls and performing such tasks as data binding and marshaling Recordsets through XML Web services. To this end, the .NET Framework includes some features that support transforming the old ADO Recordset into something consumable by a Visual Basic .NET application. This section discusses those features.

ADO with ADO.NET: Is It the Best Architecture?

Although it is certainly possible to use ADO within your Visual Basic .NET application, your mileage will vary as far as performance is concerned. This approach may not give the best performance, but it is functional and quick to implement, and it allows you to reuse your existing business objects. Later you may choose to change to a solution based solely on ADO.NET. The compatibility features discussed here span the chasm between the two technologies and make it possible to combine ADO with ADO.NET.

Binding Your ADO Recordset to .NET Controls

.NET provides a wide range of controls for both Web applications and Microsoft Windows–based form applications. Many of these controls support data binding with ADO.NET data sources. The question is how to take advantage of your existing ADO infrastructure and still use the new controls. The answer is simple: all you need to do is convert your Recordset to either a DataSet or a DataTable. Once you have one of these managed data objects, data binding is trivial.

As we mentioned previously, the OleDbDataAdapter has the ability to handle this task. Check out the following example:

'Here is the familiar ADO stuff
Dim cn As New ADODB.Connection()
Dim rs As New ADODB.Recordset()
cn.Open("Provider=SQLOLEDB.1;Integrated Security=SSPI;" & _
   "Persist Security Info=False;" & _
   "Initial Catalog=Northwind;Data Source=bart")
rs.Open("Select * From Employees", cn)

'This is where things start getting interesting. Here we declare not
'only the DataAdapter, but also the DataTable the data is destined for.
Dim olead As New Data.OleDb.OleDbDataAdapter()
Dim dt As New Data.DataTable()

'We use the DataAdapter to fill a DataTable
olead.Fill(dt, rs)

'Now we bind the DataTable to the DataGrid
Me.DataGrid1.DataSource = dt

'This is cleanup. Always close your objects as
'soon as you are done with them.
rs.Close()
cn.Close()

In this simple example, an ADO Recordset is bound to a DataGrid control on a Windows form. Figure 20-3 shows the form with the DataGrid control before and after data binding. The OleDbDataAdapter.Fill method is capable of populating a DataTable object with data from an ADODB Recordset.

In this way it is possible to take any Recordset and convert it to a managed data structure (a DataTable). Once you have done the conversion, you can bind the DataTable directly to a control, or you can add it to a DataSet and work with the table just as you would any other DataTable (including creating a DataView for the data, which we will get to a bit later on).

Figure 20-3
DataGrid before and after data binding.
Using ADO with XML Web Services

XML Web services are the cornerstone of Microsoft’s .NET initiative. They offer a simplified mechanism for implementing distributed, consumable services using SOAP, an XML-based protocol that transmits over HTTP. SOAP has significant advantages over technologies such as Remote Data Service (RDS) and Distributed Common Object Model (DCOM). XML Web services will go through Internet firewalls with no problems, and the applications are loosely coupled, eliminating issues related to component registration or changing GUIDs. These features can lead to a more robust system that is not as sensitive to changes as a COM-based system. For more information on using XML Web services, consult Chapter 21.

Suppose you’ve decided that XML Web services are cool and you want to replace your middle tier with one. If you use the middle tier to handle data access and pass Recordsets, however, you have a problem: you cannot pass any COM object through an XML Web service. COM objects lack the self-descriptive capabilities necessary to support XML serialization. How can you get the middle tier to work with Visual Basic .NET?

You actually have two options available, as Figure 20-4 illustrates. The one you choose depends entirely on your application’s architecture.

  • Option 1: Leave your existing architecture in place and serialize your Recordset to and from XML. This is a good idea if you have a lot of ADO manipulation on both the client and the server tiers.

  • Option 2: Convert your Recordset to a DataTable or DataSet (as appropriate) and pass that back through the XML Web service. (The marshaling will be handled for you.) This option is good if the client code is used mainly for presentation or data binding and can readily be replaced with managed code.

    Figure 20-4
    Options for implementing XML Web services with Recordsets.
Serializing Your Recordset to XML

The standard way to pass managed objects back and forth through XML Web services is by serializing the objects to and from XML (which is usually done for you). It is also possible to do this with Recordsets, although you have to handle the serialization yourself—sort of. Check out the following two Visual Basic .NET functions:

Function RsToString(ByVal rs As ADODB.Recordset) As String
   Dim stm As New ADODB.Stream()
   rs.Save(stm, ADODB.PersistFormatEnum.adPersistXML)
   Return stm.ReadText()
End Function

Function StringToRs(ByVal s As String) As ADODB.Recordset
   Dim stm As New ADODB.Stream()
   Dim rs As New ADODB.Recordset()
   stm.Open()
   stm.WriteText(s)
   stm.Position = 0
   rs.Open(stm)
   Return rs
End Function

By using the built-in ability to serialize an ADODB Recordset to an XML string, and by using the ADODB Stream object, you can freely pass Recordsets between tiers in your application. In addition you get the added benefits of using XML Web services, which is a versatile replacement for DCOM, especially when you need to communicate over the Internet and build loosely coupled distributed applications. Again, if you are interested in this topic, turn to Chapter 21 for details on how to implement the XML Web service itself.


Mapping ADO Objects to ADO.NET

Building on the previous discussion, let’s now see how to adapt your existing code to use the new managed data objects in place of ADO. On the whole, it is a fairly simple process. We’ll start by going over generic technology mappings to give you an idea of where to begin, and then we’ll move on to some implementation details.

In general, the most direct mapping for ADO is to the OleDb namespace under System.Data, although if you expect to be using Microsoft SQL Server, the SqlClient providers will offer the best performance. An early beta version of the .NET Framework had an ADO provider namespace. It was decided, however, that this name would be too confusing, since the only real commonality was the use of the underlying OLEDB database providers. Therefore, the namespace was renamed to OleDb. This change was meant to emphasize to developers that the OleDb provider is to be used to access the various OLE DB database providers (as was ADO). Remember, however, that ADO is a COM-based interface to OLE DB data providers. The OleDb namespace contains managed interfaces to OLE DB data providers and actually lacks certain features found in ADO (hierarchical Recordsets come to mind).

For the purposes of this discussion, we will limit comparisons to those between ADO and OleDb. Keep in mind, however, that every example using classes from the OleDb namespace has a direct equivalent in the SqlClient namespace (which is specific to SQL Server). So if you are using a SQL Server database, moving between the two managed providers is often as easy as changing the class name’s prefix from OleDb to Sql (that is, OleDbCommand becomes SqlCommand). Easy, right?

Connection and Command Objects

The Connection and Command objects still exist in ADO.NET. There are, however, significant differences between the ADO and ADO.NET classes. In ADO the Connection object not only represents your connection to a database but also can be used to execute database commands. In addition, other ADO objects (Command and Recordset) had ActiveConnection properties that accepted either an existing Connection object or a provider string, which would cause the object to create a new Connection object. This arrangement led to confusion because it was possible to execute database commands using all three of these objects, leading to wildly different coding practices from method to method (let alone from project to project).

ADO.NET is much more straightforward. Connection objects are provider specific (such as OleDbConnection) and are used only to represent a connection to a database. There is no way to use them to execute a statement against a database. That is the job of the OleDbCommand class. This class is comparable to the ADO Command class in that it can be used to execute run-time queries and stored procedures. Setting properties such as CommandText and CommandType should be instantly familiar to the seasoned ADO developer in this context. There is an important difference, however, in that it is possible to be explicit about the kind of query being performed. The simple Execute method of the ADO Command object has been replaced with four separate methods that indicate expectations regarding any given query. The methods specify what data type you want back, if any:

  • ExecuteReader Execute a query and return a DataReader object.

  • ExecuteScalar Execute a query and return a single value.

  • ExecuteXmlReader Execute an XML query and return an XmlReader object.

  • ExecuteNonQuery Execute a query with no results.

What this change means is that if you are using the Execute method on your ADO Connection objects to perform queries you will need to create an OleDbCommand object to do the actual work. This separation of functionality will hopefully lead to a more consistent coding style across applications. Although it can be painful to make the necessary adjustments in your code, we consider this an improvement over classic ADO. The following code illustrates what changes are needed to upgrade a simple ADO example:

' Visual Basic 6 Method Using ADO
Sub ExecuteSql( connStr As String, statement As String )
   Dim conn as New ADODB.Connection
   Dim rs as ADODB.RecordSet
   conn.Open connStr
   Set rs = conn.Execute( sqlStatement )
End Sub
' Visual Basic .NET Equivalent Method Using ADO.NET
Sub ExecuteSql( connStr As String, statement As String )
   Dim conn as New OleDbConnection( connStr )
   Dim cmd as New OleDbCommand( statement, conn )
   conn.Open()
   Dim dr As OleDbDataReader = cmd.ExecuteReader()
End Sub
Recordsets

Not all Recordsets are created equal. Depending on the settings you use, they can take several different forms, leading to various consequences for application performance. As we discussed in the previous section, ADO.NET seeks to separate the different types of functionality into specialized classes, both so that there is never any question as to the kind of data you are working with (disconnected, forward-only “firehose,” and so on) and so that the classes can be optimized for a particular kind of data, rather than having to be general-purpose.

Forward-Only Recordsets

The forward-only Recordset is by far the most common type. It is the kind of Recordset that ADO generates by default (which is probably why it is the most common, performance advantages aside). Often this type of Recordset is referred to as the firehose Recordset because the data is read in a continuous stream, and once it has been read, it’s gone.

ADO.NET has a direct equivalent to the firehose: the DataReader object. The DataReader is always forward only, and unlike a Recordset, a DataReader can never be disconnected. Also, it doesn’t support random access under any circumstances. To simplify reading data from streams and to standardize on a common way to read data, the DataReader shares many interfaces and implementation details with other stream reader objects in the .NET Framework.

There are other implementation differences as well that can cause some initial confusion for developers. When a DataReader object is first returned, the record pointer starts before the first record in the data stream (instead of starting at the first record in the stream). A call to the Read method must be made before the first record is loaded, and successive calls to Read are necessary to continue moving through the data stream until the end is reached (signaled when Read returns False). Here is a quick comparison of the use of ADO firehose Recordsets and the ADO.NET DataReader (note the differences in the loop structures):

' Visual Basic 6 Forward-Only Recordset Access
Sub VB6RecordSetExample(connStr As String, statement As String)
   Dim conn As New ADODB.Connection
   Dim rs As ADODB.Recordset
   conn.Open (connStr)

   Set rs = conn.Execute(statement)
   Do Until rs.EOF
      Dim str As String
      Dim i As Integer
      For i = 0 To rs.Fields.Count - 1
         str = str & rs.Fields(i).Value
      Next
      Debug.Print str
      rs.MoveNext
   Loop
End Sub

' Visual Basic .NET DataReader Example
Sub VBNETDataReaderExample(ByVal connStr As String, _
   ByVal statement As String)
   Dim conn As New OleDbConnection(connStr)
   Dim cmd As New OleDbCommand(statement, connStr)

   conn.Open()
   Dim reader As OleDbDataReader = cmd.ExecuteReader()

   While reader.Read()
      Dim str As String = "", i As Integer
      For i = 0 To reader.FieldCount - 1
         str &= reader(i)
      Next
      Debug.WriteLine(str)
   End While
End Sub
Disconnected Recordsets and Dynamic Cursors

A disconnected Recordset is retrieved in much the same way as a forward-only Recordset. So is a Recordset that uses a dynamic cursor. This is not the case with a DataSet, which requires, at least in the following example, a DataAdapter to handle the data population. Thus, while ADO had common methods for creating different kinds of Recordsets, ADO.NET has specific methods for creating different forms of data repositories.

' This is a Visual Basic 6 Dynamic Cursor Recordset Example
Function VB6DynamicRSExample(connStr As String, _
   statement As String) As Recordset
   Dim rs As New ADODB.Recordset
   rs.Open statement, connStr, adOpenDynamic

   Set rs = conn.Execute(statement)
   Set VB6DynamicRSExample = rs
   Set rs = Nothing
End Function
' This is a Visual Basic .NET DataSet Example
Function VBNETDataSetExample(ByVal connStr As String, _
   ByVal statement As String) As DataSet
   Dim adapter As New OleDbDataAdapter(statement, connStr)
   Dim ds As New DataSet()

   adapter.Fill(ds)
   Return ds
End Function

The other major difference between the disconnected Recordsets and DataSets lies in how iteration is handled. The disconnected or dynamic Recordsets can use any of the Move functions (Move, MoveFirst, MoveLast, MoveNext, and MovePrevious) or can access rows using an ordinal reference or column name. A DataTable (contained within a DataSet) can be iterated using either an ordinal reference or For Each, as in the following example.

Dim row As DataRow
For Each row In ds.Tables(0).Rows
   ' Do stuff
Next

Dim i As Integer
For i = 0 To ds.Tables(0).Rows.Count - 1
   row = ds.Tables(0).Rows(i)
   ' Do other stuff
Next


Using DataView?s

A DataView is an object that allows you to create multiple views of your data and that can be used for data binding. The concept is fairly simple, but its importance cannot be overstated. The flexibility it introduces is impressive because a DataView does not actually change the DataTable from which it is generated. Instead, it offers a filtered window to the data. The following code shows how you can use the DataView control to filter rows based on a column value (in this case, the FirstName and LastName columns):

Function DataViewTest()
   Dim adapter As New OleDbDataAdapter("Select * From Customers", _
   connStr)
   Dim ds As New DataSet()

   adapter.Fill(ds)

   Dim view1 As DataView = ds.Tables("Customers").DefaultView
   Dim view2 As DataView = ds.Tables("Customers").DefaultView

   view1.RowFilter = "'LastName' Like 'O%'"
   view2.RowFilter = "'FirstName' Like 'E%'"

   Dim i As Integer

   Debug.WriteLine("All LastNames starting with 'O'")
   For i = 0 To view1.Count - 1
      Debug.WriteLine(view1(i)("LastName"))
   Next

   Debug.WriteLine("All FirstNames starting with 'E'")
   For i = 0 To view2.Count - 1
      Debug.WriteLine(view1(i)("FirstName"))
   Next
End Function


Data Binding

Earlier in this chapter, we demonstrated data binding using a Recordset (imported into a DataTable). However, that was only a small example of what is possible. This section looks at data binding in more detail, covering data binding in different environments, Web Forms controls vs. Windows Forms controls, and the kinds of objects that are supported for binding.

Binding to Windows Forms Controls

The .NET Framework brings a wide variety of controls to the developer. The Windows Forms controls are found in the System.Windows.Forms namespace, and the vast majority of them support data binding. In general, data binding to a control is far from challenging. It requires that you use an in-memory data store and can usually accept either a DataTable or a DataSet.

The mechanism is very simple. Take a look at the following example, using a DataTable:

Dim adapter As New OleDbDataAdapter("Select * From Customers", _    connStr)
Dim dt As New DataTable()

' Import the query results into the DataTable
adapter.Fill(dt)

' Bind the DataTable to the DataGrid
DataGrid1.DataSource = dt

The DataSet works in almost exactly the same way:

Dim adapter As New OleDbDataAdapter("Select * From Customers", connStr)
Dim ds As New DataSet()

' Import the query results into the DataSet
adapter.Fill(ds)

' Bind a DataTable from the DataSet to the DataGrid
DataGrid1.DataSource = ds.Tables("Customers")

Notice in this example that instead of passing the entire DataSet to the DataGrid, we’ve chosen to bind only a single table. This is often necessary with controls for which multiple sets of tables don’t make sense. The DataGrid is not one of these controls, however. It is capable of a multitable display (try it) and likes the DataSet just fine.

Binding with Web Forms Controls

Data binding with Web Forms controls requires an additional step. Web Forms controls reside under the System.Web.UI namespace, and because they are destined to run only within the ASP.NET world, some of their behaviors are, by necessity, different from those of their System.Windows.Forms counterparts. Data binding is just one example of such a behavior. The Windows Forms controls examples apply to Web Forms controls, but with a caveat: you need to explicitly tell the control to bind to the data source you specified, as follows:

Dim adapter As New OleDbDataAdapter("Select * From Customers", _    connStr)
Dim ds As New DataSet()

' Import the query results into the DataSet
adapter.Fill(ds)

' Bind a DataTable from the DataSet to the Web Form DataGrid
DataGrid1.DataSource = ds.Tables("Customers")
DataGrid1.DataBind()

Notice that this example is exactly the same as the Windows Forms DataSet example just given, with one additional line of code. Calling DataBind on the Web Form DataGrid forces the binding to occur—a necessary step, without which the control would not be bound to the specified data.


A Note About Performance

Performance is tricky. Tweaking an application to get it running under heavy load is an expensive and time-consuming task. From the standpoint of data access, however, it is possible to ensure a high level of performance as long as you follow these two coding conventions:

  • Open connections as late as possible.

  • Close them as soon as possible.

These two guidelines seem simple, yet their importance cannot be overstated. Connections to databases represent real physical resources on your machine, and hogging those resources will inevitably result in poor performance. For performance-sensitive applications, it is absolutely essential that you explicitly close all of your database connections as soon as you are done with them. This is necessary for two reasons. First, the connection will not automatically be closed for you as soon as the reference goes out of scope. Garbage collection is an indeterministic process, and it is impossible to guarantee the timing of a collection (unless you force it, which is a horribly expensive solution). Second, if a connection is left to be collected, it might not make it back into the connection pool, requiring the creation of a new connection. This adds more overhead to an application. Ideally, you want to get a connection from the connection pool (by calling Open on your connection object) and then return it to the pool immediately when you are done with it (by calling Close).

On another performance note, you should make sure that you choose your managed data provider carefully. Although the OleDb providers enable you to access virtually any database or data source (including SQL), Microsoft has also developed a SQL-specific managed provider that does not use OLE DB. In fact, the SqlClient namespace provides significant performance gains when accessing SQL Server–based databases because it is a network-level provider and communicates directly with a SQL server through low-level network access. The OleDb managed provider uses the OLE DB database providers, not only introducing an additional layer for OLE DB but also running it through the COM interop layer, further increasing overhead. When performance is critical, use only the OleDb provider for data sources other than Microsoft SQL Server (unless other companies have produced their own managed database providers by the time you read this).

Using Open and Close Effectively

If you need to do a lot of database work (such as making multiple sequential queries), it is important to make sure that you do not hold on to your database connections throughout the series of calls. If possible, you should release the connections between queries. This does add some additional overhead to your method (because it has to get connections from and return them to the connection pool more frequently), but it increases churn in the pool and ensures that connections will always be available to handle your application’s needs. Take it from us—when your application runs out of connections, its performance craps out big time (yes, that is a technical term), and that’s putting it mildly. The following example illustrates good database etiquette:

Function DBTest()
   Dim conn As New SqlConnection(connStr)
   Dim cmd As New SqlCommand("Select * From Customers", conn)

   ' Open the connection and execute the command
   conn.Open()
   Dim dr As SqlDataReader = cmd.ExecuteReader()

   While dr.Read()
      ' Do some processing
   End While

   ' Close the reader and the connection
   dr.Close()
   conn.Close()

   ' Get ready to do your next query
   cmd.CommandText = "Select * From Employees"

   ' Open the connection and execute the next command
   conn.Open()
   dr = cmd.ExecuteReader()

   While dr.Read()
      ' Do some other processing
   End While

   ' Close the reader and the connection
   dr.Close()
   conn.Close()
End Function


Conclusion

We covered a lot of ground in this chapter, from using ADO within your Visual Basic .NET application, to data binding and getting Recordsets to work with XML Web services, to moving to ADO.NET. Remember that you have many choices regarding how you implement your application. If your investment in ADO is too heavy at this time for you to consider upgrading to ADO.NET, you can still continue to improve and develop your application with an eye to switching to ADO.NET in the future. On the other hand, when you make the plunge into ADO.NET and the various managed providers, you will reap significant performance gains over pure ADO.


Upgrading Distributed Applications

Chapter 21
Upgrading Distributed Applications

Until now we haven’t really talked about upgrading large-scale distributed applications. But building these types of applications is, after all, the cornerstone of the entire Microsoft .NET initiative. The .NET Framework is designed from the ground up to support new first-class distributed technologies. Knowing how these technologies are implemented is critical in assessing how to upgrade a given application. Unfortunately, so many possibilities and variations exist that it is impossible to address all or even a significant portion of them in a meaningful way.

This chapter gives an overview of the core concepts behind large-scale distributed applications. It then discusses how these concepts are implemented in Visual Basic .NET. We highlight the use of these technologies and describe how to implement them in your applications. Although this chapter covers some interoperability issues (such as how to build wrappers and marshal objects such as ADO Recordsets from the server to the client), there are others you may face if you get creative with your application. Consult Chapter 13 for more information regarding COM interoperability.

note | Some of the examples in this chapter use the Northwind database Northwind.mdb (included on the companion CD) as a basis for demonstrating various forms of data access.


Important Concepts for Distributed Applications

Since our topic is building large-scale distributed applications in Visual Basic .NET, now would be a good time to take a step back and provide an overview of the concepts that are important for understanding the benefits and trade-offs of the possible approaches. Writing smaller applications usually does not require a great deal of planning or design work. Issues such as calling overhead and latency don’t typically rear their heads in smaller applications. Larger-scale applications, however, are a far different beast. For the purposes of this chapter, we define a distributed application as an application in which one or more of the logical tiers are located on remote machines. It is critically important to be aware of the design issues that can potentially make or break such an application. To this end, this section looks at three important concepts in large-scale distributed application development:

  • Loosely coupled and tightly coupled applications

  • Overhead involved in method calls

  • Componentization and logical organization

Loosely Coupled vs. Tightly Coupled Applications

Imagine using a standard platform-independent communication mechanism between remote (network-separated) application segments (think SOAP). What this does is enable you to develop applications that have no knowledge or understanding of either participating system. In this scenario, you agree only on the communications mechanism. The platforms can vary wildly. In .NET, two systems are considered loosely coupled if the only mandate imposed on them both is to understand self-describing, text-based messages.

Tightly coupled systems, on the other hand, impose a significant amount of customized overhead to enable communication and require a greater understanding between the systems (think Distributed Common Object Model [DCOM] or remoting). A tightly coupled application requires that there be a greater level of system integration and dependence. A loosely coupled system is far less prone to problems with system setup, as long as the minimum communication requirements are met.

The other side to coupling is the overhead requirement. A loosely coupled system imposes greater marshaling overhead. The native binary format must be translated to something that both systems can understand. It is then the responsibility of the target system to parse that message and convert it to a format that it understands. A tightly coupled application does not need to translate to and from a universal format. Typically the network format is binary and is well understood by both parties, requiring less translation and, by extension, the use of fewer processing resources.

Overhead in Method Invocation

When you are developing a distributed application, you need to be aware of the limitations of this type of application. In a standard application, you typically don’t worry about the processing overhead of function calls. These calls are typically limited only by the speed of your computer. Moving to the network introduces more variables into the equation.

Figure 21-1 demonstrates the relative differences in the overhead of method calls, depending on where the target resides. As you can see, in-process method invocation has very little calling overhead. Out-of-process method invocation introduces more overhead, due to marshaling across the process boundaries. This type of invocation involves communicating with another application on the local computer (such as a Microsoft Windows service or a COM+ server application). Remote process method invocation brings into play not just process-boundary marshaling but also network latency. This typically means a vastly greater calling overhead than any of the other options and implies the need for a different approach to the application’s architecture.

Figure 21-1
Overhead of method calls in different contexts.

So while we can call into local methods without any concern for overhead, and even out-of-process overhead isn’t too bad, we need to be extra careful of not only how often we call remote methods but also how much work these methods do. Think of it this way: since the overhead is greater, we need to make the method calls worthwhile. Consider the following example to help demonstrate what we mean. It shows the difference between setting properties on an object—a typically simple operation with low overhead—and using a single method to set all the properties at once.

' Approach 1 – Don't do this for a remote or out-of-process object
Obj.Property1 = val1
Obj.Property2 = val2
Obj.Property3 = val3
Obj.Property4 = val4
Obj.Property5 = val5

' Approach 2 – Better for a remote or out-of-process object
Obj.SetValues(val1, val2, val3, val4, val5)

This example uses two different approaches, but it does not really clarify why one is better than the other. For this, look to Figure 21-2. It should help you visualize the differences between the approaches and understand how they affect an application’s performance. Approach 1 is “chatty” in that each property access involves a separate network operation, whereas approach 2 is “chunky” because it accomplishes its task with a single network operation.

Figure 21-2
Remote objects and the effect of network latency.

At this point, it should be clear that the design of your remote objects and interface is vitally important to your application’s scalability and performance. When you look at the performance of approach 1, you should also understand that when you access any remote object you are consuming resources on the remote server, which means that fewer connections are available to other applications. The more efficient your design, the better your application will perform and the better it will scale up in an enterprise environment.

Componentization and Logical Organization

Componentization is an extremely important concept for any large-scale application. It is more than just a separation based on the functional tiers of your application (such as user interface, business logic, and the data tier). It is also the separation of functional areas within individual tiers into specific components. This separation has several benefits. Imagine a customer management application. You might divide the server-side components into database, business logic, and general utility components, as shown in Figure 21-3. This structure gives you the ability to concentrate all of the database logic into a single area of the application and manage database changes through versions in a more feasible fashion.

Figure 21-3
Hypothetical componentized application.

From the standpoint of upgrading, logical separation of code enables a smoother approach to upgrading larger applications. It gives you more choices regarding what to upgrade and how to upgrade your application within a given application. So before you undertake any upgrading tasks, it makes a lot of sense to analyze your application and look at ways to partition the logic into separate containers and to consider the option of leaving some code in Visual Basic 6 while upgrading elements that could derive immediate benefits.


Distributed Technologies in .NET

Visual Basic .NET uses three main distributed technologies (ignoring the ADO.NET data access technologies). The first is XML Web services. This is an open standard that enables developers to expose a set of software services over the Internet and to do so simply. The second is another new technology, unique to the .NET Framework, called .NET remoting. You can think of remoting as the .NET equivalent of DCOM. It is a method of communication between different operating system processes, regardless of whether they are on the same computer. The .NET remoting system is an architecture designed to simplify communication between objects living in different application domains, whether they’re on the same computer or not, and between different contexts, whether they’re in the same application domain or not. The last of the distributed technologies is COM+ application proxies. If you have worked with COM+ in a distributed environment, you are probably already familiar with application proxies. Let’s look at each of these technologies in turn.


XML Web Services

The Internet is quickly evolving from the Web sites of today that deliver only pages to Web browsers to the next generation of programmable Web-based technologies that directly link organizations, applications, services, and devices with one another. These programmable Web sites are not passive—they are reusable, intelligent Web services. The common language runtime provides built-in support for creating and exposing XML Web services, using a programming abstraction that is consistent and familiar to both ASP.NET Web Forms developers and existing Visual Basic users. The resulting model is both scalable and extensible and embraces open Internet standards—HTTP, XML, SOAP, and Web Services Description Language (WSDL)—so that it can be accessed and consumed from any client or Internet-enabled device.

Support for XML Web services is provided by ASP.NET, using the .asmx file extension. An .asmx file contains code similar to an .aspx file. In fact, much of the ASP.NET programming model is available to XML Web service developers, with the exception of the user interface classes. For a Web service, the user interface has no real meaning. It is, after all, a middle-tier technology. There is a caveat, however. ASP.NET projects and ASP.NET Web service projects are not fundamentally different, and there is no required separation. You can add an XML Web service file to a Web project, and you can also add a Web form to an XML Web service project. These files are then URI addressable, in the same way that .aspx files are. Also like .aspx files, .asmx files are compiled automatically by the ASP.NET runtime when a request to the service is made. (Subsequent requests are serviced by a cached precompiled object.)

On a more fundamental level, XML Web services enable the exchange of data and the remote invocation of application logic using XML messaging to move data through firewalls and between heterogeneous systems. Although remote access of data and application logic is not a new concept, XML Web services enable it to take place in a loosely coupled fashion. The only assumption between the XML Web service client and the XML Web service is that recipients will understand the messages they receive. As a result, programs written in any language, using any component model, and running on any operating system can access XML Web services.

When you create and/or use an XML Web service, you are taking advantage of SOAP. You may be aware of the SOAP Toolkit that was published for Visual Basic developers. While you can think of XML Web services as an alternative to the SOAP Toolkit, they are far more powerful, thanks to the .NET Framework. The SOAP Toolkit required a lot of work on the part of the developer, and Visual Basic imposed limitations related to serialization of objects. Visual Basic .NET removes these limitations and provides a far more powerful framework for developing SOAP applications by eliminating the need to implement features such as serialization and proxy generation. These features are already implemented in Visual Basic .NET and can be leveraged without any additional effort. It is possible, of course, to implement serialization yourself to provide any custom features that your application requires. For the vast majority of applications, however, the built-in serialization capabilities of the .NET Framework are more than sufficient.

Creating a Simple XML Web Service

Implementing a basic XML Web service in any application is a straightforward process. The XML Web Service project type (selected in the New Project dialog box) contains all the necessary elements for implementing an XML Web service. If you are running Visual Basic .NET on a machine with Internet Information Services (IIS) installed, it is a trivial task to create a new project and implement an XML Web service. The companion CD contains a sample called Simple Web Service, as well as a setup document that discusses the steps necessary to get the example up and running.

The basic steps for creating an XML Web service are as follows:

  1. Create a new ASP.NET Web service project.

  2. Add a new XML Web service file to the project.

  3. Add a method to the XML Web service class, and mark the method with the WebMethod attribute.

The most important part here is the use of the WebMethod attribute. This attribute tells the compiler to generate the XML contract for the Web service, register it for discovery, and produce the implementation code necessary for marshaling parameters and return objects. It also enables you to filter the methods of your XML Web service class and publish only the methods you want to make publicly available over the Internet. Think of this filtering as taking access protection a step further. You’re already familiar with creating public, private, and friend methods. But there will probably be situations in which your application architecture requires a public method on your XML Web service class that you do not want to make available through the XML Web service itself. In such cases, the power of the WebMethod attribute really shines. It gives you absolute control over what you publish over the Internet without requiring you to sacrifice good component design in your XML Web service classes. The following excerpt from the SimpleService.asmx.vb file (part of the SimpleWebService sample project) demonstrates the WebMethod attribute in action:

<WebMethod()> Public Function HelloWorld() As String
   HelloWorld = "Hello World. The current time is " & Now()
End Function

You can do much more with the WebMethod attribute, such as manipulate additional settings, but we will use it in its simplest, most basic form, as demonstrated here. The MSDN documentation gives a more thorough treatment of the use of this attribute, above and beyond its default behavior.

Now is a good time to talk about restrictions on parameters and return values for XML Web services. An XML Web service is not a bidirectional object. This means that you cannot pass objects by reference; they must always be passed by value. In other words, your parameters and return values must be serializable. Most, if not all, of the intrinsic data types in Visual Basic .NET and the .NET Framework support serialization as long as the objects themselves are self-contained—that is, as long as they don’t represent or contain any physical system resources, such as database connections or file handles. For your custom classes you have two options. You can implement the ISerializable interface for your class, but this is the more manual process. Alternatively, you can use the Serialize class attribute. This option enables your class to serialize itself. No work is necessary on your part unless one or more of the types contained in your class do not support serialization. In such cases, you have the choice of adding the Serializable attribute to those classes (if they are under your control) or implementing the serialization yourself with the ISerializable interface.

The Simple Web Service example demonstrates the basic elements of an XML Web service. Figure 21-4 illustrates what the compiled service looks like in a Web browser. You can see all the methods exposed by the service and view the complete XML service description. We will leave the latter for you to pursue if you’re interested. It is really used only by the Web reference mechanism in Visual Basic .NET, and you don’t need to be familiar with the contents of the service description to make use of an XML Web service.

Figure 21-4
The SimpleService.asmx XML Web service as it appears in a Web browser.
Testing Your XML Web Service

As you have already seen, when you build an XML Web service, the Visual Basic IDE creates a test page for that service. This page allows you to inspect all the methods that are exposed, and it also gives you the ability to invoke the methods manually. This should be your first step in testing the service, to ensure that it is doing what you expect. In our sample service, you can click the HelloWorld method in the service description page to get more information about it. Figure 21-5 shows what this method looks like.

Figure 21-5
The SimpleWebService’s HelloWorld method.

As you can see, a great deal of information is available through this page. You can also invoke the method (using the Invoke button) and view the SOAP result message. Additionally, if the service accepts parameters, this page will have input boxes for each of the parameters. There are some limitations to this, however. If your methods require parameters in a binary format (a Byte array, for example), you will need to invoke the method programmatically.

The result of invoking the HelloWorld method is displayed in a separate window and looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<string xmlns="http://tempuri.org/">
Hello World. The current time is 10/13/2001 11:13:21 AM</string>

Pretty neat, huh? Now we need to look at how you can use this method in an application.

Consuming a Simple Web Service

Now that an XML Web service exists on the target machine, it is possible to add a Web reference to a client project. An XML Web service client can be any kind of project, from a command-line or form-based application to a full-blown Web site or COM+ component. In this case, we have gone with a simple form-based application, but feel free to experiment by adding Web references to other kinds of projects.

Recall our discussion of different types of references in Chapter 6. A Web reference is simply a process by which the Visual Basic IDE goes out to the specified XML Web service, gets the XML contract for the service, and builds a proxy class that implements the contract. This process occurs transparently, and it is flexible enough that references can be “refreshed” in the IDE. In other words, it is possible to have the IDE regenerate the proxy class (which is usually desirable if there are new features that you want to take advantage of or if there have been interface implementation changes).

This proxy is not tightly coupled to the complete interface of an XML Web service. It will use methods and properties according to how they were defined the last time the proxy was generated. If the specified methods have not changed their signature—that is, the parameter types, parameter count, or return type—your application will not break, regardless of any other changes that might have been made to the XML Web service itself.

Creating this proxy is simple, as we have already mentioned. You create a simple Windows Forms project and add a Web reference by right-clicking the References collection in the Solution Explorer, specifying the path to the XML Web service that you have already created, and clicking OK. After a short delay, a new XML Web service proxy class should be added to your application. Now all that is necessary is to use it. Our sample solution has a Web Service Tester project that implements this sample XML Web service. This is a one-button application that does the following two things in response to a button click:

  1. Create the XML Web service proxy object.

  2. Call the HelloWorld method, and display a message box with the result.

The code itself is simple:

Private Sub CallButton_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles CallButton.Click
   Dim ws As New localhost.SimpleService()
   MsgBox(ws.HelloWorld())
End Sub

You can see the results in Figure 21-6. This sample project helps demonstrate how XML Web services simplify the development of distributed applications. XML Web Services are a powerful tool that enable the development of fundamentally different kinds of applications, due to its platform- and language-independent nature.

Figure 21-6
Web Service Tester application in action.
Moving On

This is all well and good, you may be thinking, but what about my existing applications? How should I implement XML Web services in an existing COM or managed application? Does it require redesign, or are there alternatives? Read on….

Supporting Web Services in Your Existing Applications

If you have an existing application for which you want to implement XML Web services without a major rewrite, you can take the approach of implementing the service as a wrapper around your existing code. Doing so allows you to handle the necessary serialization work in the service without having to modify the original application’s code. This can be an effective way to add support for XML Web services to existing COM applications without making any radical changes. It also gives the developer the opportunity to rethink the application’s interface to the outside by hiding methods or doing additional work that was not previously done on the server side.

This approach does raise some questions. If you are wrapping existing COM applications, you need to be aware of return types and whether you will need to implement serialization for them. This concern arises as a potential problem only when you need to pass variables that are specific COM types. Intrinsic data types will have no problems; it gets tricky only when you have complex custom data types, such as COM objects. What it ultimately boils down to is that you cannot use a COM component as either a Web service parameter or a return type. You must first serialize a COM component to an intrinsic data type (an XML string or a Byte array). Of the commonly used Microsoft COM objects, only ADO supports serialization to and from XML, and it is a manual process—there is no such thing as implicit serialization for COM objects. If you create your own COM components, you may wish to add support for XML serialization. Including support is essential if you want to support passing such a component to and from an XML Web service.

There is also the possibility that you cannot modify the original COM object to add serialization capabilities. In this instance, you would be required to create code to perform the necessary serialization work in Visual Basic .NET, using it on both the server and client tiers. However, it may not be possible to work around some issues and you may be required to make some modifications to your original application. From a middle-tier standpoint, one of the most common objects passed around, and thus a likely candidate for inclusion in an XML Web service, is the ADO Recordset object, which is the subject of the next section.

Using Recordset with XML Web Services

As we mentioned earlier, it is possible to wrap an XML Web service around an existing COM object. If you are passing only the intrinsic types (such as strings, integers, and arrays of variables), you simply need to call the methods directly. The .NET Framework already knows how to serialize these types of variables. If you need to pass ADO Recordsets back and forth, however, you have some work to do.

In ADO 2.1, Microsoft added XML support to the ADO Recordset object. This object provides support for serialization to and from XML. The only caveat is that the support for serialization is not implicit. In other words, the Recordset does not support automatic or implicit serialization, but you can do the work explicitly. The key here is the Save method on the Recordset object and the use of the ADODB Stream object. Figure 21-7 illustrates the process of wrapping a COM object with an XML Web service, where the result is an ADO Recordset, and shows how the Recordset is marshaled from server to client.

Essentially, the figure depicts the process by which the Recordset is persisted to XML on the server and is returned to the client. The client then reconstructs the original Recordset. Notice that the reconstructed Recordset is not exactly the same as the original. The new Recordset does not contain any connections to a database, nor does it understand where it came from. For this reason, we say that this Recordset is disconnected.

Figure 21-7
Passing a Recordset in an XML Web service.

To give you a better feel for how this process works, we’ve provided a sample on the companion CD called Web Services and ADODB. The sample consists of two projects: an XML Web service (NorthwindDatabase) and a Windows Forms client application (Northwind Viewer). The design of the sample is simple. One method is available through the XML Web service that allows the client to specify a SQL query string and returns the result as an XML representation of the resulting Recordset.

The following is the implementation code for the XML Web service. The Query method does all the work. You can see that this method takes a query string as a parameter and returns a string. As we have discussed previously, this return string contains the XML representation of the Recordset returned by the specified query. This project references ADODB and uses the ADO Connection object directly to execute the query. It would also work just fine with a regular COM object that returns a Recordset; we are just trying to make the sample as clear and concise as possible.

Imports ADODB
Imports System.Web.Services

<WebService(Namespace:="http://tempuri.org/")> _
Public Class NorthwindDatabase
   Inherits System.Web.Services.WebService

#Region " Web Services Designer Generated Code "

   <WebMethod()> Public Function Query(ByVal queryString As String) _
      As String
      Dim conn As New Connection()

      Try
         ' You will need to specify the local path to your copy
         ' of the Northwind.mdb file. This is just an example.
         conn.Open( "Provider=Microsoft.Jet.OLEDB.4.0;" & _
            "Data Source=c:\Northwind.mdb")
      Catch e As Exception
         Return Nothing
      End Try

      Dim rs As Recordset
      Try
         rs = conn.Execute(queryString)
      Catch e As Exception
         conn.Close()
         Return Nothing
      End Try

      Dim str As New StreamClass()
      rs.Save(str, PersistFormatEnum.adPersistXML)
      Query = str.ReadText()

      str.Close()
      rs.Close()
      conn.Close()
   End Function
End Class

After the query has been executed, we use the ADODB Stream class to serialize the contents of the Recordset. (In Chapter 20, we touched on XML serialization for Recordsets and introduced the concept with the RsToString and StringToRS methods.) Notice that there are no particular requirements as to the type of Recordset used. This example uses a forward-only Recordset (which is more than sufficient), but it would work equally well with any kind of client or server-side cursor. Also notice that we close out all the ADO classes (Stream, Recordset, and Connection). Chapter 10 discusses why this step is crucial.

Now that we have the XML Web service, we need something to test it. The test sample, Northwind Viewer, has a couple of interesting features. Not only does it deserialize a Recordset from XML, but it also uses the OleDbDataAdapter to convert the Recordset to an ADO.NET DataTable. It then sets the DataSource property of the QueryDataGrid using the newly created table. That’s it, aside from additional initialization code in the form’s Load event. We should point out one thing here. We instantiate the XML Web service proxy only once in the Load event, rather than on every button click. This practice is perfectly acceptable because the proxy class does not represent an active network connection. Connections are made only when a method on the class is invoked, so we don’t bother creating the same proxy over and over. Creating it once is sufficient. Again, notice that we clean up the ADO objects by calling Close. Always play nice with your COM objects and clean up afterward.

Imports ADODB
Imports System.Data
Imports System.Data.OleDb

Public Class Form1
   Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

   Dim nwdb As localhost.NorthwindDatabase
   Private Sub Form1_Load(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Load
      nwdb = New localhost.NorthwindDatabase()

      ' Add the query strings for the database
      QueryComboBox.Items.Add("Select * From Categories")
      QueryComboBox.Items.Add("Select * From Customers")
      QueryComboBox.Items.Add("Select * From Employees")
      QueryComboBox.Items.Add("Select * From [Order Details]")
      QueryComboBox.Items.Add("Select * From Orders")
      QueryComboBox.Items.Add("Select * From Products")
      QueryComboBox.Items.Add("Select * From Shippers")
      QueryComboBox.Items.Add("Select * From Suppliers")
   End Sub

   Private Sub ExecuteButton_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles ExecuteButton.Click
      Dim xml As String

      xml = nwdb.Query(QueryComboBox.Text)
      If xml Is Nothing Then
         QueryDataGrid.DataSource = Nothing
         MsgBox("The query failed!")
         Exit Sub
      End If

      Dim str As New Stream()
      str.Open()
      str.WriteText(xml)
      str.Position = 0

      Dim rs As New Recordset()
      rs.Open(str)

      Dim da As New OleDbDataAdapter()
      Dim table As New DataTable()

      da.Fill(table, rs)

      QueryDataGrid.DataSource = table

      rs.Close()
      str.Close()
   End Sub
End Class

Figure 21-8 demonstrates the test application in action. You can see that the user selects a query from the ComboBox and then clicks the Execute button. Another neat feature of this application is that the ComboBox is editable at run time, so you can further customize the query.

Figure 21-8
Northwind Database Viewer application in action.

You should now be able to see how you might get started wrapping your application with XML Web services. Depending on how your application is implemented, it is quite possible to handle the serialization issues without a great deal of disruption. An advantage is that it is extremely easy to create a test XML Web service to discover how your application will behave and what is feasible.


Remoting

As we mentioned earlier, remoting is the process of communication between different operating system processes, regardless of whether they are on the same computer. It simplifies communication between objects living in different application domains, possibly on different computers, and between different contexts, possibly in different application domains. The remoting framework is built into the common language runtime and can be used to build sophisticated distributed applications. Some of the features provided by .NET remoting are as follows:

  • Proxy objects

  • Object passing

  • Activation models

  • Stateless and stateful objects

  • Channels and serialization

  • Lease-based lifetime

  • Hosting objects in IIS

The most basic remoting scenario requires a client application and a minimal host process. The host process must register specific object types with the runtime and specify the desired configuration details. Remoting itself can be thought of as an abstraction layer over a transport protocol that enables applications to instantiate objects that may reside on remote machines and work with them as if they were local. It is an interesting technology because it is removed from the underlying transport protocol. What this means is that, unlike DCOM, you have choices as to how your client and server processes talk to each other.

Currently, remoting supports two transport protocols: TCP/IP and HTTP. Each has its advantages, depending on your requirements. TCP/IP offers the better performance by far. It is basically a low-level binary protocol that enables virtually direct communication with the remote objects. This protocol tends to work well in an intranet environment in which you have unrestricted TCP port usage. Using TCP/IP becomes problematic when you need to support clients across multiple sites or over the Internet. Most corporations have firewalls that block anything but the standard TCP/IP ports, which would render a remoting application using a custom TCP/IP port unusable.

The solution is the HTTP protocol. Firewalls are almost always configured to allow traffic over TCP/IP port 80 (the port used by the World Wide Web). Using HTTP, your remoting applications can communicate freely over the span of the Internet. The downside is that HTTP is nowhere near as efficient as TCP/IP. When you specify HTTP as your remoting transport protocol, you basically end up with XML over HTTP. Serializing your objects into XML is not as compact as using a binary format. It increases the size of the payload, increases processing resources on both the client and server, and takes longer to transmit. This extra overhead means that a method call over HTTP costs more than one over TCP/IP. Will you notice the difference in casual use? Not really. But it does have implications for larger-scale applications. It’s nothing to worry about, just something to keep in mind.

As far as requirements for objects capable of remoting go, there is only one that is significant: they must derive the MarshalByRefObject class. Notice that this is a built-in feature of COM+ classes because the ServicedComponent class has MarshalByRefObject as a base class. Now is a good time to look at an example.

A Simple Remoting Example

The simple example in this section helps demonstrate the lifetime of remoting within your applications and illustrates some of the architectural considerations. Called Simple Remoting and included on the companion CD, it consists of a host process and a client process. The host process is a lightweight application that opens a TCP channel for listening to remoting requests and registers a type with the common language runtime. Once it is registered, the type is available for remote instantiation through the specified channel. The example uses the type SimpleClass (derived from MarshalByRefObject) as the remoting object and contains two separate projects (a server project and a client project). Before we go any further, let’s take a look at SimpleClass:

Public Class SimpleClass
   Inherits MarshalByRefObject

   Public Function GetDateTime() As Date
      Return Now()
   End Function

   Public Function Hello() As String
      Return "Hello World!"
   End Function
End Class

From the application’s perspective, the critical part is the server project, which is responsible for registering the remoting object with the host runtime. The code for doing this is fairly simple and looks like the following:

Dim channel As New TcpChannel(9999)
ChannelServices.RegisterChannel(channel)
RemotingConfiguration.ApplicationName = "SimpleRemotingServer"
Dim t As Type = Type.GetType("Server.SimpleClass")
RemotingConfiguration.RegisterWellKnownServiceType(t, "SimpleClass", _
   WellKnownObjectMode.SingleCall)

It is important to note that the type is available through remoting only when the host process is active. If the host process terminates at any time, all types registered by that process are immediately unregistered and cease to respond to any incoming requests. This includes objects that have already been instantiated. Thus, if the process terminates while a client is still working with the proxy, the client will experience a network failure on the next call through the proxy object. Figure 21-9 illustrates the lifetime of the Simple Remoting example. It emphasizes that once the SimpleClass type has been registered, it is available only during the lifetime of the server process.

Figure 21-9
Life cycle of the Simple Remoting example.

As you can see, we need to keep this process running in some form or another; otherwise, our client is out of luck. Speaking of the client, here is what it looks like.

Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Tcp

Public Class Form1
   Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

   Dim sc As SimpleClass
   Private Sub DateTimeButton_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles DateTimeButton.Click
      If Not Initialize() Then Return

      MsgBox(sc.GetDateTime(), MsgBoxStyle.DefaultButton1, _
         "GetDateTime()")
   End Sub

   Private Sub HelloButton_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles HelloButton.Click
      If Not Initialize() Then Return

      MsgBox(sc.Hello(), MsgBoxStyle.DefaultButton1, "Hello()")
   End Sub

   Public Function Initialize() As Boolean
      If sc Is Nothing Then
         Dim chan As New TcpChannel()
         ChannelServices.RegisterChannel(chan)
         Dim t As Type = Type.GetType("Server.SimpleClass")
         sc = Activator.GetObject(t, _
            "tcp://localhost:9999/SimpleClass")

         If sc Is Nothing Then
            MsgBox("Could not initialize the remoting client. " & _
               "Check your configuration.")
            Return False
         End If
      End If

      Return True
   End Function
End Class

From this example, you can see how the client initializes the SimpleClass proxy by making the request of the Activator class. An important configuration requirement that is not obvious here is that both the client class and the server class must reside in the same namespace. That is why both the server code and the client code refer to Server.SimpleClass. Both projects have a copy of the class, and their namespaces match. This allows the client to know the fully qualified type of the SimpleClass that is also known to the server process. The application itself should look like Figure 21-10.

Figure 21-10
Simple Remoting sample in action.

While this example will work, it is not the most desirable way to go about implementing remoting in an application—especially in a larger-scale application. This observation leads us to a discussion of the best architecture for an application that uses remoting.

Architecture for Remoting

The .NET Framework SDK has many decent remoting examples, but they are not in-depth enough to help you decide on an implementation strategy. Calling remote objects is by definition an expensive process, requiring you to think very carefully about how a remote object is used. Often when we use objects in our applications, we don’t worry about whether we are using properties or methods, and we are not usually concerned with when and how often we manipulate those objects. In a distributed application, however, these are extremely important considerations. With XML Web services, it is possible to make methods available selectively. With remoting, it is not quite so simple. You are working with a proxy for the original class, and by definition all public methods are available to the client process. However, there is a way around this issue: interfaces.

Interfaces are an important way to implement a class for remoting. Programming against a limited interface prevents unintentional use of an object. You can do this by creating a separate assembly that contains the public interfaces intended only for use by remoting clients. Figure 21-11 illustrates this concept.

Figure 21-11.
Suggested remoting architecture.

Programming against interfaces is important for larger-scale applications because it allows you to further control which methods are exposed to down-level clients. This technique gives you a greater level of control than just access protection for methods on any given class. We’ve provided another sample program on the companion CD, called Architecting for Remoting, that demonstrates how to use this technique. There are three main components to the application, as outlined in Figure 21-11: the INorthwindDatabase interface, the Database class (which implements the INorthwindDatabase interface), and the MyClient project. The critical difference between this example and the previous one is that the client application has awareness only of the INorthwindDatabase interface. It does not have a copy of the target class and does not know how the class is implemented. Here is the interface we are talking about:

Public Interface INorthwindDatabase
   ReadOnly Property Categories() As DataTable
   ReadOnly Property Customers() As DataTable
   ReadOnly Property Employees() As DataTable
   ReadOnly Property OrderDetails() As DataTable
   ReadOnly Property Orders() As DataTable
   ReadOnly Property Products() As DataTable
   ReadOnly Property Shippers() As DataTable
   ReadOnly Property Suppliers() As DataTable
End Interface

The next code example is the actual database class that was created to implement the INorthwindDatabase interface. Notice that the class contains other public methods that do not exist in the interface. As far as the class is concerned, these are utility methods that the client should not be able to call (the ExecuteQuery method, for example).

Requiring the client to use the interface, instead of the actual class definition, has two effects. First, it prevents the client from using methods it should not have access to. Second, it allows you to create other database classes based on the same interface and use them all interchangeably without having to make any modifications to the client application. The implementation class, in this example database, is derived from ServicedComponent instead of directly from MarshalByRefObject, for the sake of demonstrating how easy it is to support COM+ and remoting simultaneously.

Imports MyInterfaces
Imports System.Data
Imports System.Data.OleDb
Imports System.EnterpriseServices

' The database class containing the implementation for the
' INorthwindDatabase interface
Public Class Database
   Inherits ServicedComponent
   Implements MyInterfaces.INorthwindDatabase

   Dim conn As OleDbConnection
   Dim cmd As OleDbCommand
   Dim da As OleDbDataAdapter

   Public Sub New()
      conn = New OleDbConnection( _
         "Provider=Microsoft.Jet.OLEDB.4.0;" &
         "Data Source=C:\Northwind.mdb")
      cmd = New OleDbCommand("", conn)
      da = New OleDbDataAdapter(cmd)
   End Sub

   Private Function ExecuteQuery(ByVal query As String) As DataTable
      Dim t As New DataTable()
      conn.Open()
      cmd.CommandText = query
      da.Fill(t)
      conn.Close()
      Return t
   End Function

   Public ReadOnly Property Categories() As DataTable _
         Implements INorthwindDatabase.Categories
      Get
         Return ExecuteQuery("Select * From Categories")
      End Get
   End Property

   Public ReadOnly Property Customers() As DataTable _
         Implements INorthwindDatabase.Customers
      Get
         Return ExecuteQuery("Select * From Customers")
      End Get
   End Property

   Public ReadOnly Property Employees() As DataTable _
         Implements INorthwindDatabase.Employees
      Get
         Return ExecuteQuery("Select * From Employees")
      End Get
   End Property

   Public ReadOnly Property OrderDetails() As DataTable _
         Implements INorthwindDatabase.OrderDetails
      Get
         Return ExecuteQuery("Select * From [Order Details]")
      End Get
   End Property

   Public ReadOnly Property Orders() As DataTable _
         Implements INorthwindDatabase.Orders
      Get
         Return ExecuteQuery("Select * From Orders")
      End Get
   End Property

   Public ReadOnly Property Products() As DataTable _
         Implements INorthwindDatabase.Products
      Get
         Return ExecuteQuery("Select * From Products")
      End Get
   End Property

   Public ReadOnly Property Shippers() As DataTable _
         Implements INorthwindDatabase.Shippers
      Get
         Return ExecuteQuery("Select * From Shippers")
      End Get
   End Property

   Public ReadOnly Property Suppliers() As DataTable _
         Implements INorthwindDatabase.Suppliers
      Get
         Return ExecuteQuery("Select * From Suppliers")
      End Get
   End Property
End Class

The server process is not very interesting. It registers the ServerProcess.Database class with the runtime and then sits there. The code is really no different from the server project in the Simple Remoting example. The client, on the other hand, is quite different. The client requests an object from the remoting channel that is based solely on the INorthwindDatabase interface. It has no reference to or knowledge of the underlying Database class and thus cannot call any methods that are not exposed by the INorthwindDatabase interface. Here is what the code looks like:

Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Tcp

Public Class Form1
   Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

   Dim northwindDB As MyInterfaces.INorthwindDatabase
   Public Function Initialize() As Boolean
      If northwindDB Is Nothing Then
         Dim chan As New TcpChannel()
         ChannelServices.RegisterChannel(chan)
         Dim t As Type = Type.GetType( _
            "MyInterfaces.INorthwindDatabase,MyInterfaces")
         northwindDB = Activator.GetObject(t, _
            "tcp://localhost:8086/NorthwindDB")

         If northwindDB Is Nothing Then
            MsgBox("Could not initialize the remoting client." & _
               "Check your configuration.")
            Return False
         End If
      End If

      Return True
   End Function

   Private Sub CustomersButton_Click(ByVal sender As System.Object, _
         ByVal e As System.EventArgs) Handles CustomersButton.Click
      If Not Initialize() Then Return
      QueryDataGrid.DataSource = northwindDB.Customers
   End Sub

   Private Sub EmployeesButton_Click(ByVal sender As System.Object, _
         ByVal e As System.EventArgs) Handles EmployeesButton.Click
      If Not Initialize() Then Return
      QueryDataGrid.DataSource = northwindDB.Employees
   End Sub

   Private Sub CategoriesButton_Click(ByVal sender As System.Object, _
         ByVal e As System.EventArgs) Handles CategoriesButton.Click
      If Not Initialize() Then Return
      QueryDataGrid.DataSource = northwindDB.Categories
   End Sub

   Private Sub OrderDetailsButton_Click _
         (ByVal sender As System.Object, _
         ByVal e As System.EventArgs) Handles OrderDetailsButton.Click
      If Not Initialize() Then Return
      QueryDataGrid.DataSource = northwindDB.OrderDetails
   End Sub
End Class


Distributed COM+ Applications

Probably one of the most important uses for COM+ is in distributed applications. Creating application proxies was always one of the easiest ways to implement a distributed application. The number of choices in .NET might seem somewhat daunting by comparison. Not too worry, though. The increased choices are all designed to address certain deficiencies of the WinDNA platform and technologies such as DCOM and RDS and to make it easier to create distributed COM+ applications.

COM+ and Remoting

The process of creating server applications that run through remoting is fairly straightforward, if not obvious. Remember that remoting requires a host process to handle registration and configuration. This is still true for COM+ applications. Granted, you are already halfway there because any component derived from ServicedComponent has MarshalByRefObject as a base class—a prerequisite for all objects to be made available through remoting. The Architecting for Remoting example demonstrates how to register a ServicedComponent with the common language runtime. Support for the COM+ context and other services is implicit through remoting, and there really isn’t anything extra that you need to do for it to work.

Using SOAP Services

Publishing a ServicedComponent through Remoting is powerful, but it requires you to do your own work to register the individual components that are intended to be available. There is an alternative: SOAP. The self-registration process in .NET for ServicedComponents provides the ability to mark your entire COM+ application as publishable through SOAP Services (essentially an XML Web service interface). Your COM+ application is then published through a virtual root of the IIS server the application is registered on. This technique is powerful, but it has greater implicit calling overhead than a binary format such as Remoting. On the other hand, using SOAP Services allows you to publish your COM+ application in a simple and straightforward manner. Recall from Chapter 16 how important attributes are to COM+ applications. The SOAP Service is made accessible through the ApplicationActivation assembly attribute. There is a default property that you can set in this attribute that instructs the self-registration process to create a SOAP proxy for your application (and any ServicedComponent contained within the Assembly). This property allows these ServicedComponents to be activated through a Web Service.

Limitations of SOAP Services

While using SOAP Services is a painless way to publish your COM+ application as an XML Web service, it does lose some of its abilities. For example, the COM+ transaction context ends at the XML Web service boundary. This means that you cannot have a transactional client call the XML Web service and have the transaction context propagate from the client to the server. COM+ essentially begins and ends at the XML Web service boundary. Although your COM+ application can take advantage of all of the COM+ Services available, those services are not available to any calling clients. As far as any client is concerned, there is nothing COM+ about the XML Web service.

Included on the CD is the Soap Services example. It contains a simple class, SoapServicesTest, that defines the COM+ application.

Imports System.EnterpriseServices
Imports System.Reflection

<Assembly: AssemblyKeyFile("SoapServicesTest.snk")>
<Assembly: ApplicationName("COM+ SOAP Services Test")>
<Assembly: ApplicationActivation(ActivationOption.Server,
SoapVRoot:="SOAPServicesTest")>

Public Class SoapServicesTest
   Inherits ServicedComponent

   Public Sub New()
      MyBase.New()
      Debug.WriteLine("SOAPServicesTest::New()")
   End Sub

   Public Function HelloWorld() As String
      Return "Hello World"
   End Function
End Class

When this class is registered (by using RegSvcs.exe or loading it via another process), the COM+ self-registration process will create the IIS virtual directory and build all the necessary support files for an XML Web service, including the inspection page (accessible through a Web browser).

note | Windows XP will require Service Pack 1 before the included sample application will function as expected. At the time of this writing, the SOAP Services attribute is not supported for Windows 2000. Support exists only for Windows XP and the Windows .NET server product family.
COM+ Application Proxies in .NET

Using COM+ application proxies has always been an easy way to implement DCOM in your applications. Such proxies enable you to create remote components that you can deploy and program against as though the components resided on the local machine. COM+ allows you to register your component on a server in a COM+ application and generate an export package that can be registered on virtually any client (domain security issues aside). As a result, you can avoid the whole issue of creating COM proxy stub packages and implementing custom object marshaling—COM+ does it all for you.

You will be relieved to discover that nothing has changed in Visual Basic .NET. After a ServicedComponent is registered on the server, it is possible to create an export package, deploy it to the client, and use it as you always have. You will need the assembly containing the class and interface definitions on the client (unless you want to everything to be late bound), but COM+ will still marshal the objects through DCOM for you. There really isn’t a lot more that needs to be said here. It just works. Create your export package, register it on the client, and you’re done.

note | There is a caveat to the “just works” mantra for COM+ proxy applications. It turns out that there are some platform requirements for this to work correctly. Because of problems in Windows 2000, support for .NET-based application proxies will not be available until Service Pack 3 has been released. At the time of this writing, Service Pack 2 is the most recent available. Application proxies will work just fine on Windows XP and the Windows .NET server product family.


Conclusion

At this point you should have at least a basic understanding of how to implement the core distributed technologies and where the relative strengths of remoting and XML Web services lie. Traditionally, developing distributed applications has required a lot of work on the part of the developer. Microsoft started to make things a great deal easier with the introduction of Microsoft Transaction Services (MTS) and RDS. The .NET platform takes the process to a whole new level. With objects that support the innate capability of serialization and protocol abstraction, you are free to concentrate more on the architecture and design of your applications rather than spend time on network and marshaling issues.

Moving forward, you need to think carefully about what type of solution will fit well with your application. It is possible, and sometimes desirable, to maintain some components in Visual Basic 6 rather than moving the entire application to Visual Basic .NET. Don’t feel pressured to make unnecessary changes just to be “pure” .NET. Visual Basic .NET provides the groundwork for developing whole new classes of distributed applications, but this need not be at the expense of your existing applications.


Appendix A

Part V
Appendix
Appendix A
Object Mapping Reference

This appendix shows the property, method, and event mappings from Microsoft Visual Basic 6 to Visual Basic .NET for common objects. The Upgrade Wizard uses these mappings during the upgrade.

Each section covers one Visual Basic 6 object and begins by briefly stating what object it maps to in Visual Basic .NET. The table in each section then gives the individual mappings for each property, method, and event. If the Visual Basic .NET column indicates “No mapping,” it means that there is no one-to-one mapping, and therefore the Upgrade Wizard will be unable to move the property, method, or event to Visual Basic .NET.

For most objects, the type of each element remains the same after upgrade, and any exceptions are indicated within the table. With the App object, however, many element types have not been preserved. To make the distinction clear, the table for the App object has an additional column indicating the new type.


App

No direct equivalent to upgrade to. Individual properties and methods are upgraded as shown in the table.

Visual Basic 6 | Type | Visual Basic .NET | Type
Comments | Property | System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly.- Location).Comments | Method
CompanyName | Property | System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly.Location).CompanyName | Method
EXEName | Property | VB6.GetEXEName | Method
FileDescription | Property | System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly.- Location).FileDescription | Method
HelpFile | Property | No mapping |
HInstance | Property | VB6.GetHInstance | Method
LegalCopyright | Property | System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly.- Location).LegalCopyright | Method
LegalTrademarks | Property | System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly.- Location).LegalTrademarks | Method
LogEvent | Method | No mapping |
LogMode | Property | No mapping |
LogPath | Property | No mapping |
Major | Property | System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly.- Location).FileMajorPart | Method
Minor | Property | System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly.- Location).FileMinorPart | Method
NonModalAllowed | Property | No mapping |
OleRequestPendingMsgText | Property | No mapping |
OleRequestPendingMsgTitle | Property | No mapping |
OleRequestPendingTimeout | Property | No mapping |
OleServerBusyMsgText | Property | No mapping |
OleServerBusyMsgTitle | Property | No mapping |
OleServerBusyRaiseError | Property | No mapping |
OleServerBusyTimeout | Property | No mapping |
Path | Property | VB6.GetPath | Method
PrevInstance (Boolean) | Property | UBound(Diagnostics.Process.GetProcessesByName(Diagnostics.Process.- GetCurrentProcess.ProcessName)) > 0 | Expression
ProductName | Property | System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly.- Location).ProductName | Method
RetainedProject | Property | No mapping |
Revision | Property | No mapping |
StartLogging | Method | No mapping |
StartMode | Property | No mapping |
TaskVisible | Property | No mapping |
ThreadID | Property | No mapping |
Title | Property | System.Reflection.Assembly.GetExecutingAssembly.GetName.Name No mapping for Set | Method
UnattendedApp | Property | No mapping |


CheckBox

Upgrades to System.Windows.Form.CheckBox.

Visual Basic 6 | Type | Visual Basic .NET
Alignment | Property | CheckAlign
Appearance | Property | FlatStyle
BackColor | Property | BackColor
Caption | Property | Text
CausesValidation | Property | CausesValidation
Click | Event | CheckStateChanged
Container | Property | Parent
DataChanged | Property | No mapping
DataField | Property | For ADO, used in VB6_AddADODatabinding method
DataFormat | Property | No mapping
DataMember | Property | For ADO, used in VB6_AddADODatabinding method
DataSource | Property | For ADO, used in VB6_AddADODatabinding method
DisabledPicture | Property | No mapping
DownPicture | Property | No mapping
Drag | Method | No mapping
DragDrop | Event | No mapping
DragIcon | Property | No mapping
DragMode | Property | No mapping
DragOver | Event | No mapping
Enabled | Property | Enabled
Font | Property | Font
FontBold | Property | Font.Bold, VB6.FontChangeBold
FontItalic | Property | Font.Italic, VB6.FontChangeItalic
FontName | Property | Font.Name, VB6.FontChangeName
FontSize | Property | Font.SizeInPoints, VB6.FontChangeSize
FontStrikethru | Property | Font.StrikeOut, VB6.FontChangeStrikeOut
FontUnderline | Property | Font.Underline, VB6.FontChangeUnderline
ForeColor | Property | ForeColor
GotFocus | Event | Enter
Height | Property | Height
HelpContextID | Property | No mapping
HWnd | Property | Handle
Index | Property | Index property of ControlArray object
KeyDown | Event | KeyDown
KeyPress | Event | KeyPress
KeyUp | Event | KeyUp
Left | Property | Left
LostFocus | Event | Leave
MaskColor | Property | No mapping
MouseDown | Event | MouseDown
MouseIcon | Property | No mapping
MouseMove | Event | MouseMove
MousePointer | Property | Cursor
MouseUp | Event | MouseUp
Move | Method | SetBounds
Name | Property | Name
OLECompleteDrag | Event | No mapping
OLEDrag | Method | No mapping
OLEDragDrop | Event | No mapping
OLEDragOver | Event | No mapping
OLEDropMode | Property | No mapping
OLEGiveFeedback | Event | No mapping
OLESetData | Event | No mapping
OLEStartDrag | Event | No mapping
Parent | Property | FindForm
Picture | Property | Image
Refresh | Method | Refresh
RightToLeft | Property | RightToLeft
SetFocus | Method | Focus
ShowWhatsThis | Method | No mapping
Style | Property | Appearance
TabIndex | Property | TabIndex
TabStop | Property | TabStop
Tag | Property | Tag
ToolTipText | Property | SetToolTip method on ToolTip component
Top | Property | Top
UseMaskColor | Property | No mapping
Validate | Event | Validating
Value | Property | CheckState
Visible | Property | Visible
WhatsThisHelpID | Property | No mapping
Width | Property | Width
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


ComboBox

Upgrades to System.Windows.Forms.ComboBox.

Visual Basic 6 | Type | Visual Basic .NET
AddItem | Method | Items.Add, or Items.Insert (if Index parameter exists)
Appearance | Property | No mapping
BackColor | Property | BackColor
CausesValidation | Property | CausesValidation
Change | Event | TextChanged
Clear | Method | Items.Clear
Click | Event | SelectedIndexChanged
Container | Property | Parent
DataChanged | Property | No mapping
DataField | Property | For ADO, used in VB6_AddADODatabinding method
DataFormat | Property | No mapping
DataMember | Property | For ADO, used in VB6_AddADODatabinding method
DataSource | Property | For ADO, used in VB6_AddADODatabinding method
DblClick | Event | No mapping
Drag | Method | No mapping
DragDrop | Event | No mapping
DragIcon | Property | No mapping
DragMode | Property | No mapping
DragOver | Event | No mapping
DropDown | Event | DropDown
Enabled | Property | Enabled
Font | Property | Font
FontBold | Property | Font.Bold, VB6.FontChangeBold
FontItalic | Property | Font.Italic, VB6.FontChangeItalic
FontName | Property | Font.Name, VB6.FontChangeName
FontSize | Property | Font.SizeInPoints, VB6.FontChangeSize
FontStrikethru | Property | Font.StrikeOut, VB6.FontChangeStrikeOut
FontUnderline | Property | Font.Underline, VB6.FontChangeUnderline
ForeColor | Property | ForeColor
GotFocus | Event | Enter
Height | Property | Height
HelpContextID | Property | No mapping
HWnd | Property | Handle
IMEMode | Property | ImeMode
Index | Property | Index property of ControlArray object
IntegralHeight | Property | IntegralHeight
ItemData | Property | VB6.GetItemData, VB6.SetItemData methods
KeyDown | Event | KeyDown
KeyPress | Event | KeyPress
KeyUp | Event | KeyUp
Left | Property | Left
List | Property | VB6.GetItemString, VB6.SetItemString methods
ListCount | Property | Items.Count
ListIndex | Property | SelectedIndex
Locked | Property | No mapping
LostFocus | Event | Leave
MouseIcon | Property | No mapping
MousePointer | Property | Cursor
Move | Method | SetBounds
Name | Property | Name
NewIndex | Property | Return result of Items.Add (if preceding line is Items.Add)
OLECompleteDrag | Event | No mapping
OLEDrag | Method | No mapping
OLEDragDrop | Event | No mapping
OLEDragMode | Property | No mapping
OLEDragOver | Event | No mapping
OLEDropMode | Property | No mapping
OLEGiveFeedback | Event | No mapping
OLESetData | Event | No mapping
OLEStartDrag | Event | No mapping
Parent | Property | FindForm
Refresh | Method | Refresh
RemoveItem | Method | Items.RemoveAt
RightToLeft | Property | RightToLeft
Scroll | Event | No mapping
SelLength | Property | SelectionLength
SelStart | Property | SelectionStart
SelText | Property | SelectedText
SetFocus | Method | Focus
ShowWhatsThis | Method | No mapping
Sorted | Property | Sorted
Style | Property | DropDownStyle
TabIndex | Property | TabIndex
TabStop | Property | TabStop
Tag | Property | Tag
Text | Property | Text
ToolTipText | Property | SetToolTip method on ToolTip component
Top | Property | Top
TopIndex | Property | No mapping
Validate | Event | Validating
Visible | Property | Visible
WhatsThisHelpID | Property | No mapping
Width | Property | Width
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


CommandButton

Upgrades to System.Windows.Forms.Button.

Visual Basic 6 | Type | Visual Basic .NET
Appearance | Property | No mapping
BackColor | Property | BackColor
Cancel | Property | At design time, Form.CancelButton property At run time,VB6.GetCancel, VB6.SetCancel methods
Caption | Property | Text
CausesValidation | Property | CausesValidation
Click | Event | Click
Container | Property | Parent
Default | Property | At design time, Form.AcceptButton At run time, VB6.GetDefault, VB6.SetDefault methods
DisabledPicture | Property | No mapping
DownPicture | Property | No mapping
Drag | Method | No mapping
DragDrop | Event | No mapping
DragIcon | Property | No mapping
DragMode | Property | No mapping
DragOver | Event | No mapping
Enabled | Property | Enabled
Font | Property | Font
FontBold | Property | Font.Bold, VB6.FontChangeBold
FontItalic | Property | Font.Italic, VB6.FontChangeItalic
FontName | Property | Font.Name, VB6.FontChangeName
FontSize | Property | Font.SizeInPoints, VB6.FontChangeSize
FontStrikethru | Property | Font.StrikeOut, VB6.FontChangeStrikeOut
FontUnderline | Property | Font.Underline, VB6.FontChangeUnderline
GotFocus | Event | Enter
Height | Property | Height
HelpContextID | Property | No mapping
hWnd | Property | Handle
Index | Property | Index property of ControlArray object
KeyDown | Event | KeyDown
KeyPress | Event | KeyPress
KeyUp | Event | KeyUp
Left | Property | Left
LostFocus | Event | Leave
MaskColor | Property | No mapping
MouseDown | Event | MouseDown
MouseIcon | Property | No mapping
MouseMove | Event | MouseMove
MousePointer | Property | Cursor
MouseUp | Event | MouseUp
Move | Method | SetBounds
Name | Property | Name
OLECompleteDrag | Event | No mapping
OLEDrag | Method | No mapping
OLEDragDrop | Event | No mapping
OLEDragOver | Event | No mapping
OLEDropMode | Property | No mapping
OLEGiveFeedback | Event | No mapping
OLESetData | Event | No mapping
OLEStartDrag | Event | No mapping
Parent | Property | FindForm
Picture | Property | Image
Refresh | Method | Refresh
RightToLeft | Property | RightToLeft
SetFocus | Method | Focus
ShowWhatsThis | Method | No mapping
Style | Property | No mapping
TabIndex | Property | TabIndex
TabStop | Property | TabStop
Tag | Property | Tag
ToolTipText | Property | SetToolTip method on ToolTip component
Top | Property | Top
UseMaskColor | Property | No mapping
Value | Property | No mapping for Get PerformClick method if value is nonzero for Set
Visible | Property | Visible
WhatsThisHelpID | Property | No mapping
Width | Property | Width
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


DirListBox

Upgrades to Microsoft.VisualBasic.Compatibility.VB6.DirListBox.

Visual Basic 6 | Type | Visual Basic .NET
Appearance | Property | Used to determine BorderStyle
BackColor | Property | BackColor
CausesValidation | Property | CausesValidation
Change | Event | Change
Click | Event | SelectedIndexChanged
Container | Property | Parent
Drag | Method | No mapping
DragDrop | Event | No mapping
DragIcon | Property | No mapping
DragMode | Property | No mapping
DragOver | Event | No mapping
Enabled | Property | Enabled
Font | Property | Font
FontBold | Property | Font.Bold, VB6.FontChangeBold
FontItalic | Property | Font.Italic, VB6.FontChangeItalic
FontName | Property | Font.Name, VB6.FontChangeName
FontSize | Property | Font.SizeInPoints, VB6.FontChangeSize
FontStrikethru | Property | Font.StrikeOut, VB6.FontChangeStrikeOut
FontUnderline | Property | Font.Underline, VB6.FontChangeUnderline
ForeColor | Property | ForeColor
GotFocus | Event | Enter
Height | Property | Height
HelpContextID | Property | No mapping
HWnd | Property | Handle
IMEMode | Property | ImeMode
Index | Property | Index property of ControlArray object
KeyDown | Event | KeyDown
KeyPress | Event | KeyPress
KeyUp | Event | KeyUp
Left | Property | Left
List | Property | DirList
ListCount | Property | DirListCount
ListIndex | Property | DirListIndex
LostFocus | Event | Leave
MouseDown | Event | MouseDown
MouseIcon | Property | No mapping
MouseMove | Event | MouseMove
MousePointer | Property | Cursor
MouseUp | Event | MouseUp
Move | Method | SetBounds
Name | Property | Name
OLECompleteDrag | Event | No mapping
OLEDrag | Method | No mapping
OLEDragDrop | Event | No mapping
OLEDragMode | Property | No mapping
OLEDragOver | Event | No mapping
OLEDropMode | Property | No mapping
OLEGiveFeedback | Event | No mapping
OLESetData | Event | No mapping
OLEStartDrag | Event | No mapping
Parent | Property | FindForm
Path | Property | Path
Refresh | Method | Refresh
Scroll | Event | No mapping
SetFocus | Method | Focus
ShowWhatsThis | Method | No mapping
TabIndex | Property | TabIndex
TabStop | Property | TabStop
Tag | Property | Tag
ToolTipText | Property | SetToolTip method on ToolTip component
Top | Property | Top
TopIndex | Property | TopIndex
Validate | Event | Validating
Visible | Property | Visible
WhatsThisHelpID | Property | No mapping
Width | Property | Width
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


DriveListBox

Upgrades to Microsoft.VisualBasic.Compatibility.VB6.DriveListBox.

Visual Basic 6 | Type | Visual Basic .NET
Appearance | Property | No mapping
BackColor | Property | BackColor
CausesValidation | Property | CausesValidation
Change | Event | SelectedIndexChanged
Container | Property | Parent
Drag | Method | No mapping
DragDrop | Event | No mapping
DragIcon | Property | No mapping
DragMode | Property | No mapping
DragOver | Event | No mapping
Drive | Property | Drive
Enabled | Property | Enabled
Font | Property | Font
FontBold | Property | Font.Bold, VB6.FontChangeBold
FontItalic | Property | Font.Italic, VB6.FontChangeItalic
FontName | Property | Font.Name, VB6.FontChangeName
FontSize | Property | Font.SizeInPoints, VB6.FontChangeSize
FontStrikethru | Property | Font.StrikeOut, VB6.FontChangeStrikeOut
FontUnderline | Property | Font.Underline, VB6.FontChangeUnderline
ForeColor | Property | ForeColor
GotFocus | Event | Enter
Height | Property | Height
HelpContextID | Property | No mapping
HWnd | Property | Handle
Index | Property | Index property of ControlArray object
KeyDown | Event | KeyDown
KeyPress | Event | KeyPress
KeyUp | Event | KeyUp
Left | Property | Left
List | Property | Items
ListCount | Property | Items.Count
ListIndex | Property | SelectedIndex
LostFocus | Event | Leave
MouseIcon | Property | No mapping
MousePointer | Property | Cursor
Move | Method | SetBounds
Name | Property | Name
OLECompleteDrag | Event | No mapping
OLEDrag | Method | No mapping
OLEDragDrop | Event | No mapping
OLEDragOver | Event | No mapping
OLEDropMode | Property | No mapping
OLEGiveFeedback | Event | No mapping
OLESetData | Event | No mapping
OLEStartDrag | Event | No mapping
Parent | Property | FindForm
Refresh | Method | Refresh
Scroll | Event | No mapping
SetFocus | Method | Focus
ShowWhatsThis | Method | No mapping
TabIndex | Property | TabIndex
TabStop | Property | TabStop
Tag | Property | Tag
ToolTipText | Property | SetToolTip method on ToolTip component
Top | Property | Top
TopIndex | Property | No mapping
Validate | Event | Validating
Visible | Property | Visible
WhatsThisHelpID | Property | No mapping
Width | Property | Width
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


FileListBox

Upgrades to Microsoft.VisualBasic.Compatibility.VB6.FileListBox.

Visual Basic 6 | Type | Visual Basic .NET
Appearance | Property | Used to determine BorderStyle
Archive | Property | Archive
BackColor | Property | BackColor
CausesValidation | Property | CausesValidation
Click | Event | SelectedIndexChanged
Container | Property | Parent
DblClick | Event | DoubleClick
Drag | Method | No mapping
DragDrop | Event | No mapping
DragIcon | Property | No mapping
DragMode | Property | No mapping
DragOver | Event | No mapping
Enabled | Property | Enabled
FileName | Property | FileName
Font | Property | Font
FontBold | Property | Font.Bold, VB6.FontChangeBold
FontItalic | Property | Font.Italic, VB6.FontChangeItalic
FontName | Property | Font.Name, VB6.FontChangeName
FontSize | Property | Font.SizeInPoints, VB6.FontChangeSize
FontStrikethru | Property | Font.StrikeOut, VB6.FontChangeStrikeOut
FontUnderline | Property | Font.Underline, VB6.FontChangeUnderline
ForeColor | Property | ForeColor
GotFocus | Event | Enter
Height | Property | Height
HelpContextID | Property | No mapping
Hidden | Property | Hidden
hWnd | Property | Handle
IMEMode | Property | ImeMode
Index | Property | Index property of ControlArray object
KeyDown | Event | KeyDown
KeyPress | Event | KeyPress
KeyUp | Event | KeyUp
Left | Property | Left
List | Property | Items
ListCount | Property | Items.Count
ListIndex | Property | SelectedIndex
LostFocus | Event | Leave
MouseDown | Event | MouseDown
MouseIcon | Property | No mapping
MouseMove | Event | MouseMove
MousePointer | Property | Cursor
MouseUp | Event | MouseUp
Move | Method | SetBounds
MultiSelect | Property | SelectionMode
Name | Property | Name
Normal | Property | Normal
OLECompleteDrag | Event | No mapping
OLEDrag | Method | No mapping
OLEDragDrop | Event | No mapping
OLEDragMode | Property | No mapping
OLEDragOver | Event | No mapping
OLEDropMode | Property | No mapping
OLEGiveFeedback | Event | No mapping
OLESetData | Event | No mapping
OLEStartDrag | Event | No mapping
Parent | Property | FindForm
Path | Property | Path
PathChange | Event | PathChange
Pattern | Property | Pattern
PatternChange | Event | PatternChange
ReadOnly | Property | ReadOnly
Refresh | Method | Refresh
Scroll | Event | No mapping
Selected | Property | GetSelected, SetSelected
SetFocus | Method | Focus
ShowWhatsThis | Method | No mapping
System | Property | System
TabIndex | Property | TabIndex
TabStop | Property | TabStop
Tag | Property | Tag
ToolTipText | Property | SetToolTip method on ToolTip component
Top | Property | Top
TopIndex | Property | TopIndex
Validate | Event | Validating
Visible | Property | Visible
WhatsThisHelpID | Property | No mapping
Width | Property | Width
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0,SendToBack if Position parameter is 1


Form

Upgrades to System.Windows.Forms.Form.

Visual Basic 6 | Type | Visual Basic .NET
Activate | Event | Activated
ActiveControl | Property | ActiveControl
Appearance | Property | No mapping
AutoRedraw | Property | No mapping
BackColor | Property | BackColor
BorderStyle | Property | FormBorderStyle
Caption | Property | Text
Circle | Method | No mapping
Click | Event | Click
ClipControls | Property | No mapping
Cls | Method | No mapping
ControlBox | Property | ControlBox
Controls | Property | Controls
Count | Property | Controls.Count
CurrentX | Property | No mapping
CurrentY | Property | No mapping
DblClick | Event | DoubleClick
Deactivate | Event | Deactivate
DragDrop | Event | No mapping
DragOver | Event | No mapping
DrawMode | Property | No mapping
DrawStyle | Property | No mapping
DrawWidth | Property | No mapping
Enabled | Property | Enabled
FillColor | Property | No mapping
FillStyle | Property | No mapping
Font | Property | Font
FontBold | Property | Font.Bold, VB6.FontChangeBold
FontItalic | Property | Font.Italic, VB6.FontChangeItalic
FontName | Property | Font.Name, VB6.FontChangeName
FontSize | Property | Font.SizeInPoints, VB6.FontChangeSize
FontStrikethru | Property | Font.StrikeOut, VB6.FontChangeStrikeOut
FontTransparent | Property | No mapping
FontUnderline | Property | Font.Underline, VB6.FontChangeUnderline
ForeColor | Property | ForeColor
GotFocus | Event | GotFocus
HasDC | Property | No mapping
HDC | Property | No mapping
Height | Property | Height
HelpContextID | Property | No mapping
Hide | Method | Hide
HWnd | Property | Handle
Icon | Property | Icon
Image | Property | No mapping
Initialize | Event | Initialize method called from New event
KeyDown | Event | KeyDown
KeyPress | Event | KeyPress
KeyPreview | Property | KeyPreview
KeyUp | Event | KeyUp
Left | Property | Left
Line | Method | No mapping
LinkClose | Event | No mapping
LinkError | Event | No mapping
LinkExecute | Event | No mapping
LinkMode | Property | No mapping
LinkOpen | Event | No mapping
LinkTopic | Property | No mapping
Load | Event | Load
LostFocus | Event | LostFocus
MaxButton | Property | MaximizeBox
MDIChild | Property | At design time, if value is True, then MDIParent property is set in the New event At run time, maps to expression Not f.MDIParent Is Nothing
MinButton | Property | MinimizeBox
MouseDown | Event | MouseDown
MouseIcon | Property | No mapping
MouseMove | Event | MouseMove
MousePointer | Property | Cursor
MouseUp | Event | MouseUp
Move | Method | SetBounds
Moveable | Property | No mapping
Name | Property | Name
NegotiateMenus | Property | No mapping
OLECompleteDrag | Event | No mapping
OLEDrag | Method | No mapping
OLEDragDrop | Event | No mapping
OLEDragOver | Event | No mapping
OLEDropMode | Property | No mapping
OLEGiveFeedback | Event | No mapping
OLESetData | Event | No mapping
OLEStartDrag | Event | No mapping
Paint | Event | Paint
PaintPicture | Method | No mapping
Palette | Property | No mapping
PaletteMode | Property | No mapping
Picture | Property | BackgroundImage
Point | Method | No mapping
PopupMenu | Method | No mapping
PrintForm | Method | Not mapped (See Chapter 15 for how to implement PrintForm.)
PSet | Method | No mapping
QueryUnload | Event | Closing
Refresh | Method | Refresh
Resize | Event | Resize
RightToLeft | Property | RightToLeft
Scale | Method | No mapping
ScaleHeight | Property | At run time, x.ClientRectangle.Height method for Get
ScaleLeft | Property | No mapping
ScaleMode | Property | No mapping
ScaleTop | Property | No mapping
ScaleWidth | Property | At run time, x.ClientRectangle.Width method for Get
ScaleX | Method | No mapping
ScaleY | Method | No mapping
SetFocus | Method | Activate
Show | Method | Show, or ShowDialog if Modal parameter is vbModal, VB6.ShowForm method if Modal parameter is a variable
ShowInTaskbar | Property | ShowInTaskBar
StartUpPosition | Property | StartPosition
Tag | Property | Tag
Terminate | Event | Terminate method, called from Dispose event
TextHeight | Method | No mapping
TextWidth | Method | No mapping
Top | Property | Top
Unload | Event | Closed
ValidateControls | Method | VB6.ValidateControls method
Visible | Property | Visible
WhatsThisButton | Property | HelpButton
WhatsThisHelp | Property | No mapping
WhatsThisMode | Method | VB6.WhatsThisMode method
Width | Property | Width
WindowState | Property | WindowState
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


Frame

Upgrades to (1) System.Windows.Forms.Panel, or (2) System.Windows.Forms.GroupBox if control has a border.

Visual Basic 6 | Type | Visual Basic .NET
Appearance | Property | No mapping
BackColor | Property | BackColor
BorderStyle | Property | (2) BorderStyle
Caption | Property | (1) Text
Click | Event | (1) Click
ClipControls | Property | No mapping
Container | Property | Parent
DblClick | Event | (1) DoubleClick
Drag | Method | No mapping
DragDrop | Event | No mapping
DragIcon | Property | No mapping
DragMode | Property | No mapping
DragOver | Event | No mapping
Enabled | Property | Enabled
Font | Property | Font
FontBold | Property | Font.Bold, VB6.FontChangeBold
FontItalic | Property | Font.Italic, VB6.FontChangeItalic
FontName | Property | Font.Name, VB6.FontChangeName
FontSize | Property | Font.SizeInPoints, VB6.FontChangeSize
FontStrikethru | Property | Font.StrikeOut, VB6.FontChangeStrikeOut
FontUnderline | Property | Font.Underline, VB6.FontChangeUnderline
ForeColor | Property | ForeColor
Height | Property | Height
HelpContextID | Property | No mapping
hWnd | Property | Handle
Index | Property | Index property of ControlArray object
Left | Property | Left
MouseDown | Event | (1) MouseDown
MouseIcon | Property | No mapping
MouseMove | Event | (1) MouseMove
MousePointer | Property | (1) Cursor
MouseUp | Event | (1) MouseUp
Move | Method | SetBounds
Name | Property | Name
OLECompleteDrag | Event | No mapping
OLEDrag | Method | No mapping
OLEDragDrop | Event | No mapping
OLEDragOver | Event | No mapping
OLEDropMode | Property | No mapping
OLEGiveFeedback | Event | No mapping
OLESetData | Event | No mapping
OLEStartDrag | Event | No mapping
Parent | Property | FindForm
Refresh | Method | Refresh
RightToLeft | Property | RightToLeft
ShowWhatsThis | Method | No mapping
TabIndex | Property | TabIndex
Tag | Property | Tag
ToolTipText | Property | SetToolTip method on ToolTip component
Top | Property | Top
Visible | Property | Visible
WhatsThisHelpID | Property | No mapping
Width | Property | Width
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


HScrollBar

Upgrades to System.Windows.Forms.HScrollBar.

Visual Basic 6 | Type | Visual Basic .NET
CausesValidation | Property | CausesValidation
Change | Event | Change method called from Scroll event
Container | Property | Parent
Drag | Method | No mapping
DragDrop | Event | No mapping
DragIcon | Property | No mapping
DragMode | Property | No mapping
DragOver | Event | No mapping
Enabled | Property | Enabled
GotFocus | Event | Enter
Height | Property | Height
HelpContextID | Property | No mapping
HWnd | Property | Handle
Index | Property | Index property of ControlArray object
KeyDown | Event | KeyDown
KeyPress | Event | KeyPress
KeyUp | Event | KeyUp
LargeChange | Property | At design time, LargeChange At run time, three lines of code to calculate new value
Left | Property | Left
LostFocus | Event | Leave
Max | Property | At design time, Maximum At run time, value calculated from LargeChange
Min | Property | Minimum
MouseIcon | Property | No mapping
MousePointer | Property | Cursor
Move | Method | SetBounds
Name | Property | Name
Parent | Property | FindForm
Refresh | Method | Refresh
RightToLeft | Property | RightToLeft
Scroll | Event | Scroll event, or Scroll_renamed method called from Scroll event if Change event exists
SetFocus | Method | Focus
ShowWhatsThis | Method | No mapping
SmallChange | Property | SmallChange
TabIndex | Property | TabIndex
TabStop | Property | TabStop
Tag | Property | Tag
Top | Property | Top
Validate | Event | Validating
Value | Property | Value
Visible | Property | Visible
WhatsThisHelpID | Property | No mapping
Width | Property | Width
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


Image

Upgrades to System.Windows.Forms.PictureBox.

Visual Basic 6 | Type | Visual Basic .NET
Appearance | Property | Used to determine BorderStyle
BorderStyle | Property | BorderStyle
Click | Event | Click
Container | Property | Parent
DataChanged | Property | No mapping
DataField | Property | For ADO, used in VB6_AddADODatabinding method
DataFormat | Property | No mapping
DataMember | Property | For ADO, used in VB6_AddADODatabinding method
DataSource | Property | For ADO, used in VB6_AddADODatabinding method
DblClick | Event | DoubleClick
Drag | Method | No mapping
DragDrop | Event | No mapping
DragIcon | Property | No mapping
DragMode | Property | No mapping
DragOver | Event | No mapping
Enabled | Property | Enabled
Height | Property | Height
Index | Property | Index property of ControlArray object
Left | Property | Left
MouseDown | Event | MouseDown
MouseIcon | Property | No mapping
MouseMove | Event | MouseMove
MousePointer | Property | Cursor
MouseUp | Event | MouseUp
Move | Method | SetBounds
Name | Property | Name
OLECompleteDrag | Event | No mapping
OLEDrag | Method | No mapping
OLEDragDrop | Event | No mapping
OLEDragMode | Property | No mapping
OLEDragOver | Event | No mapping
OLEDropMode | Property | No mapping
OLEGiveFeedback | Event | No mapping
OLESetData | Event | No mapping
OLEStartDrag | Event | No mapping
Parent | Property | FindForm
Picture | Property | Image
Refresh | Method | Refresh
ShowWhatsThis | Method | No mapping
Stretch | Property | SizeMode
Tag | Property | Tag
ToolTipText | Property | SetToolTip method on ToolTip component
Top | Property | Top
Visible | Property | Visible
WhatsThisHelpID | Property | No mapping
Width | Property | Width
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


Label

Upgrades to System.Windows.Forms.Label.

Visual Basic 6 | Type | Visual Basic .NET
Alignment | Property | TextAlign
Appearance | Property | Used to determine BorderStyle
AutoSize | Property | AutoSize
BackColor | Property | BackColor
BackStyle | Property | No mapping
BorderStyle | Property | Used to determine BorderStyle
Caption | Property | Text
Change | Event | No mapping
Click | Event | Click
Container | Property | Parent
DataChanged | Property | No mapping
DataField | Property | For ADO, used in VB6_AddADODatabinding method
DataFormat | Property | No mapping
DataMember | Property | For ADO, used in VB6_AddADODatabinding method
DataSource | Property | For ADO, used in VB6_AddADODatabinding method
DblClick | Event | DoubleClick
Drag | Method | No mapping
DragDrop | Event | No mapping
DragIcon | Property | No mapping
DragMode | Property | No mapping
DragOver | Event | No mapping
Enabled | Property | Enabled
Font | Property | Font
FontBold | Property | Font.Bold, VB6.FontChangeBold
FontItalic | Property | Font.Italic, VB6.FontChangeItalic
FontName | Property | Font.Name, VB6.FontChangeName
FontSize | Property | Font.SizeInPoints, VB6.FontChangeSize
FontStrikethru | Property | Font.StrikeOut, VB6.FontChangeStrikeOut
FontUnderline | Property | Font.Underline, VB6.FontChangeUnderline
ForeColor | Property | ForeColor
Height | Property | Height
Index | Property | Index property of ControlArray object
Left | Property | Left
LinkClose | Event | No mapping
LinkError | Event | No mapping
LinkExecute | Method | No mapping
LinkItem | Property | No mapping
LinkMode | Property | No mapping
LinkNotify | Event | No mapping
LinkOpen | Event | No mapping
LinkPoke | Method | No mapping
LinkRequest | Method | No mapping
LinkSend | Method | No mapping
LinkTimeout | Property | No mapping
LinkTopic | Property | No mapping
MouseDown | Event | MouseDown
MouseIcon | Property | No mapping
MouseMove | Event | MouseMove
MousePointer | Property | Cursor
MouseUp | Event | MouseUp
Move | Method | SetBounds
Name | Property | Name
OLECompleteDrag | Event | No mapping
OLEDrag | Method | No mapping
OLEDragDrop | Event | No mapping
OLEDragOver | Event | No mapping
OLEDropMode | Property | No mapping
OLEGiveFeedback | Event | No mapping
OLESetData | Event | No mapping
OLEStartDrag | Event | No mapping
Parent | Property | FindForm
Refresh | Method | Refresh
RightToLeft | Property | RightToLeft
ShowWhatsThis | Method | No mapping
TabIndex | Property | TabIndex
Tag | Property | Tag
ToolTipText | Property | SetToolTip method on ToolTip component
Top | Property | Top
UseMnemonic | Property | UseMnemonic
Visible | Property | Visible
WhatsThisHelpID | Property | No mapping
Width | Property | Width
WordWrap | Property | No mapping
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


Line

Upgrades to System.Windows.Forms.Label if horizontal or vertical line. Diagonal lines are not upgraded.

Visual Basic 6 | Type | Visual Basic .NET
BorderColor | Property | BackColor
BorderStyle | Property | No mapping
BorderWidth | Property | Width for horizontal lines, or Height for vertical lines
Container | Property | Parent
DrawMode | Property | No mapping
Index | Property | Index property of ControlArray object
Name | Property | Name
Parent | Property | FindForm
Refresh | Method | Refresh
Tag | Property | Tag
Visible | Property | Visible
X1 | Property | Used for Location and Size at design time
X2 | Property | Used for Location and Size at design time
Y1 | Property | Used for Location and Size at design time
Y2 | Property | Used for Location and Size at design time
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


ListBox

Upgrades to (1) System.Windows.Forms.ListBox, or (2) System.Windows.Forms.CheckedListBox if Style =1.

Visual Basic 6 | Type | Visual Basic .NET
AddItem | Method | Items.Add Items.Insert (if Index parameter exists)
Appearance | Property | Used to determine BorderStyle
BackColor | Property | BackColor
CausesValidation | Property | CausesValidation
Clear | Method | Items.Clear
Click | Event | SelectedIndexChanged
Columns | Property | Used to determine MultiColumn, ColumnWidth properties
Container | Property | Parent
DataChanged | Property | No mapping
DataField | Property | For ADO, used in VB6_AddADODatabinding method
DataFormat | Property | No mapping
DataMember | Property | For ADO, used in VB6_AddADODatabinding method
DataSource | Property | For ADO, used in VB6_AddADODatabinding method
DblClick | Event | DoubleClick
Drag | Method | No mapping
DragDrop | Event | No mapping
DragIcon | Property | No mapping
DragMode | Property | No mapping
DragOver | Event | No mapping
Enabled | Property | Enabled
Font | Property | Font
FontBold | Property | Font.Bold, VB6.FontChangeBold
FontItalic | Property | Font.Italic, VB6.FontChangeItalic
FontName | Property | Font.Name, VB6.FontChangeName
FontSize | Property | Font.SizeInPoints, VB6.FontChangeSize
FontStrikethru | Property | Font.StrikeOut, VB6.FontChangeStrikeOut
FontUnderline | Property | Font.Underline, VB6.FontChangeUnderline
ForeColor | Property | ForeColor
GotFocus | Event | Enter
Height | Property | Height
HelpContextID | Property | No mapping
hWnd | Property | Handle
IMEMode | Property | ImeMode
Index | Property | Index property of ControlArray object
IntegralHeight | Property | IntegralHeight
ItemCheck | Event | (2) ItemCheck
ItemData | Property | VB6.GetItemData, VB6.SetItemData methods
KeyDown | Event | KeyDown
KeyPress | Event | KeyPress
KeyUp | Event | KeyUp
Left | Property | Left
List | Property | VB6.GetItemString, VB6.SetItemString methods
ListCount | Property | Items.Count
ListIndex | Property | SelectedIndex
LostFocus | Event | Leave
MouseDown | Event | MouseDown
MouseIcon | Property | No mapping
MouseMove | Event | MouseMove
MousePointer | Property | Cursor
MouseUp | Event | MouseUp
Move | Method | SetBounds
MultiSelect | Property | SelectionMode
Name | Property | Name
NewIndex | Property | Return result of Items.Add (if preceding line is Items.Add)
OLECompleteDrag | Event | No mapping
OLEDrag | Method | No mapping
OLEDragDrop | Event | No mapping
OLEDragMode | Property | No mapping
OLEDragOver | Event | No mapping
OLEDropMode | Property | No mapping
OLEGiveFeedback | Event | No mapping
OLESetData | Event | No mapping
OLEStartDrag | Event | No mapping
Parent | Property | FindForm
Refresh | Method | Refresh
RemoveItem | Method | Items.RemoveAt
RightToLeft | Property | RightToLeft
Scroll | Event | No mapping
SelCount | Property | (1) SelectedItems.Count (2) CheckedIndices.Count
Selected | Property | (1) GetSelected, SetSelected (2) GetItemChecked, SetItemChecked
SetFocus | Method | Focus
ShowWhatsThis | Method | No mapping
Sorted | Property | Sorted
Style | Property | Used to determine control type to upgrade to
TabIndex | Property | TabIndex
TabStop | Property | TabStop
Tag | Property | Tag
Text | Property | Text
ToolTipText | Property | SetToolTip method on ToolTip component
Top | Property | Top
TopIndex | Property | TopIndex
Validate | Event | Validating
Visible | Property | Visible
WhatsThisHelpID | Property | No mapping
Width | Property | Width
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


MDIForm

Upgrades to System.Windows.Forms.Form, with IsMDIContainer property set to True.

Visual Basic 6 | Type | Visual Basic .NET
Activate | Event | Activated
ActiveControl | Property | ActiveControl
ActiveForm | Property | ActiveMDIChild
Appearance | Property | No mapping
Arrange | Method | LayoutMDI
AutoShowChildren | Property | If True, code is added to child forms in New event
BackColor | Property | No mapping
Caption | Property | Text
Click | Event | No mapping
Controls | Property | Controls
Count | Property | Controls.Count
DblClick | Event | DoubleClick
Deactivate | Event | Deactivate
DragDrop | Event | No mapping
DragOver | Event | No mapping
Enabled | Property | Enabled
Height | Property | Height
HelpContextID | Property | No mapping
Hide | Method | Hide
HWnd | Property | Handle
Icon | Property | Icon
Initialize | Event | Initialize method called from New event
Left | Property | Left
LinkClose | Event | No mapping
LinkError | Event | No mapping
LinkExecute | Event | No mapping
LinkMode | Property | No mapping
LinkOpen | Event | No mapping
LinkTopic | Property | No mapping
Load | Event | Load
MouseDown | Event | No mapping
MouseIcon | Property | No mapping
MouseMove | Event | No mapping
MousePointer | Property | Cursor
MouseUp | Event | No mapping
Move | Method | SetBounds
Moveable | Property | No mapping
Name | Property | Name
NegotiateToolbars | Property | No mapping
OLECompleteDrag | Event | No mapping
OLEDrag | Method | No mapping
OLEDragDrop | Event | No mapping
OLEDragOver | Event | No mapping
OLEDropMode | Property | No mapping
OLEGiveFeedback | Event | No mapping
OLESetData | Event | No mapping
OLEStartDrag | Event | No mapping
Picture | Property | No mapping
PopupMenu | Method | No mapping
QueryUnload | Event | Closing
Resize | Event | Resize
RightToLeft | Property | RightToLeft
ScaleHeight | Property | At run time, x.ClientRectangle.Height for Get
ScaleWidth | Property | At run time, x.ClientRectangle.Width for Get
ScrollBars | Property | No mapping
SetFocus | Method | Activate
Show | Method | Show, or ShowDialog if Modal parameter is vbModal, VB6.ShowForm method if Modal parameter is a variable
StartUpPosition | Property | StartPosition
Tag | Property | Tag
Terminate | Event | Terminate method, called from Dispose event
Top | Property | Top
Unload | Event | Closed
ValidateControls | Method | VB6.ValidateControls method
Visible | Property | Visible
WhatsThisButton | Property | HelpButton
WhatsThisHelp | Property | No mapping
WhatsThisMode | Method | VB6.WhatsThisMode method
Width | Property | Width
WindowState | Property | WindowState
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


Menu

Upgrades to System.Windows.Forms.MenuItem. A MainMenu control is also automatically added to the form.

Visual Basic 6 | Type | Visual Basic .NET
Caption | Property | Text
Checked | Property | Checked
Click | Event | Click
Enabled | Property | Enabled
HelpContextID | Property | No mapping
Index | Property | Index property of ControlArray object
Name | Property | Name
NegotiatePosition | Property | No mapping
Parent | Property | GetMainMenu.GetForm
Shortcut | Property | Shortcut
Tag | Property | No mapping
Visible | Property | Visible
WindowList | Property | MDIList


OptionButton

Upgrades to System.Windows.Forms.RadioButton.

Visual Basic 6 | Type | Visual Basic .NET
Alignment | Property | CheckAlign
Appearance | Property | FlatStyle
BackColor | Property | BackColor
Caption | Property | Text
CausesValidation | Property | CausesValidation
Click | Event | CheckedChanged
Container | Property | Parent
DblClick | Event | No mapping
DisabledPicture | Property | No mapping
DownPicture | Property | No mapping
Drag | Method | No mapping
DragDrop | Event | No mapping
DragIcon | Property | No mapping
DragMode | Property | No mapping
DragOver | Event | No mapping
Enabled | Property | Enabled
Font | Property | Font
FontBold | Property | Font.Bold, VB6.FontChangeBold
FontItalic | Property | Font.Italic, VB6.FontChangeItalic
FontName | Property | Font.Name, VB6.FontChangeName
FontSize | Property | Font.SizeInPoints, VB6.FontChangeSize
FontStrikethru | Property | Font.StrikeOut, VB6.FontChangeStrikeOut
FontUnderline | Property | Font.Underline, VB6.FontChangeUnderline
ForeColor | Property | ForeColor
GotFocus | Event | Enter
Height | Property | Height
HelpContextID | Property | No mapping
hWnd | Property | Handle
Index | Property | Index property of ControlArray object
KeyDown | Event | KeyDown
KeyPress | Event | KeyPress
KeyUp | Event | KeyUp
Left | Property | Left
LostFocus | Event | Leave
MaskColor | Property | No mapping
MouseDown | Event | MouseDown
MouseIcon | Property | No mapping
MouseMove | Event | MouseMove
MousePointer | Property | Cursor
MouseUp | Event | MouseUp
Move | Method | SetBounds
Name | Property | Name
OLECompleteDrag | Event | No mapping
OLEDrag | Method | No mapping
OLEDragDrop | Event | No mapping
OLEDragOver | Event | No mapping
OLEDropMode | Property | No mapping
OLEGiveFeedback | Event | No mapping
OLESetData | Event | No mapping
OLEStartDrag | Event | No mapping
Parent | Property | FindForm
Picture | Property | Image
Refresh | Method | Refresh
RightToLeft | Property | RightToLeft
SetFocus | Method | Focus
ShowWhatsThis | Method | No mapping
Style | Property | Appearance
TabIndex | Property | TabIndex
TabStop | Property | TabStop
Tag | Property | Tag
ToolTipText | Property | SetToolTip method on ToolTip component
Top | Property | Top
UseMaskColor | Property | No mapping
Validate | Event | Validating
Value | Property | Checked
Visible | Property | Visible
WhatsThisHelpID | Property | No mapping
Width | Property | Width
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


PictureBox

Upgrades to (1) System.Windows.Forms.PictureBox, or (2) System.Windows.Forms.Panel if the control has child controls.

Visual Basic 6 | Type | Visual Basic .NET
Align | Property | Dock
Appearance | Property | Used to determine BorderStyle
AutoRedraw | Property | No mapping
AutoSize | Property | (1) SizeMode
BackColor | Property | BackColor
BorderStyle | Property | Used to determine BorderStyle
CausesValidation | Property | No mapping
Change | Event | No mapping
Circle | Method | No mapping
Click | Event | Click
ClipControls | Property | No mapping
Cls | Method | No mapping
Container | Property | Parent
CurrentX | Property | No mapping
CurrentY | Property | No mapping
DataChanged | Property | No mapping
DataField | Property | For ADO, used in VB6_AddADODatabinding method
DataFormat | Property | No mapping
DataMember | Property | For ADO, used in VB6_AddADODatabinding method
DataSource | Property | For ADO, used in VB6_AddADODatabinding method
DblClick | Event | DoubleClick
Drag | Method | No mapping
DragDrop | Event | No mapping
DragIcon | Property | No mapping
DragMode | Property | No mapping
DragOver | Event | No mapping
DrawMode | Property | No mapping
DrawStyle | Property | No mapping
DrawWidth | Property | No mapping
Enabled | Property | Enabled
FillColor | Property | No mapping
FillStyle | Property | No mapping
Font | Property | Font
FontBold | Property | Font.Bold, VB6.FontChangeBold
FontItalic | Property | Font.Italic, VB6.FontChangeItalic
FontName | Property | Font.Name, VB6.FontChangeName
FontSize | Property | Font.SizeInPoints, VB6.FontChangeSize
FontStrikethru | Property | Font.StrikeOut, VB6.FontChangeStrikeOut
FontTransparent | Property | No mapping
FontUnderline | Property | Font.Underline, VB6.FontChangeUnderline
ForeColor | Property | ForeColor
GotFocus | Event | No mapping
HasDC | Property | No mapping
HDC | Property | No mapping
Height | Property | Height
HelpContextID | Property | No mapping
HWnd | Property | Handle
Image | Property | No mapping
IMEMode | Property | No mapping
Index | Property | Index property of ControlArray object
KeyDown | Event | No mapping
KeyPress | Event | No mapping
KeyUp | Event | No mapping
Left | Property | Left
Line | Method | No mapping
LinkClose | Event | No mapping
LinkError | Event | No mapping
LinkExecute | Method | No mapping
LinkItem | Property | No mapping
LinkMode | Property | No mapping
LinkNotify | Event | No mapping
LinkOpen | Event | No mapping
LinkPoke | Method | No mapping
LinkRequest | Method | No mapping
LinkSend | Method | No mapping
LinkTimeout | Property | No mapping
LinkTopic | Property | No mapping
LostFocus | Event | No mapping
MouseDown | Event | MouseDown
MouseIcon | Property | No mapping
MouseMove | Event | MouseMove
MousePointer | Property | Cursor
MouseUp | Event | MouseUp
Move | Method | SetBounds
Name | Property | Name
OLECompleteDrag | Event | No mapping
OLEDrag | Method | No mapping
OLEDragDrop | Event | No mapping
OLEDragMode | Property | No mapping
OLEDragOver | Event | No mapping
OLEDropMode | Property | No mapping
OLEGiveFeedback | Event | No mapping
OLESetData | Event | No mapping
OLEStartDrag | Event | No mapping
Paint | Event | Paint
PaintPicture | Method | No mapping
Parent | Property | FindForm
Picture | Property | (1) Image (2) BackgroundImage
Point | Method | No mapping
PSet | Method | No mapping
Refresh | Method | Refresh
Resize | Event | Resize
RightToLeft | Property | RightToLeft
Scale | Method | No mapping
ScaleHeight | Property | At run time, x.ClientRectangle.Height for Get
ScaleLeft | Property | No mapping
ScaleMode | Property | No mapping
ScaleTop | Property | No mapping
ScaleWidth | Property | At run time, x.ClientRectangle.Width for Get
ScaleX | Method | No mapping
ScaleY | Method | No mapping
SetFocus | Method | Focus
ShowWhatsThis | Method | No mapping
TabIndex | Property | TabIndex
TabStop | Property | TabStop
Tag | Property | Tag
TextHeight | Method | No mapping
TextWidth | Method | No mapping
ToolTipText | Property | SetToolTip method on ToolTip component
Top | Property | Top
Validate | Event | Validating
Visible | Property | Visible
WhatsThisHelpID | Property | No mapping
Width | Property | Width
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


Shape

Upgrades to System.Windows.Forms.Label if shape is a rectangle or square.

Visual Basic 6 | Type | Visual Basic .NET
BackColor | Property | BackColor
BackStyle | Property | No mapping
BorderColor | Property | No mapping
BorderStyle | Property | BorderStyle if value is solid or transparent
BorderWidth | Property | No mapping
Container | Property | Parent
DrawMode | Property | No mapping
FillColor | Property | BackColor, if FillStyle is Solid
FillStyle | Property | Used to determine FillColor and BackColor
Height | Property | Height
Index | Property | Index property of ControlArray object
Left | Property | Left
Move | Method | SetBounds
Name | Property | Name
Parent | Property | FindForm
Refresh | Method | Refresh
Shape | Property | Used to determine whether control upgrades or not
Tag | Property | Tag
Top | Property | Top
Visible | Property | Visible
Width | Property | Width
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


TextBox

Upgrades to System.Windows.Forms.TextBox.

Visual Basic 6 | Type | Visual Basic .NET
Alignment | Property | TextAlign
Appearance | Property | Used to determine BorderStyle
BackColor | Property | BackColor
BorderStyle | Property | Used to determine BorderStyle
CausesValidation | Property | CausesValidation
Change | Event | TextChanged
Click | Event | No mapping
Container | Property | Parent
DataChanged | Property | No mapping
DataField | Property | For ADO, used in VB6_AddADODatabinding method
DataFormat | Property | No mapping
DataMember | Property | For ADO, used in VB6_AddADODatabinding method
DataSource | Property | For ADO, used in VB6_AddADODatabinding method
DblClick | Event | DoubleClick
Drag | Method | No mapping
DragDrop | Event | No mapping
DragIcon | Property | No mapping
DragMode | Property | No mapping
DragOver | Event | No mapping
Enabled | Property | Enabled
Font | Property | Font
FontBold | Property | Font.Bold, VB6.FontChangeBold
FontItalic | Property | Font.Italic, VB6.FontChangeItalic
FontName | Property | Font.Name, VB6.FontChangeName
FontSize | Property | Font.SizeInPoints, VB6.FontChangeSize
FontStrikethru | Property | Font.StrikeOut, VB6.FontChangeStrikeOut
FontUnderline | Property | Font.Underline, VB6.FontChangeUnderline
ForeColor | Property | ForeColor
GotFocus | Event | Enter
Height | Property | Height
HelpContextID | Property | No mapping
HideSelection | Property | HideSelection
HWnd | Property | Handle
IMEMode | Property | IMEMode
Index | Property | Index property of ControlArray object
KeyDown | Event | KeyDown
KeyPress | Event | KeyPress
KeyUp | Event | KeyUp
Left | Property | Left
LinkClose | Event | No mapping
LinkError | Event | No mapping
LinkExecute | Method | No mapping
LinkItem | Property | No mapping
LinkMode | Property | No mapping
LinkNotify | Event | No mapping
LinkOpen | Event | No mapping
LinkPoke | Method | No mapping
LinkRequest | Method | No mapping
LinkSend | Method | No mapping
LinkTimeout | Property | No mapping
LinkTopic | Property | No mapping
Locked | Property | ReadOnly
LostFocus | Event | Leave
MaxLength | Property | MaxLength
MouseDown | Event | MouseDown
MouseIcon | Property | No mapping
MouseMove | Event | MouseMove
MousePointer | Property | Cursor
MouseUp | Event | MouseUp
Move | Method | SetBounds
MultiLine | Property | MultilLine
Name | Property | Name
OLECompleteDrag | Event | No mapping
OLEDrag | Method | No mapping
OLEDragDrop | Event | No mapping
OLEDragMode | Property | No mapping
OLEDragOver | Event | No mapping
OLEDropMode | Property | No mapping
OLEGiveFeedback | Event | No mapping
OLESetData | Event | No mapping
OLEStartDrag | Event | No mapping
Parent | Property | FindForm
PasswordChar | Property | PasswordChar
Refresh | Method | Refresh
RightToLeft | Property | RightToLeft
ScrollBars | Property | ScrollBars
SelLength | Property | SelectionLength
SelStart | Property | SelectionStart
SelText | Property | SelectedText
SetFocus | Method | Focus
ShowWhatsThis | Method | No mapping
TabIndex | Property | TabIndex
TabStop | Property | TabStop
Tag | Property | Tag
Text | Property | Text
ToolTipText | Property | SetToolTip method on ToolTip component
Top | Property | Top
Validate | Event | Validating
Visible | Property | Visible
WhatsThisHelpID | Property | No mapping
Width | Property | Width
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


Timer

Upgrades to System.Windows.Forms.Timer.

Visual Basic 6 | Type | Visual Basic .NET
Enabled | Property | Enabled
Index | Property | Index property of ControlArray object
Interval | Property | Interval
Name | Property | Name
Parent | Property | No mapping
Tag | Property | No mapping
Timer | Event | Tick


VScrollBar

Upgrades to System.Windows.Forms.VScrollBar

Visual Basic 6 | Type | Visual Basic .NET
CausesValidation | Property | CausesValidation
Change | Event | Change method called from Scroll event
Container | Property | Parent
Drag | Method | No mapping
DragDrop | Event | No mapping
DragIcon | Property | No mapping
DragMode | Property | No mapping
DragOver | Event | No mapping
Enabled | Property | Enabled
GotFocus | Event | Enter
Height | Property | Height
HelpContextID | Property | No mapping
hWnd | Property | Handle
Index | Property | Index property of ControlArray object
KeyDown | Event | KeyDown
KeyPress | Event | KeyPress
KeyUp | Event | KeyUp
LargeChange | Property | LargeChange at design time; three lines of code at run time to calculate new value
Left | Property | Left
LostFocus | Event | Leave
Max | Property | At design time,Maximum At run time, value calculated from LargeChange
Min | Property | Minimum
MouseIcon | Property | No mapping
MousePointer | Property | Cursor
Move | Method | SetBounds
Name | Property | Name
Parent | Property | FindForm
Refresh | Method | Refresh
RightToLeft | Property | RightToLeft
Scroll | Event | Scroll event, or Scroll_renamed method called from Scroll event if Change event exists
SetFocus | Method | Focus
ShowWhatsThis | Method | No mapping
SmallChange | Property | SmallChange
TabIndex | Property | TabIndex
TabStop | Property | TabStop
Tag | Property | Tag
Top | Property | Top
Validate | Event | Validating
Value | Property | Value
Visible | Property | Visible
WhatsThisHelpID | Property | No mapping
Width | Property | Width
ZOrder | Method | VB6.ZOrder method, or BringToFront if Position parameter is 0, SendToBack if Position parameter is 1


Function Mapping Reference

Appendix B
Function Mapping Reference

The following table shows the Microsoft Visual Basic 6 set functions and statements with the equivalent Visual Basic .NET functions. This is the table of mappings that the Upgrade Wizard uses when upgrading functions and stantements to Visual Basic. NET.

Visual Basic 6 | Member Type | Visual Basic .NET
Abs | Function | System.Math.Abs
AppActivate | Statement | AppActivate
Asc | Function | Asc
AscB | Function | No mapping
AscW | Function | AscW
Atn | Function | System.Math.Atan
Beep | Statement | Beep
Calendar | Property | No mapping
CallByName | Function | CallByName
CBool | Function | CBool
CByte | Function | CByte
CCur | Function | CDec
CDate | Function | CDate
CDbl | Function | CDbl
CDec | Function | CDec
ChDir | Method | ChDir
ChDrive | Method | ChDrive
Choose | Function | Choose
Chr | Function | Chr
ChrB | Function | No mapping
ChrW | Function | ChrW
CInt | Function | CShort
CLng | Function | CInt
Close | Statement | FileClose
Command | Function | Command
Cos | Function | System.Math.Cos
CreateObject | Function | CreateObject
CSng | Function | CSng
CurDir | Function | CurDir
CVar | Function | CObj
CVDate | Function | CDate
CVErr | Function | No mapping
Date | Property | Today
Date$ | Property | DateString
DateAdd | Function | DateAdd
DateDiff | Function | DateDiff
DatePart | Function | DatePart
DateSerial | Function | DateSerial
DateValue | Function | DateValue
Day | Function | Day
DDB | Function | DDB
DeleteSetting | Statement | DeleteSetting
Dir | Method | Dir
DoEvents | Statement | System.Windows.Forms.Application.DoEvents
Environ | Function | Environ
EOF | Function | EOF
Eqv | Operator | VB6.Eqv
Err | Function | Err
Error$ | Function | ErrorToString
Exp | Function | System.Math.Exp
FileAttr | Function | FileAttr
FileCopy | Method | FileCopy
FileDateTime | Function | FileDateTime
FileLen | Function | FileLen
Filter | Function | Filter
Fix | Function | Fix
Format | Function | Format
FormatCurrency | Function | FormatCurrency
FormatDateTime | Function | FormatDateTime
FormatNumber | Function | FormatNumber
FormatPercent | Function | FormatPercent
FreeFile | Function | FreeFile
FV | Function | FV
Get | Statement | FileGet
GetAllSettings | Function | GetAllSettings
GetAttr | Function | GetAttr
GetObject | Function | GetObject
GetSetting | Function | GetSetting
Hex | Function | Hex
Hour | Function | Hour
IIf | Function | IIf
IMEStatus | Function | No mapping
Imp | Operator | VB6.Imp
Input # statement | Statement | Input
Input function | Function | InputString
InputBox | Function | InputBox
InStr | Function | InStr
InStrB | Function | No mapping
InStrRev | Function | InStrRev
Int | Function | Int
IPmt | Function | IPmt
IRR | Function | IRR
IsArray | Function | IsArray
IsDate | Function | IsDate
IsEmpty | Function | IsNothing
IsError | Function | IsError
IsMissing | Function | IsNothing
IsNothing | Function | IsNothing
IsNull | Function | IsDbNull
IsNumeric | Function | IsNumeric
IsObject | Function | IsReference
Join | Function | Join
Kill | Method | Kill
LBound | Function | LBound
LCase | Function | LCase
Left | Function | Left
LeftB | Function | No mapping
Len | Function | Len
LenB | Function | No mapping
LineInput | Statement | LineInput
Loc | Function | Loc
Lock | Statement | Lock
LOF | Function | LOF
Log | Function | System.Math.Log
LSet | Statement | LSet (only for left-justifying strings within strings)
LTrim | Function | LTrim
Mid | Function | Mid
Mid | Statement | Mid
MidB | Function | No mapping
Minute | Function | Minute
MIRR | Function | MIRR
MkDir | Function | MkDir
Month | Function | Month
MonthName | Function | MonthName
MsgBox | Function | MsgBox
Name | Statement | Rename
Now | Function | Now
NPer | Function | NPer
NPV | Function | NPV
Oct | Function | Oct
Open | Statement | FileOpen
Partition | Function | Partition
Pmt | Function | Pmt
PPmt | Function | PPmt
Print | Statement | Print, PrintLine
Put | Statement | FilePut
PV | Function | PV
QBColor | Function | QBColor
Randomize | Function | Randomize
Rate | Function | Rate
Replace | Function | Replace
Reset | Method | Reset
RGB | Function | RGB
Right | Function | Right
RightB | Function | No mapping
RmDir | Method | RmDir
Rnd | Function | Rnd
Round | Function | System.Math.Round
RSet | Statement | RSet
RTrim | Function | RTrim
SaveSetting | Function | SaveSetting
Second | Function | Second
Seek | Function | Seek
SendKeys | Statement | System.Windows.Forms.SendKeys.Send
SetAttr | Method | SetAttr
Sgn | Function | System.Math.Sign
Shell | Function | Shell
Sin | Function | System.Math.Sin
SLN | Function | SLN
Space | Function | Space
Spc | Function | Spc
Split | Function | Split
Sqr | Function | System.Math.Sqrt
Str | Function | Str
StrComp | Function | StrComp
StrConv | Function | StrConv
String | Function | New String constructor
StrReverse | Function | StrReverse
Switch | Function | Switch
SYD | Function | SYD
Tab | Function | Tab
Tan | Function | System.Math.Tan
Time | Property | TimeOfDay
Time$ | Property | TimeString
Timer | Function | Timer
TimeSerial | Function | TimeSerial
TimeValue | Function | TimeValue
Trim | Function | Trim
TypeName | Function | TypeName
UBound | Function | UBound
UCase | Function | UCase
Unlock | Statement | Unlock
Val | Function | Val
VarType | Function | VarType
Weekday | Function | Weekday
WeekdayName | Function | WeekdayName
Width | Statement | FileWidth
Write | Statement | Write
Year | Function | Year


Biography

About the Authors
Ed Robinson

Ed Robinson is a program manager in the Visual Basic .NET team at Microsoft. His responsibilities include designing the upgrade technologies built into Visual Basic. NET, including the Visual Basic .NET Upgrade Wizard. Ed also works with customers preparing to move their applications to .NET. With more than eleven years of industry experience in New Zealand and the United States, he has a good understanding of the challenges and opportunities facing Visual Basic developers. Ed is a regular speaker at development conferences throughout the world, he has published a number of white papers on the Microsoft Developers Network (MSDN), and he plays an active role at Microsoft in developing training material for Visual Studio .NET.

Michael Bond

Michael Bond is the Visual Basic .NET development lead for the Visual Basic .NET Upgrade Wizard. Mike has made a career of working on Visual Basic. He started at Microsoft more than eleven years ago in Product Support Services (PSS) supporting QuickBasic 4.5, BASIC PDS 7, and Visual Basic 1. He acted as the product support lead for Visual Basic 2 and 3, working with internal software vendors (ISVs) creating VBX controls; he later moved to the Visual Basic Test Team, where he functioned as a test lead for Visual Basic 4. In 1995 he joined the Visual Basic development team to design and develop ActiveX controls and form-related features for Visual Basic 5 and 6 and to work with third-party vendors who provided ActiveX controls and designers. Mike is coauthor of the VBX to OCX Migration Toolkit.

Robert Ian Oliver

Robert Ian Oliver is a seasoned Visual Basic developer with a background in C/C++ and Java, currently working as a development engineer in the Microsoft Visual Studio group. In his two years with Microsoft, Ian has concentrated on porting enterprise customer applications to Visual Studio .NET and the .NET Framework. His previous experience varies widely, from being an independent Web consultant to working at Intel as a component design engineer on the first Itanium chipset. Ian has been involved in Web development as far back as 1995 and has implemented many e-business solutions throughout the years.