VB - Programmer - Guide To The NET FrameWork Class Library
VB - Programmer - Guide To The NET FrameWork Class Library
2 Evolution of VB .NET 29
Design Goals..........................................................................................30
Where We Came From ....................................................................30
Ideals ................................................................................................31
New Language Concepts ......................................................................31
Declaring Variables and Data Types ................................................32
FCL Classes That Replace/Augment VB6 Elements ......................36
Behavioral Changes..........................................................................36
Suggestions for Further Exploration ................................................39
vi
VISUAL BASIC PROGRAMMER’S GUIDE TO THE .NET FRAMEWORK CLASS LIBRARY
13 Messaging 597
Key Classes Related to Messaging ......................................................598
Messaging ............................................................................................600
Messaging Design Pattern ..............................................................601
Practical Applications ....................................................................602
Message Queues ..................................................................................604
Managing Queues ..........................................................................605
Enumerating Queues ......................................................................608
Sending Messages ..........................................................................612
Receiving Messages with MessageQueue........................................614
Asynchronous Messaging ..............................................................618
Suggestions for Further Exploration ..............................................621
Messages ..............................................................................................621
Timeouts and Expirations ..............................................................622
Acknowledgement and Response ..................................................624
Setting Priority................................................................................627
Default Properties ..........................................................................629
Enumerating Messages ..................................................................630
Suggestions for Further Exploration ..............................................632
Serialization ........................................................................................632
Message Body ................................................................................632
XML Format (XmlMessageFormatter) ..........................................636
Binary Messages (BinaryMessageFormatter)................................639
xiv
VISUAL BASIC PROGRAMMER’S GUIDE TO THE .NET FRAMEWORK CLASS LIBRARY
Index 1095
Foreword
It’s been said that change is “the constant, the signal for rebirth, the egg of the phoenix.” In
today’s dynamic operating environment, change and rebirth are not only constants, but also
essential elements to the survival of any successful business.
As individuals, our computing experience has continued to evolve rapidly, most noticeably in
recent years. In fact, if we take a step back in time, we can see how far things have really come
in this short period. In 1990, just over 10 years ago, I owned an NEC Powermate 286—a
machine, that at the time, fulfilled the vast majority of my computing needs quite nicely. After
all, it cranked along at a steady 8MHz, it included not only a 5.25” floppy drive, but also
20MB hard disk that I knew I would never fill, and of course, that 640KB of RAM on board
was all I would ever need. When it came time to “surf” for information, I’d simply fire up my
2400bps modem and do what? No, not dial into my Internet Service Provider—I would make a
direct phone call to my local Bulletin Board System (BBS) in order to view page after page of
flashing, colored ANSI text across my screen. Yes, it all sounds so innocent today, and yet it
was just over 10 years ago.
During the past decade, we’ve undeniably witnessed a revolution in the computing industry.
Moore’s Law, stating that computing power doubles approximately every 18 months, has been
an important agent in the corporate success of many of our companies. In addition, the prolif-
eration of mobile devices, the rapidly decreasing costs and increasing bandwidth of online con-
nectivity, as well as the penetration of standards such as XML and SOAP into the Internet,
have all been major contributing factors to the computing revolution that we continue to expe-
rience today. But what is the net effect of these radical industry changes on us as developers?
The computing revolution of the past decade has not only significantly expanded our range of
programming possibilities, but has also presented us with larger, more complex challenges than
we have ever before faced.
In 1991, a new developer tool emerged that would not only address the development chal-
lenges of the time, but would also fundamentally change the way that we build applications.
Like the computing climate of the past decade, Microsoft Visual Basic 1.0 was revolutionary—
it enabled both professional and casual developers to quickly and effectively build Windows
applications—a task that, at the time, was anything but trivial. Visual Basic introduced the con-
cept of RAD (Rapid Application Development) by providing an entirely graphical environment
to build Windows applications. Not only did this cause an upsurge in the number of Windows
applications being developed, but it also gave birth to a new market of third-party control
vendors. Together, these acted as a major driving force in the rapid adoption of the Windows
platform.
While more and more of us were jumping on the quickly growing RAD VB trend, the comput-
ing landscape and the development needs of our corporations were evolving. At each step of
the way, the Visual Basic language and tool evolved to meet these ever-changing needs.
Sometimes the changes introduced in a new version were relatively minor (Visual Basic 2.0),
sometimes they introduced more radical changes (Visual Basic 4.0), but the overall outcome
remained consistent—a developer tool agile enough to adapt to the changing needs of the
industry and radical enough to play a major part in the adoption of the platform. This not only
applied to Windows-based development with the release of VB 1.0, but also to client-server
programming with VB 2 and 3, 32-programming and the world of Windows 95 with VB 4, and
component development with Visual Basic 5.0.
Yet while each subsequent version of Visual Basic played a crucial role in driving platform
adoption, it has ironically always remained separate from the platform itself. The VB runtime,
while “protecting” us from the underlying complexities of the Win32 APIs and COM program-
ming, also served to obstruct us from new platform features until they were wrapped and made
available to us in our runtime DLL—VBRUNxxx.dll or MSVBVMxx.dll. This somewhat
unnatural separation from the platform has not only caused headaches, but has also been the
source of Visual Basic being disregarded as a second-class language—not suitable for true
enterprise-level development.
Now, as we enter a new era of challenges, highlighted by the need to build highly scalable,
secure enterprise-critical software and Web services powered by XML, a new version of Visual
Basic, Visual Basic .NET, is once again poised to revolutionize the way we build applications.
However, unlike previous versions of Visual Basic, Visual Basic .NET finally achieves that
integration with the underlying platform—the .NET Framework—that will empower VB devel-
opers to accomplish tasks never before possible.
The effects of the new .NET Framework, Visual Basic .NET, and the integration between them
are both exciting and far-reaching. Because Visual Basic .NET was built from the ground-up to
be a first-class player on the .NET Framework, it neither impedes VB developers from directly
accessing the rich feature set available in the Framework libraries nor carries with it antiquated
nonstructured programming constructs of our father’s BASIC. It provides a streamlined, mod-
ernized language that promotes both highly efficient and well-structured programming.
Combine that with the .NET Framework’s provisions for disconnected data access, multi-
threading, code access security, and a full set of object-oriented concepts, and you’ve got a
Visual Basic with unprecedented levels of both power and productivity.
However, with so many new features there comes an inevitable associated cost—that of learn-
ing the new concepts. This is precisely why I’m so excited about Lars’ and Mike’s book,
Visual Basic Programmer’s Guide to the .NET Framework Class Library. In addition to mak-
ing the large, hierarchical structure of the .NET Framework highly digestible, Lars and Mike
do so in a way that enables developers to learn the concepts as they directly relate to common
programming tasks. They cover everything from the organizational structure of the extensive
class library to the various namespaces for building UI, working with XML data, and accom-
plishing common network-based programming tasks. In addition, the book aptly discusses
techniques for accessing existing COM+ services and mapping existing skills from the Win32
APIs to their equivalents in the world of .NET.
As the application development landscape continues to evolve and reinvent itself, it will be
crucial that we as VB developers continue to evolve in concert. Visual Basic .NET and the
.NET Framework provide the springboard we need to take our next big evolutionary step. And
with this book, you’re already well on your way to understanding and applying the new con-
cepts and programming constructs that will propel us into the next 10 years of RAD Visual
Basic application development.
Ari Bixhorn
Product Manager
Visual Basic .NET
Microsoft Corporation
Preface
.NET is not simply a new set of developer tools to be unleashed in late 2001 or early 2002. It
is a multilevel strategy that will be realized over years, not months. Its impact to computing
will be broad and long lasting. Let’s take a look at where .NET came from.
.NET was made public at the Professional Developers Conference (PDC) in Orlando, July
2000. We were both lucky enough to be in attendance. What a shocker. We watched with nod-
ding heads and jaws agape as speaker after speaker illuminated solutions and described new
paradigms in computing. We furiously took notes and couldn’t wait to get our hands on the
.NET bits. We installed Beta 1 in our hotel rooms. We were bitten. It was obvious Microsoft
had done it right and had taken our favorite programming language, Visual Basic, to new
heights. One of the questions that we have since asked ourselves is, “Why did Microsoft
decide to tackle such a vision?” and “Where was .NET (or this vision) before Orlando?”
We start to get a picture of how .NET evolved out of the research laboratory and into
Microsoft’s products. As for the question, “Why .NET and not something else?” we can only
speculate. A quick look at some of Microsoft’s goals for .NET is the basis for our speculation.
• Open standards without compromising intellectual property
• Answer the requests of developers for stronger OOP support
• Answer the requests of businesses to build a scalable, robust platform
• Solve the problems of interoperability
• Integrate all Microsoft products and services
These are some ambitious goals by any standard and are by no means the only goals of
Microsoft’s .NET strategy. However, this subset allows you to see how .NET became a bet-the-
company strategy. In the end, however, Microsoft knew it was the right strategy, saw a chance
to make it happen, and boldly went for it. With the .NET Framework, Microsoft has made a
concerted effort to go back to the drawing boards and fix the fundamental problems that exist
for many developers working with the current crop of Windows DNA/COM/Win32 technolo-
gies. Ground zero for .NET is its prolific set of namespaces.
In this book, we try to answer some very fundamental questions in regard to each unique tech-
nology, such as the following:
• What does the .NET Class Library provide in terms of reusable code?
• Are .NET structures available that I can use to accomplish a specific task?
• How do I go about interfacing with the .NET Framework through my code?
In short, what we hope we have delivered is a definitive guide to the capabilities of the .NET
Framework Class Library. We have tried to provide an appropriate mix of introductory text (to
ease developers into a specific technology that they may not have worked with in the past,
such as XML or Directory Services), combined with task-driven code samples and detailed ref-
erence information.
Our hope is that you get as much joy discovering .NET through our eyes as we did discovering
.NET at the PDC in Orlando, but with a lot less frustration.
About the Authors
Lars Powers ([email protected])
Lars is a Microsoft Certified Solutions Developer (MCSD) with more than 10 years of experi-
ence analyzing business problems and developing software solutions. Most of his experience
centers on leading development teams and writing software in Microsoft development
environments.
Mike Snell ([email protected])
Mike is also a MCSD with more than 10 years of experience writing and designing software.
His experience centers on creating enterprise-level, Web-based systems using the Microsoft
platform.
Lars and Mike
Lars and Mike have been working together at four separate companies for more than six years.
In doing so, they’ve built a wealth of knowledge about executing successful projects and deliv-
ering enterprise-level systems.
Together, they have formed brilliantStorm (http://www.brilliantstorm.com): a partnership
focused on providing developers with .NET productivity tools, information, and training.
Mike
To my wife, Carrie, whose continuous support made this possible (I hope I can return the gesture),
and to my daughter, Allie, and my son, Benjamin, who shouted their encouragement (and let me
know when it was time to eat or sleep) into the ventilation ducts that carried their
messages down to me in the basement…I can come up now.
Acknowledgments
We have been involved with many software projects on many levels, and, without exception,
they have all involved a concerted team effort. This project was no different.
We would like to thank all the hard-working professionals at Sams Publishing for helping us
deliver what you see before you. We would especially like to thank Sondra Scott for believing
in us from the start, Karen Wachs for her editorial determination and patience, and JP and Dan,
our technical editors, who found errors both big and small.
Finally, we would like to thank the hardworking folks at Microsoft for having the courage and
talent to deliver such a monumental piece of technology; it has been many years since we have
been this energized and excited about what the future holds.
Tell Us What You Think!
As the reader of this book, you are our most important critic and commentator. We value your
opinion and want to know what we’re doing right, what we could do better, what areas you’d
like to see us publish in, and any other words of wisdom you’re willing to pass our way.
As an associate publisher for Sams Publishing, I welcome your comments. You can
e-mail or write me directly to let me know what you did or didn’t like about this book—
as well as what we can do to make our books stronger.
Please note that I cannot help you with technical problems related to the topic of this book,
and that because of the high volume of mail I receive, I might not be able to reply to every
message.
When you write, please be sure to include this book’s title and author as well as your name
and phone or fax number. I will carefully review your comments and share them with the
author and editors who worked on the book.
E-mail: [email protected]
Mail:
Associate Publisher
Sams Publishing
800 East 96th Street
Indianapolis, IN 46240 USA
Introduction
It’s not often you get to take over a franchise. Through hard work and dedication, Dan
Appleman has built his Visual Basic Programmer’s Guide series of API books into some of the
most recognized and best selling Visual Basic books of all time. Those books are the inspira-
tion for this one.
When Dan was approached to do a similar book for the .NET Framework, he felt he needed to
pursue other directions and other titles. The paradigm shifts brought on by .NET were also
causing authors and editors to rethink their titles and opportunities. .NET meant a total rewrite
of nearly every title and completely different approaches. Well, as Dan moved in another direc-
tion, we were talking with Sams Publishing about doing a book on the .NET namespaces. The
Programmer’s Guide concept was presented to us; we saw the opportunity and pounced.
We want to thank Dan for the groundwork he has laid for us as Visual Basic developers and
authors. By applying the approach he pioneered with the Win32 API to the world of .NET, we
hope to continue to bring this member of the Sams Programmer’s Guide series honor and dis-
tinction amongst the sea of technical books.
The namespaces themselves are language neutral. All languages in .NET derive from the same
Framework Class Library. Nearly every section in this book can be applied outside of VB. Of
course, all the source code (and there is a lot of source code) is written in VB syntax. However,
we provide liberal comments in the code to ease understanding and help with porting.
After finishing the book, you should have a solid understanding of the functionality available
in the .NET Framework Class Library. You should be able to intelligently use and inherit this
functionality to accomplish a wide variety of common and not-so-common programming tasks.
In addition, you will walk away with a firm grasp of how the different pieces of .NET relate to
one another and work together to provide a fully realized system for software development.
1
IN THIS CHAPTER
• The Composition of .NET 8
• .NET’s Relevance 16
We start this book by describing Microsoft’s .NET strategy and its relevance to the industry.
We first present .NET’s overall timetable and provide a description of the various components
that comprise .NET. We then detail the ramifications and relevance of .NET. Finally, we end
the chapter by defining the parts of the .NET Framework and detailing the benefits they pro-
vide our application development efforts.
After reading this chapter, you should
• Understand Microsoft’s .NET initiative, its components, and its timeline
• See the .NET initiative in the context of its relevance to specific audiences
• Have a solid understanding of the .NET Framework including the Common Language
Runtime (CLR) and Framework Class Library (FCL)
• Understand the benefits of the .NET Framework for you as a developer
• Begin to see the various programming paradigm shifts necessary to transition from
VB6/Win32 development to .NET
The answer? All of the above! Now you can begin to understand the problem with identifying 1
all of the different pieces of .NET. Generally speaking, Microsoft considers the pieces of .NET
to focus on three different areas:
EVOLUTION
OF .NET
1. Tools and languages for .NET-based software development
2. Building block services products for programmers and non-programmers
3. Third-party developed services
Right now, the major pieces to consider fall under the first two categories: software develop-
ment tools and so-called building block services. Third-party services will be in more evidence
after .NET has been broadly adopted.
Microsoft ASP.NET 1
Since the introduction of IIS (Internet Information Server) in late 1997, ASP (Active Server
Pages) has been the principal technology for Microsoft developers to deliver Web content. As
EVOLUTION
OF .NET
we all know, ASP forced developers to embed script and logic inside of UI (HTML) code.
While this was simple enough and relatively easy to use for basic tasks, it quickly became
cumbersome to program against and support as applications grew in scale and complexity.
The .NET version of Microsoft’s ASP finally separates user interface (HTML) from script.
ASP.NET implements a feature that Microsoft calls code-behind. This feature allows you to
write HTML and code into separate files; the HTML file simply maintains a link to its associ-
ated code file. Sounds simple enough, and it is; it is remarkable how much easier it makes life
for developers. The paradigm is equivalent to the win-forms paradigm (in fact, it is called Web
Forms) where developers create a form using drag-and-drop and write code behind the various
controls. Additionally, the code behind Web Forms can be written in any .NET language.
Another problem from which ASP suffered is slow performance due to the scripting code
being processed at runtime. ASP.NET automatically compiles code files when deployed or first
accessed. There is no longer a performance difference between a script class and one written
and compiled into a DLL! However, ASP.NET does not take away your ability to promote
code simply by copying over a file in a directory. Thanks to the JIT (just-in-time) compiler, a
file can be replaced and it will simply be recompiled by the system as need be.
NOTE
ASP developers of old, don’t fret; both the Response and the Request objects are still
available.
ASP.NET allows programmers to quickly infuse Web sites with dynamic content and function-
ality. It represents a serious step forward for the ASP technology.
that make .NET programming easier than ever, it faithfully adheres to its past goals of allow-
ing developers to build quality programs quickly. Chapter 2, “Evolution of VB .NET,” will fur-
ther explore the new Visual Basic language.
Microsoft C#
Microsoft decided to launch .NET with a new language—C#. There were multiple reasons for
creating C#. For one, the language keeps the syntax of a typical C++ application while provid-
ing some of the simplicity of Visual Basic. Microsoft’s hope is that C# will become the lan-
guage of choice for those developers hooked on C++. Secondly, C# provides developers with a
Java-like alternative. It is no secret that the .NET architecture shares some of Java’s design
concepts; C# is the Java of .NET. Of course, .NET will have a Java story (Rational and others
will make sure of that) but Microsoft hopes that C# is compelling enough to lure Java develop-
ers over to the .NET camp. Finally, Microsoft wrote most of the .NET Framework code using
C# thus solidifying it as the language of .NET.
Identity Services 1
These include such things as login and password verification and electronic wallets. These ser-
vices represent a secure and safe way to verify someone’s identity and then act accordingly.
EVOLUTION
OF .NET
Notification and Messaging Services
Building on technology already employed for things like Hotmail (Microsoft’s free e-mail ser-
vice) and Microsoft Instant Messenger (Microsoft’s free messaging software), these services
aim to provide e-mail, fax, and voicemail to and from almost any device from a PC to a hand-
held device.
Personalization Services
Targeted squarely at companies that are interested in catering to the individual, these services
manage the rules and preferences necessary to show people only what they want to see on a
Web site or other computer system.
Calendar Services
Calendar services allow for the integration and management of personal, work, and home cal-
endars. They allow people to track their time and appointments intelligently, and collaborate
with others doing the same.
passing invoices, purchase orders, and inventory information back and forth. Because it is
unrealistic to expect every company involved in such an exchange to change their data format
to some common, central format, the solution is data mapping. Quite simply, data mapping is
the processing of mapping one piece of data to another. What one company calls a SKU,
another company might call an inventory control number; one company may allow alphanu-
meric SKUs, while another allows only numeric control numbers. To help companies map
their data and overcome these obstacles, BizTalk implements a simple drag-and-drop interface.
Microsoft is also customizing BizTalk specifically for certain vertical industries.
the reach of software and data to mobile devices. It enables users to access their personal data 1
(e-mail, faxes, appointments, tasks, and so on) in real-time wherever they happen to be. This is
a key enabler for “wireless” applications in the .NET world.
EVOLUTION
OF .NET
Office .NET
Office .NET is an example of a .NET product that starts to extend services right onto a user’s
personal desktop. Details of this product are still sketchy because it hasn’t been released yet. It
is expected that Microsoft will finally make its popular Office products such as Excel, Word,
and PowerPoint available as a service for a monthly fee instead of as shrink-wrapped software.
It is important to note that Microsoft does not see this method of distributing Office as the only
method; this will become just another option or choice for consumers on how they want to pay
for and use typical functionality like word processing and spreadsheet management.
.NET’s Relevance
.NET intends to change the way we develop, access, and interact with Internet applications.
Given this, it’s easy to see its importance. Of course, .NET changes the way you write soft-
ware, but it is important to know that anyone who accesses information electronically will feel
the effects of .NET. Because .NET’s reach is so great, its relevance is defined differently for
different audiences. This section explores each audience and outlines the ramifications and rel-
evance of .NET to the given audience.
Developers
Of course, as developers, you are our primary audience for this book. We believe you are also
Microsoft’s primary target for .NET. Nothing happens without the code. You are needed to
evangelize, upgrade, learn, design, and develop .NET software. You are in control of .NET’s
future. The nice thing? .NET makes it an easy decision and migration path.
The .NET Framework gives you control. It allows you to choose your language and project
paradigm; even the development environment is completely customizable. We are no longer
forced to compromise or make trade-offs in lieu of productivity. In the past, if you chose an
easy-to-use syntax like Visual Basic you compromised features and speed; if you chose C++,
you compromised ease, manageability, and productivity. No more. In .NET, all languages are
created equal.
.NET allows us to build applications. The vast majority of today’s business application devel-
opment has some Internet component. Currently, to do routine tasks and ensure things like
security and scalability, many programmer cycles are wasted writing repetitive plumbing code.
With .NET, these things are built in. You can construct your application from .NET code
libraries (the focus of this book). You are free to refocus your efforts on solving business prob-
lems (or going home before midnight) and not working on the plumbing of your system.
.NET is done right. As application developers first and authors second, we have first-hand
knowledge of the tools. The .NET tools and environment are a joy. Developers will see
increased productivity and enhanced capabilities. It takes a few hours to get used to, but once
you do, we think you will find that .NET and the new Visual Studio are like a finely refined
cockpit; nearly everything is in its right place and works just the way you think it should.
System Architects
.NET further closes the gap between design and code for the system architects. There is no
longer a need for the architects to have one software license like Visio Enterprise and the
development team another. How many times has a developer wanted to open and change a
model only to be told he or she needed to buy another license? What typically happens is that
Evolution of .NET
17
CHAPTER 1
the models go unupdated and often unexamined past the architecture phase. Developers get 1
heads-down on code and the models become secondary. Visual Stuido.NET has built-in support
for UML. That’s right, now your models can co-exist with your code! Architects can write
EVOLUTION
OF .NET
some code and developers can help keep the models updated. Additionally, Microsoft hopes to
further extend the capabilities of the Visual Studio IDE through its partners in the Visual Studio
Integrator Program (VSIP). Companies like Rational will offer its products as both versions
and extensions of the Visual Studio .NET IDE.
Of course, .NET’s strong OOP capabilities no longer force architects into workarounds for
specific languages. When designing Visual Basic components before, for example, architects
where not free to design with inheritance. The constraints of the Visual Basic language just did
not support this design. With .NET, architects can model the right way and know that any lan-
guage that the developer chooses to implement will work just fine.
Project Managers
We consider project managers to be anyone who has to answer to both users and upper man-
agement on the state, status, or feature set of a piece of software. We know this often includes
a lot of developers. When was the last time you were free to focus on writing code and not sit-
ting in meetings or pushing paper? If you are on the front line of software development, you
are the one who faces a transfer or gets fired when the project goes a year off track and a mil-
lion dollars over budget.
.NET promises to help change this. Applications can be delivered in shorter time frames due to
increased productivity and more focus on business issues. Projects can be delivered at lower
costs. Development teams can again focus on solving real business problems and know, at the
end of the day, things like scalability, reliability, and robustness are baked in by .NET. In short,
those on the front line can once again become the hero. .NET helps ensure successful projects,
time and again.
Companies
The .NET platform fundamentally changes the way companies interact internally, with cus-
tomers, and with partners over the Internet. .NET promises a higher degree of communication,
connectivity, and productivity. It connects employee to employee, employee to partner, and
most importantly, employee to customer. Internet applications evolve from simple user forms
to rich, interactive collaboration. .NET frees the Internet from the PC. It Internet-enables and
connects cell phones, televisions, and other appliances.
.NET allows companies to explore new business models. Just like the Internet created new
markets and sales channels, Microsoft intends software services to evolve existing business
models. For example, think of a company that today gathers auto insurance rate information
An Introduction to .NET
18
PART I
for its customers. It collects this data, and helps its customers make informed decisions. It may
even expose this information on the Internet. With .NET, this company can wrap this informa-
tion into a software service that can be embedded into hundreds of applications. They still col-
lect the data; they in fact change very little. However, they now have new revenue-generating
market opportunity.
.NET opens new partnering opportunities for business. As the prior example illustrated, com-
panies can now draw on each other’s expertise to make a richer offer to potential customers.
For example, if I sell used cars on the Internet, I know my customers will need insurance. I am
not in the business of offering insurance nor do I want to be. With .NET I can find, grab, and
use the insurance service to make a more compelling offer to my customers. If there is a bank
loan rate service I’ll grab that too. In the end, I’ve increased my sales by making it easier for
customers to transact.
.NET allows companies to focus on the future without throwing out the past. Time and again
companies are told that in order to realize their new business model they must rewrite their
legacy systems. .NET is designed to extend and interoperate with those legacy systems.
Companies are encouraged to use .NET to leverage their current investments and at the same
time, plan for the future with built-in standards like XML.
End Users
We are often asked, “Will end users ever actually feel the effect of .NET?” Our answer, “You’d
better believe it.” .NET puts users in control of their information. How frustrating is it that we
have to enter our address and credit card details time and again on site after site. We have to
trust that each site secures our data properly and doesn’t sell it off to list brokers. .NET
promises centralized services. Imagine only one company knowing your private information.
Imagine never re-typing your ship-to or bill-to address. You authorize access to your informa-
tion and a service executes secure transactions on your behalf. Imagine being notified via an
alert on your cell phone that the Father’s Day gift you ordered is out of stock—and never giv-
ing out your cell phone number!
Ultimately, the end user may never hear of .NET. Although knowing the marketing might of
Microsoft, this is probably unlikely. However, it is likely end users will never fully grasp that
when they pick up the TV changer they will be accessing a myriad of .NET services and
servers, or that when they order movie tickets from their cell phone while stuck in traffic, they
are communicating with .NET components. .NET promises to empower users to communicate
on their terms.
Evolution of .NET
19
CHAPTER 1
EVOLUTION
OF .NET
so, we present each topic, describe its importance, and relate applicable or intended application
development benefits.
The .NET platform did not evolve out of Microsoft’s DNA architecture. It is an entirely new
platform and set of technologies. As a result, it requires that you bring an open mind to your
development. The pitfalls of the past are gone and typically, there is a new way to do every-
thing. This book is going to explore and teach you how to accomplish these tasks with .NET.
The term, .NET Framework, refers to Microsoft’s new programming platform, which has been
highly optimized for distributed application development. It encapsulates the runtime, classes,
interfaces, and type system, designed to speed and streamline the development process. There
are two principal pieces to the .NET Framework: the Common Language Runtime (CLR) and
the Framework Class Library (FCL).
The CLR is the foundation of the Framework. It provides the services and code execution envi-
ronment for .NET development. It is made up of a number of additional sub-components, like
the Common Type System (CTS) and the Just-In-Time (JIT) compiler, all of which we will
discuss in detail.
The other piece to the .NET Framework puzzle, the Framework Class Library (FCL), repre-
sents a collection of reusable classes that can be used to execute most common Windows pro-
gramming tasks. Of course, it is also the focus of this book.
These services provide a number of direct benefits to our application development efforts and
code execution. Some of these direct benefits are listed next:
• One of the biggest benefits to .NET development is that the CLR does not restrict the
syntax in which code is written. If a compiler exists for a given language, you can write
.NET code using its syntax. Some of the many languages currently being offered include
Visual Basic, C#, C++, Perl, COBOL, and even Java.
• Real, cross-language development is now possible thanks to the CLS. We can write code
in various languages and ensure that we can inherit from one component to the other,
debug across language boundaries, and even handle exceptions raised from one language
to the next.
• .NET does not force us to throw out the old. There is full support for interoperating with
COM/COM+ services. .NET code can access COM code of old; there is even support for
COM code accessing code written with .NET.
• We are now freed from the registry and all its pitfalls. Code written for .NET includes
metadata, or descriptive information about the code itself, including its dependencies.
With metadata, your code is said to be self-describing, thus rendering the registry, type
libraries, and Interface Definition Language (IDL) obsolete. It also makes the task of
installation and removal much more trivial.
• Our code will execute much faster due to performance gains with the platform, the use
of more compiled code (VB, ASP), and managed services.
• We now have full support for all object-oriented features from within VB, including
implementation inheritance!
• .NET should make memory leaks and reference counting things of the past thanks to its
garbage collector (GC).
• There is now the potential to compile once (to MSIL) and run on any platform! This is
the real Holy Grail of .NET. If a platform supports the runtime, your code will execute
on it.
That said, it is still quite possible (and very likely) for both language and library authors to 1
implement non-CLS–compliant features. A language author may target the CLR, but may also
need to create specific features that are not understood by other .NET languages. The key is
EVOLUTION
OF .NET
that any code you write using a language or library must only use CLS features in the API that
it exposes. If you stay true to this rule, your code is guaranteed to be accessible from all pro-
gramming languages that support the CLS. All non-compliant features are marked as such in
the language definition and usually have a good reason for their non-compliance.
For example, nearly every member of the .NET Framework Class Library is CLS-compliant.
This ensures their access and use from every .NET language. However, some members provide
support for features that are not defined by the CLS. All non-conforming members are identified
in the documentation, and in all cases, a CLS-compliant alternative has been made available.
We are the big benefactors of the CLS. Developers currently can choose from more than 20
different languages when writing .NET code! And the best thing? Every class that you learn to
use by reading this book works the exact same way from all these other languages. We are no
longer constrained by syntax, but instead, are free to choose the language with which we are
most productive—and we only have to learn one API!
It is the responsibility of the Just-In-Time (JIT) compiler to convert the MSIL (using the meta-
data) into native code. It is important to note that MSIL is not interpreted by the runtime. It is,
in fact, compiled natively as a method is requested. Compiling one method at a time saves the
runtime the overhead of compiling the entire library when it only needs one method. Addi-
tionally, methods that are never requested don’t need compiling. Of course, subsequent calls to
the method do not require a recompile. The native code is stored in memory, which is used to
process the additional requests.
NOTE
If you just can’t stand knowing that you are deploying IL code, Microsoft has shipped
a pre-JIT compiler. This allows you to JIT compile your code and store it on disk at
deployment. The pre-JIT compiler is called ngen.exe.
Managed Code
Code written specifically for the CLR is said to be managed code. The term, managed, refers
to the runtime’s services executing against your code. For instance, .NET’s memory manager
and garbage collector (GC) manage the memory used for a class you write. The advantage: You
are no longer responsible for reference counting or controlling memory leaks. Or course, the
runtime offers a number of other managed services in addition to memory management.
All code written with VB .NET is managed code by default. On the other hand, code written
with C++ is unmanaged by default. To write managed code with C++ you must throw a com-
piler switch and mark code as managed with a keyword inside of the managed extensions for
C++. Similarly, C# can mark data as unmanaged by using a keyword.
The benefit of managed code is that it can take advantage of the .NET runtime. Cross language
interoperability, code access security, and garbage collection are available only to managed
code. One drawback is that managed classes created to target the CLR can only inherit from
one base class.
NOTE
The .NET Framework provides the System.Runtime.InteropServices namespace to
facilitate access to native operating system services and other unmanaged code. The
namespace exposes a set of types for working with unmanaged code.
Evolution of .NET
23
CHAPTER 1
Assemblies 1
An assembly represents a group of functionality that is deployed as a single, logical unit.
EVOLUTION
Assemblies represent the code we write and can include other resources such as images or
OF .NET
other binary files associated with our applications. Assemblies are typically the bricks of our
.NET solution. They group functionality, which forms a boundary around code access security,
type, reference scope, version, and deployment unit.
All .NET applications must contain one or more assemblies. Assemblies have what are called
manifests. Assembly manifests represent the metadata that describes the assembly. The mani-
fest contains information on the exposed portions of the assembly, its references to other
assemblies, its version, name, and the files that make up the assembly.
One direct benefit of the .NET assembly model is that it is the end of DLL hell. Assembly ver-
sioning and referencing allows specific reference to versions of a component. This means mul-
tiple versions of a single component can now execute side-by-side.
Another benefit of assemblies is easier installation and deployment. Assemblies will make zero
impact and XCOPY installs possible.
NOTE
To add, remove, or view assemblies in the GAC, Microsoft provides a developer tool
called the Global Assembly Tool (gacutil.exe). Alternatively, you can drag-and-drop
components into the GAC using Windows Explorer since the GAC is also represented
as a directory.
An Introduction to .NET
24
PART I
Assemblies stored in the GAC often exhibit better runtime performance. Thanks to the key-
pairs, the CLR does not have to recheck the assembly’s security, and thus, tends to locate these
bits faster.
NOTE
There is no need to install assemblies into the GAC in order to make them accessible
to COM or unmanaged code.
Namespace
The term namespace is nothing more than a design-time, logical naming scheme for .NET
types. Types are organized in a namespace based on hierarchy indicated by a dot (.). For
example, in the namespace, System.IO, the dot separation indicates that IO is under the hierar-
chy of System. There can only be one System at that level and only one IO under the System
level. Of course, there could be an IO.System or even a System.System.
Evolution of .NET
25
CHAPTER 1
NOTE 1
The System namespace is the root namespace in the .NET Framework. Classes in this
EVOLUTION
OF .NET
namespace represent the data types used in our applications. Types include: Object
(the root of all that is .NET), Byte, Char, Array, String, Int16, and so on. Typically, these
types correspond directly to the data types used in the .NET languages. For instance,
the Integer keyword in VB corresponds (and derives from) .NET’s Int32 type.
As developers, we can create and control our own namespaces. To do so, we simply use the
Namespace keyword. This allows us to organize our types under a hierarchy. It’s important to
note, however, that namespaces are only a design-time convenience. As we’ve discussed, at
runtime, names scope is controlled by the assembly.
NOTE
The Imports keyword allows you to treat the contents of a namespace as part of your
own namespace. It does not actually import anything. It simply provides you the con-
venience of refering to members of the namespace as if they were part and parcel to
your own. For example, the call Dim q as System.Messaging.MessageQueue can be
shortened to Dim q as MessageQueue provided that your application has the line
Imports System.Messaging at the the top.
NOTE
To access the Win32 API, you use P/Invoke. This is explained in detail in Appendix A,
“Calling the Win32 API from Managed Code.”
Interoperability
The CLR provides true language-to-language interoperability, both at design and runtime.
Thanks to the CLS and CTS, you can inherit, debug, and raise exceptions across languages.
COM promised developers interoperation, and it worked, but only at runtime. A component
written with VB could be called from C++ and vice versa, but this only worked at the binary
level. There was no support for true, design-time, cross-language development.
Of course .NET supports calling the raft of existing COM objects; any existing COM compo-
nent can be called from managed code. Microsoft knows you cannot (and should not have to)
re-create all your existing code for .NET. Instead, it provides the Runtime Callable Wrapper
(RCW) inside the Framework. The RCW acts as a proxy that translates COM interfaces into
those of .NET. With the RCW, your managed code thinks it is calling other .NET code.
.NET also supports the calling of COM components directly from managed code. To do so,
you must create a COM Callable Wrapper (CCW). COM does limit the .NET constructs of
which your application can take advantage. Things like parameterized constructors and static
methods are not supported.
One drawback, as you may have guessed, is performance. While it is possible—and often a
very good idea—to communicate between COM and .NET, all of this cross marshaling will
have a performance impact. This is a trade-off you will have to make when deciding when to
rewrite for .NET or interoperate.
Security
.NET provides us with a host of security options. Security in .NET is grouped into the follow-
ing basic set of services:
• ASP.NET Web Application Security is a model that allows you to authenticate users of
a site against the NT file system permissions or an XML file that lists users and their
roles.
• Code Access Security (CAS) defines to what resources your code has access. This
model allows you to build distributed components that can be easily trusted due to their
access permissions.
Evolution of .NET
27
CHAPTER 1
• Role-based security makes decisions on what the user can do or access based on his or 1
her identity and role membership. This is similar to the security model of MTS/COM+.
EVOLUTION
OF .NET
NOTE
For more information on Security in .NET, read Appendix C, “.NET Security Models.”
Summary
In this chapter we presented Microsoft’s .NET strategy and discussed its makeup and impor-
tance relative to various target audiences. We then took a quick look under the hood of .NET.
This information helped position the benefits of .NET to our application development. It will
also serve as a basis for discussion in the coming chapters.
The following is a summary of some of the key points presented in this chapter:
• .NET is Microsoft’s initiative to deliver software as a service.
• .NET is more than a set of developer tools. It includes services, server products, operat-
ing systems, and so on.
• The .NET timeline spans years, not months.
• .NET is important to anybody who accesses, stores, or interacts with data electronically.
• Code in .NET is compiled into MSIL and metadata, stored in PE files, and JIT compiled
natively for a specific platform and hardware.
• Code written in one .NET language can be easily used by any other .NET language
thanks to the CLS and the CTS.
An Introduction to .NET
28
PART I
• The Framework Class Library provides basic programming functions and works the
same from all .NET languages.
• You can call COM objects from .NET and .NET objects from COM.
• Security in .NET is accomplished through Web Application Security, Code Access
Security (CAS), and role-based security.
Evolution of VB .NET CHAPTER
2
IN THIS CHAPTER
• Design Goals 30
The evolution of Visual Basic, over time, has led to inconsistencies and redundancy. VB .NET
did not evolve out of VB6; it is not VB7. VB .NET exists to clean up the language and promote
it to equal footing with other modern languages.
This chapter walks readers through some of the profound changes and productivity enhance-
ments that VB .NET and the new tool, Visual Studio .NET, provide. Developers can expect
nearly every aspect of the way they write code today to be altered in some manner. For the
most part, these changes are intuitive and logical—you should not have trouble picking up and
adapting them.
First, we illustrate the prime drivers and goals behind the new language and tools. We then
walk through a number of key new language concepts. Finally, we present some of the
enhancements the new tools provide.
After reading this chapter you should
• Understand the direction Microsoft has taken the VB language and for what reasons
• Appreciate how traditional VB language constructs have changed
• Begin to use the new VB and .NET language constructs
• Understand and work with some of the key new tools and enhancements VS .NET pro-
vides
Design Goals
VB developers represent a very vocal, loyal, and large Microsoft customer base. These devel-
opers (of which we count ourselves) want to hold on to VB’s ease of use and its hiding of com-
plexities. At the same time, we desire the power and access that other language developers
have. Microsoft had to walk this tightrope when redefining VB for .NET.
To meet these demands, VB clearly had to move from a language of features (or a technology)
to an actual programming language. In this section, we present some of the principles that were
adhered to when VB was extended for .NET. This should help put in perspective the sweeping
changes that the language has undergone.
Ideals
VB .NET had to adhere to some of the ideals of past incarnations of VB. Additionally, it had to
make sure that new features that VB developers have been requesting for years were supported.
Some of the VB ideals include the following:
• The language should be simple and consistent.
• Code written in VB .NET should be easy to read, maintain, and understand.
• Applications should be easy to error proof and debug.
• Developers should be relieved of writing plumbing or redundant code. 2
• The language should allow for rapid application development.
EVOLUTION OF
VB .NET
Some of the new features developers requested and received include the following:
• The capability to execute programming tasks, access servers, write tasks, and so on with-
out leaving the IDE.
• Full support for OO development, including inheritance, constructors, and the like.
• Performance should not be compromised for choosing VB .NET over another language.
When designing .NET, the architects of Visual Basic knew that major changes were in store in
order to support the Common Language Runtime (CLR) and adhere to the Common Language
Specification (CLS). The broad scope of the required changes allowed for a major overhaul
and cleanup of the language. Thankfully, they did not divert and create a crippled .NET
language in favor of ease of use, but instead elevated Visual Basic to a first-class language
for .NET.
NOTE
The largest impact to the language involves VB .NET’s new object-oriented (OO) sup-
port. This includes inheritance, constructors, overriding, delegates, interfaces, and the
like. The changes are so many, in fact, that we devoted Chapter 3, “Object-Oriented
Concepts in .NET,” to the subject. Similar in impact, the topic of multithreading is cov-
ered in Chapter 12, “Working with Threads,” and exception handling is covered in
Chapter 20, “Profiling, Debugging, and Exception Handling.” For a complete list of
data type changes, see Appendix D, “.NET Framework Base Data Types.” A number of
the items presented next are also explained in further detail throughout the book.
Syntax
A number of syntactical changes affect dimensioning variables. Variables declared on one line
separated by a comma, for instance, all result in the same data type. In VB6, a declaration such
as Dim x, y, z as Integer resulted in x and y being declared as Variants and z as an
Integer. With .NET, all three variables would be of type Integer. You can still, of course,
declare two variable types on the same line, such as Dim x as Integer, y as String.
The syntax for creating objects at the time of declaration is also slightly altered by VB .NET.
You can now use the following to create new instances of objects during a Dim statement:
‘not syntactically dissimilar to VB6
Dim myObject As New SomeObject()
Note that the caveats that applied to dimensioning objects As New in VB6 no longer apply with
VB .NET. In previous versions of Visual Basic, if a variable was declared As New, it was often
impossible to destroy; simply accessing it added a reference count. This practice often led to
lost reference counts, and Dim As New was considered taboo. In VB .NET, however, no implic-
it object creation exists; this, along with garbage collection, should remove the As New con-
struct from the “bad form” list.
Evolution of VB .NET
33
CHAPTER 2
Arrays
A number of changes were made to the way .NET handles array declarations. The first is that
the Option Base statement is gone from VB .NET. Option Base allowed you to set the lower
bound value for all arrays declared in your code. With .NET, all arrays have a lower bound
value of zero (0).
Additionally, the support for creating arrays with a range of boundaries is also gone with .NET.
VB6 developers could set an array with elements 2 through 5 with the following syntax:
Dim myArray(2 to 5)
2
NOTE
EVOLUTION OF
VB .NET
You can still call the keywords UBound and LBound to return the upper and lower lim-
its of a given array. However, arrays created in .NET derive from the Array class. As
such, they expose the methods like GetUpperBound and GetLowerBound. Each takes
a value indicating the dimension of which you want to determine the bound. For
example, you can write code that looks like the following:
For i = 0 To myArray.GetUpperBound(0)
Contrary to early beta releases, the number of elements in a VB .NET array is unchanged from
VB6. The statement, Dim MyArray(5), for instance, still contains six items (0–5).
Finally, .NET offers a couple of new ways to dimension arrays. Dim MyArray() as Integer
= New Integer(5) is equivalent to Dim MyArray(5) as Integer. The first example simply
explicitly creates the Integer object. Additionally, you can now initialize the items in an array
at the time of dimensioning. The following code creates a five-item array, each with an initial
value:
Dim myArray(5) as Integer = {1, 2, 3, 4, 5}
Note that Redim and Redim Preserve are still supported by VB .NET.
Strings
With .NET, you no longer need to dimension a string variable with a given length. For
instance, the VB6 syntax, Dim myString as String * 50, is no longer supported. VB .NET
manages strings differently and allocates memory based on the size of the actual string at the
time of assignment.
In fact, the String data type in .NET is said to be immutable. Immutable refers to the fact that
after it is created, the contents of the string cannot be changed. Consider the following example:
An Introduction to .NET
34
PART I
Scope
For the most part, variable scoping remains the same with VB .NET with the exception of
block scope. Block scope dictates that variables dimensioned within a code block are available
only for the term of the given code block. This enables you to dimension variables that are not
actually allocated unless the code falls into the block. A code block is defined as the section
of code contained in the constructs If ... Then, For ... Next, Select ... Case, and Do
... Loop.
The following code illustrates block scope. Notice that as you nest blocks, you simply narrow
the possible scope. However, variables declared inside a parent block are available within the
child blocks. In this case, this means the z that is declared within the For ... Next loop is
accessible inside the nested If ... Then block, but y is not accessible to the For ... Next
block.
Sub Main()
For x = 1 To 10
If x = 2 Then
z = 3
Evolution of VB .NET
35
CHAPTER 2
y = 5
End If
Next
End Sub
Note that if you exit a code block and re-enter during the same call, the variable’s value is still 2
maintained for the life of the object; it is simply available only inside the block.
EVOLUTION OF
VB .NET
Integers
The Integer data type in VB .NET is compliant with both the CLS and CTS, (and therefore
other .NET languages). Unfortunately, an Integer in VB6 is no longer an Integer in VB
.NET, but rather a Short. The following table describes the changes:
NOTE
One nice effect of this change is that Integers in VB .NET are now the equivalent of
Integers of the SQL data type.
Variant
The Variant data type is gone from .NET. Its replacement is the Object data type.
Currency
The Currency data type is absent from VB .NET. Its replacement is the Decimal type.
An Introduction to .NET
36
PART I
NOTE
The Microsoft.VisualBasic namespace is imported by default with any VB .NET
project created within the IDE.
Behavioral Changes
VB6 developers need to be aware of a number of behavioral changes. For instance, VB .NET
sometimes completely reverses VB6 defaults. All changes were made for a good reason, how-
ever, and developers should be able to easily adapt to them. This section indicates a number of
key changes that the language, and its support for the CLR and CLS, dictates.
Destroying Objects
VB6 COM objects relied on reference counting (and the set myObj = nothing construct) to
destroy objects and free resources. VB .NET, however, uses the CLR’s garbage collection (GC)
service to clean up and destroy objects. The result of this change is that you no longer have to
worry about cleaning up your objects.
In exchange for this feature, however, the Class_Terminate event is no longer supported. In its
place is a Finalize method that can be overridden to provide similar support. This method will
be called by the GC service when freeing your object. Developers should, however, not rely on
Evolution of VB .NET
37
CHAPTER 2
this method to destroy key system resources such as database connections and the like. The
nature of GC is such that its time of execution cannot be reliably predicted. Waiting for the GC
to call Finalize on your object to free these resources can impact and limit the scalability of
your system.
Instead of a Finalize, developers are encouraged to implement a Dispose method on all their
objects that require cleanup code at the time of destruction. Cleanup code should be placed
directly inside this Dispose method. It is important to note, however, that unlike Finalize
(and Class_Terminate), Dispose is not automatically called by the runtime. This standard
construct should be called explicitly by all clients of an object. In fact, .NET provides the 2
interface IDisposable that should be implemented to ensure a standard construct for all
EVOLUTION OF
objects. The following is a simple example:
VB .NET
Public Class SomeClass
Implements System.IDisposable
End Class
Parameter Usage
By default, parameters are now passed by value (ByVal) rather than by reference (ByRef),
which was the default in VB6. We suggest that you still explicitly indicate ByVal and ByRef to
make your code readable and easily understood.
One key change developers will quickly appreciate is the standardization of parentheses inside
parameterized calls to methods, objects, and the like. In VB6, if you expected a return value,
you used parameters around your call:
myVar = MyObject.MyMethod(myOtherVar)
When not expecting a return, you omitted the parameters, unless you used the Call keyword,
in which case you used the parentheses.
VB .NET makes it simple: Parentheses are always required.
Another change to parameters is the use of optional parameters in VB .NET. All optional para-
meters must have an explicitly defined default value. This eliminates the need for the
IsMissing function. VB .NET optional parameters look like the following:
Notice that optional parameters still must be defined at the end of the function signature.
An Introduction to .NET
38
PART I
Property Declarations
VB .NET unifies, and renders obsolete, the Property Get and Property Set statements with
the Property declaration statement. The following is an example of the VB .NET method for
defining a property:
Public Class SomeOtherObject
Dim myLocalValue
Get
Return myLocalValue
End Get
Set(ByVal Value)
myLocalValue = Value
End Set
End Property
End Class
VB6 supported the concept of default properties on objects. This required you to use the Set
statement when executing an object assignment instead of accessing the default property. It
also had the effect of making code difficult to read and developers were therefore encouraged
to be explicit. For instance, code that “assigned” an object to a string variable required a devel-
oper to look up the object and determine its default property before being able to work with it.
VB .NET does not allow for default properties without parameters. Now, developers must be
explicit when executing an object assignment or accessing a property. The new syntax elimi-
nates the need for the Set and Let statements. The compiler is no longer confused by the call
myObject = SomeObject—it is clearly a reference assignment.
.NET does support default properties that take arguments. This seems contradictory at first
glance. However, calls to these properties are not ambiguous and therefore are supported.
Primarily, default properties should be reserved to collection classes. It is apparent to the com-
piler and the developer that a call to myVar = myCollection(1) is accessing a default proper-
ty (most likely, an Item property). To declare a default property, you use the Default keyword.
Short Circuiting
One of the key changes to Visual Basic’s behavior inside of .NET is the new support for short
circuiting. Short circuiting states that items evaluated in an expression are not evaluated unless
accessed.
Evolution of VB .NET
39
CHAPTER 2
To support this new feature and at the same time maintain compatibility with existing code,
VB .NET introduces the short circuiting operators OrElse and AndAlso. When you want to
have your expressions short circuit, you use OrElse as a replacement for the Or operator and
AndAlso for the And operator. For example, when two items in an expression are separated by
an OrElse operator, if the first item in the expression evaluates to True, the second item is
never evaluated by the runtime. Instead, the code short circuits. In VB6 (and with the Or opera-
tor in VB .NET) the entire expression (both conditions) is evaluated before execution—this
often resulted in errors or had the effect of limiting developers. The following code presents an
example.
2
Dim x As Object ‘Variant for VB6
Dim y As Int16
EVOLUTION OF
VB .NET
x = “Hello World”
y = 2
Console.WriteLine(y)
End If
New Tools
The IDE offers a number of new tools as well as improvements on nearly all previous tools. In
the following section, we will walk through some of the key productivity enhancing tools
inside of the VS .NET IDE.
An Introduction to .NET
40
PART I
NOTE
To change the project that starts when you click the Run button inside a project
group, you simply right-click the project file and choose Set as Startup Project.
Similarly, in an ASP.NET project, you can right-click a page and choose Set as Start
Page.
The Solution Explorer is also used to manage the various files that relate to an application. All
VB .NET files have the same extension, .vb. These are equivalent to VB6’s class files (.cls),
forms (.frm), modules (.mod), and so on. Additionally, you can work with and link to related
files directly within the Explorer. Support exists for miscellaneous files, similar to VB6’s relat-
ed documents, and solution items—even projects of different sources can be mixed within the
solution. Figure 2.1 captures a shot of the Solution Explorer.
FIGURE 2.1
VS .NET Solution Explorer.
Evolution of VB .NET
41
CHAPTER 2
Class View
The counterpart to the Solution Explorer is the Class View. In Class View, you work with a
code-centric view of your source files. As with the Object Browser or other object-oriented
(OO) views of your code, each class, method, property, and so on is represented in a hierarchi-
cal view. Additionally, each OO type has its own graphical icon. Each icon is altered slightly
with a padlock when a given member is private. Overall, this view provides fast and easy
access to the key areas within your source files without forcing you to scroll through .vb files
in search of the property or method you want to work with. Figure 2.2 illustrates the Class
View tool.
2
EVOLUTION OF
VB .NET
FIGURE 2.2
VS .NET Class View.
Server Explorer
Perhaps one of the biggest gains in ease of use is the embedded Server Explorer. This tool pro-
vides developers with access to all servers and associated tools and services that a given server
might publish. This simple concept should save you time switching between and launching the
various server-management applications. For instance, if your component logs to the event log
on your development server and you want to check a bug, you do not have to leave the IDE to
view that server’s events. Additionally, when programming against a database, you can view
the tables, procedures, and so on from within your source editor. The Server Explorer allows
you to watch messaging queues and manage server services. The Server Explorer tool also
allows you to view and use XML Web services that a given server might expose. Figure 2.3 is
a screenshot of this tool.
Clipboard Ring
The Clipboard Ring is one of those tools that, after using it for a day or two, you will wonder
how you got along without it. The tool is simple: It keeps track of the items that you cut or
copy. It caches each item in the Clipboard, up to a total of 15 items. The last item copied
becomes the first item in the ring. After 15 items, the old items start dropping off the list.
An Introduction to .NET
42
PART I
You can access items from the ring in two ways: The easiest way is a shortcut key,
Ctrl+Shift+V. Repeatedly pressing the V key when you’re holding down Ctrl and Shift cycles
through the ring within the text editor. Additionally, you can access the items in the ring from
the toolbox; Figure 2.4 illustrates this concept.
FIGURE 2.3
The Server Explorer.
FIGURE 2.4
The Clipboard Ring.
Command Window
The Command Window inside the IDE is a new concept for VB .NET developers. The basic
premise is that it enables you to execute Visual Studio commands by typing them command
style directly into a Command Window embedded inside the IDE. Commands range from
those that enable you to manage files, projects, and source code to those that execute builds,
list threads, and view the stack contents. Figure 2.5 captures a shot of the Command Window.
Notice the Intellisense and AutoCompletion support for the commands.
Evolution of VB .NET
43
CHAPTER 2
FIGURE 2.5 2
The Command Window.
EVOLUTION OF
Incremental Search
VB .NET
Incremental Search is another one of those tools that you’ll wonder how you ever did without.
The tool enables you to easily find items within your source code from within the editor. Like
most good tools, it is simple in concept and simple to use. Inside a source file, press Ctrl+I
(Edit, Advanced, Incremental, and Search). Your cursor will change to the binocular icon and
an arrow indicating the direction in which the search is being performed. As you enter text, the
cursor will find and highlight the nearest occurrence of the complete text entered. For instance,
typing c might take you to the first occurrence of a class declaration, but adding the letters an
could take you to your defined property, CanImport. To remove a letter from your search, press
the Backspace key. To find the next occurrence of a search string, press Ctrl+I again. To
change search directions, press Ctrl+Shift+I.
Printing Code
Finally, developers have control over the printing of their source code! From VS .NET, you
can print all source code, in color. VB developers have been clamoring for this for a long time,
usually resorting to some third-party application or tool to allow the printing of their code.
This may seem basic, but it is another example of IDE enhancements to your productivity.
Figure 2.6 captures the Page Setup dialog box for managing how your code is output; note the
check box for line numbers.
Code Editor
The code editor is typically where developers live, breath, and create. VS .NET incorporates
a number of new features that make editing source code less painful, without being overly
intrusive.
An Introduction to .NET
44
PART I
FIGURE 2.6
The Page Setup dialog box.
Task List
The Task List embedded in the IDE is both a new tool for VB developers and a code-editing
enhancement. The tool provides task-based management that directly relates to and tightly
integrates with your source code. Developers can add tasks directly within the task pane; no
more going out to other applications such as Outlook to manage your development tasks. But
the biggest benefit is the automated tasks. These are tasks that the IDE writes directly into the
list when an error occurs while you’re editing code or building a project. These tasks are
tracked by the IDE. You can double-click one and be taken directly to the hot spot in your
code that pertains to the task. As a problem is fixed, the task automatically disappears.
Additionally, using special comment tokens, you type your own tasks within your code as
comments. The default tokens are TODO, UNDONE, and HACK. VB .NET developers sim-
ply type a standard comment followed by the token and a colon. The IDE automatically picks
up the token and creates a task in the list with the appropriate category, source file, and line
number reference. You can even create your own custom tokens. To do so, from within the
IDE, choose Tools, Options, Environment, and Task List. Figure 2.7 shows the Task List in
action.
FIGURE 2.7
Task List.
Evolution of VB .NET
45
CHAPTER 2
Dynamic Help
Another tool that works with the text editor is the context-based help system. Again, keeping
with the theme of staying in the IDE to execute development tasks, VS .NET embeds a help
system that links directly to the code you are in the process of writing. In past incarnations, if
you had a question about a particular object or method that Intellisense (what did we do before
Intellisense?) can’t answer, you had to launch MSDN and start searching the index or browsing
the content tree. Now, as you code, the help system updates itself with relevant information
based on your keystrokes. For example, take a look at Figure 2.8. Here the cursor is positioned
on a line that dimensions a variable of the type EventLog. Notice the Dynamic Help pane. We
have access to the Dim statement, the EventLog class, its members, and other associated topics. 2
EVOLUTION OF
VB .NET
FIGURE 2.8
Dynamic Help.
Outlining Code
The text editor with VS .NET supports code outlining. Code outlining automatically arranges
your code in hierarchical, tree-like blocks. A block might be a class or property definition.
Code inside a block can be hidden and expanded per your preferences. This makes it easier to
work with long source files. You can hide the blocks that you have finished with or are not
working on and view them only as needed. To turn outlining off and on, you use either a short-
cut key or access the associated menu from Edit, Outlining.
Additionally, VB developers have the #Region directive available. This enables you to define
outlined blocks (or regions) of code independent of the editor. To do so, you simply time
#Region “RegionName” and end the region with #End Region, where RegionName is a string
used to name your region. Figure 2.9 shows a number of outlined blocks. Notice the collapsed
block “Properties.” This is a custom-defined region we created to block all our property state-
ments together.
An Introduction to .NET
46
PART I
FIGURE 2.9
Outlined code.
Line Numbering
As you’ve undoubtedly noticed from some of the previous screenshots, VS .NET supports line
numbering. Developers can quickly scan and find code based on error reports that indicate line
numbers. To turn line numbering on and off, you navigate to Tools, Options, Text Editor.
Hyperlinked Comments
Hyperlinked comments represent another small but important enhancement. Developers can
embed hyperlinks to more information directly within their source code. One use for this
would be to embed author details within the source code. This way, if the original developer
changes jobs, moves, and so on, she can update her credentials on a Web site, and all those
maintaining her code can have up-to-date access information for her. To create a URL inside a
comment, simply type http. Figure 2.10 illustrates a URL embedded in a comment.
FIGURE 2.10
Hyperlinked code comment.
Brace Matching
Brace matching is another small, elegant productivity enhancement. When you’re typing code
using braces { [ (, the editor highlights the opening and closing active set of braces. This is
very useful when writing long statements that contain braces embedded inside of braces; how
many times in VB6 have you sat counting and matching closing and ending braces?
Evolution of VB .NET
47
CHAPTER 2
Because VS .NET represents such a sweeping overhaul of the tools, languages, and environ-
ments, a number of additional new tools and enhancements are available for you to explore.
These include but are not limited to the following:
• Start page
• Application templates
• Object Browser
• XML editor
• HTML editor 2
• Style Builder for cascading style sheets
EVOLUTION OF
VB .NET
Summary
VB .NET takes the language that we all know and love to its destined height. The transition,
although not easy, should be intuitive—and worth the effort.
The following are key points presented in this chapter:
• VB .NET is not VB7. Instead, it moves the language away from being a technology
toward being an actual language.
• VB .NET changes a number of programming constructs such as scope, array declaration,
integer, data type, and the like.
• The .NET FCL replaces a number of VB6 functions with classes and associated methods.
• VS .NET intends to embed all necessary tools to realize the goal of allowing developers
to stay within the IDE to execute all programming-related tasks.
Object-Oriented Concepts CHAPTER
3
in .NET
IN THIS CHAPTER
• Classes—Wrapping Data and Behavior
Together 51
Starting with the release of Visual Basic 4.0, the capability to create classes has been intrinsic
to the Visual Basic language. Some might say that Microsoft’s move to support this was the
true beginning of VB’s evolution into an object-oriented language. Whenever it started, and
whatever you thought of Visual Basic’s prior ability (or inability) to support object-oriented
(OO) concepts, .NET brings Visual Basic up to speed with all of the basic properties of an
object-oriented programming language. The deep object support in Visual Basic .NET, and the
.NET Framework in general, is certainly one of the most compelling changes offered in this
new environment.
This chapter will focus on defining the concepts of object orientation as they relate to software
development in general. In Chapter 4, “Introduction to the .NET Framework Class Library,” we
will also examine their specific manifestations in the .NET Framework.
There have been more than a few books written on object-oriented programming, so this chap-
ter will not attempt to deliver a full treatise on a subject well deserving of hundreds of pages.
Instead, we will cover only the ground that we need to cover so that programmers new to
object-oriented programming and programmers with no OO experience at all will have a good
backdrop of knowledge for exploring the .NET Framework Class Library.
We’ll start by reviewing all of the pertinent characteristics of object-oriented languages—an
obvious first step when you consider that the classes and other pieces of the Framework Class
Library are all object-oriented in nature. Then we’ll examine how these concepts have been
brought to life inside of .NET and Visual Basic .NET, hopefully arming you with a solid-
enough understanding of these concepts to make your programming experiences with the
Framework Class Library more productive.
In years past, many developers have debated whether Visual Basic was an object-oriented lan-
guage. Instead of investigating any of these prior claims, arguments, or discussions, let’s focus
instead on the here and now. Visual Basic .NET supports the major traits of an object-oriented
language, including the capability to:
• Wrap data and behavior together into packages called classes (this is a trait known as
encapsulation)
• Define classes in terms of other classes (a trait known as inheritance)
• Override the behavior of a class with a substitute behavior (a trait known as
polymorphism)
We’ll examine each one of these traits in detail. We’ll also examine ways in which you will see
these concepts at work inside of the .NET Framework. Chapter 4 will continue this thread by
specifically examining the nature of the Framework Class Library and attempting to relate
these object-oriented concepts directly to the Framework Class Library.
Object-Oriented Concepts in .NET
51
CHAPTER 3
OBJECT-ORIENTED
CONCEPTS IN
Classes as Approximations
.NET
If we discuss classes in the context of programming, we say that they establish a template for
objects by defining a common set of possible procedures and data. Procedures are used to
imbue the class with a set of behaviors; when implemented in a class they are called methods.
Classes maintain data inside of properties (which may or may not be visible to other classes).
Behaviors are the verbs of classes, and properties are the nouns. A car, for instance, will accel-
erate in a prescribed fashion. This would be a behavior. A car will also have a specific weight,
color, length, and so on. These are properties. From a technical, implementation point of view,
there is actually no difference between the way that methods and properties are implemented.
They both have function signatures, and both execute some body of code. In addition, both of
them can accept parameters and return values.
NOTE
There are some general guidelines for when to use properties versus methods (and
vice versa), but probably the best advice is to just be consistent. Most of the time,
these rules will help steer you to the correct decision:
continues
An Introduction to .NET
52
PART I
• Use a method if you are going to be passing in more than a few parameters.
• If you find yourself writing a method called GetXXX or SetYYY, chances are good
this should be a property instead.
• Methods are more appropriate than properties if there will be many object
instantiations or inter-object communication inside of the function.
• Properties, when implemented, should be stateless with respect to one another.
In other words, a property should not require that another property be set
before or after it is set. If you find this kind of dependency inside of a property,
it should probably be a method instead.
NOTE
The difference between the system that we are programming and the real-world
process that we are modeling is often referred to as the semantic gap. You could
summarize some of what we have been talking about here by saying that object-
oriented programming aims to reduce the semantic gap between programming and
the real world.
Object-Oriented Concepts in .NET
53
CHAPTER 3
Of course, just because the basic premises of objected-oriented programming are simple to
understand doesn’t mean that the actual programming of object-oriented systems is trivial.
Once you can work your way through the syntax and condition yourself to think in an object-
like fashion while actually designing your applications, some of the perceived complexity
associated with software development will begin to fade.
OBJECT-ORIENTED
plexity of an application. In other words, if Class A doesn’t have to implement code to under-
CONCEPTS IN
stand how Class B operates, we have just reduced the complexity of the code.
.NET
As programmers, we initiate a message from one class to another by calling a method or prop-
erty on the target class. Part of this message that we send encapsulates any parameters or data
needed by the receiving class to execute the action.
Thus, we have classes in an object-oriented programming environment. A physical manifesta-
tion of a class in the programming world consists of code that defines these attributes and
behaviors through property and method routines.
In this book, our focus on the Framework Class Library will introduce you to new classes in
each chapter. They will exhibit all of the traits and characteristics of the classes that we have
just defined.
Now, let’s move on and discuss the next OO trait of Visual Basic .NET—inheritance.
An Introduction to .NET
54
PART I
Detail that is
publicly visible to
other objects
Class DataSet
method: InsertRecord()
method: DeleteRecord()
method: UpdateRecord()
Actual execution
details are Detail that is
hidden… publicly visible to
other objects
Class DataFile
InsertRecord method: InsertRecord()
Source method: DeleteRecord()
Object method: UpdateRecord()
Class DataFile
method:InsertRecord()
dlmrstas Recordset
Actual execution buffArray = ret.Serialize()
details are While Not(buffArray.EOF
hidden… .
.
.
FIGURE 3.1
Messaging and information hiding.
FIGURE 3.2
Three classes—no parent class.
By looking at them, it quickly becomes clear that we could hierarchically structure these
classes by abstracting their common traits into a parent class. These three classes would then
be child classes of that one parent class. Figure 3.3 shows how this results in a tree structure
for our classes. Another way to think about this is called sub-typing. Children classes can often
be thought of as different “types” of the parent class (an HR employee is a type of Employee,
and so on).
3
Employee
OBJECT-ORIENTED
CONCEPTS IN
Attributes
Age
.NET
DateofHire
Behaviors
Promote
Terminate
FIGURE 3.3
Introduction of a parent class.
NOTE
If you examine Figure 3.3, you will see that our arrows point from the child classes to
the parent class. This may not seem intuitive to you; after all, aren’t we creating a
child class from a parent class? This notation is advocated because it shows that the
child knows about the parent, but the parent does not (necessarily) know about
the child.
NOTE
There are many different ways to express the inheritance relationship: parent to
child, super-class to sub-class, ancestor class to descendant class, generalized class to
specialized class, and so on. In this book, anytime we use these terms you should
know that we are just referring back to this basic concept of inheritance relationship.
Figure 3.4 shows class nomenclature, in ancestor/descendant terms, against a class tree.
Object-Oriented Concepts in .NET
57
CHAPTER 3
Class A
Generalized Ancestor
Class B
Class C Class D
Specialized Descendant
FIGURE 3.4 3
Inheritance nomenclature examples.
OBJECT-ORIENTED
CONCEPTS IN
We now know that identifying logical relationships between objects will help us out in the area
.NET
of code reuse. But the examples we have talked about so far have been based on relationships
between objects—an appraisal of one object being a type of another object. If, however, you
approach inheritance by first looking at its end result, you’ll find that you can end up with an
entirely different perspective. Let’s look at an example: Let’s say that we have a class that
defines operations for a specific type of printer. We’ll call this class InkJet. Intuitively, we
sense a parent class that would most likely be called Printer. Introducing a Printer parent
class produces the inheritance that we see in Figure 3.5.
Inheriting from the Printer class is a good solution for us because it already defines some
basic operations (line feed, paper out, and so on) that we can use as building blocks for our
InkJet class. At the same time, we will add some of our own behaviors that are specific to
inkjet printers. But what if we had the requirement for some low-level communication code
that would send an error signal across a parallel port? Also, what if that code was already
available to us in yet another class?
Figure 3.6 shows how we could inherit from a fictional ParallelPort object to leverage the
SendErrorSignal code that we need.
An Introduction to .NET
58
PART I
Printer
Printer
Attributes
CommMethod
ISPaperOut
Behaviors
LineFeed
FormFeed
On
Off
InkJet
*denotes inherited members
Attributes
IsOpen
IsInkinstalled
Behaviors
FormFeed*
On*
Off*
AlignCartridges
FIGURE 3.5
Inheritance based on relationship.
ParallelPort
Attributes
IsOpen
LineSpeed
Behaviors
ReadBuffer
WriteBuffer
SendErrSignal
FIGURE 3.6
Inheritance strictly for code reuse.
Object-Oriented Concepts in .NET
59
CHAPTER 3
This is subtly different from what we were doing before because it is very difficult to envision
a logical relationship between a parallel port object and an inkjet object. After all, an inkjet
printer is not a type of a parallel port—there is no obvious hierarchical relationship to draw
between the two. In this case, we would be implementing inheritance to get at raw code reuse.
This doesn’t do anything for us in terms of making our code easier to understand—it does not
reinforce a relationship between abstract classes and real-world objects.
NOTE
Inheriting for pure code reuse in the absence of a sub-type relationship is certainly
something that you can do with classes, but isn’t always the best approach. You gain
code reuse at the expense of increased complexity in your system (and therefore, a
corresponding increase in the effort required to understand your system). In .NET, we
advocate implementing an interface instead of using class inheritance to represent
this relationship; you still get the desired code reuse without complicating your class
relationships (more on interfaces in Chapter 4).
What if you decided to proceed ahead with class inheritance, and decided to inherit from both 3
OBJECT-ORIENTED
the Printer and the ParallelPort class? This is called multiple inheritance (see Figure 3.7).
CONCEPTS IN
Multiple inheritance is not supported by the .NET runtime.
.NET
Printer ParallelPort
Attributes Attributes
CommMethod IsOpen
Is PaperOut LineSpeed
Behaviors Behaviors
LineFeed ReadBuffer
FormFeed WriteBuffer
On SendErrSignal
Off
Attributes
IsPaperOut*
IsInkinstalled
Behaviors
FormFeed*
On*
Off*
AlignCartridges
SendErrSignal*
FIGURE 3.7
An example of multiple inheritance.
An Introduction to .NET
60
PART I
Overriding
One of the common examples used to demonstrate this concept involves a class library that
describes geometric shapes. One of the behaviors that we would like to imbue into our shape
classes is the capability to draw themselves. Using our basic knowledge of geometry, we know
that each shape will require different parameters and use different operations to actually
accomplish the draw operations (pi may be used when drawing circles, squares will need to
know a side length, and so on). Because a procedural programming language doesn’t allow us
to reuse behavior names (think methods), we would end up with a different routine for each
shape type such as DrawCircle, DrawTriangle, and so on. Because we can reuse method
names with polymorphism, we can simplify the programming model considerably by reusing
one method called Draw; each shape class would implement this in a slightly different fashion.
This is called overriding and specific manifestations of this in the Framework Class Library
are discussed in Chapter 4.
Overriding further promotes the concept of information hiding that we talked about earlier:
Each class knows internally how to implement its behaviors, but calling classes don’t know
and don’t care. We just send a message saying, “Draw,” and the target class worries about how
to carry it out. You will often see overriding with inheritance. A child class may override a par-
ent class’s methods to implement specific functionality not relevant to the parent class.
Overloading
A class may also override its own methods based on parameter lists. Consider the class shown
in Figure 3.8.
Square
Behaviors
Draw(length, drawUnits)
Draw(point,point)
FIGURE 3.8
Method overloading.
Object-Oriented Concepts in .NET
61
CHAPTER 3
It represents a class, Square, and its draw methods. The implementation of the draw behavior
differs based on the information that is passed into the Draw method. This is a special case of
overriding called overloading. In our example here, we want to avoid implementing methods
called DrawFromLength and DrawFromCoords; we simplify our class architecture by implement-
ing just one method, Draw, and let it determine which implementation of Draw to use based on
the function signature. In this way, both of the following would be valid method calls:
mySquare.Draw(10,”inches”)
mySquare.Draw(topLeftPoint, bottomRightPoint)
Polymorphism is really all about keeping interfaces between classes the same while allowing
actual implementations to differ. This encourages loosely coupled object designs and hopefully
clarifies system architecture and reduces complexity.
Summary
In this chapter, we introduced the major object-oriented concepts that you will need to under-
stand to get the most out of programming against the Framework Class Library.
We have seen how:
3
• Classes are used as templates for objects, defining how they behave and the types of
OBJECT-ORIENTED
information they need to store.
CONCEPTS IN
.NET
• Inheritance is used to implement class hierarchies and reuse code from a parent class in
its children classes.
• Polymorphism allows classes to take on many different forms of behaviors, depending
on the particular circumstances.
As we explore the class library in Part II of this book, you will see ample evidence of these
concepts in action. In the next chapter, we will show how many of these OO concepts physi-
cally manifest themselves in the .NET Class Library.
Introduction to the .NET CHAPTER
4
Framework Class Library
IN THIS CHAPTER
• Introducing the Framework Class Library 64
The Framework Class Library is a vast tapestry; it will take a while before you are familiar
enough with it to immediately identify its possible uses in a given situation. This chapter will
introduce you to the class library that ships with the .NET Framework. We will examine its
organization, and look at exactly how the classes operate as a reusable layer of functional
building blocks for .NET developers. You should walk away from this chapter understanding,
in principle, what position in the larger .NET initiative that the Framework Class Library holds,
and how it is really designed with the end developer in mind. You should also be primed and
ready to look at actual code examples in the chapters that follow in Part II, “Working with the
.NET Namespaces,” of this book.
This chapter will specifically
• Describe the organization of the class library namespaces.
• Illustrate how the class library implements the core concepts of classes, enumerations,
delegates, interfaces, and structures.
• Identify the key productivity enhancements offered by programming against the class
library.
So, before getting in to the actual makeup of the .NET base classes, we will examine a few
basic aspects of the namespaces that constitute the class library.
NOTE
Referencing pre-existing libraries of code has traditionally been pretty easy with
Visual Basic. One of the main issues, however, was that many times these code
libraries weren’t initially consumable by Visual Basic developers. If you were a C++
programmer, the world was open to you in terms of functionality. VB programmers
had to wait for Microsoft or some other entity to make a wrapper or interface avail-
able that we could use from VB. Documentation also tended to be a problem.
Introduction to the .NET Framework Class Library
65
CHAPTER 4
INTRODUCTION
CLASS LIBRARY
FRAMEWORK
compilation).
THE .NET
Collections Collections of objects including arrays, hash tables, and
dictionaries.
ComponentModel Runtime and design-time behavior of components and controls.
TO
Configuration Configuration settings management for the Framework.
Data Data access and management (essentially defines the ADO.NET
technology).
Diagnostics Application debugging and execution tracing. Also included in
this namespace are classes related to event log manipulation and
performance monitoring.
DirectoryServices Access to the Active Directory.
Drawing Graphics and drawing, including printing.
An Introduction to .NET
66
PART I
You should know that the Framework Class Library can be extended, but that the system root
namespace will always contain classes that are universally useful to applications. Companies
may introduce their own libraries that will co-exist with the System namespace and that will,
in fact, operate under their own root namespace. Microsoft, for instance, has already shown us
an example of this by including several language-focused namespaces under a root Microsoft
namespace. Thus, we have Microsoft.Csharp, Microsoft.VisualBasic, and so on.
object-oriented way. That is to say that, while the Framework Class Library does introduce
some new features, much of the functionality exposed by the namespaces was previously avail-
able to us through the Win32 API, ActiveX controls, COM-based DLLs, and so on. Now, how-
ever, it is wrapped in a way that allows us to program at an abstracted level where we don’t
have to deal with the complexities and granular pieces of data required by the Win32 API. As a
direct result, we only have to deal with one over-arching design pattern in our applications: one
that uses and promotes components in an object-oriented environment. Moreover, the function-
ality is available across all of the CLR-targeted languages!
NOTE
In case you were wondering, the Framework Class Library does not replace the Win32
API. The class library still relies on the actual Win32 API classes to talk directly to the
Win32 API to execute code through something called a P/invoke (platform invoke).
We’ll talk more about this process when we dig into the details of calling the Win32
API from your code in Appendix A, “Calling the Win32 API from Managed Code.” Of
course, if the .NET Framework is ported to another environment, the P/invoke code
will be calling into that environment’s native API.
INTRODUCTION
CLASS LIBRARY
FRAMEWORK
namespace exposes classes for basic input/output operations, and so on. And inside the
THE .NET
System.Drawing namespace, we find objects that would be familiar to any Windows graphics
programmer: the Pen object, the Brush object, and so on.
With any sufficiently sized API, the task of actually locating the code or function that you need TO
for a given task is not an inconsiderable issue. Contrast the organization of the namespaces that
we have talked about thus far with the organization of the flat, monolithic namespace offered
up by the Win32 API. The Win32 API has given us such gems as
FindClosePrinterChangeNotification (which doesn’t find anything; it closes a resource). The
problem is that, as the Win32 API has grown at its core, its developers have had to be more and
more creative with their function names. The ability to look at a function name and know with-
out some research what its purpose is has started to deteriorate. To be fair, it is possible to be
productive using the Win32 API from Visual Basic. It just takes some determination and a lot
An Introduction to .NET
68
PART I
of reference information. The Framework Class Library is a more approachable API: Things
are where you expect them to be. It appeals to the OO programmer in all of us who, after all,
is just interested in simplifying software by using objects.
NOTE
We call any code that runs under the control of the .NET runtime managed code.
Unmanaged code is any code that runs outside of the .NET runtime, such as the
Win32 API, COM components, and ActiveX controls. Currently, the only development
tool available from Microsoft for writing unmanaged code is Visual C++.
Because the Framework Class Library is written in managed code that runs on top of the .NET
Common Language Runtime, it reaps all of the benefits of any other piece of managed code,
such as:
• Freedom from GUIDS, hResults, and so on
• Automatic garbage collection of unused resources and memory
• Support for structured exception handling
Code Reuse
The holy grail of all object-oriented development, code reuse is a large part of the value of the
Framework Class Library. As all code libraries are intended to do, the intent with the class
library is to provide developers with a foundation for application development. If you recall the
concepts of inheritance that we talked about in Chapter 3, “Object-Oriented Concepts in
.NET,” VB .NET programmers are free to derive any of their classes from a base class defined
in one of the system namespaces (assuming, of course, that the class has not been marked as
sealed—see our note on sealed classes later). These classes don’t behave any differently than a
class that you would write in Visual Basic .NET. In VB .NET, the Inherits keyword is all that
is needed. The following code snippet shows a program inheriting from the XMLDocument class
in the System.Xml namespace:
Public Class MyDOM
Inherits System.Xml.XmlDocument
.
.
.
End Class
In addition to using the Framework classes for inheritance, you can also simply instantiate an
4
object directly from one of these classes, and use it—you simply reference the namespace that
INTRODUCTION
CLASS LIBRARY
you need, and then dimension and instantiate an object from one of the classes in that name-
FRAMEWORK
THE .NET
space. Object instantiation will look familiar to VB developers, because this is similar to what
you have done with type libraries and COM DLLs.
This code snippet shows how you can use the Imports keyword to reference a specific name-
TO
space in the class library. The DNS class contained in the System.Net.Sockets namespace is
then used to instantiate an object. The code is clear and simple to read.
Imports System.Net.Sockets
.
.
.
End Sub
End Class
NOTE
You should know that there are classes that don’t allow you to inherit from them.
Conversely, there are also classes that require you to inherit from them. These classes
are called sealed and abstract, respectively, and are discussed in more detail in the
following section.
The end result is a universal, free, logical, and component-based API that can be easily con-
sumed by any of the .NET languages without loss of functionality.
Classes
As we discussed in Chapter 3, a class is a template or blueprint for an object. It defines how an
object should look and behave. It does this principally through methods, properties, and events.
If you have read on from the last chapter or have some experience with object-oriented pro-
gramming, you should be familiar by now with the concepts of classes. You should note, how-
ever, that the .NET runtime supports some class attributes that you may not be familiar with.
These attributes are summarized in Table 4.2.
Classes typically define a constructor; this is a specialized behavior of a class that is called
whenever a new instance is created from that class. In Visual Basic .NET, you will define your
constructors using the Sub New routine. Class_Initialize and Class_Terminate events are
no longer supported. Classes may also implement destructors that will be called when the
object is being destroyed.
Object destruction in .NET works a little bit differently than you have come to expect. .NET
4
INTRODUCTION
implements something called a garbage collector. The garbage collector continually examines
CLASS LIBRARY
FRAMEWORK
THE .NET
a “reference tree;” if it finds an object that doesn’t have any more branches on the reference
tree, it calls the destructor of that object. It is no longer necessary to explicitly destroy man-
aged objects; the garbage collector will take care of it for you.
TO
NOTE
If your class uses resources that are unmanaged in nature, you should implement a
Finalize method. This method, which is not public, will be called by the garbage
collector as it destroys unneeded objects. If you need to provide a public method for
freeing resources (such as a Close method for your class), you should implement the
IDisposable interface.
An Introduction to .NET
72
PART I
All objects in the .NET Framework inherit from the System.Object class. System.Object is
the ultimate superclass in the Framework Class Library.
Structures
Structures are the .NET version of user-defined types from prior versions of Visual Basic.
User Defined Type (or UDT) syntax in Visual Basic, versions 6 and earlier, looked something
like this:
Type InventoryItem
SKU As String * 50
Price As Single
Name As String * 25
Count As Integer
End Type
The metamorphosis into structures means the Type...End Type syntax is no longer supported.
It has been replaced with Structure...End Structure:
Structure InventoryItem
Public SKU As String
Private Price As Single
Public Name As String
Private Count As Integer
End Structure
Note that with structures, each structure element can have its own scope modifier (for exam-
ple, can be public, private, and so on). This was not previously possible with UDTs.
And that’s not all that has changed. Structures in .NET behave a lot like classes. They support
most of the constructs of a class including properties, methods, and events. Table 4.3 summa-
rizes some of the key similarities and differences between classes and structures.
In the class library, structures are used to represent value types such as integers (Int16, Int32,
Int64). In fact, the actual structure object inherits directly from the class ValueType in the root
System namespace.
Introduction to the .NET Framework Class Library
73
CHAPTER 4
Delegates
Delegates may be an unfamiliar concept to the average Visual Basic developer. In essence, del-
egates are function pointers: They encapsulate method calls to other objects. Delegates are
described by the parameters they accept and the value type they return. In other words, you can
declare a delegate that will map to a method call that takes an integer and a single and returns
a string:
public delegate myDelegate(value1 As Integer, value2 As Single) As String
To use the delegate, you would declare a variable that references the delegate like this:
Dim myCallback As myDelegate = New myDelegate(obj.SomeMethod)
If the SomeMethod method that you reference has a function signature that matches the defini-
tion of myDelegate, you are home free, otherwise an error will be raised.
One of the powerful capabilities of a delegate is its ability to perform its function pointing in a
late bound fashion. You can use the preceding delegate to map to any method that follows the
parameter and return type signature; you don’t need to explicitly make a tie between the dele-
gate and a specific function at design time.
Delegates are defined throughout the Framework Class Library namespaces and fulfill a variety
of different tasks, most associated with implementing designs regarding callbacks and event
processing.
Interfaces
An interface is most commonly described as a contract. If you implement an interface, you are
essentially entering into a contract with all other components that exist that says, “I agree to
provide the following functionality in the following manner forever and ever….” Interfaces can 4
have methods and properties just like classes.
INTRODUCTION
CLASS LIBRARY
FRAMEWORK
THE .NET
You can implement interfaces defined in the Framework Class Library by using the
Implements keyword:
Back when we talked about inheritance and code reuse in Chapter 3, we said that interfaces
provide a good structure for implementing code belonging to a class that isn’t necessarily “log-
ically” related to our core class.
This kind of code reuse keeps our class hierarchies from becoming cluttered with seemingly
random inheritance relationships.
NOTE
There are some design considerations to think about when deciding between class
inheritance and interface implementation. For instance, because interfaces are meant
to be binding and perpetual contracts, they can’t (or shouldn’t) be changed. Imple-
menting code in an interface that is likely to change, such as business rules, leads to a
very brittle architecture. You don’t have this concern with class inheritance because
methods and properties can always be added to a base class without “breaking” its
descendant classes.
Enumerations
An enumeration is essentially a named constant—it is an aid to developer productivity because
it allows you to reference values using a recognizable name. Using enumerations greatly
improves code readability and speeds up coding because they provide a way to name or refer-
ence a value that maps to one of the underlying data types defined by the CTS. Visual Basic
developers should be familiar with the concept of “enums”—the syntax has not changed mov-
ing into Visual Basic .NET. The .NET runtime allows enumerations to evaluate to any of the
signed or unsigned integer data types that are defined (such as Int32, Int64, and so on).
As far as the Framework class library is concerned, enumerations are in many of the name-
spaces. One example of an enumeration in the class library is Appearance, contained in the
System.Windows.Forms namespace.
To use an enumeration from your code, simply reference the enumeration’s name and the value
name that you want to use:
myButton.Appearance=Appearance.Button
In the preceding code, we use the Appearance enumeration to specify that the button control
we are using should take on the “Button” appearance.
Enumerations are derived from the System.Enum class, which means you can reference enu-
merations in some very cool ways. For instance, you can call the GetValues() method to get
Introduction to the .NET Framework Class Library
75
CHAPTER 4
an array of all values defined by an enumeration. You can also call the GetNames() method to
get an array of all names for the values defined by an enumeration.
INTRODUCTION
CLASS LIBRARY
FRAMEWORK
THE .NET
NOTE
Not all classes in the Framework class library allow you to create instances just by
using the New operator—some force you to go through a class factory method to get
TO
your initial instance of the class. The WebRequest class is one example: In order to
create a new WebRequest object, you have to use the WebRequest.Create method.
You should also be aware that some classes have static methods and properties. Static
methods apply to classes and not instances. That means that you don’t have to create
an instance of the class in order to use the method. The WebRequest.Create method
is an example of a static method: We simply call it using the class reference without
an actual instance having been created.
An Introduction to .NET
76
PART I
This example shows the method prototype for the GetYear method on the
JulianCalendarClass. It shows us that this function is overridden from its base class (in this
case, from the Calendar class).
Overloading is a form of overriding by providing multiple method instances that differ only in
their parameter list.
As with overriding, when you explore the Framework Class Library you will notice plenty of
examples of overloading. Many class constructors are overloaded to give developers the maxi-
mum choice of instantiation based on the available data.
Consider the following constructors for the TCPClient class:
Overloads Public Sub New()
Overloads Public Sub New(ByVal localEP As IPEndPoint)
Overloads Public Sub New(ByVal hostname As String, port As Integer)
These constructors give you the choice of how you want to instantiate a TCPClient object.
NOTE
If you have been paying attention, you will have noticed by now that both Visual
Basic .NET and the Framework Class Library define base data types. In other words,
you have the Int32 data type defined in the System root namespace, and the Integer
data type defined in Visual Basic. From a best-practice perspective, you may be asking
yourself, which is the preferred method: declaring things using VB .NET intrinsics, or
their actual System types?
To illustrate, both of the following lines of code are valid:
Dim SomeVar As Integer
Dim AnotherVar As System.Int32
We suggest you pick whatever comes more naturally to you, and use it consistently.
Using the actual Framework data types has some attraction if you are programming
Introduction to the .NET Framework Class Library
77
CHAPTER 4
across multiple languages; you don’t have to shift gears. On the other hand, dimen-
sioning a variable as Integer will come much more naturally to Visual Basic develop-
ers. And of course, you don’t have to worry about the repercussions of your choice:
The CLR ensures that all of your code is mapped to the correct underlying data type.
Exception Handling
Exception Handling is the process of managing errors that may be encountered during the exe-
cution of your code. The .NET runtime, and the .NET languages, supports the concept of
Structured Exception Handling (SEH). These exception handlers follow a standard format that
defines a Try block, a Catch block, and a Finally block.
Writing an exception handler requires you to place the code that could possibly generate an
error into the Try block. In the Catch block, you place your code that deals with the error. The
Finally block is where you place operations that should be performed regardless of whether
an error was raised or not. The following code snippet shows a simple routine that implements
its code inside of an exception handler.
Sub DoCalc(ByVal num1 As Integer, ByVal num2 As Integer) As Integer
‘The exception handler is initiated with the ‘try’ block
Try
DoCalc = num1 / num2
Catch appError As Exception
‘handle the error; here, we just alert the user
‘through a message box. To get more detailed
‘debugging level info, we could use the
‘Exception.StackTrace property...
MsgBox(“Error:” & appError.Message)
4
INTRODUCTION
CLASS LIBRARY
FRAMEWORK
Finally
THE .NET
Beep()
End Try TO
End Sub
Notice that the Catch statement syntax allows you to deal with the exception as an object. The
class library defines an actual Exception class that allows the runtime to treat pass exceptions
through as instances of the Exception class. The Exception base class is, in turn, used to
derive more specialized exception classes such as the ApplicationException and
SystemException classes (both defined in the System root namespace) and the WebException
class (defined in the System.Net namespace). In fact, there is a fairly deep class hierarchy built
from the Exception class base (see Figure 4.1).
An Introduction to .NET
78
PART I
Exception
ApplicationException
CookieException
SystemException
ArgumentException
ArithmeticException
ExternalException
Win32Exception
…
FIGURE 4.1
Partial snapshot of the Exception Class Hierarchy.
As you examine the Exception class descendants, you will see that they offer methods and
properties specific to a given coding scenario. They often, for instance, override the ErrorCode
property to provide specific error codes for their particular scope. An exception handler with
multiple catch blocks looks like this:
‘The exception handler is initiated with the ‘try’ block
Try
‘code that could raise an exception goes here
Catch appError As Exception
‘handle the generic error
Finally
Introduction to the .NET Framework Class Library
79
CHAPTER 4
End Try
You can use the appropriate level (generalized or specialized) of exception object that is appro-
priate to your specific piece of code. You will often find yourself using multiple catch blocks
in your code to deal with exceptions raised across different levels of the exception class
hierarchy.
As we talk in-depth about the Framework classes in the chapters to come, we’ll devote time to
talking about these specialized exception classes in the namespaces, and the additional proper-
ties that they offer.
Summary
In this chapter, we have seen how the .NET Framework Class Library employs the basic OO
concepts of classes, inheritance, and polymorphism to provide developers with a rich API for
managed code.
We also investigated:
• The various pieces of a namespace including classes, delegates, interfaces, structures,
and so on
• How the Framework Class Library is designed to make it easy for developers to find and
then consume functionality exposed by its classes
• How to use the class library as an API and as a bed for class inheritance
Now that the anatomy of the class library has been exposed, the next part of this book will
concentrate on visiting, in-depth, some of these namespaces. We’ll explore their classes and
figure out how to write code against them.
4
INTRODUCTION
CLASS LIBRARY
FRAMEWORK
THE .NET
TO
PART
Working with the .NET
Namespaces
II
Part II of this book focuses on an in-depth examination of select
Framework Class Library namespaces. The structure of Part II is
developer task–based. It presents you with an overview of how
to accomplish a given task, leads you through detailed examples
(written in VB .NET), and finally, provides detailed reference
information on select namespace classes, delegates, structures,
and interfaces.
Part II explores
• Which classes and namespace elements to use for a given
programming task
• Design patterns and best practices for using a namespace
• Sample applications to cement the concepts covered in
each chapter
• Reference information on specific namespace elements to
help you become immediately productive with the class
library
This section can be read in sequence, but most readers will want
to skip around as needed. It is organized around common pro-
gramming tasks and designed to get you producing quality code
with a given namespace.
Forms, Menus, and Controls CHAPTER
5
IN THIS CHAPTER
• Key Classes Related to Windows Forms 84
• Creating Forms 86
Visual Basic has always excelled at forms-based application development. It has made the art
of crafting user interfaces with graphical windows a quick and easy job, involving drag-and-
drop controls and simple event coding. Visual Basic .NET continues that tradition, aligning
itself squarely with rapid forms development. Today, Visual Studio .NET and the .NET
Framework itself have become key enablers for graphical user interface (GUI) development.
With the .NET Framework, access to Windows Forms constructs is provided through the
System.Windows.Forms namespace (and its descendants). This means that all languages that
leverage the .NET runtime and its Framework can take advantage of these powerful classes to
provide forms capabilities.
In this chapter, we will start our exploration of the Forms namespace by investigating how it
allows you to create and manipulate Windows Forms. Then, we’ll cover adding menus to forms
and working with menu events. We’ll wrap up with a discussion of controls and how to create
custom controls from the UserControl class. We will not cover the design of Windows Forms
using Visual Studio itself; instead, we will focus explicitly on those items strictly from a class
library perspective. We will also not examine the multitude of controls available with Visual
Studio.
After reading this chapter, you should be able to
• Create forms and alter their appearance programmatically
• Control the interaction of multiple-document interface (MDI) parent and child forms
• Add menus to forms and respond to their events
• Add controls to a form programmatically and create your own controls
dialog box.
PageSetupDialog This class is a representation of the page setup dialog box,
which is common to forms that allow printing.
Working with the .NET Namespaces
86
PART II
Creating Forms
A form is a design-time version of a window. That is, when executed, a form appears as a win-
dow. In the design-time environment, however, a form functions as a canvas with which pro-
grammers can interact. Programmers can place code behind a form, and they can add controls,
menus, and other items to the form; they can customize the way the form looks and behaves.
Users and programmers alike are familiar with forms and their properties for a good reason:
They are the graphical, visual manifestations of programs. They are how consumers of pro-
gram functionality interact with programs to balance bank accounts, manage inventory, calcu-
late taxes, write books, and complete many other tasks.
FIGURE 5.1
Creating a new project.
5
FORMS, MENUS,
AND CONTROLS
FIGURE 5.2
The New Project dialog box.
Working with the .NET Namespaces
88
PART II
Note that because forms are objects in the .NET Framework, there is no magic that happens
inside the Visual Studio .NET IDE to make them work. The IDE is a convenient shell for
forms development because it allows for drag-and-drop form building through the Windows
Form Designer, which takes much of the grunt work out of creating forms and controls. You
could write these windows applications by using Notepad or any other simple text editor, but it
would be an arduous undertaking—there would be no syntax checking, auto-completion, or
even common formatting.
If you double-click on a form, you see the code behind the form. By default, the code you see
should look like this:
Public Class Form1
Inherits System.Windows.Forms.Form
End Sub
End Class
Let’s examine this line by line. First, a class declaration defines a class called Form1:
Public Class Form1
Inherits System.Windows.Forms.Form
This declaration shows that this class will inherit from System.Windows.Forms.Form; that is, it
will inherit from the Form class in the System.Windows.Forms namespace. This simple declara-
tion is where all the action takes place. In one fell swoop, the IDE has created some code that
is poised to take advantage of all the inherent functionality of the Framework-defined Form
class, and as you will see, there is quite a lot of functionality there to be consumed.
The next line might look peculiar to someone who is new to the Visual Studio .NET IDE
because the concept of code regions is new to Visual Studio .NET:
#Region “ Windows Form Designer generated code “
The #Region directive is a new element in the Visual Basic language. It tells the IDE to col-
lapse and hide sections of code. In the IDE, as shown in Figure 5.3, this directive is preceded
by a plus or minus box that you can click on to selectively reveal or hide the code bracketed by
the #Region directive.
Forms, Menus, and Controls
89
CHAPTER 5
End Sub
End Class
FIGURE 5.3
A collapsed code region in the IDE.
The collapsed code region in Figure 5.3 is given a name—Windows Form Designer generated
code—to let users know who put it there and what it contains. By default, Visual Studio
assumes that you won’t want to be bothered by all the code, so it is hidden. However, this is
where all the meat of the form creation code sits. If you expanding the collapsed code region,
the following code is displayed:
#Region “ Windows Form Designer generated code “
End Sub
End Sub
#End Region
As you can see, most of this code is commented with warnings such as “Do not modify it
using the code editor” and “Required by the Windows Form Designer.” In other words, if you
use the Form Designer, as a rule it is not a good idea to change the code it has written. To be
on the safe side, if you need to change something, you should do it through the Form Designer
itself instead of through brute-force code editing; this is yet another reason this region is hid-
den by default.
property (that is, the size of the client area of the form), AutoScaleBaseSize property (that is,
a value used to determine how much to scale the form if auto-scaling is used), Name property
(that is, the name used to reference the form in code), and Text property (that is, the text dis-
played on the title bar of the form).
The Dispose routine is the polar opposite of the New constructor: It is where you place code to
dereference objects, close files, zero out variables, and basically take care of anything impor-
tant that you opened or initialized in the constructor. The Form Designer uses the Dispose rou-
tine to clean up any components that it has used in conjunction with the form.
As you can see, this is no different from instantiating other objects from classes. It gives you a
base form to work with, and from here, you need to decide what style of form should actually
manifest itself when it is shown. We’ll cover these form characteristics when we continue our
discussion about the properties and methods of the Form class later in this chapter. Before we
do that, though, let’s take some time to examine the inheritance hierarchy that culminates in
the custom Form1 class.
Component System.ComponentModel
Control System.Windows.Forms
ScrollableControl
ContainerControl
Form
UserControl 5
FORMS, MENUS,
AND CONTROLS
FIGURE 5.4
An inheritance tree for the Form and UserControl classes.
The Control class inherits from the Component class in the System.ComponentModel name-
space. This is why you see references to components in Form1’s constructor.
Working with the .NET Namespaces
92
PART II
Minimize Box
Title Bar
Resize Handle
Restore/Maximize Box
FIGURE 5.5
The parts of a form.
With prior versions of Visual Basic, forms were modeless by default. However, you could dis-
play a modal form by using an enumeration passed into the Show method of the form object,
like this:
frmAbout.Show vbModal
The Visual Basic–defined constant vbModal tells the runtime to display the form modally. The
Form class in the .NET Framework provides two different types of Show methods: ShowDialog
and Show. You display a form modally by calling the ShowDialog method, as in the following
example:
aboutBox.ShowDialog()
You can pass an owner to the ShowDialog method in the following manner:
aboutBox.ShowDialog(mainForm)
This tells the runtime that the dialog box is a child form to the window, represented by an
instance called mainForm. The owner parameter is optional; if you don’t provide it in the call to
ShowDialog, the resulting dialog box is automatically assigned to the currently active window
as its parent/owner.
You display a modeless form by using the Show method:
mainForm.Show()
A typical design consideration involves the concepts of single-document interface (SDI) versus
MDI applications. An SDI application supports one document at a time, whereas an MDI
application can support multiple open documents at a time. As shown in Table 5.2, a few
properties that are exposed on the Form class can be used to manage MDI applications.
Working with the .NET Namespaces
94
PART II
To initially configure an MDI application, you set the IsMdiContainer property to true for the
parent form. Then, when you create each child form, you set its IsMdiChild property to true.
Listing 5.1 illustrates this concept by setting up Form1 as the MDI container (or parent form)
and then creating three child forms.
‘Indicate that this form will function as the MDI parent form
Me.IsMdiContainer = True
‘Create three new forms; each one will have its MdiParent
‘property set to the current form. This means they will
‘be contained inside of Form1 as children forms
childForm1 = New Form()
Forms, Menus, and Controls
95
CHAPTER 5
End Sub
Me.Controls.AddRange(New System.Windows.Forms.Control() _
AND CONTROLS
{New System.Windows.Forms.MdiClient()})
Me.IsMdiContainer = True
Me.Name = “Form1”
Me.Text = “MDI Form Demo”
Working with the .NET Namespaces
96
PART II
#End Region
End Class
Figure 5.6 shows how the application in Listing 5.1 looks when it is run.
FIGURE 5.6
An example of an MDI application.
You can visually organize MDI child forms by using the Form.LayoutMdi method. The
Form.LayoutMdi method accepts an MdiLayout enumeration value (see Table 5.3) and can cas-
cade the forms (as shown in Figure 5.6), tile them vertically, tile them horizontally, or arrange
their icons if they are minimized. Typically, MDI applications allow users access to these orga-
nizational functions through a Windows menu on the parent MDI container form.
Name Description
CenterParent Centers the form within the parent form.
CenterScreen Centers the form on the screen.
Manual Positions the form based on its current location and size.
WindowsDefaultBounds Positions the form in accordance with the Windows
defaults for location and bounds.
WindowsDefaultLocation Positions the form in accordance with the Windows
default location.
When you create a form, you can also specify the state of the form. By default, all forms are
created in the Normal state. A form in a Normal state displays with its default size (as specified
in the Form object’s Height, Left, Top, and Width properties). You can also cause a form to
maximize itself (that is, consume all available screen real estate) or minimize itself (that is,
shrink down to an icon). The form’s state is controlled through the Form.WindowState prop-
erty, which is used in conjunction with the FormWindowState enumeration. The
FormWindowState enumeration is described in Table 5.5.
5
Changing Border Style
FORMS, MENUS,
AND CONTROLS
The Form class exposes its border style through the property FormBorderStyle. This property
is used in conjunction with the FormBorderStyle enumeration to customize the way a form’s
borders are displayed. For example, the following code line:
myForm.FormBorderStyle = System.WinForms.FormBorderStyle.Sizable
Working with the .NET Namespaces
98
PART II
shows all the members of the FormBorderStyle enumeration (see Table 5.6). Note that chang-
ing the border style also affects the visual aspects of the title bar.
NOTE
The control box shows up as an icon in the left-hand side of the form’s title bar.
Right- or left-clicking on the icon displays the Control Box menu, which usually fea-
tures the commands Restore, Move, Size, Minimize, Maximize, and Close.
Forms, Menus, and Controls
99
CHAPTER 5
Controlling Opacity
One of the intriguing visual changes you can make to a form is to alter its opacity by using the
Opacity property. This property has an effect only on windows that are displayed on machines
running Windows 2000 or higher, and it can be used to achieve some interesting effects. For
instance, you could slowly alter this property so that you slowly phase a form into view when
launched and then slowly fade it out of view when closed. Setting Opacity to 1.0 (that is,
100%) displays an entirely opaque form, and setting it to 0 displays an entirely transparent
form. Note that these settings also affect any controls on the form.
To test the Opacity property, start a new project and add a form to it. Drag a TrackBar control
onto the form. The TrackBar control should show up in your control toolbox in Visual Studio
.NET. It looks like this:
Set the TrackBar control’s Maximum property to 100 and its Minimum property to 0. Double-
click on the control to get to the TrackBar1 change event, and set the form’s opacity to the
trackbar’s current value multiplied by .01. Run the application, and play around with the way
different opacities look by moving the slider on the trackbar. Here’s the code to alter the
Opacity value based on trackbar scroll movements:
form. Changing to a larger font might result in titles not fitting on the title bar or other
examples of overflow.
Working with the .NET Namespaces
100
PART II
‘SetDataObject is overloaded:
‘version 1
Overloads Public Shared Sub SetDataObject(ByVal data As Object)
‘version 2
Overloads Public Shared Sub SetDataObject(ByVal data As Object, _
ByVal copy As Boolean)
This section discusses how to interact directly with the Clipboard. As you start to develop
applications that use Clipboard functionality, you should keep in mind that many controls sup-
port Clipboard commands directly. For example, to copy selected text from a textbox, you
could either use the Clipboard.SetDataObject method or the TextBox.Copy method.
to talk to many different applications, each potentially with its own format and understanding
of data.
You need to first check to see if the data on the Clipboard can be massaged into a format that
is appropriate for a particular situation. For example, if you wanted to take data from the
Clipboard and set a text box’s Text property equal to that data, you would first determine
whether the data could be expressed as a string. The class DataFormats comes to the rescue
here. The DataFormats class provides a number of shared properties that are used to identify
the format of data stored in an IDataObject object. In this respect, its use resembles that of an
enumeration. Table 5.7 shows the different shared properties exposed by DataFormats.
The following code references one of the GetDataPresent methods of the IDataObject inter-
face; it inspects the data in the Clipboard and tells whether it matches the specifications:
Dim clipData As IDataObject = Clipboard.GetDataObject()
The GetDataPresent method accepts one of the fields from the DataFormats class as a para-
meter, and it returns a true or false value, indicating whether the data in the IDataObject
object is amenable to conversion to that format. If the method indicates that the data is in
fact in text form, you have one more step to go through: You need to get the data out of
IDataObject and assign it to the TextBox.Text property. IDataObject.GetData does this.
You again have to pass a field from the DataFormats class to tell the GetData method how to
receive the data. From there, it is a straight assignment to the TextBox.Text property.
This might seem like a lot of work, but notice that only four lines of code have allowed you to
use a very powerful, generic object that can share and store heterogeneous data across different
applications.
NOTE
The real beauty of the DataFormats class is that it allows you to create new data for-
mats and add them to the list of items that it understands. In this way, the Clipboard
and other objects that rely on the DataFormats class are infinitely extensible and
applicable to items that the .NET Framework runtime might not know about up
front.
Creating Menus
Like forms, menus are objects in their own right in the .NET Framework. When you set out to
create menus for an application, you are either creating a main menu that appears below the
title bar, or you are creating a context menu that is displayed when the user right-clicks over
an area of the form. (Context menus are often called pop-up menus.) Menus have become an
established part of user interface design. This section covers the menu-related classes in the
.NET Framework and shows you how to imbue your forms with menu-based functionality.
The MainMenu class is the menu that you will see directly on the form. Some standard items
that would appear on a main menu include File, Edit, View, Window, and Help, among others,
but you can place whatever you want on your main menu. Each item on the main menu is, in
.NET Framework terms, a menu item represented by the MenuItem class. To add a File element
to a main menu, you could write code like the following:
Dim fileMenu As MenuItem = appMenu.MenuItems.Add(“&File”)
From this code snippet, you can see that each MainMenu instance has a collection, called
MenuItems, that can contain instances of the MenuItem class. Membership in this collection
determines what the main menu actually contains. To add something to the main menu, you
simply add a menu item to this collection.
NOTE
There is a Menu class in the forms library. For the most part, you will never need to
deal with it directly; your primary interaction with menus is likely to be through the
MainMenu, ContextMenu, and MenuItem classes. However, it is important to know that
all these classes—and many others that are associated with menus—inherit directly
from the Menu class. For more information, see the .NET Framework documentation
on the Menu class.
5
FORMS, MENUS,
AND CONTROLS
A menu item can be thought of as any selectable item that appears on any menu, not just on
the main menu. This means that in order to add a selection to the File menu—such as an Open
Working with the .NET Namespaces
104
PART II
item—you would access the collection of MenuItems, only this time you would reference the
fileMenu object instead of the appMenu object:
fileMenu.MenuItems.Add(“&Open”)
NOTE
A particular instance of a MenuItem class can be contained in only a single menu at
a time. It also cannot be added more than once to the same menu. If you need to
duplicate a menu item from one menu to another, you use the MenuItem.CloneMenu
method, as follows:
menu2.MenuItems.Add(myMenuItem.CloneMenu())
In this way, you can essentially copy an existing menu item into a new menu.
You now have all the base code elements you need to create a simple main menu and use it on
a form. At this point, however, all you have done is created a MainMenu instance and added
some menu items to it. If you were to run a form project with just that code, you still wouldn’t
see a menu on the form because you now need to associate the MainMenu instance to the form.
This is not done automatically for you; you must set the reference in your code by using the
Form.Menu property:
Form1.Menu = appMenu
Forms, Menus, and Controls
105
CHAPTER 5
Listing 5.2 shows a standard main menu implemented on a form. Figure 5.7 shows what the
generated menu and form look like.
With appMenu
fileMenu = .MenuItems.Add(“&File”)
editMenu = .MenuItems.Add(“&Edit”)
windowMenu = .MenuItems.Add(“&Window”)
helpMenu = .MenuItems.Add(“&Help”)
End With
Me.Menu = appMenu
End Sub
MyBase.Dispose(disposing)
End Sub
End Sub
#End Region
End Sub
End Class
FIGURE 5.7
A form and its main menu.
Listing 5.2 expands the Windows Form Designer region because that is where all the initializa-
tion code for the menu appears. You first declare the menu objects that you need—one
MainMenu and three MenuItem instances—and then in the constructor you actually create the
main menu object and add items to it.
Forms, Menus, and Controls
107
CHAPTER 5
Similarly to the way you assign a main menu to a form, you assign the context menu to a form
by using the ContextMenu property. The following snippet assumes that Me refers to a form:
Me.ContextMenu = formContextMenu
Unlike the MainMenu property, the ContextMenu property is first implemented way back in the
class hierarchy with the Control class. This is because context menus are useful across any
type of control, not just forms. To set up a context menu for a text box control, for instance,
you can use this code:
Dim ctrlContextMenu As New ContextMenu()
myTextBox.ContextMenu = ctrlContextMenu
a Boolean property, and setting it to true causes that particular menu item to automatically
display a list of all child forms that are declared within the application.
Working with the .NET Namespaces
108
PART II
NOTE
If an application has more than nine child forms, the window list itemizes only the
first nine and then displays the More Windows item. Selecting the More Items item
launches a dialog box that has a complete list of all the child windows.
Another common menu feature of MDI applications is the consolidated menu. The parent MDI
window’s menus may contain items that are specific to the currently displayed child window.
Users can select functionality that may be specific or relevant only to the child window from
the parent window’s menus, allowing more flexibility and ease of use. The .NET Framework
runtime automatically merges a child form’s menu onto the parent form’s menus in an MDI
application. You can use the MergeMenu method in code to make this happen. We’ll investigate
using this method in the next section.
Adding Submenus
To demonstrate how to add submenus, in this section we will expand on the File menu from
the previous section. To add the contents of the File menu, you need to add a few new items to
the MenuItems collection of the file menu:
With appMenu
fileMenu = .MenuItems.Add(“&File”)
fileMenu.MenuItems.Add(“&Open...”)
fileMenu.MenuItems.Add(“&Save”)
fileMenu.MenuItems.Add(“Save As...”)
fileMenu.MenuItems.Add(“View As Web Pa&ge”)
fileMenu.MenuItems.Add(“&Close”)
fileMenu.MenuItems.Add(“E&xit”)
editMenu = .MenuItems.Add(“&Edit”)
windowMenu = .MenuItems.Add(“&Window”)
helpMenu = .MenuItems.Add(“&Help”)
End With
The File menu constructed in this code example is shown in Figure 5.8. The main menu con-
sists of several menu items, and each of these menu items is itself a submenu that contains
Forms, Menus, and Controls
109
CHAPTER 5
items that the user can select. This nested relationship might seem confusing, but it is actually
quite simple. After creating the main menu, you have the choice of implementing submenus
(which show lists of commands) or items (which are commands).
FIGURE 5.8
File menu items.
If you were to nest even more items under one of the File menu items, you would end up with
a cascading, or fly-out, menu. For example, you can add a menu item to the existing Close
item, which means Close becomes a way of showing another menu:
Dim fileCloseMenu As MenuItem
With appMenu
fileMenu = .MenuItems.Add(“&File”)
fileMenu.MenuItems.Add(“&Open...”)
fileMenu.MenuItems.Add(“&Save”)
fileMenu.MenuItems.Add(“Save As...”)
fileCloseMenu = fileMenu.MenuItems.Add(“&Close”)
fileCloseMenu.MenuItems.Add(“Now”)
fileMenu.MenuItems.Add(“E&xit”)
editMenu = .MenuItems.Add(“&Edit”)
windowMenu = .MenuItems.Add(“&Window”)
helpMenu = .MenuItems.Add(“&Help”)
End With
To indicate on or off menu selections, the Windows menu system supports the ability to place
checkmarks next to menu items. You can also add shortcut keys and include separator bars to
help organize menus. Let’s add some of these features to the File menu we’ve been working
Working with the .NET Namespaces
110
PART II
with in this chapter. First, let’s add some separator bars to organize the menu items. You add
separator bars the same way you add menu items; you just set the menu item name to a dash
(“-”), like this:
fileMenu.MenuItems.Add(“-”)
FIGURE 5.9
A File menu with Close menu.
You want to group the Open, Save, and Save As items together. The View As Web Page item
should sit in a group by itself, and the Close and Exit items should sit together in the last
group. The code to make this happen looks like this:
With appMenu
fileMenu = .MenuItems.Add(“&File”)
fileMenu.MenuItems.Add(“&Open...”)
fileMenu.MenuItems.Add(“&Save”)
fileMenu.MenuItems.Add(“Save As...”)
fileMenu.MenuItems.Add(“-”)
fileMenu.MenuItems.Add(“View As Web Pa&ge”)
fileMenu.MenuItems.Add(“-”)
fileMenu.MenuItems.Add(“&Close”)
fileMenu.MenuItems.Add(“E&xit”)
editMenu = .MenuItems.Add(“&Edit”)
windowMenu = .MenuItems.Add(“&Window”)
helpMenu = .MenuItems.Add(“&Help”)
End With
You add shortcut keys and check (or uncheck) menu items through properties in the MenuItem
class. To check a menu item, you use the Checked property, which is a Boolean property that
you can set to true or false. To add a shortcut key to one of the menu items, you use the
Shortcut property, which is an enumeration of type System.Windows.Forms.Shortcut. You
use the enumeration Shortcut to specify the key that you want to assign as the shortcut key.
Table 5.8 lists the values that are available in the Shortcut enumeration. The menu now looks
like the one in Figure 5.10.
Forms, Menus, and Controls
111
CHAPTER 5
Name Description
Alt0 (…Alt9) Creates a shortcut with the key combination Alt+0.
Values range from Alt+0 through Alt+9.
AltBksp Creates a shortcut with the key combination
Alt+Backspace.
AltF1 (…AltF12) Creates a shortcut with the key combination Alt+F1.
Values range from Alt+F1 through Alt+F12.
Ctrl0 (…Ctrl9) Creates a shortcut with the key combination Ctrl+0.
Values range from Ctrl+0 through Ctrl+9.
CtrlA (…CtrlZ) Creates a shortcut with the key combination Ctrl+A.
Values range from Ctrl+A through Ctrl+Z.
CtrlDel Creates a shortcut with the key combination
Ctrl+Delete.
CtrlF1 (…CtrlF12) Creates a shortcut with the key combination Ctrl+F1.
Values range from Ctrl+F1 through Ctrl+F12.
CtrlIns Creates a shortcut with the key combination Ctrl+Insert.
CtrlShift0 (…CtrlShift9) Creates a shortcut with the key combination
Ctrl+Shift+0. Values range from Ctrl+Shift+0 through
Ctrl+Shift+9.
CtrlShiftA (…CtrlShiftZ) Creates a shortcut with the key combination
Ctrl+Shift+A. Values range from Ctrl+Shift+A through
Ctrl+Shift+Z.
CtrlShiftF1 (…CtrlShiftF12) Creates a shortcut with the key combination
Ctrl+Shift+F1. Values range from Ctrl+Shift+F1
through Ctrl+Shift+F12.
Del Creates a shortcut with the key Delete.
F1 (…F12) Creates a shortcut with the key F1. Values range from
F1 through F12.
Ins Creates a shortcut with the key Insert.
None No shortcut key is associated with the menu item.
ShiftDel Creates a shortcut with the key combination
Shift+Delete. 5
ShiftF1 (…Shift12) Creates a shortcut with the key combination Shift+F1.
FORMS, MENUS,
AND CONTROLS
FIGURE 5.10
Setting shortcut keys and checking menu items.
NOTE
You can show or hide the shortcut keys that you have defined for a menu item.
Shortcut keys are displayed by default, but you can modify them by using the prop-
erty MenuItem.ShowShortcut. This can be useful if you want to control (or let users
control) the verbosity of the menus. Not showing shortcuts can help reduce the over-
all image footprint of menus, perhaps for advanced users who don’t need or want to
be reminded of the shortcuts all the time.
editMenu = .MenuItems.Add(“&Edit”)
windowMenu = .MenuItems.Add(“&Window”)
helpMenu = .MenuItems.Add(“&Help”)
End With
The resulting radio button style, which takes care of visually displaying the radio button check-
mark, is shown in Figure 5.11. You still have to write the code to enforce the mutually exclu-
sive nature of the selection by turning the checkmark off for one menu item while turning it on
for another.
FIGURE 5.11
Checking mutually exclusive menu items.
fileMenu.MenuItems.Add(“&Close”)
fileMenu.MenuItems.Add(“E&xit”)
editMenu = .MenuItems.Add(“&Edit”)
windowMenu = .MenuItems.Add(“&Window”)
helpMenu = .MenuItems.Add(“&Help”)
End With
Working with the .NET Namespaces
114
PART II
FIGURE 5.12
Setting a default menu item.
Merging Menus
The menu items contained in one menu can be programmatically merged with the menu items
in another menu. Context menus, for instance, may have items in them that are mere duplicates
of items already defined inside the form’s main menu. Rather than recode all those items, you
can use the MenuItem class to simply merge the items from the main menu into the context
menu, via the MenuItem.MergeMenu method.
There are a few types of possible merges. The MenuMerge enumeration (see Table 5.9) classi-
fies these merges; this enumeration is used with the MenuItem.MergeType property to specify,
for each menu item if needed, exactly how it should be merged when the MergeMenu method is
called. To merge menus, you need to first set the MergeType property appropriately for each
menu item that is supposed to take part in the merge. You can even control in what order the
items are merged, through the MenuItem.MergeOrder property (an integer value). The lower
the MergeOrder value, the higher up in the merge menu the item will appear.
The sender object is not new; it is a standard object used with events, and it represents an
instance of the object that fired the event. DrawItemEventArgs is the key here. It is a class,
contained in the Windows.Forms namespace, that essentially encapsulates a bunch of proper-
ties that can be set to alter how the menu is drawn. Table 5.10 shows the properties exposed by
the DrawItemEventArgs class and their description.
Specifies the state of the menu item being drawn. State is defined with the
AND CONTROLS
State
DrawItemState enumeration.
Working with the .NET Namespaces
116
PART II
If you wanted to draw your own menu items, you would first write a handler for the DrawItem
event for the particular MenuItem object. It would look something like this:
Private Sub OwnerDraw_DrawItem(ByVal sender As System.Object, _
ByVal e As DrawItemEventArgs) Handles MenuItem2.DrawItem
Dim rect As New Rectangle(0, 0, 16, 16)
Dim i As New Icon(“sampleicon.ico”)
e.Graphics.DrawIcon(i, rect)
End Sub
In this example, the drawing of MenuItem2 is handled through raw code. To draw an icon onto
the menu, you use an instance of the Rectangle class and the Icon class, in conjunction with
the DrawItemEventArgs.Graphics class instance that is passed into the event.
NOTE
If you plan to create owner-drawn menus, remember that it is an all-or-nothing
proposition. If you set the OwnerDraw property to true, you are telling the compiler
and the runtime that you will handle all the drawing for that menu: background,
text, colors, positioning, and so on. You do not have the ability to paint some of the
menu and then let the runtime do the rest.
Now, you can stub out a routine that will fire whenever the click event fires. Here is an
example:
Protected Sub fileMenuClose_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles fileMenuClose.Click
End Sub
The key to this routine is the Handles fileMenuClose.Click syntax, which informs the com-
piler that the routine will be the “sink”, or recipient, of the click event for the particular object
that is referenced—in this case, the fileMenuClose object.
The second way to react to the selection of a menu item is by supplying your own event han-
dler. An event handler is a delegate or function pointer that acts as a sink for an event. In this
case, the event is the selection, either by direct selection of a menu item with the keyboard or
mouse or through a shortcut key or through a mnemonic.
Remember the MenuItems.Add method? In this case, you overload it to accept an event handler
as well as the name of the new menu item. Where you previously used this:
fileMenu.MenuItems.Add(“&Open...”)
Assigning event handlers to menu items has the same effect as using the click event; you are
simply instructing the compiler to execute a specific block of code whenever a menu item is
Working with the .NET Namespaces
118
PART II
selected. There are, of course, syntactical differences between the two. We have already seen
one, with the new use of the Add method on MenuItems. The routine that handles the event has
a slightly different signature as well. Because you are not shadowing a system-defined event,
there is no need to use the Handles keyword. Continuing from the preceding snippet, if you
wrote a routine called myEvent to handle the File, Open selection, it would look like this:
Protected Sub myEvent(ByVal sender As System.Object, _
ByVal e As System.EventArgs)
MsgBox(“File->Open was selected!”)
End Sub
For the most part, using the click event works just fine. There are a few situations, however, in
which you should use your own event handler. If you need to react to a parent menu item being
selected, you have to use your own event handler because the click event does not fire for
those items. Also, if you want to write one routine that multiple menu items use, you can do
this quite easily with the event handler by just pointing different menu items at the same block
of code.
Next, you can write a simple stub routine that executes when the Popup event is fired:
Protected Sub PopupEventHandler(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles appContextMenu.Popup
End Sub
You use the sender object to determine which control fired the event, and then you can react
accordingly.
Forms, Menus, and Controls
119
CHAPTER 5
Say you have one text box control and one rich text box control on a form. You would like to
implement a context menu with each. It makes sense to implement Cut, Copy, and Paste com-
mands for both. For the rich text box, you want to add an additional item to the context menu
that exploits the ability of RichTextBox objects to load files. In this case, you can implement
some branching logic inside the Popup event to determine whether the control that launches the
menu is the rich text box and, if it is, to add the special-case Load File item. Listing 5.3 shows
a sample application with a text box (TextBox1) and a rich text box (RichTextBox1). In the
Popup event handler, you determine which control causes the context menu to display. If it is
the text box, you show a standard Edit menu, with Cut, Copy, and Paste commands. If it is the
rich text box, in addition to the standard Edit menu commands, you add a Load File item.
(Notice that this example leverages the Clipboard class that we talked about earlier in this
chapter, to implement the code behind the Cut, Copy, and Paste menu items.)
LISTING 5.3 Using the Popup Event and the ContextMenu Class
Public Class Form2
Inherits System.Windows.Forms.Form
End Sub
End If
MyBase.Dispose(disposing)
End Sub
Friend WithEvents TextBox1 As System.Windows.Forms.TextBox
Friend WithEvents RichTextBox1 As System.Windows.Forms.RichTextBox
Working with the .NET Namespaces
120
PART II
End Sub
#End Region
TextBox1.ContextMenu = appContextMenu
RichTextBox1.ContextMenu = appContextMenu
End Sub
End Sub
With appContextMenu
currItem = .MenuItems.Add(“Cu&t”, New EventHandler(AddressOf _
menuCut_Click))
currItem.Shortcut = Shortcut.CtrlX
currItem = .MenuItems.Add(“&Copy”, New EventHandler(AddressOf _
menuCopy_Click))
currItem.Shortcut = Shortcut.CtrlC
currItem = .MenuItems.Add(“&Paste”, New EventHandler(AddressOf _
menuPaste_Click))
currItem.Shortcut = Shortcut.CtrlV
End With
End If
End Sub
‘When closed, the dialog will have the selected file name
‘and path in its FileName property
Dim filePath As String = openFile.FileName()
‘If the FileName prop was empty (as can happen if the cancel
‘button is selected instead of the OK button), we just ignore
‘it; otherwise, the file name and path are passed into the
‘LoadFile method on the RichTextBox1 control. This should
‘load the file into the rich text box.
If Trim(filePath) <> “” Then
RichTextBox1.LoadFile(openFile.FileName())
End If
End Sub
End Class
5
FORMS, MENUS,
AND CONTROLS
FIGURE 5.13
A dynamic context menu.
FIGURE 5.14
A selected menu item.
NOTE
One popular use of the Select event is to display help text in a form’s status bar to
indicate the exact use of the menu item that is highlighted.
An Introduction to Controls
As mentioned earlier in the chapter, we will not dig into the details of the myriad controls that
ship with the .NET Framework. However, it is useful to talk about some generic control con-
cepts that apply to a variety of programming tasks in the .NET Framework.
Forms, Menus, and Controls
125
CHAPTER 5
When working with the .NET Framework controls, such as the listbox, treeview, and button
controls, programmers appreciate that they all expose a common and standard set of properties.
Gone are the days of setting a label control’s Caption property; if you need to alter the text on
a control, whether it is a command button or label, you set the Text property.
The .NET Framework also improves on the programmer’s ability to create a custom control.
These user controls can inherit from the rich class tree that supports all the standard controls in
the .NET Framework. This leaves the door wide open for developers to implement the controls
that they want by either implementing a standard control, inheriting and extending a standard
control, or implementing a control that diverges widely from existing functionality. In this sec-
tion, we’ll review some of the basics of programming with controls and talk about how to cre-
ate your own user controls.
Property Description
AllowDrop A Boolean property that indicates whether the control is capable of
receiving context from a drag-and-drop operation.
Anchor Specifies which sides of the control, if any, are anchored to the edges
of its parent container (from the AnchorStyle enumeration).
BackColor A color instance that indicates the background color of the control.
BackgroundImage An image instance that indicates the background image of the
control.
CausesValidation A Boolean that indicates whether the control causes validation.
ContextMenu Specifies a ContextMenu instance associated with the control.
Controls A collection (Control.ControlCollection) that represents all of the
controls contained within the control.
Cursor Displays when the mouse pointer is within the bounds of the control.
5
FORMS, MENUS,
AND CONTROLS
Dock A DockStyle value that indicates which sides of the parent container
the control is docked to.
Enabled A Boolean property that indicates whether the control is enabled.
HasChildren A Boolean property that indicates whether the control has child
controls.
Working with the .NET Namespaces
126
PART II
Likewise, you can get a good picture of a control’s functionality by examining its methods and
events. Table 5.12 shows some of the most important methods available in the Control class,
and Table 5.13 shows a sampling of the supported events. (To see the entire list of possible
members, refer to the .NET Framework documentation on the class libraries.)
Method Description
BringToFront Causes the control’s z-order to change to the front.
CreateControl Manually forces the control to be created.
CreateGraphics Creates the Graphics object associated with the control; encap-
sulates the control’s brush, font, foreground, and background
colors.
DoDragDrop Starts a drag-and-drop process.
FindForm Returns an instance of the form in which the control is contained.
Focus Places the focus on the control.
GetChildAtPoint Given a set of coordinates, returns a control instance that repre-
sents the child control found there (if any).
Forms, Menus, and Controls
127
CHAPTER 5
Event Description
BackColorChanged Fires when the BackColor property has been changed.
BackgroundImageChanged Fires when the BackgroundImage property has been changed.
Click Fires when the control is clicked.
ContextMenuChanged Fires when the control’s ContextMenu property has been
changed.
ControlAdded Fires when a new control is added.
ControlRemoved Fires when a control is removed.
DockChanged Fires when the control’s Dock property has been changed.
DoubleClick Fires when the control is double-clicked.
DragDrop Fires when a drag-and-drop event is completed.
DragEnter Fires when something is dragged into the interior of the con-
trol’s region.
DragLeave Fires when something that has been dragged into a control is
subsequently dragged out of the control’s region.
GotFocus Fires when the control receives the focus. 5
KeyDown Fires when a key is pressed down while the control has focus.
FORMS, MENUS,
AND CONTROLS
KeyPress Fires when a key is pressed while the control has focus.
KeyUp Fires when a key is released while the control has focus.
LostFocus Fires when the focus shifts from this control to another
control.
Working with the .NET Namespaces
128
PART II
Figure 5.15 shows a simple form with two command buttons. The OK button is anchored to
the top and to the left, and the Cancel button is anchored to the right and to the bottom.
FIGURE 5.15
Anchored controls before resizing a form.
After resizing the form, as shown in Figure 5.16, you can see what happens to the buttons.
FIGURE 5.16
Anchored controls after resizing a form.
Anchoring a control doesn’t just cause its relative position to change; it can also change the
size of the control. Continuing the example with the OK and Cancel buttons, if we elect to
anchor the OK button to the bottom as well as to the top and left, the OK button resizes itself
to remain true to its anchors, as shown in Figure 5.17.
FIGURE 5.17
Control resizing based on anchors.
By using the Or operator, you can specify multiple anchors at a time with the AnchorStyles
enumeration.
Working with the .NET Namespaces
130
PART II
NOTE
Some controls won’t resize, regardless of the anchors specified. The TextBox control,
for instance, relies on its font setup to dictate the possible sizes it can change to; for
instance, it does not allow partial lines of text to appear, so it must be resized in mul-
tiples of the font size.
Docking Controls
The Control class provides support for docking controls. Similar in concept to anchoring a
control, docking a control causes it to anchor to a section of the form and then be resized to fit
that section of the parent form. Docking is traditionally used with multipaned applications such
as Windows Explorer. In Windows Explorer, the treeview control of files and objects appears
on the left, with actual folder content on the right. This treeview control is docked to the left of
the window. Toolbars are another item that is commonly docked in an application.
Figures 5.18 and 5.19 show a form with a treeview, first docked to the left and then docked to
the top.
FIGURE 5.18
A treeview docked to the left.
The code to implement this is also similar to the code used with the Anchor property:
Me.TreeView1.Dock = System.Windows.Forms.DockStyle.Top
In the case of docking, you use the DockStyle enumeration, whose values are listed in Table
5.15.
Forms, Menus, and Controls
131
CHAPTER 5
FIGURE 5.19
A treeview docked to the top.
Name Description
Bottom Docks the control to the bottom edge of the form.
Fill Docks the control to all sides of the form; the control expands to always fill the
form’s interior client area upon resizing.
Left Docks the control to the left edge of the form.
None Removes any docking attributes from the control.
Right Docks the control to the right edge of the form.
Top Docks the control to the top edge of the form.
that might be used by an ActiveX control, which allows you to specify such things as the con-
trol’s background color, cursor, font, and tab order. To fully leverage an ActiveX control,
Working with the .NET Namespaces
132
PART II
however, you have to custom-code the properties and methods that you need. This means that
the typical usage for the AxHost class is as a base class from which you inherit to derive some
basic operations.
Form
X ActiveX
Control
AxHost Wrapper
Form
ActiveX
Control
FIGURE 5.20
The AxHost wrapper.
After the wrapper assemblies have been created, you can use the ActiveX control on forms by
referencing its runtime callable wrapper from the project. Microsoft has distributed a tool with
the .NET Framework SDK—called Aximp.exe—that automatically creates a set of wrapper
assemblies for a given ActiveX control. (Full details on the operation of Aximp.exe can be
found in the .NET Framework MSDN documentation.)
If you need a control that cannot benefit directly from any of the existing controls, you should
create a completely custom control. Otherwise, inheriting from the UserControl class allows
you to create a composite control; in that case, you can pick and choose from the entire control
palette and select pieces of control functionality to include in your control.
From this point on, the code you write depends on exactly what you want the control to do.
You implement your custom code by overriding the existing properties and methods of the
control class and adding the appropriate logic to customize your control’s behavior. Of course,
you can also add new properties, methods, and events.
MyBase.OnPaint(pe)
End Sub
Working with the .NET Namespaces
134
PART II
This code snippet simply references the ClientRectangle structure (a property that is defined
on the base Control class) to instruct the paint event to draw the words “Hello, world!” using
the entire available control surface.
.
.
.
Get
Return orientText
End Get
End Property
End Class
Adding a method is just as easy. You simply create a new public method and include logic
inside it to affect the control in some way:
Public Sub RestoreDefault()
orientText = “Horizontal”
End Sub
Adding an event is a little more involved, but it is still quite approachable. The first step is to
declare the event:
Public Event OrientationChanged(ByVal sender As Object, _
ByVal ev As EventArgs)
Then, you need to create the subroutine that handles the event (by convention, such subroutines
start with On, as in OnOrientationChanged):
Forms, Menus, and Controls
135
CHAPTER 5
The final step is to actually write the code that raises the event:
Public Property Orientation() As String
Get
Return orientText
End Get
End Property
Next, the control is initialized and positioned on the form, inside the form’s
InitializeComponents subroutine:
‘TextControl
Me.TestControl.Location = New System.Drawing.Point(44, 80)
Me.TestControl.Name = “TextControl1”
Me.TestControl.Size = New System.Drawing.Size(156, 20)
Me.TestControl.TabIndex = 0
That is all that is necessary to get the base instance of the control created and placed on a
form.
trols. This listing includes the code fragments discussed in the preceding section, and it
includes an enumeration called OrientationMode, to help ease get/set operations with the
Orientation property. When this control, called CustomControl, is included on a form, you
Working with the .NET Namespaces
136
PART II
should be able to change the orientation of the displayed text from a horizontal to a vertical
position and back again. Because you are raising an OnOrientationChange event, which in
turn invalidates the control and forces a repaint, the effect on the text should be immediate.
After being compiled, the custom control is referenced inside the CustomControlLibrary
assembly.
Case OrientationMode.Vertical
Dim textDirection As StringFormat = New StringFormat()
‘Paint the Text property on the control; note the use of the
‘textDirection instance to specify vertical text
pe.Graphics.DrawString(TEXT_STRING, Font, _
New SolidBrush(ForeColor), rect, textDirection)
End Select
End Sub
Get
AND CONTROLS
Return orientText
End Get
Working with the .NET Namespaces
138
PART II
orientText = Value
End Set
End Property
End Class
Listing 5.5 shows the code for one possible form-based consumer of the CustomControl.
End Sub
Me.TextControl.TabIndex = 0
AND CONTROLS
‘
‘RadioButton2
‘
Me.RadioButton2.Location = New System.Drawing.Point(204, 36)
Working with the .NET Namespaces
140
PART II
End Sub
#End Region
End Sub
End Class
FIGURE 5.21
The TextControl custom control.
Then, for each control that will become part of the composite control, you add a reference. The
following example continues with the scenario of creating a login control that is created from
two textbox controls, two label controls, and a button control:
Friend WithEvents TextBox1 As System.Windows.Forms.TextBox
Friend WithEvents Label1 As System.Windows.Forms.Label
Friend WithEvents Label2 As System.Windows.Forms.Label
Friend WithEvents TextBox2 As System.Windows.Forms.TextBox
Friend WithEvents Button1 As System.Windows.Forms.Button
Next, you add these controls to the overall UserControl container by calling the AddRange 5
method:
FORMS, MENUS,
AND CONTROLS
This takes care of adding the constituent controls to the composite control’s control collection.
Composite controls, at this point, start to follow the design pattern for custom controls, with
code being added to handle events, properties, and methods. The sample application shown in
the following section demonstrates creating and consuming a composite control.
FIGURE 5.22
The EventLogControl composite control.
Code Walkthrough
LISTING 5.6 Code for the EventLogControl Object
All composite controls should inherit from UserControl. This gives you automatic support for
all the base Control properties, events, and methods, and ensures that the new control will
function seamlessly in the IDE.
Here you can see where we set up an enumeration, LogType, for specifying the event log to
view. We also publish an event, LogSourceChanged, which is fired whenever the log is changed
on the control.
Public Class EventLogControl
Inherits System.Windows.Forms.UserControl
ByVal ev As EventArgs)
Working with the .NET Namespaces
144
PART II
So that the new control is easy to work with in a drag-and-drop environment such as Visual
Studio .NET, you anchor the listbox, button, and label controls to allow for easy, dynamic
resizing at design time.
‘Anchor the listbox and button controls to enable easy
‘resizing
ListEvents.Anchor = AnchorStyles.Left Or AnchorStyles.Top _
Or AnchorStyles.Right Or AnchorStyles.Bottom
‘ButtonRefresh
‘
Me.ButtonRefresh.Location = New System.Drawing.Point(288, 280)
Me.ButtonRefresh.Name = “ButtonRefresh”
Working with the .NET Namespaces
146
PART II
End Sub
#End Region
This is the class property, which allows users to select a log to view. Note that you raise the
LogSourceChanged event here.
‘Raise an event
OnLogSourceChanged(New EventArgs())
End Set
End Property
ListEntries(EventLog1.Entries)
‘Invalidate()
RaiseEvent LogSourceChanged(Me, e)
End Sub
The ListEntries routine is used internally by the control to actually enumerate the
EventLogEntry objects contained in the EventLogEntryCollection object. All this is first set
up by specifying the log.
Private Sub ListEntries(ByVal entriesCol As EventLogEntryCollection)
Dim entry As EventLogEntry
count = 0
Next
End Sub
Private Sub EventLogControl_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
End Sub 5
FORMS, MENUS,
AND CONTROLS
End Sub
‘
‘fileMenuLog
‘
Me.fileMenuLog.Index = 0
Me.fileMenuLog.MenuItems.AddRange(New _
System.Windows.Forms.MenuItem() _
Working with the .NET Namespaces
150
PART II
End Sub
Forms, Menus, and Controls
151
CHAPTER 5
#End Region
End Sub
Summary
In this chapter, we have looked at the specific classes in the System.Windows.Forms name-
space that can be used to quickly and efficiently assemble the user interface pieces of an appli- 5
cation. By now, you should have a firm grasp of the following:
FORMS, MENUS,
AND CONTROLS
• Responding to events
• Working with classes that inherit from the Control class
• Creating your own controls
The Visual Studio .NET environment automates and simplifies a lot of the programming activi-
ties discussed in this chapter. You should investigate those capabilities so that you can avoid
having to write all the code yourself. This chapter should give you a much greater appreciation
and understanding for the underlying classes that make the .NET Framework so useful.
Font, Text, and Printing CHAPTER
6
Operations
IN THIS CHAPTER
• Key Classes Related to Font, Text, and Printing
Operations 154
• Fonts 156
• Printing 179
Font, printing, and text manipulation together represent one of the more complex parts of the
Windows operating system. Fortunately, the .NET Framework Class Library simplifies this for
all of us. The classes presented in this chapter should give you enough insight into this technol-
ogy to become very productive, very quickly.
The chapter focuses primarily on the namespaces System.Drawing.Printing,
System.Drawing.Text, and to some extent on System.Drawing. Fonts are presented first, fol-
lowed by a simple, Notepad-like sample application. We then discuss printing with the library
and extend the font sample application by adding printing capabilities.
After reading this chapter, you will be able to
• Work with individual fonts and font families
• Retrieve all fonts installed on a system
• Modify text as output to the screen using the Font class and its associated members
• Send output to a print device
• Control printing, including paper source, page orientation, and margins
• Execute print preview functionality
OPERATIONS
PRINTING
Working with Fonts and Text
System.Drawing
Font The Font class enables you to define a specific format for
text, including the following: font face, size, and style
attributes.
FontFamily The FontFamily class abstracts a group of typefaces hav-
ing a similar design but a certain variation in style.
System.Drawing.Text
InstalledFontsCollection With the InstalledFontsCollection, you can reference
all the fonts installed on a specific system.
PrivateFontsCollection The PrivateFontsCollection method enables you to
create and add custom fonts into memory for use by your
application.
Printing
System.Drawing.Printing
Margins The Margins class enables you to manipulate the size of
the margins (the space surrounding the text) of a printed
page.
PageSettings The PageSettings class enables you to specify print set-
tings for one specific page.
PaperSize The PaperSize class lets you represent the size of piece
of paper.
PaperSource The PaperSource class enables you to choose the paper
tray from which the printer gets its paper for printing a
given document.
PreviewPageInfo The PreviewPageInfo class enables you to specify print
preview information for a single page.
PreviewPrintController The PreviewPrintController class enables you to dis-
play a document as a series of images prior to printing.
PrintDocument The PrintDocument class enables you to control output to
a given printer.
PrinterSettings The PrinterSettings class enables you to set various
properties of the printer and thus control how documents
are printed.
Working with the .NET Namespaces
156
PART II
Fonts
A font describes the way a string of text appears on a device—in most cases, a monitor or a
printer. Fonts can vary in size, weight, and style. Bold fonts, for instance, are said to have a
heavier weight than normal fonts. Windows automatically installs a number of standard fonts;
literally thousands of fonts are available to users.
Fonts are known by their typeface name and attributes. For example, Courier Bold 12 point is
a common fixed-pitch, or monospaced, font. Typically, nonscalable fonts actually demand a
new font for each attribute change. For example, if you modify the point size of a font or
change its characteristics to bold, italic, or underline, Windows accesses a physically different
font for each version.
Font, Text, and Printing Operations
157
CHAPTER 6
In many cases, however, Windows can synthesize one font from another. For example, 6
Windows can usually do a good job of creating an underlined font from a normal font, so you
OPERATIONS
PRINTING
occurs. For example, when Windows scales a raster font from a small size to a very large one,
the result can be truly ugly because slight imperfections in a letter’s form become pronounced
as the letter increases in size.
A font family describes a general class of font. In Windows, the term family is used to describe
classifications of fonts, and the terms typeface or facename are used to identify a set of fonts
that share a common character set and design but vary in attributes such as size, weight, slant,
and so on. Every font used by Windows falls into one font-family category.
A font is really a tool that takes a character as input and enables you to determine how that
character should be displayed in a given device context. The .NET Font and FontFamily
classes encapsulate and simplify the use of fonts. With a few simple objects, you can write
some very useful code to enable users to interact with text.
Font Attributes
When working with fonts, it is important to first understand some of the basic characteristics
and dimensions of a font and to define some of the terms used in describing these attributes.
To create fonts, Windows uses various font technologies, each with different advantages and
disadvantages. The following describes the three key font technologies in Windows:
• Raster fonts—A raster font is a series of character bitmaps. When a character needs to
be displayed, the bitmap for the character is copied to the device. The advantage is that
fonts can be optimized to look good on the device for which they are created. The disad-
vantage is that the font is not easily scalable.
• Vector (or stroke) fonts—Vector fonts are made up of graphical elements represented by
GDI function calls. Because they are represented mathematically, they can be scaled eas-
ily with good results. For the most part, vector fonts have been replaced with TrueType
fonts.
• Scalable fonts—Scalable fonts describe their characters mathematically using vectors
and curves. These are the TrueType fonts built in to Windows. These fonts can be scaled
to virtually any size without loss in quality. The disadvantages are at small sizes, they do
not look as good as raster fonts, and they are somewhat slower to draw (not a big deal
with today’s horsepower).
A font’s pitch can be either fixed or variable. In a fixed-pitch font, the width of each character
cell is equal. In a variable-pitch font, the spacing varies depending on the character. For exam-
ple, the letter “I” is narrower than the letter “W.” In fact, a simple character cell actually has a
Working with the .NET Namespaces
158
PART II
great many attributes and characteristics. (It is beyond the scope of this chapter to delve much
further into font dimensions, because our focus is on writing productive code.)
Font Classes
In .NET, the Font class encapsulates a font and is found in the System.Drawing namespace. At
first, this sounds strange. Why would a Font class be in a drawing namespace? The answer is
quite simple: Fonts, like everything else in the user interface, have to be drawn to the display.
In fact, the drawing functions are used to render fonts to a device or drawing surface.
Text is defined by font face, size, and style attributes. The following is a simple example of
how to manipulate the various attributes of a font at runtime. The code assumes a label control
(labelExample) has been placed on a form. We then create a Font instance based on a font-
family name, size, and a font style (here we use bold). Then, because .NET uses the same
libraries throughout, we simply set the Font property of the label control to equal our new
Font instance. The results are that the text inside the label control is now displayed using our
new Font instance.
‘create a new instance of the font object
Dim myFont as New Font(familyName:=”Tahoma”, emSize:=18, _
style:=FontStyle.Bold, unit:=GraphicsUnit.Point)
To create an instance of a font, we can choose from a variety of constructors in the .NET tool-
box. Table 6.2 lists these constructors and provides a description of each. You can see that the
table is a little long, but it does provide all the right combinations. Most of the constructors are
variations on a theme.
PRINTING
emSize(Single): The size of the new font.
Note: Use this constructor to create a new Font object directly from a FontFamily name
and a specific size.
New Font (ByVal family as FontFamily, ByVal emSize as Single, Byval style as
FontStyle)
family(FontFamily): A valid FontFamily object.
emSize(Single): The size of the new font.
Style(FontStyle); A valid FontStyle enumeration member (Bold, Italic, and so on).
Note: Use this constructor to create a new Font object from a FontFamily object, a specific
size, and specific style. You can get FontStyle and FontFamily objects in a variety of
ways, including straight from a string (see example code).
New Font (ByVal family as FontFamily, ByVal emSize as Single, ByVal unit as
GraphicsUnit)
family(FontFamily): A valid FontFamily object.
emSize(Single): The size of the new font.
unit(GraphicsUnit): A valid GraphicsUnit enumeration member (Point, Pixel, Inch, and
so on).
Note: Use this constructor to create a new Font object from a FontFamily object, a specific
size, and a GraphicsUnit object. The GraphicsUnit value indicates how the font size is
calculated.
New Font (ByVal familyName as String, ByVal emSize as Single, ByVal style as
FontStyle)
familyName(String): A string that represents a FontFamily. For example, “Tahoma.”
emSize(Single): The size of the new font.
Style(FontStyle): A valid FontStyle enumeration member (Bold, Italic, and so on).
Note: Use this constructor to create a new Font object directly from a FontFamily name, a
specific size, and a specific style (Bold, Italics, and so on).
New Font (ByVal familyName as String, ByVal emSize as Single, ByVal unit as
GraphicsUnit)
familyName(String): A string that represents a FontFamily. For example, “Tahoma.”
emSize(Single): The size of the new font.
unit(GraphicsUnit): A valid GraphicsUnit enumeration member (Point, Pixel, Inch, and
so on).
Note: Use this constructor to create a new Font object directly from a FontFamily name, a
specific size, and a GraphicsUnit object. The GraphicsUnit value indicates how the font
size is calculated.
Working with the .NET Namespaces
160
PART II
PRINTING
specific size, a FontStyle, a GraphicsUnit object, and a gdiCharSet.
New Font (ByVal family as FontFamily, ByVal emSize as Single, ByVal style as
FontStyle, ByVal unit as GraphicsUnit, ByVal gdiCharSet as Byte,
gdiVerticalFont as Boolean)
family(FontFamily): A valid FontFamily object.
emSize(Single): The size of the new font.
Style(FontStyle): A valid FontStyle enumeration member (Bold, Italic, and so on).
unit(GraphicsUnit): A valid GraphicsUnit enumeration member (Point, Pixel, Inch, and
so on).
gdiCharSet(Byte): A GDI character set value found in WinGDI.h.
gdiVerticalFont(Boolean): Indicates if the font is derived from a GDI vertical font.
Note: Use this constructor to create a new font object directly from a FontFamily, a specific
size, a FontStyle, a GraphicsUnit object, a gdiCharSet value, and a indication of
gdiVerticalFont.
New Font (ByVal familyName as String, ByVal emSize as Single, ByVal style as
FontStyle, ByVal unit as GraphicsUnit, ByVal gdiCharSet as Byte,
gdiVerticalFont as Boolean)
familyName(String): A string that represents a FontFamily. For example, “Tahoma.”
family(FontFamily): A valid FontFamily object.
emSize(Single): The size of the new font.
Style(FontStyle): A valid FontStyle enumeration member (Bold, Italic, and so on).
unit(GraphicsUnit): A valid GraphicsUnit enumeration member (Point, Pixel, Inch, and
so on).
gdiCharSet(Byte): A GDI character set value found in WinGDI.h.
gdiVerticalFont(Boolean): Indicates if the font is derived from a GDI vertical font.
Note: Use this constructor to create a new font object directly from a FontFamily name, a
specific size, a FontStyle, a GraphicsUnit object, a gdiCharSet value, and an indication
of gdiVerticalFont.
When creating a font instance, the FontStyle enumeration allows us to indicate a standard for-
mat for the text. Table 6.3 displays the members of the FontStyle enumeration. A text exam-
ple is provided alongside each enumeration member. Note that the font family used in the
examples is Times New Roman.
Working with the .NET Namespaces
162
PART II
Font Collections
We often need to work with fonts as a group, sometimes to display all the installed fonts on a
system to the user for selection or to output a document’s used fonts to a dialog box. To work
with groups of fonts, we use the System.Drawing.Text namespace. This namespace exposes
to us two key classes: InstalledFontCollection and PrivateFontCollection. These classes
enable us to create and use collections of fonts.
The InstalledFontCollection class behaves as its name indicates; it returns a collection of
FontFamily objects that represent the fonts installed on a given system. The following code
creates an instance of the collection, loops through it, and adds the font-family names to a list
box control (listBox1).
‘local scope
Dim myFonts As System.Drawing.Text.InstalledFontCollection
Dim i As Integer
‘loop through the font families and add to the list box
For i = 1 To UBound(myFonts.Families)
listBox1().Items.Add(myFonts.Families(i).Name)
Next
Private Fonts
The PrivateFontCollection allows us to install a private version of an existing font without
replacing the system version of the font. For example, we could create a private version of the
Arial font in addition to the Arial font that the system uses. The PrivateFontCollection can
also be used to install fonts that don’t exist on a system. This is a temporary font installation
that doesn’t affect the system-installed collection. This is great when you want to use custom
fonts in your application but not install them onto users’ machines.
Font, Text, and Printing Operations
163
CHAPTER 6
Listing 6.1 provides an example of how to load a font from a file into the private font’s collec- 6
tion and then use that font.
‘local scope
Dim myPrivateFonts As System.Drawing.Text.PrivateFontCollection
Dim myFont As Font
‘create of font object from the font family in the private collection
myFont = New Font(family:=myPrivateFonts.Families(0), emSize:=12, _
style:=FontStyle.Regular)
End Sub
Font Classifications
Fonts can be classified into what is called generic font families. These families are independent
of the font families we’ve been discussing. In .NET, a generic font family represents a higher-
level (or parent) category to which all fonts must belong. All fonts (or font families) belong to
one generic font family. Table 6.4 lists the GenericFontFamilies enumeration members and
provides a description of each.
Hotkey Prefix
A hotkey prefix enables users to use a keyboard sequence (usually CTRL+HotKey or
ALT+HotKey) to access functionality represented by text displayed on the screen. The
HotKeyPrefix enumeration stores the possible values for indicating how these keys should be
displayed to the user.
The HotKeyPrefix enumeration is used by the StringFormat class. The StringFormat class
specifies the Windows Forms string class format, which Windows Forms uses to store string
objects. Table 6.5 describes the enumeration’s members.
Member Description
Hide Tells the application not to display a specific hotkey.
None Indicates that there is no hotkey for a specific function.
Show Displays the hotkey prefix.
Text Rendering
A number of options are available to you for indicating how you want .NET to draw your text
to the screen. These options can be set using the TextRenderingHint enumeration. Options
range from the fast-performing but low-quality SingleBitPerPixel to the clearer but slower-
performing ClearType. This enumeration is used to set the TextRenderingHint property of a
Graphics instance used to output text to a screen. Table 6.6 presents a visual representation of
each member using a Bold, 18-point Tahoma font.
Font, Text, and Printing Operations
165
CHAPTER 6
AntiAliasGridFit
ClearTypeGridFit
SingleBitPerPixel
SingleBitPerPixelGridFit
SystemDefault
For text editing, we use the RichTextBox control. This control is set to size with the form. This
is done by setting its dock property to Fill.
Figure 6.1 shows an example of FontPad’s main form.
FIGURE 6.1
FontPad main form.
Code Walkthrough
The code behind the form is nearly as straightforward as the form itself. Listing 6.2 walks you
through the code.
The code listing starts by defining the form and its controls.
OPERATIONS
PRINTING
‘Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
OPERATIONS
Me.Menu = Me.mainMenu1
PRINTING
Me.Text = “FontPad”
End Sub
This procedure gets called both when the form loads and when users apply changes to the
application’s font settings.
Public Sub resetRichTextBox()
‘local scope
Dim font As Font
Dim fontStyle As FontStyle
Dim fontFamily As FontFamily
Dim styleType As System.Type
‘get a font style object (note: must turn off option strict)
fontStyle = fontStyle.Parse(enumType:=styleType, _
value:=g_FontStyle)
End Sub
Working with the .NET Namespaces
170
PART II
End Sub
When users click the Font item on the Format menu, the following code executes to show the
Font Selection dialog box.
Private Sub menuItemFont_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles menuItemFont.Click
‘local scope
Dim dialogFont As FontSettings
End Sub
When users click the Exit item on the File menu, the application ends.
Private Sub menuItemExit_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles menuItemExit.Click
End Sub
Font, Text, and Printing Operations
171
CHAPTER 6
#End Region 6
OPERATIONS
PRINTING
FontPad Font Settings Dialog Box
The Font Settings dialog box is also similar in feel to that of Notepad’s. For example, the set-
tings apply to the application as a whole (this is a text editor and not a word processor). Users
can browse a list of font families, styles, and sizes (see Figure 6.2). As a user selection
changes, an example of the most recent selection is displayed inside the sample group box.
This gives users a visual cue before selecting a new font setting.
FIGURE 6.2
FontPad: Font Settings form (formFontSettings.vb).
Code Walkthrough
Again, .NET makes the code rather simple. Listing 6.3 represents the code behind the form.
The listing starts with the form and control setup code.
End Sub
OPERATIONS
Me.labelSample.TabIndex = 0
PRINTING
Me.labelSample.Text = “AaBbCcDd - WwXxYyZz”
Me.labelSample.TextAlign = System.Drawing.ContentAlignment.MiddleCenter
Me.listBoxSizes.Location = New System.Drawing.Point(320, 28)
Me.listBoxSizes.Size = New System.Drawing.Size(56, 95)
Me.listBoxSizes.TabIndex = 2
Me.buttonOk.Location = New System.Drawing.Point(384, 28)
Me.buttonOk.TabIndex = 3
Me.buttonOk.Text = “OK”
Me.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel
Me.buttonCancel.Location = New System.Drawing.Point(384, 60)
Me.buttonCancel.TabIndex = 4
Me.buttonCancel.Text = “Cancel”
Me.listBoxFamilies.Location = New System.Drawing.Point(12, 28)
Me.listBoxFamilies.Size = New System.Drawing.Size(196, 95)
Me.listBoxFamilies.TabIndex = 0
Me.listBoxStyles.Location = New System.Drawing.Point(216, 28)
Me.listBoxStyles.Size = New System.Drawing.Size(96, 95)
Me.listBoxStyles.TabIndex = 1
Me.label2.Location = New System.Drawing.Point(216, 12)
Me.label2.Size = New System.Drawing.Size(120, 20)
Me.label2.TabIndex = 7
Me.label2.Text = “Font Style”
Me.label4.Location = New System.Drawing.Point(320, 12)
Me.label4.Size = New System.Drawing.Size(52, 20)
Me.label4.TabIndex = 9
Me.label4.Text = “Size”
Me.groupBox1.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.labelSample})
Me.groupBox1.Location = New System.Drawing.Point(12, 136)
Me.groupBox1.Size = New System.Drawing.Size(364, 112)
Me.groupBox1.TabIndex = 5
Me.groupBox1.TabStop = False
Me.groupBox1.Text = “Sample”
Me.label3.Location = New System.Drawing.Point(12, 12)
Me.label3.Size = New System.Drawing.Size(120, 20)
Me.label3.TabIndex = 8
Me.label3.Text = “Font Family”
Me.AcceptButton = Me.buttonOk
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog
Me.CancelButton = Me.buttonCancel
Me.ClientSize = New System.Drawing.Size(473, 262)
Working with the .NET Namespaces
174
PART II
End Sub
The form’s load event initializes all the controls on the form. It uses the
InstalledFontsCollection class to load the font-family list box (listBoxFamilies). The
font styles are loaded from the FontStyle enumeration member names using a method from
the Reflection classes. The size list box values are hard-coded from an array on the form
load.
Last, we set the selected items for each list box to be that of the application’s default values.
Notepad actually stores these user-configurable values between each use. We will leave this
code up to you—perhaps you could create an XML file for these settings. FontPad’s default
settings are stored in global variables.
Private Sub formFontSettings_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
‘loop through the font families and add to the list box
For i = 1 To UBound(myFonts.Families)
listBoxFamilies().Items.Add(myFonts.Families(i).Name)
Next
PRINTING
listBoxStyles().Items.Add(myArray(i))
Next
End Sub
The next procedure, SetSampleText, is responsible for keeping the label control on the font
settings form in synch with user selections. Each list box’s index change event calls this sub-
routine. The code itself creates a Font instance from the values selected in the three list boxes
and applies this object to the sample label’s Font property.
Private Sub setSampleText()
‘local scope
Dim font As Font
Dim fontStyle As FontStyle
Dim fontFamily As FontFamily
Dim styleType As System.Type
Dim fontFamilyName As String
Dim fontSize As String
Dim fontStyleName As String
Else
End If
Else
End If
Else
End If
‘get a font style object (note: must turn off option strict)
fontStyle = fontStyle.Parse(enumType:=styleType, value:=fontStyleName)
emSize:=CSng(fontSize), _ 6
style:=fontStyle, _
OPERATIONS
PRINTING
‘reset the font on the label control
labelSample().Font = font
End Sub
When a user clicks the OK button, the global font values are updated and FontPad’s
ResetRichTextBox procedure is called, thus applying the new settings.
End Sub
When a user clicks the Cancel button, the form simply closes without applying any changes.
Private Sub buttonCancel_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonCancel.Click
End Sub
The following are the change events for the various list boxes. Each simply makes a call to
SetSampleText to update the label control used as a visual cue.
End Sub
End Sub
End Sub
#End Region
End Class
FontPad Module
We use a standard module in the FontPad application to store global variables. Listing 6.4 rep-
resents our three font settings and a reference to the main FontPad form.
‘declare variables that have global scope & set the defaults
Public g_FontFamily As String = “Courier New”
Public g_FontStyle As String = “Regular”
Public g_FontSize As String = “10”
End Module
Font, Text, and Printing Operations
179
CHAPTER 6
Printing 6
OPERATIONS
PRINTING
control and/or the Win32 API. The former did not always provide enough flexibility and the
later sapped the productivity level to which VB developers have become accustomed.
System.Drawing.Printing to the rescue! This namespace provides us with a ton of flexibility
while encapsulating the low-level stuff that can often bog down programmers.
2. Set properties of the PrintDocument class to describe how and where to print. Of course,
in an actual application, we would not hard-code the printer’s name but would query the
system for the default printer instead.
‘tell the print document object the name of the printer
printDocument.PrinterSettings.PrinterName = _
“Epson Stylus COLOR 640 ESC/P 2”
3. Set an event handler to intercept calls from a delegate when a page is printed. We do this
by telling PrintDocument that we want to intercept its PrintPage event. This ensures
that the procedure PrintPage_handler will receive the PrintDocument’s PrintPage
event. This event gets fired for every page that needs to be printed.
AddHandler printDocument.PrintPage, AddressOf Me.printPage_handler
4. Call the Print method to print the document and raise the print event.
printDocument.Print()
Working with the .NET Namespaces
180
PART II
5. Define an event handler routine to process the printing. Note: The handler’s
PrintPageEventArgs passes the appropriate Graphics object used to send output to the
printer.
Private Sub printPage_handler(ByVal sender As Object, _
ByVal ev As System.Drawing.Printing.PrintPageEventArgs)
End Sub
Printer Configuration
The Printing namespace provides a number of enumerations that enable us to manage various
printer configuration settings. This section provides an overview of what is available. It is
arranged into a number of tables that describe the enumerations and their members.
Table 6.7 lists the Duplex enumeration members. This enables you to read and write the
printer’s duplex setting. Duplex, in printing, describes how printers print on both sides of the
paper. This enumeration provides values for the PrinterSettings class’s Duplex property. A
PrinterSettings instance is used to control how a printer is configured to send output.
PaperKind refers to the physical type of paper loaded into the printer. The enumeration is used
by the PaperSize class for its Kind property. PaperSize itself is used by the PrinterSettings
class when specifying the PaperSizes property. The PaperSizes property is a collection of
PaperSize objects that indicate the various sizes of paper the given printer supports. Table 6.8
lists some of the key PaperKind members most commonly used in the United States.
Font, Text, and Printing Operations
181
CHAPTER 6
PRINTING
Folio Standard folio paper (8.5 in. by 13 in.)
Ledger Standard ledger paper (17 in. by 11 in.)
Legal Standard legal paper (8.5 in. by 14 in.)
Letter Standard letter paper (8.5 in. by 11 in.)
Tabloid Standard tabloid paper (11 in. by 17 in.)
Table 6.9 documents the PaperSourceKind enumeration members. Paper sources can be
thought of as paper trays and the like on a physical printer. The enumeration is used by the
PaperSource class for its Kind property. This property is used to both return and to set the
source of the paper used when printing. PaperSource itself is used by the PrinterSettings
class when specifying the PaperSources property. The PaperSources property is a collection
of PaperSource objects that indicates the various paper sources a given printer supports. The
most common setting is AutomaticFeed, which tells most printers that they should handle the
source from where the paper comes.
Member Description
AutomaticFeed Indicates that the source of the paper is automatically fed paper.
Cassette Indicates that the source of the paper is a paper cassette.
Custom Indicates that the source of the paper is a printer-specific paper source.
Envelope Indicates that the source of the paper is an envelope.
FormSource Indicates that the source of the paper is the printer’s default input bin.
LargeCapacity Indicates that the source of the paper is the printer’s large-capacity bin.
LargeFormat Indicates that the source of the paper is large-format paper.
Lower Indicates that the source of the paper is the lower bin of a printer.
Note: If the printer has only one bin, it will be used.
Manual Indicates that the source of the paper is manually fed paper.
ManualFeed Indicates that the source of the paper is manually fed envelopes.
Middle Indicates that the source of the paper is the middle bin of a printer.
SmallFormat Indicates that the source of the paper is small-format paper.
TractorFeed Indicates that the source of the paper is a tractor feed.
Upper Indicates that the source of the paper is the upper bin of a printer.
Note: If the printer has only one bin, it will be used.
Working with the .NET Namespaces
182
PART II
Table 6.10 lists the various printer resolution settings that are available to your applications.
The PrinterResolutionKind enumeration members enable you to tell the printer to output
documents based on some standard resolutions. The enumeration is used by the
PrinterResoultion class for its Kind property. PrinterResoultion itself is used by the
PrinterSettings class when specifying the PrinterResolutions property. The
PrinterResolutions property is a collection of PrinterResolution objects that indicate the
various resolutions supported by a given printer. These properties are handy when users want a
quick version, or draft, of their documents for proofing, or a slower, but high-quality version
for release.
Member Description
Custom Specifies a custom printer resolution setting. If this is set to custom, use
the x and y properties of the PrinterResolution class to determine the
printer resolution in the horizontal and vertical directions, respectively.
Draft Specifies the draft printer resolution setting.
High Specifies the high printer resolution setting.
Low Specifies the low printer resolution setting.
Medium Specifies the medium printer resolution setting.
Table 6.11 lists the PrintRange enumeration members. The enumeration is used to represent
the portions of the document that should be output to the printer. This enumeration is used by
the PrinterSettings class to indicate the range that should be printed.
Member Description
AllPages Indicates that all pages in the document should be printed.
Selection Indicates that only the selected pages in the document should be printed.
SomePages Indicates that only some pages (those from x to y) in the document should
be printed. If using this value, reference the PrinterSettings class,
fromPage and toPage properties.
to the screen as a series of images. This class makes extensive use of the PreviewPageInfo 6
class. The PreviewPageInfo class represents the print preview information of one specific
OPERATIONS
PRINTING
application:
1. First, you declare a number of variables to represent the classes you will need.
Dim printDocument As System.Drawing.Printing.PrintDocument
Dim previewController As System.Drawing.Printing.PreviewPrintController
Dim pageInfoArray() As System.Drawing.Printing.PreviewPageInfo
Dim i As Integer
5. Next, indicate an event handler for each page that is output. Note: The event handler can
be identical to the one defined in the basic printing walkthrough. Perhaps one exception
is that you might want to add the line ev.Graphics.ScaleTransform(sx:=0.25,
sy:=0.25) to the printPage_handler routine. This code scales the output of the
Graphics object by 25%, which makes viewing print images a little easier.
AddHandler printDocument.PrintPage, AddressOf Me.printPage_handler
6. Now call the Print method of the PrintDocument instance. This raises a call to the event
handler and forces the printed content into the preview print controller.
printDocument.Print()
7. All that is left is to view the preview images. After the document is finished printing, the
PreviewPrintController instance that you set as the target of the print output is filled
with a collection of PreviewPageInfo objects. There is one item in the collection per
printed page. Return the images by calling the GetPreviewPageInfo method of the
PreviewPrintController object. You can then loop through the array and output each
image into a picture box defined on a form.
‘return an array of PreviewPageInfo objects from the print controller
pageInfoArray = previewController.GetPreviewPageInfo()
Code Walkthrough
The additional code behind each new menu item’s click event is in Listing 6.5.
‘local scope
Dim dialogPageSetup As PageSetup
End Sub
When the user fires the menuItemPrint_Click event, we get the text from the RichTextBox
control (printString = Me.richTextBox.Text). This gives us a text string to load into the
StreamReader class. The StreamReader will be used when we actually send output to the
printer. Of course, we then show the dialog box to the user.
Private Sub menuItemPrint_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles menuItemPrint.Click
‘local scope
Dim dialogPrint As Print
End Sub
Page Setup
The Page Setup dialog box enables users to select the paper size, the printer’s paper source, the
page orientation, and the page margins. Figure 6.3 shows the Page Setup dialog box. Note its
similarities to Notepad’s Page Setup dialog box.
Working with the .NET Namespaces
186
PART II
FIGURE 6.3
FontPad: Page Setup form (formPageSetup.vb).
Code Walkthrough
The code behind the Page Setup form can be read in Listing 6.6.
End Sub
PRINTING
Private WithEvents groupBox1 As System.Windows.Forms.GroupBox
Private WithEvents textBoxBottom As System.Windows.Forms.TextBox
Private WithEvents comboBoxSize As System.Windows.Forms.ComboBox
Private WithEvents buttonCancel As System.Windows.Forms.Button
Private WithEvents label4 As System.Windows.Forms.Label
Private WithEvents label5 As System.Windows.Forms.Label
Private WithEvents label6 As System.Windows.Forms.Label
Private WithEvents label7 As System.Windows.Forms.Label
Private WithEvents textBoxRight As System.Windows.Forms.TextBox
Private WithEvents label1 As System.Windows.Forms.Label
Private WithEvents label2 As System.Windows.Forms.Label
Private WithEvents label3 As System.Windows.Forms.Label
Private WithEvents textBoxTop As System.Windows.Forms.TextBox
Private WithEvents comboBoxSource As System.Windows.Forms.ComboBox
Private WithEvents buttonOk As System.Windows.Forms.Button
Private WithEvents textBoxLeft As System.Windows.Forms.TextBox
Private WithEvents radioButton1 As System.Windows.Forms.RadioButton
Private WithEvents radioButton2 As System.Windows.Forms.RadioButton
Private WithEvents radioButtonLandscape As System.Windows.Forms.RadioButton
Private WithEvents radioButtonPortrait As System.Windows.Forms.RadioButton
PRINTING
Me.label2.Size = New System.Drawing.Size(4, 0)
Me.label2.TabIndex = 1
Me.label2.Text = “label2”
‘
‘label1
‘
Me.label1.Location = New System.Drawing.Point(12, 24)
Me.label1.Name = “label1”
Me.label1.TabIndex = 0
Me.label1.Text = “Size”
‘
‘groupBox2
‘
Me.groupBox2.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.radioButtonLandscape, Me.radioButtonPortrait})
Me.groupBox2.Location = New System.Drawing.Point(12, 108)
Me.groupBox2.Name = “groupBox2”
Me.groupBox2.Size = New System.Drawing.Size(108, 80)
Me.groupBox2.TabIndex = 1
Me.groupBox2.TabStop = False
Me.groupBox2.Text = “Orientation”
‘
‘radioButtonLandscape
‘
Me.radioButtonLandscape.Location = New System.Drawing.Point(12, 48)
Me.radioButtonLandscape.Name = “radioButtonLandscape”
Me.radioButtonLandscape.Size = New System.Drawing.Size(80, 24)
Me.radioButtonLandscape.TabIndex = 1
Me.radioButtonLandscape.Text = “Landscape”
‘
‘radioButtonPortrait
‘
Me.radioButtonPortrait.Location = New System.Drawing.Point(12, 24)
Me.radioButtonPortrait.Name = “radioButtonPortrait”
Me.radioButtonPortrait.Size = New System.Drawing.Size(60, 24)
Me.radioButtonPortrait.TabIndex = 0
Me.radioButtonPortrait.Text = “Portrait”
‘
‘groupBox3
‘
Me.groupBox3.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.textBoxBottom, Me.textBoxRight, Me.textBoxTop, Me.textBoxLeft, _
Me.label7, Me.label6, Me.label5, Me.label4})
Working with the .NET Namespaces
190
PART II
PRINTING
Me.label6.Location = New System.Drawing.Point(112, 24)
Me.label6.Name = “label6”
Me.label6.TabIndex = 2
Me.label6.Text = “Right”
‘
‘label5
‘
Me.label5.Location = New System.Drawing.Point(12, 52)
Me.label5.Name = “label5”
Me.label5.TabIndex = 1
Me.label5.Text = “Top”
‘
‘label4
‘
Me.label4.Location = New System.Drawing.Point(12, 24)
Me.label4.Name = “label4”
Me.label4.TabIndex = 0
Me.label4.Text = “Left”
‘
‘buttonCancel
‘
Me.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel
Me.buttonCancel.Location = New System.Drawing.Point(280, 200)
Me.buttonCancel.Name = “buttonCancel”
Me.buttonCancel.TabIndex = 4
Me.buttonCancel.Text = “Cancel”
‘
‘buttonOk
‘
Me.buttonOk.Location = New System.Drawing.Point(196, 200)
Me.buttonOk.Name = “buttonOk”
Me.buttonOk.TabIndex = 3
Me.buttonOk.Text = “OK”
‘
‘PageSetup
‘
Me.AcceptButton = Me.buttonOk
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.CancelButton = Me.buttonCancel
Me.ClientSize = New System.Drawing.Size(367, 236)
Me.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.buttonOk, Me.buttonCancel, Me.groupBox3, Me.groupBox2, Me.groupBox1})
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog
Working with the .NET Namespaces
192
PART II
End Sub
#End Region
When users click the Cancel button, the form simply closes and no setup changes are made.
Private Sub buttonCancel_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonCancel.Click
End Sub
Inside the OK button’s click event, we simply store the user-selected form values to our global
variables. This makes sure that these user-defined settings are available to us inside our Print
dialog box.
Private Sub buttonOk_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonOk.Click
‘local scope
Dim isValid As Boolean
If isValid Then 6
OPERATIONS
PRINTING
‘set paper size and paper source
If comboBoxSize().SelectedIndex <> -1 Then
paperSize = comboBoxSize().SelectedItem.ToString
End If
If comboBoxSource().SelectedIndex <> -1 Then
paperSource = comboBoxSource().SelectedItem.ToString
End If
Else
End If
End Sub
Inside the form’s load event, we select the user’s current default settings. These variables are
declared in a global module. Among the settings is the user’s default printer. Inside this event,
we create a PrintDocument instance and set it to this printer. We then enumerate the
PaperSizes and PaperSources collections, adding their values to the associated combo boxes.
This ensures that the paper size and source is relevant to the current printer and allows users to
select appropriate values.
Private Sub PageSetup_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Working with the .NET Namespaces
194
PART II
‘local scope
Dim printDocument As System.Drawing.Printing.PrintDocument
Dim i As Integer
Dim paperSizes As _
System.Drawing.Printing.PrinterSettings.PaperSizeCollection
Dim paperSources As _
System.Drawing.Printing.PrinterSettings.PaperSourceCollection
Case “PORTRAIT”
radioButtonPortrait().Checked = True
Case Else
radioButtonLandscape().Checked = True
End Select
‘tell the print document object the name of the selected printer
printDocument.PrinterSettings.PrinterName = printerDefault
Next
PRINTING
‘select the user settings
comboBoxSize().SelectedItem = paperSize
comboBoxSource().SelectedItem = paperSource
‘close objects
printDocument.Dispose()
End Sub
End Class
FIGURE 6.4
FontPad: Print form (formPrint.vb).
Code Walkthrough
The code behind the print form is similar to our print example earlier in the chapter. Listing 6.7
provides the code for you to reference.
The code starts by setting up the form and its associated controls. Note that we declare an
instance of the PrintDocument class and the StringReader class at the form level. This
enables us to use these objects from within all the procedures in the form module.
Working with the .NET Namespaces
196
PART II
End Sub
PRINTING
Private WithEvents labelTo As System.Windows.Forms.Label
Private WithEvents labelFrom As System.Windows.Forms.Label
Private WithEvents checkBoxCollate As System.Windows.Forms.CheckBox
Private WithEvents labelColor As System.Windows.Forms.Label
PRINTING
Me.buttonCancel.Text = “Cancel”
‘
‘buttonOk
‘
Me.buttonOk.Location = New System.Drawing.Point(212, 220)
Me.buttonOk.Name = “buttonOk”
Me.buttonOk.TabIndex = 1
Me.buttonOk.Text = “OK”
‘
‘checkBoxCollate
‘
Me.checkBoxCollate.Checked = True
Me.checkBoxCollate.CheckState = System.Windows.Forms.CheckState.Checked
Me.checkBoxCollate.Location = New System.Drawing.Point(12, 80)
Me.checkBoxCollate.Name = “checkBoxCollate”
Me.checkBoxCollate.TabIndex = 2
Me.checkBoxCollate.Text = “Collate”
‘
‘groupBox1
‘
Me.groupBox1.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.comboBoxPrinterName, Me.labelColor, Me.label2, Me.label1})
Me.groupBox1.Location = New System.Drawing.Point(8, 8)
Me.groupBox1.Name = “groupBox1”
Me.groupBox1.Size = New System.Drawing.Size(364, 76)
Me.groupBox1.TabIndex = 2
Me.groupBox1.TabStop = False
Me.groupBox1.Text = “Printer”
‘
‘comboBoxPrinterName
‘
Me.comboBoxPrinterName.DropDownWidth = 304
Me.comboBoxPrinterName.Location = New System.Drawing.Point(52, 20)
Me.comboBoxPrinterName.Name = “comboBoxPrinterName”
Me.comboBoxPrinterName.Size = New System.Drawing.Size(304, 21)
Me.comboBoxPrinterName.TabIndex = 7
‘
‘label2
‘
Me.label2.Location = New System.Drawing.Point(12, 48)
Me.label2.Name = “label2”
Me.label2.TabIndex = 1
Me.label2.Text = “Supports Color:”
‘
Working with the .NET Namespaces
200
PART II
PRINTING
Me.groupBox3.Location = New System.Drawing.Point(236, 92)
Me.groupBox3.Name = “groupBox3”
Me.groupBox3.Size = New System.Drawing.Size(136, 120)
Me.groupBox3.TabIndex = 4
Me.groupBox3.TabStop = False
Me.groupBox3.Text = “Copies”
‘
‘numericUpDownCopies
‘
Me.numericUpDownCopies.Location = New System.Drawing.Point(12, 48)
Me.numericUpDownCopies.Maximum = New Decimal(New Integer() {99, 0, 0, 0})
Me.numericUpDownCopies.Minimum = New Decimal(New Integer() {1, 0, 0, 0})
Me.numericUpDownCopies.Name = “numericUpDownCopies”
Me.numericUpDownCopies.Size = New System.Drawing.Size(44, 20)
Me.numericUpDownCopies.TabIndex = 1
Me.numericUpDownCopies.Value = New Decimal(New Integer() {1, 0, 0, 0})
‘
‘Print
‘
Me.AcceptButton = Me.buttonOk
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.CancelButton = Me.buttonCancel
Me.ClientSize = New System.Drawing.Size(389, 258)
Me.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.groupBox3, Me.groupBox2, Me.groupBox1, Me.buttonOk, Me.buttonCancel})
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog
Me.MaximizeBox = False
Me.MinimizeBox = False
Me.Name = “Print”
Me.Text = “FontPad: Print”
Me.groupBox1.ResumeLayout(False)
Me.groupBox2.ResumeLayout(False)
Me.groupBox3.ResumeLayout(False)
CType(Me.numericUpDownCopies, _
System.ComponentModel.ISupportInitialize).EndInit()
Me.ResumeLayout(False)
End Sub
#End Region
‘form-level scope
Private printDocument As New System.Drawing.Printing.printDocument()
Private printStream As System.IO.StringReader
Working with the .NET Namespaces
202
PART II
Inside the load event of the print form, we load a combo box with all the installed printers on
the system. This is done with the PrintDocument.InstalledPrinters collection. We then set
the selected printer in the combo box to the user’s default settings as stored in our application-
wide variable printerDefault.
Private Sub Print_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
‘local scope
Dim i As Integer
Next
End Sub
When users click the Cancel button, we simply unload the form and do not send output to the
printer.
Private Sub buttonCancel_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonCancel.Click
End Sub
When a user clicks the form’s OK button, we start the printing process. First, we do some sim-
ple form-field validation. Then we call the printText procedure, passing the form’s values as
parameters. This gives us a slightly more generic print method that could be used elsewhere.
Private Sub buttonOk_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonOk.Click
6
‘local scope
OPERATIONS
Dim fromPage As Integer
PRINTING
Dim toPage As Integer
End If
End If
Me.Close()
Else
Beep()
End If
End Sub
Inside the PrintText procedure, we control the printing process and set printer settings. We
first create a new instance of the StringReader class and set its string constructor value to our
printString value. Then we tell the PrintDocument object that we will handle its PrintPage
event with our own procedure, printPage_handler.
The page ranges to print are then set.
Next, we create an instance of the Margins class based on the user’s defined margin settings.
Note that margin values are indicated in hundredths of an inch.
Then we set the orientation, paper size, paper source, number of copies, and collate properties
of the PrintDocument.PrinterSettings object.
Finally, we call the Print method of the PrintDocument class. This will fire the PrintPage
event (which we intercept) for every page that it needs to print.
Private Sub printText(ByVal fromPage As Integer, _
ByVal toPage As Integer, _
ByVal copiesToPrint As Integer, _
ByVal isCollate As Boolean)
‘purpose: print the contents of the rich text box to the selected printer
‘local scope
Dim margins As System.Drawing.Printing.Margins
Dim count As Integer
OPERATIONS
End If
PRINTING
‘set the page margin values based on user settings (hundredth of an inch)
margins = New System.Drawing.Printing.Margins(Left:=(marginLeft * 100), _
Right:=(marginRight * 100), Top:=(marginTop * 100), _
Bottom:=(marginBottom * 100))
printDocument.DefaultPageSettings.Margins = margins
‘set the paper size to print from by looping through the paper
‘ sizes collection, matching the paper size name that the
‘ user has selected,
‘ and setting the paperSize property of defaultPageSettings =
‘ to the correct paperSize
For count = 0 To printDocument.PrinterSettings.PaperSizes.Count - 1
If printDocument.PrinterSettings.PaperSizes.Item(count).PaperName = _
paperSize Then
printDocument.DefaultPageSettings.PaperSize = _
printDocument.PrinterSettings.PaperSizes.Item(count)
Exit For
End If
Next
printDocument.DefaultPageSettings.PaperSource = _
printDocument.PrinterSettings.PaperSources.Item(count)
Exit For
Working with the .NET Namespaces
206
PART II
End If
Next
End Sub
Within our custom print page event handler (printPage_handler), we first set the print font to
that of the user’s defined font setting for the application. Next, we start drawing lines to the
printer, one at a time. We calculate the number of lines per page and begin looping through our
count (linesPerPage). We use the StringReader instance (printStream) to read each line and
the Graphics class DrawString method to output the string to the printer.
Private Sub printPage_handler(ByVal sender As Object, _
ByVal ev As System.Drawing.Printing.PrintPageEventArgs)
‘local scope
Dim linesPerPage As Single = 0
Dim yPos As Single = 0
Dim count As Integer = 0
Dim leftMargin As Single = ev.MarginBounds.Left
Dim topMargin As Single = ev.MarginBounds.Top
Dim line As String = “”
Dim printFont As Font
Dim fontStyle As FontStyle
Dim fontFamily As FontFamily
Dim styleType As System.Type
PRINTING
fontStyle = fontStyle.Parse(enumType:=styleType, value:=fontStyleSetting)
‘create a new font object for the printer based on user selected font
‘ set it to the user’s selected font setting
printFont = New Font(family:=fontFamily, _
emSize:=CSng(fontSizeSetting), _
style:=fontStyle, _
unit:=System.Drawing.GraphicsUnit.Point)
Loop
End Sub
Working with the .NET Namespaces
208
PART II
The following are simply click events used to control our form operations:
Private Sub toggleRange(ByVal toggleValue As Boolean)
labelFrom().Enabled = toggleValue
labelTo().Enabled = toggleValue
textBoxFrom().Enabled = toggleValue
textBoxTo().Enabled = toggleValue
End Sub
Call toggleRange(True)
End Sub
Call toggleRange(False)
End Sub
‘tell the print document object the name of the selected printer
printDocument.PrinterSettings.PrinterName = _
comboBoxPrinterName().SelectedItem.ToString
End Sub
End Class
Font, Text, and Printing Operations
209
CHAPTER 6
PRINTING
for your reference.
‘purpose: declare variables that have global scope & set the defaults
‘font settings
Public g_FontFamily As String = “Courier New”
Public g_FontStyle As String = “Regular”
Public g_FontSize As String = “10”
Member Description
PrintDialog The PrintDialog class enables programmers to easily create a
form that gives users access to printer and print property selec-
tions.
PrintPreviewControl The PrintPreviewControl class encapsulates the print pre-
viewing process without any dialog boxes.
PrintPreviewDialog The PrintPreviewDialog class allows programmers to easily
display print preview information to users of their application.
The PrintPreviewDialog uses a PrintPreviewControl.
PageSetupDialog The PageSetupDialog class enables you to create a dialog box
that can be manipulated by users to modify page settings, mar-
gins, and paper orientation.
FontDialog The FontDialog class gives you a form to display to users rep-
resenting a list of fonts that are currently installed on a system.
Users can select a font, size, and style.
FileDialog The FileDialog class enables programmers to easily create a
form that will allow users to navigate their machine and net-
work in search of a file.
Font, Text, and Printing Operations
211
CHAPTER 6
Summary 6
OPERATIONS
PRINTING
sending output to the printer with the .NET Class Library. From here you should be able to
easily strengthen this foundation through your own explorations as you extend your knowledge
into your own application development.
The following are key points to writing code for font, text, and printing functionality with the
.NET Class Library:
• A font describes the way text appears on a device. Fonts can vary in size, weight,
and style.
• The Font class encapsulates an individual font inside of .NET.
• The InstalledFontCollection class enables you to return all fonts installed on a given
system.
• The PrivateFontsCollection class enables you to work with custom fonts without
actually installing them on a system.
• The PrintDocument class is used to control output to a printer.
• To set and retrieve specific settings on a given printer, use the PrinterSettings class.
• The PreviewPrintController class provides a print controller that outputs printed pages
as images that can be viewed prior to submission to the printer.
Stream and File Operations CHAPTER
7
IN THIS CHAPTER
• Key Classes Related to File I/O 214
One of the most important issues any programmer faces is how to go about storing and retriev-
ing information on disk. This issue can be surprisingly complex. The .NET Base Class Library
provides a number of classes that simplify the issue somewhat. The library is a substantial
improvement over the file-related operations Visual Basic programmers had available to them
previously.
This chapter focuses on the .NET namespaces related to directories, files, and synchronous and
asynchronous reading from and writing to data streams. First, an overview is presented that
details the key classes within the namespace. Then we get into files, streams, and data types.
And finally, we write a file-monitoring application that demonstrates the use of these classes.
After reading this chapter, you should be able to do the following:
• Manage directories and files including creating, deleting, and accessing their property
information
• Monitor the file system and respond to basic system events
• Read and write files as streams of data both synchronously and asynchronously
• Access file data at the binary level
• Understand some of the basic design considerations for choosing a file I/O strategy
The code for the example is provided in Listing 7.1. It involves code to create the form, a form
load event, and a pair of index change events for the two list boxes.
The code starts with a few global variable declarations to store a path, directory, and filename.
This is followed by the basic form creation code.
OPERATIONS
Me.AcceptButton = Me.buttonClose
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog
Me.ClientSize = New System.Drawing.Size(453, 410)
Me.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.buttonClose, Me.labelFileInfo, Me.listBoxFiles, _
Me.listBoxDirectories, Me.labelDirectoryInfo, Me.label2, _
Me.label1})
Me.Text = “Directory And Files”
End Sub
Inside the form’s load event we return a list of directories. To do so, first we instantiate a
DirectoryInfo class with the line
where myPath is a valid path (c:\). Next, we call the GetDirectories method of the
DirectoryInfo class. This returns an array of DirectoryInfo objects that represent the path’s
subdirectories. Finally, we select an item in the directory list box. This fires the index-changed
event of the directory list box.
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
‘local scope
Dim myDirectories() As System.IO.DirectoryInfo
Dim i As Integer
‘loop through the directories and add them to the list box
For i = 0 To UBound(myDirectories) - 1
Next
End Sub
Once a user selects a directory, we must return to her a list of files. In our example, we do this
inside the listBoxDirectories index change event
(listBoxDirectories_SelectedIndexChanged). We first create a new DirectoryInfo object
based on our starting path value and the user’s selected directory:
myDirectory = New System.IO.DirectoryInfo(path:=(myPath & myDirName))
Next we return an array of FileInfo objects using the method GetFiles of the DirectoryInfo
class. Finally, we loop through the array and add the filenames to the list box and select the
first file in the list.
Private Sub listBoxDirectories_SelectedIndexChanged(ByVal sender As _
System.Object, ByVal e As System.EventArgs) Handles _
listBoxDirectories.SelectedIndexChanged
‘purpose: synchronize the files list box with the selected directory
‘ and display the selected directory’s information
‘local scope
Dim myDirectory As System.IO.DirectoryInfo
Dim dirInfo As String
Dim myFiles() As System.IO.FileInfo
Dim i As Integer
OPERATIONS
If UBound(myFiles) >= 0 Then
‘select the file in the list, this will trigger the event to change
‘ the file info label control
listBoxFiles().SelectedIndex = 0
End If
End Sub
‘local scope
Dim myFile As System.IO.FileInfo
Dim fileInfo As String
End Sub
End Sub
#End Region
End Class
In the prior example we used the FileInfo class to display file attributes to the user. This class
has a number of properties that are useful when working with files. Some of these that are of
keen interest are listed in Table 7.2.
Directories
For manipulating directories we primarily use five basic methods: Create,
CreateSubdirectory, Delete, MoveTo, and Refresh. These methods are called from the
DirectoryInfo class:
Working with the .NET Namespaces
224
PART II
• The Create method creates the directory to which the DirectoryInfo object refers. The
CreateSubdirectory method accepts the subdirectory’s path as a parameter. It then cre-
ates the subdirectory and returns it as a DirectoryInfo instance.
• The Delete method has two overloaded parameter sets. The first takes no parameters
and simply deletes the directory and its contents to which the DirectoryInfo instance
refers. The second takes a Boolean value that indicates whether the delete call should
also be recursive; that is, whether it should delete all its subdirectories and their contents.
• The MoveTo method takes a string as a parameter (destDirName) that represents the des-
tination path. The destination path, as you may have guessed, defines the directory and
its path to which the current directory (defined by the DirctoryInfo instance) should be
moved.
• The Refresh method refreshes the DirectoryInfo object instance. The method reloads
the directory information. This is useful for long-running objects to maintain the most
up-to-date information on the directory attributes.
Listing 7.2 is a procedure that demonstrates these methods. The procedure can be copied into a
console application and executed.
Sub Main()
‘local scope
Dim myDirectoryParent As System.IO.DirectoryInfo
Dim myDirectory2 As System.IO.DirectoryInfo
OPERATIONS
‘ to delete its sub directories as well
myDirectory2.Delete()
End Sub
End Module
Files
For working with files we use a similar set of methods found in the FileInfo class. The
Create, Delete, MoveTo, and Refresh methods are all present. However, programming with
files requires a few additional methods: AppendText, CopyTo, CreateText, Open, OpenRead,
and OpenWrite (all of which will be defined later in this chapter).
The Create, MoveTo, and Delete methods are very similar to the DirectoryInfo class. The
CopyTo method copies a version of the file to which the FileInfo instance refers. The method
is overloaded with two parameter sets. The first copies an instance of the given file to the new
directory specified in the parameter destFileName. This method raises an exception if a file
with the same name already exists in the destination directory. Upon success, the method
returns an instance of the FileInfo class based on the copied file. The second overloaded
method takes both the destFileName and the parameter overwrite as a Boolean. Set this para-
meter to True if you want the Copy method to overwrite any existing file with the same name
and False if you do not. Sample code that illustrates these methods is provided for you in
Listing 7.3.
Note the return type of the Create method. On file create, we are returned an instance of the
FileStream object. This object is a stream object based on the new file. The stream object pro-
vides us the ability to read and write to the file. Streams, as well as reading and writing to
files, are discussed later in this chapter.
Working with the .NET Namespaces
226
PART II
Module Module1
Sub Main()
‘local scope
Dim myFileStream As FileStream
Dim myFile As FileInfo
Dim myCopiedFile As FileInfo
OPERATIONS
‘delete the files
myFile.Delete()
myCopiedFile.Delete()
End Sub
End Module
The level of access users are granted to a file is controlled by the FileAccess enumeration.
The parameter is specified in many of the constructors of the File, FileInfo, and FileStream
classes. Table 7.3 lists the members of the enumeration and a brief description of each.
Member Description
Read The Read member indicates that data can be read from a file.
ReadWrite The ReadWrite member defines both read and write access to a given file.
Write The Write member provides write access to a file.
The various attribute values for files and directories are controlled using the FileAttributes
enumeration. It should be noted that not all members are applicable to both files and directo-
ries. Table 7.4 lists the members of the FileAttributes enumeration.
Member Description
Archive The Archive member indicates a file’s archive status. The archive
status is often used to mark files for backup or removal.
Compressed The Compressed member indicates that a file is compressed.
Working with the .NET Namespaces
228
PART II
To control whether a file is created, overwritten, opened, or some combination thereof, you use
the FileMode enumeration. It is used in many of the constructors of the FileStream, File,
FileInfo, and IsolatedStorageFileStream classes. Table 7.5 lists the members of the
FileMode enumeration.
Member Description
Append The Append parameter specifies that a file be opened or created, and its
end searched (seek) out. FileMode.Append can only be used in conjunc-
tion with FileAccess.Write. Any attempt to read fails and throws an
ArgumentException.
Stream and File Operations
229
CHAPTER 7
OPERATIONS
This requires FileIOPermissionAccess.Read FileIOPermission.
OpenOrCreate The OpenOrCreate member indicates that Windows should open a file if it
exists; otherwise, a new file should be created. If the file is opened with
FileAccess.Read, FileIOPermissionAccess.Read FileIOPermission is
required. If file access is FileAccess.ReadWrite and the file exists,
FileIOPermissionAccess.Write FileIOPermission is required. If file
access is FileAccess.ReadWrite and the file does not exist,
FileIOPermissionAccess.Append FileIOPermission is required in
addition to Read and Write.
Truncate The truncate member indicates that Windows should open an existing file
and truncate its size to zero bytes. This requires
FileIOPermissionAccess.Write FileIOPermission.
Use the FileShare enumeration to control whether two processes can access a given file
simultaneously. For example, if a user opens a file marked FileShare.Read, other users can
open the file for reading but cannot save or write to it. The FileShare enumeration is used in
some of the constructors for the FileStream, File, FileInfo, and
IsolatedStorageFileStream classes. Table 7.6 lists the FileShare enumeration members.
Member Description
None The None value indicates that the file should not be shared in any way. All
requests to open the file will fail until the file is closed.
Read The Read member allows users to open a given file for reading only.
Attempts to save the file (or write to it) but read-only processes will fail.
ReadWrite The ReadWrite parameter indicates that a file can be opened for both reading
and writing by multiple processes. Obviously this can cause problems
because the last user to save has his changes applied.
Working with the .NET Namespaces
230
PART II
all files in the directory. If you are trying to monitor only Microsoft Excel files, then you’d set
the Filter property to *.xls. Table 7.7 lists additional properties and describes when you would
implement their use.
OPERATIONS
Filter
directory that are of a specific type.
Target Use the Target property to watch for changes on only a file,
only a directory, or both a file and a directory. By default, the
Target property is set to watch for changes to both directory
and file-level items.
IncludeSubDirectories Set the IncludeSubDirectories property to True to monitor
changes made to subdirectories that the root directory con-
tains. The watcher will watch for the same changes in all
directories.
ChangedFilter Use the ChangedFilter property to watch for specific changes
to a file or directory when handling the Changed event.
Changes can apply to Attributes, LastAccess, LastWrite,
Security, or Size.
Once you’ve decided what objects you are monitoring you’ll need to indicate the events or
actions to which you are listening. The changes that the FileSystemWatcher can monitor
include changes in the directory’s or file’s properties, size, last write time, last access time,
and security settings.
You use the NotifyFilters enumeration of the FileSystemWatcher class to specify changes
for which to watch on files and directories. The members of this enumeration can be combined
using BitOr (bitwise or comparisons) to watch for multiple kinds of changes. An event is
raised when any change you are watching for is made. Table 7.8 lists the members of the
NotifyFilters enumeration and provides a brief description of each.
Working with the .NET Namespaces
232
PART II
Member Description
Attributes The Attributes member allows you to watch for changes made to
the attributes of a file or directory.
CreationTime The CreationTime member allows you to watch for changes made
to the time the file or directory was created.
DirectoryName The DirectoryName member allows you to watch for changes
made to the name of the file or directory.
FileName The FileName member allows you to watch for changes made to
the name of a file.
LastAccess The LastAccess member allows you to watch for changes made to
the date the file or directory was last opened.
LastWrite The LastWrite member allows you to watch for changes made to
the date the file or directory had data written to it.
Security The Security member allows you to watch for changes made to
the security settings of the file or directory.
Size The Size member allows you to watch for changes made to the
size of the file or directory.
NOTE
You might notice that some common tasks such as file copy or move do not corre-
spond directly to an event raised by the component. However, upon closer examina-
tion, you will notice that when a file is copied, the system raises a created event to
the directory to which the file was copied. Similarly, when a file is moved, the system
raises both a deleted event in the file’s original directory and a created event in the
file’s new directory. These events serve in place of actual copy and move to events.
This buffer becomes very important in high-volume monitoring applications. It has the poten-
tial to receive a lot of events. Let’s examine this further. Every change to a file in a directory
raises a separate event. This sounds simple enough, but we have to be careful. For example, if
Stream and File Operations
233
CHAPTER 7
we are monitoring a directory that contains 25 files and we reset the security settings on the
directory, we will get 25 separate change events. Now if we write an application that renames
those files and resets their security we’ll get 50 events: one for each file for both change and
rename.
All these events are stored in the FileSystemWatcher’s internal buffer, which has a maximum
size limit and can overflow. If this buffer overflows, the FileSystemWatcher will raise the
InternalBufferOverflow event. Fortunately, the component allows us to increase this
buffer size.
The default buffer size is set to 8KB. Microsoft indicates that this can track changes on 7
OPERATIONS
using the InternalBufferSize property. For best performance, this property should be set in
increments of 4K (4096, 8192, 12288, and so on) because this corresponds to the operating
system’s (Windows 2000) default page size.
Increasing this internal buffer comes at a cost. The buffer uses non-paged memory that cannot
be swapped to disk. Therefore, we need to keep the buffer size as small as possible. Strategies
to limit the buffer’s size include the NotifyFilter and IncludeSubDirectories properties to
filter out those change notifications in which we have no interest. It should be noted that the
Filter property actually has no effect on the buffer size since the filter is applied after the
notifications are written to the buffer.
To actually connect to the FileSystemWatcher’s events we add handlers in our code as we
would with any other event. For example, to hook into the Changed event, we add code similar
to the following:
AddHandler myWatcher.Changed, AddressOf watcher_OnChange
This tells your application to intercept the Changed event and process through a custom event
called watcher_onChange. The custom event need only have the correct function signature.
Table 7.9 lists the events to which you can subscribe and their associated function signatures.
Once inside the event, we have access to a number of properties related to the event and event
type. These properties come from the event arguments that are passed to us when the event is
raised. They include things like the change type and the path and name to the file or directory.
The WatcherChangeTypes enumeration is used by events of the FileSystemWatcher class. The
enumeration’s members indicate to the event the type of change that occurred to a file or direc-
tory. Table 7.10 lists the members of the WatcherChangeTypes enumeration.
Member Description
All The All member indicates that any of the creation, deletion, change, or
renaming of a file or folder actions occurred.
Changed The Changed member indicates that a change action occurred to a file or
event. Changes can include: size, attributes, security, last write, and last
access time.
Created The Created member indicates that a file or folder was created.
Deleted The Deleted member indicates that a file or folder was deleted.
Renamed The Renamed member indicates that a file or folder was renamed.
Listing 7.4 provides a sample FileSystemWatcher application that serves to further illustrate
these concepts. The code can be executed inside a console application (and downloaded from
www.samspublishing.com). The application monitors a directory for changes to text files.
When a change occurs, the user is notified with a simple call to Console.WriteLine from
within the intercepted change event.
Stream and File Operations
235
CHAPTER 7
Imports System.IO
Module Module1
Sub Main()
‘Call directory()
Call watchDirectory(watchPath:=”c:\watch”)
End Sub 7
‘local scope
Dim myWatcher As FileSystemWatcher
Dim stopValue As String
End Sub
End Sub
End Sub
End Module
➲ For additional information on .NET security issues (for user, file, directory, and code
access), start with the System.Security namespace. Of course, you will also want to
read Appendix D, “.NET Framework Base Data Types,” which deals specifically with
security in .NET.
➲ You will want to check out the FileDialog class found in System.Windows.Forms. This
class allows you to easily display a window’s dialog that allows users to select a file. The
class is the replacement of the old common dialog control.
Member Description
AppendText The AppendText method creates an instance of the StreamWriter class
that allows us to append text to a file. The StreamWriter class implements
a TextWriter instance to output the characters in a specific encoding.
CreateText The CreateText method creates an instance of the StreamWriter class
that creates a new text file to which to write.
Open The Open method opens a file and returns it to us as a FileStream object.
The method has three constructors that allow us to specify the open mode
(open, create, append, and so on), the file access (read, write, read and
write), and how we want the file to be shared by other FileStream objects.
OpenText The OpenText method creates a StreamReader object based on the associ-
ated text file.
OpenRead The OpenRead method creates a FileStream object that is read only.
OpenWrite The OpenWrite method creates a FileStream object that is both read and
write.
You can see that the FileInfo class makes extensive use of the FileStream, StreamWriter,
and StreamReader classes. These classes expose the necessary functionality to read and write
to files in .NET. As you might have guessed, these objects are designed to work with persisted
text files. They are based on the TextWriter and TextReader classes.
The FileStream class can be created explicitly. You’ve already seen that FileInfo uses this
class to expose reading and writing to files. Table 7.12 lists the version of the FileStream con-
structors that can be used to create a FileStream object.
OPERATIONS
New FileStream (ByVal path as String, ByVal mode as FileMode, _ByVal access as
FileAccess)
path: A valid path to the file that the FileStream object will represent.
mode: A member of the FileMode enumeration (Append, Create, CreateNew, Open,
OpenOrCreate, Truncate) that specifies how the file should be opened.
access: A member of the FileAccess enumeration (Read, ReadWrite, Write).
Note: Use this constructor when you know the file’s path, wish to specify how the file is
opened, and need to specify the read/write permissions on the file.
New FileStream (ByVal handle as IntPtr, ByVal access as FileAccess, _ByVal
ownsHandle as Boolean, ByVal bufferSize as Integer)
handle: A valid handle to a file.
access: A member of the FileAccess enumeration (Read, ReadWrite, Write).
ownsHandle: Indicates if the file’s handle will be owned by the given instance of the
FileStreamObject.
bufferSize: Indicates the size of the buffer in bytes.
Note: Use this constructor when you have a valid file pointer, need to specify the read/write
permissions, with to own the file’s handle, and need to set the stream’s buffer size.
New FileStream (ByVal path as String, ByVal mode as FileMode, _ByVal access as
FileAccess, ByVal share as FileShare)
path: A valid path to the file that the FileStream object will represent.
mode: A member of the FileMode enumeration (Append, Create, CreateNew, Open,
OpenOrCreate, Truncate) that specifies how the file should be opened.
access: A member of the FileAccess enumeration (Read, ReadWrite, Write).
share: A member of the FileShare enumeration that indicates how the file will be shared.
Working with the .NET Namespaces
240
PART II
OPERATIONS
This example is a simple, console-based application. It creates a new FileStream object based
on a physical file. It then creates a StreamWriter instance based on the FileStream class. It
calls the WritLine method of StreamWriter to output a line of text to the file. After it closes
the StreamWriter instance, it creates a StreamReader instance based on a FileStream object.
Finally, it loops through the lines in the file and outputs them to the console for your viewing.
Imports System.IO
Module Module1
Sub Main()
‘local scope
Dim fileStream As FileStream
Dim streamWriter As StreamWriter
Dim streamReader As StreamReader
End Sub
End Module
the operation is complete. For instance, suppose your application takes orders in the form of
text files written to a queue. When a file is placed in the queue (or directory), your application
reads the contents of the file and processes the order(s) accordingly. Each file can represent
one order, or can contain a batch of orders. If your application is set up to handle each order
from start to finish as it comes in (synchronously), then a long order will block your applica-
tion from continuing to process orders while simply reading the file.
For a more efficient use of your resources, you will want to read orders asynchronously. That
is, as an order comes in, you will tell a version of the Stream object to start reading the file
and to let you know when it is done. This way, once you fire the BeginRead method, you can 7
continue executing other program logic including responding to and processing additional
OPERATIONS
With asynchronous file I/O, the main thread of your application continues to execute code
while the I/O process finishes. In fact, multiple asynchronous IO requests can process simulta-
neously. Generally, an asynchronous design offers your application better performance. The
tradeoff to this performance is that a greater coding effort is required.
The FileStream class provides us the BeginRead method for asynchronous file input and the
BeginWrite method for asynchronous file output. As a parameter to each, we pass the name of
the method we wish to have called when the operation is complete (userCallback as
AsynchCallback). In VB .NET, the syntax looks like this:
Where myCallbackMethod is the name of the method you wish to have intercept and process
the completed operation notification. From within this callback method, you should call
EndRead or EndWrite as the case dictates. These methods end their respective operations.
EndRead returns the number of bytes that were read during the operation. Both methods take a
reference to the pending asynchronous I/O operation (AsynchResult as IAsynchResult).
This object comes to us as a parameter to our custom callback method. The code in Listing 7.6
further illustrates these concepts.
The application’s Sub Main simply controls the calls to the read operation. You can see in
Listing 7.6 that we execute three separate read requests on three different files. The remaining
bits of functionality are nicely encapsulated and thus, should be easy to reuse.
Module Module1
Sub Main()
Working with the .NET Namespaces
244
PART II
‘local scope
Dim myFiles(3) As String
Dim i As Int16
myFiles(0) = “c:\file1.txt”
myFiles(1) = “c:\file2.txt”
myFiles(2) = “c:\file3.txt”
‘NOTE: now that file reads have started, our application can
‘ continue processing other information and await a callback
‘ from the read operation indicating read is complete
End Sub
The procedure asynchRead sets up the asynchronous file input. The class StateObject is a
simple state object that allows us to maintain file input information, in the form of properties,
across read requests.
Notice that when calling BeginRead, in addition to indicating a callback method, we must
specify both a byte array (array() as Byte) and the total number of bytes (numBytes as
Integer) we wish to have read. To store the bytes, we dimension an array of type byte inside
our state object. We pass byteArraySize in the object’s constructor. We get its size by reading
the file size from the FileInfo object’s Length property. This allows us to create an array of
the exact size we need. Similarly, when we set the number of bytes to read, we use
Stream and File Operations
245
CHAPTER 7
‘local scope
Dim fileStream As FileStream
Dim state As StateObject
Dim fileInfo As FileInfo
7
OPERATIONS
If Not File.Exists(path:=filePath) Then
End If
End Sub
The fileRead method is the application’s callback implementation. This method receives noti-
fication when a BeginRead has completed for a given file.
‘local scope
Dim state As StateObject
Dim bytesRead As Integer
‘set the state object = to the one returned by the asynch results
state = asyncResult.AsyncState
End Sub
End Module
‘class-level scope
Private localFilePath As String
Private localByteArray() As Byte
‘public properties
Public FileStream As FileStream
Get
Return localByteArray
End Get
End Property
OPERATIONS
Get
Return localFilePath
End Get
End Property
End Sub
End Class
Figure 7.2 represents the output of the code listing. Notice that in this case, each file was read
in the same order the request was made. However, there is no guarantee of processing order
due to the asynchronous nature of the request and additional factors like file size and processor
availability. Also notice that after the first (and subsequent) read requests were made, our code
did not stop executing. Rather, it made additional requests, and ultimately, waited on user input
to stop the application. Finally, as each read completed, the notification was sent to our
readFile method and the results of the operation were written to the console.
Working with the .NET Namespaces
248
PART II
FIGURE 7.2
Output of asynchronous example.
Similarly, BinaryWriter provides a number of write methods for writing primitive data to a
stream. Unlike BinaryReader, BinaryWriter exposes only one method, WriteByte, for exe-
cuting binary writes. However, this method has a number of overloads that allow us to specify
Stream and File Operations
249
CHAPTER 7
whether we are writing byte data or string, decimal, and so on. Calls to WriteByte write out
the given data to the stream and advance its current position by the length of the data. Again,
WriteByte(value as Byte) will be the most commonly used method.
The FileStream class also exposes the basic binary methods, ReadByte and WriteByte.
ReadByte and WriteByte behave in the exact same manner as BinaryReader.ReadByte and
BinaryWriter.WriteByte(value as Byte). It is often easier to simply use FileStream for all
your basic needs; this is why it exists. Should you need additional functionality, then you will
want to implement one or more of the binary classes.
Listing 7.7 provides an example of the BinaryReader and BinaryWriter classes. In the exam- 7
OPERATIONS
time, we write each byte out to another file using BinaryWriter. The result is two identical
files. Notice that to create both the reader and the writer we must first create a valid
FileStream (or similar Stream derivation) for the instances to use as their backing.
Imports System.IO
Module Module1
Sub Main()
‘local scope
Dim fsRead As FileStream
Dim fsWrite As FileStream
Dim bRead As BinaryReader
Dim b As Byte
Dim bWrite As BinaryWriter
End
Working with the .NET Namespaces
250
PART II
‘delete file
File.Delete(path:=”c:\test2.bmp”)
End If
Try
Catch
Exit Do
End Try
OPERATIONS
‘wait for the user to stop the console application
Console.WriteLine(“Operation complete.”)
Console.WriteLine(“Enter ‘s’ to stop the application.”)
End Sub
End Module
Open Dialog
To open files, we must present users with a method to browse the file system. .NET provides
us with a FileDialog class specifically designed for this purpose. This class is similar to the
common dialogs with which we’re familiar from the VB of old. The .NET team built the dia-
log using the namespace we’ve presented in this chapter.
In our example application we will create our own dialog using the namespace rather than
FileDialog. This makes sense because our objective is to teach the namespace. However, we
suggest you further explore this class if you need fast and easy access to the file system.
Figure 7.3 is a screen capture of our open dialog.
Obviously, this form will not win any usability or user interface design awards; it was built to
illustrate code. There are two list boxes on the form. One maintains a current list of subdirecto-
ries of the given path. The other displays files of type text within the selected directory. Users
navigate down through subdirectories by double-clicking a directory. To navigate back, they
click the Up button. As directories and files are selected, we write related information to a cou-
ple of label controls.
Stream and File Operations
253
CHAPTER 7
Code Walkthrough
The code used by the open dialog should be very familiar to you by now. The code can be
found in Listing 7.8.
The code starts with some form-level declarations for directory name and filename. This is fol-
lowed by the basic code to build the form.
Inherits System.Windows.Forms.Form
End Sub
Working with the .NET Namespaces
254
PART II
OPERATIONS
Me.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel
Me.buttonCancel.Location = New System.Drawing.Point(352, 336)
Me.buttonCancel.TabIndex = 0
Me.buttonCancel.Text = “Cancel”
Me.buttonUp.Location = New System.Drawing.Point(224, 48)
Me.buttonUp.Size = New System.Drawing.Size(28, 24)
Me.buttonUp.TabIndex = 5
Me.buttonUp.Text = “up”
Me.listBoxDirectories.Location = New System.Drawing.Point(8, 48)
Me.listBoxDirectories.Size = New System.Drawing.Size(212, 121)
Me.listBoxDirectories.TabIndex = 4
Me.listBox1.Location = New System.Drawing.Point(20, 60)
Me.listBox1.Size = New System.Drawing.Size(0, 4)
Me.listBox1.TabIndex = 3
Me.label5.Location = New System.Drawing.Point(224, 180)
Me.label5.Size = New System.Drawing.Size(92, 16)
Me.label5.TabIndex = 2
Me.label5.Text = “Info”
Me.labelFileInfo.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D
Me.labelFileInfo.Location = New System.Drawing.Point(224, 196)
Me.labelFileInfo.Size = New System.Drawing.Size(204, 132)
Me.labelFileInfo.TabIndex = 7
Me.buttonOk.Location = New System.Drawing.Point(272, 336)
Me.buttonOk.TabIndex = 1
Me.buttonOk.Text = “OK”
Me.label1.Location = New System.Drawing.Point(8, 8)
Me.label1.Size = New System.Drawing.Size(100, 16)
Me.label1.TabIndex = 8
Me.label1.Text = “Directories”
Me.label2.Location = New System.Drawing.Point(224, 80)
Me.label2.Size = New System.Drawing.Size(92, 16)
Me.label2.TabIndex = 2
Me.label2.Text = “Info”
Me.label3.Location = New System.Drawing.Point(6, 180)
Working with the .NET Namespaces
256
PART II
End Sub
On the form load event we simply make a call to the sub procedure that loads the directory
list box.
‘local scope
End Sub
The procedure loadDirListBox refreshes the contents of the directory list box when the form
loads and as users double-click a subdirectory or press the Up button.
‘purpose: load the list box control based on the current path
‘local scope
Dim myDirectory As System.IO.DirectoryInfo
Dim myDirectories() As System.IO.DirectoryInfo
Dim i As Integer
Stream and File Operations
257
CHAPTER 7
‘loop through the directories and add them to the list box
7
OPERATIONS
‘add the directory name to the list
listBoxDirectories().Items.Add(myDirectories(i).Name)
Next
End Sub
Once the user has selected a file to open and has clicked the OK button, the button’s click
event calls a custom procedure we call readFile.
Private Sub buttonOk_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonOk.Click
‘open the text file as a stream and read it into our editor
‘note: this new file will replace the current file without saving
Call readFile()
End If
End Sub
The readFile procedure creates a StreamReader object, sets its read position to the start of the
file, and reads the file line-by-line. Finally, our rich text box control is updated with the con-
tents of the opened file.
Private Sub readFile()
‘local scope
Dim fileStream As IO.FileStream
Dim streamReader As IO.StreamReader
‘handle any errors the occur when loading the file stream
Try
‘create a new instance of the file stream object
‘ based on the current path and selected file
fileStream = New IO.FileStream(path:=myPath & myDirName & _
myFileName, mode:=IO.FileMode.Open, access:=IO.FileAccess.Read)
Catch
7
End Try
End Sub
Inside the listBoxDirectories double-click event we simply reset the path (myPath) and call
load directories sub (loadDirListBox).
Private Sub listBoxDirectories_DoubleClick(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles listBoxDirectories.DoubleClick
‘local scope
End Sub
When users select a directory from the list box, the directory list box’s index change event gets
fired. This event loads the file’s list box based on the user-selected subdirectory. Inside this
event, we trap for file security access issues. For instance, if you call the GetFiles method of
the Directory object and the user is not granted access to the directory’s files, we must raise
this issue to our user.
Working with the .NET Namespaces
260
PART II
‘purpose: update the directory info label and the file’s list box
‘ when users select a directory
‘local scope
Dim myDirectory As System.IO.DirectoryInfo
Dim dirInfo As String
Dim myFiles() As System.IO.FileInfo
Dim i As Integer
‘select the file in the list, this will trigger the event to _
change
Stream and File Operations
261
CHAPTER 7
End If
Catch
MsgBox(prompt:= _
“Sorry, but you do not have access to browse this folder’s _
7
OPERATIONS
Exception”)
End Try
End Sub
When a user selects a file, the SelectedIndexChanged event is fired. This event allows us to
set the selected filename (myFileName) and update the file properties to the user.
Private Sub listBoxFiles_SelectedIndexChanged(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles listBoxFiles.SelectedIndexChanged
‘local scope
Dim myFile As System.IO.FileInfo
Dim fileInfo As String
End Sub
The Up button’s click event simply navigates the user back up one folder.
‘local scope
Dim intPos As Integer
If intPos = 0 Then
‘no more path info
Beep()
Else
‘trim the path back
myPath = Mid(myPath, 1, intPos)
Call loadDirListBox()
End If
End Sub
#End Region
End Class
Save Dialog
Our application needs a way to persist its data to the file system. We create a Save dialog and
associated code to do just that. Figure 7.4 is a screen capture of the Save dialog in action.
Again, this dialog is sure to offend UI designers but it serves to illustrate the use of the classes.
Stream and File Operations
263
CHAPTER 7
The directory list box and associated Up button were stolen from the Open form. This simple
paradigm provides users with access to the file system. A text box is provided for users to type
the name to which they want to save the file.
The directory browsing provided by the form’s code is nearly the same as the Open example
(we might have considered creating a common dialog to be used by both features).
Code Walkthrough
Listing 7.9 presents the code behind the Open form.
Listing 7.9 starts with a form-level declare for storing the directory name. This is followed by
the basic form code. After that, much of the code is similar to the code in the open dialog with
the exception of the saveFile function.
‘note: issues
‘1. cannot save to the root directory
‘2. the return character not coming out right
‘3. files saved cannot be seen by the open
‘4. delete the file if exists??
End Sub
OPERATIONS
Me.buttonOk.Text = “OK”
‘
‘label2
‘
Me.label2.Location = New System.Drawing.Point(4, 172)
Me.label2.Name = “label2”
Me.label2.TabIndex = 2
Me.label2.Text = “File name:”
‘
‘buttonCancel
‘
Me.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel
Me.buttonCancel.Location = New System.Drawing.Point(216, 220)
Me.buttonCancel.Name = “buttonCancel”
Me.buttonCancel.TabIndex = 0
Me.buttonCancel.Text = “Cancel”
‘
‘listBoxDirectories
‘
Me.listBoxDirectories.Location = New System.Drawing.Point(8, 72)
Me.listBoxDirectories.Name = “listBoxDirectories”
Me.listBoxDirectories.Size = New System.Drawing.Size(244, 95)
Me.listBoxDirectories.TabIndex = 3
‘
‘labelSaveTo
‘
Me.labelSaveTo.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D
Me.labelSaveTo.Location = New System.Drawing.Point(8, 28)
Me.labelSaveTo.Name = “labelSaveTo”
Me.labelSaveTo.Size = New System.Drawing.Size(280, 36)
Me.labelSaveTo.TabIndex = 6
‘
‘textBoxFileName
‘
Working with the .NET Namespaces
266
PART II
End Sub
‘local scope
End Sub
‘purpose: load the list box control based on the current path
‘ note: this procedure is the same as the one in formOpen
Stream and File Operations
267
CHAPTER 7
‘loop through the directories and add them to the list box
For i = 0 To UBound(myDirectories) - 1
Next
End Sub
Private Sub listBoxDirectories_SelectedIndexChanged( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles listBoxDirectories.SelectedIndexChanged
‘local scope
End Sub
Working with the .NET Namespaces
268
PART II
‘local scope
Dim intPos As Integer
If intPos = 0 Then
‘no more path info
Beep()
Else
‘trim the path back
myPath = Mid(myPath, 1, intPos)
Call loadDirListBox()
End If
End Sub
‘local scope
End Sub
‘local scope
Dim fileName As String
‘validate filename
7
OPERATIONS
‘raise a message to the user
MsgBox(prompt:=”Please enter a file name.”, _
buttons:=MsgBoxStyle.Exclamation, title:=”FontPad Save”)
‘position cursor
textBoxFileName().Focus()
Else
End If
End Sub
The primary piece of functionality (save) can be found in the saveFile procedure. First, we
use methods of the File class to determine if the file already exists. If a file does exist, we
handle the situation by first deleting it and then saving it as new. This is a shortcut and not the
best solution. A more robust design would tell the user the file already exists and prompt him
to overwrite. Next, we create the FileStream object to write out the file. We open a
StreamWriter instance and set it to start writing at the beginning of our file. After this, we call
the Write method and pass it our contents as a string value. Finally we Flush and Close the
StreamWriter instance. Our new file is now saved to disk.
Working with the .NET Namespaces
270
PART II
‘local scope
Dim fileStream As IO.FileStream
Dim streamWriter As IO.StreamWriter
Dim filePath As String
End If
End Sub
#End Region
End Class
Stream and File Operations
271
CHAPTER 7
Summary
The ability to handle common file I/O tasks is essential to all developers. The .NET
Framework namespace, System.IO, exposes this functionality. By now, you should have a solid
foothold in this namespace that will support you in your further explorations.
The following is a summary of some of the key points regarding common file I/O program-
ming tasks:
• DirectoryInfo and FileInfo classes contain specific instance methods related to a
given directory or file. Whereas, the Directory and File classes pertain to static meth- 7
ods and are thus used in more generic cases.
• The class, FileSystemWatcher, allows us to monitor the file system for new files, files
being renamed, moved, and so on.
• Use the FileStream object for all basic file reading and writing including asynchronous
and binary.
• To read files asynchronously, we call FileStream.BeginRead and pass the name of a
procedure to receive callback notification when the operation is complete.
• StreamWriter, StreamReader, BinaryWriter, and BinaryReader all provide additional,
more advanced file streaming functionality.
• ReadByte and WriteByte are the primary methods used for reading and writing binary
data.
Networking Functions CHAPTER
8
IN THIS CHAPTER
• Key Classes Related to Network
Programming 274
• Sockets 278
• Implementing a Request/Response
Model 292
• An Asynchronous Request/Response
Pattern 302
This chapter begins our in-depth discussion of namespaces by examining the functionality
exposed by the System.Net and System.Net.Sockets namespaces. This chapter will show you
how to accomplish common network-based programming tasks using the components exposed
in these two namespaces. We’ll begin by examining how the network classes in these name-
spaces provide comprehensive coverage of both high and low-level network programming
tasks. Then, by working our way from the low-level classes dealing with protocol-independent
sockets to the high-level classes that operate over HTTP, we will paint a picture of the exact
functionality offered by these base classes. We will also look at how they can serve as a great
foundation for building network-enabled .NET applications.
After reading this chapter, you should be able to:
• Discuss the layered approach to network functionality offered by the classes of the
System.Net and System.Net.Sockets namespaces
NETWORKING
FUNCTIONS
HttpWebResponse This is an implementation of the WebResponse class, specifi-
cally designed to work with the HTTP protocol.
Generic Request/Response Classes
FileWebRequest The FileWebRequest class allows you to issue requests for files
to remote servers. In other words, this class encapsulates
“file://” requests.
FileWebResponse The FileWebResponse class provides access to a file response
stream sent from a remote server.
WebRequest The WebRequest class abstracts Web-based server requests. This
class is protocol agnostic.
WebResponse The WebResponse class is used to work with Web-based server
responses. This class is protocol agnostic.
WebClient The WebClient class represents the highest level of abstraction
in the .NET networking stack. It allows for simple sending and
receiving of data from a resource. This class is protocol
agnostic.
WebException The WebException class signals errors encountered with any of
the pluggable network protocols supported by .NET.
Working with the .NET Namespaces
276
PART II
FIGURE 8.1
The network class layers in .NET.
Working from the most complex, highest level of control to the least complex, least amount of
control, the base classes can be grouped in the following way: 8
• The socket level of functionality allows direct interaction with datagrams and streams.
NETWORKING
FUNCTIONS
Representative classes include the Socket class.
• The transport level of functionality allows us access to TCP- and UDP-based features.
The core classes here are the TCPClient, UDPClient, and TCPListener.
• The application level of functionality deals principally with HTTP-based Web communi-
cations. The HTTPWebRequest and HTTPWebResponse classes represent this level of
control.
• The “Protocol Agnostic” level of functionality allows easy connection and communica-
tion to a URI without worrying about protocol specifics. A URL (for example,
www.microsoft.com) is a common form of a URI. The WebRequest, WebResponse,
and WebClient classes represent this level of functionality.
Each successive layer of the networking classes builds on the layer below it, abstracting func-
tionality and complexity for us until we reach the pinnacle of ease: the WebClient class which
can, with one line of code, open a connection to a URI (Uniform Resource Identifier) and
retrieve a result.
Working with the .NET Namespaces
278
PART II
NOTE
URI stands for Uniform Resource Identifier; it is a string that uniquely identifies a
resource. URLs, or Uniform Resource Locators, are one form of a URI that describe
Web addresses.
The .NET networking classes offer a few distinct advantages to the old Windows DNA/Win32
API style of networking code. For one, the classes are specifically designed to function in
high-load environments. That means that you won’t have to worry about their performance,
whether you are deploying them on the server side, where scalability and speed are critical, or
on the client side, where functionality tends to be more important than scalability or speed. In
addition, they provide a way to implement a simple architecture that can achieve complex
results by exposing a logical, hierarchical component object model for use.
Now that we’ve examined the general concepts of networking and seen how the System.Net
and System.Net.Sockets classes are arranged to provide programmatic access to these net-
working tasks, we can start looking at the tangible code structures that we’ll deal with when
writing our network-level software. We will start at the bottom with the Socket class.
Sockets
A socket can be thought of as a two-way pipe; it connects two end points and allows data to
flow across the pipe from one end point to the other. As a technology, it was first implemented
as a method for exposing the TCP/IP suite to calling applications. Today, most socket APIs are
generic enough to be used for almost any interprocess communication request. From an appli-
cation developer’s point of view, a socket is something that can be “plugged into” to allow data
to be sent from one endpoint to another.
Sockets are a core technology for programming applications that communicate across IP net-
works—if you program against the Winsock API or use the Winsock Control (an OCX) in
Visual Basic 6, you are already familiar with sockets and what they can do. The
System.Net.Sockets namespace provides straightforward access to this general purpose net-
working API. Working with the Socket class in .NET very closely resembles working with the
socket calls in the Win32 API. When run on the Microsoft Windows platform, the Socket class
is merely an abstraction of the Winsock libraries; in many cases it just passes its calls into the
Winsock API for execution.
Networking Functions
279
CHAPTER 8
NOTE
Keep in mind that the .NET Framework is meant to port to platforms other than
Windows. Should that happen, the Socket class would, of course, have to talk to
something other than the WinSock API, which doesn’t exist outside of Windows.
The other System.Net and System.Net.Sockets classes often build directly on top of the
Socket class for their functionality.
Creating Sockets
A common analogy used to describe the process of socket communication is the concept of a
telephone call: Before you start talking, you first have to dial a number. In our world of net-
work classes, this means establishing a connection; and that means first creating an instance of
the Socket class.
When dealing with sockets-based communication, we are primarily concerned with the two
following classes: 8
• Socket class (from the System.Net.Sockets namespace)—This represents an instance
NETWORKING
FUNCTIONS
of a sockets interface. Remember that this is a bidirectional pipeline that is used to send
and receive data across a network.
• IPEndPoint class (from the System.Net namespace)—This class encapsulates an IP
address (and thus, one end of our “pipe”).
Instancing a socket object from the Socket class is straightforward. Its constructor takes three
arguments: the address family that the socket will use, the type of socket to create, and the type
of protocol that the socket will use once created. Let’s examine each one of these in order.
The address family identifies which type of addressing schema or scope the socket will use
.NET provides us with an enumeration constant that we can use to identify our desired address
family. Appropriately, it is named AddressFamily. Table 8.2 shows the different values sup-
ported by this enumeration.
For our purposes here, we will use the InterNetwork address family (for Internet protocol
addressing).
Working with the .NET Namespaces
280
PART II
Name Description
AppleTalk AppleTalk Addressing
Atm Asynchronous Transfer Mode addressing
Banyan Banyan networking addressing
Ccitt CCITT (X.400/X.500 addressing)
Chaos MIT CHAOS addressing
Cluster Microsoft cluster addressing
DataKit DataKit protocol addressing
DataLink DataLink interface addressing
DecNet DECNet addressing
ECMA European Computer Manufacturers Association addressing
FireFox FireFox addressing
HyperChannel NSC Hyperchannel addressing
Ieee12844 IEEE 1284.4 workgroup addressing
ImpLink ARPANET IMP addressing
InterNetwork IP v4 addressing
InterNetworkv6 IP v6 addressing
Ipx Internetwork Packet Exchange addressing
Irda Infrared Data Association addressing
Iso ISO protocol addressing
Lat Local Address Table addressing
Max MAX addressing
NetBios Network Basic Input/Output System addressing
NetworkDesigners NetworkDesigners OSI protocols addressing
NS Xerox NS addressing
Osi OSI protocol addressing
Pup PUP protocol addressing
Sna IBM SNA addressing
Unix Unix local to host addressing
Unknown <unknown address family>
Unspecified <unspecified address family>
VoiceView VoiceView addressing
Networking Functions
281
CHAPTER 8
The next parameter into our socket constructor is the socket type. There are two types available
to us: stream or datagram.
NOTE
In general, stream-based communication protocols are more reliable because they
have the capability to resend packets that have been received with an error.
Datagram-based communication tends to be faster because it doesn’t have any of the
error control functionality as overhead, but of course you pay the price in reliability.
TCP packets are stream oriented, and UDP packets are datagram oriented.
Since we are specifically talking about TCP transmissions here, we will select stream. Just as
with the previous example, the System.Net.Sockets namespace contains an enumeration,
SocketType (see Table 8.3), that we can use to easily specify our stream socket type like this:
8
SocketType.SockStream
NETWORKING
FUNCTIONS
TABLE 8.3 SocketType Enumeration
Name Description
Dgram Datagram socket
Raw Raw socket
Rdm Reliably-Delivered Messages socket
SeqPacket Sequential packet socket
Stream Stream socket
Unknown Unknown socket
The last parameter required by our socket constructor is the protocol type. Once again, an enu-
meration comes to the rescue. The ProtocolType enumeration (see Table 8.4) allows us to eas-
ily specify protocols such as TCP, UDP, or SPX among others:
ProtocolType.Tcp
Working with the .NET Namespaces
282
PART II
Name Description
Ggp Gateway to Gateway Protocol
Icmp Internet Control Message Protocol
Idp Internet Datagram Protocol
Igmp Internet Group Management Protocol
IP Internet Protocol
Ipx Internetwork Packet Exchange
ND Net Disk Protocol
Pup PUP Protocol
Raw RAW UP Protocol
Spx Sequence Packet Exchange Protocol
SpxII Sequence Packet Exchange II Protocol
Tcp Transmission Control Protocol
Udp User Datagram Protocol
Unknown Unknown Protocol
Unspecified Unspecified Protocol
Putting all of this together, we arrive at the following statement that creates an instance of the
Socket class with the appropriate properties set through its constructor:
Dim ourSocket As Socket = New Socket(AddressFamily.InterNetwork, _
SocketType.Stream, ProtocolType.Tcp)
Sending Data
Now that the socket has been created, we need to connect it to its end points. Since we are
talking in terms of TCP/IP in this example, our end points will be IP addresses. The
IPEndPoint class embodies the concept of an IP-based end point, and is available in the
System.Net library. Also in the System.Net namespace is the IPAddress class. We will use an
IPAddress instance to create our IPEndPoint instance. The first order of business is to create
an IPAddress object from a valid IP address. One convenient way of doing this is by using the
IPAddress.Parse method. We can supply this method with an IP address in dotted quad form
and have an IPAddress object returned to us:
Dim localAddress As IPAddress = IPAddress.Parse(“10.0.0.1”)
Networking Functions
283
CHAPTER 8
Now, to create an IPEndPoint instance, we pass our IPAddress into the constructor, along with
a port number, like this:
Dim localEndPoint As IPEndPoint = New IPEndPoint(localAddress, 8080)
We have now specified our local address (that is, the “near” side of the connection). Now we
need to set up the remote address (the far side of the connection). Here we will assume that we
don’t know the actual IP address of the machine to which we want to connect—we just know
its host name. To resolve a host name into an IP address, we turn to the DNS class, also located
in the System.Net library. The DNS class has a Resolve method that will accept a host/server
name:
Dim targetAddress As IPAddress
targetAddress = DNS.Resolve(“www.microsoft.com”).AddressList(0)
Dim endPoint As IPEndPoint = New IPEndPoint(targetAddress, 8080)
The Resolve method returns an IPHostEntry object. This isn’t quite what we are looking for;
we just want an IPAddress object. But the IPHostEntry object exposes an AddressList prop-
erty that will return what we are looking for. In the code example, you can see that we are
going after the first IP address associated with that particular IPHostEntry by typing this:
.AddressList(0). 8
Now, all that is left to do is connect our socket to the two end points. The Socket.Bind method
NETWORKING
FUNCTIONS
will connect us with our local end point:
‘connect the socket to the local end-point
ourSocket.Bind(localEndPoint)
And the Socket.Connect method will connect us with the remote end point:
‘connect the socket using our endpoint object
ourSocket.Connect(endPoint)
Once the socket is fully connected, we can move data across the pipe. What would it take to
send a plain text message across the socket to our end point? Not much. Socket.Send accepts
a byte array for transmission across the socket. If we wanted to send a test message like
“socket programming is easy!” we first have to massage it into the required byte array form.
Again, the .NET namespaces provide us with the answer in the form of another ready to use
class. The Encoding class from the System.Text library and its GetBytes method provide a
quick way to get what we want—an array of bytes.
Dim encoder As Encoding
Dim ourMsg As Byte() = encoder.GetBytes(“socket programming is easy!”)
To send, the string requires a Send method call from our Socket class:
ourSocket.Send(ourMsg)
Working with the .NET Namespaces
284
PART II
Receiving Data
If your application is one that sits on the other end of the pipe and is a data recipient, or if you
are expecting a reply back from data that you have sent out across the socket, you will employ
the Receive method of the Socket class.
The Receive method acts in much the same way as the Send method in that it deals with byte
arrays instead of serialized strings. To get the byte array into a more usable form, we can again
use the Encoding class. This time we will call the GetString method, passing it the byte array.
Examine the code in Listing 8.1. First, we create an empty buffer to hold the data coming in
via the Receive method. The buffer is a byte array, pre-declared at a set size (we could have
used a variable array here as well). The Receive function expects an array to store the data,
and also needs to know the total length of the array and in what position in the array it should
start filling the data. Besides filling the provided buffer with the incoming data, the receive
method also returns the number of bytes received as its return parameter—and starts filling
from the beginning of the array.
After receiving all of the data, we want to shut the socket down. This is supported through the
Socket.Shutdown method. This method takes an instance of the SocketShutdown enumeration
(see Table 8.5). We can tell the socket that we want to shut down its ability to send data,
receive data, or both send and receive data.
Name Description
Both Shut down the socket from sending and receiving data
Receive Shut down the socket for receiving data
Send Shut down the socket for sending data
Module Module1
Sub ReceiveOverSocket()
‘buffer for the incoming data (capped at 256)
Dim buffArray(256) As Byte
NETWORKING
‘IPAddress(objects)
FUNCTIONS
‘A host may have more than one IP attached to it; we just want the
‘first one returned in the AddressList property (zero based)
targetAddress = Dns.Resolve(“www.microsoft.com”).AddressList(0)
Dim endPoint As IPEndPoint = New IPEndPoint(targetAddress, 8080)
‘To serialize, just pass the byte array through the GetString
‘method.
dataString = dataEncoder.GetString(buffArray, 0, numBytes)
ourSocket.Shutdown(SocketShutdown.Both)
End Sub
End Module
Working with the .NET Namespaces
286
PART II
NOTE
If you have written any socket-oriented applications in Visual Basic before, you may
already have a routine ripe for porting to .NET that will map these error numbers to
their more meaningful error descriptions.
So, we can add a catch block to a generic exception handler to deal specifically with socket
exceptions:
Sub Main()
‘The exception handler is initiated with the ‘try’ block
Try
‘this is where we would put some code that deals with
‘sockets
NETWORKING
FUNCTIONS
Catch appError As Exception
‘handle the error; here, we just alert the user
‘through a message box. To get more detailed
‘debugging level info, we could use the
‘Exception.StackTrace property...
MsgBox(“Error:” & appError.Message)
Finally
Beep()
End Try
End Sub
NOTE
Any class that inherits from the Socket class will throw SocketException instances.
This includes the DNS class as well as the TCPClient, TCPListener, and UDPClient
classes.
Working with the .NET Namespaces
288
PART II
The previous instantiation code handles the details of actually obtaining a connection to the
URI that we want. Writing data to the resulting socket is also simple. The GetStream method
defined by the TCPClient class returns an interesting structure that deserves more investiga-
tion—a NetworkStream.
Network Streams
With .NET, Microsoft has generalized the many different applications and uses of data streams
into one super-class called Stream. This powerful class, which is represented in the System.IO
namespace, can be used to represent and interact with any type of stream that you can think of
including file streams, network streams, XML data streams, and data streams from databases,
among others. As you gain more exposure to the .NET base classes, you will realize that
streams are a consistent concept that runs through the entire fabric of .NET. In fact, the
System.Net.Sockets library contains a class called NetworkStream that is a direct subclass of
the Stream class.
NOTE
Although the NetworkStream class is derived from the parent Stream class, there are a 8
few methods and properties that are not supported. The Seek and Position methods
NETWORKING
that are defined in the Stream class will throw an exception if you try to use them.
FUNCTIONS
And NetworkStream objects are not seekable (you can test for this by examining the
CanSeek property—it will always return false).
The Write method available off of the NetworkStream class can be used to send data across the
stream to the target end point. Similar to the example we looked at with the Socket send
method, the NetworkStream Write method accepts a byte array containing the actual data to be
sent across the stream. It also accepts an offset into the byte array (the point at which you want
to start sending data) and an integer representing the total number of bytes to send:
Dim netStream As NetworkStream = Tcp.GetStream()
netStream.Write(bytArray, 0, bytArray.Length)
If we were to rewrite our previous socket code using the TCPClient class, it would look some-
thing like this:
‘create our TCPClient class
Dim tcp As TCPClient = New TCPClient(“www.microsoft.com”,8080)
The third class we need to discuss at this level of networking code is the TCPListener class.
But before moving on, let’s take a minute to recap network operations using the Socket class
and the TCPClient class by looking at their key differentiators.
Socket
• Allows programming and interoperability across a wide range of protocols (including
custom protocols).
• Represents the lowest level of control over network communications in the Framework
Class Library.
TCPClient
• Streamlined and simplified specifically for Internet-based TCP communication.
• Leverages the NetworkStream class for reads and writes.
• Knows only about TCP packet construction.
• Actually built on top of the Socket class; represents a higher level of abstraction.
NOTE
There is no equivalent to the TCPListener class for UDP packets; in other words, the
UDPListener class does not exist.
The TCPListener class has an overloaded constructor. You can create an instance by providing
one of the following: a port to listen to, an IPEndPoint, or an IP address and port. Once instan-
tiated, use the Start method and Accept methods to access any incoming connection through
its socket. The Start method initiates the listener, while the Accept method actually allows a
Networking Functions
291
CHAPTER 8
connection to be made, returning either a Socket instance or a TCPClient instance that you
can then use to talk across the connection. The code in Listing 8.2 shows the specifics of lis-
tening and reacting to TCP connections. The following example creates a listener object that
waits for a TCP connection on port 8080.
Module Module1
NETWORKING
FUNCTIONS
‘If a connection is requested, the next line of code will
‘return a TCPClient object. If you wanted a socket object
‘instead of a TCPClient object, you could do the following
‘instead:
‘Dim objSocket As Socket = objListener.AcceptSocket()
‘Code will block here until a connection is attempted
Dim tcp As TcpClient = listener.AcceptTcpClient()
End Sub
End Module
Working with the .NET Namespaces
292
PART II
Creating Requests
The WebRequest and WebResponse classes form the underpinnings for the request/response
model in .NET. They represent a protocol-agnostic view; the classes themselves contain a class
factory that will manufacture the appropriate protocol-specific class depending on what
Uniform Resource Identifier (URI) you are trying to connect to. These so-called descendant
classes are protocol-specific implementations of the more generic WebRequest/WebResponse
classes. A request to an HTTP URI, for instance, would generate a HTTPWebRequest class, a
request to a file-based URI would generate an FileWebRequest class, and so on. For example,
the following code attempts a connection to a URL through the WebRequest class:
Dim rqst = WebRequest.Create(“http://www.samspublishing.com”)
Retrieving Responses
We can then use the GetResponse method off of the WebResponse class to retrieve the
response generated by our target URI:
Dim resp = rqst.GetResponse()
It is important to note a subtle difference in dealing with these classes: You do not directly cre-
ate instances of these classes. Rather, you must rely on the Create method to generate new
instances of the WebRequest class and the GetResponse method to generate new instances of
the WebResponse class.
After retrieving the response, we can deal with the stream it represents by using the
GetResponseStream method:
From this point, our interaction can follow the same design patterns that we previously saw
when discussing streams created from the TCPClient class.
Networking Functions
293
CHAPTER 8
NOTE
If there is no compelling reason to deal with protocol-specific properties, it is better
to construct your request/response designs by using the WebRequest and WebResponse
classes. In this way, if new protocols are added to .NET, your code will automatically
function appropriately with those protocols with no coding changes necessary.
The resulting HTTPWebResponse object offers a slightly different pattern of properties and
methods than the parent WebResponse class. We now have access to new properties and meth-
ods that were not previously available. In addition, the methods and properties defined by the 8
WebResponse class have been overridden to return or process HTTP information. The
NETWORKING
FUNCTIONS
ProtocolVersion property, for instance, does not exist on the WebResponse base class but is
implemented on the HTTPWebResponse class to return the actual version of the HTTP protocol
with which the response was formatted. This is a good example of a subclass providing a new
member to the base class. An example of a property being overridden is found in the Headers
property: This method now returns HTTP-specific name/value pairs. For more information on
actual properties and methods supported, see the reference located at the end of this chapter.
You can examine the StatusCode property on the HttpWebResponse class to get access to the
HTTP status codes returned as part of a response. It returns an enumeration that evaluates to
the HttpStatusCode enumeration, shown in Table 8.7.
Equivalent HTTP
Name Status Code Description
Accepted 202 The request was accepted.
Ambiguous 300 The server couldn’t decide
what to return (equivalent to
MultipleChoices).
BadGateway 502 An intermediate proxy server
received a bad response.
Working with the .NET Namespaces
294
PART II
NETWORKING
FUNCTIONS
could not be found on the server.
NotImplemented 501 The server does not have the func-
tionality required to fulfill the
request.
NotModified 304 The requested resource has not
been changed from the client’s
cached copy.
OK 200 The request was successful, the
requested data is in the response.
PartialContent 206 In response to a GET command that
included a byte range, the server
answered with a partial response.
PaymentRequired 402 Not currently defined in the HTTP
protocol.
PreconditionFailed 412 The server can not meet one or
more of the conditional request
headers specified.
Working with the .NET Namespaces
296
PART II
NETWORKING
FUNCTIONS
Unauthorized 401 The requested resource requires
proper authentication, which was
not supplied.
UnsupportedMediaType 415 The server has refused the request
because the request itself is an
unsupported type.
Unused 306 Not currently defined in HTTP 1.1.
UseProxy 305 The requested resource needs to be
accessed through the proxy identi-
fied in the Location header.
Listing 8.3 pulls together all the request/response objects we have talked about for creating
requests, receiving responses, and dealing with exceptions. This console application attempts
to open a Web page (by issuing a request), and then displays the results to the console window
(by writing out the response). Any errors encountered along the way are also written out to the
console window.
Module Module1
Sub Main()
Try
Networking Functions
299
CHAPTER 8
‘Read from the stream and write any data to the console.
numBytes = netStream.Read(buffArray, 0, buffArray.Length)
8
While numBytes > 0
NETWORKING
FUNCTIONS
For currIndex = 0 To numBytes - 1
Console.Write(“{0}”, buffArray(currIndex))
Next currIndex
Console.WriteLine()
numBytes = netStream.Read(buffArray, 0, buffArray.Length)
End While
End If
Catch appErr As Exception
‘ non-web error raised...
Console.WriteLine(“An app error was encountered: {0}”, _
appErr.ToString)
Finally
Console.WriteLine(“...”)
Console.WriteLine(“Finished.”)
Console.WriteLine(“Hit <ENTER> to exit.”)
Console.ReadLine()
End Try
End Sub
End Module
Networking Functions
301
CHAPTER 8
To upload data to a server, all we need to know is the URI and a byte array with the data that 8
we want to send. The following code shows how easy this is:
NETWORKING
FUNCTIONS
Dim web As New WebClient()
web.UploadData(myURL, byteArray())
Uploading an actual file is just a slight variation on this syntax. Instead of a byte array of data,
we pass in a fully qualified filename:
web.UploadFile(myURL, myFile)
NOTE
Both the UploadData and UploadFile methods perform their work through HTTP
POST commands.
The DownloadData and DownloadFile methods are mirrors of their upload counterparts that we
just discussed. The DownloadData method takes an address parameter and returns a byte array
of the data that was downloaded. The DownloadFile method takes an address and a fully quali-
fied filename (and doesn’t return anything).
buffArray() = web.DownloadData(myURL)
web.DownloadFile(myURL, localFilename)
Working with the .NET Namespaces
302
PART II
Overview
First, let’s talk through a scenario: You want to issue a request for a Web page from a server.
After the request has been made, your application should continue without waiting for the
response. Once the response comes back, your application should be signaled somehow, so
that it can now deal with the data it has received. This sets the stage for our asynchronous
request/response design pattern.
With this scenario in mind, let’s walk through each step and see how we can use the intrinsic
capabilities of the .NET Framework and the networking classes to handle each piece of this
pattern.
Networking Functions
303
CHAPTER 8
This will create an AsyncCallBack instance with the address of the subroutine that needs to
react to the callback.
The state object, used in the BeginGetResponse method call, is a little tricky to understand 8
until we get farther into the async process. For now, just accept the fact that it is used to persist
NETWORKING
FUNCTIONS
data between asynchronous method calls. One of the things that we are interested in persisting
is the actual WebRequest object used to make the BeginGetResponse call. Other than its
slightly confusing reason for existence at this point, there is really no mystery to this state
object. It is simply an instance of a class that you create to hold state through properties. In
this example, we could define the class like this:
Public Class State
Public httpRqst As HttpWebRequest
Note that we have explicitly defined the request property as an HttpWebRequest instance since
we know we are going after a Web page. To actually issue our async request, two things need
to be done. First, the request object itself must be instantiated and then assigned into an
instance of the State class that we created:
Dim rqst As HttpWebRequest = WebRequest.Create(someURL)
Dim aState As State = New State()
aState.httpRqst = rqst
Working with the .NET Namespaces
304
PART II
End Sub
This IAsyncResult interface is the key here: We will use it to get to the response object.
Remember that the WebResponse object is what is created in response to the GetResponse
method. The same holds true for our BeginGetResponse call—we will need to get a handle to
the resulting WebResponse object in order to examine the response data. The EndGetResponse
method will return us the response object that we are looking for. First, the request object will
need to be pulled back out of the state object’s property, and then the EndGetResponse method
will be called:
Public Sub ReceiveResponse(rslt As IAsyncResult)
Dim retState As State = CType(rslt.AsyncState, State)
Dim httpRqst As HttpWebRequest = retState.httpRqst
NOTE
Just because you have implemented an async pattern with the WebRequest and
WebResponse classes doesn’t mean that you have to do so with the Stream class as
well. After receiving the response instance, you could simply interact with its stream
synchronously. Microsoft, however, strenuously advises against “mixing” synchronous
access with asynchronous in a tightly bound process like this for performance reasons.
It is often easiest to just reuse the state object you have already created; add a few new proper-
ties to the class to hold the read buffer for the stream, and perhaps the concatenated results of
the multiple stream reads, and you are all set.
Dim respStream As Stream = resp.GetResponseStream()
retState.respStream = respStream
NETWORKING
FUNCTIONS
this point, you should recognize the stream read pattern from our previous sections.
Public Sub ReadStream(rslt As IAsyncResult)
Dim retState As RequestState = rslt.AsyncState
Figure 8.2 shows the entire pattern laid out from a process flow perspective. You can see that
Step 1 is indeed a call to BeginGetResponse.
1:BeginGetResponse
9: EndRead
2: (callback)
8: (callback)
4: (instantiate)
7: BeginRead
5: GetResponseStream
WebResponse
Stream
6: (instantiate)
FIGURE 8.2
A basic async flow for the WebRequest/WebResponse classes.
NETWORKING
scape of network resources and servers that blindly accept our requests for connections, file
FUNCTIONS
downloads, resource uploads, and protocol queries. This has been useful to conduct our quick
tour of these networking classes, but the real world acts a bit differently: Conscientious server
administrators and software architects tend to require proper credentials from a client before
handing over the rights to access files or traverse Web directories. In this section, we will talk
about the System.Net structures that allow programmers to provide credentials when querying
resources. We will also talk a bit about using proxies in our programming efforts.
Authentication Methods
Before we look at the actual helper classes for dealing with credentials and authentication,
we’ll take a high-level look at the different types of authentication supported by the .NET
classes. For Internet communication, there are primarily five different types of authentication
supported: Basic, Digest, NTLM, Kerberos, and Negotiate.
You may already be familiar with these concepts if you have been responsible for structuring
security on IIS Web servers before—particularly, with IIS 8.0 and above. For a more in-depth
treatment of security in general, you may want to look at Chapter 14, “Browser/Server
Communications,” where we cover security functions. Because we will be referencing some of
these in our code examples in this section, here is some basic information on these different
authentication methods:
Working with the .NET Namespaces
308
PART II
• Basic Authentication: This is a clear-text method in which the username and password
are encoded (not encrypted) and then sent to the server.
• Digest Authentication: This is an encrypted method of authentication: The server will
issue a nonce—a random data string—that the client then uses to encrypt its credential
information. This information is sent back to the server where it is compared to the
expected values. If the two match, authentication is accomplished.
• Kerberos Authentication: This method of authentication relies on users being authenti-
cated by a Kerberos Authentication Server. Once authenticated, the user uses this
encrypted “ticket” as a pass to access specific services.
• NTLM: Probably more commonly known as Windows NT Challenge/Response, this also
is an encrypted method of sending user credentials. In this case, the encryption is based
on a hash algorithm and it is also uuencoded. NTLM stands for NT LAN Manager.
Now let’s look at the specific classes that will help us to actually use credentials when query-
ing network resources.
Encapsulating Credentials
The NetworkCredential class is used to encapsulate credentials for use when requesting a
resource. Credentials are simply pieces of identifying data that can be associated with a level
of authorization in a given system. When we log into a Windows 2000 Server, we supply a
login name (or username) and password as credentials.
NOTE
Other authentication schemes may make use of other forms of credentials, but the
login name and password pair is arguably the most common form that you will bump
into in your programming efforts, and certainly the prevailing form of credentials on
the Web.
As we saw in the previous paragraphs, these credentials are commonly encoded or encrypted
using standardized methods.
The NetworkCredential class is fairly light in terms of properties and methods. It allows you
to supply password and username pairs through the Password and UserName properties—you
can also specify these items through the class constructor. The class also supports specification
of a domain through the Domain property. In terms of methods, its one unique method is the
GetCredential method that accepts a URI and an authentication type and returns a
NetworkCredential instance.
Networking Functions
309
CHAPTER 8
Probably the most common and easiest use of this class will be through simple instantiation
and the use of its constructor. The following code creates a NetworkCredential instance with
the username “John Doe”, password “fortknox”:
Dim creds As NetworkCredential = new NetworkCredential( “John Doe”, _
“fortknox”)
The NetworkCredential class is to be used in conjunction with some of the other classes that
we have talked about in the previous sections of this chapter. Let’s take a look at how we can
use an instance of the NetworkCredential class in conjunction with the WebClient class and
the WebRequest class. In general, most of the network classes support the specification of cre-
dentials through a Credentials property. This property can be assigned an instance of a
NetworkCredential object. The specified data is then presented for authentication (if needed)
to the resource controller (server). In essence, this is meant to provide evidence of your code’s
capability to perform the particular function or access the particular resource that you are tar-
geting. Thus, to specify credentials for use with a WebClient instance, we can say:
Dim web as WebClient
web.Credentials = New NetworkCredential(“John Doe”, “fortknox”)
NETWORKING
rqst.Credentials = New NetworkCredential(“John Doe”, “fortknox”)
FUNCTIONS
Table 8.9 shows the classes in the System.Net and System.Net.Sockets namespaces that sup-
port the use of the Credentials property.
Using the NetworkCredential class in the ways we have shown is best if you are dealing only
with a small number of URIs that you need to access, or if you will be providing the same set
of credentials for each of the URIs you need to access. If you are dealing with a multitude of
URIs, each with their own credentials that have to be passed, the preferred solution is to use
the System.Net structure for caching credentials: the CredentialCache class.
Working with the .NET Namespaces
310
PART II
Module Module1
Sub Main()
‘the WebClient we will use with the cred cache
Dim myClient As WebClient
End Module
Caching your credentials centralizes maintenance of logins in your code, and helps to make for
a much more robust solution.
Networking Functions
311
CHAPTER 8
NOTE
The WebProxy constructor has many different, overloaded forms. We could, for
instance, have supplied a URI instance instead of a string to specify our URL. Check
with the .NET Framework SDK documentation to see which constructor works best
8
for your particular situation.
NETWORKING
FUNCTIONS
If you do not specify a proxy when using the request classes, the system will use the global
proxy server setting. By default, this will be set to whatever your local Internet Explorer set-
tings are set. You can also change this global setting by using the GlobalProxySelection
class. Let’s look at some code that sets a global proxy server; the proxy settings we implement
will hold for all created instances of WebRequest/HTTPWebRequest objects, unless we choose
to explicitly override them by using the Proxy property that we just discussed.
This code will have the same effect as our previous code example: All requests made through
the request object that we have created will be routed through http://ourproxy:8080.
Dim myProxy As WebProxy = New WebProxy(“http://ourprxy:8080”)
GlobalProxySelection.Select = myProxy
are trying to connect. Refer to the .NET Framework SDK documentation for more infor-
mation on how this class can help you to centralize connection management and speed
up URI requests.
➲ General information on security in the .NET Framework can be found in Appendix C,
“.NET Security Models.”
FIGURE 8.3
The SocketTransmitter application.
8
3. Type in a message/command to send to the remote end point. Confused about what to
NETWORKING
FUNCTIONS
enter for a command? Try researching the different protocol standards to see what
common sets of commands they have defined. To get you started, you could try the
following:
• Connect to a server that you know is a newsgroup server. Then issue NNTP-
specific commands such as “authinfo user xxxx pass yyyy” where xxxx is your
login id and yyyy is your password. Once logged in, try selecting a specific news-
group by sending “GROUP xxxx” where xxxx is the newsgroup name. From there,
you can read different articles by using the article command (“ARTICLE xxxx”,
where xxxx is the article number).
• Connect to an HTTP server and then request a Web page by using the GET com-
mand. “GET / HTTP/1.1\r\nHost: “ + server + “\r\nConnection: Close\r\n”.
• You can get more ideas of command by examining the actual Request for
Comments or Standards available at www.w3.org.
4. The messages sent out, the responses received, and any errors encountered should all
show up in the Activity Log.
Working with the .NET Namespaces
314
PART II
Code Walkthrough
Listing 8.4 walks you through the code of a sample application—the Socket Transmitter.
For the most part, the Windows Forms designer generates this code. We have added code to
initialize the visual state of the form and to actually instantiate a socket instance through a call
to a private subroutine called CreateSocket.
‘Add any initialization after the InitializeComponent() call
SetFormState(“INITIAL”)
CreateSocket()
End Sub
NETWORKING
‘It can be modified using the Windows Form Designer.
FUNCTIONS
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub _
InitializeComponent()
Me.Label4 = New System.Windows.Forms.Label()
Me.buttonBind = New System.Windows.Forms.Button()
Me.Label1 = New System.Windows.Forms.Label()
Me.Label2 = New System.Windows.Forms.Label()
Me.Label3 = New System.Windows.Forms.Label()
Me.textBoxLocalPort = New System.Windows.Forms.TextBox()
Me.checkResolveRemote = New System.Windows.Forms.CheckBox()
Me.GroupBox2 = New System.Windows.Forms.GroupBox()
Me.buttonSend = New System.Windows.Forms.Button()
Me.textBoxSend = New System.Windows.Forms.TextBox()
Me.GroupBox3 = New System.Windows.Forms.GroupBox()
Me.listBoxActivity = New System.Windows.Forms.ListBox()
Me.buttonClear = New System.Windows.Forms.Button()
Me.checkResolveLocal = New System.Windows.Forms.CheckBox()
Me.buttonConnect = New System.Windows.Forms.Button()
Me.textBoxLocal = New System.Windows.Forms.TextBox()
Me.textBoxRemote = New System.Windows.Forms.TextBox()
Me.StatusBar = New System.Windows.Forms.StatusBar()
Me.GroupBox1 = New System.Windows.Forms.GroupBox()
Me.textBoxRemotePort = New System.Windows.Forms.TextBox()
Me.GroupBox2.SuspendLayout()
Working with the .NET Namespaces
316
PART II
NETWORKING
FUNCTIONS
Me.GroupBox2.Size = New System.Drawing.Size(396, 52)
Me.GroupBox2.TabIndex = 1
Me.GroupBox2.TabStop = False
Me.GroupBox2.Text = “Send a Message”
‘
‘buttonSend
‘
Me.buttonSend.Location = New System.Drawing.Point(316, 20)
Me.buttonSend.Name = “buttonSend”
Me.buttonSend.Size = New System.Drawing.Size(72, 20)
Me.buttonSend.TabIndex = 1
Me.buttonSend.Text = “Send”
‘
‘textBoxSend
‘
Me.textBoxSend.Location = New System.Drawing.Point(8, 20)
Me.textBoxSend.Name = “textBoxSend”
Me.textBoxSend.Size = New System.Drawing.Size(300, 20)
Me.textBoxSend.TabIndex = 0
Me.textBoxSend.Text = “”
‘
‘GroupBox3
‘
Me.GroupBox3.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.listBoxActivity, Me.buttonClear})
Working with the .NET Namespaces
318
PART II
NETWORKING
FUNCTIONS
Me.GroupBox1.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.Label4, Me.textBoxRemotePort, Me.textBoxLocalPort, Me.Label3, _
Me.checkResolveRemote, Me.checkResolveLocal, Me.buttonConnect, _
Me.buttonBind, Me.Label2, Me.textBoxRemote, Me.textBoxLocal, _
Me.Label1})
Me.GroupBox1.Location = New System.Drawing.Point(8, 4)
Me.GroupBox1.Name = “GroupBox1”
Me.GroupBox1.Size = New System.Drawing.Size(400, 172)
Me.GroupBox1.TabIndex = 0
Me.GroupBox1.TabStop = False
Me.GroupBox1.Text = “Specify the Socket End-Points”
‘
‘textBoxRemotePort
‘
Me.textBoxRemotePort.Location = New System.Drawing.Point(76, 116)
Me.textBoxRemotePort.MaxLength = 5
Me.textBoxRemotePort.Name = “textBoxRemotePort”
Me.textBoxRemotePort.Size = New System.Drawing.Size(40, 20)
Me.textBoxRemotePort.TabIndex = 1
Me.textBoxRemotePort.Text = “”
‘
‘SocketTransmitter
‘
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(416, 509)
Working with the .NET Namespaces
320
PART II
End Sub
#End Region
End Sub
This routine creates a new socket instance. We use a structured exception handler to catch any
errors at this point and display them to the user.
Private Sub CreateSocket()
‘This creates our socket object; we have hard-coded the address family,
‘socket type, and protocol type. Feel free to lay around with these
‘settings...
Try
netSocket = New Socket(AddressFamily.InterNetwork, _
SocketType.Stream, ProtocolType.Unspecified)
End Sub
Clicking on the Bind button will call this routine. This assumes that CreateSocket has previ-
ously been called, and that we now have a valid socket object (netSocket) to work with. If the
local end point address requires DNS resolution, we indicate this through the useDNS Boolean
parameter. The local parameter identifies the local address for the bind, and the port parame-
ter identifies the local port for the bind.
Networking Functions
321
CHAPTER 8
If useDNS Then
‘User has supplied a server name; we first need to resolve it
‘using the DNS class
localAddress = Dns.Resolve(“local”).AddressList(0)
Else
‘User has supplied an IP address in dotted quad format; just
‘use the IPAddress.Parse method to get at the actual
‘IPAddress instance
localAddress = IPAddress.Parse(local)
End If
NETWORKING
FUNCTIONS
Catch sockErr As SocketException
‘A socket exception was encountered.
End Try
End Sub
Working with the .NET Namespaces
322
PART II
If useDNS Then
‘User has supplied a server name; we first need to resolve it
‘using the DNS class
serverAddress = Dns.Resolve(server).AddressList(0)
Else
‘User has supplied an IP address in dotted quad format; just
‘use the IPAddress.Parse method to get at the actual
serverAddress = IPAddress.Parse(server)
End If
End Try
End Sub
This is where all of the action is. The SendToSocket routine takes the text “command” or mes-
sage typed in and sends it across the socket to the machine sitting at the remote end point.
After sending the message, the ReceiveFromSocket routine is called.
Private Sub SendToSocket(ByVal msg As String)
‘This routine sends data across the socket and then
‘receives the reply.
8
‘Translate the string into a byte array
Dim encoder As Encoding
NETWORKING
FUNCTIONS
Dim sendMsg As Byte() = encoder.GetBytes(msg)
Try
Dim bytesSent As System.Int32
ReceiveFromSocket()
End Try
End Sub
The ReceiveFromSocket subroutine collects data coming back across the socket in response to
the message sent. It will loop through the bytes received until there are no more left; it then
writes the response out to the activity log on the main form.
Private Sub ReceiveFromSocket()
Try
Dim bytesRcvd As System.Int32
Dim rcvBuffer As Byte()
Dim reply As String
End Try
8
NETWORKING
FUNCTIONS
End Sub
This is just a utility routine; it is responsible for doing some basic formatting on the “mes-
sages” we write to the screen by way of the activity log.
Private Sub WriteActivity(ByVal msg As String, ByVal msgType As String)
Dim prefix As String
End Sub
Case “BOUND”
textBoxLocal.Enabled = False
buttonBind.Enabled = False
checkResolveLocal.Enabled = False
textBoxRemote.Enabled = False
checkResolveRemote.Enabled = False
textBoxSend.Enabled = False
buttonSend.Enabled = False
Case “CONNECTED”
textBoxLocal.Enabled = False
buttonBind.Enabled = False
checkResolveLocal.Enabled = False
textBoxRemote.Enabled = False
checkResolveRemote.Enabled = False
textBoxSend.Enabled = True
buttonSend.Enabled = True
End Select
End Sub
This is a helper class that we use to provide more descriptive error descriptions whenever the
application encounters an actual WinSock exception.
Public Class WinSockError
Public Function GetDescription(ByVal errNum As System.Int32) As String
NETWORKING
FUNCTIONS
GetDescription = “Address family not supported by protocol _
family”
Case 10037
GetDescription = “Operation already in progress”
Case 10053
GetDescription = “Software caused connection abort”
Case 10061
GetDescription = “Connection refused”
Case 10054
GetDescription = “Connection reset by peer”
Case 10039
GetDescription = “Destination address required”
Case 10014
GetDescription = “Bad address”
Case 10064
GetDescription = “Host is down”
Case 10065
GetDescription = “No route to host”
Case 10036
GetDescription = “Operation now in progress”
Case 10004
GetDescription = “Interrupted function call”
Case 10022
GetDescription = “Invalid argument”
Working with the .NET Namespaces
328
PART II
End Function
End Class
NETWORKING
the form and by watching the actual price column—it will transition from “waiting” to an
FUNCTIONS
actual dollar amount (that is, if the program was successful in parsing a price out of the HTML
response). Here are a few notes on the application:
• The application will prompt you for an ISBN number (some very basic bounds and pat-
tern checking will be done to see if you actually typed in a valid ISBN from a format
perspective).
• The application will then go out and query a list of Web site resources that function as
ISBN-based interfaces into a bookseller’s Web site.
• The resulting HTML response is parsed in an attempt to find the price of the book. The
application stores a specific string pattern by each site entry; it will look for this pattern
when attempting to discern the actual pricing information. Note that HTML parsing is a
pretty poor way to derive data from a Web page; it would be far better if the data was
delineated in an XML format!
FIGURE 8.4
ISBNCrawler: The main dialog.
This dialog is pretty self-explanatory. Simply select which of the “big three” you want to query
for a particular book, enter the ISBN number in the text box, and press the Go button. You
should see a message that the server is being queried. After being queried, the hourglass should
go away, control should be returned back to the application, and the form will indicate that it is
waiting for a response.
Once the response is received, the raw HTML will be displayed in the Returned HTML text
box. If the application was successful at parsing a price out of the HTML, it will show up to
the right of the Go button.
Code Walkthrough
Listing 8.5 walks you through the code of a sample application—the ISBNCrawler.
We hold the actual URL of the site to be queried in the form-local variable targetSite. We
also hold our State object at this scope as well.
‘Currently targeted site
Dim targetSite As String
Dim aState As State = New State()
These are the constants being used for the ISBN query facility for each site.
Const AMAZON_QRY As String = “/exec/obidos/ASIN/”
Const BARNES_QRY As String = “/isbninquiry.asp?isbn=”
Const BORDERS_QRY As String = “/fcgi-bin/db2www/search/search.d2w/
➥Details?mediaType=Book&searchType=ISBNUPC&code=”
Windows Form Designer Code: nothing special here, although we do “initialize” the targetSite
variable with the URL for Amazon.com.
#Region “ Windows Form Designer generated code “
8
Public Sub New()
MyBase.New()
NETWORKING
FUNCTIONS
‘This call is required by the Windows Form Designer.
InitializeComponent()
NETWORKING
FUNCTIONS
‘
Me.textBoxHTML.Location = New System.Drawing.Point(12, 24)
Me.textBoxHTML.Multiline = True
Me.textBoxHTML.Name = “textBoxHTML”
Me.textBoxHTML.Size = New System.Drawing.Size(424, 180)
Me.textBoxHTML.TabIndex = 4
Me.textBoxHTML.Text = “”
‘
‘GroupBox2
‘
Me.GroupBox2.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.RadioButton3, Me.RadioButton2, Me.RadioButton1})
Me.GroupBox2.Location = New System.Drawing.Point(16, 12)
Me.GroupBox2.Name = “GroupBox2”
Me.GroupBox2.Size = New System.Drawing.Size(208, 112)
Me.GroupBox2.TabIndex = 1
Me.GroupBox2.TabStop = False
Me.GroupBox2.Text = “Target Site”
‘
‘RadioButton3
‘
Me.RadioButton3.Location = New System.Drawing.Point(20, 80)
Me.RadioButton3.Name = “RadioButton3”
Me.RadioButton3.Size = New System.Drawing.Size(172, 16)
Me.RadioButton3.TabIndex = 2
Me.RadioButton3.Text = “Borders.com”
‘
Working with the .NET Namespaces
334
PART II
End Sub
#End Region
8
When you press the Go button, we first check to see if a valid ISBN number was entered (this
is a rudimentary check at best). If everything checks out, we change the cursor, display a mes-
NETWORKING
FUNCTIONS
sage through the form title bar, and then call IssueAsyncRequest.
Private Sub buttonGo_Click(ByVal sender As System.Object, ByVal e As _
System.EventArgs) Handles buttonGo.Click
‘We will first pass the ISBN number through a short validation
‘routine; if everything looks good, we can go ahead and issue our async
‘requests.
If ValidISBN(Trim(textBoxISBN().Text)) Then
buttonGo.Enabled = False
Me.Cursor = System.Windows.Forms.Cursors.WaitCursor
Me.textBoxHTML.Text = “”
Me.Text = “ISBNCrawler - Issuing request”
targetSite = targetSite & Trim(textBoxISBN.Text)
IssueAsyncRequest()
Me.Text = “ISBNCrawler - Waiting for response...”
Me.Cursor = System.Windows.Forms.Cursors.Default
buttonGo.Enabled = True
Else
IssueAsyncRequest launches a request object, thereby starting off the async design pattern.
The WebRequest object is created with the URL specified in targetSite, and then the
BeginGetResponse method is called.
ResetFormCursor()
ResetFormCursor()
End Try
End Sub
This is the subroutine, which should receive the callback once a response is received. After
retrieving the response stream, an asynchronous read is started on the stream by calling
BeginRead.
Networking Functions
337
CHAPTER 8
NETWORKING
FUNCTIONS
Dim respStream As Stream = httpResp.GetResponseStream()
ResetFormCursor()
ResetFormCursor()
End Try
End Sub
The ReadStream subroutine received the call back from the async stream read. If more bytes
remain to be read, it will call itself again until the data is exhausted. Once all of the data has
been read in, its entirety is written out to the HTML results box, and the parsed price (if one
was found) is displayed to the screen as well.
Private Sub ReadStream(ByVal rslt As IAsyncResult)
Try
‘Get the state object from the async result
Dim retState As State = CType(rslt.AsyncState, State)
Else
‘No more data in the stream; parse the price out and
‘write the HTML to the form
Dim price As String = ParsePrice(retState.RqstData.ToString)
Me.textBoxHTML.Text = retState.RqstData.ToString
Me.Cursor = System.Windows.Forms.Cursors.Default
End If
ResetFormCursor()
8
NETWORKING
FUNCTIONS
MsgBox(“An IO error occurred with the stream object->” & _
streamErr.Message & “;” & streamErr.StackTrace)
ResetFormCursor()
End Try
End Sub
ParsePrice attempts to pull the actual book price out of a string by looking for the pattern
“Our Price:”. Again, this is not the best way to do things, but it suffices for the scope of this
demonstration.
Private Function ParsePrice(ByVal resp As String) As String
‘This routine just performs some rudimentary guessing in terms of the
‘price returned to us in the response object
Dim currPos As Integer
Dim priceGuess As String
Working with the .NET Namespaces
340
PART II
End Function
‘now look for evidence that it is not (these are not exhaustive _
obviously...)
If isbn.Length > 10 Or isbn.Length < 10 Then
ValidISBN = False
ElseIf Not IsNumeric(isbn) Then
ValidISBN = False
End If
End Function
End Sub
This class is our state class, responsible for holding onto our stream items and request/response
items between async calls.
8
Imports System
Imports System.Net
NETWORKING
FUNCTIONS
Imports System.Text
Imports System.IO
End Sub
End Class
Working with the .NET Namespaces
342
PART II
Summary
The networking classes exposed in the class library represent a very powerful tool for the
Visual Basic .NET developer. The ease with which developers can perform complex opera-
tions, coupled with the capability to write low-level network functions, represents a large step
forward from Visual Basic’s previous abilities in this arena.
In this chapter, we examined:
• Socket programming using the System.Net.Sockets namespace
• Sending and receiving TCP/IP network traffic using the TCPListener and TCPClient
classes
• Using HTTP-specific, as well as protocol-agnostic, classes to issue requests to Web
servers and react to their responses
• How to employ a typical .NET design pattern with the networking classes to allow appli-
cations to issue and receive data in an asynchronous fashion
• Creating variables that are specific and local to individual threads
Drawing Functions CHAPTER
9
IN THIS CHAPTER
• Key Classes Related to Drawing 344
• Transformations 375
Nothing in Windows gets to the user’s screen without the aid of a drawing function. This
includes images, colors, and even text. The OS must render all things visually by drawing pix-
els to an output device (monitor, printer, and so on). Of course, Windows does a good job of
hiding drawing functions from the average developer. When did you last need to call an API
function to display text to the screen or change the background color of a button? Our controls,
compiler, and operating system serve to limit our need to make direct calls into the drawing
library. However, there is always the case where your application requirements are beyond the
scope of what can be done with controls and so on. Perhaps you must create custom pie charts
for your users on-the-fly. Or maybe you need to allow your users to view a group of fonts or
send output to the printer. Chances are that you will eventually need to write your own custom
visual display code. This is where the .NET drawing library comes into play. It provides you
with a host of classes that make adding drawing capabilities to your application easy and fun.
This chapter illustrates common programming tasks using the namespaces related to drawing
in the .NET Framework Class Library. The chapter starts by illustrating the key classes used to
execute drawing functions with the namespace. Then follows a detailed discussion of these key
classes and related code examples. Lastly, we will create a simple drawing application that
serves to demonstrate how these classes can be used in the context of a larger application and
serves as an experimental ground.
After reading this chapter, you should be able to do the following:
• Understand how Windows manages coordinates
• Draw basic shapes including lines, curves, rectangles, and polygons
• Fill shapes and lines with various colors, patterns, and gradients
• Work with groups of shapes
• Work with bitmaps and icons in your application
• Rotate, stretch, and skew graphics
FUNCTIONS
DRAWING
Pen The Pen class is used to draw the outlines of objects (lines, rectan-
gles, ellipses, and so on). It defines the line weight and color simi-
lar to an actual pen.
Rectangle The Rectangle structure stores information about a rectangle
(location, width, and height). This structure is used to draw rectan-
gles, ellipses, pies, and so on.
CustomLineCap The CustomLineCap class is used to create a custom, user-defined
end cap for a line.
Working with the .NET Namespaces
346
PART II
GDI+
All drawing with .NET-managed code happens through the Graphics Device Interface plus
(GDI+) layer. GDI+ is the new API Windows uses to provide the .NET Framework with graph-
ics, imaging, printing, and typography capabilities. Prior to .NET, VB programmers mostly had
to rely on Win32 API calls into GDI to execute drawing functions. In .NET, GDI+ is wrapped
by the drawing namespace. This provides easy, object-oriented access to drawing functions
from all .NET languages.
GDI+ shields your application from having to deal with the details and particulars of device
drivers. It allows you to send output to the screen or printer without concern for calling into
the driver that manages a given device. For example, your application need not write new code
to support an Epson printer versus an HP; think if you had to write new code for every graph-
ics card your application had to support. Instead, GDI+ makes the calls to the specific device
driver for us, thus insulating our application from the hardware and allowing us to easily create
device-independent software.
Practical Applications
GDI+ provides objects like pens and brushes—objects used by programmers to illustrate ideas
and to create tools for their users to do the same. If you are creating applications with illustrat-
9
ing capabilities, you see the obvious need for drawing functions. For instance, if your applica-
tion allows users to select a color, you’ll most likely use the Color structure or the
FUNCTIONS
DRAWING
ColorPalette class.
Beyond illustration applications, however, you might be surprised by how often drawing func-
tions are required. For instance, word processing applications use lines and curves to render
borders for tables, pages, and around text. A search word game might use the DrawLine func-
tion to cross out words as users find them. Spreadsheet applications and the like could use the
DrawPie method to create pie charts based on user data. CAD (computer-aided design) applica-
tions outline objects and calculate distance between points with lines and curves. Even Web
applications might create graphics on the server based on user-submitted data. These images
could be stored to the file system and displayed out to the user’s browser. You can see that,
before long, you will more than likely need to execute drawing functions with the .NET
Framework Class Library. So, let’s get started learning to draw using the .NET namespaces.
Working with the .NET Namespaces
348
PART II
Drawing Basics
Most computer-based drawing is done on a two-dimensional plane using a basic set of objects.
These objects are like building blocks. In the hands of a competent craftsman, they can be
manipulated to create interesting effects and complex shapes. But before we can build the sky-
scraper, we must first set the basic foundation.
origin
x-axis
(0, 0)
FIGURE 9.1
Windows default coordinate system.
Drawing Functions
349
CHAPTER 9
Pens
The companion to the Graphics class is the Pen class. In fact, in order to draw nearly anything,
you’ll need at least a Graphics and a Pen instance. The Pen class is used to define how the out-
lines of shapes are rendered to the surface. Similar to a real pen, the Pen class defines a width
color that will be used to do the drawing. Additionally, you can create a Pen based on a Brush
instance. This allows you to draw with more stylized lines. Table 9.2 demonstrates the various
constructors that are available to you when creating new Pen instances.
Constructor Description
New Pen(Color, Single) Creates a Pen object based on a color (Color structure) and a
width (Single) in pixels.
New Pen(Color) Creates a Pen object based on a color defined by the Color
structure. Sets the pen’s width to the default of 1 pixel. 9
New Pen(Brush, Single) Creates a Pen object using a valid class derived from the
FUNCTIONS
Brush base class. The pen’s width (Single) is defined in
DRAWING
pixels.
New Pen(Brush) Creates a Pen object based on a valid Brush object. Sets the
pen’s width to the default of 1.0 pixels.
The Color parameter, used in the Pen constructor, is defined by an instance of the Color struc-
ture. The Color structure represents an ARGB (Alpha, Red, Green, and Blue) color. Most col-
ors come predefined as properties of the Color structure for easy use. For example, Color.Red
indicates the ARGB equivalent of red. There are a wide variety of predefined colors, every-
thing from LawnGreen to Tomato to Transparent. Additionally, you can call methods of the
Color structure to create custom colors or return the brightness, saturation, and hue of a given
color.
Working with the .NET Namespaces
350
PART II
Lines
So far, we’ve talked about pens and drawing methods but have yet to render anything to the
screen. Now we’ll use a Pen object to draw a line onto a form. This may not seem exciting, but
it provides a foundation.
A line is a set of pixels linked by a start and end point. Line attributes are defined by the Pen
object with which they are drawn. Of course, as pens can vary in width and color, so to can
lines. To draw a line, we use the DrawLine method of the Graphics class. This method is over-
loaded; it defines a number of ways you can pass it parameters. For instance, you can pass it a
Pen object and two Point structures between which GDI+ will draw the line. A Point struc-
ture stores the x and y coordinates of a point on a 2D plane.
The following code uses the DrawLine method and a Pen instance to draw a blue line onto a
form. You can test this code, create a new form-based application, add a button to it, and add
the code in the listing to the button’s click event.
‘local scope
Dim myGraphics As Graphics
Dim myPen As Pen
Note that before we could draw anything to the screen, we needed to return a valid drawing
surface. To do so, we created an instance of the Graphics class. This provides us an object on
which to draw. The constructor accepts a Windows handle as its parameter. We pass it the
active Windows handle. This sets up the Graphics object to use the active form as its target for
drawing our line.
Next, a Pen instance is created. We pass its constructor a valid color and width. Finally, the
DrawLine method of the Graphics object is called to render the line onto the form. The version
of the DrawLine method we used requires a Pen instance and a set of start and end coordinates.
These coordinates are simply passed in order as two points defined as (x1, y1) and (x2, y2).
The method connects the two coordinate points with a blue line based on our Pen object.
dashed lines and to attach start and end line caps. Line caps can be as simple as an arrowhead
or as complex as a custom-defined cap. Table 9.3 lists properties of the Pen class that are spe-
cific to dashes and caps.
Property Description
CustomStartCap The CustomStartCap property is used to set or get a custom-
defined line cap. The CustomStartCap property defines the cap at
a line’s start. The property is of type CustomLineCap.
CustomEndCap The CustomEndCap property is used to set or get a custom-defined
line cap. The CustomEndCap property defines the cap at a line’s
end. The property is of type CustomLineCap.
DashCap The DashCap property is used to set or get the style used for the
start or end caps of dashed lines.
DashOffset The DashOffset property is used to set or get the distance
between the start of a line and the start of the dash pattern.
DashPattern The DashPattern property sets or gets an array of integers that
indicates the distances between dashes in dash-patterned lines.
DashStyle The DashStyle property sets or gets the style used for dashing a
line. The property is of the type DashStyle enumeration.
DashStyle enumeration members include the following: Dash,
DashDot, DashDotDot, Dot, Solid.
EndCap The EndCap property sets or gets the LineCap object used to define
the end of the line. EndCap is of the type LineCap. The LineCap
enumeration includes the following members: AnchorMask,
ArrowAnchor, Custom, DiamondAnchor, Flat, NoAnchor, Round, 9
RoundAnchor, Square, SquareAnchor, and Triangle.
FUNCTIONS
StartCap The StartCap property sets or gets the LineCap object used to
DRAWING
define the start of the line. StartCap is of the type LineCap. The
LineCap enumeration includes the following members:
AnchorMask, ArrowAnchor, Custom, DiamondAnchor, Flat,
NoAnchor, Round, RoundAnchor, Square, SquareAnchor, and
Triangle.
The following code demonstrates setting the styles and cap properties of a Pen object. The
code first creates a Pen object of the color blue. It then sets the EndCap property to an arrow
using the LineCap enumeration. Last, it indicates the line’s DashStyle to be a dash followed
by a dot (DashDot).
Working with the .NET Namespaces
352
PART II
Joins
Suppose we have multiple lines that are joined to indicate a shape or routing direction through a
diagram. The point at which two lines are joined can be rendered with three distinct styles. The
Pen class defines how lines are joined. To do so, it provides the LineJoin property. This prop-
erty is of the type LineJoin enumeration whose members include those listed in Table 9.4.
To join lines, you must add each line to a Path object (discussed later in the chapter). The path
is drawn to the surface using one Pen instance. Intersecting lines are then joined based on the
LineJoin property of the given Pen instance. The following snippet illustrates this with code.
‘local scope
Dim myPath As New System.Drawing.Drawing2D.GraphicsPath()
Dim myGraphics As Graphics
Dim myPen As New Pen(color:=Color.Blue, Width:=8)
Curves
A curve is an array of points defining the perimeter of a conic section. Curves can be used for
such things as connecting points on a graph or drawing a handlebar mustache.
There are two types of curves in the .NET library: cardinal splines and Bèzier splines. There
are also a number of methods of the Graphics class that can be used to draw curves. Table 9.5
lists these methods. For our discussion, we will focus on the DrawCurve and DrawBezierCurve
methods.
Method Description
DrawCurve The DrawCurve method connects an array of points using a curved
line.
DrawClosedCurve The DrawClosedCurve method draws a closed curve using an array of
points. A closed curve ensures that the shape is closed. For instance,
if you drew a curve between three points, the method would close the
curve by connecting the third point with the first.
DrawBezier The DrawBezier method is used to draw a Bèzier curve.
DrawArc The DrawArc method draws an arc from a specified ellipse.
9
Cardinal Splines
A cardinal spline is an array of points through which a line smoothly passes. The curve or
FUNCTIONS
DRAWING
bend of the line is defined by a tension parameter. The curve’s tension indicates how tightly the
curve bends, the lower the tension on a given curve, the flatter (straighter) the line. A curve
with a tension of zero (0), for instance, is equivalent to drawing a straight line between points.
To create a cardinal spline, you use the DrawCurve method. This method allows us to control
the curve’s tension and define the number of points in a given curve. The method is over-
loaded, and as such, provides a number of ways to display a curve. In the following example,
we create a blue curve that passes through three points. Notice that we did not specify the ten-
sion. When left blank, the method uses the default tension of 0.5.
Working with the .NET Namespaces
354
PART II
Figure 9.2 helps illustrate the concept of curve tension. The innermost line is drawn with a ten-
sion setting of zero. Each successive line increases the tension by .5 until we reach 2.0.
FIGURE 9.2
Curve tension.
Bèzier Splines
Bèzier splines can be used to create a wide variety of shapes. Fonts, for instance, often use
Bèzier splines for outlining characters. Four points define a Bèzier spline: a start and end point
and two control points. The curve is drawn between the start and end point. The control points
influence how the curve flows between the points. As the curve moves from point to point, it is
“pulled” toward the nearest control point.
Consider the following code:
Drawing Functions
355
CHAPTER 9
In the preceding example, we created a Bézier curve using the DrawBezier method. First, we
defined a set of four points. The first point (100, 75) is the starting point. The next two points,
(125, 50) and (150, 75) act as the control points. The curve ends at the point (175, 50).
FUNCTIONS
DRAWING
➲ Use DrawBeziers method to create a series of Bèzier curves.
Rectangles
The Rectangle structure is the backbone of the shapes presented in this section. Classes like
Ellipse and Pie use it to bind their shape. The structure stores the size and location of a rec-
tangular region.
We have two constructors available to create an instance of the Rectangle structure. One cre-
ates the rectangle based on the upper-left x and y coordinate, the width of the rectangle, and its
height. To the other constructor, you pass both location as an instance of the Point structure,
and Size as an instance of the Size structure.
Listing 9.1 illustrates both rectangle constructors. The code is simply fired by a button’s click
event. The rectangles are output to the active form.
End Sub
Drawing Functions
357
CHAPTER 9
The DrawRectangle method of the Graphics object allows us to draw the outline of a rectan-
gle to the drawing surface. The method is overloaded with three different sets of parameters.
The first set allows you to create a rectangle based on a Pen and Rectangle instance. The other
two sets create a rectangle directly from a Pen instance and the rectangle’s x and y coordinates
(width and height). The difference between these two is the data types used to define the coor-
dinates and size. One uses the Int32 data type, and the other uses a Single.
Ellipses
An ellipse is simply a circle or oval bound inside a Rectangle structure. To draw an ellipse,
you can use the DrawEllipse method of the Graphics class. This method requires a Pen object
and some semblance of a rectangle definition (structure instance, coordinates, and so on). The
following code illustrates drawing an ellipse inside of a defined Rectangle instance.
‘dimension variables of local scope
Dim myGraphics As Graphics
Polygons
A polygon is a closed plane, or object, represented by at least three lines (segments). To draw
polygons with the namespace, we simply play connect-the-dots. We first create the dots using
the Point structure. These dots are actually a series of coordinates through which we will draw
lines. Figure 9.3 shows a set of six points using the basic Windows coordinate system. 9
To connect the dots, we use the DrawPolygon method of the Graphics class. We indicate the
FUNCTIONS
DRAWING
order in which to connect the points of the polygons by their order in our Point array. You can
see from code Listing 9.2 that the points can be connected in a variety of ways to produce var-
ied results.
Working with the .NET Namespaces
358
PART II
x-axis
5 15 25 35 45 55 65
5
(25, 15)
15
(35, 20)
(15, 20)
25
35
y-axis
(15, 40)
(35, 40)
45
(25, 45)
55
65
FIGURE 9.3
Polygon coordinates.
‘draw the polygon (connect the dots between the points in the array)
myGraphics.DrawPolygon(pen:=New Pen(Color.Blue, Width:=2), _
points:=myPoints)
‘draw the polygon (connect the dots between the points in the array)
myGraphics.DrawPolygon(pen:=New Pen(Color.Blue, Width:=2), _
points:=myPoints)
End Sub
Notice that the last member of the array in Listing 9.2 always points back to the starting point.
This closes the polygon. If you omit this last element in the array, the method assumes it and
will close the polygon for you.
Pies
A pie is simply a wedge of a circle, similar to a section in a pie chart or a piece of pie. GDI+
defines a pie section by an Ellipse object (which is contained by a Rectangle), and the two
radial lines that intersect with the endpoints of the arc defined by the Ellipse. Figure 9.4 illus-
trates this point.
x-axis
5 15 25 35 45 55 65 75 85 95 9
5
FUNCTIONS
95 85 75 65 55 45 35 25 15
DRAWING
x, y
height
y-axis
width
startAngle
sweepAngle
FIGURE 9.4
DrawPie method.
Working with the .NET Namespaces
360
PART II
From Figure 9.4, you can see that the x and y coordinates actually define the upper-left corner
(or starting point) of the bounding rectangle. The pie piece is drawn from the center of this
rectangle. The width and height of the rectangle define the size of the ellipse, which in turn
defines the size of our wedge.
To create a pie wedge we use, you guessed it, the Graphics class. The DrawPie method has
two key parameters (in addition to the Rectangle that defines the Ellipse and the Pen object
that is used to draw the pie), startAngle and sweepAngle. The startAngle parameter is an
angle that is defined clockwise from the x-axis to the first side of the pie. The parameter,
sweepAngle, is defined clockwise from the first side of the pie to the second side of the pie
section. This is easier to grasp by looking at both the code in Listing 9.3.
End Sub
Drawing Functions
361
CHAPTER 9
From Listing 9.3, you can see that when the first section of the pie is created, we set the start
angle to 0 and the sweep angle to 90° counterclockwise (negative). The next piece starts where
the last one left off and again creates a section of equal value (90°). Note how the last piece
was created with the FillPie method with an offset to add extra highlighting to the piece.
Filling Shapes
So far, we’ve dealt with the outline of a shape. Now we will focus on the interior, or fill area,
of a shape. GDI+ gives us the concept of a brush to indicate how a shape is filled. Brushes are
useful when blending colors for a desired effect like a fade or indicating a shape’s texture like
sand, stone, or brick. Brushes can be a single solid color, a blend of colors, a texture, a bitmap,
or a hatched pattern.
To create brush objects in our code, we use a derivative of the Brush class. Brush is an abstract
base class. Classes that derive from Brush are as follows: SolidBrush, TextureBrush,
RectangleGradientBrush, LinearGradientBrush, and HatchBrush. This section
discusses the various Brush derivatives.
SolidBrush
The SolidBrush class could not be more basic. It works just as it sounds; it provides a single- 9
colored brush with which to fill shapes. It has one constructor and one property, Color. Of
course, this property is of the type Color structure. The following is an example of how to cre-
FUNCTIONS
DRAWING
ate a SolidBrush object:
Dim myBrush as New SolidBrush(color:=Color.Red)
TextureBrush
To create custom fill effects, you use the TextureBrush class. Custom fills are useful when
you want to apply your own design to the interior of a shape. For example, suppose you’ve
created a bar graph and you want to fill each bar with the logo of a different company. The
TextureBrush class would allow you to use a bitmap of each company’s logo to fill each rec-
tangle or bar in the graph. Using the TextureBrush class and bitmap images, you can create
endless fill patterns.
Working with the .NET Namespaces
362
PART II
The following code creates a TextureBrush instance based on a simple bitmap made up of
three 45° lines. The bitmap is defined inside of an ImageList control (imageList1). When we
create the object instance, we set the WrapMode parameter to the TileFlipXY enumeration
member. This reverses the image both vertically and horizontally before it is applied to the
graphic surface.
‘dimension a local variable of type TextureBrush Class
Dim myBrush As TextureBrush
Table 9.6 demonstrates how you can use the WrapMode property to tile an image inside the
TextureBrush object to create a desired effect.
Effect Description
This is the original 16 × 16 bitmap that is used to create the various effects.
A 32 × 32 filled area using the WrapMode.Tile property. The 16 × 16
bitmap is repeated four times in a tiled fashion.
A 32 × 32 filled area using the WrapMode.TileFlipX property. The 16 × 16
bitmap is reversed horizontally and then repeated four times (tiled).
A 32 × 32 filled area using the WrapMode.TileFlipY property. The 16 × 16
bitmap is reversed vertically and then repeated four times (tiled).
A 32 × 32 filled area using the WrapMode.TileFlipXY property. The 16 ×
16 bitmap is reversed vertically and horizontally before being tiled.
LinearGradientBrush
In Windows 9x and above, you’ve undoubtedly seen how you can blend two colors across
the title bar of a window from within the “Display Settings” control panel. Well, the
LinearGradientBrush class allows us to do just that; we can blend two colors across a
given shape.
To do so, we first create an instance of the class based on two colors and a blend style. Blend
styles are defined by the LinearGradientMode enumeration. We then use a fill method of the
Graphics object to paint our shape with the blended style. Listing 9.4 illustrates this by creat-
ing a Rectangle object and then using the blended LinearGradientBrush to fill its interior by
calling FillRectangle.
Drawing Functions
363
CHAPTER 9
‘local scope
Dim myGraphics As Graphics
Dim myBrush As System.Drawing.Drawing2D.LinearGradientBrush
Dim myRectangle As Rectangle
End Sub
9
Notice that when we created the brush, we set the LinearGradientMode parameter to indicate
FUNCTIONS
DRAWING
a blend from the top of the shape to its bottom (Vertical). You can get the four effects defined
in Table 9.7 by using this enumeration.
HatchBrush
Remember the first paint programs? Remember showing your friends a wall built out of red
brick that you drew with a rectangle and filled with the brick pattern? Well, the HatchBrush
class allows us to create numerous predefined fill patterns, including brick.
We create a HatchBrush by passing in a hatch style, using the HatchStyle enumeration, and a
foreground and background color to be used by the hatch style. Listing 9.5 fills an ellipse
using a checkerboard HatchBrush instance.
‘local scope
Dim myGraphics As Graphics
Dim myBrush As System.Drawing.Drawing2D.HatchBrush
Dim myRectangle As Rectangle
End Sub
Table 9.8 provides a visual representation of the various patterns possible using the
HatchBrush class. Hopefully, this will serve as a handy reference when you need to pick
the perfect pattern. A description of each member is not necessary; the associated graphic
tells the whole story. Remember, the table uses black and white, but you can use any color
for the foreColor and backColor parameters for nearly unlimited effects.
Horizontal Vertical
LightHorizontal LightVertical
DarkHorizontal DarkVertical
DashedHorizontal DashedVertical
NarrowHorizontal NarrowVertical
DiagonalBrick Cross 9
FUNCTIONS
HorizontalBrick DiagonalCross
DRAWING
SmallGrid SolidDiamond
LargeGrid DottedDiamond
DottedGrid OutlinedDiamond
SmallCheckerBoard SmallConfetti
LargeCheckerBoard LargeConfetti
Working with the .NET Namespaces
366
PART II
Percent05 Percent10
Percent20 Percent25
Percent30 Percent40
Percent50 Percent60
Percent70 Percent75
Percent80 Percent90
Plaid Sphere
Trellis Shingle
Wave Weave
BackwardDiagonal ForwardDiagonal
DarkDownwardDiagonal LightDownwardDiagonal
DarkUpwardDiagonal LightUpwardDiagonal
DashedDownwardDiagonal DashedUpwardDiagonal
WideDownwardDiagonal WideUpwardDiagonal
Divot
Drawing Functions
367
CHAPTER 9
Collections of Shapes
It is often useful to collect various “building block” shapes into a single unit. Rather than man-
aging each rectangle in a bar graph, for instance, it is often easier to group these objects into a
single, manageable unit. If the objects need to be moved or redrawn, you can simply make one
method call. Similarly, if you are transforming the objects, maybe rotating them all 45°, it is
much easier to transform a group than transform each item independently. The
System.Drawing.Drawing2D namespace provides us the Path class for grouping shapes.
Additionally, once you’ve defined your various object groups, it is often necessary to indicate
how those groups interact with one another. If you’ve ever used a drawing application, you are
undoubtedly familiar with the concepts of bring-to-front and send-to-back. These are features
that allow an artist to indicate how shapes (or groups of shapes) relate to one another in layers.
The System.Drawing namespace gives us the Region class for indicating object interaction
and layers.
Paths
Paths enable more advanced drawing techniques in .NET. A path is made of one or more geo-
metric shapes (rectangle, line, curve, and so on). By grouping shapes together in a path, we are
able to manage and manipulate the group as one object. We add shapes to a path for storage in
what is called the world coordinate space. This coordinate system is essentially virtual. It is
the place where the shapes logically exist in memory relative to one another. The graphic can
then be manipulated as a whole. It can be drawn to the screen over and over. In addition, it can
be transformed (rotated, sheared, reflected, scaled) when moving from this logical world space
to the physical device space (form). For example, you might have a 10 × 20 rectangle stored
inside a path. When you place it on the form, you can rotate it 20° and sheer the rectangle. The
key is that the rectangle still exists as a 10 × 20 rectangle (not rotated, not sheared) in the 9
world space.
FUNCTIONS
DRAWING
To create a path, we use the GraphicsPath class. This class provides methods like AddLine,
AddRectangle, AddArc, and so on; each adds their shape to the path. Paths can contain multi-
ple figures or groups of shapes that represent one object. When adding a shape to a path, it is
best to indicate to which figure the shape belongs. We do this by calling the StartFigure
method. Each subsequent call to an add function adds the shape to the figure. If we call
StartFigure again, a new figure is started and all following shapes are added to the new fig-
ure. We call the CloseFigure method prior to starting a new figure if we wish the figure to be
closed off, or connected from start point to end point.
Listing 9.6 creates a GraphicsPath instance. We add a few shapes to the GraphicsPath class
and then display the path to the form.
Working with the .NET Namespaces
368
PART II
End Sub
Drawing Functions
369
CHAPTER 9
If you use paths a lot, you will want to check out the Flatten method of the GraphicsPath
class. This method allows you to change how items are stored within the object instance. By
default, state is maintained for each item added to the path. This means that if a curve and an
ellipse, for instance, are stored in a GraphicsPath, then data for the curve’s points and control
points as well as data that defines the ellipse is stored in the object. By flattening the path, you
allow the object to manage the shape as a series of line segments, thus reducing overhead. In a
completely flattened path, all points are stored as points to be connected by line segments.
FUNCTIONS
You still draw with the Graphics class, but the Region class allows you to set parameters for
DRAWING
drawing. For example, you set the region parameter of the SetClip method of the Graphics
object to your instance of Region. This tells the graphics object that your Region further
defines the graphics area on the given drawing surface.
Listing 9.7 presents a clipping example. We first draw a rectangle and add it to a Path object.
We then define a Region instance based on the Path object. After that, we call the SetClip
method of our Graphics container and pass in the Region object. Finally, we draw a number of
strings to the graphic surface; notice how our defined region clips them.
Working with the .NET Namespaces
370
PART II
‘local scope
Dim myGraphics As Graphics
Dim myPen As New Pen(color:=Color.Blue, Width:=2)
Dim myPath As New System.Drawing.Drawing2D.GraphicsPath()
Dim myPoints(2) As Point
Dim myRegion As Region
Dim i As Short
‘define a triangle
myPath.StartFigure()
myPoints(0) = New Point(x:=100, y:=20)
myPoints(1) = New Point(x:=50, y:=100)
myPoints(2) = New Point(x:=150, y:=100)
Next
End Sub
Drawing Functions
371
CHAPTER 9
Did you notice that when we called SetClip we also set something called the combineMode?
This indicates how the two regions or shapes should be combined. In our case, we had a trian-
gular Region object and a few strings that we drew. We set the combineMode enumeration to
Replace to indicate that the string information should replace the region inside the triangle. Of
course, there are a number of additional members to this enumeration. Table 9.9 lists them.
Also, note that each enumeration member has a corresponding method on the Region class
(Region.Replace for example).
FUNCTIONS
Union
DRAWING
should be joined. The result is an area that is
defined by all points in both regions.
Xor The Xor member is the opposite of the
Intersect member. It contains all points that
are not common to either region.
The following code was used to create the example graphics in the previous table. Note that we
created two regions and combined them using the Region.[Method] syntax.
Working with the .NET Namespaces
372
PART II
‘local scope
Dim myGraphics As Graphics
Dim myPath As New System.Drawing.Drawing2D.GraphicsPath()
Dim myPath2 As New System.Drawing.Drawing2D.GraphicsPath()
Dim myRegion As Region
Dim myRegion2 As Region
Images
The namespace library gives us three classes for working with images: Image, Bitmap and
Icon. Image is simply the base class from which the others inherit. Bitmap allows us to convert
a graphics file into the native GDI+ format (bitmap). This class can be used to define images
as fill patterns, transform images for display, define the look of a button—its uses are many.
Although the bitmap format is used to manipulate images at the pixel level, GDI+ can actually
work with the following image types:
• Bitmaps (BMP)
• Graphics Interchange Format (GIF)
• Joint Photographic Experts Group (JPEG)
• Exchangeable Image File (EXIF)
• Portable Network Graphics (PNG)
• Tag Image File Format (TIFF)
Creating an instance of Bitmap requires a filename, stream, or another valid Image instance.
For example, the following line of code will instantiate a Bitmap object based on a JPEG file:
Dim myBitmap As New System.Drawing.Bitmap(fileName:=”Sample.jpg”)
Once instantiated, we can do a number of things with the image. For instance, we can change
its resolution with the SetResolution method or make part of the image transparent with
MakeTransparent. Of course, we will also want to draw our image to the form. We use the
DrawImage method of the Graphics class to output the image to the screen. The DrawImage
method has over 30 overloaded parameter sets. In its simplest form, we pass the method an
instance of Bitmap and the upper-left coordinate of where we want the method to begin draw-
ing. For example: 9
myGraphics.DrawImage(image:=myBitmap, point:=New Point(x:=5, y:=5))
FUNCTIONS
DRAWING
Scaling and Cropping
It is often helpful to be able to scale or crop an image to a different size. Suppose you need a
100 × 100 image to fit in a 20 × 20 space, or you want to give your users the ability to zoom in
on a portion of an image. You use a variation of the DrawImage method to scale images. This
overloaded method takes a Rectangle instance as the destination for drawing your image.
However, if the rectangle is smaller or larger than your image, the method will automatically
scale the image to match the bounds of the rectangle.
Another version of the DrawImage method takes both a source rectangle and a destination rec-
tangle. The source rectangle defines the portion of the original image to be drawn into the des-
tination rectangle. This, effectively, is cropping. The source rectangle defines how the image
Working with the .NET Namespaces
374
PART II
gets cropped when applied to the destination. Of course, you can crop to the original size or
scale the cropped portion to a new size. Listing 9.8 provides a detailed code example of both
scaling and cropping an image.
‘local scope
Dim myBitmap As System.Drawing.Bitmap
Dim myGraphics As Graphics
Dim mySource As Rectangle
Dim myDestination As Rectangle
End Sub
Notice that we actually drew the image to the form three times. The first time, we drew the
image into a rectangle (mySource) based on its original size. The second time, we scaled the
image to two times its original size (myDestination) by creating a larger rectangle and out-
putting the image accordingly. Finally, we cropped a portion of the original output and put it in
a new, larger rectangle. Figure 9.5 shows the code’s output to the form.
Drawing Functions
375
CHAPTER 9
FIGURE 9.5
Scale and crop output.
Icons
An icon in Windows is a small bitmap image that represents an object. You cannot go far in
without seeing and working with icons. For example, the File Explorer uses icons to represent
folders and files; your desktop contains icons for My Computer, Recycle Bin, and My Network
Places.
We use the Icon class to work with icons in .NET. We can instantiate an Icon instance in
much the same way we created Bitmap objects. The following code creates an icon based on a
file name:
Dim myIcon as New Icon(fileName:=”myIcon.ico”)
The DrawIcon method of the Graphics class is used to draw the icon to the form. To it, you
can pass the icon and either just the upper-left x and y coordinates or a bounding rectangle. If
you pass a Rectangle instance, the icon will be scaled based on the bounding rectangle. The
following line of code draws an icon object into a bounding rectangle.
myGraphics.DrawIcon(icon:=myIcon, _
rectangle:=New Rectangle(x:=5, y:=5, width:=32, height:=32))
The Graphics class also gives us the DrawIconUnstretched method that allows us to specify a 9
bounding rectangle without actually scaling the icon. In fact, if the icon is larger than the
bounding rectangle, it will be cropped to fit from the left corner down and to the right.
FUNCTIONS
DRAWING
Suggestions for Further Exploration
➲ To animate images, take a look at the ImageAnimator class.
➲ Check out the SmoothingMode property of the Graphics class and the SmoothingMode
enumeration members. This method allows you to set things like antialiasing to make
your graphics look “smoother.”
Transformations
In the previous section, we saw how we could enlarge an image based on the dimensions of a
rectangle. This was a kind of a transformation. Transformations not only allow us to scale
Working with the .NET Namespaces
376
PART II
images, but also rotate, flip, and skew them. In drawing applications, you oftentimes need to
enlarge an image to fill an area. Or you may have to rotate an arrow in a flow chart to point at
a given process. This is all done through transformation.
Origins
As we stated earlier, by default GDI+ sets the upper-left corner of our drawing surface as the
origin. But suppose that we want items stored in the world coordinate space to be output to a
different section of the screen. For instance, suppose you have three line segments stored in a
GraphicsPath object and want the DrawPath to interpret the origin of the form as 50, 0 instead
of 0, 0. To do so, you would use the TranslateTransform method of the Graphics class. In its
simplest form you pass the amount of pixels to transform in the x direction and the y direction.
In our example, the code would look as follows:
MyGraphics.TranslateTransform(dx:=50, dy:=0)
This is an example of a global transformation. That is, it applies to the Graphics instance as a
whole. Now all items drawn with this instance will have an origin of 50, 0. To reset the origin
back to the default state, you can simply call the ResetTransform method. There is also a
local transformation. Local transformations apply to a single object or collected set of shapes.
For example, we could have achieved the same results by calling the Transform method
of the GraphicsPath class. Like the Tranform property of the Graphics class, the
GraphicsPath.Transform method requires a matrix object to set its value.
Unit of Measurement
Tired of working with pixels? Are you more comfortable with inches or centimeters? You can
reset the graphics unit used by the Graphics class by setting the PageUnit property. This
property takes a valid member of the GraphicsUnit enumeration. Members include Inch,
Millimeter, Pixel, Point, and so on. After setting the PageUnit property, subsequent calls to
the Graphics object will interpret your values as the new unit of measurement. For instance,
the following code sets the unit to inches and creates a rectangle one inch wide and a half
inch high:
myGraphics.PageUnit = GraphicsUnit.Inch
myGraphics.DrawRectangle(pen:=New Pen(Color.Red, Width:=0.1F), _
x:=0, y:=0, Width:=1, Height:=0.5F)
Why do we care? Well, through matrix math, we can transform objects. For instance, suppose
you have a point at (2, 3) and you wish to rotate that point 90°. Well, you would multiply the
matrix [2 3] by the matrix [0 1 | -1 0]. The result would be the rotated point at (-2, 3). The
math is: (2*0)+(2*-1) = x and (3*1) + (3*0) = y.
The namespace library provides the Matrix class for working with these transformations. It
exposes the methods Multiply, Rotate, Scale, and Shear. Each of these applies its intended
effect on the Matrix instance. The Graphics class also has similar methods like
RotateTransform, ScaleTransform, and MultiplyTransform. Table 9.10 demonstrates
transformations and their effects.
Transformation Code
myGraphics.ScaleTransform(sx:=2, sy:=0.5F)
Effect
Description This example uses the ScaleTransform method to stretch the image out to
twice its width but compress it to half of its height.
Transformation Code 9
Dim myMatrix As New System.Drawing.Drawing2D.Matrix( _
FUNCTIONS
DRAWING
m11:=1.5F, m12:=0, m21:=1.5F, m22:=0.75F, dx:=0, dy:=0)
myGraphics.MultiplyTransform(matrix:=myMatrix)
Effect
Description This example creates a Matrix instance and multiplies the output by the
supplied matrix. The result is an image that is stretched along the x axis
(m11:=1.5) and condensed along the y axis (m22:=.75) and skewed along the
y axis (m21:=1.5).
Working with the .NET Namespaces
378
PART II
Description This example uses the RotateFlip method of the Bitmap class to flip the
image on its x axis. Notice that we use the FlipType enumeration. There are
a number of additional members to this enumeration to produce more flip-
ping and rotating results.
Transformation Code
Dim myMatrix As New System.Drawing.Drawing2D.Matrix( _
m11:=1, m12:=0, m21:=0, m22:=1, dx:=0, dy:=0)
myMatrix.Shear(shearX:=-0.75F, shearY:=0)
myGraphics.MultiplyTransform(matrix:=myMatrix)
Effect
Description In this example we create a basic Matrix object that has no effect on the
object. We then call the Shear method to indicate that the x-axis should be
sheared by -.75. The result is a “faster .NET.”
9
FIGURE 9.6
FUNCTIONS
DRAWING
System.Drawing MDI form.
Code Walkthrough
The code for the MDI form (see Listing 9.9) is rather basic. There is code to control the menu
events, to load the form, and to reset the form when users click on the “New” menu item.
The code starts with form-level declarations and basic form building code.
Working with the .NET Namespaces
380
PART II
Inherits System.Windows.Forms.Form
End Sub
End Sub
The form load event, formMDI_Load, is where we initialize the application and set a global
reference to both the child form (m_myChild) and a Graphics object that references it
(m_myGraphics). This allows us to maintain a reference to the drawing surface at all times.
9
Private Sub frmMDI_Load(ByVal sender As System.Object, _
FUNCTIONS
DRAWING
ByVal e As System.EventArgs) Handles MyBase.Load
End Sub
The resetChildForm procedure allows us to create new forms based on the default values for
the application.
Private Sub resetChildForm()
End Sub
The following sub routines are the click events for the various menu items:
• menuItemNew_Click creates a new drawing surface
• menuItemExit_Click exits the application
• menuSuface_Click loads the surface dialog
• menuItemDraw_Click loads the drawing form
Private Sub menuItemNew_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles menuItemNew.Click
‘purpose: user clicks the NEW item on the menu bar to create a new
‘ drawing surface
‘call the method used to reset the child form to its defaults
Call resetChildForm()
End Sub
‘purpose: close the app. when the user clicks the EXIT menu item
End Sub
‘purpose: show the surface dialog when the user clicks the
‘ SURFACE menu item
‘local scope
Dim mySurface As frmSuface
‘set the start position of the modal dialog to the center position
‘ of its parent
mySurface.StartPosition = FormStartPosition.CenterParent
9
‘show the form as modal
mySurface.ShowDialog(Me)
FUNCTIONS
DRAWING
End Sub
‘local scope
Dim myDraw As frmDraw
Working with the .NET Namespaces
384
PART II
End Sub
#End Region
End Class
Even though the parent form is of the type MDI, our code restricts the number of child win-
dows to just one. We do not allow users to create more than one child form. This simplifies the
example. Microsoft Paint has a similar design pattern. With very little additional effort, you
can modify the code to manage multiple drawing surfaces.
The child form itself contains no additional code. Listing 9.10 shows the default code gener-
ated by Visual Studio .NET. The only items of interest are the form’s property settings. The
form’s BorderStyle is set to None and the ShowInTaskBar property is set to False. This pro-
vides users with the illusion of working on a document, when in reality, all documents in
Windows are simply versions of forms.
End Sub
Drawing Functions
385
CHAPTER 9
‘the following are key properties of the child form that were
‘ changed to make the form look more like a painting surface
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.BackColor = System.Drawing.Color.White
Me.BorderStyle = System.Windows.Forms.FormBorderStyle.None
Me.MaximizeBox = False
Me.MinimizeBox = False
Me.ShowInTaskbar = False
Me.Text = “formChild”
End Sub
#End Region
9
End Class
FUNCTIONS
DRAWING
Surface Form
The surface form demonstrates the Color structure. It allows users to manage properties of the
drawing surface. Users can change the background color of the child form and its height and
width. Figure 9.7 is a screen shot of the surface dialog.
Code Walkthrough
Listing 9.11 is long for such a simple form, but much of the code is simply default property
overrides for the form and its controls. The key procedures in this listing are the form load
event, the Defaults button event, and the OK button event.
Working with the .NET Namespaces
386
PART II
FIGURE 9.7
System.Drawing drawing surface form.
End Sub
FUNCTIONS
Me.groupBox2.TabIndex = 0
DRAWING
Me.groupBox2.TabStop = False
Me.groupBox2.Text = “Background Color”
Me.comboBoxColors.DropDownStyle = _
System.Windows.Forms.ComboBoxStyle.DropDownList
Me.comboBoxColors.DropDownWidth = 121
Me.comboBoxColors.Location = New System.Drawing.Point(12, 28)
Me.comboBoxColors.MaxDropDownItems = 10
Me.comboBoxColors.Size = New System.Drawing.Size(156, 21)
Me.comboBoxColors.Sorted = True
Me.comboBoxColors.TabIndex = 1
Me.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel
Me.buttonCancel.Location = New System.Drawing.Point(196, 44)
Me.buttonCancel.TabIndex = 5
Working with the .NET Namespaces
388
PART II
End Sub
The form load event (formSurface_Load) uses the Reflection namespace to load a drop-
down box with the names of the properties of the Color structure. The load event then initial-
izes the remaining fields on the form to match the current state of the child form.
NOTE
The System.Reflection namespace is a very powerful set of classes. While they are
beyond the scope of this book, you are encouraged to browse the MSDN reference to
see what can be accomplished with this namespace.
FUNCTIONS
DRAWING
Dim myColor As System.Drawing.Color
Dim myProps() As System.Reflection.PropertyInfo
Dim myType As System.Type
Dim count As Integer
End If
Next
End Sub
The Cancel button click event closes the form without applying any updates.
Private Sub buttonCancel_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonCancel.Click
‘purpose: respond the the cancel button’s click event and close the
‘ form without the applying the changes
End Sub
The Defaults button event simply loads the form fields with the application’s default values.
Private Sub buttonDefaults_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonDefaults.Click
End Sub
When users click the OK button, the properties of the child form are set to these new values.
The key piece here is that after changing properties of the child form, we have to get a new
reference to it for the Graphics object to function properly. If we do not rereference the form,
the graphics object is unaware of the changes to the drawing surface, which results in shapes
getting cut off at the window’s old size and other undesirable behavior.
Private Sub buttonOk_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonOk.Click
Else
FUNCTIONS
DRAWING
m_myChild.Refresh()
End If
End Sub
#End Region
End Class
Working with the .NET Namespaces
392
PART II
Draw Form
The draw form allows users to draw shapes onto the child form. Obviously, this is not the
ideal way to create graphics; mouse or pen-based input is much easier. Nevertheless, for the
clarity of this example and in the interest of simplicity, we’ll define shapes by text boxes and
drop-downs.
The features of the draw form allow users to create basic shapes (line, rectangle, and ellipse).
They can set the color and width of the shape outline. Shapes can be filled with a solid color, a
blend, or a pattern. The form also allows users to rotate the shape prior to drawing it to the
surface. The Apply and Clear buttons were added so that you could create multiple shapes onto
the child form without leaving the draw dialog. Figure 9.8 is a screen capture of the draw form.
FIGURE 9.8
System.Drawing draw form.
Code Walkthrough
Listing 9.12 represents the code behind the draw form. Again, much of the code is form and
control property settings.
End Sub
FUNCTIONS
DRAWING
Private WithEvents groupBox5 As System.Windows.Forms.GroupBox
Private WithEvents label6 As System.Windows.Forms.Label
Private WithEvents label7 As System.Windows.Forms.Label
Private WithEvents textBoxHeight As System.Windows.Forms.TextBox
Private WithEvents textBoxWidth As System.Windows.Forms.TextBox
Private WithEvents textBoxX As System.Windows.Forms.TextBox
Private WithEvents textBoxY As System.Windows.Forms.TextBox
Private WithEvents label8 As System.Windows.Forms.Label
Private WithEvents buttonCancel As System.Windows.Forms.Button
Private WithEvents buttonOk As System.Windows.Forms.Button
Private WithEvents label9 As System.Windows.Forms.Label
Private WithEvents label10 As System.Windows.Forms.Label
Private WithEvents comboBoxBlendTo As System.Windows.Forms.ComboBox
Private WithEvents comboBoxBlendFrom As System.Windows.Forms.ComboBox
Working with the .NET Namespaces
394
PART II
FUNCTIONS
CType(Me.numericUpDownWeight, _
DRAWING
System.ComponentModel.ISupportInitialize).BeginInit()
Me.buttonApply.Location = New System.Drawing.Point(336, 432)
Me.buttonApply.TabIndex = 5
Me.buttonApply.Text = “Apply”
Me.radioButtonNone.Checked = True
Me.radioButtonNone.Location = New System.Drawing.Point(12, 24)
Me.radioButtonNone.TabIndex = 0
Me.radioButtonNone.TabStop = True
Me.radioButtonNone.Text = “No Fill”
Me.numericUpDownRotate.Location = New System.Drawing.Point(56, 24)
Me.numericUpDownRotate.Maximum = New Decimal(New Integer() _
{360, 0, 0, 0})
Working with the .NET Namespaces
396
PART II
FUNCTIONS
Me.comboBoxOutlineColor.DropDownStyle = _
DRAWING
System.Windows.Forms.ComboBoxStyle.DropDownList
Me.comboBoxOutlineColor.DropDownWidth = 121
Me.comboBoxOutlineColor.Location = New System.Drawing.Point(12, 80)
Me.comboBoxOutlineColor.Size = New System.Drawing.Size(148, 21)
Me.comboBoxOutlineColor.TabIndex = 2
Me.buttonOk.Location = New System.Drawing.Point(252, 432)
Me.buttonOk.TabIndex = 4
Me.buttonOk.Text = “Ok”
Me.label8.Location = New System.Drawing.Point(28, 112)
Me.label8.Size = New System.Drawing.Size(48, 23)
Me.label8.TabIndex = 5
Me.label8.Text = “From”
Me.label9.Location = New System.Drawing.Point(12, 32)
Working with the .NET Namespaces
398
PART II
FUNCTIONS
Me.groupBox3.Controls.AddRange(New System.Windows.Forms.Control() _
DRAWING
{Me.numericUpDownWeight, Me.comboBoxOutlineColor, Me.label10, _
Me.label9})
Me.groupBox3.Location = New System.Drawing.Point(328, 12)
Me.groupBox3.Size = New System.Drawing.Size(168, 124)
Me.groupBox3.TabIndex = 1
Me.groupBox3.TabStop = False
Me.groupBox3.Text = “Outline”
Me.groupBox4.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.label15, Me.numericUpDownRotate, Me.label14})
Me.groupBox4.Location = New System.Drawing.Point(12, 332)
Me.groupBox4.Size = New System.Drawing.Size(484, 88)
Me.groupBox4.TabIndex = 3
Me.groupBox4.TabStop = False
Working with the .NET Namespaces
400
PART II
End Sub
The Cancel button click event simply closes the form without applying the current form val-
ues. Of course, if you’ve already pressed the Apply button, then the Cancel button just closes
the form.
Private Sub buttonCancel_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonCancel.Click
End Sub
The formDraw_Load procedure initializes the controls on the form. In it, we fill the various
combo boxes directly from enumeration and structure members using the Reflection
namespace.
Note that at the end of the form load event, we set the AcceptButton and CancelButton prop-
erties of the form to the OK and Cancel buttons, respectively. The AcceptButton property indi-
cates what button on the form should respond to the Enter key being pressed (default button).
The CancelButton property indicates the button that is fired when users click the Escape key.
This is new in .NET. In past versions of VB, in order to implement a button that responds to
the Enter or Cancel keys you would set properties of the button; now, you set properties of
the form.
Private Sub frmDraw_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
‘local scope
Dim myColor As System.Drawing.Color
Dim myProps() As System.Reflection.PropertyInfo
Dim myType As System.Type
Dim count As Integer
Dim colorName As String
Dim myHatchStyle As Drawing.Drawing2D.HatchStyle
Dim myArray() As String
Dim myLinGradMode As Drawing2D.LinearGradientMode 9
‘return the type of the color structure
FUNCTIONS
DRAWING
myType = myColor.GetType()
End If
Next
Next
Next
‘set the cancel button on the form to respond to the escape key
Me.CancelButton = buttonCancel()
End Sub
dialog
The Apply button’s click event simply validates the form by calling validateForm. If no vali-
dation rules were broken, it calls the submitForm method to apply the shape to the child form.
Private Sub buttonApply_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonApply.Click
‘purpose: allow users to draw items to the form without closing the
‘dialog before drawing can happen the form fields must be validated
If Not validateForm() Then
FUNCTIONS
DRAWING
Beep()
Else
End If
End Sub
The validateForm function checks for valid entries in our text boxes and returns either True to
indicate all fields are valid or False to indicate one or more are invalid.
Working with the .NET Namespaces
404
PART II
Return False
Else
Return True
End If
End Function
The submitForm method simply calls the draw method contained in our module. Of course, it
passes all the form values as parameters.
Private Sub submitForm()
‘local scope
Dim strFillType As String
fillType:=strFillType, _
outlineWeight:=CSng(numericUpDownWeight().Value), _
outlineColor:=comboBoxOutlineColor().SelectedItem.ToString, _
solidColor:=comboBoxSolidColor().SelectedItem.ToString, _
blendFrom:=comboBoxBlendFrom().SelectedItem.ToString, _
blendTo:=comboBoxBlendTo().SelectedItem.ToString, _
pattern:=comboBoxPattern().SelectedItem.ToString, _
patternForeColor:=comboBoxPattFore().SelectedItem.ToString, _
patternBackColor:=comboBoxPattBack().SelectedItem.ToString, _
blendStyle:=comboBoxBlendStyle().SelectedItem.ToString, _
rotateAngle:=numericUpDownRotate().Value)
End Sub
The OK button’s click event checks the field entries for validity using validateForm. It then
calls submitForm to apply the shape to the child form. Finally, it closes the dialog.
Private Sub buttonOk_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles buttonOk.Click
Else
FUNCTIONS
DRAWING
‘close the form
Me.Close()
End If
End Sub
End Sub
#End Region
End Class
Drawing Module
The drawing module contains the application’s global variables and the actual draw procedure.
Code Walkthrough
We first set the application’s default values including the drawing surface settings. We then
declare the global objects that reference the child form and the associated graphics objects.
The drawing module is presented in Listing 9.13.
Module modDrawing
The draw method takes all of the necessary values from the draw form as parameters. It uses
the Graphics class to render shapes onto the surface. You can see that most of the code is just
simple logic to determine the type of shape to draw, the shape’s outline, and its fill type. One
interesting method call is myState = m_myGraphics.Save(), where myState is declared as
Drawing Functions
407
CHAPTER 9
FUNCTIONS
DRAWING
‘purpose: draw a shape to the drawing surface (frmChild)
‘local scope
Dim myPen As Pen
Dim myRectangle As Rectangle
Dim myBrush As Brush
Dim myHatchStyle As Drawing2D.HatchStyle
Dim myType As System.Type
Dim myBlendStyle As Drawing2D.LinearGradientMode
Dim myState As Drawing2D.GraphicsState
Else
‘draw a rectangle
m_myGraphics.DrawRectangle(pen:=myPen, _
rect:=myRectangle)
End If
Case “PATTERN”
Case “BLEND”
FUNCTIONS
DRAWING
color1:=color.FromName(name:=blendFrom), _
color2:=color.FromName(name:=blendTo), _
LinearGradientMode:=myBlendStyle.Parse( _
enumType:=myType, value:=blendStyle))
End Select
Else
End If
End If
End Select
End Sub
End Module
Summary
In this chapter, we walked through the key classes related to drawing using managed code. The
following is a summary of some of the key points related to executing common programming
tasks with the .NET Framework Class Library:
• GDI+ is the underlying technology used by the managed code to execute drawing tasks.
• By default, a graphic’s surface has its origin in the upper-left corner.
• The Graphics class is used to execute nearly all drawing tasks. You use its methods like
DrawLine, DrawEllipse, and FillRectangle to render shapes and colors to the drawing
surface.
• Classes derived from Brush can be used to fill the interior of shapes. Brush classes
include SolidBrush, TextureBrush, and HatchBrush.
• The GraphicsPath class is used to group shapes. This allows you to manipulate various
shapes as a whole.
• The Region class allows you to define areas of your drawing surface for clipping and hit
testing.
• The Bitmap class encapsulates an image. You can use the DrawImage method of the
Graphics class to render it to the surface.
• The Matrix class provides a number of ways to transform images and shapes.
• The following methods of the Graphics class can be used to transform shapes:
RotateTransform, ScaleTransform, MultiplyTransform.
Reading and Writing XML CHAPTER
10
IN THIS CHAPTER
• Key Classes Related to XML 412
• Learning by Example:
The Hotel Reservations Desk 467
Working with the .NET Namespaces
412
PART II
If you think of the .NET Framework as a multilayered technology, then the eXtensible Markup
Language (XML) is the glue that binds those layers together. The .NET Framework provides
unprecedented functionality for the VB programmer to natively interact with and use XML
inside applications. As a storage mechanism, XML is the common denominator for storing
data and persisting objects. As a communications mechanism, XML forms the underpinnings
for how the .NET classes talk to one another. It is the default way that objects in .NET express
and exchange data. For these reasons alone, an understanding of XML is necessary to be pro-
ductive in the .NET runtime. But the real story is not how the Framework uses XML, but how
the Framework enables you, the developer, to speak the universal XML language.
This chapter introduces the XML class libraries, System.XML and System.Xml.Schema, that
.NET uses as an API for parsing, validating, and manipulating XML. First, we’ll establish
some baseline definitions and summaries for XML concepts. Then we will talk about the
XML-related industry standards that the .NET Framework supports. Finally, we get to the meat
of the chapter: an in-depth look at the classes that constitute the System.Xml and
System.Xml.Schema namespaces.
Markup Languages
XML is a language that is used to describe data. This stands in contrast to a language such as
the Hypertext Markup Language (HTML), which is a language used to display data. To fully
appreciate what XML is and what it does, it is useful to have a baseline understanding of
markup languages in general.
10
What Is a Markup Language?
WRITING XML
READING AND
Markup languages exist to add meaning or formatting to documents. Rich Text Format (RTF)
is one example of a markup language. It consists of a defined set of tags or tokens that lend
instruction on how to display a piece of a document. Figure 10.1 shows a formatted sentence
typed into WordPad.
Working with the .NET Namespaces
414
PART II
FIGURE 10.1
An RTF document in WordPad.
When the document is saved with the RTF format, the following code results:
{\rtf1\ansi\ansicpg1252\deff0\deflang1033
{\fonttbl{\f0\fswiss\fcharset0 Arial;}}
\viewkind4\uc1\pard\f0\fs20\par
Markup languages add \i meaning \i0 or \b formatting \b0 to documents.
\par}
In this example, you can see that the \i tag indicates that the piece of the document between
the \i and \i0 tags should be italicized. The \b and \b0 tags indicate that the text between
them should be displayed in bold text. The RTF format and others like it do their job well, but
they are ill suited to the Web. For one thing, they don’t generate the most readable of docu-
ments; their syntax can be confusing and awkward to the human eye. For another, there is little
to the document that actually looks like it has structure. For programmers, who rely on struc-
ture, this is anathema.
FIGURE 10.2
Formatted HTML inside Internet Explorer.
This is what the HTML code needed to generate the previous document looks like:
<html>
<head>
<title>Markup Languages</title>
</head>
<body>
<p>
<font face=”Arial”>Markup languages add <i>meaning</i> or
<b>formatting</b> to documents.</font>
</p>
</body>
</html>
HTML and other markup languages follow a set of rules, which usually are explained inside a
specification. For XML (and HTML), this specification is maintained by the World Wide Web
Consortium (W3C). The language specification defines the actual syntax rules of the docu-
ment. The HTML specification, for instance, defines a rule that says that each HTML docu-
ment must begin with an <html> tag and end with an </html> tag. Typically, these language
specifications define general syntax rules, the order in which the various tags can appear,
whether different tags are dependent upon other tags, and so on.
10
In the next section, we look at the structure of XML documents specifically.
WRITING XML
READING AND
Working with the .NET Namespaces
416
PART II
Nodes
An XML document consists of various discrete chunks of information called nodes. Nodes are
the lowest level of informational unit contained in an XML document. When you read an
Reading and Writing XML
417
CHAPTER 10
XML document using some of the XML reader classes in System.XML, they will read those
documents one node at a time. If you started listing the nodes of the XML document in Listing
10.1, this is what the list would look like:
• Node 1: <?xml version=”1.0” encoding=”utf-8” ?>
• Node 2: <guests>
• Node 3: <guest id=”jlk0910211”>
• Node 4: <firstname>
• Node 5: Jim
• Node 6: </firstname>
…and so on. Nodes are useful for low-level parsing, but they don’t carry enough context with
them to be ultimately useful for understanding the data in an XML document. Consider the
fifth node containing the text Jim. What does this node mean? We can guess, based on the
nodes before (<firstname>) and after (</firstname>), that this is the first name of a hotel
guest, but, taken by itself, the node doesn’t offer up a whole lot of meaning. When people look
at or create an XML document, they typically think in terms of elements, not nodes.
Elements
An element is used to describe and contain data. As such, it is the real power behind an XML
document’s structure. Elements are named and can encapsulate data through values or attrib-
utes. The syntax for an element is as follows:
<elementname attrib1=”value1” attrib2=”value2” ...>Data</elementname>
Attributes and data are all optional with elements, but the starting tags and ending tags are
required. For instance, this is also a valid element:
<MyElement></MyElement>
If the element in question does not have any data associated with it, the ending tag can be
short-circuited by closing out the starting tag with a / character, like this:
<MyElement someAttrib=”Yes”/>
or like this:
<MyElement/>
10
Elements are very useful because, unlike nodes, they provide you with enough contextual
WRITING XML
READING AND
information to evaluate the data that they contain. The following is an element from the hotel
register XML document:
<firstname>Jim</firstname>
Working with the .NET Namespaces
418
PART II
Because elements are said to include the starting tag, the ending tag, and everything in
between, the following is also an element from our hotel example:
<guests>
<guest id=”jlk0910211”>
<firstname>Jim</firstname>
<middlename>L</middlename>
<lastname>Kelley</lastname>
<roomnbr>295</roomnbr>
<checkindate>6/17/2001</checkindate>
<numnights>4</numnights>
<preferred>No</preferred>
</guest>
</guests>
The <guests> element, in this case, happens to be the outermost element in the XML docu-
ment, and it is referred to as the document element or the root element. You can see that nested
elements set up a parent-child relationship between different pieces of data. The <guest> ele-
ment is a child of the <guests> element and has several child elements of its own, such as
<firstname>, <roomnbr>, and <preferred>. Because of their capability to contain other ele-
ments, elements form the basis for data relationships inside an XML document.
Now let’s take a further step back and examine the different sections to an XML document.
Each XML document has two major sections, the prolog and the document elements.
The Prolog
The prolog section of an XML document is used to specify document-wide settings or attrib-
utes. Typically, this includes the version of XML that the document adheres to, the character
set that it was encoded with, and any external resource references, such as style sheets or
schemas (more on these later in the chapter). The tags and structure of this section are con-
trolled by the actual XML specification. This stands in sharp contrast to the document ele-
ments section, whose structure and tag content are entirely up to the XML document author.
The prolog consists of the XML declaration, processing instructions, and comments.
The XML declaration must be the very first line in the document. The XML declaration con-
sists of three parts: the XML version number, the encoding type, and a “standalone” declara-
tion. The XML version number is required. This references which version of the XML
Reading and Writing XML
419
CHAPTER 10
specification was used to construct the document. The encoding type is optional; if specified, it
identifies which character set was used to encode the document.
NOTE
Utf-8 encoding is the most common type of encoding supported by XML parsers and
writers. If the XML document is encoded with Utf-8 or Utf-16, the parser should be
capable of figuring this out automatically, without it being specified in the XML dec-
laration. If the encoding is not Utf-8 or Utf-16, definitely be sure to include it in the
encoding type. Other encoding types that you might run into include ISO-8859-1, Big-
5, and Shift-JIS. For a good description of XML encoding, see the article “Character
Encodings in XML and Perl,” by Michel Rodriguez, currently available at http://www.
xml.com/pub/a/2000/04/26/encodings/index.htm.
The standalone declaration is also optional. If we had used one in the hotel register XML file,
it would appear like this:
<?xml version=”1.0” encoding=”utf-8” standalone=”no”?>
The standalone value can be set to either Yes or No. A value of Yes indicates that the document
will not reference any external files. That is, no external files will be needed to correctly parse
or understand the document’s content. If an external resource is indicated (such as a style
sheet) and the value is set to Yes, the parser will throw an error. A standalone value of No indi-
cates that external resources may be used to parse and understand the document. No is the
default value if no standalone declaration is made.
Processing Instructions
Processing instructions are a very loosely defined set of statements, typically used for refer-
encing style sheets from within an XML document. Processing instructions really are meant to
be instructions that are passed directly through to applications; it is up to the application how
to handle the instruction. While these processing instructions typically are contained in the
document prolog, they also may appear at the end of the document or even inside the docu-
ment. Here is an example of a processing instruction:
<?xml-stylesheet type=”text/xsl” href=”register_display.xsl”?>
tify the root element of the XML document, and it allows a way for you to relate the XML
document to a Document Type Definition file (DTD). (DTDs are covered in much greater detail
in the section titled “XML Schemas.”) DOCTYPE declarations are not required; when used, the
Working with the .NET Namespaces
420
PART II
root element parameter is the only one required (DTD references are optional). The following
is an example of a DOCTYPE declaration:
<!DOCTYPE guests SYSTEM “URIToDTD”>
The DTD reference can be of type SYSTEM (as shown previously) or of type PUBLIC. Again, we
will cover this topic in more detail during our actual discussion of DTDs later in this chapter
(again, in the “XML Schemas” section).
Comments
Comments also are allowed in the document prolog. Comments look like this:
<!--This is a test-->
Comments also may appear inside the document proper or at the end of the document,
although they cannot be contained within element tags.
Document Elements
All the nodes following the prolog are document elements. This is the actual meat of the docu-
ment. The tag definitions here can be completely customized to structure data exactly the way
that it needs to be structured for the particular application or process that will consume or
write it. Document elements must follow the syntax for well-formed XML, something we will
talk about in detail in the section, “Validating XML Documents.” Essentially, this means that
the elements must follow certain basic rules: They must have a starting tag and an ending tag,
and parent elements must wholly contain their child elements. That is, this is correct:
<parent>
<child></child>
</parent>
This is not:
<parent>
<child>
</parent>
</child>
XmlNodeReader
XmlReader
XmlTextReader
XmlValidatingReader
XmlTextWriter
FIGURE 10.3
The XML reader and writer classes.
The XMLReader class implements functions that can read in an XML text file or stream and
then navigate through the different attributes contained in that file. It also implements proper-
ties that enable you to programmatically determine the current node that is being processed 10
and the content of that node. The XMLWriter class provides functionality to output XML as a
WRITING XML
READING AND
stream. It enables you to generate well-formed XML and manage the progress and status of
the output.
Working with the .NET Namespaces
422
PART II
You can choose from two major design patterns when parsing an XML document. One
involves processing XML text in a forward-only manner, node by node. The other way
involves building a “tree” of an XML document in memory and then hopping from node to
node (or element to element) on the tree. Both approaches are supported by different classes in
the System.XML namespace, and both have their advantages and disadvantages. We’ll start by
examining the first approach: forward-only parsing of nodes.
Quite a few different overloaded constructors are available with the XmlTextReader class.
Supplying a string (as we previously did with fileName) enables you to load a file either from
a local file path or from a URL, as you see in the following two lines of code:
‘read from a local file-path
reader = New XmlTextReader(“C:\xml documents\transcript.xml”)
You also can provide a stream object, instead of a string, to the constructor to parse in an XML
document sitting inside of a stream object. Likewise, you can specify a TextReader object. For
now, let’s concentrate on parsing XML from a local file.
The XmlTextReader exposes a Read method that is used to actually start the parsing. When you
use the Read method, you actually are moving a virtual “viewport” across the document. This
viewport can look at one node at a time; calling the Read method advances it to the next node,
enabling you to examine that node’s properties. Listing 10.2 shows a simple console applica-
tion that reads in an XML file (hotel_register.xml) and then prints its content to the console
window.
Module dataReader
Reading and Writing XML
423
CHAPTER 10
Try
‘Read in an XML file (location stored in fileName)
reader = New XmlTextReader(fileName)
reader.WhitespaceHandling = WhitespaceHandling.None
Finally
If Not (reader Is Nothing) Then
reader.Close()
End If
End Try
End Sub
READING AND
End Module
Working with the .NET Namespaces
424
PART II
In this example, we are setting up a while loop based on the value returned from the Read
method. The Read method has a few different overloaded definitions. The one that we are
using here takes no parameters and simply tells the Reader object to read in the next node
from the target file. If there are no nodes left in the file, it will return a value of false.
Inside the while loop, we execute a second method XmlTextReader.ReadOuterXML. The
ReadOuterXML method returns all the XML content of the current node and all its children as a
string. Because we have just issued the Read command once at this point, the current node
would be the very outermost node (<hotel>). That means that we would expect this method to
return the entire file, with the exception of the XML version spec. In other words, it should
return all the document elements. In fact, this is just what it does (see Listing 10.3).
Compare the actual XML source, Listing 10.3, to the output in Figure 10.4.
FIGURE 10.4
XML file output to the console.
The XmlTextReader.ReadState property is a handy way to tell exactly what state the reader is
currently in. You can use it to tell whether the reader has reached the end of the file, to see
whether it is currently reading a node, or even whether it has been closed. The ReadState
10
property returns an instance of a ReadState enumeration (see Table 10.2).
WRITING XML
READING AND
Working with the .NET Namespaces
426
PART II
This example gets us started. You have seen how to reference an XML document into our
XmlTextReader through its class constructor. You also have seen how to use the Read method
to advance through the document one node at a time, and how to use the ReadState property
to interpret exactly what the reader is currently doing. Now, let’s see what type of information
we can retrieve about the specific node that we have read in.
Let’s make a few modifications to the code.
Instead of using the ReadOuterXml method, let’s truly parse this document node by node, print-
ing out the pertinent node properties to the console along the way. Because the XmlTextReader
class is constructed entirely around node-by-node access, many of its properties return infor-
mation about the current node that has been read in through the Read method. Table 10.3
shows all the properties of the XmlTextReader class that are specific to the current node.
Name Description
AttributeCount Returns the number of attributes defined on the current node
BaseURI Returns the base URI string for the current node
Depth Returns the depth of the current node in the overall XML docu-
ment structure
Encoding Returns the encoding attribute for the current node
HasAttributes Returns a Boolean value indicating whether the current node has
any attributes
HasValue Returns a Boolean value indicating whether the current node has
a value
IsDefault Returns a Boolean value indicating whether the current node’s
value was derived as the result of using a default value specified
in a DTD or XSD file
Reading and Writing XML
427
CHAPTER 10
By including code inside our while loop (which is based on the Read method), we can exam-
ine the value of each of these properties and then write that value to the console. The new,
revised code appears in Listing 10.4.
Module dataReader
Sub Main()
Const fileName = “hotel_register.xml”
Try
‘Read in an XML file (location stored in fileName)
reader = New XmlTextReader(fileName)
Dim nodeType As XmlNodeType
reader.WhitespaceHandling = WhitespaceHandling.None
‘false
While reader.Read()
End While
MsgBox(“An XML error has occurred (“ & xmlErr.Message & “).” & _
vbCrLf & vbCrLf & “ Xml Source Line Nbr: “ & _
reader.LineNumber & vbCrLf & “ Xml Source Position: “ & _
reader.LinePosition)
MsgBox(“An error occurred (“ & appErr.Message & “). Stack info:” & _
appErr.StackTrace)
End Try
End Module
When you run this code, you will get output similar to that shown in Figure 10.5. In this
screenshot, you can see the start of a <guest> node.
In the code, you will notice that we use an enumeration to determine the element type. The
XmlNodeType enumeration (documented for you in Table 10.4) is often a useful branching indi-
cator when parsing a document. For instance, our parser might not care about comments, XML
declarations, or processing instructions; we could choose to just ignore those types of nodes
Reading and Writing XML
429
CHAPTER 10
and process only nodes of type Element or CDATA. It also comes in handy in understanding
how nodes are pieced together to form elements and other constructs. Peruse the output gener-
ated by the code in Listing 10.4—it will give you a much better understanding of how parsers
view and treat each node.
FIGURE 10.5
Node properties output.
• Processing instructions
• XML declarations
• DOCTYPE declarations
Working with the .NET Namespaces
432
PART II
Because these items are rarely needed by your actual parsing algorithm, ignoring them auto-
matically by using the MoveToContent method is an efficient way to get at core data in the
XML document.
The second way that you can efficiently ignore irrelevant XML markups is through the use of
XmlReader.Skip. The Skip method enables you to jump from element to element instead of
from node to node. Let’s examine a snippet from our hotel_register.xml file:
<guest id=”jlk0910211”>
<firstname>Jim</firstname>
<middlename>L</middlename>
<lastname>Kelley</lastname>
<roomnbr>295</roomnbr>
<checkindate>6/17/2001</checkindate>
<numnights>4</numnights>
<preferred>No</preferred>
</guest>
<guest id=”nlt0000704”>
<firstname>Nadia</firstname>
<middlename>L</middlename>
<lastname>Tatonovich</lastname>
<roomnbr>615</roomnbr>
<checkindate>6/17/2001</checkindate>
<numnights>4</numnights>
<preferred>No</preferred>
</guest>
If we were reading in this file using XmlTextReader.Read, we would visit each node in turn
until the end of the document. If we wanted only a certain guest, however, it would be easier to
skip over the entire <guest> element to get to the next one if the guest doesn’t have the ID we
are looking for. If we were specifically interested only in the guest record with the ID of
nlt0000704 and we had just read in the node <guest id=”jlk0910211”>, we could immedi-
ately skip to the next guest record by calling Skip. Listing 10.5 shows a revision to our previ-
ous code that iterated through each node. By using the Skip method, we jump over entire
elements at a time until we arrive at the specific node that we want, which is then printed to
the console (see Figure 10.6).
Module dataReader
Sub Main()
Const fileName = “hotel_register.xml”
Reading and Writing XML
433
CHAPTER 10
Try
‘Read in an XML file (location stored in fileName)
reader = New XmlTextReader(fileName)
Dim nodeType As XmlNodeType
reader.WhitespaceHandling = WhitespaceHandling.None
Exit While
READING AND
Else
reader.Skip()
Working with the .NET Namespaces
434
PART II
MsgBox(“An XML error has occurred (“ & xmlErr.Message & “).” & _
vbCrLf & vbCrLf & “ Xml Source Line Nbr: “ & _
reader.LineNumber & vbCrLf & “ Xml Source Position: “ & _
reader.LinePosition)
MsgBox(“An error occurred (“ & appErr.Message & “). Stack info:” & _
appErr.StackTrace)
Finally
If Not (reader Is Nothing) Then
reader.Close()
End If
End Try
End Module
Using the XmlTextReader is a great way to quickly run through an XML document because it
is specifically optimized for speed. Its disadvantage lies in its forward-only nature. It doesn’t
facilitate hopping around an XML document, forward and backward. An alternative approach
to parsing XML documents revolves around the XmlDocument class.
Reading and Writing XML
435
CHAPTER 10
FIGURE 10.6
The Skip output.
When the XML file is loaded into the XmlDocument object, you can traverse the nodes by using
instances of the XmlNode class. The XmlNode class represents individual nodes in a document
and has methods available to move from node to node. If we wanted to process the tree from
the top down, we would first set our current node to the document’s root. This is called the
document element, and a reference to it is returned through the XmlDocument.DocumentElement
property (we touched on the concept of a document element earlier in the chapter in our dis-
cussion of elements).
Dim startNode As XmlNode
startNode = document.DocumentElement
Now, we can set up a loop to run through all the children of the document root. If we are care-
ful in the way we approach this, we can even set up a recursive routine to parse through the 10
nodes like this:
WRITING XML
READING AND
In this code, we accept an instance of the XmlNode class and first determine whether it has any
children nodes. We then assign the current node instance to be the first child of itself. Now the
recursive part comes into play: We then pass this new node reference into the RecurseNodes
routine again. Finally, we move on to the next sibling node by using the XmlNode.NextSibling
method call.
NOTE
A node is said to be a sibling to another node if they are both immediate children of
the same parent node in the tree structure of the document. In our hotel_register.xml
file, all the <guest> nodes are siblings to one another.
By using the FirstChild, ParentNode, and NextSibling properties on the XmlNode class, we
can navigate through the document using the parent and child relationships inherent in its
structure. See Listing 10.6 for an example of navigating through nodes. The following revision
of the previous console application displays the parent-child relationships in the XML
document.
Module dataReader
Sub Main()
‘Path and name of the XML file to parse
Const fileName = “..\hotel_register.xml”
Try
‘Read in an XML file (location stored in fileName)
reader = New XmlTextReader(fileName)
Reading and Writing XML
437
CHAPTER 10
MsgBox(“An XML error has occurred (“ & xmlErr.Message & “).” & _
vbCrLf & vbCrLf & “ Xml Source Line Nbr: “ & _
reader.LineNumber & vbCrLf & “ Xml Source Position: “ & _
reader.LinePosition)
MsgBox(“An error occurred (“ & appErr.Message & “). Stack info:” & _
appErr.StackTrace)
Finally
If Not (reader Is Nothing) Then
reader.Close()
End If
Console.ReadLine()
End Try 10
WRITING XML
READING AND
End Sub
Working with the .NET Namespaces
438
PART II
End While
End If
Reading and Writing XML
439
CHAPTER 10
Figure 10.7 shows the output from this applet. Each node in the document is displayed along
with its ancestry (a list of all the parent nodes).
FIGURE 10.7
Parent-child lineage from the XmlDocument class.
Just like the XmlTextReader class, the XmlNode class exposes a NodeType property that can tell
you which type of node you are dealing with (in conjunction with the XmlNodeType enumera-
tion—refer back to Table 10.4).
There is also a way to return nodes by name by using the GetElementsByTagName method. The
XmlDocument.GetElementsByTagName method is handy because it returns an instance of an
XmlNodeList class, which has a list of node entities matching a tag name (which you pass to
the method). The following code returns a node list of all nodes matching the tag name
product. It then processes each of the nodes contained in the XmlNodeList object and writes
out their XML content:
10
WRITING XML
READING AND
If we had a requirement to extract all the <guest> elements from the hotel_register.xml docu-
ment, this would be quite easy to implement using the XmlNodeList returned by this method.
In Listing 10.7, we first load our familiar hotel_register.xml file into an XmlDocument instance,
and then we place a call to GetElementsByTagName, passing in the tag name of guest. The
application then writes out the XML contained by each of the guest elements to the console.
Module dataReader
Sub Main()
Const fileName = “hotel_register.xml”
Dim xmlDoc As New XmlDocument()
Try
‘simple var for our loop into the node list
Dim i As Integer
‘get the node list of all nodes matching the tag name “guest”
Dim nodes As XmlNodeList = xmlDoc.GetElementsByTagName(“guest”)
End Module
FIGURE 10.8
Parsing elements by their tag name.
developer’s job easier). These two categories are referred to as fundamental and extended,
respectively. The .NET Framework Class Library has support for both fundamental structures
and extended structures. Table 10.5 lists the Framework classes considered to be fundamental;
Working with the .NET Namespaces
442
PART II
Table 10.6 lists those considered to be extended. Developers already familiar with program-
ming against the W3C DOM should be very comfortable with using these classes.
There is a fairly even match between the properties and methods supported on both the
XmlTextReader class and the XmlNodeReader class—so why would you use XmlNodeReader
objects? Remember that instances of XmlDocument are in-memory representations of XML
documents. If you have an extremely large XML document, this can prove to be problematic in
terms of resource usage. In those cases, the XmlTextReader is the optimal solution because the
XML document is not represented in memory at all; operations are conducted as straight read
operations from a file or a stream.
tation on the actual XmlReader class to see what it provides you in terms of base func-
tionality; you might want to implement your own class to deal with XML that is not
coming from a source that the .NET classes recognize.
Working with the .NET Namespaces
444
PART II
➲ We have talked about parsing discrete pieces from an XML document, but if you need to
actually query an XML document, you will need to dig into the System.Xml.Xpath
namespace. It supports the XPath standards for querying XML documents and returning
nodes as data.
Now that the stage is set, let’s see how to write a simple XML file.
You can see that, in addition to the file and stream outputs, you have the option to specify a
TextWriter instance. This turns out to be a very powerful feature. The TextWriter class, in
the System.IO namespace, is an abstract class designed to allow for any possible text output.
The Framework already implements a few customized writers for HTTP and HTML; others
can be created from the TextWriter base class. In this way, the options for XML output are
bound only by what can be implemented using the TextWriter class. See Chapter 6, “Font,
Text, and Printing Operations,” for more information.
After you have specified what form the output will take, you can begin to write into that output
channel. As expected, XmlTextWriter exposes a vast spectrum of write methods to handle all
the different node types. In fact, out of the 30 different methods implemented in the class
(whether inherited from XmlWriter or newly implemented), all but three of them are actual
methods that write XML. Most of these write methods are specialized to write specific XML
nodes or elements. In addition to these dedicated tag writers, one method enables you to write
raw text into the XML document (WriteRaw), and one enables you to “copy” a node from an
instance of an XML reader (WriteNode). Table 10.8 presents all the pertinent methods and
their descriptions.
Name Description
WriteAttributes Writes out any attributes located at the current node of an
associated reader object
WriteAttributeString Writes out an attribute and value
WriteBase64 Encodes bytes in Base64 and then writes out the result
WriteBinHex Encodes bytes as binhex and then writes out the result
WriteCData Writes out a CDATA block with the supplied text
WriteCharEntity Writes out a character entity with the supplied text
WriteChars Writes out characters to the output channel
WriteComment Writes out a comment containing the supplied text
WriteDocType Writes out a DOCTYPE declaration with the supplied name
and attributes 10
Writes out an element with the supplied text
WRITING XML
WriteElementString
READING AND
To see this in action, let’s write a simple (but slightly meaningless) program that reads in an
XML file using XmlTextReader and then writes it back out, a node at a time, using
XmlTextWriter (see Listing 10.8).
The first step is to set up a loop that runs through the entire source XML file. For each node
encountered, the program will examine the type of node and its various properties and will
execute the appropriate write method. After having processed the file, it should be easy to tell
whether we have gotten the XmlTextWriter code right: The files should be visibly identical to
one another.
Reading and Writing XML
447
CHAPTER 10
Module dataReaderWriter
Sub Main()
Const FILE_NAME_IN = “..\hotel_register.xml”
Const FILE_NAME_OUT = “..\hotel_register_out.xml”
Try
‘Read in an XML file (location stored in fileName)
reader = New XmlTextReader(FILE_NAME_IN)
‘Ignore whitespaces
reader.WhitespaceHandling = WhitespaceHandling.None
‘Loop through the source file; for each different node type
‘encountered, call the corresponding write method off the writer
‘object. The goal here is a carbon copy of the source file. If a
‘node type is encountered, the user will be notified via the
‘console. Try experimenting on your own XML documents. Where the
‘code encounters a node type that isn’t handled in the Select Case,
‘try adding it in with the appropriate writer method.
10
While reader.Read()
WRITING XML
READING AND
Working with the .NET Namespaces
448
PART II
Case XmlNodeType.Comment
writer.WriteComment(reader.Value)
Case XmlNodeType.Element
Console.WriteLine(“Writing: start of element -> ‘“ _
& reader.Name & “‘“)
writer.WriteStartElement(reader.Name)
If reader.HasAttributes Then
For i = 0 To reader.AttributeCount - 1
reader.MoveToAttribute(i)
writer.WriteAttributeString(reader.Name, _
reader.Value)
Next
End If
Case XmlNodeType.Text
Console.WriteLine(“Writing: element content -> ‘“ _
& reader.Value & “‘“)
writer.WriteString(reader.Value)
Case XmlNodeType.EndElement
Console.WriteLine(“Writing: end of element -> ‘“ & _
reader.Name _
& “‘“)
writer.WriteEndElement()
Case Else
Console.WriteLine(“!!!An un-handled node type was _
encountered:” & reader.NodeType)
End Select
End While
MsgBox(“An XML error has occurred (“ & xmlErr.Message & “).” & _
vbCrLf & vbCrLf & “ Xml Source Line Nbr: “ & _
reader.LineNumber & vbCrLf & “ Xml Source Position: “ & _
reader.LinePosition)
MsgBox(“An error occurred (“ & appErr.Message & “). Stack info:” & _
appErr.StackTrace)
End Try
End Module
Most of this code should be self-explanatory. However, a few pieces deserve a closer look.
In the Select Case statement, if the node is a start element node, we also have to deal with
the possibility that the start element tag will have attributes that we must write out. There are
two ways of doing this. In Listing 10.8, we have used the WriteAttribute method. This writes
out a textual name value pair to the specified document. An alternative, and arguably easier,
way is to use the WriteAttributes method. The WriteAttributes method is designed to
work in conjunction with a reader object, and it behaves in different ways depending on where
the reader is currently positioned. If the reader is positioned at a start element tag, it will write
out any and all attributes and then close the tag. In other words, we would have replaced that
Select Case element with this:
Case XmlNodeType.Element 10
Console.WriteLine(“Writing: start of element -> ‘“ & reader.Name _
WRITING XML
READING AND
& “‘“)
writer.WriteStartElement(reader.Name)
Working with the .NET Namespaces
450
PART II
If reader.HasAttributes Then
writer.WriteAttributes(reader, False)
End If
.
.
.
The second parameter supplied to the reader is a Boolean value that indicates whether to write
any default attributes that might be attached to the XML document.
The second item that can’t be discerned from a simple examination of the code is how the
XmlTextWriter.Close method works. This method closes out the document (and thus the file,
stream, and so on) that the writer was working on. But you should know that it also automati-
cally closes out any open-ended tags by writing out their end element tags for you. That means
that if you left the document in a state like this
<parent>
<child>
and then called Close, the document would actually look like this:
<parent>
<child>
</child>
</parent>
Now that we have a grasp of the mechanics for reading and writing XML documents, we can
move on to examining how XML documents can be validated. Validation implies that a
schema exists that will describe the content of a particular XML document, so that is
where we will start our discussion.
XML Schemas
Because XML documents adhere to a specific schema, or layout, it is often desirable to verify
that a particular XML document remains true to the schema that it is supposed to follow.
Having the capability to define a schema externally to an XML document allows XML as a
technology and a language to continue further than other markup languages such as HTML.
What do we mean when we say that schemas can be defined externally? Well, one of the
things that makes XML so well suited to data descriptions is this: The actual tags for XML are
not defined in the XML specification. This means that XML can behave as a metalanguage.
Metalanguages can be used to define other languages. This neatly side-steps the problem of
being locked into a specific tag set. Regardless of how large a predefined tag set is, it will
never satisfy everyone’s needs regarding structuring data.
Reading and Writing XML
451
CHAPTER 10
The capability for XML documents to define their own multiple tag sets is an important differ-
entiator from the other markup languages that we have discussed. XML documents can define
their tags through yet another document—this second document contains the schema to which
the first document must adhere. A few different standards define what this schema document
does and what its syntax looks like.
The XML classes in the .NET Framework support Document Type Definitions (DTD), XML
Schema Documents (XSDs), and XML-Data Reduced Language schemas (XDRs). This sec-
tion examines each of these in turn and then walks through the Framework support for validat-
ing XML documents against each.
DTD Documents
We’ll start our look at XML schema descriptions with the DTD. Here, we have assembled a
short DTD file:
<!ELEMENT books (book)*>
<!ELEMENT book (title, chapters, summary, price)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT chapters (chapter)*>
<!ELEMENT chapter (#PCDATA)>
<!ELEMENT summary (#PCDATA)>
<!ELEMENT price (#PCDATA)>
This is a DTD that a publishing company might use. It defines the structure for an XML docu-
ment that will describe books. The syntax might look a little strange, but it should be easy to
see what the intent of the different pieces is. Of course, the DTD—which is just a plain text
file—follows rules of its own. The DTD rules are part of the actual W3C XML specification.
An XML document that adheres to the previous DTD (let’s call it books.dtd) would look
something like this:
<!DOCTYPE books SYSTEM “book.dtd”>
<books>
<book>
<title>VB.NET Unleashed</title>
<chapters>
<chapter>Introduction</chapter>
<chapter>The New IDE</chapter>
<chapter>...</chapter>
</chapters> 10
<summary> blah blah blah </summary>
WRITING XML
READING AND
<price>59.99</price>
</book>
</books>
Working with the .NET Namespaces
452
PART II
If you closely examine this XML document, you can start to make a tie back to the concepts of
specifications versus tag usage rules. The XML specification says that we must enclose our
tags in < and > characters (as you can see with <books>); the DTD tells us that the <name>,
<chapters>, <summary>, and <price> tags are contained within the <book> tag.
Because the DTD used in a particular XML document can vary from document to document,
the XML specification provides syntax for indicating which DTD defines the structure of the
XML document. This is the DOCTYPE declaration that we visited earlier in this chapter when we
discussed the anatomy of an XML document—and that we now see as the first line in our
XML document snippet.
</xsd:complexType>
READING AND
</xsd:element>
</xsd:schema>
The XSD specification is a more sophisticated descendant of the XDR specification. The XDR
format started life at Microsoft. It was this format that was later adopted and extensively
extended by the W3C to create the XSD format standard. In principle, the XDR format most
likely will be phased out in favor of the XSD format, but the XML classes are “aware” of both.
As a reference point, Listing 10.10 again shows our book DTD document. In this incarnation,
it follows the XDR format.
NOTE
Although we can’t recommend using XDR as your schema format, you might have
some significant work already invested in that precursor to the XSD format. Microsoft
provides a utility—distributed along with the .NET Framework SDK—called XSD.exe.
It accepts an XDR file as input and writes out an equivalent XSD file:
xsd.exe <schema.xdr> [/outputdir:]
This handy utility also can generate XSD schemas from .NET assemblies and XML
documents.
In an XML file, you can reference an XDR or XSD schema by using the following syntax:
<HeadCount xmlns=’x-schema:books.xdr’>
Reading and Writing XML
455
CHAPTER 10
This directs any schema-aware parser to the source schema document used to structure the
XML document.
Both XSD files and XDR files enable you to define new attribute types in addition to just spec-
ifying the order and relationship between elements of an XML file. Consider this revised XML
file that describes a book. Note that, in the line where we begin the actual book node, we have
added an attribute to the node called ISBN.
<books>
<book ISBN=”xxxxxxxx”>
<title>VB.NET Unleashed</name>
<chapter>Introduction</chapter>
<chapter>The New IDE</chapter>
<chapter>...</chapter>
<abstract> blah blah blah </summary>
<price>59.99</price>
</book>
</books>
We now can define this attribute, which lives inside the book tag, by doing this inside
our XSD:
<AttributeType name=”ISBN” dt:type=”string” required=”yes”/>
We have just created a brand new attribute type; this new attribute type is of the string data
type, and must be included everywhere that it is referenced in association with an element.
Integrating this into our XSD results in this:
<Schema name=”BOOK” xmlns=”urn:schemas-microsoft-com:xml-data”>
<AttributeType name=”ISBN” dt:type=”string” required=”yes”/>
<ElementType name=”name” content=”textOnly”/>
<ElementType name=”chapter” content=”textOnly”/>
<ElementType name=”chapters” content=”eltOnly” model=”closed”>
<element type=”chapter” maxOccurs=”*”/>
</ElementType>
<ElementType name=”summary” content=”textOnly”/>
<ElementType name=”price” content=”textOnly”/>
<ElementType name=”book” content=”eltOnly” model=”closed”/>
<attribute type=”ISBN”/>
<element type=”title”/>
<element type=”chapters”/>
<element type=”summary”/> 10
<element type=”price”/>
WRITING XML
</ElementType>
READING AND
NOTE
The System.Xml.Schema namespace implements .NET’s XML Schema Object Model
(SOM). The SOM is essentially a Document Object Model specifically designed to
implement XML Schemas. The classes in the SOM directly correspond to the specifica-
tions laid out in the W3C’s XML Schema Recommendation (visit http://www.w3.org
for more information).
To explore the capabilities of the schema classes that the .NET Framework Class Library pro-
vides, let’s revisit our hotel registration scenario. An XSD that would describe the hotel_regis-
ter.xml document is presented in Listing 10.11. To refresh your memory on what the hotel
registration XML document looks like, refer back to Listing 10.3.
LISTING 10.11 An XSD Schema for the Hotel Register XML Document
<xsd:schema xmlns:xsd=”http://www.w3.org/2001/XMLSchema” _
attributeFormDefault=”qualified” elementFormDefault=”qualified”>
<xsd:element name=”hotel”>
<xsd:complexType>
<xsd:sequence>
<xsd:element name=”guests” minOccurs=”0” maxOccurs=”unbounded”>
<xsd:complexType>
<xsd:sequence>
<xsd:element name=”guest” minOccurs=”0” maxOccurs=”unbounded”>
<xsd:complexType>
<xsd:sequence>
<xsd:element name=”firstname” type=”xsd:string”
minOccurs=”0” />
<xsd:element name=”middlename” _
type=”xsd:string”
minOccurs=”0” />
<xsd:element name=”lastname” _
type=”xsd:string”
minOccurs=”0” />
<xsd:element name=”roomnbr” type=”xsd:string”
Reading and Writing XML
457
CHAPTER 10
This XSD seems dense and complicated, but it really isn’t. Let’s walk through the pieces. First,
what do we know about the hotel_register.xml document itself?
• It contains a container element for the hotel itself.
• The hotel element contains a guests element, which, in turn, contains all the guest ele-
ments.
• Each guest element contains a firstname, middlename, lastname, roomnbr,
checkindate, numnights, and preferred element.
• There may be many guest elements and many hotel elements (although we have shown
examples with only one hotel instance, to keep things simple).
The XSD file should be a straightforward replay of this information. The first thing that the
XSD does is set up a bunch of header information, such as the name of the XSD, the name- 10
space it belongs to, and so on. Then it identifies the root document element, hotel, like this:
WRITING XML
READING AND
<xsd:element name=”hotel”>
The hotel element is considered a complex type because its subnodes are nontextual and
because it has attributes. Simple types can hold only values and may not have element or
Working with the .NET Namespaces
458
PART II
attribute subnodes. Everything inside this complex type is described by a sequence schema ele-
ment. A sequence element simply defines an order to the child elements. After this, the docu-
ment matches up to our next XML element: the guests element.
<xsd:complexType>
<xsd:sequence>
<xsd:element name=”guests” minOccurs=”0” maxOccurs=”unbounded”>
Just as with the hotel element, the guests element is also a complex type, with a sequence of
subnodes below it. Our next element up for description is the guest element. This element is
contained inside the guests element and thus appears nested inside it. As usual, this element is
described using a complex type.
<xsd:element name=”guest” minOccurs=”0” maxOccurs=”unbounded”>
<xsd:complexType>
<xsd:sequence>
<xsd:element name=”firstname” type=”xsd:string” minOccurs=”0” />
<xsd:element name=”middlename” type=”xsd:string” minOccurs=”0” />
<xsd:element name=”lastname” type=”xsd:string” minOccurs=”0” />
<xsd:element name=”roomnbr” type=”xsd:string” minOccurs=”0” />
<xsd:element name=”checkindate” type=”xsd:string” minOccurs=”0” />
<xsd:element name=”numnights” type=”xsd:string” minOccurs=”0” />
<xsd:element name=”preferred” type=”xsd:string” minOccurs=”0” />
</xsd:sequence>
<xsd:attribute name=”id” form=”unqualified” type=”xsd:string” />
</xsd:complexType>
</xsd:element>
We also can see that the guest ID attribute is defined inside the guest element schema. The
rest of the document consists of just the closing tags for all these elements that we have dis-
cussed. So, you can see that the XSD might be confusing at first glance, but it is very easy to
read and understand when looked at from the vantage point of the XML file that it is supposed
to define.
To add the schema elements into the overall schema document, we use the XmlSchemaElement
class. This class encapsulates XSD elements. To create our hotel “root” element, the code
would look like this:
Reading and Writing XML
459
CHAPTER 10
To create the complex type and sequence groupings, we need to use the
XmlSchemaComplexType class and the XmlSchemaSequence class:
Setting the SchemaType property of the elementHotel object to the previously created
complex type object tells the schema generator that the hotel element is a complex type. The
XmlSchemaComplexType.Particle property then is used to identify whether this complex type
element contains a choice (represented by XmlSchemaChoice), a sequence (represented by
XmlSchemaSequence), or a nonsequenced grouping of child nodes (XmlSchemaAll).
We now have created the opening pieces of the hotel element. To add it to the schema, we sim-
ply write this:
‘Add the element to the schema
mySchema.Items.Add(elementHotel)
This adds the elementHotel object to the root schema document. To add more child nodes to
the hotel element itself, we can just execute the same Add method. Keep in mind, however,
that these child elements are being added to the sequence grouping and not directly to the
elementHotel node:
Listing 10.12 shows a console application that builds the XSD file that we witnessed in
Listing 10.11.
Sub Main()
Try
Working with the .NET Namespaces
460
PART II
mySchema.Write(Console.Out)
Finally
Console.WriteLine()
Console.WriteLine(“Hit ‘Enter’ to exit.”)
Console.ReadLine()
End Try
End Sub
Reading and Writing XML
463
CHAPTER 10
If we were targeting XSD validation specifically, we would set the property like this:
validator.ValidationType = ValidationType.Schema
NOTE
You must set the ValidationType property before the first call to
XmlValidatingReader.Read.
The source XML document should specify its attendant schema document, but you also have
the option of building up a collection of schemas and then validating XML documents against
that collection. The XmlSchemaCollection class can contain both XSD and XDR schemas
for this purpose. An XmlSchemaCollection instance then can be assigned through the
XmlValidatingReader.Schemas property. When the document is parsed by calling the validat-
ing reader’s Read, ReadInnerXml, ReadOuterXml, or Skip methods, the document is compared
and analyzed against the schema defined inside the XmlSchemaCollection instance:
‘create a new schema collection object
Dim schemaCollection as XmlSchemaCollection = new XmlSchemaCollection()
‘now, add the schema collection through the validating reader’s schemas
‘property
reader.ValidationType = ValidationType.Schema
reader.Schemas.Add(schemaCollection)
If you are validating multiple XML documents against the same XML schema, using an XML
schema collection object will help improve application performance because the actual schema
is loaded only once and then is cached for further references.
Now that we have indicated the type of schema validation that we want performed and exactly
what schema we want to validate against, we can start parsing through the XML document
using the validating reader. As the validating reader encounters XML elements that do not con-
form to the specified schema and schema type, it flags a validation issue. It may do so by rais-
ing either exceptions or events.
There are a few things to note about the event handler. For one, its signature needs to match
that of the ValidationEventHandler delegate (this means that we need the sender object and
the ValidationEventArgs object as parameters). The second thing to note is how the actual
validation issue is carried into the event handler: through the ValidationEventArgs instance.
The ValidationEventArgs, also defined in System.Xml.Schema, carries three properties useful
for specifying the exact validation failure that occurred. The Message property returns the
actual description of the failed validation, the Exception property returns an instance of the
XmlSchemaException class associated with the validation failure, and the Severity property 10
returns a value from the XmlSeverityType enumeration (documented in Table 10.10).
WRITING XML
READING AND
Working with the .NET Namespaces
466
PART II
Because the actual validation error event is defined on the XmlValidatingReader class (it is
called ValidationEventHandler), the next step is easy: Use the AddHandler command to tell
the runtime that our HandleValidationError sub should handle any
ValidationEventHandler events:
With these two pieces in place, we can start the read of the XML document. This happens in
an identical way to the read design pattern of the XmlTextReader class.
While validator.Read()
.
.
.
End While
Reading and Writing XML
467
CHAPTER 10
Finally
‘Close the reader.
If Not (validator Is Nothing) Then
validator.Close()
End If
End Try
In general, it is preferable to handle validation issues using events instead of exceptions. Using
the event handler gives you access to more information about the error, allows the validator to
continue or to be restarted, and enables you to differentiate between actual errors and valida-
tion discrepancies.
FIGURE 10.9
The main window.
Code Walkthrough
Listing 10.13 shows a sample XML file that you can use for testing the ReservationsDesk
application.
<guest id=”cnh0002652”>
READING AND
<firstname>Casper</firstname>
<middlename>N</middlename>
<lastname>Houston</lastname>
<roomnbr>109</roomnbr>
Working with the .NET Namespaces
470
PART II
Only two items have been added into the global class scope: resFile (a string pointing to the
currently selected XML document) and xmlDoc (an XmlDocument instance holding the XML
file pointed at by resFile). Listing 10.14 shows the ReservationsDesk application source
code.
Me.TextBoxMiddleName.Name = “TextBoxMiddleName”
Me.TextBoxMiddleName.ReadOnly = True
Me.TextBoxMiddleName.Size = New System.Drawing.Size(168, 20)
Me.TextBoxMiddleName.TabIndex = 3
Working with the .NET Namespaces
474
PART II
‘
READING AND
‘ComboHotel
‘
Me.ComboHotel.DropDownStyle = _
System.Windows.Forms.ComboBoxStyle.DropDownList
Working with the .NET Namespaces
476
PART II
Me.GroupBox1.TabIndex = 0
READING AND
Me.GroupBox1.TabStop = False
‘
‘Main
‘
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Working with the .NET Namespaces
478
PART II
End Sub
#End Region
This menu event handler launched the Open File dialog box to allow users to “load” an XML
document (this needs to be a document that specifically follows the correct schema). After
loading the XML document, it parses the hotel nodes to get a list of unique hotel IDs.
Private Sub MenuItem2_Click(ByVal sender As System.Object, ByVal e As _
System.EventArgs) Handles MenuItem2.Click
OpenFileDialog1.DefaultExt = “.xml”
OpenFileDialog1.Filter = “Reservation files (*.xml)|*.xml”
OpenFileDialog1.InitialDirectory = _
System.Reflection.Assembly.GetExecutingAssembly.Location
OpenFileDialog1.ShowDialog()
resFile = OpenFileDialog1.FileName
If resFile = “” Then
Me.Text = “ReservationsDesk (no file loaded)”
Else
Me.Text = “ReservationsDesk (file: “ & resFile & “)”
LoadResFile(resFile)
ListHotels()
Reading and Writing XML
479
CHAPTER 10
End Sub
This is the routine that performs the actual load of the XML document from a file:
Public Sub LoadResFile(ByVal file As String)
Dim str As String
Dim i As Integer
Dim reader As XmlTextReader
Dim validator As XmlValidatingReader
Try
‘If the file didn’t validate, this handler should catch that...
Catch validationErr As XmlException
MsgBox(“The reservation file ‘“ & resFile & “‘ has failed to _
validate against the corporate data schema standard. The file _
may be corrupt, or may be from an invalid source. Please _
select a new file.”)
‘“ & resFile & “‘. The file may be corrupt, or may be from an _
READING AND