Professional Documents
Culture Documents
Table of Contents
Foreword xv
Introduction xvii
v
A04T61587x.fm Page vi Friday, November 16, 2001 10:17 AM
vi Table of Contents
Forms Packages 34
A Single Standard for Windows Forms 34
Two Forms Packages for the Price of One 35
Language Differences 35
All Subroutine Calls Must Have Parentheses 37
ByVal or ByRef Is Required 38
Is That My Event? 38
Arrays Must Have a Zero-Bound Lower Dimension 39
Fixed-Length Strings Are Not Supported 40
Variant Data Type Is Eliminated 40
Visibility of Variables Declared in Nested Scopes Is Limited 41
Changes in the Debugger 42
No Edit and Continue 42
Cannot Continue After an Error 42
No Repainting in Break Mode 42
Conclusion 43
3 Upgrading Options 45
Upgrading Is Optional 45
Don’t Upgrade 45
Partial Upgrade 46
Complete Upgrade 47
Upgrade with Interoperability 47
Role of the Upgrade Wizard 48
The Upgrade Report 48
Testing 49
Upgrading from Earlier Versions of Visual Basic 49
Selecting Projects to Upgrade 50
Evaluating the Benefits 51
Evaluating the Effort Required 55
Developing the Upgrade Plan 58
Conclusion 59
4 Preparing Your Project for the Upgrade to Visual Basic .NET 61
Why Change Anything? 61
Cleaning Up Legacy Code 62
VarPtr, DefInt, and Other No-Shows 62
DAO and RDO Data Binding 63
A04T61587x.fm Page vii Friday, November 16, 2001 10:17 AM
Table of Contents ix
9 Using Visual Basic 6 with Visual Basic .NET: COM Interop 175
Where COM Interop Comes into Play 177
ActiveX Controls 177
Communication Between a .NET Client and a COM Server Component 177
Communication Between a COM Client and a .NET Server Component 178
Upgrading a Visual Basic 6 Client/Server Application 178
Creating a .NET Client That Talks to a COM Server 180
Debugging Between the Visual Basic .NET Client and Visual Basic 6 Server 182
Exposing a Visual Basic .NET Component to Be Called by a
Visual Basic 6 Client 184
Debugging Between the Visual Basic 6 Client and .NET Server 187
Tying It All Together 188
Replacing COM with .NET: Binary Compatibility 189
Indirect Replacement Model 190
Enabling Binary Compatibility in Visual Basic .NET Classes 191
Conclusion 196
x Table of Contents
Table of Contents xi
Part V Appendixes
Appendix A Object Mapping Reference 467
Appendix B Function Mapping Reference 515
Index 521
A06I61587x.fm Page xvii Friday, November 16, 2001 10:07 AM
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.
xvii
A06I61587x.fm Page xviii Friday, November 16, 2001 10:07 AM
xviii Introduction
Introduction xix
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.
xx Introduction
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.
C0161587x.fm Page 1 Thursday, November 15, 2001 1:59 PM
Part I
Introduction to
Upgrading
1 Visual Basic .NET Is More Than Visual Basic 6 + 1 3
2 Visual Basic 6 and Visual Basic .NET: Differences 19
3 Upgrading Options 45
4 Preparing Your Project for the Upgrade to Visual Basic .NET 61
C0161587x.fm Page 2 Thursday, November 15, 2001 1:59 PM
C0161587x.fm Page 3 Thursday, November 15, 2001 1:59 PM
3
C0161587x.fm Page 4 Thursday, November 15, 2001 1:59 PM
development tool, Visual Basic .NET is designed to leverage the .NET plat-
form, 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:
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 plumb-
ing in every program.
In Visual Basic versions 2 though 6, Microsoft kept the underlying archi-
tecture of the product the same and simply added new features. Visual Basic 2
and 3 introduced the property grid, Data Access Objects (DAO) database pro-
gramming, 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,
C0161587x.fm Page 5 Thursday, November 15, 2001 1:59 PM
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 Win-
dows 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 exist-
ing 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 develop-
ing 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 technolo-
gies 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 ask-
ing for some significant new language features: inheritance, easier access to the
underlying platform, and a solution for the many different component version-
ing problems that had collectively been labeled “DLL hell.” When looking for a
solution to these problems, Microsoft also saw the opportunity to create a uni-
fied framework for developing applications. To understand why such a frame-
work was desirable, consider that developers wanting to create forms for
Windows in Visual Basic 6, Visual C++, and Microsoft Office Visual Basic for
C0161587x.fm Page 6 Thursday, November 15, 2001 1:59 PM
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.
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
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 prin-
ciple of upgrading: Visual Basic .NET is subtly different from Visual Basic 6, and
therefore upgraded applications will be different from their Visual Basic 6 coun-
terparts in subtle ways. In most cases you will not notice the difference, yet it’s
C0161587x.fm Page 9 Thursday, November 15, 2001 1:59 PM
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. Chap-
ter 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.
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 com-
ponents 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:
C0161587x.fm Page 11 Thursday, November 15, 2001 1:59 PM
Interfaces in Code
Along with “real” inheritance, Visual Basic .NET still supports interface inherit-
ance and improves on it by providing the Interface keyword. The Interface
keyword defines the interfaces in code. Your classes then implement the inter-
faces, 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
Sub Main()
Try
Windows.Forms.Application.Run( _
New Form1())
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
C0161587x.fm Page 12 Thursday, November 15, 2001 1:59 PM
In Visual Basic .NET, you can write this in a much more elegant format:
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 num-
ber of parameters or accepting parameters of different types. For example, sup-
pose 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:
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 ele-
ments 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 DebuggerHid-
den attribute allows you to do this:
<DebuggerHidden()> _
Function nToz(ByVal input) As Integer
If Not IsNumeric(input) Then input = 0
Return Input
End Function
C0161587x.fm Page 13 Thursday, November 15, 2001 1:59 PM
Multithreading
By default, your Visual Basic .NET applications are single threaded, but the lan-
guage 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
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 devel-
opment. 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 inher-
itance 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+ semitranspar-
ency in a Windows application.
F01km01
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 con-
trols with different text for both Spanish and English.
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
C0161587x.fm Page 16 Thursday, November 15, 2001 1:59 PM
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.
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.
F01km02
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
C0161587x.fm Page 17 Thursday, November 15, 2001 1:59 PM
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.
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 meth-
ods 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.
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 pro-
gramming 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 differ-
ences 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 fur-
ther 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 walk-
through of upgrading an application. Welcome to Visual Basic .NET, the future of
Visual Basic.
C0261587x.fm Page 19 Thursday, November 15, 2001 2:02 PM
19
C0261587x.fm Page 20 Thursday, November 15, 2001 2:02 PM
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 hap-
pens under the hood. For example, you do not need to know that you are cre-
ating or using a .NET component. A .NET component is just like any other
component. It has properties, methods, and events just as an ActiveX compo-
nent 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 under-
stand 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.
C0261587x.fm Page 21 Thursday, November 15, 2001 2:02 PM
.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 man-
agement, 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 Hash-
Table 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 man-
ager. 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-con-
tained 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 com-
ponent 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 environ-
ment 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
C0261587x.fm Page 22 Thursday, November 15, 2001 2:02 PM
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 gener-
ates exactly the same set of .NET runtime instructions, called Microsoft Interme-
diate Language (MSIL). With each language distilled down to one base
instruction set (MSIL), running against the same runtime (the .NET common lan-
guage runtime), and using one set of .NET Framework classes, sharing and con-
sistency 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 ele-
ment 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 asso-
ciated 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 vari-
able comes into existence and the exact moment that it becomes nonexistent.
The lifespan of a Visual Basic .NET variable, on the other hand, is indetermin-
istic, 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
C0261587x.fm Page 23 Thursday, November 15, 2001 2:02 PM
If you run this application in Visual Basic 6, it will run without error. How-
ever, 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 Col-
lector 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
they will actually terminate. Nor do you know what resources are being con-
sumed 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 mecha-
nism 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 IDispos-
able 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.
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.
C0261587x.fm Page 25 Thursday, November 15, 2001 2:02 PM
GC.Collect
GC.WaitForPendingFinalizers
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.
C0261587x.fm Page 26 Thursday, November 15, 2001 2:02 PM
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.
F01km01
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 click-
ing 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 applica-
tion. 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.
C0261587x.fm Page 27 Thursday, November 15, 2001 2:02 PM
Visual Basic .NET is stricter about the use of the same types in an applica-
tion. 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:
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 exam-
ple, type references can become quite bothersome when you are dealing with
.NET types such as System.Runtime.Interopservices.UnmanagedType. To sim-
plify 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
Imports System.Runtime.Interopservices.
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.
C0261587x.fm Page 28 Thursday, November 15, 2001 2:02 PM
No More GUIDs
Each ActiveX type, whether it is a class, an interface, an enumerator, or a struc-
ture, 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 imple-
ments 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 consid-
ered the same if they meet all of the following conditions:
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 assem-
bly such as Microsoft.ADODB.dll with the same name. There could be two
Microsoft.ADODB.dll assemblies on your machine with different version num-
bers, 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.
C0261587x.fm Page 29 Thursday, November 15, 2001 2:02 PM
Threading Model
Visual Basic 6 ActiveX DLLs and controls can be either single threaded or apart-
ment 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 ran-
dom 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 mul-
tithreaded Visual Basic .NET components multithread safe.
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 syn-
tax 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.
F01km02
F01km03
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 Win-
dows Forms project, the Toolbox will contain categories titled Data, Compo-
nents 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 compo-
nents 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 refer-
ence to that component is added to your project.
C0261587x.fm Page 32 Thursday, November 15, 2001 2:02 PM
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.
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 dif-
ference is that the default view for the Property Browser in Visual Studio .NET
is Category view, meaning that related properties are grouped under a descrip-
tive 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
C0261587x.fm Page 33 Thursday, November 15, 2001 2:02 PM
F01km04
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.
F01km05
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.
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 key-
words that have been added to the Visual Basic .NET language.
C0261587x.fm Page 36 Thursday, November 15, 2001 2:02 PM
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.
you are required to use parentheses in your Visual Basic .NET code, as follows:
MsgBox(“Hello World”)
C0261587x.fm Page 38 Thursday, November 15, 2001 2:02 PM
Is That My Event?
Visual Basic 6 associates events by name, using the pattern <Object-
Name>_<EventName>. For example, the click event associated with a com-
mand CommandButton is
Private Sub Command1_Click()
If you change the name of the Visual Basic 6 event to the name of a sub-
routine 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 sub-
routine 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:
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:
Event Parameters
Another interesting change related to events is that event parameters are differ-
ent 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 refer-
ence 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
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 Item-
CheckEventArgs object e.
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
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 decla-
rations in Visual Basic 6.
To set the value of a FixedLengthString variable you need to use the Value
property as follows:
Refer to Chapter 7 for more information about the Visual Basic .NET com-
patibility library.
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)
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 com-
piler error occurs. It does this by moving the declaration for Inner-
Scope 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.
C0261587x.fm Page 42 Thursday, November 15, 2001 2:02 PM
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.
C0261587x.fm Page 44 Thursday, November 15, 2001 2:02 PM
C0361587x.fm Page 45 Thursday, November 15, 2001 2:09 PM
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 rea-
sons 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 appli-
cations will benefit from the new features in Visual Basic .NET. Some applica-
tions 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:
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
45
C0361587x.fm Page 46 Thursday, November 15, 2001 2:09 PM
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 ben-
efits 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 refer-
ence 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.
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. Chap-
ter 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 technol-
ogies 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.
F03km01
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.
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 Wiz-
ard, 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.
Is it worth No
the effort?
Yes
Develop
upgrade
plan
F03km02
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 bet-
ter 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 tan-
gible. 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.
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.
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 Sys-
tem.Text namespace.
Sub StringTest()
Dim before, after As DateTime
Dim diff As Double
before = Now()
SlowString()
after = Now()
(continued)
C0361587x.fm Page 54 Thursday, November 15, 2001 2:09 PM
before = Now()
FastString()
after = Now()
Dim i As Integer
Return result
End Function
Dim i As Integer
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 concate-
nation. In other words the FastString() method can be called approxi-
mately 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.
C0361587x.fm Page 55 Thursday, November 15, 2001 2:09 PM
Moving On
That sums up the benefits of upgrading to Visual Basic .NET. If you’re still plan-
ning to forge ahead, it’s time to move on to evaluate the effort that the upgrade
will require.
■ Architecture
■ Code quality
■ Technologies
■ Size
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
C0361587x.fm Page 56 Thursday, November 15, 2001 2:09 PM
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:
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 con-
vert 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 embed-
ded in a Web browser and others with client-installed executables, COM com-
ponents, and third-party controls. Unless their design is fairly straightforward,
these applications require more work to upgrade to the .NET Framework. Since
C0361587x.fm Page 57 Thursday, November 15, 2001 2:09 PM
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 rede-
signed 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 diffi-
culty 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
C0361587x.fm Page 58 Thursday, November 15, 2001 2:09 PM
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.
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.
C0361587x.fm Page 60 Thursday, November 15, 2001 2:09 PM
C0461587x.fm Page 61 Thursday, November 15, 2001 2:31 PM
61
C0461587x.fm Page 62 Thursday, November 15, 2001 2:31 PM
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 fol-
lowing sections demonstrate what you can do to improve your application and
make it more suitable to an upgrade.
Chapter 4 Preparing Your Project for the Upgrade to Visual Basic .NET 63
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 Wiz-
ard just to see what it does, but that is a subject covered in a later chapter. (See
Chapter 5)
Chapter 4 Preparing Your Project for the Upgrade to Visual Basic .NET 65
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 pro-
grammer intends to happen. We also get the benefits of compile-time valida-
tion: 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.
C0461587x.fm Page 66 Thursday, November 15, 2001 2:31 PM
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 program-
ming 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.
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.
C0461587x.fm Page 67 Thursday, November 15, 2001 2:31 PM
Chapter 4 Preparing Your Project for the Upgrade to Visual Basic .NET 67
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 con-
stants 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 func-
tion 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 offend-
ing 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
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.
Decimal point
Dim dt As Date
Dim dbl As Double
dbl = 37094.776724537
dt = dbl
Debug.Print dt
Debug.Print dbl
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 Dec-
imal value less than 1.
C0461587x.fm Page 69 Thursday, November 15, 2001 2:31 PM
Chapter 4 Preparing Your Project for the Upgrade to Visual Basic .NET 69
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 gen-
erate 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
Late Binding
Late binding occurs when the Variant type is used (either implicitly or explic-
itly). 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 responsi-
bility 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 pro-
cessing overhead to a single line of code. While the performance consequences
are not obvious in a small application, large-scale enterprise or Web applica-
tions 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
Chapter 4 Preparing Your Project for the Upgrade to Visual Basic .NET 71
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:
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. Imag-
ine a situation involving a form (MyForm) with a label (Label1). Consider the fol-
lowing example, in which you pass the form and new text for your control:
Sub Form_Load()
SetLabelText Me, “This is soft binding!"
End Sub
Notice that everything is strongly typed. There is certainly no obvious late bind-
ing 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.
C0461587x.fm Page 73 Thursday, November 15, 2001 2:31 PM
Chapter 4 Preparing Your Project for the Upgrade to Visual Basic .NET 73
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)
‘ 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 decla-
ration. 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 instantiat-
ing 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 actu-
ally 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 cre-
ated in its place unless the application does so explicitly. In the second exam-
ple, 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
C0461587x.fm Page 75 Thursday, November 15, 2001 2:31 PM
Chapter 4 Preparing Your Project for the Upgrade to Visual Basic .NET 75
and lifetimes), but it could have consequences for your code. Example 3 dem-
onstrates 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 con-
trols 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 mer-
rily create the object for you and at the same time hide a bug in your appli-
cation. 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 (espe-
cially 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
C0461587x.fm Page 76 Thursday, November 15, 2001 2:31 PM
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, cre-
ating a file, or whatever). In Visual Basic .NET, those kinds of assumptions can-
not 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 pro-
grammer, it increases the clarity of your code. No object will exist until you cre-
ate 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. Abandon-
ing 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 “Pre-
paring a Visual Basic 6.0 Application for Upgrading.” You can find it by search-
ing the Help files included with Visual Studio .NET.
C0561587x.fm Page 77 Thursday, November 15, 2001 2:37 PM
Part II
Upgrading
Applications
5 Your First Upgrade 79
6 Common Tasks in Visual Basic .NET 101
7 Upgrading Wizard Ins and Outs 117
8 Errors, Warnings, and Issues 149
9 Using Visual Basic 6 with Visual Basic .NET: COM Interop 175
C0561587x.fm Page 78 Thursday, November 15, 2001 2:37 PM
C0561587x.fm Page 79 Thursday, November 15, 2001 2:37 PM
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. Hav-
ing 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 prod-
ucts 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.)
79
C0561587x.fm Page 80 Thursday, November 15, 2001 2:37 PM
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.
C0561587x.fm Page 81 Thursday, November 15, 2001 2:37 PM
F05km01
When you run the project, you’ll see that the TextBox updates every sec-
ond 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 loca-
tion where you saved your Visual Basic 6 project file, select prj-
FirstUpgrade.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.
C0561587x.fm Page 82 Thursday, November 15, 2001 2:37 PM
F05km02
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 upgrad-
ing 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 dis-
abled, and for this reason the second option, Generate Default Inter-
faces 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 Imple-
ments 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.
C0561587x.fm Page 83 Thursday, November 15, 2001 2:37 PM
F05km03
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 <project-
name>.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.
F05km04
Status text and a progress bar show you what the Upgrade Wizard is pro-
cessing 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.
F05km05
F05km06
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 num-
ber every second, as shown in Figure 5-7.
C0561587x.fm Page 86 Thursday, November 15, 2001 2:37 PM
F05km07
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.
F05km08
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
F05km09
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.
C0561587x.fm Page 88 Thursday, November 15, 2001 2:37 PM
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
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:
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 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 state-
ment 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.
If you want to, you can put several forms or classes into a single file, pro-
vided each has its own Class statement.
Just beneath the Class statement, you’ll see two collapsed regions: Win-
dows Form Designer Generated Code and Upgrade Support, as shown in
Figure 5-10.
F05km10
to
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,
C0561587x.fm Page 91 Thursday, November 15, 2001 2:37 PM
an object that contains extra arguments (such as mouse coordinates). The con-
tents 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 con-
trol 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.Text-
Changed events:
The line after the Sub statement contains the following upgrade warning:
Me.Timer1.Interval = 0
is upgraded to
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 prop-
erty 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 ver-
sion. 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.
F05km11
The upgrade report captures in one place the issues found while upgrad-
ing 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 prob-
lem. 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.
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 prj-
FirstUpgrade 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.
F05km12
After you’ve added this code, the project group will look like
Figure 5-13.
F05km13
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 refer-
ences 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 Walk-
through” earlier in this chapter.) You won’t need to make any modi-
fications to the upgraded code, since we replaced Timer.Interval = 0
with code that shows a message box.
C0561587x.fm Page 97 Thursday, November 15, 2001 2:37 PM
F05km07
You now know the basic procedure for upgrading project groups. If your
Visual Basic 6 project group contains many projects, you simply repeat these
C0561587x.fm Page 98 Thursday, November 15, 2001 2:37 PM
steps for every project in the group. Start with the most downstream project,
and work your way up the dependency hierarchy.
F05km15
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 fol-
lowing 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:
C0561587x.fm Page 99 Thursday, November 15, 2001 2:37 PM
Dim x As Short
System.Diagnostics.Debug.WriteLine(x)
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.
vbupgrade /?
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:
Because it is a command-line tool, the upgrade tool doesn’t show any wiz-
ard 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.
C0661587x.fm Page 101 Thursday, November 15, 2001 3:08 PM
101
C0661587x.fm Page 102 Thursday, November 15, 2001 3:08 PM
F06km01
F06km02
F06km03
Now let’s set some properties for the controls we just added to the form.
F06km04
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 Add-
Button 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 Remove-
Button. 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.
C0661587x.fm Page 106 Thursday, November 15, 2001 3:08 PM
F06km05
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.
F06km06
So it’s broken. Click Break and let’s dive into the debugger.
C0661587x.fm Page 107 Thursday, November 15, 2001 3:08 PM
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 ses-
sions. Moving to the Code Editor, replace the body of the RemoveButton_Click
event with the following code:
C0661587x.fm Page 108 Thursday, November 15, 2001 3:08 PM
This code prevents the application from attempting to remove any items
from the ListBox unless an item is selected. It will avoid the exception previ-
ously 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 actu-
ally 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.
F06km07
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.
F06km08
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 Envi-
ronment 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
C0661587x.fm Page 110 Thursday, November 15, 2001 3:08 PM
of your application and prioritizing feature areas. For example, you can cre-
ate 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 Jan-
ice that indicate areas that individuals on your team need to address. This
approach is often useful when implementing large applications.
F06km9
‘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.
C0661587x.fm Page 111 Thursday, November 15, 2001 3:08 PM
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.
F06km10
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 Ref-
erence 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.
F06km11
Problem-Solving Techniques
You need to master a number of skills to become effective at debugging. This
section explores some of those skills.
(MSDN) documentation is that the Trace class can be used to instrument release
builds. Instrumentation allows you to monitor the health of your application run-
ning in real-life settings, isolate problems, and fix them without disturbing a run-
ning 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 insert-
ing 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:
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.
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 com-
mands is typical:
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 simi-
lar debugging experience to the Visual Basic .NET debugger.
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 stor-
ing the results of each statement in a temporary variable. The following sample
shows how to do this:
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 pro-
vided 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.
C0661587x.fm Page 116 Thursday, November 15, 2001 3:08 PM
C0761587x.fm Page 117 Thursday, November 15, 2001 2:43 PM
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 fol-
lowing statements:
“It’s your code.”
“Just make it work.”
117
C0761587x.fm Page 118 Thursday, November 15, 2001 2:43 PM
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 ele-
ment 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 Frame-
work equivalent, Button.Text property. If no element in Visual Basic .NET rep-
resents 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.
Compatibility Library
To help your upgraded applications work, Visual Basic .NET includes a com-
patibility 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 func-
tions 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.
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:
Figure 7-1 illustrates how the elements of a form are upgraded. Figure 7-2 illus-
trates how code is upgraded.
ActiveX controls
User controls
Intrinsic controls
Compatibility class
F07km01
Compatibility class
Behavior warning
F07km02
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. Equiva-
lent Visual Basic .NET project types exist for common project types such as
Standard EXE, ActiveX DLL, and UserControl.
C0761587x.fm Page 122 Thursday, November 15, 2001 2:43 PM
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 set-
tings, 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 con-
tinue 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.
C0761587x.fm Page 123 Thursday, November 15, 2001 2:43 PM
F07km03
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.
F07km04
F07km05
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 Prop-
erties from the Project menu. Within the Properties dialog box, select Build
under the Configuration Properties folder, as shown in Figure 7-6.
F07km06
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 Prop-
erties dialog box, as shown in Figure 7-7. You could also set a project-level
Help context ID.
F07km07
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 dif-
ferent. 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.
More Info 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 Applica-
tions; 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.
C0761587x.fm Page 126 Thursday, November 15, 2001 2:43 PM
■ Comments
■ Company Name
■ File Description
■ Legal Copyright
■ Legal Trademarks
■ Product Name
■ Major Version Number
■ Minor Version Number
■ Revision
F07km08
Compiler settings The Upgrade Wizard doesn’t upgrade any Visual Basic com-
piler 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
C0761587x.fm Page 128 Thursday, November 15, 2001 2:43 PM
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.
F07km09
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 gen-
erated 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.)
F07km10
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
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
Table 7-3 shows how the original .frm description and the generated
Visual Basic .NET code match up line for line.
After the form is upgraded, it will look nearly identical to its Visual Basic 6
counterpart, as shown in Figure 7-11.
F07km11
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 MyBut-
ton 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 sec-
tion, 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 possi-
ble, it offers suggestions on how to work around or solve the issue.
C0761587x.fm Page 135 Thursday, November 15, 2001 2:43 PM
F07km12
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 con-
trol 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 chap-
ter 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. Con-
trol arrays and all associated code are upgraded as is, with minor syntactical
changes in some cases, as illustrated in the following example.
C0761587x.fm Page 136 Thursday, November 15, 2001 2:43 PM
Consider a Visual Basic 6 form that has two command buttons in a con-
trol 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
Here’s the Visual Basic .NET code after the Upgrade Wizard has upgraded it:
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:
Despite these differences, the good news is that Visual Basic .NET and the
Upgrade Wizard both support control arrays.
C0761587x.fm Page 137 Thursday, November 15, 2001 2:43 PM
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 Wiz-
ard 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 fol-
lowing code to hook up data binding:
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 con-
trol, you know that it would be pretty useless if you couldn’t place other con-
trols inside it. The control reaches the height of uselessness when you place it
on a Windows form. Windows Forms doesn’t support the ISimpleFrame inter-
face, so you can’t place any controls on the tabs within the SSTab.
The Upgrade Wizard recognizes this situation and rectifies it by automati-
cally upgrading your instances of SSTab controls to instances of Windows
Forms TabControls. The Windows Forms TabControl is similar—but not identi-
cal—in behavior to the SSTab control. You’ll find that after upgrade, the Tab-
Control 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 Tab-
Control 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 appli-
cation. The UpDown control doesn’t update the buddy control.
C0761587x.fm Page 140 Thursday, November 15, 2001 2:43 PM
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.
b = IsEmpty(v)
b = IsObject(v)
b = IsMissing(v)
b = IsNull(v)
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)
C0761587x.fm Page 141 Thursday, November 15, 2001 2:43 PM
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.
This code upgrades to the following Visual Basic .NET code, using the equiva-
lent function from the System.Math class:
Dim i As Short
i = System.Math.Abs(-1)
upgrades to
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 appro-
priate 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
Sub Main
Dim s As String
s = “I’m a String"
End Sub
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.
Code-Behind Forms
Code-behind forms refers to code that makes reference to a form, control, prop-
erty, 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
C0761587x.fm Page 145 Thursday, November 15, 2001 2:43 PM
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 man-
ually upgrade the code related to the other global objects. For example, to
upgrade your Printer object–related code, use the .NET System.Drawing.Print-
ing.PrintDocument class. Chapter 12 contains more information on upgrading
global objects.
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 Get-
DataMember 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.
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 origi-
nal Visual Basic 6 DataEnvironment. Any code that references the DataEnviron-
ment 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 sup-
ports reading reports stored in DataReport designer format.
C0761587x.fm Page 148 Thursday, November 15, 2001 2:43 PM
Conclusion
149
C0861587x.fm Page 150 Thursday, November 15, 2001 2:49 PM
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
Set o = Form1.Label1
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:
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.
C0861587x.fm Page 151 Thursday, November 15, 2001 2:49 PM
Declaring it as a label makes it strongly typed, and the wizard will have no trou-
ble 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”
Dim o As System.Windows.Forms.Label
o = Form1.DefInstance.Label1
o.Text = “VB Rocks”
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:
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 com-
pilation or execution of your project.
C0861587x.fm Page 152 Thursday, November 15, 2001 2:49 PM
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
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.
C0861587x.fm Page 153 Thursday, November 15, 2001 2:49 PM
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 fol-
lowing 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 key-
word 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 ini-
tializes 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 Initial-
ize method. The following code shows the modified Sub Main:
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)
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 prop-
erty to Manual. When you upgrade the project, the OLEDropMode property is
not upgraded, since there is no direct equivalent in Windows Forms. What hap-
pens 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 associ-
ated 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.
C0861587x.fm Page 155 Thursday, November 15, 2001 2:49 PM
■ 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 col-
umns 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.
F08km02
F08km03
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.
C0861587x.fm Page 159 Thursday, November 15, 2001 2:49 PM
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 modifi-
cations 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 mod-
ifications might take quite some time to fix, since you’re learning about upgrad-
ing 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.
C0861587x.fm Page 160 Thursday, November 15, 2001 2:49 PM
Upgrade Issues
■ <statement> <statementname> is not supported Marks the Cal-
endar 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
C0861587x.fm Page 161 Thursday, November 15, 2001 2:49 PM
Upgrade ToDos
■ Uncomment and change the following line to return the col-
lection 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 Ini-
tialize 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.
C0861587x.fm Page 164 Thursday, November 15, 2001 2:49 PM
Upgrade Warnings
■ As <variable type> was removed from ReDim <variable>
statement Arrays can be redimensioned using the ReDim state-
ment 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 marshal-
ling 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 initial-
ized before it can be used.
■ Array ‘xxx’ may need to have individual elements
initialized If an array of structures has been declared or redimen-
sioned 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 <lower-
bound> 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 con-
trol 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.
C0861587x.fm Page 165 Thursday, November 15, 2001 2:49 PM
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.
❑ 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 Item-
Data 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
C0861587x.fm Page 169 Thursday, November 15, 2001 2:49 PM
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 rec-
ommended 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 ref-
erences to examine the type libraries, create wrappers, and instanti-
ate 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 installa-
tion 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.
C0861587x.fm Page 170 Thursday, November 15, 2001 2:49 PM
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 sec-
tion 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 con-
stant 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 con-
stants 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.
C0861587x.fm Page 171 Thursday, November 15, 2001 2:49 PM
■ 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 func-
tion 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
■ Visual Basic 6 has one exception to this behavior: if you pass a prop-
erty as a ByRef parameter, it will not be changed. For example, sup-
pose 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
C0861587x.fm Page 173 Thursday, November 15, 2001 2:49 PM
myFunction(Me.Text1.Text)
whereas this code passes the Text property ByVal (just as Visual
Basic 6 did):
myFunction((Me.Text1.Text))
■ 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 con-
trols. 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.
C0861587x.fm Page 174 Thursday, November 15, 2001 2:49 PM
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.
C0961587x.fm Page 175 Thursday, November 15, 2001 3:33 PM
175
C0961587x.fm Page 176 Thursday, November 15, 2001 3:33 PM
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 con-
trol in your application with the .NET version. Eventually your entire system
evolves to .NET, smoothly and without interruption.
Chapter 9 Using Visual Basic 6 with Visual Basic .NET: COM Interop 177
ActiveX Controls
Visual Basic .NET allows you to place either .NET or ActiveX controls on a Win-
dows 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 iden-
tical to the way you use them in Visual Basic 6. You add the control to the Tool-
box, place the control on the form, set design-time property values, and then
write code behind it. No sweat.
Runtime callable
wrapper
.NET component .NET look-alike
component
COM
component
F09km01
COM callable
wrapper
COM component COM look-alike
component
.NET
component
F09km0
Chapter 9 Using Visual Basic 6 with Visual Basic .NET: COM Interop 179
nent from a Visual Basic .NET client application. First, however, you need to
build the Visual Basic 6 server component by following these steps:
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 Transla-
torServer component.
Sub Main()
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.
C0961587x.fm Page 180 Thursday, November 15, 2001 3:33 PM
F09km03
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 compo-
nent 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.
Chapter 9 Using Visual Basic 6 with Visual Basic .NET: COM Interop 181
You have now taken a simple desktop application and made it available to
the world. Hola mundo indeed.
F09km04
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.
F09km05
Chapter 9 Using Visual Basic 6 with Visual Basic .NET: COM Interop 183
F09km06
9. From the File menu, choose Open File, and open Transla-
torServer.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:
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.
Chapter 9 Using Visual Basic 6 with Visual Basic .NET: COM Interop 185
Let’s take the Visual Basic 6 TranslatorServer component located on the com-
panion CD and upgrade it to Visual Basic .NET by performing the following steps:
Let’s change the name of the .NET server so that it doesn’t con-
flict 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 Transla-
torServer.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
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
C0961587x.fm Page 186 Thursday, November 15, 2001 3:33 PM
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:
F09km07
Note Registering a .NET component for COM interop does not nec-
essarily mean that only COM clients can use the component. Instead,
it opens that component up to the world of both COM and .NET. Expos-
ing 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.
C0961587x.fm Page 187 Thursday, November 15, 2001 3:33 PM
Chapter 9 Using Visual Basic 6 with Visual Basic .NET: COM Interop 187
Now you are ready to call the Visual Basic .NET TranslatorServer compo-
nent 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:
to
Note If you want to add features to your upgraded Visual Basic .NET
component, you should add COM attributes to ensure binary compati-
bility. 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.
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.
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 debug-
ger. Pretty cool, eh?
Chapter 9 Using Visual Basic 6 with Visual Basic .NET: COM Interop 189
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 Transla-
tor class to the Web service class, and marking the Translate function as a Web-
Method. We leave this task as an exercise for you to complete.
Chapter 9 Using Visual Basic 6 with Visual Basic .NET: COM Interop 191
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 com-
ments 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.
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:
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
C0961587x.fm Page 193 Thursday, November 15, 2001 3:33 PM
Chapter 9 Using Visual Basic 6 with Visual Basic .NET: COM Interop 193
7. Replace the ComClass attribute in the Translator class with the fol-
lowing attributes:
<Guid(ClassId), ComVisible(True)> _
Public Class Translator
<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
11. Modify the Public Event declaration to implement the interface event
as follows:
When completed, you should have the following class with all the neces-
sary COM attributes defined. You can rebuild the class without breaking com-
patibility 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 meth-
ods 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
<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
Chapter 9 Using Visual Basic 6 with Visual Basic .NET: COM Interop 195
End Function
End Class
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 immedi-
ately to take advantage of the new features. You can start using those new fea-
tures now and continue to use your existing Visual Basic 6 code and
components as needed to accomplish your goals more quickly and effectively.
C1061587x.fm Page 197 Friday, November 16, 2001 8:43 AM
Part III
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 deter-
mine 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 defi-
nitely cases in which it is not possible to make those changes anywhere but in
the upgraded application.
199
C1061587x.fm Page 200 Friday, November 16, 2001 8:43 AM
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 InvalidCast-
Exception. The exception says the following: “Cast from type ‘TextBox’ to type
‘String’ is not valid.”
Here is the code the Upgrade Wizard generates:
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 LateBound-
Copy method looking like this:
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 func-
tionality has been maintained. The next section examines how using default
properties can also lead to additional and more subtle run-time differences.
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
C1061587x.fm Page 203 Friday, November 16, 2001 8:43 AM
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:
It is important to emphasize that if the original sample had not used late
binding, no modifications would have been required.
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
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 guaran-
tees 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:
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 phys-
ical 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 applica-
tions.
The Close method ensures that the database connection is released immedi-
ately. 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.
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 WaitForPending-
Finalizers, 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.
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:
C1061587x.fm Page 207 Friday, November 16, 2001 8:43 AM
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 declara-
tion. 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
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.
C1061587x.fm Page 208 Friday, November 16, 2001 8:43 AM
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:
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 difficul-
ties. 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.
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.
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. Other-
wise, when the specified form is closed, the application will terminate immedi-
ately. 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.
C1061587x.fm Page 210 Friday, November 16, 2001 8:43 AM
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. Con-
trols 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 demon-
strate what happens when raster fonts are upgraded. Figure 10-1 shows a sam-
ple application (FontTest, included on the companion CD) prepared in Visual
Basic 6. Notice how both raster and TrueType fonts are represented.
C1061587x.fm Page 211 Friday, November 16, 2001 8:43 AM
F10km01.tif
As Figure 10-2 shows, the fonts can change dramatically after the applica-
tion is upgraded to Visual Basic .NET with the Upgrade Wizard. This example
demonstrates some of the challenges you will face in your form layouts.
F10km02.tif
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.
C1061587x.fm Page 212 Friday, November 16, 2001 8:43 AM
Bad Constants
In Visual Basic 6, you can create a set of named constants, known as an enu-
meration. Each member of the enumeration is assigned an integer value; in
code, the member name evaluates to its assigned integer. Because enumera-
tions in Visual Basic 6 represent integer values instead of distinct types, the pro-
cess can verify only their underlying values. Unfortunately, this arrangement
allows developers to use constants intended for different purposes interchange-
ably 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 col-
lections 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
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 Mouse-
Pointer 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 follow-
ing results from the Upgrade Wizard illustrate this scenario:
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.
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 OLE-
DragAndDrop 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.
F10km03.tif
Sub Source_MouseDown(...)
The user clicks on the source control.
source.OLEDrag
Sub source_MouseDown(...)
Sub Source_DragLeave(...)
Sub Target_DragEnter(...)
Sub Target_DragOver(...)
F10km05.tif
If Data.GetFormat(vbCFFiles) Then
Dim i As Integer
For i = 1 To Data.Files.Count
Dim file As String
file = Data.Files(i)
Data.Files.Add FileList
Data.SetData , vbCFFiles
AllowedEffects = vbDropEffectCopy
End Sub
(continued)
C1061587x.fm Page 218 Friday, November 16, 2001 8:43 AM
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
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
mCol.Remove vntIndexKey
End Sub
Essentially, what will happen when you upgrade this code is that the New-
Enum 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.
mCol.Remove(vntIndexKey)
End Sub
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 pro-
vided 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.
C1161587x.fm Page 223 Thursday, November 15, 2001 3:46 PM
223
C1161587x.fm Page 224 Thursday, November 15, 2001 3:46 PM
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.
You should make sure that you have turned on all the conditional compi-
lation 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.
Why was the Const declaration changed to a Dim? The reason is that Sys-
tem.Drawing.Color.Red, although it looks like a constant, is not; it is a noncon-
stant expression. This means that, rather than being a constant value, such as
C1161587x.fm Page 225 Thursday, November 15, 2001 3:46 PM
The resulting Visual Basic .NET code after upgrade will be as follows:
Because the declarations for the color objects Red, Lime, and Blue are not con-
stant, 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.
C1161587x.fm Page 226 Thursday, November 15, 2001 3:46 PM
Enum RGBColors
Red = vbRed
Green = vbGreen
Blue = vbBlue
End Enum
how you can change the original upgraded Visual Basic .NET code to use the
System.Drawing.Color values directly:
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 state-
ment, 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 copy-
ing 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
C1161587x.fm Page 228 Thursday, November 15, 2001 3:46 PM
Animals(0) = “Alligator"
Animals(1) = “Monkey"
Animals(2) = “Monkey"
For i = 0 To UBound(Animals)
ctAnimal = ctAnimal + 1
PrevAnimal = Animals(i)
Next
Exit Sub
ShowDetail:
MsgBox PrevAnimal & “ “ & ctAnimal
ctAnimal = 0
Return
End Sub
The Upgrade Wizard upgrades the code as is, inserting UPGRADE_ISSUE com-
ments 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 Show-
Detail 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
C1161587x.fm Page 229 Thursday, November 15, 2001 3:46 PM
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
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
ReadMode:
MsgBox “Read mode"
Exit Sub
WriteMode:
MsgBox “Write mode”
The Upgrade Wizard upgrades the code to use Select Case and GoTo as follows:
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:
Mode = 2 ‘WriteMode
Select Case Mode
Case 1
MsgBox(“Read mode”)
Case 2
MsgBox(“Write mode”)
Case Else
MsgBox(“Unexpected mode”)
End Select
C1161587x.fm Page 231 Thursday, November 15, 2001 3:46 PM
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 partic-
ular 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
Const Deposit = 1
Const Withdrawal = 2
m_AccountBalance = 500
UpdateAccountBalance Deposit, 100
End Sub
Deposit:
m_AccountBalance = m_AccountBalance + Amount
Return
Withdrawal:
m_AccountBalance = m_AccountBalance - Amount
Return
End Sub
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 illus-
trates those relationships.
Print, PrintLine, Write, and WriteLine The Visual Basic .NET Print and Print-
Line functions are equivalent to the Visual Basic 6 Print # function. Which func-
tion 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 fol-
lowing 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";
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.
C1161587x.fm Page 234 Thursday, November 15, 2001 3:46 PM
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.
If you specify the size of the array as part of the declaration, it is con-
sidered 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.
C1161587x.fm Page 235 Thursday, November 15, 2001 3:46 PM
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, Output-
Data, 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
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
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:
For i = 0 To UBound(OutputData)
(continued)
C1161587x.fm Page 236 Thursday, November 15, 2001 3:46 PM
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 dif-
ference 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 follow-
ing 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 mes-
sage 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 con-
tained 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
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.
C1161587x.fm Page 237 Thursday, November 15, 2001 3:46 PM
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 func-
tion 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 get-
ting 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 param-
eter 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
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
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 initializa-
tion values and fixing up the code to test for “<Missing>” yields the following:
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 lan-
guage 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 work-
ing 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 vCus-
tomerMiddleName 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:
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 any-
where in your code—as follows:
Arrays
Visual Basic .NET supports two array types—strongly typed and generic Sys-
tem.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 decla-
ration 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
C1161587x.fm Page 243 Thursday, November 15, 2001 3:46 PM
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 ele-
ments 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 com-
piler 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:
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 con-
stant, 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 Cre-
ateInstance, 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
‘ 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
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 differ-
ence is that you cannot create instances of a structure, and all structure mem-
bers implicitly include the Shared attribute. This means that a structure can
C1161587x.fm Page 246 Thursday, November 15, 2001 3:46 PM
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 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
C1161587x.fm Page 247 Thursday, November 15, 2001 3:46 PM
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 NullReferenceExcep-
tion, “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 Ini-
tialize 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:
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 mem-
ory 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 ver-
sion 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 inte-
ger to the user-defined type containing the version parts. The following Visual
Basic 6 code demonstrates how this can be done.
C1161587x.fm Page 248 Thursday, November 15, 2001 3:46 PM
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.
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:
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.
End Function
End Class
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 state-
ment, a second thread comes along, requesting an amount of $20 and complet-
ing the following statement:
The second thread also sees that the bank balance, $20, is sufficient to cover the
withdrawal request and proceeds to the next line:
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 legiti-
mate leads to unanticipated results—although the customer would need to sub-
mit two withdrawal requests simultaneously to encounter the problem.
SyncLock GetType(GreedyBank)
End SyncLock
If there is any chance that your Visual Basic .NET component or UserCon-
trol will run in a multithreaded environment, we highly recommend using Sync-
Lock 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 lan-
guage 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.
C1161587x.fm Page 252 Thursday, November 15, 2001 3:46 PM
However, because the Visual Basic language and forms package does not rep-
resent every Windows API function available, Visual Basic supports calling Win-
dows 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 cre-
ate 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 Win-
dows 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 Upgrade Wizard will automatically change all the variable declara-
tions 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 exist-
ing statements.
C1161587x.fm Page 253 Thursday, November 15, 2001 3:46 PM
The last parameter has been declared As Any. The following code is an exam-
ple 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
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 func-
tion 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 call-
back 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 differ-
ence 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
Sub Main()
End Sub
End Function
When you use the Visual Basic .NET Upgrade Wizard to upgrade the above
code, you get the following result:
End Sub
TextBuffer = Space(255)
ActualLen = GetWindowText(hwnd, TextBuffer, Len(TextBuffer))
TextBuffer = Left(TextBuffer, ActualLen)
End Function
EnumWindows(AddressOf EnumWindowsCallback, 0)
C1161587x.fm Page 257 Thursday, November 15, 2001 3:46 PM
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 EnumWin-
dowsCallback function is to perform the following steps:
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:
The code will compile and run without error, producing the same results as the
original Visual Basic 6 code.
C1161587x.fm Page 258 Thursday, November 15, 2001 3:46 PM
Marshaling Attributes
A marshaling attribute is an attribute that you attach to a structure mem-
ber 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—parame-
ter, 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 dem-
onstrates how to apply the MarshalAs attribute to your API function dec-
larations:
Imports System.Runtime.InteropServices
C1161587x.fm Page 259 Thursday, November 15, 2001 3:46 PM
For structures passed to API functions, declare the structure using the
StructLayout attribute to ensure compatibility with Visual Basic 6. The Struct-
Layout attribute takes a number of parameters, but the two most significant for
ensuring compatibility are LayoutKind and CharSet. To specify that the struc-
ture 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
Structure MyStructure
Dim MyStringMember As String
Dim MyFixedLenStringMember As String
End Structure
<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
C1161587x.fm Page 260 Thursday, November 15, 2001 3:46 PM
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
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.
C1161587x.fm Page 261 Thursday, November 15, 2001 3:46 PM
Sub Main()
Dim sourceString As String, destString As String
sourceString = “Hello"
destString = “ World"
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. Call-
ing 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:
sourceString = “Hello"
destString = “ World"
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 func-
tionality, or by using the .NET Framework to achieve equivalent behavior. Ulti-
mately, 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 lan-
guage and the .NET Framework.
C1161587x.fm Page 264 Thursday, November 15, 2001 3:46 PM
C1261587x.fm Page 265 Thursday, November 15, 2001 3:51 PM
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 prin-
ciple 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 com-
patible 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.
265
C1261587x.fm Page 266 Thursday, November 15, 2001 3:51 PM
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.
F12km01
F12km02
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 funda-
mental differences should give you a good base to start from when you are try-
ing to understand issues you encounter involving a specific form or control.
In a similar manner, the Visual Basic 6 Width and Height properties can be rep-
resented 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 proper-
ties to set the position and size of controls on the form. The Upgrade
Wizard will automatically map properties such as Left and Top to con-
structor parameters on an object such as Point.
Support Available in a Different Manner You won’t find the Visual Basic 6 prop-
erties 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 Sys-
tem.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 accom-
plish 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.
C1261587x.fm Page 269 Thursday, November 15, 2001 3:51 PM
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.
Technology Differences
Since its inception in 1991, the focus of Visual Basic has been to enable Win-
dows 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
C1261587x.fm Page 270 Thursday, November 15, 2001 3:51 PM
technologies has accumulated over the years. Visual Basic 6 supports all tech-
nologies, 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 sec-
tion 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 Sys-
tem.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 con-
tinue 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.
C1261587x.fm Page 271 Thursday, November 15, 2001 3:51 PM
expose public data and methods do so via COM, DDE is generally not the pri-
mary way that you retrieve public data and invoke public methods on a Win-
dows application. You use COM as a means of communicating with most
Windows applications.
After upgrading your application to Visual Basic .NET, it is highly recom-
mended 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 imple-
menting 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 exam-
ple, 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:
f.Text1.LinkMode = 0 ‘None
f.Text1.LinkTopic = ServerTopic
f.Text1.LinkMode = 2 ‘Manual
f.Text1.LinkExecute ExecCommand
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 opera-
tion. 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 pre-
serves 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.
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
If you use the Upgrade Wizard to upgrade the application to Visual Basic
.NET, the upgraded code will be as follows:
When you run the upgraded Visual Basic .NET application, an error
occurs on the assignment of Command1(1).Top. The error is “Control array ele-
ment 1 doesn’t exist.” It occurs because the Resize event takes place before the
Load event.
C1261587x.fm Page 276 Thursday, November 15, 2001 3:51 PM
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:
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
m_Loaded = 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
Visual Basic .NET does not support a default form instance. If you write
the equivalent code in Visual Basic .NET, as follows:
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:
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:
Now you can write code that accesses the default instance of a form as follows:
Form1.DefInstance.Text
C1261587x.fm Page 278 Thursday, November 15, 2001 3:51 PM
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.”
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 vari-
ous settings under a number of categories. Select the General cate-
gory 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.
C1261587x.fm Page 279 Thursday, November 15, 2001 3:51 PM
F12km03
Figure 12-3. Startup Object list in the Visual Basic .NET project set-
tings 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 appli-
cation 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 ter-
minates 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
C1261587x.fm Page 280 Thursday, November 15, 2001 3:51 PM
If you upgrade the project to Visual Basic .NET, the application will termi-
nate 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())
System.Windows.Forms.Application.Run()
This version loops until you call Application.Exit from somewhere in your pro-
gram. 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 decre-
mented and reaches 0, meaning that no more forms are loaded, call Applica-
tion.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:
Sub AddForm()
FormCount += 1 ‘Equivalent to FormCount = FormCount + 1
End Sub
C1261587x.fm Page 281 Thursday, November 15, 2001 3:51 PM
Sub RemoveForm()
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 proce-
dure and a call to RemoveForm to the Form_Closed event procedure. Here is
the resulting code for Form1:
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 dis-
plays 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:
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.
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 cre-
ate 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, proper-
ties 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.
C1261587x.fm Page 284 Thursday, November 15, 2001 3:51 PM
C1361587x.fm Page 285 Friday, November 16, 2001 8:16 AM
285
C1361587x.fm Page 286 Friday, November 16, 2001 8:16 AM
■ 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
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 con-
trols 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 dis-
play contained controls on separate tabs—an essential feature of the SSTab con-
trol—is lost.
Windowless Controls
The Windows Forms environment provides support only for controls that dis-
play within a standard window. To support windowless controls, a host needs
to support painting the control directly within the form window. One nice fea-
ture is that windowless controls can be made transparent, since the host man-
ages the painting for the control and the form on the same window surface. A
C1361587x.fm Page 288 Friday, November 16, 2001 8:16 AM
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 con-
trols, 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 work-
able, windowed appearance. In the case of Windows Forms, all windowless con-
trols will be asked to create a window and render as a windowed control.
extensibility interfaces. The problem is that Visual Basic .NET supports a com-
pletely 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 Web-
Class 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.
Windows Forms
Ax wrapper
Location
Tag
ActiveX control Visible
BackColor
Caption
Font
F13km01
Ax wrapper
ActiveX control Translation layer
Picture
System.Drawing.Image
F13km02
Take, for example, the following Visual Basic 6 code, which assigns a Pic-
ture 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
ListView1.BackColor = System.Drawing.Color.Red
ListView1.Picture = _
System.Drawing.Image.FromFile(Environ(“WINDIR”) & _
“\Prairie Wind.bmp”)
Picture1.Image = ListView1.Picture
C1361587x.fm Page 292 Friday, November 16, 2001 8:16 AM
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
ImageList1.ListImages.Add( , , _
VB6.ImageToIPictureDisp(System.Drawing.Image.FromFile( _
Environ(“WINDIR”) & “\Zapotec.bmp”)))
■ 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.
C1361587x.fm Page 294 Friday, November 16, 2001 8:16 AM
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
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.Draw-
ing.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 Sys-
tem.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 con-
vert 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.
C1361587x.fm Page 295 Friday, November 16, 2001 8:16 AM
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 excep-
tion 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 com-
ponent 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 con-
trol 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 follow-
ing 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 underly-
ing 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()
C1361587x.fm Page 297 Friday, November 16, 2001 8:16 AM
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 con-
trol’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 Win-
dows 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”
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:
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.
C1361587x.fm Page 299 Friday, November 16, 2001 8:16 AM
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:
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.List-
Items.Count:
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))
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
RetStringArray = s
End Function
Suppose that you are trying to call the RetStringArray function, using the
following Visual Basic .NET code:
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 Sys-
tem.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.
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.
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
C1361587x.fm Page 303 Friday, November 16, 2001 8:16 AM
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 follow-
ing Visual Basic .NET code:
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)
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
C1361587x.fm Page 304 Friday, November 16, 2001 8:16 AM
305
C1461587x.fm Page 306 Friday, November 16, 2001 9:10 AM
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
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 upgrad-
ing, 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!Field-
Name. 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:
C1461587x.fm Page 307 Friday, November 16, 2001 9:10 AM
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
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, sup-
ports 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 implemen-
tation allowed a seamless integration, but it also tied the data binding technol-
ogy 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 Win-
dows 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 con-
trol and the ADO data environment.
Code Code
DAO
Data control
Code Code
Code Code
F14km01
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 mod-
ule with the following code:
Module UpgradeSupport
Friend DAODBEngine_definst As New DAO.DBEngine
End Module
Default instances are a good solution for module methods. If you’re con-
tinuing 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 Begin-
Trans and CommitTrans methods:
DAODBEngine_definst.BeginTrans()
DAODBEngine_definst.CommitTrans()
Similarly, you can add the following DAO methods and properties:
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 meth-
ods, you will need to add a global default instance variable to your application
to access these methods.
C1461587x.fm Page 311 Friday, November 16, 2001 9:10 AM
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 prjADO-
Event (included on the companion CD). This project has a TextBox bound to an
ADO Data control that accesses the Employees table in the Northwind data-
base. 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.
F14km02
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
C1461587x.fm Page 312 Friday, November 16, 2001 9:10 AM
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.EventReasonE-
num, _
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
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.
C1461587x.fm Page 313 Friday, November 16, 2001 9:10 AM
F14km03
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 rdoCon-
nection 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.
F14km04
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. How-
ever, if it is in Form_Load, it will fail with the error shown in Figure 14-5.
F14km05
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.
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
C1461587x.fm Page 315 Friday, November 16, 2001 9:10 AM
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 = ““
With a few exceptions that we will discuss now, the upgraded data environ-
ment 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 exam-
ple, 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.
C1461587x.fm Page 316 Friday, November 16, 2001 9:10 AM
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.
Me.Adodc1.CursorLocation = ADODB.CursorLocationEnum.adUseServer
This rule applies to the ADO data environment, the ADO Data control, and con-
nections created in code.
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 Record-
C1461587x.fm Page 317 Friday, November 16, 2001 9:10 AM
set 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.
■ Inserts a form variable that manages the control binding. The vari-
able 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 auto-
matically. Let’s look at them now.
C1461587x.fm Page 318 Friday, November 16, 2001 9:10 AM
F16km06
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”
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”)
C1461587x.fm Page 320 Friday, November 16, 2001 9:10 AM
VB6_AddADODataBinding()
Remove this line of code and add it to the frmMain_Load event, so that the
Load event looks like the following:
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.
C1461587x.fm Page 321 Friday, November 16, 2001 9:10 AM
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.
C1461587x.fm Page 322 Friday, November 16, 2001 9:10 AM
C1561587x.fm Page 323 Friday, November 16, 2001 8:26 AM
323
C1561587x.fm Page 324 Friday, November 16, 2001 8:26 AM
Visual Basic .NET does not support the OLE Container control. What hap-
pens 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!!”
F15km01
Figure 15-1 OLE Container control with linked object in Visual Basic 6.
C1561587x.fm Page 325 Friday, November 16, 2001 8:26 AM
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.
F15km02
Let’s work on replacing the OLE control and getting our upgraded appli-
cation 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.
C1561587x.fm Page 326 Friday, November 16, 2001 8:26 AM
F15km03
Here are the steps required to replace an OLE control with the Web-
Browser control:
You may want to substitute the filename in this example for the loca-
tion 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.
C1561587x.fm Page 327 Friday, November 16, 2001 8:26 AM
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 Con-
tainer 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 loca-
tion in the following source. Here are the steps:
That’s it! Press F5 and notice that the PictureBox retrieves the photo from
the database, as shown in Figure 15-4.
C1561587x.fm Page 328 Friday, November 16, 2001 8:26 AM
F15km04
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()
F15km05
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. Luck-
ily, 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.
C1561587x.fm Page 330 Friday, November 16, 2001 8:26 AM
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 Sys-
tem.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.
F15km06
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
C1561587x.fm Page 333 Friday, November 16, 2001 8:26 AM
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.
This code adds a TextBox to the form and makes it visible. Figure 15-7 shows
the result.
F15km07
One of the annoying things about the Visual Basic 6 model is that the con-
trols collection has no IntelliSense, so you have to remember the methods,
parameters, and ProgID of the control to add. There is no automatic upgrade
C1561587x.fm Page 334 Friday, November 16, 2001 8:26 AM
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)
Dim c As Control
For Each c In Me.Controls
If c.Name = “myTreeView” Then
Me.Controls.Remove(c)
Exit For
End If
Next
The only way to achieve the same effect in Visual Basic .NET is to imple-
ment your own forms collection. Luckily, this is easier than it sounds. First cre-
ate 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
(continued)
C1561587x.fm Page 336 Friday, November 16, 2001 8:26 AM
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)
That’s all you need to do to implement your forms collection. If your applica-
tion has many forms, you’ll have to put the New and Dispose code in every one.
C1561587x.fm Page 337 Friday, November 16, 2001 8:26 AM
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.
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:
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, espe-
cially 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:
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.
F15km08
If you add the control to a form, you will notice that it has a new property, bit-
mapCollection, and that you can edit this property using a collection editor. Fig-
ure 15-9 shows this editor. It’s that’s simple.
C1561587x.fm Page 341 Friday, November 16, 2001 8:26 AM
F15km09
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.
C1561587x.fm Page 342 Friday, November 16, 2001 8:26 AM
F15km10
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()
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
C1561587x.fm Page 345 Friday, November 16, 2001 8:26 AM
If you run this code, you’ll see that the source string is copied into the destina-
tion 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 num-
ber 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 rede-
sign 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.
C1561587x.fm Page 346 Friday, November 16, 2001 8:26 AM
C1661587x.fm Page 347 Friday, November 16, 2001 8:30 AM
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 informa-
tion here applies to the upgrading of all kinds of COM+ components. From this
chapter you should take away a basic understanding of how COM+ compo-
nents 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.
347
C1661587x.fm Page 348 Friday, November 16, 2001 8:30 AM
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.
.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.
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 develop-
ing 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.
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 ser-
vices it supports. That is where attributes come into play.
<Assembly: AssemblyTitle(““)>
<Assembly: AssemblyDescription(““)>
<Assembly: AssemblyCompany(““)>
<Assembly: AssemblyProduct(““)>
<Assembly: AssemblyCopyright(““)>
C1661587x.fm Page 353 Friday, November 16, 2001 8:30 AM
<Assembly: AssemblyTrademark(““)>
<Assembly: CLSCompliant(True)>
<Assembly: Guid(“17465E40-15E9-4F52-8954-CF931AC54747”)>
‘ 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.*”)>
Unconfigured
Attribute Scope Configured Default Value
Default Value
Let’s see how our sample COM+ class looks with some of these important
attributes:
Imports System.EnterpriseServices
<Transaction(TransactionOption.Required), _
ConstructionEnabled([default]:=“ This is a test”), _
ObjectPooling()> _
End Sub
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 high-
light 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 mod-
ifiable) in the Component Services Microsoft Management Console (MMC).
C1661587x.fm Page 356 Friday, November 16, 2001 8:30 AM
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 nec-
essary for you to explicitly commit or abort the transaction in every circum-
stance. This attribute reduces a great deal of repetition by requiring code only
for the extraordinary cases. The following example shows how simple imple-
menting transactions now becomes with the AutoComplete attribute:
Imports System.EnterpriseServices
<Transaction(TransactionOption.Required)>
Public Class Sample COMPlusClass
Inherits ServicedComponent
‘ 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 set-
tings 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.
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.
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 applica-
tion has a unique signature. That is, after all, why the strong name fea-
ture 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.
C1661587x.fm Page 358 Friday, November 16, 2001 8:30 AM
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 appro-
priate 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.
F16km02
F16km01
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 appli-
cations. 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:
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.
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
(continued)
C1661587x.fm Page 362 Friday, November 16, 2001 8:30 AM
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 unex-
pected and potentially undesirable behavior.
Using the previous example, let’s walk through the steps necessary to get
the class working:
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
conn.Open(connStr)
conn.Execute(sqlCmd)
Notice that the DoTasks method does not explicitly commit or abort the trans-
action 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.
C1661587x.fm Page 364 Friday, November 16, 2001 8:30 AM
Conclusion
Upgrading your COM+ application to Visual Basic .NET is not a totally auto-
matic process. The Upgrade Wizard does the hard work, upgrading your appli-
cation 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 Ser-
vices” in the Visual Basic .NET Help.
C1761587x.fm Page 365 Friday, November 16, 2001 9:22 AM
Upgrading VB Application
Wizard Projects
This chapter looks specifically at upgrading projects created with the VB Appli-
cation 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.
365
C1761587x.fm Page 366 Friday, November 16, 2001 9:22 AM
F17km01
Figure 17-1 Starting the VB Application Wizard from the New Project
dialog box.
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 frmDocu-
ment; 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 work-
ing 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
C1761587x.fm Page 368 Friday, November 16, 2001 9:22 AM
upgrades to
There are two ways to fix the compile error. The first and easiest is simply
to remove the App.Revision part of the line:
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:
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:
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.
C1761587x.fm Page 369 Friday, November 16, 2001 9:22 AM
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 Sys-
tem 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
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 deter-
mine whether the file Msinfo32.exe exists, the warning can be ignored, since it
applies only to directories.
C1761587x.fm Page 370 Friday, November 16, 2001 9:22 AM
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.
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
After upgrading, this code causes an error because it uses App.Revision to gen-
erate 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)
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.
C1761587x.fm Page 372 Friday, November 16, 2001 9:22 AM
is upgraded to
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:
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.
form is determined only at run time; therefore, it is common to use the Active-
Form 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:
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:
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 “GetActiveMdi-
Child”. 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:
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.
This code upgrades to the following. Not shown here is that Form_Unload is
renamed frmMain_Closed during the upgrade.
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).
You can fix the problem by adding two helper methods to Module1.
These helper methods get and set the Clipboard text:
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()
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
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:
End Sub
C1761587x.fm Page 378 Friday, November 16, 2001 9:22 AM
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
If this is an MDI project, you need to adjust the behavior of the frm-
Browser 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 prop-
erty. Data binding by using a data class cannot be upgraded for two reasons:
C1761587x.fm Page 379 Friday, November 16, 2001 9:22 AM
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:
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
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.
C1761587x.fm Page 382 Friday, November 16, 2001 9:22 AM
C1861587x.fm Page 383 Friday, November 16, 2001 8:59 AM
Part IV
385
C1861587x.fm Page 386 Friday, November 16, 2001 8:59 AM
F18km01
Figure 18-1 Dashboard opens the registry editing form and the snappy
graphics form.
C1861587x.fm Page 387 Friday, November 16, 2001 8:59 AM
After this code runs, the filePathArray variable is filled with an array of file
paths, such as
C:\DynamicShell\bin\GraphicsFeatures.dll
C1861587x.fm Page 388 Friday, November 16, 2001 8:59 AM
MsgBox(System.IO.Path.GetDirectoryName( _
“C:\DynamicShell\bin\GraphicsFeatures.dll”))
MsgBox(System.IO.Path.GetFileName( _
“C:\DynamicShell\bin\GraphicsFeatures.dll”))
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.
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.
message box. This sample is on the companion CD, and it is called FileReadAnd-
Write.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()
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 meth-
ods. 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.
F18km02
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:
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 Note-
pad and change the setting to something like
“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 appli-
cation 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 partic-
ular property dynamic, you use the (Dynamic Properties) property in the Prop-
erty Browser; you use the (Advanced) builder to select the properties that you
want to make dynamic, as shown in Figure 18-3.
C1861587x.fm Page 392 Friday, November 16, 2001 8:59 AM
F18km03
F18km04
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
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.
C1861587x.fm Page 394 Friday, November 16, 2001 8:59 AM
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 LocalMa-
chine, 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 ver-
sion 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.
C1861587x.fm Page 395 Friday, November 16, 2001 8:59 AM
F18km05
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.Thread-
ing.Thread.CurrentThread.Sleep method waits 5 milliseconds before continu-
ing 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.
C1861587x.fm Page 396 Friday, November 16, 2001 8:59 AM
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.
F18km06
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 differ-
ent 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.”
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.
F18km07
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))
(continued)
C1861587x.fm Page 398 Friday, November 16, 2001 8:59 AM
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 graph-
ics object, it is important to destroy it using the Dispose method:
g.Dispose()
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:
Figure 18-8 shows a form with standard controls on the left and the same
forms with Windows XP controls on the right.
Default controls
F18km08
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 (refer-
ring 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
C1861587x.fm Page 401 Friday, November 16, 2001 8:59 AM
Conclusion
This chapter has looked at several ways you can extend your upgraded appli-
cations. 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 applica-
tions. 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 distrib-
uted applications.
C1861587x.fm Page 402 Friday, November 16, 2001 8:59 AM
C1961587x.fm Page 403 Friday, November 16, 2001 9:02 AM
403
C1961587x.fm Page 404 Friday, November 16, 2001 9:02 AM
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 appli-
cations in which the control is used will share the control from the same loca-
tion. 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 appli-
cation 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
C1961587x.fm Page 405 Friday, November 16, 2001 9:02 AM
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 Win-
dows 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 appli-
cation 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 Rich-
Text, 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.
StartTime = Timer
Do While Timer - StartTime < 0.01
Loop
ProgressBar1.Value = i
ProgressBar1.Refresh
Next
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.
F19km01
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, fol-
lowing the steps outlined earlier.
Copy the extended property settings Let’s start by copying the upgraded
extended property settings found in the upgraded form file. To see these prop-
erty 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.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 Note-
pad 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 repre-
sents 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 infor-
mation 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 prop-
erty 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 set-
tings 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
C1961587x.fm Page 409 Friday, November 16, 2001 9:02 AM
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.
After you’ve copied the settings, Notepad should contain the following:
(continued)
C1961587x.fm Page 410 Friday, November 16, 2001 9:02 AM
Me.ProgressBar1.Appearance = 1
Me.ProgressBar1.Min = 1
Me.ProgressBar1.Max = 200
Me.ProgressBar1.Appearance = 1
Me.ProgressBar1.Min = 1
Me.ProgressBar1.Max = 200
Using IntelliSense, you can quickly get a feel for what properties the Win-
dows 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
C1961587x.fm Page 411 Friday, November 16, 2001 9:02 AM
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:
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 Win-
dows Forms control.
The ActiveX controls provided with Visual Basic 6 do not allow you to cre-
ate 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 con-
trol, 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 prop-
erty 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:
TreeView1.ImageList = ImageList1
TreeView1.Nodes.Add(nodeParent)
Conclusion
Although the Upgrade Wizard makes life easier by having your upgraded Visual
Basic 6 applications use the same ActiveX controls, keeping the ActiveX con-
trols 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
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:
417
418 Part IV Techniques for Adding Value
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 inte-
gration 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 underly-
ing 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.
F20km01
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.
Chapter 20 Moving from ADO to ADO.NET 419
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 Record-
sets 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 min-
imal support.
DataSets
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 DataRela-
tions. 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 relation-
ship to the data-specific ADO.NET classes.
DataSet
Tables
Table
Columns
Column
Constraints
Constraint
Rows
Row
Relations
Relation
F20km02
F20km03
code is used mainly for presentation or data binding and can readily
be replaced with managed code.
ADO and XML Web Services Option 1 ADO and XML Web Services Option 2
Recordset Recordset
XML
Logic Logic DataTable
DataTable DataTable
F20km04
Figure 20-4 Options for implementing XML Web services with Recordsets.
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
Chapter 20 Moving from ADO to ADO.NET 427
Recordsets
Not all Recordsets are created equal. Depending on the settings you use, they
can take several different forms, leading to various consequences for applica-
tion 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 (discon-
nected, forward-only “firehose,” and so on) and so that the classes can be opti-
mized 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 imple-
mentation 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 fire-
hose 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)
(continued)
428 Part IV Techniques for Adding Value
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
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
Set rs = conn.Execute(statement)
Set VB6DynamicRSExample = rs
Set rs = Nothing
End Function
Chapter 20 Moving from ADO to ADO.NET 429
adapter.Fill(ds)
Return ds
End Function
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 DataViews
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)
(continued)
430 Part IV Techniques for Adding Value
Dim i As Integer
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.
Notice in this example that instead of passing the entire DataSet to the Data-
Grid, we’ve chosen to bind only a single table. This is often necessary with con-
trols 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.
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 nec-
essary step, without which the control would not be bound to the speci-
fied data.
432 Part IV Techniques for Adding Value
These two guidelines seem simple, yet their importance cannot be over-
stated. Connections to databases represent real physical resources on your
machine, and hogging those resources will inevitably result in poor perfor-
mance. 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 auto-
matically 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 solu-
tion). 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).
Chapter 20 Moving from ADO to ADO.NET 433
Function DBTest()
Dim conn As New SqlConnection(connStr)
Dim cmd As New SqlCommand(“Select * From Customers", conn)
While dr.Read()
‘ Do some processing
End While
While dr.Read()
‘ Do some other processing
End While
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 signif-
icant performance gains over pure ADO.
C2161587x.fm Page 435 Friday, November 16, 2001 9:08 AM
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 corner-
stone of the entire Microsoft .NET initiative. The .NET Framework is designed
from the ground up to support new first-class distributed technologies. Know-
ing 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 data-
base Northwind.mdb (included on the companion CD) as a basis for
demonstrating various forms of data access.
435
C2161587x.fm Page 436 Friday, November 16, 2001 9:08 AM
In-process
Out-of-process
Remote process
Time
F21km01
So while we can call into local methods without any concern for over-
head, and even out-of-process overhead isn’t too bad, we need to be extra care-
ful 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 sin-
gle method to set all the properties at once.
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 prop-
erty access involves a separate network operation, whereas approach 2 is
“chunky” because it accomplishes its task with a single network operation.
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 appli-
cations. The more efficient your design, the better your application will perform
and the better it will scale up in an enterprise environment.
C2161587x.fm Page 439 Friday, November 16, 2001 9:08 AM
Controls
Utility classes
Business logic
Utility classes
SQL
F21km03
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 meth-
ods 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 appli-
cation 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 fol-
lowing excerpt from the SimpleService.asmx.vb file (part of the SimpleWebSer-
vice sample project) demonstrates the WebMethod attribute in action:
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 dem-
onstrated 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 val-
ues 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 cus-
tom classes you have two options. You can implement the ISerializable inter-
face 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.
C2161587x.fm Page 443 Friday, November 16, 2001 9:08 AM
F21km04
F21km05
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:
Pretty neat, huh? Now we need to look at how you can use this method in an
application.
You can see the results in Figure 21-6. This sample project helps demon-
strate how XML Web services simplify the development of distributed applica-
tions. XML Web Services are a powerful tool that enable the development of
fundamentally different kinds of applications, due to its platform- and lan-
guage-independent nature.
C2161587x.fm Page 446 Friday, November 16, 2001 9:08 AM
F21km06
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….
COM
object ADO Recordset
Recordset
in XML
WebMethod WebMethod
call return
Recordset
Button Click
in XML
ADO Recordset
F21km07
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 Win-
dows 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 represen-
tation 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.
C2161587x.fm Page 449 Friday, November 16, 2001 9:08 AM
Imports ADODB
Imports System.Web.Services
<WebService(Namespace:="http://tempuri.org/”)> _
Public Class NorthwindDatabase
Inherits System.Web.Services.WebService
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
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 seri-
alization 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.
C2161587x.fm Page 450 Friday, November 16, 2001 9:08 AM
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 accept-
able 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
xml = nwdb.Query(QueryComboBox.Text)
If xml Is Nothing Then
QueryDataGrid.DataSource = Nothing
MsgBox(“The query failed!”)
Exit Sub
End If
C2161587x.fm Page 451 Friday, November 16, 2001 9:08 AM
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 but-
ton. Another neat feature of this application is that the ComboBox is editable at
run time, so you can further customize the query.
F21km08
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.
C2161587x.fm Page 452 Friday, November 16, 2001 9:08 AM
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 min-
imal 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 appli-
cations 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
C2161587x.fm Page 453 Friday, November 16, 2001 9:08 AM
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.
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:
Process Process
starts starts
Create channel
and register
SimpleClass type
Channel opened
SimpleClass
registered
SimpleClass Request
instantiated SimpleClass proxy
Loop
Call GetDateTime
SimpleClass
method
proxy
Terminate proxy
Process
ends Channel closed
SimpleClass
unregistered
Process
ends
F21km09
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.
C2161587x.fm Page 455 Friday, November 16, 2001 9:08 AM
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Tcp
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
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
C2161587x.fm Page 456 Friday, November 16, 2001 9:08 AM
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.
F21km10
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 applica-
tion. This observation leads us to a discussion of the best architecture for an
application that uses remoting.
Interfaces assembly
INorthwind
Project reference
INorthwind INorthwind
Database Proxy
F21km11
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
C2161587x.fm Page 458 Friday, November 16, 2001 9:08 AM
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 defini-
tion, 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
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Tcp
Return True
End Function
End Class
Imports System.EnterpriseServices
Imports System.Reflection
<Assembly: AssemblyKeyFile(“SoapServicesTest.snk”)>
<Assembly: ApplicationName(“COM+ SOAP Services Test”)>
<Assembly: ApplicationActivation(ActivationOption.Server,
SoapVRoot:="SOAPServicesTest”)>
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 Win-
dows XP and the Windows .NET server product family.
Conclusion
At this point you should have at least a basic understanding of how to imple-
ment the core distributed technologies and where the relative strengths of
remoting and XML Web services lie. Traditionally, developing distributed appli-
cations 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 proto-
col 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.
Z01A61587x.fm Page 465 Friday, November 16, 2001 9:38 AM
Part V
Appendixes
A Object Mapping Reference 467
B Function Mapping Reference 515
Z01A61587x.fm Page 466 Friday, November 16, 2001 9:38 AM
Z01A61587x.fm Page 467 Friday, November 16, 2001 9:38 AM
Appendix A
App
No direct equivalent to upgrade to. Individual properties and methods are
upgraded as shown in the table.
467
Z01A61587x.fm Page 468 Friday, November 16, 2001 9:38 AM
CheckBox
Upgrades to System.Windows.Form.CheckBox.
ComboBox
Upgrades to System.Windows.Forms.ComboBox.
CommandButton
Upgrades to System.Windows.Forms.Button.
DirListBox
Upgrades to Microsoft.VisualBasic.Compatibility.VB6.DirListBox.
DriveListBox
Upgrades to Microsoft.VisualBasic.Compatibility.VB6.DriveListBox.
FileListBox
Upgrades to Microsoft.VisualBasic.Compatibility.VB6.FileListBox.
Form
Upgrades to System.Windows.Forms.Form.
Frame
Upgrades to (1) System.Windows.Forms.Panel, or
(2) System.Windows.Forms.GroupBox if control has a border.
HScrollBar
Upgrades to System.Windows.Forms.HScrollBar.
Image
Upgrades to System.Windows.Forms.PictureBox.
Label
Upgrades to System.Windows.Forms.Label.
Line
Upgrades to System.Windows.Forms.Label if horizontal or vertical line. Diago-
nal lines are not upgraded.
ListBox
Upgrades to (1) System.Windows.Forms.ListBox, or
(2) System.Windows.Forms.CheckedListBox if Style =1.
MDIForm
Upgrades to System.Windows.Forms.Form, with IsMDIContainer property set to
True.
Menu
Upgrades to System.Windows.Forms.MenuItem. A MainMenu control is also
automatically added to the form.
OptionButton
Upgrades to System.Windows.Forms.RadioButton.
PictureBox
Upgrades to (1) System.Windows.Forms.PictureBox, or
(2) System.Windows.Forms.Panel if the control has child controls.
Shape
Upgrades to System.Windows.Forms.Label if shape is a rectangle or square.
TextBox
Upgrades to System.Windows.Forms.TextBox.
Timer
Upgrades to System.Windows.Forms.Timer.
VScrollBar
Upgrades to System.Windows.Forms.VScrollBar
Appendix B
515
Z02B61587x.fm Page 516 Friday, November 16, 2001 9:43 AM
521
Index
Symbols and Numbers installing with Visual Basic 6 Setup, 80
<> (angle brackets), 352 mappings for, 412–13
+= (assignment shortcut), 223 no direct .NET replacements for, 286
-= (assignment shortcut), 223 outside Visual Basic environments, 286
. and .. directories, 154 placing on a Visual Basic .NET form, 32
= (operator, testing for Null and Empty), 73 removing dynamically at run time, 334–35
( ) (parentheses) replacing with Windows Forms controls, 403, 405–6
with function calls, 92 subobjects in, 293
indicating parameters not to be set, 299 support for, 285–86
required on subroutine calls, 37 Toolbox references to, 31–32
95 percent automated upgrade, 9–10 types not supported in the Windows Forms
100 percent .NET compatibility, 404 environment, 286–87
100 percent upgrade, 9 upgrade strategy for, 286
upgrading, 137–40
using from .NET, 298–303
A in Visual Basic .NET, 177
About box form, 369 ActiveX Data Objects. See ADO
About menu item, 371 ActiveX designers, 289
Abs function, 141–42 ActiveX DHTML page projects, 48
abstract base class, 36 ActiveX DLL project types, 122, 365, 366
abstraction, 66–69 ActiveX DLLs, 249
Activator class, 455 ActiveX document projects, 48
active form, 373 ActiveX documents, 170
ActiveConnection properties, 425–26 ActiveX EXE project, 272
ActiveForm object, 372–73 ActiveX EXE project types, 122
ActiveMDIChild, 373–74 ActiveX libraries, installing, 80
ActiveX ADO Data controls. See ADO Data control ActiveX MousePointer property, 295–96
ActiveX components ActiveX MSBind object, 137
designing and testing for target environments, 21 ActiveX .NET control, 289
reusing in Visual Basic .NET applications, 121 ActiveX object types, conversion functions for common,
threading of, 29 295
types not supported in the Windows Forms ActiveX portion of an ActiveX .NET control, 289
environment, 286–87 ActiveX ProgressBar control, 406
using from .NET, 298–303 ActiveX types, unique identifiers associated with, 28
ActiveX control library, 412 ActiveX UpDown control. See UpDown control
ActiveX control project type, 122 ActiveX vs. .NET framework, 19–29
ActiveX controls ActiveX wrapper. See Ax wrapper
adding dynamically at run time, 334 “Add a delegate for AddressOf” upgrade issue, 163
compared to Windows Forms controls, 413–16 add-in components for Visual Basic 6, 288–89
copying design-time property settings for, 407–10 Add method
creating in Visual Basic 6, 20 calling for a ComboBox or ListBox, 201
deleting from forms, 410 calling the ToString method, 202
exposing property types to .NET types, 291 Add Project from File menu in Visual Basic 6, 94
extended property settings supported by Visual Basic Add Reference dialog box, 111
6, 408–9 Add Web Reference dialog box, 112
extra DLLs generated for, 405 AddForm function, 280–81
global use of, 404 AddHandler keyword, 36
hosting, 173, 286–89
521
Z03I61587x.fm Page 522 Tuesday, November 20, 2001 3:16 PM
collections 525
526 compatibility
compatibility constants
with COM clients, 194 declaring as colors, 170
reasons for breaking, 6–8 defining to represent a property, 224
compatibility library, 119 proper usage of, 67
functions provided by, 142 upgrading, 161
MSBind .NET class in, 137 in Visual Basic 6, 66
support for control arrays, 135 construction
Compensating Resource Manager, 349 not supported by COM objects in Visual Basic 6, 76
compilation, background, 18 in Visual Basic .NET, 75–76
compilation errors ConstructionEnabled attribute, 353, 355
flagging in real time, 16 constructors
reporting of, 109 calling, 76
compile errors, upgrade issues warning of, 152 defining for user-defined types, 246
Compile tab of a Project Properties dialog box, 182 container controls, 287
compile-time validation Context parameter of MsgBox and InputBox, 167
lost by reliance on the Variant type, 64, 65 context-sensitive Help, reimplementing in Visual Basic
provided by early binding, 207 .NET, 371, 372
compiler context sharing, enabling between COM+ and .NET
background, 16 Framework classes, 351
binding of objects and methods, 69 control anchoring, 14, 394
errors compared to run-time errors, 296 control array classes, 135
upgrading Visual Basic 6 settings, 127 “Control array element 1 doesn’t exist” error, 275
compiling Visual Basic .NET compared to Visual Basic control arrays, 135–36, 318
6, 106 control flow, 227–32
complete upgrade to Visual Basic .NET, 47 “Control was not upgraded” design error, 167
complex binding, 316 controls
complex expressions, simplifying, 114–15 adding dynamically at run time, 333
COMPlusClass project, 358 adding to a Windows Forms project, 104–5
Component Services MMC, 359 adjusting the size of, 394
component tray, 85 benefits of upgrading ActiveX to Windows Forms,
ComponentAccessControl attribute, 353 404–5
componentization of an application, 439 binding to a database image, 327–28
components binding to ADO objects, 316
attaching attributes to, 352–53 displaying all associated properties and methods,
no direct replacement of Visual Basic 6 by .NET, 190 410–11
sharing in closed environments, 21 explicit declaration and allocation of, 133
upgrading one at a time, 175–76 global use of, 404
COMTIIntrinsics attribute, 353 indexing, 334
conditional compilation constant settings, 123–24 with no subobjects, 414
conditional compilation statements normal compared to Windows XP, 400
maintaining after an upgrade, 224 removing by name in Windows Forms, 334
upgrading, 171 removing dynamically at run time, 333
Configuration Properties folder, 124 replacing ActiveX with Windows Forms, 405–6
conn reference, setting to Nothing in Visual Basic 6 vs. resolving the name of, 164
.NET, 205 returning complex structures, 414
Connection object, 418, 425–26 setting properties for, 104–5
connection pool support for OLE drag and drop in Visual Basic 6, 214
getting connections from, 432 upgrading manually, 406–12
increasing churn in, 433 controls collection, 333–35
connections, 432–33. See also database connection Controls.Add, 32
“Constant expression is required” compiler error, 225 conversion, performing a narrowing, 89
constant values, defining, 226, 227 conversion functions
“Constant was not upgraded” upgrade issue, 161 adding after an upgrade, 141
for common ActiveX object types, 295
Z03I61587x.fm Page 527 Tuesday, November 20, 2001 3:16 PM
conversion helper functions, listing of, 294–95 data binding, 69, 307, 430–31. See also ADO data
Cooper, Alan, 265 binding; DAO data binding; early binding; late
copy function, replacing an LSet statement, 248 binding; RDO data binding
CopyMemory Windows API function, 261–63 adding and removing, 138–39
CopyTo method, 161 with ADO.NET data sources, 422
CorDbg, 113–14 changing DAO and RDO to ADO, 147
Cos function, 141–42 with code, 378
“Could not find file” global warning, 169 to a control, 430
“Could not load referenced component” global with a data class, 378–79
warning, 169 forcing, 431
“Couldn’t resolve default property of object” upgrade with Web Forms controls, 431
warning, 166 data binding properties, setting at run time, 318–20
Courier font, 210 Data control, 128, 134
Courier New font, 210 data environments, 315
Create Symbolic Debug Info check box, 182 data forms, 367
CreateInstance method, 244–45, 389 data object model of ADO.NET, 417
CreateSubKey method, 393 data persistence, .NET Framework vs. ActiveX, 146
cross-language interoperability, 15–16 data repositories, creating different forms of, 428–29
cryptographic key pair, 357 data types, 238–49
CrystalReports package, 147 standardizing across languages, 6
CShort keyword, 36 upgrading based on size, 234
CtlRefresh method, 411 upgrading to fit, 143–44
CType keyword, 36 DataAdapter, 420, 428
Currency data type, 8, 223 database class in the Architecting for Remoting sample,
current class instance, 36 457–59
CurrentConfig property of the Registry object, 394 database connection, 313–14. See also connections
CurrentUser property, 393 database images, binding controls to, 327–28
CurrentX statement, 271 DataBind, calling on the Web Form DataGrid, 431
cursor, 316 DataEnvironment class, 147
Cursor object, assigning to a numeric type, 296 DataEnvironment designer, 147, 289
CursorToIPicture conversion function, 295 DataField property
custom mouse pointers, 163 controlling simple binding, 317
custom property editors, 342 of a TextBox control, 137, 138
custom tokens, 109–10 DataFormat property, controlling simple binding, 317
Customize Toolbox component picker, 326 DataGrid control
CVErr function, 160 binding an ADO Recordset to, 422, 423
mapping to Visual Basic .NET, 413
DataMember property, controlling simple binding, 317
D DataReader class, 419
DAO (Data Access Objects), 25–26, 63, 310
DataReader object, 427, 428
DAO data binding, 63, 307, 308
DataRelation class, 419
DAO Data control, 308
DataReport designer, 147
DAO-related elements, 147
DataReport Writer, 289
DAODBEngine_definst variable, 310–11
DataSet class, 419, 420
dashboard in DynamicApp, 386
DataSets, 419, 428, 429, 431
data
DataSource interface, 146
creating multiple views of, 429–30
DataSource property
synchronizing access to, 251
controlling simple binding, 317
data access
not supported by Visual Basic .NET controls, 378, 379
code, 310–15
of the QueryDataGrid, 450
forms, 378–79
of a TextBox control, 137, 138
models, 25
DataSourceBehavior property, 146
technologies, 63, 305–21
DataTable class, 419
in Visual Basic, 306–8
Data Access Objects. See DAO
Z03I61587x.fm Page 528 Tuesday, November 20, 2001 3:16 PM
528 DataTables
530 EWIs
538 pictures
property pages, 170, 340–42 Recordsets. See also cursor disconnected Recordsets;
property settings, copying from the Visual Basic 6 form firehose Recordsets; forward-only Recordsets
file, 408–10 accessing fields of, 306
“property was not upgraded” design error, 167 binding to .NET controls, 422–23
“Property was not upgraded” upgrade issue, 162 converting to DataTables or DataSets, 423–24
Protected Friend keyword, 37 deserializing from XML, 450
Protected keyword, 37 forms of, 427–29
protocols, supported by remoting, 452 passing between tiers, 425
providers, 421 serializing to and from XML, 423, 424–25
proxy class, 445 with XML Web services, 447–51
PSet statement, 271 red labels
public class, creating an interface for every, 82 on forms, 134
public noncreatable class attribute, 146 placeholder replacing controls, 167
public PMEs, 289 replacing an OLE control during an upgrade, 325
public/private key pair, 357 redesign problems, solutions for common, 323–45
Put statement, mapping, 232 ReDim statement, 164, 173
reference counting in COM, 203–4
referenced projects, compiling, 170
Q references, 111
Query method in NorthwindDatabase, 448, 449
to ActiveX controls in a Toolbox, 31–32
QueryUnload event, 162
not finding during an upgrade, 169
queued components, 349
removing to ActiveX control libraries, 412
References dialog box, 95
R References list, viewing in Visual Basic 6, 302
random-access binary files, header information in, 234 reflection, 388
random-access files, reading fixed-length strings from, Refresh method for the Windows Forms ProgressBar
237 control, 411
random number generator, 81 RegCreateKey API, 392
raster fonts RegDeleteKey API, 392
no longer supported, 168, 209 regions of forms, 396
upgrading, 210, 211 Register For COM Interop attribute, 186
RCW (runtime callable wrapper), 177, 178 registration
RDO (Remote Data Objects), accessing module for COM+ applications, 358–59
methods, 310 of a .NET component for COM interop, 186
RDO connection error, 314 registry, accessing, 392–94
RDO data binding, 307 registry editing form
not supported in Visual Basic .NET, 63 in DynamicApp, 386
upgrading, 308 in the Registry Features project, 392–94
RDO DataControl, 288 registry file in Visual Basic .NET installation, 169
RDO-related elements, 147 registry key, 393
rdoConnection, opening, 313 RegistryFeatures project, 392
RDOrdoEngine_definst variable, 310 RegSvcs.exe utility, 359
Read method, 427 Remote Data Objects. See RDO
ReadFromFile method, 324 remote objects, calling, 456
reading the contents of a file, 390 remote process method invocation, calling overhead,
ReadOnly keyword, 37 437
ReadProperties event, 146 remoting, 440, 452–61
“real” inheritance, 10, 11 architecture for, 456–61
reconstructed Recordsets, 447 COM+ applications and, 461
Recordset filter, setting to an empty string, 314 example of, 453–56
Recordset object, 309 features of, 452
Recordset type Remove Breakpoint from the shortcut menu, 110
changing in Visual Basic 6, 26 Remove button, disabling if no item is selected, 108
RemoveButton_Click event, 105
Z03I61587x.fm Page 540 Tuesday, November 20, 2001 3:16 PM
SOAP 541
Toolbox 543
Toolbox U
Visual Basic 6 vs. Visual Basic .NET, 31–32 UInt32 color value, 294
in the Visual Basic .NET IDE, 103, 104 UInt32 type, 293
ToOle function, 294 “Unable to determine which constant to upgrade to”
ToOle method, 226 upgrade issue, 161
top level scope, moving variable declarations to, 41 “Uncomment and change the following line to return
Top property, setting with Location, 267–68 the collection enumerator”, 163
ToString method, 201–3 underlying data types, 67–68
Trace class, 112–13 underlying integer values for constants, 66–67
Transaction attribute, 354, 355, 360–63 undetected errors, 172–74
transactions, explicitly committing or aborting, 363 undocumented functions, supported in Visual Basic 6,
TranslatorClient sample application, 178, 179 343
TranslatorServer project Unicode keyword, 36
calling the .NET version, 187 unified debugger, 16
creating and running, 178–80 unified framework, 6
upgrading to Visual Basic .NET, 185–86 universal forms package, 35
transparent controls, not supported by Windows Forms, Unload event, 162
129 “Unload <object> was not upgraded” upgrade issue,
transparent labels, 288 162
TreeView control UnloadMode for QueryUnload, 162
adding a single parent and child node to, 414–15 Unlock statement, mapping, 232
Anchor property of, 394 unmanaged code, 46
applying the Ax wrapper to, 293 Unmanaged Code Debugging option, 183
mapping to Visual Basic .NET, 413 unqualified parameters, upgrading and qualifying, 144
structure of, 414 unqualified variable declarations, 143
subobjects exposed by, 293 Update Controls option, 50
troubleshooting link in the upgrade report, 155, 157 UpDown control, 139–40
TrueType fonts, 209, 210, 211 mapping to Visual Basic .NET, 413
Try keyword, 37 upgrading to .NET Framework UpDown control, 288
Try...Catch error trapping, 312–13 upgrade, performing a two-pass, 160
Try...Catch statement, 223 UPGRADE_ comments for code-behind forms, 145
Try...Catch...End Try exception-handling block, 11 upgrade form file, 407
twips, 329 upgrade issues, 152
two-pass upgrade, 160 across forms, controls, and components, 267–74
type aliasing, 301–2 generated by the Upgrade Wizard, 142
type-casting function, 40 listing of, 160–63
type checking, 13 upgrade log, loading into Excel, 157–58
Type class, returning, 36 upgrade notes, 155, 170–71
type conversion functions, 41 upgrade options, 45–47
type conversions, exceptions requiring, 295–96 upgrade plan, developing, 58
type declaration, 36, 74 upgrade problems, 199–222
type identity, 25–28 upgrade process, 9, 10, 79–84
Type keyword, 153 upgrade report, 93–94, 134–35, 155–57
type mismatch assignments in upgraded code, 295–96 generating for problem identification, 160
type names, fully qualifying, 26 parts of, 155, 156–57
Type...End Type, 223, 245 produced by the Upgrade Wizard, 48
types, 238–49, 388 upgrade settings in, 155, 157
converting between in Visual Basic .NET code, 141 Upgrade ToDos, 153–54, 163
listing all in the current application, 388–89 upgrade warnings, 71, 164–67
qualifying ambiguous, 27 upgrade with interoperability option, 47
Z03I61587x.fm Page 544 Tuesday, November 20, 2001 3:16 PM
X Z
zero-bound lower dimension for arrays, 39–40
XA interoperability, 349
XCopy deployment, 400–401
Z03I61587x.fm Page 548 Tuesday, November 20, 2001 3:16 PM
IMPORTANT-READ CAREFULLY: This End-User License Agreement (“EULA”) is a legal
agreement between you (either an individual or a single entity) and Microsoft Corporation for the
Microsoft software that accompanies this EULA, which includes computer software and may
include associated media, printed materials, “online” or electronic documentation, and Internet-
based services (“Software”). An amendment or addendum to this EULA may accompany the
Software. YOU AGREE TO BE BOUND BY THE TERMS OF THIS EULA BY INSTALLING,
COPYING, OR OTHERWISE USING THE SOFTWARE. IF YOU DO NOT AGREE, DO NOT
INSTALL, COPY, OR USE THE SOFTWARE.
1. GRANT OF LICENSE. Microsoft grants you the following rights provided that you
comply with all terms and conditions of this EULA:
1.1 Microsoft grants you a personal, nonexclusive, royalty-free license to install and
use the Software for design, development, and testing purposes. You may install and use the
Software on an unlimited number of computers so long as you are the only individual using the
Software.
1.2 Solely with respect to the sample source code contained in the Software (“MS
Samples”), Microsoft also grants you a limited, nonexclusive, royalty-free license to: (a) use and
modify the source code version of those portions of the MS Samples for the sole purposes of
designing, developing, and testing your software product(s), and (b) to reproduce and distribute
the MS Samples, along with any modifications thereof, in object and/or source code form. For
applicable redistribution requirements for MS Samples, see Section 2 below.
2. DESCRIPTION OF OTHER RIGHTS AND LIMITATIONS
2.1 If you choose to redistribute the MS Samples, you agree: (i) to distribute the MS
Samples only as a part of a software application product developed by you (“Licensee
Software”); (ii) not to use Microsoft’s name, logo, or trademarks to market the Licensee Software;
(iii) to display your own valid copyright notice which shall be sufficient to protect Microsoft’s
copyright in the MS Samples; (iv) to indemnify, hold harmless, and defend Microsoft from and
against any claims or lawsuits, including attorney’s fees, that arise or result from the use or
distribution of the Licensee Software; (v) not to permit further distribution of the MS Samples by
your end users; and (vi) that Microsoft reserves all rights not expressly granted.
2.2 If you use the MS Samples, then the following also applies. Your license rights to
the MS Samples are conditioned upon your (i) not incorporating Identified Software into, or
combining Identified Software with, the MS Samples or a derivative work thereof; and (ii) not
distributing Identified Software in conjunction with the MS Samples or a derivative work thereof.
“Identified Software” means software which is licensed pursuant to terms that directly or
indirectly (A) create, or purport to create, obligations for Microsoft with respect to the MS
Samples or derivative work thereof or (B) grant, or purport to grant, to any third party any rights
or immunities under Microsoft’s intellectual property or proprietary rights in the MS Samples or
derivative work thereof. Identified Software includes, without limitation, any software that
requires as a condition of its use, modification and/or distribution, that any other software
incorporated into, derived from or distributed with such software must also be (1) disclosed or
distributed in source code form; (2) licensed for the purpose of making derivative works; or (3)
redistributable at no charge.
3. RESERVATION OF RIGHTS AND OWNERSHIP. Microsoft reserves all rights not
expressly granted to you in this EULA. The Software is protected by copyright and other
intellectual property laws and treaties. Microsoft or its suppliers own the title, copyright, and
other intellectual property rights in the Software. The Software is licensed, not sold.
4. LIMITATIONS ON REVERSE ENGINEERING, DECOMPILATION, AND
DISASSEMBLY. You may not reverse engineer, decompile, or disassemble the Software, except
and only to the extent that such activity is expressly permitted by applicable law notwithstanding
this limitation.
5. SUPPORT SERVICES. No technical support will be provided for the Software.
6. LINKS TO THIRD PARTY SITES. You may link to third party sites through the use of
the Software. The third party sites are not under the control of Microsoft, and Microsoft is not
responsible for the contents of any third party sites, any links contained in third party sites, or
any changes or updates to third party sites. Microsoft is not responsible for webcasting or any
other form of transmission received from any third party sites. Microsoft is providing these links
to third party sites to you only as a convenience, and the inclusion of any link does not imply an
endorsement by Microsoft of the third party site.
7. ADDITIONAL SOFTWARE/SERVICES. This EULA applies to updates, supplements,
add-on components, or Internet-based services components, of the Software that Microsoft may
provide to you or make available to you after the date you obtain your initial copy of the
Software, unless we provide other terms along with the update, supplement, add-on component,
or Internet-based services component. Microsoft reserves the right to discontinue any Internet-
based services provided to you or made available to you through the use of the Software.
8. U.S. GOVERNMENT LICENSE RIGHTS. All Software provided to the U.S.
Government pursuant to solicitations issued on or after December 1, 1995 is provided with the
commercial license rights and restrictions described elsewhere herein. All Software provided to
the U.S. Government pursuant to solicitations issued prior to December 1, 1995 is provided with
“Restricted Rights” as provided for in FAR, 48 CFR 52.227-14 (JUNE 1987) or DFAR, 48 CFR
252.227-7013 (OCT 1988), as applicable.
9. EXPORT RESTRICTIONS. You acknowledge that the Software is subject to U.S. export
jurisdiction. You agree to comply with all applicable international and national laws that apply
to the Software, including the U.S. Export Administration Regulations, as well as end-user, end-
use, and destination restrictions issued by U.S. and other governments. For additional
information see <http://www.microsoft.com/exporting/>.
10. SOFTWARE TRANSFER. The initial user of the Software may make a one-time
permanent transfer of this EULA and Software to another end user, provided the initial user
retains no copies of the Software. The transfer may not be an indirect transfer, such as a
consignment. Prior to the transfer, the end user receiving the Software must agree to all the
EULA terms.
11. TERMINATION. Without prejudice to any other rights, Microsoft may terminate this
EULA if you fail to comply with the terms and conditions of this EULA. In such event, you must
destroy all copies of the Software and all of its component parts.
12. DISCLAIMER OF WARRANTIES. TO THE MAXIMUM EXTENT PERMITTED BY
APPLICABLE LAW, MICROSOFT AND ITS SUPPLIERS PROVIDE TO YOU THE
SOFTWARE, AND SUPPORT SERVICES (IF ANY) AS IS AND WITH ALL FAULTS; AND
MICROSOFT AND ITS SUPPLIERS HEREBY DISCLAIM ALL OTHER WARRANTIES AND
CONDITIONS, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT NOT
LIMITED TO, ANY (IF ANY) IMPLIED WARRANTIES, DUTIES OR CONDITIONS OF
MERCHANTABILITY, OF FITNESS FOR A PARTICULAR PURPOSE, OF RELIABILITY OR
AVAILABILITY, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OF
WORKMANLIKE EFFORT, OF LACK OF VIRUSES, AND OF LACK OF NEGLIGENCE, ALL
WITH REGARD TO THE SOFTWARE, AND THE PROVISION OF OR FAILURE TO
PROVIDE SUPPORT OR OTHER SERVICES, INFORMATION, SOFTWARE, AND
RELATED CONTENT THROUGH THE SOFTWARE OR OTHERWISE ARISING OUT OF
THE USE OF THE SOFTWARE. ALSO, THERE IS NO WARRANTY OR CONDITION OF
TITLE, QUIET ENJOYMENT, QUIET POSSESSION, CORRESPONDENCE TO
DESCRIPTION OR NON-INFRINGEMENT WITH REGARD TO THE SOFTWARE.
13. EXCLUSION OF INCIDENTAL, CONSEQUENTIAL AND CERTAIN OTHER
DAMAGES. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO
EVENT SHALL MICROSOFT OR ITS SUPPLIERS BE LIABLE FOR ANY SPECIAL,
INCIDENTAL, PUNITIVE, INDIRECT, OR CONSEQUENTIAL DAMAGES WHATSOEVER
(INCLUDING, BUT NOT LIMITED TO, DAMAGES FOR LOSS OF PROFITS OR
CONFIDENTIAL OR OTHER INFORMATION, FOR BUSINESS INTERRUPTION, FOR
PERSONAL INJURY, FOR LOSS OF PRIVACY, FOR FAILURE TO MEET ANY DUTY
INCLUDING OF GOOD FAITH OR OF REASONABLE CARE, FOR NEGLIGENCE, AND
FOR ANY OTHER PECUNIARY OR OTHER LOSS WHATSOEVER) ARISING OUT OF OR
IN ANY WAY RELATED TO THE USE OF OR INABILITY TO USE THE PRODUCT, THE
PROVISION OF OR FAILURE TO PROVIDE SUPPORT OR OTHER SERVICES,
INFORMATON, SOFTWARE, AND RELATED CONTENT THROUGH THE PRODUCT OR
OTHERWISE ARISING OUT OF THE USE OF THE PRODUCT, OR OTHERWISE UNDER
OR IN CONNECTION WITH ANY PROVISION OF THIS EULA, EVEN IN THE EVENT OF
THE FAULT, TORT (INCLUDING NEGLIGENCE), MISREPRESENTATION, STRICT
LIABILITY, BREACH OF CONTRACT OR BREACH OF WARRANTY OF MICROSOFT OR
ANY SUPPLIER, AND EVEN IF MICROSOFT OR ANY SUPPLIER HAS BEEN ADVISED OF
THE POSSIBILITY OF SUCH DAMAGES.
14. LIMITATION OF LIABILITY AND REMEDIES. NOTWITHSTANDING ANY
DAMAGES THAT YOU MIGHT INCUR FOR ANY REASON WHATSOEVER (INCLUDING,
WITHOUT LIMITATION, ALL DAMAGES REFERENCED ABOVE AND ALL DIRECT OR
GENERAL DAMAGES IN CONTRACT OR ANYTHING ELSE), THE ENTIRE LIABILITY OF
MICROSOFT AND ANY OF ITS SUPPLIERS UNDER ANY PROVISION OF THIS
SUPPLEMENTAL EULA AND YOUR EXCLUSIVE REMEDY FOR ALL OF THE
FOREGOING SHALL BE LIMITED TO THE GREATER OF THE ACTUAL DAMAGES YOU
INCUR IN REASONABLE RELIANCE ON THE SOFTWARE UP TO THE AMOUNT
ACTUALLY PAID BY YOU FOR THE SOFTWARE OR U.S.$5.00. THE FOREGOING
LIMITATIONS, EXCLUSIONS AND DISCLAIMERS SHALL APPLY TO THE MAXIMUM
EXTENT PERMITTED BY APPLICABLE LAW, EVEN IF ANY REMEDY FAILS ITS
ESSENTIAL PURPOSE.
15. APPLICABLE LAW. If you acquired this Software in the United States, this EULA is
governed by the laws of the State of Washington. If you acquired this Software in Canada, unless
expressly prohibited by local law, this EULA is governed by the laws in force in the Province of
Ontario, Canada; and, in respect of any dispute which may arise hereunder, you consent to the
jurisdiction of the federal and provincial courts sitting in Toronto, Ontario. If you acquired this
Software in the Eurpean Union, Iceland, Norway, or Switzerland, then local law applies. If you
acquired this Software in any other country, then local law may apply.
16. ENTIRE AGREEMENT; SEVERABILITY. This EULA (including any addendum or
amendment to this EULA which is included with the Software) are the entire agreement between
you and Microsoft relating to the Software and the support services (if any) and they supersede
all prior or contemporaneous oral or written communications, proposals and representations
with respect to the Software or any other subject matter covered by this EULA. To the extent the
terms of any Microsoft policies or programs for support services conflict with the terms of this
EULA, the terms of this EULA shall control. If any provision of this EULA is held to be void,
invalid, unenforceable or illegal, the other provisions shall continue in full force and effect.
Au cas où vous auriez des questions concernant cette licence ou que vous désiriez vous mettre en
rapport avec Microsoft pour quelque raison que ce soit, veuillez utiliser l’information contenue
dans le Logiciel pour contacter la filiale de succursale Microsoft desservant votre pays, dont
l’adresse est fournie dans ce produit, ou visitez écrivez à : Microsoft sur le World Wide Web à
<http://www.microsoft.com>Sales Information Center, One Microsoft Way, Redmond,
Washington 98052-6399.