0% found this document useful (0 votes)
28 views1,151 pages

VB - Programmer - Guide To The NET FrameWork Class Library

The document is a comprehensive guide to programming with Visual Basic in the .NET Framework, covering its evolution, object-oriented concepts, and the Framework Class Library. It includes detailed sections on working with various .NET namespaces, real-world programming practices, and advanced topics such as COM+ services and XML handling. The guide serves as a resource for developers looking to enhance their skills in .NET programming with Visual Basic.

Uploaded by

lila scali
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
28 views1,151 pages

VB - Programmer - Guide To The NET FrameWork Class Library

The document is a comprehensive guide to programming with Visual Basic in the .NET Framework, covering its evolution, object-oriented concepts, and the Framework Class Library. It includes detailed sections on working with various .NET namespaces, real-world programming practices, and advanced topics such as COM+ services and XML handling. The guide serves as a resource for developers looking to enhance their skills in .NET programming with Visual Basic.

Uploaded by

lila scali
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 1151

Visual Basic Programmer’s Guide

to the .NET Framework


Class Library
Lars Powers and Mike Snell

800 East 96th Street, Indianapolis, Indiana 46240 USA


Copyright © 2002 by Sams Publishing ASSOCIATE PUBLISHER
Linda Engelman
All rights reserved. No part of this book shall be reproduced, stored in a
retrieval system, or transmitted by any means, electronic, mechanical, photo- ACQUISITIONS EDITOR
copying, recording, or otherwise, without written permission from the pub- Sondra Scott
lisher. No patent liability is assumed with respect to the use of the information
DEVELOPMENT EDITOR
contained herein. Although every precaution has been taken in the preparation
Karen Wachs
of this book, the publisher and author assume no responsibility for errors or
omissions. Neither is any liability assumed for damages resulting from the use MANAGING EDITOR
of the information contained herein. For information, address Sams Publishing, Charlotte Clapp
A division of Pearson Technology Group, 201 W. 103rd St., Indianapolis, IN
PROJECT EDITOR
46290.
Tony Reitz
International Standard Book Number: 0-672-32232-3
COPY EDITOR
Library of Congress Catalog Card Number: 2001089222 Barbara Hacha
First Printing: December 2001
INDEXER
Composed in Frutiger, Times, and MacUSADigital by Pearson Technology Rebecca Salerno
Group
TECHNICAL EDITORS
Printed in the United States of America Jawahar Puvvala
Dan Suceava
Trademarks
All terms mentioned in this book that are known to be trademarks or service TEAM COORDINATOR
marks have been appropriately capitalized. Sams Publishing cannot attest to Lynne Williams
the accuracy of this information. Use of a term in this book should not be
regarded as affecting the validity of any trademark or service mark. INTERIOR DESIGNER
Gary Adair
Warning and Disclaimer COVER DESIGNER
Every effort has been made to make this book as complete and as accurate as Aren Howell
possible, but no warranty or fitness is implied. The information provided is on
an “as is” basis. The authors and the publisher shall have neither liability nor PAGE LAYOUT
responsibility to any person or entity with respect to any loss or damages aris- Julie Swenson
ing from the information contained in this book or programs accompanying it.
Overview
Foreword xx
Preface xxiii
Introduction 1

PART I An Introduction to .NET 5


1 Evolution of .NET 7
2 Evolution of VB .NET 29
3 Object-Oriented Concepts in .NET 49
4 Introduction to the .NET Framework Class Library 63

PART II Working with the .NET Namespaces 81


5 Forms, Menus, and Controls 83
6 Font, Text, and Printing Operations 153
7 Stream and File Operations 213
8 Networking Functions 273
9 Drawing Functions 343
10 Reading and Writing XML 411
11 XSLT and XPath 489
12 Working with Threads 539
13 Messaging 597
14 Browser/Server Communications 691
15 Data Storage and Access 721
16 Directory Services 775

PART III Real-World .NET Programming 803


17 Accessing COM+ Services 805
18 .NET Interop with COM Applications 849
19 Managing Collections of Objects 869
20 Profiling, Debugging, and Exception Handling 895
21 Globalization and Localization Techniques 925
22 Deploying, Configuring, and Licensing .NET Components 949
A Calling the Win32 API from Managed Code 977
B Win32 API-to-Namspace Cross-Reference 989
C .NET Security Models 1079
D .NET Framework Base Data Types 1091
Index 1095
Contents
Introduction 1

PART I An Introduction to .NET 5


1 Evolution of .NET 7
The Composition of .NET ......................................................................8
Microsoft .NET Timeline ..................................................................9
Tools and Languages ........................................................................10
Building Block Services ..................................................................12
.NET Enterprise Server and Office Products ..................................13
Suggestions for Further Exploration ................................................15
.NET’s Relevance ..................................................................................16
Developers ........................................................................................16
System Architects ............................................................................16
Project Managers ..............................................................................17
Companies ........................................................................................17
End Users..........................................................................................18
The .NET Framework: Under the Hood................................................19
Common Language Runtime (CLR) ................................................19
Common Language Specification (CLS) ........................................20
Common Type System (CTS) ..........................................................21
Microsoft Intermediate Language (MSIL) ......................................21
Managed Code..................................................................................22
Assemblies........................................................................................23
Global Assembly Cache (GAC) ......................................................23
Garbage Collector (GC) ..................................................................24
Namespace........................................................................................24
Framework Class Library (FCL) ......................................................25
Interoperability ................................................................................26
Security ............................................................................................26
Suggestions for Further Exploration ................................................27
Summary ................................................................................................27

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

Interactive Development Environment (IDE)........................................39


New Tools ........................................................................................39
Code Editor ......................................................................................43
Summary ................................................................................................47

3 Object-Oriented Concepts in .NET 49


Classes—Wrapping Data and Behavior Together ................................51
Classes as Approximations ..............................................................51
Talking Between Classes..................................................................53
Inheritance—Defining Classes in Terms of One Another ....................54
Inheritance by Natural Relationship ................................................56
Polymorphism—Overriding One Class Method with Another ............60
Overriding ........................................................................................60
Overloading ......................................................................................60
Summary ................................................................................................61

4 Introduction to the .NET Framework Class Library 63


Introducing the Framework Class Library ............................................64
Organization—The Namespace Hierarchy ......................................65
Walking the Tree ..............................................................................65
Enhancing Developer Productivity ........................................................66
Finding the Code That You Need ....................................................67
Using the Class Library....................................................................68
Code Reuse ......................................................................................69
The Elements of a Namespace ..............................................................70
Classes ..............................................................................................70
Structures ..........................................................................................72
Delegates ..........................................................................................73
Interfaces ..........................................................................................73
Enumerations ....................................................................................74
Programming with the Framework Class Library ................................75
Instantiating Objects from the Class Library ..................................75
Inheriting from the Framework Class Library ................................76
Exception Handling ..........................................................................77
Summary ................................................................................................79

PART II Working with the .NET Namespaces 81

5 Forms, Menus, and Controls 83


Key Classes Related to Windows Forms ..............................................84
Creating Forms ......................................................................................86
Introducing the Form Class ..............................................................86
Using the Windows Form Designer ................................................87
Working with the Form Class Constructor ......................................90
Instantiating a Form Object..............................................................91
vii
CONTENTS

The Form Class Hierarchy......................................................................91


The Control Class ............................................................................92
The ScrollableControl Class ........................................................92
The ContainerControl Class ..........................................................92
Visual Characteristics of Forms ............................................................92
Setting Form Modality ....................................................................92
Creating MDI Forms ........................................................................93
Controlling Form Position and State................................................97
Changing Border Style ....................................................................97
Creating Minimize, Maximize, and Restore Buttons ......................98
Controlling Opacity ..........................................................................99
Suggestions for Further Exploration ................................................99
Using the Clipboard ............................................................................100
Pushing Data onto the Clipboard ..................................................100
Reading Data from the Clipboard ..................................................100
Suggestions for Further Exploration ..............................................102
Creating Menus....................................................................................103
Adding a Main Menu to a Form ....................................................103
Creating Context Menus ................................................................107
Managing MDI Applications and Menus ......................................107
Working with Menu Items ..................................................................108
Adding Submenus ..........................................................................108
Adding Checkmarks, Shortcuts, and Separator Bars to Menus ....109
Working with Radio Checks ..........................................................112
Setting Default Menu Items ..........................................................113
Merging Menus ..............................................................................114
Creating OwnerDraw Menu Items ....................................................115
Suggestions for Further Exploration ..............................................116
Handling Menu Events ........................................................................116
Menu Items and Event Handlers ....................................................117
Reacting to the Popup Event ..........................................................118
Detecting Menu Selection ..............................................................124
An Introduction to Controls ................................................................124
Common Control Properties, Methods, and Events ......................125
Handling Form Resizing ................................................................128
Docking Controls............................................................................130
Windows Forms and ActiveX Controls..........................................131
Creating Your Own Controls ..........................................................132
Learning by Example: The EventLog Control ....................................142
Key Concepts Covered ..................................................................142
Code Walkthrough ..........................................................................143
Summary ..............................................................................................151
viii
VISUAL BASIC PROGRAMMER’S GUIDE TO THE .NET FRAMEWORK CLASS LIBRARY

6 Font, Text, and Printing Operations 153


Key Classes Related to Font, Text, and Printing Operations ..............154
Font, Text, and Printing ......................................................................156
Fonts ....................................................................................................156
Font Attributes ................................................................................157
Font Classes....................................................................................158
Font Collections ............................................................................162
Private Fonts ..................................................................................162
Font Classifications ........................................................................163
Hotkey Prefix..................................................................................164
Text Rendering ..............................................................................164
Suggestions for Further Exploration ..............................................165
Learning by Example: FontPad, a Simple Text Editor........................165
Key Concepts Covered ..................................................................165
FontPad Main Dialog Box..............................................................166
FontPad Font Settings Dialog Box ................................................171
FontPad Module ............................................................................178
Printing ................................................................................................179
Sending Output to the Printer ........................................................179
Printer Configuration......................................................................180
Previewing Documents Prior to Printing........................................182
Learning by Example: Adding Printing Capabilities to FontPad ........184
Key Concepts Covered ..................................................................184
Main Form Changes ......................................................................184
Page Setup ......................................................................................185
Print Dialog Box ............................................................................195
FontPad Print Example Module Changes ......................................209
Suggestions for Further Exploration ..............................................209
Printing and Font-Related Controls and Dialog Boxes ......................210
Summary ..............................................................................................211

7 Stream and File Operations 213


Key Classes Related to File I/O ..........................................................214
Directory and File Operations ............................................................216
Access and Attributes ....................................................................216
Creation, Deletion, and Manipulation ............................................223
Monitoring the File System............................................................230
Suggestions for Further Exploration ..............................................236
Reading and Writing to Files and Streams ..........................................237
Reading and Writing Files..............................................................237
Asynchronous Reading and Writing ............................................242
Binary Reading and Writing ........................................................248
Suggestions for Further Exploration ..............................................251
ix
CONTENTS

Learning By Example: Adding Open and Save to FontPad ................252


Key Concepts Covered ..................................................................252
Open Dialog....................................................................................252
Save Dialog ....................................................................................262
Summary ..............................................................................................271

8 Networking Functions 273


Key Classes Related to Network Programming ..................................274
Sockets ................................................................................................278
Creating Sockets ............................................................................279
Sending Data ..................................................................................282
Receiving Data ..............................................................................284
Handling Socket Exceptions ..........................................................286
Suggestions for Further Exploration ..............................................288
A More Simplified Approach to Socket Programming ......................288
Network Streams ............................................................................289
Listening for Connections ..............................................................290
Implementing a Request/Response Model ..........................................292
Creating Requests ..........................................................................292
Retrieving Responses ....................................................................292
Working with HTTP-Specific Requests and Responses ................293
Handling Web Exceptions ..............................................................297
Using the WebClient Class ..................................................................301
Uploads and Downloads ................................................................301
Working with Streams ....................................................................302
Suggestions for Further Exploration ..............................................302
An Asynchronous Request/Response Pattern......................................302
Overview ........................................................................................302
Issuing an Asynchronous Request..................................................303
Authentication and Proxies..................................................................307
Authentication Methods ................................................................307
Encapsulating Credentials ..............................................................308
Optimizing Credentials Through the Cache ..................................310
Dealing with Proxies ......................................................................311
Suggestions for Further Exploration ..............................................311
Learning by Example: A Socket Transmitter Application ..................312
Key Concepts Covered ..................................................................312
Code Walkthrough ..........................................................................314
Learning by Example: ISBNCrawler Application ..............................329
Key Concepts Covered ..................................................................329
Code Walkthrough—A Basic Async Design Pattern ....................330
Code Walkthrough ..........................................................................330
Summary ..............................................................................................342
x
VISUAL BASIC PROGRAMMER’S GUIDE TO THE .NET FRAMEWORK CLASS LIBRARY

9 Drawing Functions 343


Key Classes Related to Drawing ........................................................344
Drawing with the .NET Namespaces ..................................................347
GDI+ ..............................................................................................347
Practical Applications ....................................................................347
Suggestions for Further Exploration ..............................................348
Drawing Basics ....................................................................................348
Understanding Windows Coordinate Systems ..............................348
The Graphics Class ........................................................................349
Pens ................................................................................................349
Lines ..............................................................................................350
Curves ............................................................................................353
Suggestions for Further Exploration ..............................................355
Drawing Basic Shapes ........................................................................355
Rectangles ....................................................................................356
Ellipses............................................................................................357
Polygons ........................................................................................357
Pies..................................................................................................359
Suggestions for Further Exploration ..............................................361
Filling Shapes ......................................................................................361
SolidBrush ....................................................................................361
TextureBrush ..................................................................................361
LinearGradientBrush ....................................................................362
HatchBrush ....................................................................................364
Collections of Shapes ..........................................................................367
Paths................................................................................................367
Regions and Clipping ....................................................................369
Suggestions for Further Exploration ..............................................372
Working with Images ..........................................................................372
Images ............................................................................................373
Icons................................................................................................375
Suggestions for Further Exploration ..............................................375
Transformations ..................................................................................375
Origins ............................................................................................376
Unit of Measurement......................................................................376
Rotating, Scaling, Skewing, and Flipping......................................376
Suggestions for Further Exploration ..............................................378
Learning by Example: A Forms-Based Drawing Application ............378
Key Concepts Covered ..................................................................379
MDI Parent and Child Forms ........................................................379
Surface Form ..................................................................................385
Draw Form......................................................................................392
Drawing Module ............................................................................406
Summary ..............................................................................................410
xi
CONTENTS

10 Reading and Writing XML 411


Key Classes Related to XML ..............................................................412
Markup Languages ..............................................................................413
What Is a Markup Language? ........................................................413
Markups with HTML ....................................................................414
Suggestions for Further Exploration ..............................................416
The Anatomy of an XML Document ..................................................416
Nodes ..............................................................................................416
Elements ........................................................................................417
The Prolog ......................................................................................418
Document Elements........................................................................420
Suggestions for Further Exploration ..............................................420
Parsing XML Documents ....................................................................421
The Reader and Writer Base Classes ............................................421
Forward-Only XML Text Parsing ..................................................422
Parsing Only the Important Stuff ..................................................431
Parsing XML Document Trees ......................................................435
Mapping to the W3C DOM............................................................441
Introducing the XmlNodeReader Class..................................................443
Suggestions for Further Exploration ..............................................443
Writing XML Documents....................................................................444
Reader and Writer Similarities ......................................................444
Creating a Simple XML Document ..............................................444
XML Schemas ....................................................................................450
DTD Documents ............................................................................451
XSD and XDR Documents ............................................................452
Programmatically Building Schemas ............................................456
Adding Elements into the Schema Document ..............................458
Suggestions for Further Exploration ..............................................463
Validating XML Documents................................................................463
Creating Validation Event Handlers ..............................................463
Handling Validation Errors with Events ........................................465
Handling Validation Errors with Exceptions..................................466
Suggestions for Further Exploration ..............................................467
Learning by Example: The Hotel Reservations Desk ........................467
Key Concepts Covered ..................................................................468
Code Walkthrough ..........................................................................468
Summary ..............................................................................................487
xii
VISUAL BASIC PROGRAMMER’S GUIDE TO THE .NET FRAMEWORK CLASS LIBRARY

11 XSLT and XPath 489


Key Classes Related to XSLT and XPath............................................490
XSLT—Document Transformation ....................................................491
The Transformation Process ..........................................................492
Anatomy of an XSLT Style Sheet ..................................................496
The Transformation Process in Summary ......................................499
Suggestions for Further Exploration ..............................................501
XPath Basics ........................................................................................501
XSLT and XPath: Partners in the Transform Process ....................501
XPath Expressions..........................................................................503
XSLT Processing with .NET ..............................................................506
Loading a Style Sheet ....................................................................506
Sources of Input..............................................................................511
Types of Output ..............................................................................513
Handling Exceptions ......................................................................515
Suggestions for Further Exploration ..............................................515
Working with XPath ............................................................................515
Navigating XML Documents ........................................................516
Querying XML Documents............................................................519
Suggestions for Further Exploration ..............................................524
Learning by Example: ReservationsDesk 2 ........................................524
Key Concepts Covered ..................................................................525
Summary ..............................................................................................538

12 Working with Threads 539


Key Classes Related to Threading ......................................................540
Understanding and Applying Threads ................................................541
Microsoft Windows Threading Support ........................................541
Multithreaded Application Design Goals ......................................542
Threads Are Not a Panacea ............................................................546
Suggestions for Further Exploration ..............................................546
Basic Operations with Threads............................................................546
Starting and Stopping Threads ......................................................546
Prioritizing Threads ........................................................................548
Pausing a Thread ............................................................................550
Stopping a Thread ..........................................................................551
Working with the Thread Pool ......................................................552
Threading Basics—A Summary ....................................................554
Understanding Thread States ..............................................................554
Querying for State Information ......................................................554
Suggestions for Further Exploration ..............................................557
xiii
CONTENTS

Avoiding Contention Issues ................................................................557


Races and Deadlocks......................................................................557
Serialization and the Mutex Class..................................................560
The ReaderWriterLock Class ........................................................564
Locks with the Monitor Class ........................................................568
Synchronizing Access to Variables ................................................569
Suggestions for Further Exploration ..............................................571
Variables and Their Scope ..................................................................571
General Scope Rules ......................................................................571
Thread-Private Variables ................................................................572
Thread Local Storage ....................................................................572
Learning by Example—ThreadedTimer ..............................................576
Key Concepts Covered ..................................................................577
Suggestions for Further Exploration ..............................................583
Learning by Example: Divide and Conquer........................................584
Key Concepts Covered ..................................................................584
Code Walkthrough ..........................................................................584
Suggestions for Further Exploration ..............................................594
Summary ..............................................................................................595

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

Transactional Messaging ....................................................................641


Transactional Queues ....................................................................642
Transactional Sending ..................................................................643
Transactional Receiving ................................................................645
Security and Encryption ......................................................................649
Managing Permissions....................................................................649
Authentication ................................................................................652
Auditing ..........................................................................................657
Encrypting Messages......................................................................658
Learning by Example: qManager ......................................................660
Key Concepts Covered ..................................................................661
Selecting a Machine ......................................................................661
Exploring Queues ..........................................................................663
Queue Properties ............................................................................667
Transaction Management ..............................................................683
Summary ..............................................................................................688
Suggestions for Further Exploration ..............................................689

14 Browser/Server Communications 691


Key Classes Used for Browser/Server Communication ......................692
Client Request and Server Response ..................................................695
Managing User Requests................................................................695
Responding to Requests ................................................................700
Suggestions for Further Exploration ..............................................703
Determining Browser Capabilities ......................................................703
Detecting Browser Information......................................................703
State Management................................................................................705
Storing Global State ......................................................................705
Storing User-Specific State ............................................................707
Suggestions for Further Exploration ..............................................711
Learning by Example: MyStatus Indicator..........................................712
Key Concepts Covered ..................................................................712
Home Page......................................................................................712
MyStatus Page ................................................................................714
The XML ........................................................................................720
Summary ..............................................................................................720

15 Data Storage and Access 721


Key Classes Related to Data................................................................722
An Overview of ADO.NET ................................................................725
Data Manipulation with Data Providers ........................................725
Data Access with the DataSet Class ..............................................730
xv
CONTENTS

Queries and Resultsets ........................................................................731


Establishing a Connection ..............................................................731
Constructing and Issuing a SQL Command ..................................732
Working with the Resultset ............................................................734
Suggestions for Further Exploration ..............................................735
Updating Data Directly to a Database ................................................736
SQL Commands with Static Strings ..............................................738
SQL Commands with Parameters ..................................................740
Placing SQL Commands in Transactions ......................................742
Executing Stored Procedures ..............................................................744
Supplying Input Parameters ..........................................................744
Capturing Output Parameters ........................................................748
Capturing Return Values ................................................................750
Managing Cached Data and Schemas ................................................754
The Anatomy of a DataSet ............................................................754
Creating a DataSet ........................................................................755
Connecting DataSets to Databases ................................................761
Suggestions for Further Exploration ..............................................762
Learning by Example: DatabaseExplorer............................................762
Key Concepts Covered ..................................................................762
Summary ..............................................................................................773

16 Directory Services 775


Key Classes Related to Directory Services ........................................776
Basics of Directory Services................................................................777
What Is a Directory? ......................................................................777
Active Directory Schemas ..................................................................779
Classes ............................................................................................779
Directory Object Identities ............................................................782
Accessing Objects in the Directory ....................................................783
Loading a Specific Directory Entry ..............................................783
Examining Base Properties ............................................................784
Examining Hierarchical Relationships ..........................................784
Examining Directory Object Attributes..........................................785
Object Security ..............................................................................788
Suggestions for Further Exploration ..............................................789
Searching a Directory ..........................................................................789
Specifying What to Search For ......................................................789
Specifying Where to Search ..........................................................790
Specifying How the Results Are Sorted ........................................790
Specifying What to Return ............................................................791
Executing the Search ......................................................................791
xvi
VISUAL BASIC PROGRAMMER’S GUIDE TO THE .NET FRAMEWORK CLASS LIBRARY

Learning by Example: DirectoryBrowser ..........................................791


Key Concepts Covered ..................................................................792
Code Walkthrough ..........................................................................792
Summary ..............................................................................................802

PART III Real-World .NET Programming 803

17 Accessing COM+ Services 805


COM+ Services ..................................................................................806
Introduction ....................................................................................807
Practical Application ......................................................................807
Services Available to .NET ............................................................808
Creating a Serviced Component ..........................................................810
The Basics ......................................................................................810
Using Attributes to Define Services ..............................................813
Registration and Configuration ......................................................818
Suggestions for Further Exploration ..............................................819
Role-Based Security ............................................................................820
Adding and Associating Roles ......................................................820
Security Call Context ....................................................................820
Resource Management ........................................................................824
Object Pooling and Construction ..................................................824
Just-in-Time Activation ..................................................................827
Shared Property Manager ..............................................................828
Suggestions for Further Exploration ..............................................832
Transaction Processing ........................................................................833
Transaction Management ..............................................................833
Transaction Example ......................................................................835
Suggestions for Further Exploration ..............................................837
Events ..................................................................................................838
Loosely Coupled Event Model ......................................................838
Accessing the COM+ Event Service..............................................839
Asynchronous Components ................................................................843
Using the COM+ Queued Component Service..............................844
Summary ..............................................................................................848

18 .NET Interop with COM Applications 849


.NET Interop with COM......................................................................850
Interop Design Decisions ..............................................................851
For Further Exploration..................................................................852
Calling COM from .NET Clients ........................................................853
The Basics ....................................................................................853
The Runtime Callable Wrapper (RCW) ........................................856
xvii
CONTENTS

Calling .NET from COM Clients ........................................................859


The Basics ....................................................................................859
The COM Callable Wrapper (CCW) ............................................863
Interop Considerations ........................................................................863
Marshaling and Translation ............................................................864
Object Life Cycle ..........................................................................864
Error and Exception Handling........................................................864
Handling Events ............................................................................865
Attribute Classes ............................................................................865
Summary ..............................................................................................867

19 Managing Collections of Objects 869


Managing Collections of Objects ........................................................870
System.Collections Namespace ..................................................870
Stack ..............................................................................................871
Queue ..............................................................................................876
ArrayList ......................................................................................878
Hashtable ......................................................................................883
Thread Safety and Synchronization ..............................................886
Suggestions for Further Exploration ..............................................887
Strongly Typed Collections ................................................................888
CollectionBase ..............................................................................888
DictionaryBase ..............................................................................893
Suggestions for Further Exploration ..............................................894
Summary ..............................................................................................894

20 Profiling, Debugging, and Exception Handling 895


Handling Errors with Structured Exception Handlers ........................896
Structured Exception Handling ......................................................896
Anatomy of a Structured Exception Handler ................................897
Implementing a Structured Exception Handler..............................899
Throwing Exceptions......................................................................905
Debugging and Tracing........................................................................907
The Debug and Trace Classes ........................................................908
Compiling a Debug Build ..............................................................909
Listening for Trace Messages ........................................................911
Controlling the Level of Tracing ..................................................914
Using Assertions ............................................................................917
Profiling Applications ..........................................................................918
The .NET Runtime Performance Counters ....................................919
Creating Your Own Performance Counter ....................................919
Summary ..............................................................................................923
xviii
VISUAL BASIC PROGRAMMER’S GUIDE TO THE .NET FRAMEWORK CLASS LIBRARY

21 Globalization and Localization Techniques 925


Globalization and Localization............................................................926
Culture ............................................................................................926
Design Considerations....................................................................928
Working with Regional Data ..............................................................932
Numbers and Currency ..................................................................932
Dates and Times ............................................................................933
Calendars ........................................................................................935
Suggestions for Further Exploration ..............................................936
Resource Files......................................................................................937
Creation ..........................................................................................937
Accessing........................................................................................940
Deploying ......................................................................................941
Suggestions for Further Exploration ..............................................946
Summary ..............................................................................................946

22 Deploying, Configuring, and Licensing .NET Components 949


The Deployment Dilemma ..................................................................950
DLL Hell ........................................................................................950
Component Identification ..............................................................951
Preserving Backward Compatibility ..............................................952
Component Locks ..........................................................................952
The Deployment Solution? ..................................................................953
Side-by-Side Component Sharing ..................................................953
Windows File Protection ................................................................954
The .NET Answer ................................................................................954
Self-Describing Components..........................................................954
Version Control ..............................................................................954
Assemblies: The Basic Unit of Deployment ......................................955
The Parts of an Assembly ..............................................................955
Examining an Assembly ................................................................956
Versioning ......................................................................................959
Deploying Private and Shared Components ........................................960
Distribution ....................................................................................960
Local Components..........................................................................961
Shared Components........................................................................962
Probing for Assemblies ..................................................................963
Strong Names and Signing Assemblies..........................................963
Deploying Assemblies into the GAC ..................................................968
Using Gacutil.exe ..........................................................................968
Using the Assembly Cache Viewer ................................................970
xix
CONTENTS

Using Application Configuration Files................................................971


Configuration File Content ............................................................971
Runtime Settings ............................................................................971
Licensing Your Application ................................................................973
Enabling Licensing ........................................................................973
The License Provider......................................................................973
The License Manager and License Instance ..................................974
Summary ..............................................................................................975

A Calling the Win32 API from Managed Code 977


Platform Invoke....................................................................................978
Consuming API Functions ..................................................................979
Declaring Unmanaged Functions ..................................................981
Passing Structures and Classes ......................................................985
Callback Functions ........................................................................986

B Win32 API-to-Namespace Cross-Reference 989

C .NET Security Models 1079


Security Policies ................................................................................1080
Code Access Security ........................................................................1080
Declarative Code Access ..............................................................1081
Imperative Code Access ..............................................................1085
Role-Based Security ..........................................................................1088
Principals ......................................................................................1088
Declarative Role-Based Security Checks ....................................1089
Imperative Role-Based Security Checks ......................................1089
Joining Permissions ......................................................................1089

D .NET Framework Base Data Types 1091

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?”

The .NET Vision


Bill Gates relates that the vision for .NET could actually be traced back as far as 1990. In
his Forum 2000 keynote address he stated, “I’d even go back to the vision efforts like the
Information at Your Fingertips work that we did back in 1990.” He also referred to the “the
investments [Microsoft] made in basic research over the intervening years” and stated that
.NET is the result of the billions of dollars Microsoft has poured into research and develop-
ment over the years. He states, “almost every one of these new capabilities I’m talking about
benefits from work that took many, many years and was off in that research environment being
pulled together without a particular schedule in mind.”
.NET did not evolve out of Microsoft’s existing DNA architecture or strategy. It is nearly a
total rewrite around delivering a distributed infrastructure. In fact, what’s interesting is that the
phrase, “software as a service” is actually credited to two of Microsoft’s chief competitors. A
recent whitepaper by the Gartner Group grades .NET after its first six months. It says it best in
the following:
The software-as-a-service phrase originated around 1997 with Oracle’s CEO Larry
Ellison and Sun Microsystems’ CEO Scott McNealy, during the age of network computer
hype. Oracle and Sun never really delivered on this concept, and their vision was more
of centralized-only resources. However, to any software company listening—and
Microsoft certainly listens—Ellison and McNealy’s remarks indicated that a big change
in the software market was about to happen. .NET is Microsoft’s attempt to define this
change and the rate at which it will occur.
This definition of .NET—i.e., a shift to a software-as-a-service paradigm—is the one best
understood by most users. Gartner believes Microsoft is now providing more vision and
influence regarding this shift than any other vendor.
xxiv
VISUAL BASIC PROGRAMMER’S GUIDE TO THE .NET FRAMEWORK CLASS LIBRARY

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.

The Vision of This Book


This book’s genesis can be traced back to the recognition of a simple fact: The .NET
Framework Class Library is a universal starting point for developers. This includes those tran-
sitioning from older versions of Visual Basic and developers just entering the .NET realm—
regardless of their chosen language.
As we started to dig more and more into .NET, we recognized the need for an all-encompassing
book that would take the class library, split it into digestible pieces, and then feed those pieces
to developers as they relate to a typical programming task. This book, we figured, would need to
teach developers how to execute common programming tasks using the .NET namespace classes
and components while retaining a reference aspect that would document and explain many of
the various elements that developers would interact with in their code.
Thankfully, a few individuals at Sams Publishing shared in our vision and suggested that
we go ahead and write the book! In a way, this book plays a similar role to the book,
VB Programmers Guide to the Win32 API, that we all know and love. It attempts to demystify
what is essentially a large and complex code library (thankfully, the developers at Microsoft
have made this code library one that is actually a pleasure to deal with). When VB .NET
developers first set out to create applications for .NET, it will be crucial for them to have a
base understanding of the capabilities of the intrinsic .NET technologies. They will need to
comprehend the capabilities of the existing namespaces in .NET and how they can then be
leveraged by extension or reference to create applications ranging from the simple to the com-
plex. This book is for them.
xxv
PREFACE

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.

About the Technical Editors


Dan Suceava is currently a Senior Programmer for Vitrix, Inc., a time and attendance software
company located in Tempe, Arizona. He has been developing desktop and n-tiered applications
in Visual Basic since 1996. He has recently completed work on an ASP solution that offers
timekeeping over the Web to other businesses. He holds a Master’s Degree in Computer
Science from Arizona State University.
Jawahar (JP) Puvvala is currently working as a senior developer. He has extensive experience
with Microsoft and Java technologies, having designed and developed several enterprise sys-
tems. He has two Master’s Degrees, and currently holds MCSD, MCSE, and MCDBA certifi-
cations. JP also has research experience and has published several conference and journal
papers.
Dedication
Lars
To my wife, Cheryl, and my daughter, Kelsey, who unfortunately bore the brunt of my absence
from all things social as my world closed down to just a computer screen and various builds of .NET.
Their simple contribution of understanding and patience helped in ways too numerous to mention.

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.

What Is the Purpose of This Book?


Our intention is to provide developers a roadmap to the .NET Framework Class Library.
Chapters are designed to teach you how to use the .NET namespaces to solve your specific
business problems. This book attempts to educate current Visual Basic 6 programmers and new
VB .NET programmers on the core .NET namespaces and their uses.
Potential buyers should view this book as a reference and guide to the new API exposed by the
.NET Framework through its many namespaces, classes, and data types. We realize that when
you first set out to create applications using .NET, it is crucial to have a basic understanding of
the capabilities of the .NET technologies. You need to comprehend the capabilities of the exist-
ing namespaces in .NET and how they can then be leveraged by extension or reference to cre-
ate applications ranging from the simple to the complex.
Bottom line: We want to get you writing useful, production-ready code utilizing .NET.

Who Should Read This Book?


.NET developers should read this book. We’ve set our sights directly on the intermediate to
advanced VB developer transitioning to .NET. Those ready to see what the class library can do
to make their coding day more productive will benefit most from this title. That said, although
there are not many facets to this book targeted at the novice or beginning VB developer, the
book will be a useful resource to them as well. It will assist in teaching the promise of .NET
and serve as a great how-to reference for the namespace functionality.
2
VISUAL BASIC PROGRAMMER’S GUIDE TO THE .NET FRAMEWORK CLASS LIBRARY

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.

What Is This Book Not?


This book is not a comprehensive reference to the .NET Framework, although there is a great
deal of useful reference information included. The size of the Framework Class Library forced
us to take a different approach. There are more than 3,000 classes (and growing) in the name-
space. It would be a daunting task to reference all this information, and the value of duplicat-
ing material easily available from MSDN would be questionable. Additionally, this reference
information is now exposed to you directly in the IDE.
Instead of a reference, this book stresses the “guide” in its title. We provide a roadmap to the
various namespaces, and teach you how to write code using the classes. From there, you
should have no problem finding and using that specific enumeration value or just the right
overload to a method.
This book is not Teach Yourself VB .NET Programming in 24 Hours or The Complete Idiot’s
Guide to VB .NET. Those titles are certainly useful in their own right but we expect readers to
have a degree of proficiency with VB and programming in general. If you do not, we suggest
you get a quality beginner-level book and return to this as a how-to reference for various
namespaces.

What Is in This Book?


This book is divided into three parts. Part I, “An Introduction to .NET,” is designed to give
readers a solid footing with regard to .NET, object-oriented programming, and the Framework
Class Library. Part II, “Working with the .NET Namespaces,” walks readers through specific
programming tasks using the namespaces. Part III, “Real-World .NET Programming,” is where
we get into some of the more advanced issues related to .NET development. We position the
lessons learned in Part II within the context of real projects and present some of the more
advanced concepts in the library.
3
INTRODUCTION

How to Read This Book


We encourage readers to read the book from front to back. We also know this is often not prac-
tical due to time pressures and the length of this title. Therefore, if you are familiar with .NET
(have been using the betas) and are fairly comfortable with OOP, just skim the first section.
You can always go back, right?
For Part II, we suggest readers examine the table of contents and peruse the actual content in
order to become familiar with what is available to you. Then, pick a topic that you need to
know for a project or intend to use and read the associated chapter. This will give you a flavor
for the other Part II chapters. Now, you can go back and get the information as needed.
We believe Part III is a must-read. Developers need to be concerned with the issues presented
to help make their project more successful.

What Is on the Web Site?


There is a companion Web site to this title. On this site you will find full source code available
for download. This should save you the time of retyping and allow us to provide you with the
most up-to-date information. Any corrections or known issues with the source code or text in
the book will be noted. Additionally, we provide you with a means to communicate thoughts,
comments, and suggestions directly to us. We will be sure to post as many comments as
we can.
To visit the companion Web site, visit Sams Publishing at www.samspublishing.com.
Or, you can visit the author’s Web site at http://www.brilliantStorm.com. This site contains
the book’s source and related information. Plus, it will contain links to other .NET resources,
tips, and articles.
PART
An Introduction to .NET
I
Part I of this book focuses on introducing developers to .NET
as a whole, delineating and defining the pieces of .NET, and
explaining the general format, structure, and intent of the
Framework Class Library. This section positions Microsoft’s
.NET strategy within the context of previous Microsoft tech-
nologies and environments. Focus will be given to how .NET
affects you, the developer.
Part I explores
• What .NET is and why .NET is important
• The evolution of VB .NET
• The object-oriented characteristics of VB .NET
• The structure of the Framework Class Library
After reading this section, you should be well equipped for the
content on the Framework Class Library that follows in Part II.
Evolution of .NET CHAPTER

1
IN THIS CHAPTER
• The Composition of .NET 8

• .NET’s Relevance 16

• The .NET Framework: Under the Hood 19


An Introduction to .NET
8
PART I

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 Composition of .NET


Simply stated, Microsoft is reinventing its company around the concept of delivering software
as a service across the Internet. Its ambition is to connect islands of information and function-
ality into a collaborative, rich, and coherent experience for users.
In order to realize this vision, Microsoft is recreating its development tools, server software,
operating systems, user applications, and Web browser. It is creating new tools and applications
and helping to define new Internet standards. This all revolves around the concept of providing
software as a service.
It all starts with the development platform. That is the core of the .NET platform and the focus
of this book.
But what are the physical pieces that make up .NET? This is a difficult question to answer. For
instance, try your luck with the following multiple-choice question:
Microsoft .NET is a
a. Point of view advocating software as a service
b. Series of software development tools
c. Set of server products (like Windows 2000)
d. Marketing and branding strategy initiated by Microsoft
e. Evolution of Microsoft’s recommended way to design and build software
Evolution of .NET
9
CHAPTER 1

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 .NET Timeline


Table 1.1 outlines the products and parts that make up Microsoft’s .NET initiative.

TABLE 1.1 .NET Timetable


2000 2001 2002+
Experiences Windows XP Additional .NET Experiences
bCentral
MSN
Clients Windows CE Windows XP Tablet PC
Windows NT Embedded Windows XP Embedded Other smart devices
Windows 2000 Talisker
Stinger
XBox

Services Hailstorm Additional Services


Tools Visual Studio .NET Beta 1 Visual Studio .NET
SOAP Toolkit Beta .NET Framework
MSDN Online .NET Compact Framework
Servers Windows 2000 Server Mobile Information Server Additional Servers
SQL Server 2000 Whistler Server Embedded
BizTalk Server 2000 Whistler Server
An Introduction to .NET
10
PART I

Tools and Languages


Most of the visible thrust of .NET has been concentrated on the .NET development tools. In
order to accomplish the vision of creating software as a service, programmers must first have
tools that directly support that vision. .NET offers a suite of tools and technologies that will
help you write distributed software.

The .NET Framework


The .NET Framework represents the actual plumbing of .NET; it is a set of services and pre-
built classes that you can use to access core operating system functionality such as sending
e-mail or output to the printer. That is, in fact, the focus of this book: the .NET Framework
Class Library. This runtime environment allows us to write robust code more quickly, effi-
ciently, and to manage and deploy the code more easily.
Along with these classes, the framework includes such things as the Common Type System
(CTS), Common Language Runtime (CLR), and the Common Type Specification (CTS).
These items will be explained in detail later in this chapter.
The framework can be broken down to the following definition: The .NET Framework is the
base structure necessary to write .NET applications. Without it, there is no .NET and no .NET
applications.
One of key aspects of the .NET Framework is that it is language agnostic. The way you pro-
gram with it does not change depending on the .NET language you are using. This is a huge
productivity benefit for programmers and finally creates a level playing field for all Windows
developers.

Visual Studio .NET


Visual Studio .NET is the flagship IDE for .NET development. Finally, all Microsoft develop-
ment shares a single IDE! No more VB for this, InterDev for that; .NET consolidates design,
development, and debugging into one cohesive environment. Of course the IDE is fully cus-
tomizable and extensible. Look for third parties to offer extensions or versions of the IDE. And
of course, the object model is exposed for you to write custom extensions.
Another powerful technology is .NET enterprise templates. These templates allow team leaders
to control features and access portions of the IDE. For example, you can restrict certain team
members to only be able to create forms or do UI design. At the same time, other developers
might only be able to create components using a specific language. .NET enterprise templates
give managers control over the capabilities of specific team members.
Evolution of .NET
11
CHAPTER 1

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.

Microsoft Visual Basic .NET


After 10 years, VB is finally moving from a cool technology with which to build applications
to an actual, first-class programming language. Visual Basic .NET represents a complete over-
haul of the previous Visual Basic products from Microsoft. While introducing new components
An Introduction to .NET
12
PART I

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-Managed Extensions for Visual C++


The latest incarnation of Microsoft’s acclaimed C++ development environment, Visual C++
.NET differs from the other .NET languages: It is the only language in which you can write
unmanaged code. Unmanaged code does not run inside the .NET runtime, whereas .NET code
is “managed” by the runtime.

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.

Building Block Services


One of the many benefits of .NET is the ease with which you can integrate legacy applications
and software written on disparate systems. The software industry still struggles with this con-
cept; a concept that other industries have down to a science. For instance, a newly built home
may be assembled from a complete, modular staircase made by a company in Canada, a deck
composed of pre-assembled timber from a company in Minnesota, and front and side frames
from a company in Illinois. It is a rare occasion when a builder or architect doesn’t leverage
work done by another firm or company when it comes to actually raising the walls of a house.
It is just such a business model that the software industry has been pursuing: one where com-
panies are free to build the “blocks” that they have the most experience and expertise with, and
then sell them (as a service) multiple times, creating a commodity item.
Traditionally, this has been difficult to do with software because the process of taking someone
else’s code and making it work inside of, or in conjunction with, your own was a technically
challenging and risky proposition. It is definitely not as easy as integrating that staircase you
brought from Canada in your house: A few nails, some sweat, and you have a structurally
sound set of stairs! Software integration typically involves data format incompatibilities as
well as discrepancies in architecture and design.
Some of these building block services that have already been announced are listed next.
Evolution of .NET
13
CHAPTER 1

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.

Directory and Search Services


Directory and search services are a means of cataloging and then searching a variety of things
(people, places, and so on). They expose a virtual “yellow pages” of information that can be
queried for specific, relevant information.

.NET Enterprise Server and Office Products


To accelerate the introduction, development, and propagation of service-based software,
Microsoft is rapidly infusing its core product lines with .NET technology, positioning it as yet
another building block layer of helper services, all with the same goal of easing development
of software as a service.

Application Center 2000


Application Center 2000 is a management tool designed to simplify the management of groups
of servers. Using Application Center makes it much easier to administer and maintain Web
applications that span multiple machines. How? This product lets you treat a group of servers
as one server.

BizTalk Server 2000


When different companies have to exchange data between their systems, problems inevitably
arise. Most of the issues revolve around the fact that, typically, different companies will store
their data in different formats. A typical scenario involves trading partner hubs—companies
An Introduction to .NET
14
PART I

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.

Commerce Server 2000


Commerce Server provides all of the things necessary to build and deploy an e-commerce Web
site. It aims to reduce the complexity of publishing products onto the Web for sale. It is a com-
prehensive solution that covers business to business (businesses selling or buying from other
businesses) or business to consumer (the traditional retail model of commerce—customers buy
products) models.

Exchange Server 2000


Exchange Server is Microsoft’s premiere collaboration and messaging tool. It gives a company
the ability to store and easily share information through e-mail and other mediums such as
real-time conferencing, workflow, and instant messaging.

Host Integration Server


One of the premises that .NET applications are built on is the capability to “talk” between soft-
ware applications—even if they reside on different types of machines. Host Integration Server
is Microsoft’s product to enable just that. This is a substantial revision of its prior product,
Microsoft SNA Server. It provides a gateway for talking to many different varieties of systems,
both mainframe and PC based.

Internet Security and Accelerator Server


The Internet has been a great vehicle for progress, enabling unprecedented communication
between individuals and companies. This same ease of communication, however, comes with a
downside: It is all too easy for predators to maliciously destroy information, steal identities,
and, in general, make life a mess for those on the receiving end of a hacker’s attack. Internet
Security and Accelerator Server is a software-based “firewall”: It sits between a company’s
machines and the Internet to guard against intrusion and criminal activity. This server also
offers another benefit: By storing copies of Web pages, images, and other data, it can substan-
tially speed up Web surfing.

Mobile Information Server


One of the big goals of .NET is to bring software services to all sorts of devices: personal
computers, hand-held computers, even cell phones! Mobile Information Server helps to extend
Evolution of .NET
15
CHAPTER 1

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.

SQL Server 2000


SQL Server 2000 is a complete database and data management package. One of its key .NET
characteristics is its ability to support “queries”—requests for data—across the Internet in a
variety of different formats, including XML. SQL Server is designed to be “self-tuning;” that
is, it can monitor its own performance and functions and make changes on its own without
human interaction.

Windows 2000 Server, Advanced Server, and Data Center Server


Last, but certainly not least, is the Windows 2000 family of operating systems. These are mul-
tipurpose operating systems for businesses of all sizes. These operating systems are responsible
for implementing services that computer users usually take for granted: sharing files, printing,
hosting Web sites, and running applications like Office .NET. As part of the .NET strategy,
Windows 2000 was built with all of the technologies to enable software as a service to become
reality. Windows XP will continue this trend even further.

Suggestions for Further Exploration


➲ For the definitive resource on .NET, check out http://www.microsoft.com/net
➲ Visit MSDN® Online for .NET information at http://msdn.microsoft.com/net
➲ For more information on Visual Studio .NET check out
http://msdn.microsoft.com/vstudio/nextgen

➲ For more information on SOAP go to http://msdn.microsoft.com/soap


➲ Check out the ASP.NET site at http://www.asp.net
➲ GotDotNet is Microsoft’s community .NET site: http://www.gotdotnet.com
➲ To read about .NET Enterprise Servers: http://www.microsoft.com/servers/net
An Introduction to .NET
16
PART I

.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

The .NET Framework: Under the Hood 1


This section lifts the hood of the .NET Framework and shows you what’s underneath. In doing

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.

Common Language Runtime (CLR)


The Common Language Runtime (CLR) is the basis for all code in .NET. It is the execution
engine designed to manage and control our code. It provides a number of services for our
application (most are discussed in detail in the coming paragraphs). The CLR
• Manages memory and provides isolation and garbage collection
• Provides and enforces Code Access Security (CAS)
• Ensures type safety through the Common Type System (CTS)
• Provides a Just-In-Time (JIT) compiler for converting Intermediate Language (IL) code
into actual native code
• Supplies developer services including profiling and debugging
• Grants support for an unlimited number of languages via its Common Language
Specification (CLS)
• Provides versioning and deployment support
An Introduction to .NET
20
PART I

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.

Common Language Specification (CLS)


The Common Language Specification (CLS) is a group of rules that provide language and
compiler authors a guide when creating or porting a language targeted at the .NET Framework
CLR. The CLS defines the basic set of features that each language must implement for it to be
considered .NET compliant. Thanks to the CLS, and the architects of .NET, it is possible to
ensure true language-to-language interoperability. Languages might be syntactically different,
but their strict adherence to the CLS ensures their cross-language compatibility.
Evolution of .NET
21
CHAPTER 1

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!

Common Type System (CTS)


The Common Type System (CTS) is a set of rich types that are built directly into the CLR. It
is the rule system that all languages (and developers) must adhere to when defining types. The
system enforces how types are used (type safety) and managed within the runtime.
The CTS is one of the key components to ensuring language-to-language interaction. For
instance, you can be assured that an integer in VB is the same size in C#. Thanks to the CTS,
we are now afforded language interoperation not just at runtime, but also during development.

Microsoft Intermediate Language (MSIL)


The Microsoft Intermediate Language (MSIL or simply IL) is the language into which code
written with the Framework gets precompiled. The point of MSIL is to provide a CPU-
independent instruction set. This way, code can be deployed on a varying number of platforms
(in the form of MSIL) and efficiently converted to native code for the given platform by the
runtime. Yes, it seems the future of .NET lies beyond simply the WINTEL platform.
Along with the MSIL, compilers create the metadata that describes the types, members, and
code references for the given compiled code. This metadata helps the runtime do things like
enforce security, locate and load class, and generally describes the code’s interaction with the
runtime. The MSIL and metadata are combined in one file called a Portable Executable (PE)
file. Again, thanks to metadata, code written for the runtime is self-describing and eliminates
the need for the registry, type libraries, and IDL.
An Introduction to .NET
22
PART I

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.

Global Assembly Cache (GAC)


Most assemblies are created to be private to our applications. That is, the assemblies are con-
sumed by one another and not shared outside of the application. Private assemblies are installed
into the application’s directory. This is the preferred method unless sharing an assembly is
required.
Shared assemblies are those building blocks that need to be referenced by more than one appli-
cation. To do so, you must load the assembly into the Global Assembly Cache (GAC). The
GAC is a machine-wide cache designed for the purpose of sharing assemblies. GAC assem-
blies must have strong names. That is, their names must be unique via the use of cryptographic
key pairs. This ensures versioning and security for your assembly in the deployment
environment.

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.

Garbage Collector (GC)


Garbage collection is a feature built into the CLR that detects and cleans up (de-allocates
memory) managed objects that are no longer accessible. As VB developers, we are somewhat
familiar with this concept, as we’ve been relying on COM/COM+ services to handle this
operation.
However, if you are accustomed to using the Terminate event for code clean-up routines then
you will need some slight restructuring of your component design. While the runtime does call
the Dispose method when de-allocating your object, you cannot rely on when this method will
be called. It is better to move this Terminate code into another close method that can be
explicitly called by users of your component as is necessary.
The benefits of a garbage collector include:
• The elimination of persisted objects in memory due to circular references
• Faster object de-allocation and memory reclamation
• Never having to worry about memory leaks or reference counting

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.

Framework Class Library (FCL)


The Framework Class Library is a set of Window’s utility classes designed around providing
developers prebuilt code for executing common programming tasks. The library is, of course,
organized around a namespace hierarchy. Classes include functionality for file I/O, printing,
font management, security, data access, threading, Web services, string manipulation, messag-
ing, Windows Forms, and the list goes on and on.
All classes are designed to be object-oriented and are tightly integrated with the CLR. Often,
you can derive functionality directly from the library into your own managed code. And, of
course, the principal benefit to developers is that you only have to learn one library; the FCL
works the same across languages. You no longer have to know the details of the Win32 API,
ATL, and MFC—one library for all languages.
Should you need to access the Win32API, however, it is still there. It is simply no longer needed
as often thanks to the rich set of classes inside the framework. These libraries replace the major-
ity of its functionality, and often, actually derive their features from the API themselves.
An Introduction to .NET
26
PART I

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.”

Suggestions for Further Exploration


➲ For a great comparison of .NET languages, their constructs, types, and so on, check out
MSDN: Visual Studio .NET/Developing with Visual Studio .NET/Reference/Language
Equivalents.
➲ For more detailed information on the inner workings of the .NET Framework, including
using the CTS to create your own value types from VB, details on the makeup of assem-
blies, and description of the CLS in relation to languages and compilers, visit MSDN:
Visual Studio .NET/.NET Framework/Inside the .NET Framework.
➲ For a great source on language-to-language interoperation, read MSDN: The MSDN
Library/.NET Development (General)/Technical Articles/Handling Language
Interoperability with the Microsoft .NET Framework.

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

• New Language Concepts 31

• Interactive Development Environment


(IDE) 39
An Introduction to .NET
30
PART I

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.

Where We Came From


The latest versions of VB were created to handle client/server, database-driven development.
You need to look no further than the existence of Visual InderDev and Active Server Pages
(ASP) to understand this. VB .NET, on the other hand, was created in response to a full-blown
shift to highly distributed and loosely coupled application development.
Evolution of VB .NET
31
CHAPTER 2

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.

New Language Concepts


The changes reflected in Visual Basic .NET are in direct relation to many of the changes
brought about by the evolution of Windows programming to .NET. The CLR and adherence to
the CLS, coupled with the goals set out for the language, required a great deal of change. The
Framework Class Library (FCL) resulted in many Visual Basic functions being replaced or
augmented by methods inside of a class. Together, these factors ensured that the changes to the
Visual Basic language and the underpinning technology would be far reaching and numerous.
That said, the new and very much improved language is intuitive and, as a result, makes for an
easy transition. This section details some of the major changes to the Visual Basic language
brought about by .NET.
An Introduction to .NET
32
PART I

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.

Declaring Variables and Data Types


This section details a number of the changes within VB .NET regarding the manner in which
variables are declared and scoped. In nearly all cases, the developer is left with more control as
poor or outdated parts of the VB language have been eliminated or replaced.

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()

‘equivalent to above, but more explicit


Dim myObject As SomeObject = New SomeObject()

‘create an object and set constructor parameters


Dim myObject As SomeObject = New SomeObject(New SomeOtherObject(), x)

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

Dim myString as String = “This value cannot change”


myString = “Changed Value”
The code executes without issue (an example of Visual Basic easing complexities and main-
taining backward compatibility). However, .NET actually has to create two separate strings to
handle this code. The first is created (and memory allocated) when the variable is declared.
The second line of code actually causes a new set of memory to be allocated and referenced by
the variable myString.
The same immutable nature of strings holds true for concatenation. The line, myString =
myString & “ some more text”, will execute fine, but .NET is required to create and destroy
memory accordingly. For this reason, .NET provides the System.Text.StringBuilder class.
This class is optimized for heavy string concatenation and should be used when performing a
lot of string concatenations. Its syntax is simple: myStringBuilder.Append(“ some more
text”). After it is concatenated, you can convert the object into a string value by calling
myStringBuilder.ToString. Of course, the class has a number of other properties, methods,
and parameters that you are encouraged to explore.

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()

‘available for the sub


Dim x As Int16

For x = 1 To 10

‘available inside the loop and all narrower


Dim z As Int16

If x = 2 Then

z = 3
Evolution of VB .NET
35
CHAPTER 2

‘only available inside the if block


Dim y As Int16

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:

VB6 VB .NET CLS/CTS


Integer Short Int16 (16 bits)
Long Integer Int32 (32 bits)
None Long Int64 (64 bits)
Given the confusion this might cause, we suggest declaring your types explicitly using the
CLS/CTS types (Int16, Int32, Int64). This way, you will know what size type you are creat-
ing and non-VB programmers, when reading your code, will also know!

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

FCL Classes That Replace/Augment VB6 Elements


Thanks to the FCL, a number of functions that were inherent to the Visual Basic language are
now abstracted to class library methods. The helps ensure that C# and VB .NET objects can
communicate with one another, because they both use the same library. Table 2.1 indicates a
number of these changes.

TABLE 2.1 FCL Classes to VB6 Elements Cross Reference


Namespace VB6 Programming Element
System.Drawing Circle, Line
System.Math Atn, Sgn, Sqr, Rnd, Round
System.Diagnostics Debug.Pring, Debug.Assert
System.String LSet, RSet, and string functions like Replace,
Split, Trim, and so on
System.Windows.Forms.MessageBox MsgBox
DateTime Structure Now, Date, Time, Date$, Time$
System.Globalization Calendar

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

Public Sub Dispose() Implements IDisposable.Dispose


‘clean up and free resources
End Sub

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:

Private Sub myOptions(ByVal somOtherParam As Integer, _


Optional ByVal someParam As Boolean = True)

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

Public Property SomeProperty()

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

‘the second expression is not allowed but it never gets evaluated


If y = 2 OrElse x / y = 10 Then

Console.WriteLine(y)

End If

Suggestions for Further Exploration


➲ For more information on what is new within VB .NET, check out MSDN: Visual Studio
.NET, Visual Basic and C#, Getting Started, What’s New in Visual Basic and C#.

Interactive Development Environment (IDE)


The goal in designing the new IDE was simple: developers should not have to go outside of
their development environment to execute programming tasks. The result is a powerful set of
tools, with very intuitive access to their features. The VS .NET IDE will change the way in
which you write your VB .NET code. This section provides exposure to some of the tools and
editing enhancements that VS .NET offers. It is not meant to be a complete or detailed refer-
ence, but it will make you aware of what is available to you.

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

Solutions, Projects, and Source Files


The Visual Studio .NET Solution Explorer enables you to manage the various files that make
up and relate to your application. The tool is not a radical departure from the VB6. In fact, if
you are familiar with Visual InterDev, the revised Solution Explorer should be very familiar.
The biggest difference, which is a great productivity enhancer, is that the Solution Explorer
and IDE now handle all Microsoft source files in one place. This simple concept allows you to
work on an XML file, an ASP.NET form, a VB class, a C# class, and so on—without switch-
ing tools or even opening and closing projects!
Files are grouped by two containers. The solution container (.sln) enables you to group a num-
ber of projects under one solution. This is similar to VB6’s group (.vbg) file. Grouping projects
in this way allows you to compile, run, and debug the group as a whole. The project container
(.vbproj) is similar to VB6’s project file (.vbp). This file contains the source files that make up
a given project. A project’s source is compiled directly into DLLs, EXEs, and the like.

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

• Inheritance—Defining Classes in Terms of One


Another 54

• Polymorphism—Overriding One Class Method


with Another 60
An Introduction to .NET
50
PART I

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

Classes—Wrapping Data and Behavior Together


Classes are blueprints or specifications for actual objects that we will create in our code. They
define a standard set of attributes and behaviors. Because classes only define a structure or
intent, they are virtual in nature. For instance, a class cannot hold data, it can’t receive a mes-
sage, and in fact can’t do any processing at all. This is because classes are only meant to be
object factories. Just like real engineering blueprints of a building, they only exist to construct
something else. When we program, this “something else” we are trying to construct is an
object. An object can hold data, can receive messages, and can actually carry out processing.
While you don’t typically use the term class in your everyday (non-programming) life, we are
all certainly familiar with the concept of objects. These are the things that surround us day in
and day out; they are the nouns in our universe. We are used to interacting with objects. For
example, you place a plate on your table for dinner. The plate has food on it—a few different
types of food, in fact. We can see that all of these things have distinct properties: The plate is
white with a faint flower pattern, and the food has a particular texture, taste, and smell. We
also expect that objects will allow us to interact with them in different ways.
Just like in the real world, code objects (we also call them instances) are actual physical mani- 3
festations of classes.

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.

Classes are typically constructed to mimic, or approximate, real-world physical structures or


concepts. By using classes in your code, you can simplify both your architecture and your
understanding of the code; this is due to the inherent approachability of objects—your mind is
used to deal with objects. For example, which do you suppose would make more intuitive
sense to you?
You are writing code to move an icon from the left side of the screen to the right side of the
screen. The procedural programming way would probably have you calling some API function
(maybe it’s called SystemDskRsrcBlit) and passing parameters into the function call. But,
what if you were free to do this:
• Create an icon object
• Tell it to MoveLeft
The object-oriented way just seems to make more sense to us—it seems to appeal to the way
that our minds are wired.

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.

Talking Between Classes


We have said that classes define a set of behaviors. These behaviors would be useless to us
unless we had a way to actually stimulate or initiate a particular behavior. Therefore, we have
the concept of messaging. A message is nothing more than a request, from one object to
another, to perform some sort of action. The receiving object may choose to ignore the action
(especially if it doesn’t have a behavior defined that would map to the requested action) or it
may perform a specific action that could, in turn, send messages to other objects.
A core tenet of object-oriented systems is that classes think for themselves. A particular class
knows how it should react to an incoming message; the calling class isn’t forced to understand
how or why the receiving class behaves the way that it does. This is the essence of information
hiding. In information hiding, an object hides its internal machinations from other objects (see 3
Figure 3.1). Information hiding is important because it helps reduce the overall design com-

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()

InsertRecord() Class Data Set


method:InsertRecord()
Source dlmrstas Recordset
Object buffArray = ret.Serialize()
While Not(buffArray.EOF

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.

Inheritance—Defining Classes in Terms of One


Another
Inheritance is the capability for one class to inherit or take on the traits of another class.
Typically, this happens in a hierarchical fashion. Consider the following simple example to see
how this inheritance results in a natural hierarchy of classes. Figure 3.2 shows three classes: HR
Employee, IT Employee, and Warehouse Employee. Each of these is shown with some of their
properties and methods.
Object-Oriented Concepts in .NET
55
CHAPTER 3

HR Employee Stock Employee IT Employee


Attributes Attributes Attributes
Age Age Age
Salary HourlyRate Salary
DateofHire DateofHire DateofHire
Behaviors Behaviors Behaviors
Promote Promote Promote
Terminate Terminate Terminate
Assign Tech

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

HR Employee Stock Employee IT Employee


Attributes Attributes Attributes
Age Age Age
Salary HourlyRate Salary
DateofHire DateofHire DateofHire
Behaviors Behaviors Behaviors
Promote Promote Promote
Terminate Terminate Terminate
Assign Tech

FIGURE 3.3
Introduction of a parent class.

This hierarchical structure is typical of well-engineered class libraries—the Framework Class


Library is organized in just such a way.
An Introduction to .NET
56
PART I

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.

Inheritance by Natural Relationship


One of the nice things about inheritance is that it often simply realizes a relationship that we
already make in our minds. That is, it is often just a recognition of real-world relationships. We
can tell that a dog or a cat is a type of an animal—the inheritance between the concept of an
animal class and a dog or cat class is obvious. Again, this is a good thing as it helps to reduce
the semantic gap that we talked about earlier and helps you make your code organization eas-
ier to understand. Organizing your code is only one benefit of inheritance—code reuse is
another.
We have said that a class can inherit the traits of another class. We have also said that the traits
of a class are implemented as property and method routines. When these routines are inherited
between classes, it obviously means we are assuming the actual source code of one class into
another. Thus, we have code reuse.
If we look back at Figure 3.3 we can see how each line of code that was written to implement
the parent class methods can be reused by each of the child classes. Code reuse in this fashion
becomes a powerful rapid application development enabler. If we needed to change some lines
of code in one of the parent class methods, the change would be immediately realized in its
children classes. This also allows us to build up complexity in a child class by inheriting from
potentially simple base classes.

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

Class E Class F Class G

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

InkJet *denotes inherited members


Attributes
IsPaperOut
Isinkinstalled
Behaviors
SendErrSignal*
AlignCartridges

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

InkJet *denotes inherited members

Attributes
IsPaperOut*
IsInkinstalled
Behaviors
FormFeed*
On*
Off*
AlignCartridges
SendErrSignal*

FIGURE 3.7
An example of multiple inheritance.
An Introduction to .NET
60
PART I

Polymorphism—Overriding One Class Method


with Another
The next OO trait we will discuss is polymorphism. Unlike inheritance, polymorphism is con-
cerned with how a class presents itself to the outside world. Polymorphism roughly means
“many forms,” and alludes to the fact that a specific named behavior can be implemented in
different ways by different classes.
In other words, classes can reuse behavior names but implement them differently.

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

• Enhancing Developer Productivity 66

• The Elements of a Namespace 70

• Programming with the Framework Class


Library 75
An Introduction to .NET
64
PART I

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.

Introducing the Framework Class Library


As you have probably figured out from its name, the class library is merely a collection of
classes and related structures that can be leveraged as base building blocks for application
development. As such, it is safe to think of this collection of classes as an API: They are a
boundary interface between our applications and the operating system. This concept, of course,
is really nothing new to Visual Basic developers: the ADO library, the Win32 API, the COM+
services library—all of these constructs have allowed us to reference and use pre-existing code
in our applications. The class library is a massive library of pre-existing code that you can use
as a foundation for your application features.

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

Organization—The Namespace Hierarchy


The primary units of organization for the class library are namespaces. A namespace is just a
bucket for functionality: It describes a grouping of like-focused classes and constructs. You can
liken the concept of a namespace to that of a file system folder—they both attempt to imple-
ment organization across objects with parent and child relationships. Just as a folder can con-
tain other folders and actual documents, a namespace can contain other namespaces or actual
classes, delegates, and so on.
All namespaces stem from a common root: the System namespace. The System namespace is
the one and only common root for all other namespaces.
The System namespace, for instance, contains the actual structures that define the common
data types used in .NET such as Boolean, DateTime, and Int32. It also contains the most
important data type of all, Object, which is the base object inherited by all other .NET objects.

Walking the Tree


The first level of children namespaces under the System namespace represents the high-level
functionality groupings exposed by the API. Table 4.1 shows these high-level namespaces,
with a brief description of their focus. Remember that each one of these high-level namespaces
can (and likely will) have other children namespaces that further decompose and organize the
functionality and focus of the parent namespace. A good example of this is the System.XML
namespace, which parents the System.XML.Schema, the System.XML.Serialization, the
System.XML.XPath, and the System.XML.XSL namespaces.

TABLE 4.1 Primary Namespaces Under the System Root


Name Focus 4
CodeDOM Source code document structure and manipulation (including

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

TABLE 4.1 Continued


Name Focus
EnterpriseServices COM interactions and settings.
Globalization Definition of culture-specific settings.
IO Synchronous and asynchronous access to files and streams.
Management Access to WMI functionality.
Messaging Message creation and transmission in addition to management of
message queues.
Net Network communications and protocols.
Reflection Examination and on-the-fly creation of types, methods, and
fields.
Resources Management of culture-specific resources.
Runtime Access to low-level runtime functions, including compilation and
interop services.
Security General runtime security including policies, permissions, and
credential resolution.
ServiceProcess Creation and installation of Windows services.
Text Conversion and formatting of text.
Threading Explicit thread creation and management.
Timers Server-based timers.
Web Browser/server communication over HTTP.
Windows Form-based Windows creation.
XML XML processing.

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.

Enhancing Developer Productivity


An important design goal of the Framework Class Library is to enhance developer productivity.
It may surprise you to know that the class library primarily targets the goal of enhanced pro-
ductivity not by introducing new functionality but by repackaging functionality in an
Introduction to the .NET Framework Class Library
67
CHAPTER 4

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.

Finding the Code That You Need


If we start to examine the ways that the class library helps to make for a more productive
development experience, we can see a few things immediately. For one, the class library allows
you to easily find the functionality for which you are looking. While the size of the class
library may seem daunting at first, its logical presentation inside of the namespaces allows
functionality to be discovered in a straightforward fashion. For instance, the System.Drawing 4
namespace contains classes that will provide you with drawing functions. The System.IO

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++.

Using the Class Library


Finding the class you need is the first step; after you have found it, the Framework Class
Library also makes utilizing it easy. By exposing functionality through properties and methods,
we have straightforward access to features that are extremely complicated under the hood. In
the true spirit of data hiding and encapsulation, we are dealing with black boxes—we neither
know nor care what takes place in the box. What we do care about is that we talk to the black
box in a standard and predictable way.
This element of productivity is not insignificant. Again consider the effort required to work
directly with the Win32 API. Because Visual Basic was talking to an API that had a different
type system, there was not a clear, one-to-one mapping between API calls and actual Visual
Basic syntax. Now, due to the integral role that the Common Type System plays, all languages
talk to runtime components through the same data types. This ensures that the classes in the
Framework Class Library can be consumed evenly by any of the .NET languages. For the first
time, Visual Basic developers have the full range of framework functionality open to them with
no corresponding drop in productivity.
There is little to no disjoint when a programmer hops between language-intrinsic functions and
the class library. In other words, Visual Basic developers are finally free to accomplish their
application programming without jumping in and out of different coding paradigms (such as
moving from procedural Win32API patterns to VB component patterns). Design patterns can
remain consistent throughout your code regardless of the actual type of component being con-
sumed. This is because the entire library was built to support object and component-based pro-
gramming intrinsically—these concepts are “built-in” and not bolted on. What’s more, the
class library usage follows familiar design patterns for VB developers. There is no esoteric
syntax required; instead, you reference the component, instantiate it, and then use its methods
and properties.
Introduction to the .NET Framework Class Library
69
CHAPTER 4

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

Public Class TargetServer


Sub Main()
Dim resolver As DNS = New DNS()
An Introduction to .NET
70
PART I

.
.
.
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.

The Elements of a Namespace


Each namespace can define its own set of classes, structures, delegates, interfaces, and enumer-
ations. And, as we have seen with the System namespace, they can also sometimes contain
value types. Let’s examine each one of these in turn.

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.

TABLE 4.2 .NET Class Attributes


Class Attribute Description
Sealed Specifies that you cannot inherit from this class. A good example
of a sealed class in the Framework Class Library is the String
class. In Visual Basic .NET you can create your own sealed
classes by using the NotInheritable keyword.
Introduction to the .NET Framework Class Library
71
CHAPTER 4

TABLE 4.2 Continued


Class Attribute Description
Implements Specifies the interfaces implemented by a particular class. For
instance, the Array Class implements the Ilist interface, imbuing
Arrays with the capability to add members and retrieve the index
of a member.
Abstract Indicates that the class cannot be directly instantiated. They typi-
cally exist as a means of organizing lower-level derived classes,
or as a convenient virtual class that is used solely as a template
for other classes instead of instances. Examples of abstract
classes include the XMLWriter class in the System.XML name-
space, and the Image class in the System.Drawing namespace.
Inherits Identifies the base class that the current class inherits from. As an
example, the ColorDialog Class inherits from the CommonDialog
class—an acknowledgment of the fact that it is a type of com-
mon dialog.
Exported/Not Exported Indicates whether a class is visible outside of its defining assem-
bly. Exported classes are those visible outside of the assembly in
which they are defined.

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.

TABLE 4.3 Structures versus Classes


Class Structure
Allows parameter-less constructors? Yes No
Supports properties, methods, and events? Yes Yes
Can be inherited from? Yes No
Can implement interfaces? Yes Yes
Support for destructors? Yes No

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:

Public Class Class1


Implements IDisposable TO

Public Sub Finalize() Implements IDisposable.Dispose


.
.
.
End Sub
End Class

This code snippet shows a class implementing the IDisposable interface.


An Introduction to .NET
74
PART I

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.

Programming with the Framework Class Library


Because the Framework Class Library contains classes in the true object-oriented spirit of that
word, there are two primary ways you will find yourself using these base classes:
• Black boxes that you can call into
• Classes that you can inherit from and extend to craft your own functionality

Instantiating Objects from the Class Library


The use of base classes as a black box is probably the primary path for most developers. In this
mode, your code will be treating the class library as a simple API to get at core functions on
your particular operating system.
Using the class library classes in this manner does not represent a departure from the program-
ming model that Visual Basic developers are used to. As an example, if you have programmed
Visual Basic applications that have made use of the ADO library, you probably did the
following:
1. You set a reference to the code library through the VB IDE.
2. You Dim’d a container for one of the ADO objects.
3. After you had a container object, you instantiated a version of an ADO object into it.
4. After that, you simply invoked its methods and properties as needed.
With Visual Basic .NET, your consumption of Framework classes will follow the exact same
pattern. 4

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

Inheriting from the Framework Class Library


With the exception of those classes marked as sealed, you are free to build your own classes
on any of the base classes in the class library using the inheritance model in .NET.
As you explore the Framework Class Library, you will see many instances of inherited classes
that override and overload class members (both of these concepts were discussed in the previ-
ous chapter). In the documentation for a specific class, you may see a method defined as
Overrides:

Overrides Public Function GetYear(ByVal time As DateTime) As Integer

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

Catch win32Error As Win32Exception


‘handle win32 error

Catch sockError As SocketException


‘handle network socket error

Finally
Introduction to the .NET Framework Class Library
79
CHAPTER 4

‘code to run regardless of exception or not

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

• The Form Class Hierarchy 91

• Visual Characteristics of Forms 92

• Using the Clipboard 100

• Creating Menus 103

• Working with Menu Items 108

• Handling Menu Events 116

• An Introduction to Controls 124

• Learning by Example: The EventLog


Control 142
Working with the .NET Namespaces
84
PART II

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

Key Classes Related to Windows Forms


In the .NET Framework, the visual design of applications starts at the class library level, and a
number of classes are dedicated to providing Windows Forms functionality. Table 5.1 lists the
classes that are discussed in this chapter.

TABLE 5.1 Key Classes of the System.Windows.Forms Namespace


Class Description
AxHost This class provides a way to wrap ActiveX controls so that
they can be used as Windows Forms controls.
Clipboard This class is used to interact with the Windows Clipboard.
ContainerControl This class encapsulates all controls that can act as containers
for other controls.
ContextMenu This class represents a shortcut menu.
Forms, Menus, and Controls
85
CHAPTER 5

TABLE 5.1 Continued


Class Description
Control This class is the parent class for all control inheritance.
Cursor This class controls the visual representation of the mouse
pointer on the screen.
Cursors This class is a collection class that can be populated with
Cursor class objects.
DataFormats This class is used in conjunction with the Clipboard class to
specify a format for data that is to be interchanged with the
Clipboard.
DataFormats.Format Just as with the DataFormats class, the
DataFormats.Format class is used in conjunction with the
Clipboard class. It provides ID/name pair access to a spe-
cific data format.
DataObject This class provides a way to transfer data in a format-
independent fashion.
FileDialog This class can be used to programmatically show a file
selection dialog box to the user and then process the user’s
selection.
FontDialog This class can be used to programmatically show a dialog
box that lists all the fonts that are installed on the system.
Form This class represents a Windows Form.
Form.ControlCollection This class provides access to all the controls associated with
a given form.
IWin32Window Interface This class allows programmatic access to Windows HWnd
handles.
MainMenu This class is used to add the initial menu structure to a form.
Menu This is the base class from which all other menu classes
derive.
Menu.MenuItemCollection This class is used to manage collections of MenuItem
instances.
MenuItem This class encapsulates specific items in a menu.
Message Structure This class is used to wrap Windows messages. 5
OpenFileDialog This class is used to provide access to the Windows file open
FORMS, MENUS,
AND 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

TABLE 5.1 Continued


Class Description
PrintDialog This class can be used to provide common printing options
(including printer selection) for a forms-based application.
PrintPreviewDialog This class is used to display a print preview dialog box for
printing-enabled forms applications.
SaveFileDialog This class is used to display a dialog box for specifying save
options for a file.
Screen This class represents the actual display screen for the current
system. It is commonly used to retrieve the display bounds
of the screen.
SendKeys This class is used to send keystrokes to a specified applica-
tion.
SystemInformation This class is used to examine operating system information.

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.

Introducing the Form Class


In the .NET Framework, the Form class is the primary object that you use to create and manip-
ulate forms. It is a representation of any type of window that you might need to display in an
application, from dialog boxes to resizable windows to MDI windows. You interact with the
Form class as you do any other class in the .NET Framework; there is nothing inherently spe-
cial or different about it. This means you don’t have to alter your approach to development just
to deal with forms (as developers have had to do in the past).
As .NET base classes go, the Form class is a fairly large class. We’ll touch on its most impor-
tant members in this chapter; you can explore the rest of the Form class members on your own.
Forms, Menus, and Controls
87
CHAPTER 5

Using the Windows Form Designer


Every forms-based .NET application, regardless of whether it is crafted inside the Visual
Studio Integrated Development Environment (IDE), ends up inheriting from the Form class in
one way or another. For instance, you could choose to inherit directly from the Form class, or
you could inherit from another form that you have created (which in turn probably inherits
from the Form class). When you add a form to an application, Visual Studio auto-generates
some code that is educational. The Visual Studio template for forms-based applications shows
up in the New Project dialog box as Windows Application. Selecting File, New gets you to the
New Project dialog box (see Figures 5.1 and 5.2).

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

#Region “ Windows Form Designer generated code “

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As _


System.EventArgs) Handles MyBase.Load

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

- Public Class Form1


Inherits System.Windows.Forms.Form
+ Windows Form Designer generated code

- Private Sub Form1_Load (ByVal sender As System.Object,

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 “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call

End Sub

‘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

‘Required by the Windows Form Designer 5


Private components As System.ComponentModel.Container
FORMS, MENUS,
AND CONTROLS
Working with the .NET Namespaces
90
PART II

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub _
InitializeComponent()

‘Form1

Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(292, 273)
Me.Name = “Form1”
Me.Text = “Form1”

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.

Working with the Form Class Constructor


The New subroutine is the constructor for the Form1 object. The first thing the New constructor
does is call MyBase.New(). This is standard operating procedure for implementing inheritance
through a class hierarchy. The MyBase keyword is a reference to the parent class from which
the current class is inheriting. In this case, MyBase returns a reference to a generic Form class.
The Form class, in its constructor, does the same thing, its parent class does the same thing, and
so on. The net result is a series of cascading New calls back up through the class hierarchy tree.
Thus, the most basic class has its constructor code execute first, followed by each of its chil-
dren in turn, until you finally arrive at the ultimate root class, Form1. This allows you to place
any specialized code that you might want in the Form1 constructor, and you are assured that all
the inherited constructor code has already run.
Normally, the New constructor is where you would place any initialization code for variables,
object references, and so on. Because we haven’t touched any of the code yet, the only code
block currently in the New constructor is a call to InitializeComponent, which the Form
Designer has automatically added.
The InitializeComponent subroutine is a block of housekeeping code that the Form Designer
uses to set initial form property values. You can see that it is setting the form’s ClientSize
Forms, Menus, and Controls
91
CHAPTER 5

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.

Instantiating a Form Object


All the code up to this point in the chapter has been needed to define the form. To actually cre-
ate a form object based on the Form1 class, you need to use the following code:
Dim frmMain As Form1 = New Form1()

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.

The Form Class Hierarchy


Figure 5.4 shows how the Form class (and the UserControl class) is inherited from the generic
Control 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

The Control Class


The Control class represents components that have a visual aspect, such as buttons, check
boxes, and treeviews. Because the Control class is the first level in the class hierarchy that
specializes in visible structures, it is also the first class to start exposing visual attributes such
as ForeColor, BackColor, Height, Width, and Cursor. The Control class also implements
support for such user interface conventions as mouse and keyboard events.

The ScrollableControl Class


The ScrollableControl class provides functionality for objects that need the capability to
scroll, either horizontally or vertically. It includes only a few properties that aren’t inherited
from Control that are related to determining whether scrollbars are visible and how to handle
auto-scrolling. There is generally not much reason to directly inherit from the
ScrollableControl class alone.

The ContainerControl Class


Container controls are controls that can function as containers for other controls. A form
is a good example of this because forms typically contain many other controls. The
ContainerControl class provides the entire code infrastructure necessary to manage focus on
controls. For instance, the ActiveControl property returns (or sets) a reference to the currently
active control inside the container. The ContainerControl class also implements support for
parent and child form referencing: The ParentForm property gets or sets the form that the con-
tainer belongs to.

Visual Characteristics of Forms


When you add a form to a project, you will likely want to stipulate how the form will look to
the end user. This section examines the various ways you can alter and affect a form’s appear-
ance.
Figure 5.5 shows the different pieces of a form or window that are discussed in this section.

Setting Form Modality


A modal form is a form that requires the user to close it before continuing to interact with the
rest of the application. A modeless form, on the other hand, allows the user to continue inter-
acting with any of the other windows that the application is currently displaying while the form
is open.
Forms, Menus, and Controls
93
CHAPTER 5

Minimize Box
Title Bar

Control Box Close Box


Main Menu

Form Interior/Client Area

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()

Creating MDI Forms 5


FORMS, MENUS,
AND CONTROLS

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

TABLE 5.2 Form Properties for MDI Management


Property Description
IsMdiChild A Boolean value that indicates whether the form is a child to a parent
MDI form.
IsMdiContainer A Boolean value that indicates whether the form is the parent form in
an MDI application.
MdiChildren An array of individual form references, each of which is a child form
to the current MDI parent form.
MdiParent A form reference that indicates the MDI parent form of the current
form.

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.

LISTING 5.1 An Example of an MDI Application


Public Class Form1
Inherits System.Windows.Forms.Form

Private childForm1 As Form


Private childForm2 As Form
Private childForm3 As Form

#Region “ Windows Form Designer generated code “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call

‘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

LISTING 5.1 Continued


childForm1.MdiParent = Me
childForm1.Text = “Child Form #1”
childForm2 = New Form()
childForm2.MdiParent = Me
childForm2.Text = “Child Form #2”
childForm3 = New Form()
childForm3.MdiParent = Me
childForm3.Text = “Child Form #3”

‘All of our MDI props have been set; go ahead and


‘display the forms.
childForm1.Show()
childForm2.Show()
childForm3.Show()

End Sub

‘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

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()

‘Form1

Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(512, 445)
5
FORMS, MENUS,

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

LISTING 5.1 Continued


End Sub

#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.

TABLE 5.3 The MdiLayout Enumeration


Name Description
ArrangeIcons Arranges all the child form icons inside the MDI parent form.
Cascade Cascades all the child forms inside the MDI parent form.
TileHorizontal Horizontally tiles all the child forms inside the MDI parent form.
TileVertical Vertically tiles all the child forms inside the MDI parent form.
Forms, Menus, and Controls
97
CHAPTER 5

Controlling Form Position and State


When you create a form, you have a few options for how to position the form. You use the
FormStartPosition property to specify where you want the form displayed. Typically, you
would set this property after instantiating the form but before displaying it. This property
accepts the FormStartPosition enumeration shown in Table 5.4.

TABLE 5.4 The FormStartPosition Enumeration

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.

TABLE 5.5 The FormWindowState Enumeration


Name Description
Maximized Resizes the form to a maximized window.
Minimized Resizes the form to a minimized window.
Normal Resizes the form to its default size, based on its Size property values.

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.

TABLE 5.6 The FormBorderStyle Enumeration


Name Description
Fixed3D Creates a thick 3D border on all four edges of the window. The
border appears beveled on the inside of the form as well as on the
outside. The window is not resizable.
FixedDialog Creates a thick 3D border on all four edges of the window. There
is no beveling on the inside of the form. In other words, the border
seems to sit at the same level as the interior area of the form. The
window is not resizable.
FixedSingle Creates a thin border on all four edges of the window that is flat in
aspect. The window is not resizable.
FixedToolWindow Creates a tool window border on all four edges of the window.
Tool windows have smaller title bars than normal windows, and
they do not support Minimize or Maximize buttons. The window is
not resizable.
None Creates a borderless window.
Sizable Creates a resizable window with normal borders on all four edges.
SizableToolWindow Creates a resizable tool window border on all four edges of the
window.

Creating Minimize, Maximize, and Restore Buttons


Each window or form can have a minimize box, maximize box, restore box, and control box.
You specify whether to include these boxes in your form’s title bar by using the ControlBox,
MaximizeBox, and MinimizeBox properties. Each of these properties is a Boolean value used to
set or query for a form’s behavior with regard to these boxes. The restore box appears automat-
ically any time either MaximizeBox or MinimizeBox is set to true. Setting ControlBox to true
displays the control box as well as a close box.

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:

Public Class Form1


Inherits System.Windows.Forms.Form

#Region “ Windows Form Designer generated code “

Private Sub TrackBar1_Scroll(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles TrackBar1.Scroll
Me.Opacity = TrackBar1.Value * .01
Me.Text = TrackBar1.Value
Me.Refresh()
End Sub
End Class

Suggestions for Further Exploration


➲ The Form object can automatically scale its size based on the font assigned to it. You can
investigate the AutoScale and AutoScaleBaseSize properties to learn more; these prop- 5
erties can be particularly useful if you want to allow users to change the font used on a
FORMS, MENUS,
AND CONTROLS

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

Using the Clipboard


The System.Windows.Forms namespace provides Clipboard functionality—that is, the tradi-
tional cut, copy, and paste functions. The Clipboard class abstracts this functionality via two
methods: GetDataObject and SetDataObject. Here are their declarations:
Public Shared Function GetDataObject() As IdataObject

‘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)

Pushing Data onto the Clipboard


The SetDataObject method allows you to place data onto the Clipboard. There are two differ-
ent implementations of this method. The second overloaded version allows you to specify,
through a Boolean value, whether the data copied onto the Clipboard should remain on the
Clipboard even after the application that placed it there has exited. A true value tells the
Clipboard to keep the data around until it is explicitly flushed or cleared from the Clipboard. A
value of false indicates that the data will be removed from the Clipboard as soon as the call-
ing application has finished. The first overloaded version does not give you the option of per-
sisting data onto the Clipboard past the lifetime of the calling application; the data placed
through that method is always cleared upon termination of the application.
You can easily copy the text selected in a text box to the Clipboard, as in the following
example:
Clipboard.SetDataObject(myTextBox.SelectedText)

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.

Reading Data from the Clipboard


After items are placed on the Clipboard, you use the GetDataObject method to retrieve them.
The data is returned as an instance of IDataObject. IDataObject is an interface, defined in the
System.Windows.Forms namespace, that allows applications to store data in many different for-
mats. This is an essential attribute for a global, universal concept like the Clipboard, which has
Forms, Menus, and Controls
101
CHAPTER 5

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.

TABLE 5.7 Shared Properties of the DataFormats Class


Property Description
Bitmap Specifies Windows bitmap format.
CommaSeparatedValue Specifies comma-separated values format.
Dib Specifies Windows device-independent bitmap format.
Dif Specifies Windows data interchange format.
EnhancedMetafile Specifies Windows enhanced metafile format.
FileDrop Specifies Windows file drop format.
Html Specifies HTML text format.
Locale Specifies locale format (that is, Windows culture).
MetafilePict Specifies the Windows metafile format.
OemText Specifies original equipment manufacturer (OEM) text format.
Palette Specifies Windows palette format.
PenData Specifies pen data format (that is, pen strokes delivered from
handwriting software).
Riff Specifies the audio format resource interchange file format.
Rtf Specifies rich text format.
Serializable Specifies serializable Windows Forms format.
StringFormat Specifies string format.
SymbolicLink Specifies Windows symbolic link format.
Text Specifies ANSI standard text format. 5
Tiff Specifies tagged image file format.
FORMS, MENUS,
AND CONTROLS

UnicodeText Specifies Windows Unicode text format.


WaveAudio Specifies wave audio format.
Working with the .NET Namespaces
102
PART II

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()

‘DataFormats.Text specifies that we are interested in text data


‘This is used in conjunction with the GetDataPresent to determine
‘if the clipboard data matches our expectations (text)
If iData.GetDataPresent(DataFormats.Text) Then
‘If it does, we can copy it into our text box’s Text property
textBox2.Text = iData.GetData(DataFormats.Text)
End If

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.

Suggestions for Further Exploration


➲ Research the DataFormats and DataFormats.Format classes to see how you would
describe a new data format to the Clipboard class. Like most things in the .NET
Framework, the ability to use the Clipboard is restricted based on security policies. It is
turned on by default. For more information, see the .NET Framework Software
Development Kit documentation on the default security policy.
Forms, Menus, and Controls
103
CHAPTER 5

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.

Adding a Main Menu to a Form


Before you associate a main menu with a form, you first have to create an object instance of
the menu. The code to do this is straightforward:
Dim appMenu As MainMenu = New MainMenu()

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”)

‘or, if we needed a reference to the Open item...


Dim openMenuItem As MenuItem = fileMenu.MenuItems.Add(“&Open”)
The ampersand (&) included in the string “&Open” tells the Form Designer that it should assign
a mnemonic, in this case the letter O, to the menu item you are adding. A mnemonic is used to
access the menu from the keyboard: You hold down the Alt key and then press the mnemonic
of the menu or menu item you want to access. In this way, you can quickly select the corre-
sponding menu. You can programmatically determine the mnemonic for any menu item by
looking at the MenuItem.Mnemonic property, which is read-only. The mnemonic is set during
the construction of the menu item (for example, through the Add method on the MenuItems
collection).
An important point is that the MenuItems collection is actually just a property on the Menu class
(and thus the MainMenu, MenuItem, and ContextMenu classes that inherit from the Menu class).
The MenuItems property returns the data type Menu.MenuItemCollection. Note the distinction
that MenuItems is a property that returns an instance of MenuItemsCollection, which is a
class.

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.

LISTING 5.2 Creating a Simple Main Menu


Public Class Form1
Inherits System.Windows.Forms.Form

Private appMenu As MainMenu


Private fileMenu As MenuItem
Private editMenu As MenuItem
Private windowMenu As MenuItem
Private helpMenu As MenuItem

#Region “ Windows Form Designer generated code “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call


appMenu = New MainMenu()

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

‘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 5
End If
FORMS, MENUS,
AND CONTROLS

MyBase.Dispose(disposing)
End Sub

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container
Working with the .NET Namespaces
106
PART II

LISTING 5.2 Continued


‘NOTE: The following procedure is required by the Windows Form Designer
‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()

‘Form1

Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(294, 183)
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog
Me.MaximizeBox = False
Me.MinimizeBox = False
Me.Name = “Form1”
Me.Text = “Form1”

End Sub

#End Region

Private Sub Form1_Load(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles MyBase.Load

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

Creating Context Menus


Context, or pop-up, menus are used to display menu items that are specific to a certain form
element. You access a context menu by-right clicking on an object such as a text box or the
client area of a form. For example, most users expect to be able to right-click on a text box to
access the cut, copy, and paste commands that are typically found in an Edit menu. Context
menus allow programmers to target specific menu-based functionality and then deliver that
functionality when and where the user expects it. This can be a powerful way to make an
application more intuitive and make users more productive.
Access to context menus is provided through the ContextMenu class, which is similar in struc-
ture to the MainMenu class described in the preceding section. To create a context menu, dimen-
sion a new ContextMenu object, as follows:
Dim formContextMenu as New ContextMenu()

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

To see an example of a context menu, look ahead to Figure 5.13.

Managing MDI Applications and Menus


A few pieces of functionality are exposed on the MenuItem class, expressly for the purpose of
managing menus in an MDI-based application. There are a few established standards for dis-
playing menus in MDI applications. First, MDI applications usually display a Window menu
on the main form. The Window menu usually contains a list of the currently open child forms,
allowing a user to select one of them from the menu to make it active. This menu usually also
provides commands for arranging the child forms (see the discussion of the MdiLayout enu-
meration earlier in this chapter, in the section “Creating MDI Forms”). The MenuItem class
provides intrinsic support for these lists of child forms, with the MdiList property. MdiList is
5
FORMS, MENUS,
AND CONTROLS

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.

Working with Menu Items


When a top-level menu is in place, you can start adding submenus or items to it. You create
submenus by using the same process you use to add items to a main menu. After creating sub-
menus, you can worry about their various visual decorations such as separator lines and check
marks.

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

Figure 5.9 shows a cascading menu in action.


You can continue nesting menus in this fashion until you decide to create the bottom of the
menu tree, where the user-selectable commands are. Obviously, for usability reasons, you
should avoid nesting levels more than a few deep.

Adding Checkmarks, Shortcuts, and Separator Bars to


Menus 5
FORMS, MENUS,
AND CONTROLS

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

TABLE 5.8 Shortcut Enumeration

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

Values range from Shift+F1 through Shift+F12.


ShiftIns Creates a shortcut with the key combination
Shift+Insert.
Working with the .NET Namespaces
112
PART II

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.

Working with Radio Checks


The MenuItem class supports a form of mutually exclusive checking of menu items. You use
this feature when presenting a group of menu items in which only one option can be selected at
any time (similar to radio button controls). First, you set the RadioChecked property to true
for each menu item that will participate in this mutually exclusive group. Then, when you set
the Checked property, it shows up as a radio button graphic instead of a checkmark. The fol-
lowing code illustrates this concept:
With appMenu
fileMenu = .MenuItems.Add(“&File”)
fileMenu.MenuItems.Add(“&Open...”)
fileMenu.MenuItems.Add(“&Save”)
fileMenu.MenuItems(1).Shortcut = Shortcut.CtrlS
fileMenu.MenuItems.Add(“Save As...”)
fileMenu.MenuItems.Add(“-”)
fileMenu.MenuItems.Add(“View As Web Pa&ge”)
fileMenu.MenuItems(4).RadioCheck = True
fileMenu.MenuItems(4).Checked = True
fileMenu.MenuItems.Add(“View As &HTML”)
fileMenu.MenuItems(4).RadioCheck = True
fileMenu.MenuItems.Add(“-”)
fileMenu.MenuItems.Add(“&Close”)
fileMenu.MenuItems.Add(“E&xit”)
Forms, Menus, and Controls
113
CHAPTER 5

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.

Setting Default Menu Items


You can specify a menu item as a default. This menu item shows up in bold, and if a user dou-
ble-clicks a menu that has a default item defined, that item is automatically selected.
The following code sets the Save item as the default item:
With appMenu
fileMenu = .MenuItems.Add(“&File”)
fileMenu.MenuItems.Add(“&Open...”)
fileMenu.MenuItems.Add(“&Save”)
fileMenu.MenuItems(1).DefaultItem = True
fileMenu.MenuItems(1).Shortcut = Shortcut.CtrlS
fileMenu.MenuItems.Add(“Save As...”)
fileMenu.MenuItems.Add(“-”)
fileMenu.MenuItems.Add(“View As Web Pa&ge”)
fileMenu.MenuItems(4).RadioCheck = True
fileMenu.MenuItems(4).Checked = True
fileMenu.MenuItems.Add(“View As &HTML”)
fileMenu.MenuItems(4).RadioCheck = True 5
fileMenu.MenuItems.Add(“-”)
FORMS, MENUS,
AND CONTROLS

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 shows how this code looks when it is executed.

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.

TABLE 5.9 The MenuMerge Enumeration


Name Description
Add Causes the referenced MenuItem object to be added to the collection of
existing MenuItem objects in a merged menu.
MergeItems Causes all submenu items of the referenced MenuItem to be merged with
those of the merged menu; the items are merged in their same positions.
Remove Causes the referenced MenuItem to not be included in a merged menu.
Replace Causes the referenced MenuItem object to replace an existing MenuItem
object in the same position in a merged menu.
Forms, Menus, and Controls
115
CHAPTER 5

Creating OwnerDraw Menu Items


Normally, when a menu is selected, Windows paints it to the screen. This means that all the
menus have a standard way that they are drawn in all applications and that they have a stan-
dard look and feel. If you want to draw a highly specialized or stylized menu, you can elect to
draw the menu yourself. This is referred to as an owner draw menu item, and you can set it for
individual menu items by using the OwnerDraw property. OwnerDraw is a Boolean property: If
it is set to true, the menu, before being displayed, is routed through code that you supply to
draw whatever you like for the menu; if it is set to false, Windows draws the menu.
The MenuItem.DrawItem event is fired if you have set a menu item’s OwnerDraw property to
true. Inside the DrawItem event, you can design a menu that looks any way you want it to
look, subject only to the capabilities and limitations of the Graphics object. (See the
System.Drawing namespace, and Chapter 9, “Drawing Functions,” for more information
on the Graphics object.)
Here is how the MenuItem.DrawItem event routine is defined:
Public Delegate Sub DrawItemEventHandler(ByVal sender As Object, _
ByVal e As DrawItemEventArgs)

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.

TABLE 5.10 Properties of the DrawItemEventArgs Class


Property Description
BackColor Specifies the background color of the menu item being drawn.
Bounds Specifies the bounded rectangle that represents the bounds of the menu item
being drawn.
Font Specifies the font of the menu item being drawn.
ForeColor Specifies the foreground color of the menu item being drawn.
Graphics Specifies an instance of a graphics surface on which the menu item will be
drawn.
Index Specifies the index value of the menu item being drawn. 5
FORMS, MENUS,

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.

Suggestions for Further Exploration


➲ We have talked about the use of the MergeMenu method to consolidate one menu’s items
onto another menu. Investigate the CloneMenu method to see what options it provides for
directly copying menu items.
➲ The MenuItem class has an overloaded constructor. We have only implied a parameterless
constructor up to this point, but there is a constructor that actually accepts a total of eight
parameters! Investigate the .NET Framework documentation and think about where it
might make sense to employ the more verbose constructor.
➲ Try mimicking Visual Studio .NET’s menus by utilizing the owner-draw capabilities of
the MenuItem class. You will need to understand how to draw graphics to the screen by
using the Graphics class. You will also need to figure out a way to make mnemonics and
accelerators work.

Handling Menu Events


When the basic menu structures are in place, it is time to write the code that reacts to a user
selecting one of those menu items. This involves crafting event handles for the menu items pre-
viously declared and writing code to react to those events.
Forms, Menus, and Controls
117
CHAPTER 5

Menu Items and Event Handlers


There are two different ways to link menu items to a piece of code that will run when the
menu item is selected. The first way is to use the click event that the MenuItem class exposes.
The click event is fired whenever a menu item is selected. Note that although the event is
called click, it is fired regardless of how the item is selected. For instance, a user navigating
to a menu item by using the keyboard and then pressing the Enter key causes this event to fire,
as does using a menu item’s shortcut key, mnemonic, and so on. The click event is only fired
for bottom-level menu items; that is, if the menu item is a parent to another menu, the click
event is not fired.
If you are going to use the click event, you need to declare the MenuItem object by using the
WithEvents keyword, like this:

Dim fileMenuClose WithEvents As MenuItem

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

‘Code that reacts to the popup event goes here...


Me.Close

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...”)

you can also write this: 5


FORMS, MENUS,
AND CONTROLS

fileMenu.MenuItems.Add(“&Open...”, New EventHandler(AddressOf ourEvent))

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.

Reacting to the Popup Event


Both the ContextMenu class and the MenuItem class have a Popup event that is fired just before
the menu is displayed. Any time you need to programmatically manipulate the contents or
appearance of a menu just before it is displayed, you can attach code to the Popup event. The
Popup event is particularly useful with context menus. Typically, you use it to customize the
content of the context menu based on which control caused the menu to be displayed. Rather
than create a separate object for each control that needs a context menu, you can add the
appropriate items to the menu inside the Popup event, depending on which control was respon-
sible for initiating the menu. Reacting to the Popup event is identical to attaching the click
event. You first declare the context menu by using the WithEvents directive, like this:
Dim ctrlContextMenu WithEvents As New ContextMenu()

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

‘Code that reacts to the popup event goes here...

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

‘Dimension the context menu


Private WithEvents appContextMenu As New ContextMenu()

#Region “ Windows Form Designer generated code “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call


SetupContextMenu()

End Sub

‘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() 5
End If
FORMS, MENUS,
AND CONTROLS

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

LISTING 5.3 Continued


‘Required by the Windows Form Designer
Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.TextBox1 = New System.Windows.Forms.TextBox()
Me.RichTextBox1 = New System.Windows.Forms.RichTextBox()
Me.SuspendLayout()

‘TextBox1

Me.TextBox1.Location = New System.Drawing.Point(16, 32)
Me.TextBox1.Name = “TextBox1”
Me.TextBox1.Size = New System.Drawing.Size(256, 20)
Me.TextBox1.TabIndex = 0
Me.TextBox1.Text = “TextBox1”

‘RichTextBox1

Me.RichTextBox1.Location = New System.Drawing.Point(16, 72)
Me.RichTextBox1.Name = “RichTextBox1”
Me.RichTextBox1.Size = New System.Drawing.Size(256, 56)
Me.RichTextBox1.TabIndex = 1
Me.RichTextBox1.Text = “RichTextBox1”

‘Form2

Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(292, 149)
Me.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.TextBox1, _
Me.RichTextBox1})
Me.Name = “Form2”
Me.Text = “Context Menus”
Me.ResumeLayout(False)

End Sub

#End Region

Private Sub Form2_Load(ByVal sender As System.Object, _


ByVal e As System.EventArgs) _
Handles MyBase.Load
Forms, Menus, and Controls
121
CHAPTER 5

LISTING 5.3 Continued


End Sub

Private Sub SetupContextMenu()


‘Add any initialization after the InitializeComponent() call
appContextMenu = New ContextMenu()

Dim currItem As MenuItem

TextBox1.ContextMenu = appContextMenu
RichTextBox1.ContextMenu = appContextMenu
End Sub

Protected Sub menuCut_Click(ByVal sender As System.Object, ByVal e As _


System.EventArgs)
If TypeOf Me.ActiveControl Is TextBox Then
Dim currControl As TextBox = CType(Me.ActiveControl, TextBox)
‘ Put selected text on Clipboard.
Clipboard.SetDataObject(currControl.SelectedText)
currControl.SelectedText = “”
ElseIf TypeOf Me.ActiveControl Is RichTextBox Then
Dim currControl As RichTextBox = _
CType(Me.ActiveControl, RichTextBox)
Clipboard.SetDataObject(currControl.SelectedText)
currControl.SelectedText = “”
End If

End Sub

Protected Sub menuCopy_Click(ByVal sender As System.Object, ByVal e As _


System.EventArgs)
If TypeOf Me.ActiveControl Is TextBox Then
Dim currControl As TextBox = CType(Me.ActiveControl, TextBox)
‘ Put selected text on Clipboard.
Clipboard.SetDataObject(currControl.SelectedText)
ElseIf TypeOf Me.ActiveControl Is RichTextBox Then
Dim currControl As RichTextBox = _
CType(Me.ActiveControl, RichTextBox)
Clipboard.SetDataObject(currControl.SelectedText)
End If
End Sub
5
FORMS, MENUS,
AND CONTROLS

Protected Sub menuPaste_Click(ByVal sender As System.Object, ByVal e As _


System.EventArgs)
Dim clipData As IDataObject = Clipboard.GetDataObject()
‘DataFormats.Text specifies that we are interested in text data
Working with the .NET Namespaces
122
PART II

LISTING 5.3 Continued


‘This is used in conjunction with the GetDataPresent to determine
‘if the clipboard data matches our expectations (text)
If clipData.GetDataPresent(DataFormats.Text) Then
‘If it does, we can copy it into our text box’s Text property
If TypeOf Me.ActiveControl Is TextBox Then
CType(Me.ActiveControl, TextBox).SelectedText = _
clipData.GetData(DataFormats.Text)
ElseIf TypeOf Me.ActiveControl Is RichTextBox Then
CType(Me.ActiveControl, RichTextBox).SelectedText = _
clipData.GetData(DataFormats.Text)
End If
End If
End Sub

Protected Sub PopupEventHandler(ByVal sender As System.Object, _


ByVal e As _
System.EventArgs) Handles appContextMenu.Popup
‘ Clear all previously added MenuItems.
appContextMenu.MenuItems.Clear()

Dim currItem As MenuItem

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

‘If the context menu is being displayed by the RichTextBox control,


‘we will add a sep bar and a Load File item to the existing items
If appContextMenu.SourceControl Is RichTextBox1 Then

‘Create a new menu item holding the Load File text,


‘ and pointing to our event handler to
actually perform the load.
Forms, Menus, and Controls
123
CHAPTER 5

LISTING 5.3 Continued


Dim loadItem As New MenuItem(“&Load File...”, _
New EventHandler(AddressOf _
menuLoad_Click))

‘ Add the MenuItem to display for the PictureBox.


appContextMenu.MenuItems.Add(“-”)
appContextMenu.MenuItems.Add(loadItem)

End If
End Sub

Protected Sub menuLoad_Click(ByVal sender As System.Object, ByVal e As _


System.EventArgs)
‘When selected, this command should launch a dialog to
‘allow users to navigate to, and select, a file. To do
‘this, we need to show an OpenFileDialog object
Dim openFile As OpenFileDialog = New OpenFileDialog()

‘Because the ultimate intent here is to load the file


‘into the RichTextBox1, we will set the dialog to filter
‘out anything but rich text files (.rtf)
openFile.Filter = “RTF files (*.rtf)|*.rtf”

‘Show the open file dialog


openFile.ShowDialog()

‘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 shows the result of right-clicking the RichTextBox1 control.


Working with the .NET Namespaces
124
PART II

FIGURE 5.13
A dynamic context menu.

Detecting Menu Selection


A useful menu is the Select event, which is fired when a menu item becomes highlighted.
This can happen when the user hovers the mouse over the menu item or when the user navi-
gates to the menu item by using the keyboard. Typically, the Select event precedes a click
event. Figure 5.14 shows the menu item Save highlighted. If the user hasn’t actually clicked on
Save or pressed the Enter key, the only MenuItem event you would expect to have fired is the
Select event.

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.

Common Control Properties, Methods, and Events


Most visual controls in the .NET Framework inherit directly from the Control class.
Therefore, to get a good idea of the base set of properties supported by most controls, you can
look back at the base Control class; examining its public instance properties gives you a good
feel for the kind of default functionality provided by most controls. Table 5.11 shows a sam-
pling of the most important properties in the Control class.

TABLE 5.11 Control Class Properties

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

TABLE 5.11 Continued


Property Description
Height An integer that specifies the height of the control.
ImeMode Specifies the input method editor mode used by the control.
Left Specifies the position of the control’s left edge.
Name Specifies the name of the control.
Parent Represents the parent container of this control.
Size Specifies the height and width of the control.
TabIndex Specifies where in the tab order the control is located.
TabStop A Boolean property that indicates whether the control can be given
focus as the result of the Tab key being pressed.
Tag An object instance that can be used to associate data to the control.
Text Specifies the text associated with the control.
Top Specifies the top edge of the control.
Visible A Boolean property that indicates whether the control is visible.
Width Specifies the width of the control.

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.)

TABLE 5.12 Control Class Methods

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

TABLE 5.12 Ccontinued


Method Description
GetContainerControl Returns an interface (IContainerControl) that represents the
parent control to this control.
GetNextControl Returns a control instance of the next control in the tab order.
Refresh Invalidates the control’s client area, forcing a repaint of the
control.
Scale Scales the control per the specified ratio.
Select Activates the control.
SendToBack Causes the control’s z-order to change to the back.
Show Explicitly sets the control’s Visible property to true.
Update Forces a repaint on any invalidated areas of the control.

TABLE 5.13 Control Class Events

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

TABLE 5.13 Continued


Event Description
MouseDown Fires when a mouse button is held down while the mouse
pointer is inside the control’s interior.
MouseEnter Fires when the mouse pointer enters the control’s interior.
MouseLeave Fires when the mouse pointer leaves the control’s interior.
MouseUp Fires when a mouse button is released while the mouse
pointer is inside the control’s interior.
MouseWheel Fires when the mouse wheel is moved while the mouse
pointer is inside the control’s interior.
Move Fires when the control is repositioned.
Paint Fires when the control is drawn to the screen.
Resize Fires when the control is resized.
TextChanged Fires when the control’s Text property has been changed.
Validated Fires when the control is done validating.
Validating Fires when the control is in the process of validating.

Handling Form Resizing


A common user interface dilemma programmers face is what to do with controls on a form if a
user resizes the form. In previous versions of Visual Basic, you handled this by writing a lot of
messy code in the paint and resize events to explicitly reposition each control that needed to be
repositioned. Did you notice the Anchor property in Table 5.11? This represents intrinsic sup-
port for control positioning based on form resizing.
The Anchor property can be set to stick a control at a left, right, top, or bottom position (or any
combination of those) during form resizing. It uses the AnchorStyle enumeration to specify
the desired result (see Table 5.14).

TABLE 5.14 The AnchorStyle Enumeration


Name Description
Bottom Anchors the control to the bottom edge of the form.
Left Anchors the control to the left edge of the form.
None Removes any anchors on the control.
Right Anchors the control to the right edge of the form.
Top Anchors the control to the top edge of the form.
Forms, Menus, and Controls
129
CHAPTER 5

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.

Here is the code to set the OK button’s anchors:


Me.Button1.Anchor = ((System.Windows.Forms.AnchorStyles.Top Or _
System.Windows.Forms.AnchorStyles.Bottom) Or _
System.Windows.Forms.AnchorStyles.Left)
5
FORMS, MENUS,
AND CONTROLS

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.

TABLE 5.15 DockStyle Enumeration

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.

Windows Forms and ActiveX Controls


ActiveX controls are not directly supported by Windows Forms, but there is a mechanism for
making them function in the .NET Framework: the AxHost class. AxHost is a special wrapper
class that is designed to encapsulate an existing ActiveX control and allow it to function com-
pletely inside the Windows Forms universe.
Essentially, the AxHost wrapper communicates with the hosted ActiveX control, and it acts as
an intermediary between the Windows Form and the ActiveX control (see Figure 5.20). 5
The AxHost class, through its inheritance from the Control class, provides typical properties
FORMS, MENUS,
AND CONTROLS

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.)

Creating Your Own Controls


Although the .NET Framework and Visual Studio .NET ship with a huge assortment of user
interface controls for use with Windows Forms applications, your business requirements are
likely to sometimes dictate that a new control is needed—one that specifically satisfies your
exact design goals. You might be able to create this control from existing control functionality,
or you might need to create something entirely new.
With the .NET Framework, there are two principal ways to create a new control.
One way involves custom-coding a control class that inherits directly from the
System.Windows.Forms.Control class. The other way involves creating a new class that inher-
its from the System.Windows.Forms.UserControl class. We will distinguish between the two
by calling controls created via the first method custom controls and controls created via the
second method composite controls (you’ll understand why we call these composite controls
very shortly).
Forms, Menus, and Controls
133
CHAPTER 5

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.

Creating a Custom Control


The first step in creating a custom control is to implement the base set of properties, methods,
and events that controls need to support. You do this quite simply by creating your own class
and then inheriting from the Control class, like this:
Public Class CustomControl
Inherits System.Windows.Forms.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.

Overriding the OnPaint Event


One of the things that needs to be planned carefully is how a custom control will represent
itself on the screen. Because a control is, by definition, a visual construct, you need to write
logic that explicitly handles the painting of the control onto a form. You do this by overriding
the OnPaint event.
The OnPaint event provides one incoming parameter—an instance of the PaintEventArgs
class—which can be used to get a reference to a Graphics object. The Graphics object is the
primary instrument for drawing the control to the screen. You need to provide it, along with
what it should draw, how it should draw it, and where it should draw it. With the Graphics
object, for instance, it is very easy to write code that draws text to the screen:
Protected Overrides Sub OnPaint(ByVal pe As _
System.Windows.Forms.PaintEventArgs)

MyBase.OnPaint(pe)

Dim rect As RectangleF = New RectangleF(ClientRectangle.X, _


ClientRectangle.Y, ClientRectangle.Width, _
ClientRectangle.Height)

‘Draw the text onto the control’s visible surface


5
pe.Graphics.DrawString(“Hello, world!”, Font, _
FORMS, MENUS,
AND CONTROLS

New SolidBrush(ForeColor), rect)

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.

Adding New Properties, Methods, and Events


To add a property to a custom control, you add a private reference to a variable that will hold
the property value, and then you add a property handler to the code, like this:
Public Class CustomControl1
Inherits System.Windows.Forms.Control

Private orientText As String

.
.
.

Public Property Orientation() As String

Get
Return orientText
End Get

Set(ByVal Value As String)


orientText = Value
End Set

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

Protected Overridable Sub OnOrientationChanged(ByVal e As EventArgs)


Invalidate()
RaiseEvent OrientationChanged(Me, e)
End Sub

The final step is to actually write the code that raises the event:
Public Property Orientation() As String

Get
Return orientText
End Get

Set(ByVal Value As String)


If orientText <> Value Then
OnOrientationChanged(New EventArgs())
End If
orientText = Value
End Set

End Property

Consuming a Custom Control


A custom control class must be compiled (into a dynamic link library [DLL]); it can then be
referenced just like any other control inside a Form object.
First, a reference is made to the control object, somewhere in the form’s declaration section:
Friend WithEvents TestControl As CustomControlLibrary.CustomControl

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.

Implementing a Custom Control 5


Listing 5.4 consolidates all the items we have talked about up to this point about custom con-
FORMS, MENUS,
AND CONTROLS

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.

LISTING 5.4 Constructing a Custom Control


Public Class CustomControl
Inherits System.Windows.Forms.Control

‘Sets up an event for dealing with changes


‘to the Orientation property
Public Event OrientationChanged(ByVal sender As Object, _
ByVal ev As EventArgs)

Private orientText As Integer

Public Enum OrientationMode


Horizontal = 0
Vertical = 1
End Enum

Public Sub New()


MyBase.New()
End Sub

Protected Overrides Sub OnPaint(ByVal pe As _


System.Windows.Forms.PaintEventArgs)
MyBase.OnPaint(pe)

‘Dim a SizeF struct to hold the size of the control region


Dim size As SizeF

‘Here is the text we want to display


Const TEXT_STRING As String = “Hello, World!”

‘To avoid clipping the text, we get its exact size in


‘a SizeF struct
size = pe.Graphics.MeasureString(TEXT_STRING, Font)

‘Based on the orientation mode in orientText, we either display


‘the text horizontally or vertically
Select Case orientText
Case OrientationMode.Horizontal
Forms, Menus, and Controls
137
CHAPTER 5

LISTING 5.4 Continued


‘We create our clipping rectangle here using
‘the overall position
‘of the control and the exact height and width necessary to
‘accommodate the text
Dim rect As RectangleF = New RectangleF(ClientRectangle.X, _
ClientRectangle.Y, _
size.Width, size.Height)

‘Paint the Text property on the control; since we have not


‘specified a vertical orientation in the DrawString method, it
‘will write out horizontally
pe.Graphics.DrawString(TEXT_STRING, Font, _
New SolidBrush(ForeColor), rect)

Case OrientationMode.Vertical
Dim textDirection As StringFormat = New StringFormat()

‘This enumeration allows us to specify text direction


‘inside of the DrawString method
textDirection.FormatFlags = _
StringFormatFlags.DirectionVertical

‘We create our clipping rectangle here using


‘the overall position
‘of the control and the exact height and width necessary to
‘accommodate the text (note that we have flipped the height
‘and width to account for the vertical display of the text)
Dim rect As RectangleF = New RectangleF(ClientRectangle.X, _
ClientRectangle.Y, _
size.Height, size.Width)

‘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

Public Property Orientation() As OrientationMode


5
FORMS, MENUS,

Get
AND CONTROLS

Return orientText
End Get
Working with the .NET Namespaces
138
PART II

LISTING 5.4 Continued


Set(ByVal Value As OrientationMode)
If orientText <> Value Then
‘Raise an event
OnOrientationChanged(New EventArgs())
End If

orientText = Value
End Set
End Property

Public Sub RestoreDefault()


orientText = OrientationMode.Horizontal
End Sub

Protected Overridable Sub OnOrientationChanged(ByVal e As EventArgs)


‘When the Orientation of the control has changed, we want
‘to invalidate the control to force a repaint
Invalidate()
RaiseEvent OrientationChanged(Me, e)
End Sub

End Class

Listing 5.5 shows the code for one possible form-based consumer of the CustomControl.

LISTING 5.5 Using a Custom Control


Public Class Form1
Inherits System.Windows.Forms.Form

#Region “ Windows Form Designer generated code “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call

End Sub

‘Form overrides dispose to clean up the component list.


Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
Forms, Menus, and Controls
139
CHAPTER 5

LISTING 5.5 Continued


If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
Friend WithEvents TextControl As CustomControlLibrary.CustomControl
Friend WithEvents RadioButton1 As System.Windows.Forms.RadioButton
Friend WithEvents RadioButton2 As System.Windows.Forms.RadioButton
‘Friend WithEvents TextBox1 As System.Windows.Forms.TextBox

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.RadioButton1 = New System.Windows.Forms.RadioButton()
Me.TextControl = New CustomControlLibrary.CustomControl()
Me.RadioButton2 = New System.Windows.Forms.RadioButton()
Me.SuspendLayout()

‘RadioButton1

Me.RadioButton1.Location = New System.Drawing.Point(204, 16)
Me.RadioButton1.Name = “RadioButton1”
Me.RadioButton1.Size = New System.Drawing.Size(104, 16)
Me.RadioButton1.TabIndex = 1
Me.RadioButton1.Text = “Horizontal”

‘TextControl

Me.TextControl.Location = New System.Drawing.Point(16, 16)
Me.TextControl.Name = “TextControl”
Me.TextControl.Orientation = _
CustomControlLibrary.CustomControl.OrientationMode.Horizontal
Me.TextControl.Size = New System.Drawing.Size(156, 96)
5
FORMS, MENUS,

Me.TextControl.TabIndex = 0
AND CONTROLS


‘RadioButton2

Me.RadioButton2.Location = New System.Drawing.Point(204, 36)
Working with the .NET Namespaces
140
PART II

LISTING 5.5 Continued


Me.RadioButton2.Name = “RadioButton2”
Me.RadioButton2.Size = New System.Drawing.Size(104, 16)
Me.RadioButton2.TabIndex = 1
Me.RadioButton2.Text = “Vertical”

‘Form1

Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(322, 159)
Me.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.RadioButton2, Me.RadioButton1, Me.TextControl})
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog
Me.MaximizeBox = False
Me.MinimizeBox = False
Me.Name = “Form1”
Me.Text = “CustomControlHost”
Me.ResumeLayout(False)

End Sub

#End Region

Private Sub RadioButton1_CheckedChanged(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles RadioButton1.CheckedChanged
If RadioButton1.Checked Then
Me.TextControl.Orientation = _
CustomControlLibrary.CustomControl.OrientationMode.Horizontal
End If
End Sub

Private Sub RadioButton2_CheckedChanged(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles RadioButton2.CheckedChanged
If RadioButton2.Checked Then
Me.TextControl.Orientation = _
CustomControlLibrary.CustomControl.OrientationMode.Vertical
End If

End Sub
End Class

Figure 5.21 shows TextControl in action.


Forms, Menus, and Controls
141
CHAPTER 5

FIGURE 5.21
The TextControl custom control.

Creating a Composite Control


The UserControl class is a shell class that enables you to mix and match the functionality of
existing .NET Framework controls to create something unique and specially suited to the work
at hand.
User controls allow you to group other controls together to synthesize a new control. Consider
a login form that uses a login ID textbox and a password textbox, along with a command but-
ton labeled Login. If you find yourself using these controls in this combination over and over
again, it might make sense to simply compile them together as one control.
One of the benefits of creating a UserControl class is that it is fully enabled in the Visual
Studio .NET IDE: It shows up in your toolbox (after you have referenced it from your project),
and you can drag it from the toolbox onto a form.
To create a composite control, you first create a class that inherits from UserControl:
Public Class LoginControl
Inherits System.Windows.Forms.UserControl

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

Me.Controls.AddRange(New System.Windows.Forms.Control() {Me.Button1, _


Me.TextBox2, Me.Label2, Me.Label1, Me.TextBox1})
Working with the .NET Namespaces
142
PART II

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.

Learning by Example: The EventLog Control


The EventLog control application demonstrates how to create a composite control and then use
it in a simple form-based application. In this case, you are creating a visual control based on a
label control, a listbox control, a button control, and an EventLog component. In essence, you
are giving the EventLog component a visual dimension. The EventLogControl object allows
you to specify an event log in its Log property; it then shows you the log entries inside the
ListBox control. This section also shows the actual test application, which shows an example
of consuming the EventLogControl object. You can specify a log file by selecting the File
menu and then the Log menu.

Key Concepts Covered


The EventLog control application demonstrates the following:
• Creating a composite control by inheriting from the UserControl class
• Basic menu operations with MenuItem objects
• Handling custom properties, events, and methods inside a composite control
• Instantiating and working with a control on a Windows Form
To test the new control, you must first compile the composite control code (as shown in Listing
5.6) into a DLL. Then, you need to compile the code in Listing 5.7 to see how to use the previ-
ously created control. Note that you have to add a reference to the control DLL in order to use
it. After you have done this, you should see the control show up in the toolbox region of your
Visual Studio .NET IDE. Figure 5.22 shows the custom control in action, hosted in a Windows
Forms application.
Forms, Menus, and Controls
143
CHAPTER 5

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

‘The name of the targeted log


Private logSource As String

‘Max number of entries to hold in the listbox


Private maxCount As Int32

‘This is the event fired when the targeted log changes 5


Public Event LogSourceChanged(ByVal sender As Object, _
FORMS, MENUS,
AND CONTROLS

ByVal ev As EventArgs)
Working with the .NET Namespaces
144
PART II

LISTING 5.6 Continued


‘Our public enum for specifying the log to view
Public Enum LogType
Application = 0
System = 1
Security = 2
End Enum

#Region “ Windows Form Designer generated code “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call

‘Initialize the state of the control to look at the app log


EventLog1.Log = “Application”

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.Anchor = AnchorStyles.Right Or AnchorStyles.Bottom

Label1.Anchor = AnchorStyles.Left Or AnchorStyles.Top


End Sub

‘UserControl1 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
Forms, Menus, and Controls
145
CHAPTER 5

LISTING 5.6 Continued


Friend WithEvents ListEvents As System.Windows.Forms.ListBox
Friend WithEvents ButtonRefresh As System.Windows.Forms.Button
Friend WithEvents EventLog1 As System.Diagnostics.EventLog
Friend WithEvents Label1 As System.Windows.Forms.Label

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.Label1 = New System.Windows.Forms.Label()
Me.EventLog1 = New System.Diagnostics.EventLog()
Me.ListEvents = New System.Windows.Forms.ListBox()
Me.ButtonRefresh = New System.Windows.Forms.Button()
CType(Me.EventLog1, _
System.ComponentModel.ISupportInitialize).BeginInit()
Me.SuspendLayout()

‘Label1

Me.Label1.Location = New System.Drawing.Point(4, 4)
Me.Label1.Name = “Label1”
Me.Label1.Size = New System.Drawing.Size(128, 16)
Me.Label1.TabIndex = 2
Me.Label1.Text = “Application Log”

‘EventLog1

Me.EventLog1.SynchronizingObject = Me

‘ListEvents

Me.ListEvents.Location = New System.Drawing.Point(4, 24)
Me.ListEvents.Name = “ListEvents”
Me.ListEvents.Size = New System.Drawing.Size(352, 251)
Me.ListEvents.TabIndex = 0 5

FORMS, MENUS,
AND CONTROLS

‘ButtonRefresh

Me.ButtonRefresh.Location = New System.Drawing.Point(288, 280)
Me.ButtonRefresh.Name = “ButtonRefresh”
Working with the .NET Namespaces
146
PART II

LISTING 5.6 Continued


Me.ButtonRefresh.Size = New System.Drawing.Size(68, 20)
Me.ButtonRefresh.TabIndex = 1
Me.ButtonRefresh.Text = “Refresh”

‘EventLogControl

Me.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.Label1, Me.ButtonRefresh, Me.ListEvents})
Me.Name = “EventLogControl”
Me.Size = New System.Drawing.Size(364, 304)
CType(Me.EventLog1, System.ComponentModel.ISupportInitialize).EndInit()
Me.ResumeLayout(False)

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.

Public Property Log() As LogType


Get
Return logSource
End Get

Set(ByVal Value As LogType)

Dim logEnum As LogType


logSource = LogType.GetName(logEnum.GetType, Value)

‘Raise an event
OnLogSourceChanged(New EventArgs())

End Set
End Property

Public Property MaxEntries() As Int32


Get
Return maxCount
End Get

Set(ByVal Value As Int32)

Dim logEnum As LogType


maxCount = Value
Forms, Menus, and Controls
147
CHAPTER 5

LISTING 5.6 Continued


End Set
End Property

Protected Overridable Sub OnLogSourceChanged(ByVal e As EventArgs)


‘When the Orientation of the control has changed, we want
‘to invalidate the control to force a repaint
ListEvents.Items.Clear()
EventLog1.Log = logSource
Label1.Text = logSource & “ Log”

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

Dim count As Int32

count = 0

For Each entry In entriesCol


If count > maxCount Then Exit For

ListEvents.Items.Add(entry.TimeGenerated & “:: “ & entry.Message)


count = count + 1

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

Private Sub ButtonRefresh_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles ButtonRefresh.Click
ListEntries(EventLog1.Entries)
End Sub
End Class
Working with the .NET Namespaces
148
PART II

Listing 5.7 shows one example of how to use EventLogControl on a form.

LISTING 5.7 Code for the EventLogHost Control


Public Class CompositeControlForm
Inherits System.Windows.Forms.Form

#Region “ Windows Form Designer generated code “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call

End Sub

‘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
Friend WithEvents appMenu As System.Windows.Forms.MainMenu
Friend WithEvents fileMenu As System.Windows.Forms.MenuItem
Friend WithEvents fileMenuLog As System.Windows.Forms.MenuItem
Friend WithEvents fileMenuLogApp As System.Windows.Forms.MenuItem
Friend WithEvents fileMenuLogSec As System.Windows.Forms.MenuItem
Friend WithEvents fileMenuLogSys As System.Windows.Forms.MenuItem
Friend WithEvents fileMenuExit As System.Windows.Forms.MenuItem
Friend WithEvents GroupBox1 As System.Windows.Forms.GroupBox
Friend WithEvents EventLogControl1 As EventLogControl.EventLogControl

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Forms, Menus, and Controls
149
CHAPTER 5

LISTING 5.7 Continued

Me.fileMenuLogSys = New System.Windows.Forms.MenuItem()


Me.GroupBox1 = New System.Windows.Forms.GroupBox()
Me.appMenu = New System.Windows.Forms.MainMenu()
Me.fileMenu = New System.Windows.Forms.MenuItem()
Me.fileMenuLog = New System.Windows.Forms.MenuItem()
Me.fileMenuLogApp = New System.Windows.Forms.MenuItem()
Me.fileMenuLogSec = New System.Windows.Forms.MenuItem()
Me.fileMenuExit = New System.Windows.Forms.MenuItem()
Me.EventLogControl1 = New EventLogControl.EventLogControl()
Me.GroupBox1.SuspendLayout()
Me.SuspendLayout()

‘fileMenuLogSys

Me.fileMenuLogSys.Index = 2
Me.fileMenuLogSys.RadioCheck = True
Me.fileMenuLogSys.Text = “System”

‘GroupBox1

Me.GroupBox1.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.EventLogControl1})
Me.GroupBox1.Location = New System.Drawing.Point(4, 12)
Me.GroupBox1.Name = “GroupBox1”
Me.GroupBox1.Size = New System.Drawing.Size(424, 380)
Me.GroupBox1.TabIndex = 0
Me.GroupBox1.TabStop = False
Me.GroupBox1.Text = “Event Log”

‘appMenu

Me.appMenu.MenuItems.AddRange(New System.Windows.Forms.MenuItem()_
{Me.fileMenu})

‘fileMenu

Me.fileMenu.Index = 0
Me.fileMenu.MenuItems.AddRange(New System.Windows.Forms.MenuItem() _
{Me.fileMenuLog, Me.fileMenuExit}) 5
Me.fileMenu.Text = “File”
FORMS, MENUS,
AND CONTROLS


‘fileMenuLog

Me.fileMenuLog.Index = 0
Me.fileMenuLog.MenuItems.AddRange(New _
System.Windows.Forms.MenuItem() _
Working with the .NET Namespaces
150
PART II

LISTING 5.7 Continued

{Me.fileMenuLogApp, Me.fileMenuLogSec, Me.fileMenuLogSys})


Me.fileMenuLog.Text = “Log”

‘fileMenuLogApp

Me.fileMenuLogApp.Index = 0
Me.fileMenuLogApp.RadioCheck = True
Me.fileMenuLogApp.Checked = True
Me.fileMenuLogApp.Text = “Application”

‘fileMenuLogSec

Me.fileMenuLogSec.Index = 1
Me.fileMenuLogSec.RadioCheck = True
Me.fileMenuLogSec.Text = “Security”

‘fileMenuExit

Me.fileMenuExit.Index = 1
Me.fileMenuExit.Text = “Exit”

‘EventLogControl1

Me.EventLogControl1.Location = New System.Drawing.Point(8, 20)
Me.EventLogControl1.MaxEntries = 1024
Me.EventLogControl1.Log = _
EventLogControl.EventLogControl.LogType.Application
Me.EventLogControl1.Name = “EventLogControl1”
Me.EventLogControl1.Size = New System.Drawing.Size(412, 348)
Me.EventLogControl1.TabIndex = 0

‘CompositeControlForm

Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(436, 397)
Me.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.GroupBox1})
Me.Menu = Me.appMenu
Me.Name = “CompositeControlForm”
Me.Text = “CompositeControl”
Me.GroupBox1.ResumeLayout(False)
Me.ResumeLayout(False)

End Sub
Forms, Menus, and Controls
151
CHAPTER 5

LISTING 5.7 Continued

#End Region

Private Sub CustomControl_Load(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles MyBase.Load

End Sub

Private Sub fileMenuLogApp_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles fileMenuLogApp.Click
fileMenuLogApp.Checked = True
fileMenuLogSec.Checked = False
fileMenuLogSys.Checked = False
EventLogControl1.Log = _
EventLogControl.EventLogControl.LogType.Application
End Sub

Private Sub fileMenuLogSec_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles fileMenuLogSec.Click
fileMenuLogSec.Checked = True
fileMenuLogApp.Checked = False
fileMenuLogSys.Checked = False
EventLogControl1.Log = _
EventLogControl.EventLogControl.LogType.Security
End Sub

Private Sub fileMenuLogSys_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles fileMenuLogSys.Click
fileMenuLogSys.Checked = True
fileMenuLogApp.Checked = False
fileMenuLogSec.Checked = False
EventLogControl1.Log = EventLogControl.EventLogControl.LogType.System
End Sub
End Class

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

• Creating and adding forms to an application


• Adding menus and menu items to a form
Working with the .NET Namespaces
152
PART II

• 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

• Font, Text, and Printing 156

• Fonts 156

• Learning by Example: FontPad, A Simple Text


Editor 165

• Printing 179

• Learning by Example: Adding Printing


Capabilities to FontPad 184

• Printing and Font-Related Controls and Dialog


Boxes 210
Working with the .NET Namespaces
154
PART II

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

Key Classes Related to Font, Text, and Printing


Operations
The following details the namespaces this chapter discusses
• System.Drawing—Provides basic drawing functionality inside of .NET. Outside of the
Font classes listed here, the namespace is covered in detail in Chapter 9, “Drawing
Functions.”
• System.Drawing.Text—Provides access to some advanced GDI+ typography
functionality.
• System.Drawing.Printing—Provides functionality to manage printing functions in
Windows.
Table 6.1 presents the key classes in these namespaces that you will use most often when writ-
ing printing and text-related code.
Font, Text, and Printing Operations
155
CHAPTER 6

TABLE 6.1 Key Classes in the System.Drawing, System.Drawing.Text, and 6


System.Drawing.Printing Namespaces

FONT, TEXT, AND


Class Description

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

Font, Text, and Printing


All text typed on a computer, a Web page, or a typewriter requires a font. The number and
variety of fonts available to users is astounding. How many of us fuss with our documents,
picking just the right font? I’ve seen programmers spend hours customizing the code windows
they live in to evoke just the right feel. Take the book you are reading, for instance; a typeset-
ter or layout artist has chosen just the right set of fonts to make the consumption of the mater-
ial appetizing. The code listings are set in a monospaced font, in which all the characters in the
font are of the same size. This enables you to easily compare lines of code and visually see
blocks of code and nesting.
All application development requires some form of text and therefore some use of fonts. Even
Web applications require developers to set a font and font size for their output. And rich-client
applications often allow users to select their favorite fonts for displaying their data. The classes
available through the .NET Framework Class Library provide a very powerful set of tools for
managing fonts and text in your applications.
Of course, even with expensive displays and storage systems, users still like to read text from a
printed page. Reading from a piece of paper is almost always easier and clearer (although tech-
nology is getting closer). On average, people read printed documents almost twice as fast as
those onscreen. How many times have you been presented with a huge piece of documentation
and after about a page, you hit the Print button?
Printing is a key piece of nearly all client-based application development. Users demand to be
able to print and distribute their data. Again, .NET provides a rich set of tools for you to
embed this functionality within your applications.
After reading this chapter, you should be well positioned to start using font and printing fea-
tures in your own development.

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

FONT, TEXT, AND


rarely need to purchase separate underlined fonts. In other cases, a significant drop in quality

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)

‘change the font of the label control


labelExample().Font = myFont

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.

TABLE 6.2 Font Class Constructors


New Font (ByVal prototype as Font, ByVal newStyle as FontStyle)
Prototype(Font): An existing font instance that will be used to create the new font.
NewStyle(FontStyle): A FontStyle enumeration member that will be applied to the new
font instance.
Note: Use this constructor to create a new Font object from an existing Font object.
New Font (ByVal family as FontFamily, ByVal emSize as Single)
family(FontFamily): A valid FontFamily object.
emSize(Single): The size of the new font.
Note: Use this constructor to create a new Font object from a FontFamily object and a spe-
cific size. You can get a FontFamily object in a variety of ways, including straight from a
string (see example code).
Font, Text, and Printing Operations
159
CHAPTER 6

TABLE 6.2 Continued 6


New Font (ByVal familyName as String, ByVal emSize as Single)

FONT, TEXT, AND


OPERATIONS
familyName(String): A string that represents a FontFamily. For example, “Tahoma.”

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

TABLE 6.2 Continued


New Font (ByVal family as FontFamily, ByVal emSize as Single, ByVal style as
FontStyle, ByVal unit as GraphicsUnit)
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).
Note: Use this constructor to create a new Font object directly from a FontFamily, a spe-
cific size, a FontStyle, and a GraphicsUnit object.
New Font (ByVal familyName as String, ByVal emSize as Single, ByVal style as
FontStyle, ByVal unit as GraphicsUnit)
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).
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, a FontStyle, and a GraphicsUnit object.
New Font (ByVal family as FontFamily, ByVal emSize as Single, ByVal style as
FontStyle, ByVal unit as GraphicsUnit, ByVal gdiCharSet as Byte)
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.
Note: Use this constructor to create a new Font object directly from a FontFamily, a spe-
cific size, a FontStyle, a GraphicsUnit object, and a gdiCharSet.
New Font (ByVal familyName as String, ByVal emSize as Single, ByVal style as
FontStyle, ByVal unit as GraphicsUnit, ByVal gdiCharSet as Byte)
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).
unit(GraphicsUnit): A valid GraphicsUnit enumeration member (Point, Pixel, Inch, and
so on).
Font, Text, and Printing Operations
161
CHAPTER 6

TABLE 6.2 Continued 6


gdiCharSet(Byte): A GDI character set value found in WinGDI.h.

FONT, TEXT, AND


OPERATIONS
Note: Use this constructor to create a new Font object directly from a FontFamily name, a

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

TABLE 6.3 System.Drawing and FontStyle Enumeration Members

Member Example Description


Bold This is bold text. Text that has a heavier weight.
Italic This is italic text. Text that is italicized or slanted.
Regular This is regular text. Normal text.
Strikeout This is strikeout text. Text that has a line going through the
middle.
Underline This is text that is underlined. Text that has a line underneath.

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

‘return the collection of installed fonts


myFonts = New System.Drawing.Text.InstalledFontCollection()

‘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.

FONT, TEXT, AND


OPERATIONS
PRINTING
LISTING 6.1 Private Fonts Collection
Private Sub ButtonLoadFont_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles ButtonLoadFont.Click

‘local scope
Dim myPrivateFonts As System.Drawing.Text.PrivateFontCollection
Dim myFont As Font

‘instantiate a PrivateFontCollection object


myPrivateFonts = New System.Drawing.Text.PrivateFontCollection()

‘add a font to the private fonts collection


‘NOTE: this font is stored in a file in the app’s bin directory
‘ this font does NOT exist on the system
myPrivateFonts.AddFontFile(fileName:=”andalemo.ttf”)

‘create of font object from the font family in the private collection
myFont = New Font(family:=myPrivateFonts.Families(0), emSize:=12, _
style:=FontStyle.Regular)

‘change the label’s font property to use the new font


LabelExample().Font = myFont

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.

TABLE 6.4 System.Drawing.Text and GenericFontFamilies Enumeration Members

Member Example Description


Monospace This is a monospace font Represents a generic font family that is
of type Monospace. Monospace refers
to the fact that each character is the
exact same width.
Working with the .NET Namespaces
164
PART II

TABLE 6.4 Continued


Member Example Description
Sans Serif This is a sans serif font Represents a generic font family that is
of type Sans Serif. Sans Serif refers to
the fact that each character is without
(sans) serifs.
Serif This is a serif font Represents a generic font family that is
of type serif. Serifs are the fine lines
that finish off the main strokes of a
character.

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.

TABLE 6.5 System.Drawing.Text and HotKeyPrefix Enumeration 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

TABLE 6.6 TextRenderingHint Enumeration Members 6


Member Visual Representation

FONT, TEXT, AND


OPERATIONS
PRINTING
AntiAlias

AntiAliasGridFit

ClearTypeGridFit

SingleBitPerPixel

SingleBitPerPixelGridFit

SystemDefault

Suggestions for Further Exploration


➲ Explore the ins and outs of TrueType font technology at Microsoft’s Typography site:
http://www.microsoft.com/typography/default.asp. Of course, there is a devel-
oper’s section!
➲ Check out Microsoft’s clear type initiative. Microsoft is rewriting the way we view text
to bring it inline with the printed page. In fact, the first version of this has been released
with Windows XP. Information on this technology can be found inside MSDN.

Learning by Example: FontPad, a Simple Text


Editor
In this example, we extend our knowledge of the font-related namespaces to create a text edi-
tor similar in design and scope to Notepad. Of course, the example concentrates on mimicking
the functionality in Notepad’s Font dialog box. At the end of this chapter, we will fill in the
printing aspect of our application. In Chapter 7, “Stream and File Operations,” we extend this
sample by adding both Save and Open features.

Key Concepts Covered


The following represents the key classes and concepts demonstrated with this sample
application:
• Working with the Font class to create and change the default font on a control
• Displaying all the fonts installed on a system using the InstalledFontsCollection class
• Working with font families using the FontFamily class
Working with the .NET Namespaces
166
PART II

FontPad Main Dialog Box


The main dialog box of our application includes a menu bar and a RichTextBox control. The
menu bar enables users to interact with our application through a standard set of menu items.
The only functioning items in the menu are File, Exit—which exits the application—and
Format, which displays the FontSelection dialog box.

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.

LISTING 6.2 FontPad Main Form


Option Strict Off

Public Class FormMain


Inherits System.Windows.Forms.Form

#Region “ Windows Form Designer generated code “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call


Font, Text, and Printing Operations
167
CHAPTER 6

LISTING 6.2 Continued 6

FONT, TEXT, AND


End Sub

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

Private WithEvents richTextBox As System.Windows.Forms.RichTextBox


Private WithEvents button1 As System.Windows.Forms.Button
Private WithEvents button2 As System.Windows.Forms.Button
Private WithEvents button3 As System.Windows.Forms.Button
Private WithEvents mainMenu1 As System.Windows.Forms.MainMenu
Private WithEvents menuItemEdit As System.Windows.Forms.MenuItem
Private WithEvents menuItemFile As System.Windows.Forms.MenuItem
Private WithEvents menuItemHelp As System.Windows.Forms.MenuItem
Private WithEvents menuItemFormat As System.Windows.Forms.MenuItem
Private WithEvents menuItemFont As System.Windows.Forms.MenuItem
Private WithEvents menuItemWrap As System.Windows.Forms.MenuItem
Private WithEvents menuItem1 As System.Windows.Forms.MenuItem
Private WithEvents menuItemExit As System.Windows.Forms.MenuItem
Private WithEvents menuItemNew As System.Windows.Forms.MenuItem
Private WithEvents menuItem2 As System.Windows.Forms.MenuItem
Private WithEvents menuItem3 As System.Windows.Forms.MenuItem

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.menuItem1 = New System.Windows.Forms.MenuItem()
Me.menuItemFont = New System.Windows.Forms.MenuItem()
Me.menuItem2 = New System.Windows.Forms.MenuItem()
Me.menuItem3 = New System.Windows.Forms.MenuItem()
Me.menuItemExit = New System.Windows.Forms.MenuItem()
Me.menuItemEdit = New System.Windows.Forms.MenuItem()
Me.menuItemWrap = New System.Windows.Forms.MenuItem()
Me.menuItemFile = New System.Windows.Forms.MenuItem()
Working with the .NET Namespaces
168
PART II

LISTING 6.2 Continued


Me.richTextBox = New System.Windows.Forms.RichTextBox()
Me.menuItemHelp = New System.Windows.Forms.MenuItem()
Me.menuItemNew = New System.Windows.Forms.MenuItem()
Me.mainMenu1 = New System.Windows.Forms.MainMenu()
Me.menuItemFormat = New System.Windows.Forms.MenuItem()
Me.menuItem1.Index = 1
Me.menuItem1.Text = “-”
Me.menuItemFont.Index = 1
Me.menuItemFont.Text = “Font...”
Me.menuItem2.Index = 2
Me.menuItem2.Text = “Page Setup”
Me.menuItem3.Index = 3
Me.menuItem3.Text = “Print”
Me.menuItemExit.Index = 4
Me.menuItemExit.Text = “Exit”
Me.menuItemEdit.Index = 1
Me.menuItemEdit.Text = “Edit”
Me.menuItemWrap.Index = 0
Me.menuItemWrap.Text = “Word Wrap”
Me.menuItemFile.Index = 0
Me.menuItemFile.MenuItems.AddRange(New System.Windows.Forms.MenuItem() _
{Me.menuItemNew, Me.menuItem1, Me.menuItem2, Me.menuItem3, _
Me.menuItemExit})
Me.menuItemFile.Text = “File”
Me.richTextBox.AcceptsTab = True
Me.richTextBox.AutoSize = True
Me.richTextBox.AutoWordSelection = True
Me.richTextBox.Dock = System.Windows.Forms.DockStyle.Fill
Me.richTextBox.Size = New System.Drawing.Size(591, 336)
Me.richTextBox.TabIndex = 0
Me.richTextBox.Text = “richTextBox1”
Me.menuItemHelp.Index = 3
Me.menuItemHelp.Text = “Help”
Me.menuItemNew.Index = 0
Me.menuItemNew.Text = “New”
Me.mainMenu1.MenuItems.AddRange(New System.Windows.Forms.MenuItem() _
{Me.menuItemFile, Me.menuItemEdit, Me.menuItemFormat, _
Me.menuItemHelp})
Me.menuItemFormat.Index = 2
Me.menuItemFormat.MenuItems.AddRange(New System.Windows.Forms.MenuItem() _
{Me.menuItemWrap, Me.menuItemFont})
Me.menuItemFormat.Text = “Format”
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(591, 336)
Me.Controls.AddRange(New System.Windows.Forms.Control() _
Font, Text, and Printing Operations
169
CHAPTER 6

LISTING 6.2 Continued 6

FONT, TEXT, AND


{Me.richTextBox})

OPERATIONS
Me.Menu = Me.mainMenu1

PRINTING
Me.Text = “FontPad”

End Sub

The procedure, ResetRichTextBox, is of particular interest. In this subroutine, we create an


instance of a Font class based on the global variables: g_FontFamily, g_FontStyle, and
g_FontSize. We then apply this object to the RichTextBox’s Font property.

This procedure gets called both when the form loads and when users apply changes to the
application’s font settings.
Public Sub resetRichTextBox()

‘purpose: set the font properties of the rich text box

‘local scope
Dim font As Font
Dim fontStyle As FontStyle
Dim fontFamily As FontFamily
Dim styleType As System.Type

‘set a font style type


styleType = fontStyle.GetType

‘create a font family object


fontFamily = New FontFamily(g_FontFamily)

‘get a font style object (note: must turn off option strict)
fontStyle = fontStyle.Parse(enumType:=styleType, _
value:=g_FontStyle)

‘create a new font based on global values


font = New Font(family:=fontFamily, _
emSize:=CSng(g_FontSize), _
style:=fontStyle, _
unit:=System.Drawing.GraphicsUnit.Point)

‘reset the font on the control


Me.richTextBox.Font = font

End Sub
Working with the .NET Namespaces
170
PART II

The form load event initializes the RichTextBox.


Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load

‘purpose: init. the application, load any controls

‘set the text property of the rich text box


richTextBox().Text = “”

‘set the font properties to their defaults


resetRichTextBox()
g_FontPad = Me

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

‘purpose: show the font dialog when the user clicks


‘ the font menu item

‘local scope
Dim dialogFont As FontSettings

‘create new suface form


dialogFont = New FontSettings()

‘set the start position of the modal dialog to the center


‘ positions of its parent
dialogFont.StartPosition = FormStartPosition.CenterParent

‘show the form as modal dialog


dialogFont.ShowDialog(Me)

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

‘purpose: end the application

‘close the form


Me.Close()

End Sub
Font, Text, and Printing Operations
171
CHAPTER 6

#End Region 6

FONT, TEXT, AND


End Class

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.

LISTING 6.3 FontPad Font Settings Form

Option Strict Off

Public Class FontSettings


Inherits System.Windows.Forms.Form

#Region “ Windows Form Designer generated code “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()
Working with the .NET Namespaces
172
PART II

LISTING 6.3 Continued


‘Add any initialization after the InitializeComponent() call

End Sub

‘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

Private WithEvents label4 As System.Windows.Forms.Label


Private WithEvents label2 As System.Windows.Forms.Label
Private WithEvents label3 As System.Windows.Forms.Label
Private WithEvents groupBox1 As System.Windows.Forms.GroupBox
Private WithEvents labelSample As System.Windows.Forms.Label
Private WithEvents listBoxSizes As System.Windows.Forms.ListBox
Private WithEvents buttonOk As System.Windows.Forms.Button
Private WithEvents buttonCancel As System.Windows.Forms.Button
Private WithEvents listBoxFamilies As System.Windows.Forms.ListBox
Private WithEvents listBoxStyles As System.Windows.Forms.ListBox

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.labelSample = New System.Windows.Forms.Label()
Me.listBoxSizes = New System.Windows.Forms.ListBox()
Me.buttonOk = New System.Windows.Forms.Button()
Me.buttonCancel = New System.Windows.Forms.Button()
Me.listBoxFamilies = New System.Windows.Forms.ListBox()
Me.listBoxStyles = New System.Windows.Forms.ListBox()
Me.label2 = New System.Windows.Forms.Label()
Me.label4 = New System.Windows.Forms.Label()
Me.groupBox1 = New System.Windows.Forms.GroupBox()
Me.label3 = New System.Windows.Forms.Label()
Me.labelSample.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D
Me.labelSample.Location = New System.Drawing.Point(12, 24)
Font, Text, and Printing Operations
173
CHAPTER 6

LISTING 6.3 Continued 6

FONT, TEXT, AND


Me.labelSample.Size = New System.Drawing.Size(344, 80)

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

LISTING 6.3 Continued


Me.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.listBoxSizes, Me.listBoxStyles, Me.listBoxFamilies, Me.label4, _
Me.label3, Me.label2, Me.groupBox1, Me.buttonCancel, Me.buttonOk})
Me.MaximizeBox = False
Me.MinimizeBox = False
Me.Text = “Font Settings”

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

‘purpose: init. the controls on the form

‘declare variables of local scope


Dim myFonts As System.Drawing.Text.InstalledFontCollection
Dim i As Integer
Dim myType As System.Type
Dim myStyle As FontStyle
Dim myArray() As String

‘return the collection of installed fonts


myFonts = New System.Drawing.Text.InstalledFontCollection()

‘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

‘get a type object based on the font style enumeration


myType = myStyle.GetType()

‘fill an array with the font style member names


myArray = myStyle.GetNames(myType)
Font, Text, and Printing Operations
175
CHAPTER 6

‘loop through the array 6


For i = 0 To UBound(myArray)

FONT, TEXT, AND


OPERATIONS
‘fill the list box with the font styles

PRINTING
listBoxStyles().Items.Add(myArray(i))

Next

‘add basic font sizes


listBoxSizes().Items.Add(“8”)
listBoxSizes().Items.Add(“10”)
listBoxSizes().Items.Add(“12”)
listBoxSizes().Items.Add(“14”)
listBoxSizes().Items.Add(“18”)
listBoxSizes().Items.Add(“24”)
listBoxSizes().Items.Add(“36”)
listBoxSizes().Items.Add(“48”)
listBoxSizes().Items.Add(“72”)

‘select the current settings in the lists


listBoxStyles().SelectedItem = g_FontStyle
listBoxFamilies().SelectedItem = g_FontFamily
listBoxSizes().SelectedItem = g_FontSize

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()

‘purpose: reset the sample label as a visual cue to users

‘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

‘get values from family list box


If listBoxFamilies().SelectedIndex = -1 Then
Working with the .NET Namespaces
176
PART II

‘nothing is selected: set to default


fontFamilyName = g_FontFamily

Else

‘return selected item


fontFamilyName = listBoxFamilies().SelectedItem.ToString

End If

‘get values from size list box


If listBoxSizes().SelectedIndex = -1 Then

‘nothing is selected: set to default


fontSize = g_FontSize

Else

‘return selected item


fontSize = listBoxSizes().SelectedItem.ToString

End If

‘get values from the style list box


If listBoxStyles().SelectedIndex = -1 Then

‘nothing is selected: set to default


fontStyleName = g_FontStyle

Else

‘return selected item


fontStyleName = listBoxStyles().SelectedItem.ToString

End If

‘set a font style type


styleType = fontStyle.GetType

‘create a font family object


fontFamily = New FontFamily(fontFamilyName)

‘get a font style object (note: must turn off option strict)
fontStyle = fontStyle.Parse(enumType:=styleType, value:=fontStyleName)

‘create a new font based on global values


font = New Font(family:=fontFamily, _
Font, Text, and Printing Operations
177
CHAPTER 6

emSize:=CSng(fontSize), _ 6
style:=fontStyle, _

FONT, TEXT, AND


unit:=System.Drawing.GraphicsUnit.Point)

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.

Private Sub buttonOk_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles buttonOk.Click

‘purpose: respond to the user’s request to apply the form’s changes

‘reset the aplication’s font settings from the list boxes


g_FontFamily = listBoxFamilies().SelectedItem.ToString()
g_FontSize = listBoxSizes().SelectedItem.ToString()
g_FontStyle = listBoxStyles().SelectedItem.ToString()

‘apply the settings to the rich text box


Call g_FontPad.resetRichTextBox()

‘close the dialog


Me.Close()

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

‘purpose: respond to the user’s request to cancel the form

‘close the font settings dialog


Me.Close()

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.

Private Sub listBoxFamilies_SelectedIndexChanged( _


ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles listBoxFamilies.SelectedIndexChanged

‘purpose: synch. the sample label with the selected item


Working with the .NET Namespaces
178
PART II

‘call the sample update procedure


Call setSampleText()

End Sub

Private Sub listBoxStyles_SelectedIndexChanged( _


ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles listBoxStyles.SelectedIndexChanged

‘purpose: synch. the sample label with the selected item

‘call the sample update procedure


Call setSampleText()

End Sub

Private Sub listBoxSizes_SelectedIndexChanged( _


ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles listBoxSizes.SelectedIndexChanged

‘purpose: synch. the sample label with the selected item

‘call the sample update procedure


Call setSampleText()

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.

LISTING 6.4 FontPad Module


Module modFontPad

‘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”

‘used to maintain a reference to the main form


Public g_FontPad As FormMain

End Module
Font, Text, and Printing Operations
179
CHAPTER 6

Printing 6

FONT, TEXT, AND


Previous to .NET, printing with Visual Basic involved either using the common print dialog

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.

Sending Output to the Printer


Printing in Windows involves a printer device context. The good news is that this context can
be used the same way as any other device context. All the drawing functions presented in the
following chapter, for instance, work the same way for printers as they do for display devices.
This is one of the reasons you find the Printing namespace tucked under Drawing. This
device independence is one of the powerful features that Windows provides. It is possible to
render output to either the screen or the printer just by switching device contexts.
Typically, programmers execute the following basic steps when writing printing functionality
using the .NET Class Library:
1. Create a new instance of the PrintDocument class from the System.Drawing.Printing
namespace. This is the principal object used to control our printing operation.
Dim printDocument As New System.Drawing.Printing.PrintDocument

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”

‘set the page orientation to landscape


printDocument.PrinterSettings.DefaultPageSettings.Landscape = False

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)

‘draw the string to the printer device


ev.Graphics.DrawString(s:=”Hello World!”, _
font:= New Font(“Arial”, 10), brush:=Brushes.Black, _
x:= ev.MarginBounds.Left, y:=ev.MarginBounds.Top, _
format:=New StringFormat())

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.

TABLE 6.7 Duplex Enumeration Members


Member Description
Default Select the printer’s default duplex setting.
Horizontal Select double-sided, horizontal (landscape) printing.
Simplex Select single-sided printing (do not use duplex functionality).
Vertical Select double-sided, vertical (portrait) printing.

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

TABLE 6.8 PaperKind Enumeration 6


Member Description

FONT, TEXT, AND


OPERATIONS
Executive Standard executive paper (7.5 in. by 10.5 in.)

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.

TABLE 6.9 PaperSourceKind Enumeration Members

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.

TABLE 6.10 System.Printing and PrinterResolutionKind Enumeration Members

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.

TABLE 6.11 System.Printing and PrintRange Enumeration Members

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.

Previewing Documents Prior to Printing


Before sending documents to the printer, users often need to see how their output is going to
look. This saves paper, ink, and time. For instance, Microsoft Word provides the print
previewing functionality. .NET exposes two classes that enable developers to simulate this
functionality. The PreviewPrintController class is a print controller that displays documents
Font, Text, and Printing Operations
183
CHAPTER 6

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

FONT, TEXT, AND


page. The following is a step-by-step walkthrough of adding print preview capabilities to your

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

2. Next, create an instance of a PrintDocument class:


printDocument = New System.Drawing.Printing.PrintDocument()

3. Then, create an instance of the PreviewPrintController class.


previewController = New System.Drawing.Printing.PreviewPrintController()

4. Set the PrintDocument object’s PrintController property to your


PreviewPrintController object. This ensures that your output is sent to a set of images
rather than to the printer.
printDocument.PrintController = previewController

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()

‘loop through the array and display each image


For i = 0 To UBound(pageInfoArray)
Me.pictureBoxPreview.Image = pageInfoArray(i).Image
Next
Working with the .NET Namespaces
184
PART II

Learning by Example: Adding Printing Capabilities


to FontPad
In this example, we extend the FontPad application that we created in the previous section to
include printing functionality. We do so by adding a Page Setup form that allows users to indi-
cate things such as page size and orientation. We then create a simple Print Dialog form that
enables users to indicate which printer they want to use and what pages they want printed.

Key Concepts Covered


The following represents the key class and concepts demonstrated with this sample application:
• Setting and enforcing margins on a printed page
• Using the PrintDocument class to output text to the printer
• Using the PaperSizes and PaperSources properties of the PrinterSettings class to
return the sizes and sources that a given printer supports
• Printing a stream of text using the System.IO.StringReader class

Main Form Changes


The main form is identical to Figure 6.1 except that we now add two new menu items: Page
Setup and Print.

Code Walkthrough
The additional code behind each new menu item’s click event is in Listing 6.5.

LISTING 6.5 FontPad Main Form (moduleFontSample.vb) Changes


When the user clicks Print, Page Setup, we display the Page Setup dialog box to him.
Private Sub menuItemPageSetup_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles menuItemPageSetup.Click

‘purpose: display the page setup dialog

‘local scope
Dim dialogPageSetup As PageSetup

‘create new page setup form


dialogPageSetup = New PageSetup()

‘set the start position of the modal dialog to the center


‘ positions of its parent
Font, Text, and Printing Operations
185
CHAPTER 6

LISTING 6.5 Continued 6


dialogPageSetup.StartPosition = FormStartPosition.CenterParent

FONT, TEXT, AND


OPERATIONS
PRINTING
‘show the form as modal dialog
dialogPageSetup.ShowDialog(Me)

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

‘purpose: display the print dialog

‘local scope
Dim dialogPrint As Print

‘set the contents of what to print


printString = Me.richTextBox.Text

‘create new print form


dialogPrint = New Print()

‘set the start position of the modal dialog to the center


‘ positions of its parent
dialogPrint.StartPosition = FormStartPosition.CenterParent

‘show the form as modal dialog


dialogPrint.ShowDialog(Me)

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.

LISTING 6.6 Page Setup Form


The code starts by setting up and defining the form and the controls it contains.
Public Class PageSetup
Inherits System.Windows.Forms.Form

#Region “ Windows Form Designer generated code “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call

End Sub

‘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
Font, Text, and Printing Operations
187
CHAPTER 6

LISTING 6.6 Continued 6


Private WithEvents groupBox2 As System.Windows.Forms.GroupBox

FONT, TEXT, AND


OPERATIONS
Private WithEvents groupBox3 As System.Windows.Forms.GroupBox

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

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
Me.groupBox1 = New System.Windows.Forms.GroupBox()
Me.comboBoxSource = New System.Windows.Forms.ComboBox()
Me.comboBoxSize = New System.Windows.Forms.ComboBox()
Me.label3 = New System.Windows.Forms.Label()
Me.label2 = New System.Windows.Forms.Label()
Me.label1 = New System.Windows.Forms.Label()
Me.groupBox2 = New System.Windows.Forms.GroupBox()
Me.radioButtonLandscape = New System.Windows.Forms.RadioButton()
Me.radioButtonPortrait = New System.Windows.Forms.RadioButton()
Me.groupBox3 = New System.Windows.Forms.GroupBox()
Me.textBoxBottom = New System.Windows.Forms.TextBox()
Me.textBoxRight = New System.Windows.Forms.TextBox()
Me.textBoxTop = New System.Windows.Forms.TextBox()
Me.textBoxLeft = New System.Windows.Forms.TextBox()
Me.label7 = New System.Windows.Forms.Label()
Working with the .NET Namespaces
188
PART II

LISTING 6.6 Continued


Me.label6 = New System.Windows.Forms.Label()
Me.label5 = New System.Windows.Forms.Label()
Me.label4 = New System.Windows.Forms.Label()
Me.buttonCancel = New System.Windows.Forms.Button()
Me.buttonOk = New System.Windows.Forms.Button()
Me.groupBox1.SuspendLayout()
Me.groupBox2.SuspendLayout()
Me.groupBox3.SuspendLayout()
Me.SuspendLayout()

‘groupBox1

Me.groupBox1.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.comboBoxSource, Me.comboBoxSize, Me.label3, Me.label2, Me.label1})
Me.groupBox1.Location = New System.Drawing.Point(12, 12)
Me.groupBox1.Name = “groupBox1”
Me.groupBox1.Size = New System.Drawing.Size(344, 84)
Me.groupBox1.TabIndex = 0
Me.groupBox1.TabStop = False
Me.groupBox1.Text = “Paper”

‘comboBoxSource

Me.comboBoxSource.DropDownWidth = 276
Me.comboBoxSource.Location = New System.Drawing.Point(60, 52)
Me.comboBoxSource.Name = “comboBoxSource”
Me.comboBoxSource.Size = New System.Drawing.Size(276, 21)
Me.comboBoxSource.TabIndex = 1

‘comboBoxSize

Me.comboBoxSize.DropDownWidth = 276
Me.comboBoxSize.Location = New System.Drawing.Point(60, 20)
Me.comboBoxSize.Name = “comboBoxSize”
Me.comboBoxSize.Size = New System.Drawing.Size(276, 21)
Me.comboBoxSize.TabIndex = 0

‘label3

Me.label3.Location = New System.Drawing.Point(12, 56)
Me.label3.Name = “label3”
Me.label3.TabIndex = 2
Me.label3.Text = “Source”

‘label2

Font, Text, and Printing Operations
189
CHAPTER 6

LISTING 6.6 Continued 6


Me.label2.Location = New System.Drawing.Point(4, 76)

FONT, TEXT, AND


OPERATIONS
Me.label2.Name = “label2”

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

LISTING 6.6 Continued


Me.groupBox3.Location = New System.Drawing.Point(128, 108)
Me.groupBox3.Name = “groupBox3”
Me.groupBox3.Size = New System.Drawing.Size(228, 80)
Me.groupBox3.TabIndex = 2
Me.groupBox3.TabStop = False
Me.groupBox3.Text = “Margins (inches)”

‘textBoxBottom

Me.textBoxBottom.Location = New System.Drawing.Point(156, 48)
Me.textBoxBottom.Name = “textBoxBottom”
Me.textBoxBottom.Size = New System.Drawing.Size(40, 20)
Me.textBoxBottom.TabIndex = 3
Me.textBoxBottom.Text = “”

‘textBoxRight

Me.textBoxRight.Location = New System.Drawing.Point(156, 20)
Me.textBoxRight.Name = “textBoxRight”
Me.textBoxRight.Size = New System.Drawing.Size(40, 20)
Me.textBoxRight.TabIndex = 1
Me.textBoxRight.Text = “”

‘textBoxTop

Me.textBoxTop.Location = New System.Drawing.Point(52, 48)
Me.textBoxTop.Name = “textBoxTop”
Me.textBoxTop.Size = New System.Drawing.Size(40, 20)
Me.textBoxTop.TabIndex = 2
Me.textBoxTop.Text = “”

‘textBoxLeft

Me.textBoxLeft.Location = New System.Drawing.Point(52, 20)
Me.textBoxLeft.Name = “textBoxLeft”
Me.textBoxLeft.Size = New System.Drawing.Size(40, 20)
Me.textBoxLeft.TabIndex = 0
Me.textBoxLeft.Text = “”

‘label7

Me.label7.Location = New System.Drawing.Point(112, 52)
Me.label7.Name = “label7”
Me.label7.TabIndex = 3
Me.label7.Text = “Bottom”

Font, Text, and Printing Operations
191
CHAPTER 6

LISTING 6.6 Continued 6


‘label6

FONT, TEXT, AND


OPERATIONS

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

LISTING 6.6 Continued


Me.MaximizeBox = False
Me.MinimizeBox = False
Me.Name = “PageSetup”
Me.Text = “FontPad: Page Setup”
Me.groupBox1.ResumeLayout(False)
Me.groupBox2.ResumeLayout(False)
Me.groupBox3.ResumeLayout(False)
Me.ResumeLayout(False)

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

‘purpose: cancel the form without applying the changes

‘close the page setup dialog


Me.Close()

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

‘purpose: reset the page setup values for the application

‘local scope
Dim isValid As Boolean

‘validate the text box fields


isValid = True
If Not IsNumeric(textBoxBottom().Text) Then isValid = False
If Not IsNumeric(textBoxTop().Text) Then isValid = False
If Not IsNumeric(textBoxLeft().Text) Then isValid = False
If Not IsNumeric(textBoxRight().Text) Then isValid = False

‘check if passed validation


Font, Text, and Printing Operations
193
CHAPTER 6

If isValid Then 6

FONT, TEXT, AND


‘set the global values

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

‘set the page orientation value


If radioButtonPortrait().Checked = True Then
pageOrientation = “PORTRAIT”
Else
pageOrientation = “LANDSCAPE”
End If

‘set margin values


marginLeft = CSng(textBoxLeft().Text)
marginTop = CSng(textBoxTop().Text)
marginRight = CSng(textBoxRight().Text)
marginBottom = CSng(textBoxBottom().Text)

‘close the form


Me.Close()

Else

‘of course we can do better than this ...


Beep()

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

‘purpose: load the form and init. the control values

‘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

‘create a new print document


printDocument = New System.Drawing.Printing.PrintDocument()

‘set margin values


textBoxBottom().Text = CStr(marginBottom)
textBoxTop().Text = CStr(marginTop)
textBoxLeft().Text = CStr(marginLeft)
textBoxRight().Text = CStr(marginRight)

‘set the page orientation values


Select Case pageOrientation

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

‘set paper sizes


paperSizes = printDocument.PrinterSettings.PaperSizes
For i = 0 To paperSizes.Count - 1

‘add the paper sizes to the combo box


comboBoxSize().Items.Add(paperSizes.Item(i).PaperName)

Next

‘set the paper sources


paperSources = printDocument.PrinterSettings.PaperSources
For i = 0 To paperSources.Count - 1
Font, Text, and Printing Operations
195
CHAPTER 6

‘add the paper sizes to the combo box 6


comboBoxSource().Items.Add(paperSources.Item(i).SourceName)

FONT, TEXT, AND


OPERATIONS
Next

PRINTING
‘select the user settings
comboBoxSize().SelectedItem = paperSize
comboBoxSource().SelectedItem = paperSource

‘close objects
printDocument.Dispose()

End Sub

End Class

Print Dialog Box


The Print dialog box for FontPad enables users to select an installed printer, set the print range,
the number of copies, and whether to collate the output. Figure 6.4 shows the Print dialog box.

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.

LISTING 6.7 Print Form (formPrint.vb)

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

LISTING 6.7 Continued


The StringReader class is part of the System.IO namespace, and is discussed in Chapter 7,
“Stream and File Operations.” It provides us with a version of a stream object that is based on
a string. We populate this with our RichTextBox contents (printString) and output it line-by-
line to the printer.
Public Class Print
Inherits System.Windows.Forms.Form

#Region “ Windows Form Designer generated code “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call

End Sub

‘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

Private WithEvents textBoxFrom As System.Windows.Forms.TextBox


Private WithEvents label8 As System.Windows.Forms.Label
Private WithEvents buttonCancel As System.Windows.Forms.Button
Private WithEvents label1 As System.Windows.Forms.Label
Private WithEvents label2 As System.Windows.Forms.Label
Private WithEvents radioButtonAll As System.Windows.Forms.RadioButton
Private WithEvents label11 As System.Windows.Forms.Label
Private WithEvents buttonOk As System.Windows.Forms.Button
Private WithEvents groupBox1 As System.Windows.Forms.GroupBox
Private WithEvents groupBox2 As System.Windows.Forms.GroupBox
Private WithEvents groupBox3 As System.Windows.Forms.GroupBox
Private WithEvents comboBoxPrinterName As System.Windows.Forms.ComboBox
Private WithEvents radioButtonSelection As System.Windows.Forms.RadioButton
Private WithEvents textBoxTo As System.Windows.Forms.TextBox
Private WithEvents radioButtonPages As System.Windows.Forms.RadioButton
Font, Text, and Printing Operations
197
CHAPTER 6

LISTING 6.7 Continued 6


Private WithEvents numericUpDownCopies As System.Windows.Forms.NumericUpDown

FONT, TEXT, AND


OPERATIONS
Private WithEvents labelStatus As System.Windows.Forms.Label

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

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
Me.labelFrom = New System.Windows.Forms.Label()
Me.radioButtonPages = New System.Windows.Forms.RadioButton()
Me.radioButtonAll = New System.Windows.Forms.RadioButton()
Me.label11 = New System.Windows.Forms.Label()
Me.textBoxTo = New System.Windows.Forms.TextBox()
Me.labelColor = New System.Windows.Forms.Label()
Me.buttonCancel = New System.Windows.Forms.Button()
Me.buttonOk = New System.Windows.Forms.Button()
Me.checkBoxCollate = New System.Windows.Forms.CheckBox()
Me.groupBox1 = New System.Windows.Forms.GroupBox()
Me.comboBoxPrinterName = New System.Windows.Forms.ComboBox()
Me.label2 = New System.Windows.Forms.Label()
Me.label1 = New System.Windows.Forms.Label()
Me.groupBox2 = New System.Windows.Forms.GroupBox()
Me.textBoxFrom = New System.Windows.Forms.TextBox()
Me.labelTo = New System.Windows.Forms.Label()
Me.label8 = New System.Windows.Forms.Label()
Me.groupBox3 = New System.Windows.Forms.GroupBox()
Me.numericUpDownCopies = New System.Windows.Forms.NumericUpDown()
Me.groupBox1.SuspendLayout()
Me.groupBox2.SuspendLayout()
Me.groupBox3.SuspendLayout()
CType(Me.numericUpDownCopies, _
System.ComponentModel.ISupportInitialize).BeginInit()
Me.SuspendLayout()

‘labelFrom

Me.labelFrom.Location = New System.Drawing.Point(76, 64)
Me.labelFrom.Name = “labelFrom”
Me.labelFrom.Size = New System.Drawing.Size(40, 23)
Working with the .NET Namespaces
198
PART II

LISTING 6.7 Continued


Me.labelFrom.TabIndex = 5
Me.labelFrom.Text = “from”

‘radioButtonPages

Me.radioButtonPages.Location = New System.Drawing.Point(12, 56)
Me.radioButtonPages.Name = “radioButtonPages”
Me.radioButtonPages.TabIndex = 1
Me.radioButtonPages.Text = “Pages”

‘radioButtonAll

Me.radioButtonAll.Checked = True
Me.radioButtonAll.Location = New System.Drawing.Point(12, 24)
Me.radioButtonAll.Name = “radioButtonAll”
Me.radioButtonAll.TabIndex = 0
Me.radioButtonAll.TabStop = True
Me.radioButtonAll.Text = “All”

‘label11

Me.label11.Location = New System.Drawing.Point(12, 24)
Me.label11.Name = “label11”
Me.label11.TabIndex = 0
Me.label11.Text = “Number of copies”

‘textBoxTo

Me.textBoxTo.Location = New System.Drawing.Point(172, 60)
Me.textBoxTo.Name = “textBoxTo”
Me.textBoxTo.Size = New System.Drawing.Size(40, 20)
Me.textBoxTo.TabIndex = 6
Me.textBoxTo.Text = “”

‘labelColor

Me.labelColor.Location = New System.Drawing.Point(112, 48)
Me.labelColor.Name = “labelColor”
Me.labelColor.Size = New System.Drawing.Size(244, 23)
Me.labelColor.TabIndex = 4

‘buttonCancel

Me.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel
Me.buttonCancel.Location = New System.Drawing.Point(296, 220)
Font, Text, and Printing Operations
199
CHAPTER 6

LISTING 6.7 Continued 6


Me.buttonCancel.Name = “buttonCancel”

FONT, TEXT, AND


OPERATIONS
Me.buttonCancel.TabIndex = 0

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

LISTING 6.7 Continued


‘label1

Me.label1.Location = New System.Drawing.Point(12, 24)
Me.label1.Name = “label1”
Me.label1.TabIndex = 0
Me.label1.Text = “Name”

‘groupBox2

Me.groupBox2.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.textBoxFrom, Me.labelFrom, Me.textBoxTo, Me.labelTo, _
Me.label8, Me.radioButtonPages, Me.radioButtonAll})
Me.groupBox2.Location = New System.Drawing.Point(8, 92)
Me.groupBox2.Name = “groupBox2”
Me.groupBox2.Size = New System.Drawing.Size(220, 120)
Me.groupBox2.TabIndex = 3
Me.groupBox2.TabStop = False
Me.groupBox2.Text = “Print Range”

‘textBoxFrom

Me.textBoxFrom.Location = New System.Drawing.Point(108, 60)
Me.textBoxFrom.Name = “textBoxFrom”
Me.textBoxFrom.Size = New System.Drawing.Size(40, 20)
Me.textBoxFrom.TabIndex = 7
Me.textBoxFrom.Text = “”

‘labelTo

Me.labelTo.Location = New System.Drawing.Point(152, 64)
Me.labelTo.Name = “labelTo”
Me.labelTo.Size = New System.Drawing.Size(20, 23)
Me.labelTo.TabIndex = 4
Me.labelTo.Text = “to”

‘label8

Me.label8.Location = New System.Drawing.Point(116, 60)
Me.label8.Name = “label8”
Me.label8.Size = New System.Drawing.Size(4, 0)
Me.label8.TabIndex = 3
Me.label8.Text = “label8”

‘groupBox3

Font, Text, and Printing Operations
201
CHAPTER 6

LISTING 6.7 Continued 6


Me.groupBox3.Controls.AddRange(New System.Windows.Forms.Control() _

FONT, TEXT, AND


OPERATIONS
{Me.checkBoxCollate, Me.numericUpDownCopies, Me.label11})

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

‘purpose: load the form, init. the controls

‘local scope
Dim i As Integer

‘use the printDocument object to return all installed printers


‘ loop through the list
For i = 0 To printDocument.PrinterSettings.InstalledPrinters.Count - 1

‘set the printer combo box to all installed printers


comboBoxPrinterName().Items.Add( _
printDocument.PrinterSettings.InstalledPrinters.Item(i).ToString())

Next

‘select the default printer in the list


comboBoxPrinterName().SelectedItem = printerDefault

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

‘purpose: cancel the dialog without applying changes

‘close the form


Me.Close()

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

‘purpose: print the document based on settings


Font, Text, and Printing Operations
203
CHAPTER 6

6
‘local scope

FONT, TEXT, AND


Dim isValid As Boolean

OPERATIONS
Dim fromPage As Integer

PRINTING
Dim toPage As Integer

‘validate text boxes


isValid = True

‘validate the from text box


If textBoxFrom().Enabled = True Then

If Not IsNumeric(textBoxFrom().Text) Then


isValid = False
Else
‘set the from page value
fromPage = CInt(textBoxFrom().Text)
End If

End If

‘validate the to text box


If textBoxTo().Enabled = True Then

If Not IsNumeric(textBoxTo().Text) Then


isValid = False
Else
‘set the to page value
toPage = CInt(textBoxTo().Text)
End If

End If

‘check for valid range


If fromPage > toPage Then isValid = False

‘check if valid input


If isValid Then

‘print the document


Call printText(fromPage:=fromPage, _
toPage:=toPage, _
copiesToPrint:=numericUpDownCopies().Value, _
isCollate:=checkBoxCollate().Checked)

‘close the form


Working with the .NET Namespaces
204
PART II

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

‘create a new instance of the string reader class


‘ contruct it based on the contents to print from the rich text box
‘ this value (printString) is set just before the print
‘ dialog is displayed
printStream = New System.IO.StringReader(s:=printString)

‘intercept the print page event with our own handler


AddHandler printDocument.PrintPage, _
AddressOf Me.printPage_handler

‘set the range of pages to print


Font, Text, and Printing Operations
205
CHAPTER 6

If fromPage > 0 And toPage > 0 Then 6


printDocument.PrinterSettings.FromPage = fromPage

FONT, TEXT, AND


printDocument.PrinterSettings.ToPage = toPage

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 page orientation values based on user settings


If pageOrientation = “PORTRAIT” Then
printDocument.DefaultPageSettings.Landscape = False
Else
printDocument.DefaultPageSettings.Landscape = True
End If

‘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

‘set the paper source to print from by looping through


‘ the paper sources collection,
‘ matching the paper source name that the user has selected,
‘ and setting the paperSource property of pageSettings =
‘ to the correct paperSource
For count = 0 To printDocument.PrinterSettings.PaperSources.Count - 1
If printDocument.PrinterSettings.PaperSources.Item(count).SourceName = _
paperSource Then

printDocument.DefaultPageSettings.PaperSource = _
printDocument.PrinterSettings.PaperSources.Item(count)

Exit For
Working with the .NET Namespaces
206
PART II

End If
Next

‘set the number of copies to print


printDocument.PrinterSettings.Copies = copiesToPrint

‘set the collate property


printDocument.PrinterSettings.Collate = isCollate

‘print the document


printDocument.Print()

‘close the connection to the text document


printStream.Close()

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)

‘purpose: intercept and handle the printPage event


‘ of the PrintDocument object
‘ prints 1 page at a time

‘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

‘set a font style type


styleType = fontStyle.GetType
Font, Text, and Printing Operations
207
CHAPTER 6

‘create a font family object 6


fontFamily = New FontFamily(fontFamilySetting)

FONT, TEXT, AND


OPERATIONS
‘get a font style object (note: must turn off option strict)

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)

‘calculate the number of lines per page


linesPerPage = ev.MarginBounds.Height / _
printFont.GetHeight(ev.Graphics)

‘iterate through the file, printing each line


count = 0
Do While count < linesPerPage

‘increment count variable


count = count + 1

‘read a line of text from the file


line = printStream.ReadLine

‘set y coordinate of the printing position


yPos = topMargin + (count * printFont.GetHeight(ev.Graphics))

‘draw the string to the printer device


ev.Graphics.DrawString(s:=line, Font:=printFont, _
brush:=Brushes.Black, _
x:=leftMargin, y:=yPos, _
Format:=New StringFormat())

‘check for more pages to print


‘NOTE: need to figure out how to tell if at the end of the document
If line <> “” Then
ev.HasMorePages = True
Else
ev.HasMorePages = False
End If

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)

‘purpose: enable from and to controls when user selects a range

labelFrom().Enabled = toggleValue
labelTo().Enabled = toggleValue
textBoxFrom().Enabled = toggleValue
textBoxTo().Enabled = toggleValue

End Sub

Private Sub radioButtonPages_CheckedChanged(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles radioButtonPages.CheckedChanged

‘purpose: enable related controls

Call toggleRange(True)

End Sub

Private Sub radioButtonAll_CheckedChanged(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles radioButtonAll.CheckedChanged

‘purpose: disable related controls

Call toggleRange(False)

End Sub

Private Sub comboBoxPrinterName_SelectedIndexChanged( _


ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles _
comboBoxPrinterName.SelectedIndexChanged

‘tell the print document object the name of the selected printer
printDocument.PrinterSettings.PrinterName = _
comboBoxPrinterName().SelectedItem.ToString

‘set the information label to the printer settings


labelColor().Text = CStr(printDocument.PrinterSettings.SupportsColor)

End Sub

End Class
Font, Text, and Printing Operations
209
CHAPTER 6

FontPad Print Example Module Changes 6


The only changes that were made to FontPad’s module code were additions to the global scope

FONT, TEXT, AND


OPERATIONS
declarations. We added the page setup and the printer settings values. Listing 6.8 lists this code

PRINTING
for your reference.

LISTING 6.8 FontPad Module (moduleFonts.vb)


Module modFontPad

‘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”

‘group of values used for the font settings dialog


Public fontFamilySetting As String = “Courier New”
Public fontStyleSetting As String = “Regular”
Public fontSizeSetting As String = “10”

‘group of values used for the page setup dialog


Public paperSize As String = “Letter”
Public paperSource As String = “Automatically Select”
Public pageOrientation As String = “PORTRAIT”
Public marginLeft As Single = 0.75
Public marginTop As Single = 0.75
Public marginRight As Single = 1
Public marginBottom As Single = 1

‘values for the print dialog


Public printerDefault As String = “Epson Stylus COLOR 640 ESC/P 2”
Public printString As String = “”

‘used to maintain a reference to the main form


Public formFontPad As FormMain

Suggestions for Further Exploration


➲ Extend the FontPad printing capabilities to include a Print Preview feature. Use the
PreviewPrintController and PreviewPageInfo classes on the Page Setup dialog box to
allow users to click through a visual representation of their printed document.
➲ Print shapes and bitmaps to the printer using the various Graphics class methods as ref-
erenced in Chapter 9, “Drawing Functions.”
Working with the .NET Namespaces
210
PART II

Printing and Font-Related Controls and Dialog


Boxes
The .NET namespaces (and VB .NET) would be lacking if they did not provide programmers
with a solid set of controls and common dialog boxes. Although this material is beyond the
scope of this book (as are the rest of the form controls), it is important that you be aware of
their existence. All the classes defined by these controls and dialog boxes make use of the
namespaces we’ve been discussing in this chapter.
Table 6.12 lists the various controls and dialog classes that are available for embedding into an
application. The use of these controls is straightforward enough. Of course, now that you know
how to use the classes from which these dialogs are derived, you’ll be able to roll your own
custom versions of these dialogs.

TABLE 6.12 System.Windows.Forms Printing-Related Controls

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

FONT, TEXT, AND


The items we’ve covered in this chapter represent the foundation for manipulating fonts and

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

• Directory and File Operations 216

• Reading and Writing to Files and


Streams 237

• Learning By Example: Adding Open and Save


to FontPad 252
Working with the .NET Namespaces
214
PART II

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

Key Classes Related to File I/O


Every application, image, database, message, and document is stored as some form of a file.
Directories and files are the warehouses and boxes of our applications. All our applications
spend time interacting with the file system at some level. And most of our applications require
us to read, write, or manipulate files and directories.
The .NET System.IO namespace provides us with a rich set of tools to write effective file I/O
code in a productive manner. This namespace encapsulates functionality related to both syn-
chronous and asynchronous reading and writing to files and streams. Table 7.1 presents the key
classes contained in the namespaces. These classes represent those primarily used by develop-
ers when implementing features based on directories, files, and streams.

TABLE 7.1 Key Classes of the System.IO Namespace


Class Description
Managing Directories
Directory The Directory class allows you to create, move, and enumerate
directories and subdirectories. All of its methods are static and
require a security check for each call. Consider the DirectoryInfo
class if you plan to reuse the object and do not want the overhead of
the security checks.
Stream and File Operations
215
CHAPTER 7

TABLE 7.1 Continued


Class Description
Managing Directories
DirectoryInfo The DirectoryInfo class is similar to the Directory class but con-
tains only instance methods. All objects created using this class ref-
erence a specific directory. As a result, a security check is
performed only when the instance is created.
Path The Path class allows you to process directory strings in a cross-
platform manner. 7
Manipulating Files

STREAM AND FILE


OPERATIONS
File The File class allows you to write code to create, copy, delete,
move, and open files. All its methods are static and thus perform a
security check on every call. If you plan to reuse the File instance
on a single file, use the instance class, FileInfo.
FileInfo The FileInfo class is similar to the File, but like DirectoryInfo,
provides instance methods.
Reading and Writing to Files and Streams
FileStream The FileStream class allows you to create a stream instance based
on a file.
StreamReader The StreamReader class implements a TextReader object that reads
characters from a byte stream in a particular encoding.
StreamWriter The StreamWriter class implements a TextWriter object for writ-
ing characters to a stream in a particular encoding.
StringReader The StringReader class implements a TextReader object that reads
from a string.
StringWriter The StringWriter class implements a TextWriter object that
writes information to a string. The information is stored in an under-
lying StringBuilder class (from the System.Text namespace).
BinaryReader The BinaryReader class allows you to access file data at the binary
level.
BinaryWriter The BinaryWriter class allows you to write binary data out to files.
Monitoring File System Activity
FileSystemWatcher The FileSystemWatcher class allows you to listen to the file system
change notifications and intercept events when a directory or file
changes.
Working with the .NET Namespaces
216
PART II

Directory and File Operations


In past versions of Visual Basic, we were relegated to the FileSystemObject (or a similar
third-party control) or the Win32 API to implement most file operations. The System.IO name-
space provides a robust set of objects for our tool belt. These objects can be leveraged to solve
a host of problems and ease and simplify interaction with the file system.
As programmers, it is important for us to understand the key aspects of the Windows file sys-
tem. A file, in Windows, is an ordered and named collection of a sequence of bytes having per-
sistent storage. When you think of files, you think of a filename, file size, attributes, and
directory path. Directories in Windows are simply another type of file that contains other files
and subdirectories. This section illustrates how to interact with the Windows file system using
the System.IO namespace.

Access and Attributes


To access directories and files using the .NET Class Library you work with two primary
classes: DirectoryInfo and FileInfo. These classes are designed to provide most of the file
system functionality and information that your applications need.
The DirectoryInfo and Directory classes are used to create, move, and enumerate through
directories and subdirectories. The Directory class contains the associated static methods,
while DirectoryInfo contains the instance methods. An instance of the DirectoryInfo class
represents one physical directory. With it, you can call the GetDirectories method to return a
list of the directory’s subdirectories. You can also return a list of files in the directory with the
GetFiles method. Of course, there are a number of other important properties and methods
inside the DirectoryInfo class.
The FileInfo and File classes are used to create, copy, delete, move, and open files. The
File class contains the associated static methods, where FileInfo contains the instance meth-
ods. Using an instance of the FileInfo class, you can return specific attributes of a given file.
For example, you can read the file’s name, size, and parent directory. The actual content of a
file is accessed via the FileStream object. The FileStream class allows for both synchronous
and asynchronous read and writes to a file.
To best illustrate the use of these classes, we will create a simple directory and file listing
application. The application will read a list of directories, and for each directory, display a list
of files contained by it. Additionally, for both the selected file and the selected directory, we
will list a number of key attributes. Figure 7.1 illustrates the form that we will use to display
the application’s information. Of course, I use the term application loosely. This is just some
sample code with an attached form.
Stream and File Operations
217
CHAPTER 7

STREAM AND FILE


OPERATIONS
FIGURE 7.1
Access and attributes form.

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.

LISTING 7.1 Access and Attributes


Public Class Form2
Inherits System.Windows.Forms.Form

‘form-level scope declarations


Dim myPath As String = “c:\”
Dim myDirName As String
Dim myFileName As String

#Region “ Windows Form Designer generated code “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call


End Sub

‘Form overrides dispose to clean up the component list.


Working with the .NET Namespaces
218
PART II

LISTING 7.1 Continued


Public Overloads Overrides Sub Dispose()
MyBase.Dispose()
If Not (components Is Nothing) Then
components.Dispose()
End If
End Sub

Private WithEvents label1 As System.Windows.Forms.Label


Private WithEvents label2 As System.Windows.Forms.Label
Private WithEvents labelDirectoryInfo As System.Windows.Forms.Label
Private WithEvents listBoxFiles As System.Windows.Forms.ListBox
Private WithEvents labelFileInfo As System.Windows.Forms.Label
Private WithEvents listBoxDirectories As System.Windows.Forms.ListBox
Private WithEvents buttonClose As System.Windows.Forms.Button

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub _
InitializeComponent()
Me.labelFileInfo = New System.Windows.Forms.Label()
Me.listBoxFiles = New System.Windows.Forms.ListBox()
Me.labelDirectoryInfo = New System.Windows.Forms.Label()
Me.listBoxDirectories = New System.Windows.Forms.ListBox()
Me.buttonClose = New System.Windows.Forms.Button()
Me.label1 = New System.Windows.Forms.Label()
Me.label2 = New System.Windows.Forms.Label()
Me.labelFileInfo.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D
Me.labelFileInfo.Location = New System.Drawing.Point(248, 216)
Me.labelFileInfo.Size = New System.Drawing.Size(200, 160)
Me.labelFileInfo.TabIndex = 4
Me.labelFileInfo.Text = “label3”
Me.listBoxFiles.Location = New System.Drawing.Point(8, 216)
Me.listBoxFiles.Size = New System.Drawing.Size(232, 160)
Me.listBoxFiles.TabIndex = 1
Me.labelDirectoryInfo.BorderStyle = _
System.Windows.Forms.BorderStyle.Fixed3D
Me.labelDirectoryInfo.Location = New System.Drawing.Point(248, 24)
Me.labelDirectoryInfo.Size = New System.Drawing.Size(200, 160)
Me.labelDirectoryInfo.TabIndex = 4
Me.labelDirectoryInfo.Text = “label3”
Me.listBoxDirectories.Location = New System.Drawing.Point(8, 24)
Stream and File Operations
219
CHAPTER 7

LISTING 7.1 Continued


Me.listBoxDirectories.Size = New System.Drawing.Size(232, 160)
Me.listBoxDirectories.TabIndex = 0
Me.buttonClose.Location = New System.Drawing.Point(376, 384)
Me.buttonClose.TabIndex = 5
Me.buttonClose.Text = “Close”
Me.label1.Location = New System.Drawing.Point(8, 8)
Me.label1.TabIndex = 2
Me.label1.Text = “Directories”
Me.label2.Location = New System.Drawing.Point(8, 200)
Me.label2.TabIndex = 3
7

STREAM AND FILE


Me.label2.Text = “Files”

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

Dim myDirectory As New System.IO.DirectoryInfo(path:=myPath).

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

‘purpose: load the form, set the control values

‘local scope
Dim myDirectories() As System.IO.DirectoryInfo
Dim i As Integer

‘create a new directory object pointed at c:


Dim myDirectory As New System.IO.DirectoryInfo(path:=myPath)
Working with the .NET Namespaces
220
PART II

‘return the sub directories of c: from the global directory object


myDirectories = myDirectory.GetDirectories()

‘loop through the directories and add them to the list box
For i = 0 To UBound(myDirectories) - 1

‘add the directory name to the list


listBoxDirectories().Items.Add(myDirectories(i).Name)

Next

‘select the first directory in the list


‘ note: this will trigger the events that fill the label control
‘ and the files list box and its label control
listBoxDirectories().SelectedIndex = 0

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

‘clear the listbox of its current contents


listBoxFiles().Items.Clear()

‘get a directory info object based on the user’s selection


myDirName = listBoxDirectories().SelectedItem.ToString & “\”
Stream and File Operations
221
CHAPTER 7

myDirectory = New System.IO.DirectoryInfo( _


path:=(myPath & myDirName))

‘set the dir info


dirInfo = dirInfo & “Path: “ & myDirectory.FullName & vbCrLf
dirInfo = dirInfo & “Attributes: “ & myDirectory.Attributes.ToString _
& vbCrLf
labelDirectoryInfo().Text = dirInfo

‘get the files in the directory


myFiles = myDirectory.GetFiles() 7

STREAM AND FILE


‘check for files

OPERATIONS
If UBound(myFiles) >= 0 Then

‘loop through the files array and add to the listbox


For i = 0 To UBound(myFiles) - 1
listBoxFiles().Items.Add(myFiles(i).Name)
Next

‘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

As a filename is selected (listBoxFiles_SelectedIndexChanged event) we update the con-


tents of the file information label. To do this we create an instance of the FileInfo class based
on the file’s path and name. We then read some of the file’s properties and display them to a
label control.
Private Sub listBoxFiles_SelectedIndexChanged(ByVal sender As _
System.Object, ByVal e As System.EventArgs) Handles _
listBoxFiles.SelectedIndexChanged

‘purpose: change the contents of the file properties label

‘local scope
Dim myFile As System.IO.FileInfo
Dim fileInfo As String

‘set the file name


myFileName = listBoxFiles().SelectedItem.ToString

‘create a new file object


Working with the .NET Namespaces
222
PART II

myFile = New System.IO.FileInfo(fileName:=myPath & myDirName & _


myFileName)

‘set the file info to display


fileInfo = fileInfo & “Directory: “ & myFile.DirectoryName & vbCrLf
fileInfo = fileInfo & “Created Time: “ & myFile.CreationTime & vbCrLf
fileInfo = fileInfo & “Size: “ & myFile.Length & vbCrLf
fileInfo = fileInfo & “Last Accessed: “ & myFile.LastAccessTime & _
vbCrLf
fileInfo = fileInfo & “Attributes: “ & myFile.Attributes.ToString & _
vbCrLf

‘set the label to the file’s info


labelFileInfo().Text = fileInfo

End Sub

Private Sub buttonClose_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles buttonClose.Click

‘purpose: end the application

‘close the form


Me.Close()

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.

TABLE 7.2 Properties of the FileInfo Class


Property Data Type Description
Attributes FileSystemInfo.Attributes The Attributes property is used to
get or set the file’s attributes. The
value of the property is based on a
combination of the file-attribute
flags found in
FileSystemInfo.Attributes.
Values include: archive, com-
pressed, directory, hidden, offline,
read-only, system, and temporary.
Stream and File Operations
223
CHAPTER 7

TABLE 7.2 Continued


Property Data Type Description
CreationTime Datetime The CreationTime property returns
or sets the time that a file was cre-
ated.
Directory Directory class The Directory property returns an
instance of the file’s parent direc-
tory as the Directory class.
DirectoryName String The DirectoryName property 7
returns the full path to the file.

STREAM AND FILE


OPERATIONS
Exists Boolean The Exists property returns true if
a file physically exists and false if it
does not.
Extension String The Extension property returns the
file’s extension (.txt for text files).
FullName String The FullName property returns the
complete path to the file and its
name.
LastAcccessTime DateTime The LastAccessTime property
returns or sets the time that the file
was last accessed.
LastWriteTime DateTime The LastWriteTime property
returns or sets the time the file was
last written to.
Length Long The Length property returns the
size of the file in bytes.
Name String The Name property returns the name
of the file.

Creation, Deletion, and Manipulation


So far we’ve looked at how we access directories and files and return their attributes. Now we
will explore the basic tasks of creating, deleting, and moving files and directories. The .NET
Framework’s System.IO offers us all the necessary methods to perform these tasks.

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.

LISTING 7.2 Directories Create, Move, and Delete


Module Module1

Sub Main()

‘purpose: demonstrate code that does the following:


‘ 1. creates a directory
‘ 2. creates a sub directory
‘ 3. moves the sub directory under the first directory
‘ 4. deletes the sub directory

‘local scope
Dim myDirectoryParent As System.IO.DirectoryInfo
Dim myDirectory2 As System.IO.DirectoryInfo

‘create a new instance of the directory info object


myDirectoryParent = New System.IO.DirectoryInfo(path:=”c:\”)

‘create a sub directory under the parent directory


myDirectoryParent.CreateSubdirectory(path:=”new directory 1”)

‘create another sub directory under the parent directory


myDirectoryParent.CreateSubdirectory(path:=”new directory 2”)
Stream and File Operations
225
CHAPTER 7

LISTING 7.2 Continued


‘create a directory object based on the 2nd directory
myDirectory2 = New System.IO.DirectoryInfo( _
path:=”c:\new directory 2”)

‘move the second directory under the first


‘note: you must supply its new name (can be the same)
myDirectory2.MoveTo( _
destDirName:=”c:\new directory 1\new directory 2”)

‘delete the second directory and its contents


7

STREAM AND FILE


‘note: you could set the recursive property to true

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

LISTING 7.3 Files Create, Move, CopyTo, and Delete


Imports System.IO

Module Module1

Sub Main()

‘purpose: demonstrate code that does the following:


‘ 1. creates a file
‘ 3. moves the file to a different directory
‘ 4. copies the file to another directory
‘ 5. deletes the file

‘local scope
Dim myFileStream As FileStream
Dim myFile As FileInfo
Dim myCopiedFile As FileInfo

‘check if file exists, if so delete it


If File.Exists(path:=”c:\newFile.txt”) Then
File.Delete(path:=”c:\newFile.txt”)
End If

‘create a new file using, set = to file stream object


myFileStream = File.Create(path:=”c:\newFile.txt”)

‘close the file stream object


myFileStream.Close()

‘get the newly created file


myFile = New FileInfo(fileName:=”c:\newFile.txt”)

‘check if move to file exists, if so delete it


If File.Exists(path:=”c:\moveFile.txt”) Then
File.Delete(path:=”c:\moveFile.txt”)
End If

‘move the file


myFile.MoveTo(destFileName:=”c:\moveFile.txt”)

‘check to see if directory exists, else create it


If Not System.IO.Directory.Exists(path:=”C:\new directory 1”) Then
System.IO.Directory.CreateDirectory( _
path:=”C:\new directory 1”)
End If

‘copy the file back to the directory


Stream and File Operations
227
CHAPTER 7

LISTING 7.3 Continued


‘overwrite an existing file if need be
myCopiedFile = myFile.CopyTo( _
destFileName:=”C:\new directory 1\copyFile.txt”, _
overwrite:=True)

‘wait for the user to stop the console application


‘this allows you to view the directories and the results
Console.WriteLine(“Enter ‘s’ to stop the application.”)

‘loop until user presses s key


7

STREAM AND FILE


Do While Console.ReadLine <> “s” : Loop

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.

TABLE 7.3 FileAccess Enumeration Members

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.

TABLE 7.4 FileAttributes Enumeration Members

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

TABLE 7.4 Continued


Member Description
Directory The Directory file attribute indicates that a file is actually a
directory.
Encrypted The Encrypted member indicates that a file is encrypted.
Hidden The Hidden value indicates that the file is hidden, and thus not
included in an ordinary directory listing.
Normal The Normal value indicates the file has no other attributes set.
NotContentIndexed The NotContentIndexed value indicates that the file will not be
indexed by Windows’ indexing service.
Offline The Offline value indicates that the file’s data is not readily
accessible (offline).
ReadOnly The ReadOnly member indicates that the file cannot be modified;
it is for reading only.
ReparsePoint The ReparsePoint member indicates that the file contains a block
of user-defined data (reparse point).
SparsePoint The SparsePoint member indicates that the file is a sparse file.
Sparse files are large files whose data are mostly zero values.
System The System member marks a file as being part of the operating
system or used exclusively by the operating system.
Temporary The Temporary value indicates that a file is temporary. Temporary
files should be created and deleted by applications as they are
needed and no longer needed.

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.

TABLE 7.5 FileMode Enumeration Members

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

TABLE 7.5 Continued


Member Description
Create The Create parameter specifies that Windows create a new file. If the file
already exists, it will be overwritten. This requires
FileIOPermissionAccess.Write and FileIOPermissionAccess.Append
FileIOPermission.
CreateNew The CreateNew parameter indicates that Windows should create a new
file. This requires FileIOPermissionAccess.Read and
FileIOPermissionAccess.Append FileIOPermission. If the file already 7
exists, an IOException is thrown.

STREAM AND FILE


Open The Open member indicates that Windows should open an existing file.

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.

TABLE 7.6 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

TABLE 7.6 Continued


Member Description
Write The Write parameter indicates that a file can be open for writing but not
necessarily reading. This can be combined with Read to mimic the
ReadWrite parameter.

Monitoring the File System


It seems there is always a need to monitor a directory for file drops or respond to file system
events. For example, suppose your team needs to be notified whenever a documentation arti-
fact for the project you’re working on is created or modified. Wouldn’t it be nice if you could
write a simple monitoring service that responds to these events and notifies the team?
One of the most interesting objects in the namespace is the FileSystemWatcher class. This
class can be easily configured to monitor events within directories. Before the file watcher
class, Visual Basic programmers often had to implement a message queue application, monitor
SMTP, or write a service which, at different intervals, queried a directory to check for changes.
.NET provides the FileSystemWatcher as an easy-to-use class to solve these common pro-
gramming dilemmas.

Identifying What to Monitor


The FileSystemWatcher can be used to monitor changes made to files and subdirectories of a
specified parent directory. The component can be configured to work with the local file sys-
tem, a directory on the local network, or a remote machine.
The FileSystemWatcher class allows you to
• Watch files from either a Windows 2000 or an NT 4 install.
• Watch all files, including those marked hidden.
The FileSystemWatcher does not allow you to
• Watch a remote NT 4 computer from another NT 4 computer.
• Work with CDs or DVDs because files on these devices are fixed and cannot change.
There are a number of properties of the FileSystemWatcher class that allow us to target the
objects we wish to watch. The properties developers will use most often are Filter and Path.
Path simply indicates the directory path to watch. Filter indicates the type of files to watch.
The format for Filter uses Windows’ standard wildcard notation to set its value. If you do not
set the filter property, its default is *.* (star dot star), which indicates that you are monitoring
Stream and File Operations
231
CHAPTER 7

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.

TABLE 7.7 Configuration Properties of the FileSystemWatcher Class


Property Scenario Description
Path The Path property lets you indicate what directory to watch.
You can indicate a path using standard directory path notation
(c:\myDirectory) or in UNC format (\\serverName\directory\ 7
name).

STREAM AND FILE


Set the Filter property to watch for changes made to a file or

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

TABLE 7.8 NotifyFilters Enumeration Members

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.

Responding to File System Events


At this point, we’ve indicated what we are watching and what events interest us; now we must
hook up those events to our application. The FileSystemWatcher raises an event when files or
directories are created, deleted, renamed, or otherwise changed. The events are raised and
stored in a buffer before being passed to our instance of the FileSystemWatcher.

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

STREAM AND FILE


approximately 160 files in a directory. You can reset the buffer size to better match your needs

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.

TABLE 7.9 FileSystemWatcher Events

Event VB Handler and Function Signature


Changed AddHandler myWatcher.Changed, AddressOf watcher_OnChange Sub
watcher_OnChange(ByVal source As Object, _ByVal e As
IO.FileSystemEventArgs)
Created AddHandler myWatcher.Created, AddressOf watcher_OnCreate Sub
watcher_OnCreate(ByVal source As Object, _ByVal e As
IO.FileSystemEventArgs)
Working with the .NET Namespaces
234
PART II

TABLE 7.9 Continued


Event VB Handler and Function Signature
Deleted AddHandler myWatcher.Deleted, AddressOf watcher_OnDelete Sub
watcher_OnChange(ByVal source As Object, _ByVal e As
IO.FileSystemEventArgs)
Error AddHandler myWatcher.Error, AddressOf watcher_OnError Sub
watcher_OnError(ByVal source As Object, _ByVal e As
IO.ErrorEventHandler)
Renamed AddHandler myWatcher.Renamed, AddressOf watcher_OnRename Sub
watcher_OnRename(ByVal source As Object, _ByVal e As IO.
RenamedEventHandler)

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.

TABLE 7.10 WatcherChangeTypes Enumeration Members

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

LISTING 7.4 FileSystemWatcher Directory Monitor

Imports System.IO

Module Module1

Sub Main()

‘Call directory()
Call watchDirectory(watchPath:=”c:\watch”)

End Sub 7

STREAM AND FILE


OPERATIONS
Sub watchDirectory(ByVal watchPath As String)

‘purpose: watch a directory for changes to files

‘local scope
Dim myWatcher As FileSystemWatcher
Dim stopValue As String

‘check if directory exits, no = create


If Not Directory.Exists(path:=watchPath) Then
Directory.CreateDirectory(path:=watchPath)
End If

‘instantiate a new system watcher object


myWatcher = New FileSystemWatcher()

‘set the path of directory to watch


myWatcher.Path = watchPath

‘tell the watcher object to watch only for text files


myWatcher.Filter = “*.txt”

‘tell the watcher the type of changes to watch for


myWatcher.NotifyFilter = IO.NotifyFilters.DirectoryName _
Or IO.NotifyFilters.LastAccess _
Or IO.NotifyFilters.LastWrite _
Or IO.NotifyFilters.FileName

‘intercept the watcher events


AddHandler myWatcher.Changed, AddressOf watcher_OnChange
AddHandler myWatcher.Deleted, AddressOf watcher_OnChange
AddHandler myWatcher.Created, AddressOf watcher_OnChange
AddHandler myWatcher.Renamed, AddressOf watcher_OnRename
Working with the .NET Namespaces
236
PART II

LISTING 7.4 Continued


‘tell the watcher to start watching
myWatcher.EnableRaisingEvents = True

‘tell the user that watching is on


Console.WriteLine(“Watching “ & watchPath & “...”)

‘wait for the user to stop the console application


Console.WriteLine(“Press ‘s’ to stop the application.”)

‘loop until user presses s key


Do While Console.ReadLine <> “s” : Loop

End Sub

Sub watcher_OnChange(ByVal source As Object, _


ByVal e As IO.FileSystemEventArgs)

‘purpose: respond to the changed notify event


‘ (created, changed, deleted)

‘indicate that a file is changed, created, or deleted


Console.WriteLine(“File: “ & e.FullPath & “ “ & e.ChangeType)

End Sub

Sub watcher_OnRename(ByVal source As Object, _


ByVal e As IO.RenamedEventArgs)

‘purpose: respond to the renamed watcher notify event

‘tell the user the file that was renamed


Console.WriteLine(“File: {0} renamed to {1}”, e.OldFullPath, _
e.FullPath)

End Sub

End Module

Suggestions for Further Exploration


➲ To read similar information about working with file I/O in .NET, read the MSDN chapter
found at: Visual Studio .NET/.NET Framework/Programming with the .NET
Framework/Working with I/O.
Stream and File Operations
237
CHAPTER 7

➲ 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.

Reading and Writing to Files and Streams 7


As programmers, we often have to write directly to a file or data stream. If you’ve communi-

STREAM AND FILE


OPERATIONS
cated with disparate systems, you are undoubtedly familiar with writing out CSV or XML files
as a means of exchanging data. The .NET Framework gives us a group of classes and methods
inside the System.IO namespace that allows us to access data streams and files both synchro-
nously and asynchronously.
This section will define the AppendText, CreateText, Open, OpenRead, and OpenWrite meth-
ods of the FileInfo class.
File and data streams are essentially the same thing. They both are a type of stream. Their dif-
ferences lie in their backing store. Backing store refers to a storage medium, such as a disk,
tape, memory, network, and so on. Every backing store implements its own stream type as a
version of the Stream class. This allows each stream type to read and write bytes to and from
its own backing store. These streams that connect directly to backing stores are called base
streams in .NET. An example of a base stream is the FileStream class. This class gives us
access to files stored on disk inside of directories.

Reading and Writing Files


To read data to and from files using the System.IO namespace, we primarily use two classes:
FileInfo and FileStream. The FileInfo class exposes a number of methods that allow us
access to stream-related functions based on a file. These methods simply use the FileStream
and related classes to expose this functionality. However, they are useful as you often already
have an instance of FileInfo that is specific to a given file. You can then call these methods to
return and write to the contents of this file. Table 7.11 lists these methods and their associated
return types.
Working with the .NET Namespaces
238
PART II

TABLE 7.11 FileInfo Streaming Methods

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.

TABLE 7.12 FileStream Constructors

New FileStream (ByVal handle as IntPtr, ByVal access as FileAccess)


handle: A valid handle to a file.
access: A member of the FileAccess enumeration (Read, ReadWrite, Write).
Note: Use this constructor when you have a valid file pointer and need to specify the
read/write permissions.
New FileStream (ByVal path as String, ByVal mode as FileMode)
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.
Note: Use this constructor when you know the file’s path and wish to specify how the file is
opened.
Stream and File Operations
239
CHAPTER 7

TABLE 7.12 Continued


New FileStream (ByVal handle as IntPtr, ByVal access as FileAccess, _ByVal
ownsHandle as Boolean)
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.
Note: Use this constructor when you have a valid file pointer, need to specify the read/write
permissions, and wish to own (or pass off) the file’s handle. If the FileStream object owns 7
the file’s handle, a call to the Close method will also close the file’s handle and thus decre-

STREAM AND FILE


ment its handle count by one.

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

TABLE 7.12 Continued


FileShare controls how other FileStream objects can access the same file. Values include:
Inheritable, None, Read, ReadWrite, and 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, _ByVal isAsync as Boolean)
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.
isAsync: Indicates if the file should be opened asynchronously.
Note: Use this constructor when you have a valid file pointer, need to specify the read/write
permissions, wish to own (or pass off) the file’s handle, need to set the buffer size, and wish
to indicate the file should be opened asynchronously.
New FileStream (ByVal path as String, ByVal mode as FileMode, _ByVal access as
FileAccess, ByVal share as FileShare, _ByVal bufferSize as Integer)
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.
FileShare controls how other FileStream objects can access the same file. Values include:
Inheritable, None, Read, ReadWrite, and Write.
bufferSize: Indicates the size of the buffer in bytes.
Note: Use this constructor when you know the file’s path, wish to specify how the file is
opened, need to specify the read/write permissions on the file, 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, _ByVal bufferSize as Integer, ByVal
useAsynch as Boolean)
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.
Stream and File Operations
241
CHAPTER 7

TABLE 7.12 Continued


FileShare controls how other FileStream objects can access the same file. Values include:
Inheritable, None, Read, ReadWrite, and Write.
bufferSize: Indicates the size of the buffer in bytes.
useAsync: Indicates if the file should be opened asynchronously.
Note: Use this constructor when you know the file’s path, wish to specify how the file is
opened, need to specify the read/write permissions on the file, need to set the stream’s
buffer size, and need to indicate if the file is being opened for asynchronous read/write.
7

STREAM AND FILE


Listing 7.5 provides an example of the FileStream, StreamWriter, and StreamReader classes.

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.

LISTING 7.5 FileStream, StreamWriter, and StreamReader

Imports System.IO

Module Module1

Sub Main()

‘purpose: open a file and append infromation to its end

‘local scope
Dim fileStream As FileStream
Dim streamWriter As StreamWriter
Dim streamReader As StreamReader

‘create a new instance of the file stream object


‘note: if the file does not exist, the constructor create it
fileStream = New fileStream(path:=”c:\test.txt”, _
mode:=FileMode.OpenOrCreate, access:=FileAccess.Write)

‘create an instance of a character writer


streamWriter = New StreamWriter(stream:=fileStream)

‘set the file pointer to the end of the file


streamWriter.BaseStream.Seek(offset:=0, origin:=SeekOrigin.End)

‘write a line of text to the end of the file


streamWriter.WriteLine(value:=”This is a test”)
Working with the .NET Namespaces
242
PART II

LISTING 7.5 Continued

‘apply the update to the file


streamWriter.Flush()

‘close the stream writer


streamWriter.Close()

‘close the file stream object


fileStream.Close()

‘create a new instance of file stream to read the file back


fileStream = New fileStream(path:=”c:\test.txt”, _
mode:=FileMode.OpenOrCreate, access:=FileAccess.Read)

‘create a stream reader instance


streamReader = New StreamReader(stream:=fileStream)

‘set the file pointer to the start of the file


streamReader.BaseStream.Seek(offset:=0, _
origin:=SeekOrigin.Begin)

‘loop through the file and write to console until the


‘ end of file reached
Do While streamReader.Peek > -1
Console.WriteLine(value:=streamReader.ReadLine())
Loop

‘close the stream reader


streamReader.Close()

‘wait for the user to stop the console application


Console.WriteLine(“Press ‘s’ to stop the application.”)

‘loop until users presses s key


Do While Console.ReadLine <> “s” : Loop

End Sub

End Module

Asynchronous Reading and Writing


As stated earlier, streaming with the .NET Framework classes can be done both synchronously
and asynchronously. Synchronous reading and writing blocks methods from continuing until
Stream and File Operations
243
CHAPTER 7

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

STREAM AND FILE


orders.

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:

New AsyncCallback(AddressOf myCallbackMethod)

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.

LISTING 7.6 Asynchronous File Reading


Imports System.IO

Module Module1

Sub Main()
Working with the .NET Namespaces
244
PART II

LISTING 7.6 Continued


‘purpose: provide example code of asynch. file I/O

‘steps: 1. start asynch. read and processing of 3 files of


‘ varying lengths
‘ 2. wait for callback and display to screen

‘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”

‘call each asynch. read


For i = 0 To 2
Console.WriteLine(“Starting file read: “ & myFiles(i))
Call asynchRead(filePath:=myFiles(i))
Next

‘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

‘wait for the user to stop the console application


Console.WriteLine(“******** Enter ‘s’ to stop the application.”)

‘loop until user presses s key


Do While Console.ReadLine <> “s” : Loop

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

FileInfo.Length again to indicate we want to read the entire file.


Private Sub asynchRead(ByVal filePath As String)

‘purpose: execute an asynch. read against a given file


‘ throw an exception if the file is not found

‘local scope
Dim fileStream As FileStream
Dim state As StateObject
Dim fileInfo As FileInfo
7

STREAM AND FILE


‘check to see if the file exists

OPERATIONS
If Not File.Exists(path:=filePath) Then

‘file does not exist = throw an exception


Throw New Exception(message:=”File not found.”)

End If

‘file exists = create an open instance of the file


fileStream = New FileStream(path:=filePath, mode:=FileMode.Open)

‘determine size of the file to set the number of bytes


fileInfo = New FileInfo(fileName:=filePath)
Console.WriteLine(“File length: “ & fileInfo.Length)

‘create a state object


state = New StateObject(filePath:=filePath, _
byteArraySize:=fileInfo.Length)

‘set fileStream prop (useful for callback)


state.FileStream = fileStream

‘begin the file read


fileStream.BeginRead(array:=state.ByteArray, offset:=0, _
numBytes:=fileInfo.Length, _
userCallback:=New AsyncCallback(AddressOf fileRead), _
stateObject:=state)

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.

Private Sub fileRead(ByVal asyncResult As IAsyncResult)

‘purpose: provide a callback method for asynch reads


Working with the .NET Namespaces
246
PART II

‘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

‘write out the path of the object read


Console.WriteLine(state.FilePath)

‘indicate that the file was read asynch.


If asyncResult.CompletedSynchronously Then
Console.WriteLine(“File was read synchronously.”)
Else
Console.WriteLine(“File was read asynchronously.”)
End If

‘determine the number of bytes read by calling EndRead


bytesRead = state.FileStream.EndRead(asyncResult)

‘write out bytes read


Console.WriteLine(“Bytes read: “ & bytesRead)

‘close the file stream


state.FileStream.Close()

End Sub

End Module

Public Class StateObject

‘purpose: maintain state information across asynch calls

‘class-level scope
Private localFilePath As String
Private localByteArray() As Byte

‘public properties
Public FileStream As FileStream

Public Property ByteArray() As Byte()

‘purpose: get and set ByteArray property


Stream and File Operations
247
CHAPTER 7

Get
Return localByteArray
End Get

Set(ByVal Value() As Byte)


localByteArray = Value
End Set

End Property

Public Property FilePath() As String 7

STREAM AND FILE


‘purpose: get and set FilePath prop.

OPERATIONS
Get
Return localFilePath
End Get

Set(ByVal Value As String)


localFilePath = Value
End Set

End Property

Sub New(ByVal filePath As String, ByVal byteArraySize As Integer)

‘purpose: constructor, allows setting of file path info.


‘ and byte array size info.

‘set local file path info


localFilePath = filePath

‘dimension the size of the byte array


ReDim localByteArray(byteArraySize)

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.

Binary Reading and Writing


Thus far, we’ve dealt primarily with text files. While it is true that text files make up the
majority of business programming I/O tasks, you will often need to read and write files of a
proprietary type. To do so, you will access them at the binary level. Suppose that you need to
accept an Excel file streaming across the wire, chances are you will want to persist it to disk
using a binary reader and writer. Or suppose you want to read image files and store them in
your database. Again, a binary reader will make this operation go smoothly.
We have a number of options open to us for file I/O at the binary level. The principal ones
include using the BinaryReader, BinaryWriter, and FileStream classes. The best thing is
that, for the most part, you already know how to use these objects. BinaryReader and
BinaryWriter are similar to StreamReader and StreamWriter, respectively. Like these
classes, BinaryReader and BinaryWriter take an instance of a valid Stream object as a para-
meter of their constructor. The Stream object represents the backing store that is being read
from or written to.
The BinaryReader class provides a number of read methods that allow us to access primitive
data types from our file streams. Each read method returns the given data from the stream and
advances the current position in the stream ahead of the returned data. The reader you’re likely
to use most often is ReadByte. This returns one byte of data from the stream and advances the
current stream position to the next byte. When the end of the stream is reached, the exception,
EndOfStreamException, is thrown by the method. Other read methods include ReadBytes,
ReadString, ReadDecimal, and ReadBoolean to name a few.

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

STREAM AND FILE


ple, we use BinaryReader to read the contents of a bitmap file, one byte at a time. At the same

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.

LISTING 7.7 Binary Reading and Writing

Imports System.IO

Module Module1

Sub Main()

‘purpose: read a binary file and write contents to diff. file

‘local scope
Dim fsRead As FileStream
Dim fsWrite As FileStream
Dim bRead As BinaryReader
Dim b As Byte
Dim bWrite As BinaryWriter

‘check if read file exists


If Not File.Exists(path:=”c:\test.bmp”) Then

Console.WriteLine(“File, test.bmp, not found”)

‘waite for user input


Console.WriteLine(“Enter ‘s’ to stop the application.”)
Do While Console.ReadLine <> “s” : Loop

End
Working with the .NET Namespaces
250
PART II

LISTING 7.7 Continued


End If

‘create a fileStream instance to pass to BinaryReader object


fsRead = New FileStream(path:=”c:\test.bmp”, mode:=FileMode.Open)

‘check if write file exists


If File.Exists(path:=”c:\test2.bmp”) Then

‘delete file
File.Delete(path:=”c:\test2.bmp”)

End If

‘create a fileStream instance to pass to BinaryWriter object


fsWrite = New FileStream(path:=”c:\test2.bmp”, _
mode:=FileMode.CreateNew, access:=FileAccess.Write)

‘create binary writer instance


bWrite = New BinaryWriter(output:=fsWrite)

‘create instance of binary reader


bRead = New BinaryReader(Input:=fsRead)

‘set the file pointer to the start of the file


bRead.BaseStream.Seek(offset:=0, _
origin:=SeekOrigin.Begin)

‘loop until can no longer read bytes from file


Do While True

Try

‘read next byte and advance reader


b = bRead.ReadByte

Catch
Exit Do
End Try

‘write byte out


bWrite.Write(value:=b)
Stream and File Operations
251
CHAPTER 7

LISTING 7.7 Continued


Loop

‘close the reader


bRead.Close()

‘close the writer


bWrite.Close()

‘close the file streams


fsRead.Close()
7

STREAM AND FILE


fsWrite.Close()

OPERATIONS
‘wait for the user to stop the console application
Console.WriteLine(“Operation complete.”)
Console.WriteLine(“Enter ‘s’ to stop the application.”)

‘loop until user presses s key


Do While Console.ReadLine <> “s” : Loop

End Sub

End Module

Suggestions for Further Exploration


➲ To create a stream whose backing is memory (and not disk), check out the MemoryStream
class. This class is useful in that it can reduce your need for direct file I/O inside your
application and provide you with a temporary buffer.
➲ Depending on your application, you can sometimes garner additional performance by
implementing the BufferedStream class. Note that FileStream has internal buffering of
its own and is often sufficient. Programming with BufferedStream is similar to the other
stream classes we’ve discussed.
➲ To store data using isolated stores, check out the namespace
System.IO.IsolatedStorage. Isolated stores are secure data compartments that are only
accessible by the given user or code assembly. Data can also be isolated at the domain
level and user data can travel with them using roaming profiles. For more information,
check out the MSDN chapter at: Visual Studio .NET/.NET Framework/Programming
with the .NET Framework/Working with I/O/Performing Isolated Storage Tasks.
Working with the .NET Namespaces
252
PART II

Learning By Example: Adding Open and Save


to FontPad
Recall FontPad from Chapter 6, “Font, Text, and Printing.” This was our basic text editor to
which we then added printing capabilities. We will round out this application by adding basic
file I/O. After all, what’s a text editor worth if it can’t open and save files? By the time we’re
done, we should have completely replaced (or duplicated, anyway) the functionality of
Notepad.
This example extends FontPad with an open and save dialog. Therefore, only the open and
save dialogs are discussed here. To run the application, you should download the full source at
www.samspublishing.com. After walking through the application, you should be able to extend
the example as a test harness in which you can experiment with your own code.

Key Concepts Covered


The following represents the key class and concepts demonstrated with this sample application:
• Accessing directory properties and enumerating directories with the DirectoryInfo
class.
• Accessing file properties and enumerating files in a directory with the FileInfo class.
• Reading a file with the FileStream and StreamReader classes.
• Persisting a file to disk with the FileStream and StreamWriter classes.

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

STREAM AND FILE


OPERATIONS
FIGURE 7.3
FontPad: Open form screen shot.

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.

LISTING 7.8 FontPad: Open Form (formOpen.vb)


Public Class formOpen

Inherits System.Windows.Forms.Form

‘form-level scope declarations


Dim myDirName As String
Dim myFileName As String

#Region “ Windows Form Designer generated code “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call

End Sub
Working with the .NET Namespaces
254
PART II

LISTING 7.8 Continued


‘Form overrides dispose to clean up the component list.
Public Overloads Overrides Sub Dispose()
MyBase.Dispose()
If Not (components Is Nothing) Then
components.Dispose()
End If
End Sub

Private WithEvents buttonOk As System.Windows.Forms.Button


Private WithEvents buttonCancel As System.Windows.Forms.Button
Private WithEvents listBox1 As System.Windows.Forms.ListBox
Private WithEvents labelCurrentDir As System.Windows.Forms.Label
Private WithEvents buttonUp As System.Windows.Forms.Button
Private WithEvents labelDirInfo As System.Windows.Forms.Label
Private WithEvents label2 As System.Windows.Forms.Label
Private WithEvents label1 As System.Windows.Forms.Label
Private WithEvents labelFileInfo As System.Windows.Forms.Label
Private WithEvents listBoxFiles As System.Windows.Forms.ListBox
Private WithEvents listBoxDirectories As System.Windows.Forms.ListBox
Private WithEvents label5 As System.Windows.Forms.Label
Private WithEvents label3 As System.Windows.Forms.Label

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub _
InitializeComponent()
Me.labelDirInfo = New System.Windows.Forms.Label()
Me.listBoxFiles = New System.Windows.Forms.ListBox()
Me.labelCurrentDir = New System.Windows.Forms.Label()
Me.buttonCancel = New System.Windows.Forms.Button()
Me.buttonUp = New System.Windows.Forms.Button()
Me.listBoxDirectories = New System.Windows.Forms.ListBox()
Me.listBox1 = New System.Windows.Forms.ListBox()
Me.label5 = New System.Windows.Forms.Label()
Me.labelFileInfo = New System.Windows.Forms.Label()
Me.buttonOk = 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.labelDirInfo.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D
Me.labelDirInfo.Location = New System.Drawing.Point(224, 96)
Stream and File Operations
255
CHAPTER 7

LISTING 7.8 Continued


Me.labelDirInfo.Size = New System.Drawing.Size(204, 72)
Me.labelDirInfo.TabIndex = 7
Me.listBoxFiles.Location = New System.Drawing.Point(8, 196)
Me.listBoxFiles.Size = New System.Drawing.Size(212, 134)
Me.listBoxFiles.TabIndex = 9
Me.labelCurrentDir.BorderStyle = _
System.Windows.Forms.BorderStyle.Fixed3D
Me.labelCurrentDir.Location = New System.Drawing.Point(8, 28)
Me.labelCurrentDir.Size = New System.Drawing.Size(420, 16)
Me.labelCurrentDir.TabIndex = 6
7

STREAM AND FILE


Me.labelCurrentDir.Text = “Current: “

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

LISTING 7.8 Continued

Me.label3.Size = New System.Drawing.Size(100, 16)


Me.label3.TabIndex = 8
Me.label3.Text = “Files”
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(433, 366)
Me.Controls.AddRange(New System.Windows.Forms.Control() {Me.label5, _
Me.labelFileInfo, Me.label3, Me.listBoxFiles, Me.label1, _
Me.labelCurrentDir, Me.buttonUp, Me.listBoxDirectories, _
Me.labelDirInfo, Me.label2, Me.listBox1, Me.buttonOk, _
Me.buttonCancel})
Me.Text = “Open Text File”

End Sub

On the form load event we simply make a call to the sub procedure that loads the directory
list box.

Private Sub formOpen_Load(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles MyBase.Load

‘purpose: load the form, init the controls

‘local scope

‘load the directory list box


Call loadDirListBox()

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.

Private Sub loadDirListBox()

‘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

LISTING 7.8 Continued


‘clear the directory list box
listBoxDirectories().Items().Clear()

‘create a new directory object


myDirectory = New System.IO.DirectoryInfo(path:=myPath)

‘return the sub directories from the global directory object


myDirectories = myDirectory.GetDirectories()

‘loop through the directories and add them to the list box
7

STREAM AND FILE


For i = 0 To UBound(myDirectories) - 1

OPERATIONS
‘add the directory name to the list
listBoxDirectories().Items.Add(myDirectories(i).Name)

Next

‘set the current label to indicate the current path


labelCurrentDir().Text = “Current: “ & myPath

‘select the first directory in the list


‘ note: this will trigger the events that fill the label control
‘ and the files list box and its label control
If listBoxDirectories().Items.Count > 0 Then
listBoxDirectories().SelectedIndex = 0
End If

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

‘purpose: respond to the OK button event


‘ validate the form
‘ open the user’s selected file

‘determine if the user does indeed have a text file selected


If Not (listBoxFiles().SelectedIndex > -1) Then

‘tell the user to select a text file to continue


MsgBox(prompt:=”Please select a text file.”, _
buttons:=MsgBoxStyle.Information, title:=”FontPad Open”)
Working with the .NET Namespaces
258
PART II

LISTING 7.8 Continued


Else

‘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()

‘close the form


Me.Close()

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()

‘purpose: read a file as a stream object


‘ and put its content in the rich text box

‘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)

‘create a stream reader instance


streamReader = New IO.StreamReader(stream:=fileStream)

‘set the file pointer to the start of the file


streamReader.BaseStream.Seek(offset:=0, _
origin:=IO.SeekOrigin.Begin)

‘set the textContents to nothing


‘note the rich text box will be updated on form close
textContents = “”
Stream and File Operations
259
CHAPTER 7

LISTING 7.8 Continued


‘loop through the file and write to internal string variable
‘ until end of file reached
Do While streamReader.Peek > -1
textContents = textContents & streamReader.ReadLine() & vbCrLf
Loop

‘close the stream reader


streamReader.Close()

Catch
7

STREAM AND FILE


OPERATIONS
‘indicate to the user that the file cannot be opened
MsgBox(prompt:=”Sorry, unable to open the selected file”, _
buttons:=MsgBoxStyle.Exclamation, title:=”FontPad Open”)

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

‘purpose: manange the double-click event


‘ which allows users to navigate down directories

‘local scope

‘set the new global path


myPath = myPath & myDirName

‘update the list directory list box


Call loadDirListBox()

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

LISTING 7.8 Continued


Private Sub listBoxDirectories_SelectedIndexChanged( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles listBoxDirectories.SelectedIndexChanged

‘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

‘clear the listbox of its current contents


listBoxFiles().Items.Clear()

‘clear the list box label


labelFileInfo().Text = “”

‘get a directory info object based on the user’s selection


myDirName = listBoxDirectories().SelectedItem.ToString & “\”
myDirectory = New System.IO.DirectoryInfo( _
path:=(myPath & myDirName))

‘set the dir info


dirInfo = dirInfo & “Attributes: “ & myDirectory.Attributes.ToString
labelDirInfo().Text = dirInfo

‘get the files in the directory that are of type text


‘NOTE: we handle security access exceptions
Try
myFiles = myDirectory.GetFiles(searchPattern:=”*.txt”)

‘check for files


If UBound(myFiles) >= 0 Then

‘loop through the files array and add to the listbox


For i = 0 To UBound(myFiles) - 1
listBoxFiles().Items.Add(myFiles(i).Name)
Next

‘select the file in the list, this will trigger the event to _
change
Stream and File Operations
261
CHAPTER 7

LISTING 7.8 Continued


‘ the file info label control
If listBoxFiles().Items.Count > 0 Then
listBoxFiles().SelectedIndex = 0
End If

End If

Catch
MsgBox(prompt:= _
“Sorry, but you do not have access to browse this folder’s _
7

STREAM AND FILE


files.”, buttons:=MsgBoxStyle.Exclamation, title:=”FontPad _

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

‘purpose: change the contents of the file properties label

‘local scope
Dim myFile As System.IO.FileInfo
Dim fileInfo As String

‘set the file name


myFileName = listBoxFiles().SelectedItem.ToString

‘create a new file object


myFile = New System.IO.FileInfo(fileName:=myPath & myDirName & _
myFileName)

‘set the file info to display


fileInfo = fileInfo & “Directory: “ & _
myFile.DirectoryName & vbCrLf & vbCrLf
fileInfo = fileInfo & “Created Time: “ & _
CStr(myFile.CreationTime) & vbCrLf & vbCrLf
fileInfo = fileInfo & “Size: “ & _
CStr(myFile.Length / 100) & “ KB” & vbCrLf & vbCrLf
fileInfo = fileInfo & “Last Accessed: “ & _
CStr(myFile.LastAccessTime) & vbCrLf & vbCrLf
fileInfo = fileInfo & “Attributes: “ & myFile.Attributes.ToString
Working with the .NET Namespaces
262
PART II

LISTING 7.8 Continued


‘set the label to the file’s info
labelFileInfo().Text = fileInfo

End Sub

The Up button’s click event simply navigates the user back up one folder.

Private Sub buttonUp_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles buttonUp.Click

‘purpose: move back one directory

‘local scope
Dim intPos As Integer

‘find the \ at the end of the path


intPos = InStrRev(stringCheck:=Mid(myPath, 1, Len(myPath) - 1), _
stringMatch:=”\”)

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.

STREAM AND FILE


OPERATIONS
FIGURE 7.4
FontPad: Save form.

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.

LISTING 7.9 FontPad: Save Form (formSave.vb)


Public Class formSave
Inherits System.Windows.Forms.Form

‘form-level scope declarations


Dim myDirName As String

‘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??

#Region “ Windows Form Designer generated code “


Working with the .NET Namespaces
264
PART II

LISTING 7.9 Continued


Public Sub New()
MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call

End Sub

‘Form overrides dispose to clean up the component list.


Public Overloads Overrides Sub Dispose()
MyBase.Dispose()
If Not (components Is Nothing) Then
components.Dispose()
End If
End Sub
Private WithEvents label1 As System.Windows.Forms.Label
Private WithEvents buttonOk As System.Windows.Forms.Button
Private WithEvents buttonCancel As System.Windows.Forms.Button
Private WithEvents buttonUp As System.Windows.Forms.Button
Private WithEvents label2 As System.Windows.Forms.Label
Private WithEvents textBoxFileName As System.Windows.Forms.TextBox
Private WithEvents listBoxDirectories As System.Windows.Forms.ListBox
Private WithEvents labelSaveTo As System.Windows.Forms.Label

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub _
InitializeComponent()
Me.label1 = New System.Windows.Forms.Label()
Me.buttonOk = New System.Windows.Forms.Button()
Me.label2 = New System.Windows.Forms.Label()
Me.buttonCancel = New System.Windows.Forms.Button()
Me.listBoxDirectories = New System.Windows.Forms.ListBox()
Me.labelSaveTo = New System.Windows.Forms.Label()
Me.textBoxFileName = New System.Windows.Forms.TextBox()
Me.buttonUp = New System.Windows.Forms.Button()
Me.SuspendLayout()

‘label1

Stream and File Operations
265
CHAPTER 7

LISTING 7.9 Continued


Me.label1.Location = New System.Drawing.Point(8, 8)
Me.label1.Name = “label1”1
Me.label1.Size = New System.Drawing.Size(124, 23)
Me.label1.TabIndex = 2
Me.label1.Text = “Save to directory:”

‘buttonOk

Me.buttonOk.Location = New System.Drawing.Point(136, 220)
Me.buttonOk.Name = “buttonOk” 7

STREAM AND FILE


Me.buttonOk.TabIndex = 1

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

LISTING 7.9 Continued


Me.textBoxFileName.Location = New System.Drawing.Point(8, 192)
Me.textBoxFileName.Name = “textBoxFileName”
Me.textBoxFileName.Size = New System.Drawing.Size(280, 20)
Me.textBoxFileName.TabIndex = 5
Me.textBoxFileName.Text = “”

‘buttonUp

Me.buttonUp.Location = New System.Drawing.Point(256, 72)
Me.buttonUp.Name = “buttonUp”
Me.buttonUp.Size = New System.Drawing.Size(32, 23)
Me.buttonUp.TabIndex = 4
Me.buttonUp.Text = “up”

‘formSave

Me.AcceptButton = Me.buttonOk
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.CancelButton = Me.buttonCancel
Me.ClientSize = New System.Drawing.Size(298, 250)
Me.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.labelSaveTo, Me.textBoxFileName, Me.label2, Me.buttonUp, _
Me.listBoxDirectories, Me.label1, Me.buttonOk, Me.buttonCancel})
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog
Me.Name = “formSave”
Me.Text = “Save Text File”
Me.ResumeLayout(False)

End Sub

Private Sub formSave_Load(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles MyBase.Load

‘purpose: load the form

‘local scope

‘load the directory list box


Call loadDirListBox()

End Sub

Private Sub loadDirListBox()

‘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

LISTING 7.9 Continued


‘local scope
Dim myDirectory As System.IO.DirectoryInfo
Dim myDirectories() As System.IO.DirectoryInfo
Dim i As Integer

‘clear the directory list box


listBoxDirectories().Items().Clear()

‘create a new directory object


myDirectory = New System.IO.DirectoryInfo(path:=myPath)
7

STREAM AND FILE


OPERATIONS
‘return the sub directories from the global directory object
myDirectories = myDirectory.GetDirectories()

‘loop through the directories and add them to the list box
For i = 0 To UBound(myDirectories) - 1

‘add the directory name to the list


listBoxDirectories().Items.Add(myDirectories(i).Name)

Next

‘select the first directory in the list


‘ note: this will trigger the events that fill the label control
‘ and the files list box and its label control
If listBoxDirectories().Items.Count > 0 Then
listBoxDirectories().SelectedIndex = 0
End If

End Sub
Private Sub listBoxDirectories_SelectedIndexChanged( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles listBoxDirectories.SelectedIndexChanged

‘purpose: update the directory name when users select a directory

‘local scope

‘set the directory’s name


myDirName = listBoxDirectories().SelectedItem.ToString & “\”

‘reset the save to directory


labelSaveTo().Text = myPath & myDirName

End Sub
Working with the .NET Namespaces
268
PART II

LISTING 7.9 Continued


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

‘purpose: move back one directory


‘ note: this proc. is the same as formOpen

‘local scope
Dim intPos As Integer

‘find the \ at the end of the path


intPos = InStrRev(stringCheck:=Mid(myPath, 1, Len(myPath) - 1), _
stringMatch:=”\”)

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

Private Sub listBoxDirectories_DoubleClick(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles listBoxDirectories.DoubleClick

‘purpose: manange the double-click event


‘ which allows users to navigate down directories
‘ note: same as formOpen’s

‘local scope

‘set the new global path


myPath = myPath & myDirName

‘reset the selected directory to nothing


myDirName = “”

‘update the list directory list box


Call loadDirListBox()

End Sub

Private Sub buttonOk_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles buttonOk.Click
Stream and File Operations
269
CHAPTER 7

LISTING 7.9 Continued


‘purpose: user has indicated they would like to apply their
‘selections

‘local scope
Dim fileName As String

‘get the file name


fileName = Trim(textBoxFileName().Text)

‘validate filename
7

STREAM AND FILE


If fileName = “” Then

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

‘check the file name’s extension


If Not fileName.EndsWith(“.txt”) Then
fileName = fileName.Insert(fileName.Length, “.txt”)
End If

‘call save file method


Call saveFile(fileName:=fileName)

‘close the form


Me.Close()

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

Private Sub saveFile(ByVal fileName As String)

‘purpose: save the contents of the rtb to a file

‘local scope
Dim fileStream As IO.FileStream
Dim streamWriter As IO.StreamWriter
Dim filePath As String

‘set the full path of the file


filePath = myPath & myDirName & fileName

‘check if the file already exists in the directory


If IO.File.Exists(path:=filePath) Then

‘the file already exists, we cheat and delete it


IO.File.Delete(path:=filePath)

End If

‘create a new instance of the file stream object


‘note: if the file does not exist, the constructor will create it
fileStream = New IO.FileStream(path:=filePath, _
mode:=IO.FileMode.OpenOrCreate, access:=IO.FileAccess.Write)

‘create an instance of a character writer


streamWriter = New IO.StreamWriter(stream:=fileStream)

‘set the file pointer to the end of the file


streamWriter.BaseStream.Seek(offset:=0, origin:=IO.SeekOrigin.Begin)

‘write a line of text to the end of the file


streamWriter.Write(value:=textContents)

‘apply the update to the file


streamWriter.Flush()

‘close the stream writer


streamWriter.Close()

‘close the file stream object


fileStream.Close()

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.

STREAM AND FILE


OPERATIONS
• Use the following four basic methods of the Directory class for manipulating directo-
ries: Create, CreateSubdirectory, Delete, MoveTo, and Refresh.
• Use the GetFiles method of Directory to access the files in a specific directory.
• Use the following methods of FileInfo to manipulate files: Create, Delete, MoveTo,
Refresh, CopyTo, Open, OpenRead, and OpenWrite.

• 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

• A More Simplified Approach to Socket


Programming 288

• Implementing a Request/Response
Model 292

• Using the WebClient Class 301

• An Asynchronous Request/Response
Pattern 302

• Authentication and Proxies 307

• Learning by Example: A Socket Transmitter


Application 312

• Learning by Example: ISBNCrawler


Application 329
Working with the .NET Namespaces
274
PART II

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

• Create and use sockets for bidirectional communication


• Send and receive TCP or UDP packets across a network
• Issue and respond to HTTP requests
• Download and upload Web-based resources
• Use credentials to access privileged resources
• Configure your code to work with proxy servers
• Build asynchronous design patterns into your networking code

Key Classes Related to Network Programming


At its simplest, network programming is about physically moving bits from point A to point B
across a wire and responding to data that is being moved from point A to point B across a net-
work. Conceptually, this is a simple task. Physically, it can get very complicated. The .NET
Framework Class Library gives us a very useful foundation of code constructs that can either
shelter us from, or expose us to, low-level networking concepts.
Programmers familiar with Windows sockets programming will feel right at home (and even a
little spoiled) when dealing with the classes in System.Net.Sockets. At the same time, devel-
opers new to network programming will find the network classes approachable and easy to use
despite their amazing range of functionality.
Table 8.1 shows the classes that we will be talking about in this chapter.
Networking Functions
275
CHAPTER 8

TABLE 8.1 Classes Discussed in This Chapter


Class Description
Socket Classes
Socket The Socket class represents a network socket. On the Microsoft
Windows platform, this class abstracts the WinSock API.
SocketException The SocketException class is used to signal socket-specific
errors that occur.
TCP/UDP Classes
TcpClient The TCPClient class allows you to create TCP connections to a
remote server. This class is a further abstraction, or later, on top
of the socket level of functionality.
TcpListener The TCPListener class allows applications to listen for, and
react to, TCP connections from other network clients.
UdpClient Similar to the TCPClient class, this class deals specifically with
the UDP protocol.
HTTP Request/Response Classes
HttpWebRequest This is an implementation of the WebRequest class, specifically 8
designed to work with the HTTP protocol.

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

TABLE 8.1 Continued


Class Description
Authentication Classes
AuthenticationManager This class handles the varied network authentication modules
that an application may use.
CredentialCache The CredentialCache class provides for centralized storage
and retrieval of networking credentials.
NetworkCredential The NetworkCredential class stores a particular instance of
application or user network credentials.
Utility Classes
NetworkStream The Point class implements network-based stream operations.
DNS The DNS class allows server names to be resolved into IP
addresses.
IPAddress The IPAddress class is used to encapsulate an IP address.
IPEndpoint The IPEndpoint class represents one end point on a specific
network connection. An end point is typically identified by an
IP address and a port number.
IPHostEntry The IPHostEntry class is used in conjunction with the DNS
class. It stores a specific DNS host entry.
WebProxy The WebProxy class provides proxy services that can be used by
the WebRequest class and its descendants.

Mapping the Network Classes in .NET


The .NET namespaces System.Net and System.Net.Sockets expose a continuum of network
functionality for developers to use. In other words, if we want or need to interact with a net-
work at a very low level, we can. The “bottom” level of functionality exposed by .NET allows
us to deal directly with something called a socket (more on this in a little bit). Likewise, we
can also deal with some very high-level constructs that enable us to do things like open a con-
nection to a URL and retrieve its response using only one line of code.
Figure 8.1 shows how the .NET networking classes range across a spectrum of control versus
simplicity to provide developers with the appropriate level of functionality for their specific
programming task.
Networking Functions
277
CHAPTER 8

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

TABLE 8.2 AddressFamily Enumeration

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

We would express the enumeration value like this:


AddressFamily.InterNetwork

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

TABLE 8.4 ProtocolType Enumeration

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.

TABLE 8.5 SocketShutdown Enumeration

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

LISTING 8.1 Receiving Data on a Socket


Imports System.Net
Imports System.Net.Sockets
Imports System.Text

Module Module1

Sub ReceiveOverSocket()
‘buffer for the incoming data (capped at 256)
Dim buffArray(256) As Byte

‘holds number of bytes returned from the receive method


Dim numBytes As System.Int32
Networking Functions
285
CHAPTER 8

LISTING 8.1 Continued


‘holds the resulting string from the received bytes
Dim dataString As String

Dim dataEncoder As Encoding

‘Create the socket object:


‘TCP/IP, stream based with Internet addressing
Dim ourSocket As Socket = New Socket(AddressFamily.InterNetwork, _
SocketType.Stream, ProtocolType.Tcp)

‘Create the local end-point


‘(change this to the IP of the machine you will run this code from
Dim localAddress As IPAddress = IPAddress.Parse(“10.0.0.1”)
Dim localEndPoint As IPEndPoint = New IPEndPoint(localAddress, _
8080)

‘Create the remote end-point


Dim targetAddress As IPAddress

‘Here, we resolve a host name to an IP address 8


‘Resolve returns an IPHostEntry; AddressList is an array of

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)

‘connect the socket to the local end-point


ourSocket.Bind(localEndPoint)

‘connect the socket to the remote end-point


ourSocket.Connect(endPoint)

‘Using our netSocket created earlier, we call the receive method…


numBytes = ourSocket.Receive(buffArray, buffArray.Length, 0)

‘To serialize, just pass the byte array through the GetString
‘method.
dataString = dataEncoder.GetString(buffArray, 0, numBytes)

Console.WriteLine(“Data received: {0}”, dataString)


Console.WriteLine(“Shutting the socket down...”)

ourSocket.Shutdown(SocketShutdown.Both)

End Sub

End Module
Working with the .NET Namespaces
286
PART II

Handling Socket Exceptions


If you are using a structured exception handling approach to trapping errors in your code, you
should know that socket classes will throw SocketException instances when problems are
encountered with the network. (We talked about exception handlers in Chapter 4, “Introduction
to the .NET Framework Class Library,” and also in Chapter 20, “Profiling, Debugging, and
Exception Handling.”)
The SocketException class derives from the Exception class. It overrides the
Exception.ErrorCode property to return an integer that maps to the error codes defined in
the Winsock 2.0 API. For instance, a return value of 10061 maps to the Winsock 2 error of
“WSAECONNREFUSED,” and indicates that the target of the socket connection refused the
connection. Essentially, the ErrorCode property is returning the last known operating system
WinSock error. Table 8.6 shows some of the more common errors returned from the WinSock
API (version 2). For an exhaustive list, please consult the actual WinSock 2.0 API
documentation.

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.

TABLE 8.6 Common SocketException Error Codes and Descriptions


Error Code WinSock Constant Description
10013 WSAEACCESS A socket has attempted an action that was
denied to it by the associated permission levels.
10048 WSAEADDRINUSE Address is already in use. This is usually raised
when a socket has attempted to bind to an
address already bound to another socket
instance.
10049 WSAEADDRNOTAVAIL The address cannot be assigned. This is usually
raised when the socket has attempted to bind to
an address that is not available on the local
machine.
10056 WSAEISCONN An attempt was made to connect a socket that
has already been connected.
100057 WSAENOTCONN An attempt was made to send or receive data
over a socket that is not connected.
Networking Functions
287
CHAPTER 8

TABLE 8.6 Continued


Error Code WinSock Constant Description
10060 WSAETIMEDOUT The connection attempt has timed out.
10061 WSAECONNREFUSED The target machine has refused the request for
a connection.

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

Catch sockError As SocketException


‘Handle a specific socket error here. You can
‘cross-reference the ErrorCode returned against the
‘Winsock 2.0 API error codes. 8
MsgBox(“Socket error number:” & sockError.ErrorCode)

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

Suggestions for Further Exploration


➲ If you are curious about how to restrict or permit socket communications, investigate the
SocketPermission class. It is located in the System.Net namespace and will restrict or
allow an application to accept connections or contact servers based on a host, port num-
ber, and transport protocol.
➲ To implement multicasting into your socket applications, check out the
MulticastOption class. It can be used in conjunction with the SetSocketOption prop-
erty on the Socket class to indicate a list of IP address values for IP multicast packets.
➲ The LingerOption class, in the System.Net.Sockets namespace, will enable you to
specify the amount of time a socket will remain open after closing the socket if there is
any remaining data to be sent. Experiment with using this class to avoid premature shut-
down and subsequent loss of pending data.
➲ The Socket class provides a way to interactively check its current status. Please consult
the Framework documentation on the Socket.Poll method.

A More Simplified Approach to Socket


Programming
Moving up to the next layer of functionality offered by the .NET classes, we arrive at the
TCPClient, UDPClient, and TCPListener classes. These classes offer a more simplified
approach to socket programming, while retaining quite a bit of power. The two client classes
are very similar with some fundamental differences between them that are worth pointing out:
• The TCPClient class deals with TCP connections; thus, it is stream oriented.
• The UDPClient class deals with UDP connections and is datagram oriented.
• Send and receive operations with the TCPClient class are done through Stream class
instances.
• Send and receive operations with the UDPClient class can be accomplished by calling
the Send and Receive methods and passing an array holding the actual datagram data.
We will focus on the TCPClient class as opposed to the UDPClient class. Understanding one
should enable you to easily deal with the other.
The TCPClient class has an overloaded constructor that can accept either an IPEndPoint
instance or a host/port name combination. This allows you to quickly establish a socket to a
remote end point with only one line of code. There is no need to involve the IPEndPoint class,
and no need to involve the DNS class to resolve host names because the TCPClient constructor
is robust enough to handle these dynamics. Here is an example:
Dim tcp As TCPClient = New TCPClient(“www.samspublishing.com”,80)
Networking Functions
289
CHAPTER 8

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)

‘get NetworkStream object for write operation


Dim netStream As NetworkStream = Tcp.GetStream()
Working with the .NET Namespaces
290
PART II

‘encode our message in a byte array


Dim encoder As ASCIIEncoding
Dim buffArray() As Byte = encoder.GetBytes(“TCPClient programming is even _
easier!”)

‘write the message into the stream


netStream.Write(buffArray, 0, buffArray.Length)

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.

Listening for Connections


Objects created from the TCPListener class have the ability to wait and listen for TCP connec-
tion requests from other TCP clients. Once a connection request is “heard,” you can decide
how to react; you may want to send data immediately, wait for incoming data to arrive, and
so on.

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.

LISTING 8.2 Listening for TCP Connections


Imports System.Net.Sockets
Imports System.Text

Module Module1

‘This simple subroutine waits for a connection to come across port


‘8080. It then responds back to the client that made the connection
‘with the message “connection accepted”.
Sub Main()
Dim listener As TcpListener = New TcpListener(8080)

‘This starts the actual listening process…


listener.Start()
8
Console.WriteLine(“Waiting for TCP connection...”)

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()

‘Our tcp object now holds the reference to the client

‘get NetworkStream object for write operation


Dim netStream As NetworkStream = tcp.GetStream()
Dim encoder As ASCIIEncoding
Dim bytArray() As Byte = encoder.GetBytes(“connection accepted”)

‘write the message into the stream


netStream.Write(bytArray, 0, bytArray.Length)

‘Stop the listener


listener.Stop()

End Sub

End Module
Working with the .NET Namespaces
292
PART II

Implementing a Request/Response Model


Continuing on our journey up through the layers of the .NET networking libraries we arrive at
those classes that implement a standard request/response model: the WebRequest and
WebResponse classes. These classes are layered above the socket and stream level of interac-
tion and deal with request/response traffic across the Internet. Here, we are afforded the ability
to either deal at a protocol-specific level with items, or deal in a protocol-agnostic fashion.
This will depend largely on the programming task that you are working on but the important
thing to note is that you, as a .NET programmer, have a choice.

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:

Dim netStream As New NetworkStream = resp.GetResponseStream()

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.

Working with HTTP-Specific Requests and Responses


If you have the need to deal with, say, HTTP-specific header properties, you can access them
by simply casting your WebRequest or WebResponse objects into an HTTPWebRequest or
HTTPWebResponse object through the CType command. This process is illustrated through the
following code (continued from the previous example):
Dim http As HttpWebResponse = CType(resp, HttpWebResponse)

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.

TABLE 8.7 HttpStatusCode Enumeration

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

TABLE 8.7 Continued


Equivalent HTTP
Name Status Code Description
BadRequest 400 The server did not understand the
request.
Conflict 409 There was a conflict on the server
with the current state of a resource.
Continue 100 The request can be continued.
Created 201 The request was fulfilled, resulting
in the creation of a new resource.
ExpectationFailed 417 An expectation specified in the
Expect header could not be met by
the server.
Forbidden 403 The server understood the request
but has refused to respond.
Found 302 The server has indicated a redirect
to the desired resource (equivalent
to Redirect).
GatewayTimeout 504 The request timed out waiting for a
gateway to respond.
Gone 410 The resource that was requested no
longer resides on the server.
HttpVersionNotSupported 505 The server does not support the
HTTP version specified in the
request.
InternalServerError 500 The server encountered an unex-
pected error that is preventing it
from responding to the request.
LengthRequired 411 The server is refusing to answer
the request because it was sent
without a Content-length header.
MethodNotAllowed 405 The server does not allow the
method used for the request.
Moved 301 The requested resource now lives
at a different URI (the server will
return the URI in its response in
the Location header) if the initial
request was a POST, the redirect
will instead use a GET.
Networking Functions
295
CHAPTER 8

TABLE 8.7 Continued


Equivalent HTTP
Name Status Code Description
MovedPermanently 301 See above.
MultipleChoices 300 The server couldn’t decide what to
return (synonym for Ambiguous).
NoContent 204 The server has intentionally
returned a blank response to the
request.
NonAuthoritativeInformation 203 The server returned meta informa-
tion that was from a cache of the
original information and may
therefore be incorrect.
NotAcceptable 406 The server was unable to find a
response that was acceptable to the
client (the client has issued a list
of acceptable responses in the
Accept header). 8
NotFound 404 The resource that was requested

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

TABLE 8.7 Continued


Equivalent HTTP
Name Status Code Description
ProxyAuthenticationRequired 407 The requested proxy requires
authentication—the response will
indicate how to perform the
authentication in the Proxy-
authenticate header.
Redirect 302 The server has indicated a redirect
to the desired resource (equivalent
to Found).
RedirectKeepVerb 307 The requested resource is available
at the URI identified in the
Location header. If the initial
request was a POST, the redirect
will also use a POST (equivalent to
TemporaryRedirect).
RedirectMethod 303 The server is automatically redi-
recting the request to the URI
identified in the Location header—
the redirected request will be made
with a GET (synonym for
SeeOther).
RequestedRangeNotSatisfiable 416 The server could not return the
range of data indicated because
the beginning occurred before the
beginning of the data range, or
the end occurred after the end of
the data range.
RequestEntityTooLarge 413 The server cannot process the
request—it is too large.
RequestTimeout 408 The server timed out while waiting
for the request.
RequestUriTooLong 414 The server has refused the request
because the URI is longer than it
will permit.
ResetContent 205 The client should reset the current
resource.
Networking Functions
297
CHAPTER 8

TABLE 8.7 Continued


Equivalent HTTP
Name Status Code Description
SeeOther 303 The server is automatically redi-
recting the request to the URI
identified in the Location header—
the redirected request will be made
with a GET (equivalent to
RedirectMethod).
ServiceUnavailable 503 The server is currently unavailable.
SwitchingProtocols 101 Either the version of the protocol
or the actual protocol is being
changed.
TemporaryRedirect 307 The requested resource is available
at the URI identified in the
Location header. If the initial
request was a POST, the redirect
will also use a POST (equivalent to 8
RedirectKeepVerb).

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.

Handling Web Exceptions


The WebRequest and WebResponse classes, and their descendants, will throw WebException
instances when errors are encountered. By examining the WebException.Status property, you
can find what type of error was encountered. This property returns a member of the
WebExceptionStatus enumeration, which is detailed in Table 8.8.
Working with the .NET Namespaces
298
PART II

TABLE 8.8 Members of the WebExceptionStatus Enumeration


Name Description
ConnectFailure The connection attempt to the target address has failed.
ConnectionClosed The connection was closed.
KeepAliveFailure A request has specified a keep-alive in its header, and the
request connection was closed.
NameResolutionFailure The host name could not be resolved.
Pending An async request is still pending.
ProtocolError A response was received from the target address indicat-
ing that a protocol-level error was encountered.
ProxyNameResolutionFailure The proxy host name could not be resolved.
ReceiveFailure A complete response was not received from the target
address.
RequestCanceled The request was canceled.
SecureChannelFailure An error was encountered while trying to establish a
secure channel link.
SendFailure The request could not be sent to the target address.
ServerProtocolViolation The response received was not a valid HTTP response.
Success No error was encountered.
Timeout The request has timed out.
TrustFailure The server certificate could not be authenticated.

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.

LISTING 8.3 Creating Requests and Processing Responses


Imports System.Net
Imports System.Net.Sockets

Module Module1

Sub Main()

Try
Networking Functions
299
CHAPTER 8

LISTING 8.3 Continued


‘Buffer byte array to hold the stream data
Dim buffArray(1024) As Byte

‘Var for the stream looping


Dim currIndex As System.Int32

‘Var to hold the number of bytes read


Dim numBytes As System.Int32

‘Create the request for a web page


Dim rqst = WebRequest.Create(“http://www.brilliantstorm.com”)

‘Get the response to our request


Dim resp = rqst.GetResponse()

‘Create a stream object and assign it to the response stream


Dim netStream As NetworkStream = resp.GetResponseStream()

‘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

‘Close the request and response objects


rqst.Close()
resp.Close()

Catch webErr As WebException


‘Setup containers for the WebExceptionStatus enum and its
‘underlying text
Dim webStatusEnum As WebExceptionStatus
Dim webStatusDesc As String

‘Setup containers for the HttpStatusCode enum and its


‘underlying text
Dim httpStatusEnum As HttpStatusCode
Dim httpStatusDesc As String
Working with the .NET Namespaces
300
PART II

LISTING 8.3 Continued


‘If we get a protocol error back, we will need an HTTPWebResponse
‘object to get at the underlying http status code
Dim httpResp As HttpWebResponse

‘Get the desc. of the web exception


webStatusDesc = webStatusEnum.GetName(webStatusEnum.GetType, _
webErr.Status)

‘Write out the WebException text to the console


Console.WriteLine(“A web error was encountered: {0}”, _
webStatusDesc)

‘If we encountered a protocol error


‘(WebExceptionStatus.ProtocolError), cast the web response to an
‘http web response and then examine the http status code info
If webErr.Status = WebExceptionStatus.ProtocolError Then
httpResp = CType(webErr.Response, HttpWebResponse)
httpStatusEnum = httpResp.StatusCode
httpStatusDesc = httpStatusEnum.GetName
➥(httpStatusEnum.GetType, httpResp.StatusCode)

‘write out the http status code to the console


Console.Write(“HTTP Status Code: {0}”, httpStatusDesc)

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

Using the WebClient Class


The WebClient class represents the most abstract level of functionality provided by the .NET
network classes: It is the easiest and quickest way to establish network communications. The
WebClient class allows:

• Resolution of a URI using one line of code


• Uploading and downloading of files, buffers, streams, and name/value pairs
• HTTP, HTTPS, and FILE protocols transmissions
This section discusses the particular ease of use offered by this class.

Uploads and Downloads


One of the things that the WebClient class allows you to do very simply is the uploading or
downloading of resources to or from a server. There are two sets of methods that we are con-
cerned with: the DownloadData and DownloadFile methods, and the UploadData and
UploadFile methods. Their syntax is brief and concise.

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

Working with Streams


Of course, the WebClient class would not be complete without support for streams. Using the
OpenRead and OpenWrite methods, you can obtain stream references that you can read and
write into.
Dim myStream As Stream
Dim web As New WebClient()

‘obtain a stream reference for reading data…


myStream = web.OpenRead(myURL)

‘or writing data.


myStream = web.OpenWrite(myURL)

Suggestions for Further Exploration


➲ Investigate the WebPermission class to see how to control rights and permissions for a
specific Internet resource.
➲ The WebClient.UploadValues method provides a way to send name/value pairs to a
server and retrieve the response. Examine the Framework documentation for this method
and for the NameValueCollection class (in the System.Collections.Specialized
namespace) to see how to use them in your networking code.

An Asynchronous Request/Response Pattern


Until now, all of our focus has been on using the networking classes in a synchronous fashion.
However, the Internet itself is asynchronous in nature, and there are many programming tasks
that are not well served by synchronous software design. In this section, we’ll look at a design
pattern for implementing an asynchronous request/response model using the WebRequest and
WebResponse classes.

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

Issuing an Asynchronous Request


Recall that, with the WebRequest class, a request is issued for a resource by calling the
GetResponse method. This is done in a synchronous fashion; the application will block on that
line of code until the response has been received. To issue an asynchronous request, we use the
BeginGetResponse method. This is what kicks the whole process off. The BeginGetResponse
method, unlike the GetResponse method, takes some parameters that equip it to work asyn-
chronously. These parameters are an AsyncCallBack instance, and a generic “container” object
instance for holding state.
The callback that we pass into the BeginGetResponse method essentially tells the class:
“When a response has been received, fire off the code pointed to by the callback.” So, if we
had a subroutine called “ReceiveResponse”, we can specify its callback into the
BeginGetResponse method like this:

New AsyncCallback(AddressOf ReceiveResponse)

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

Public Sub New()


rqst = Nothing
End Sub
End Class

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

Next, we will finally call BeginGetResponse:


rqst.BeginGetResponse(New AsyncCallback(AddressOf ReceiveResponse), _
aState)

This brings us to the next step in the pattern.

Receiving the Response Asynchronously


After making the resource request, the application will continue on its execution path until it is
signaled that the data in response to the request has finally arrived. Our subroutine that handles
the callback—in this case called ReceiveResponse—will have an IAsyncResult object passed
to it. The IASyncResult interface exposes a property, AsyncState, which will return to us the
state object that we had originally passed in to the BeginGetResponse method:
Public Sub ReceiveResponse(rslt As IAsyncResult)

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

Dim httpResp As HttpWebResponse = _req.EndGetResponse(ar), _


HttpWebResponse)

Dim resp As WebResponse = rqst.EndGetResponse(ar)


.
.
.
End Sub

Reading the Response Asynchronously


Just like the WebRequest class, the Stream class also has explicit support for asynchronous
operations. The Stream.BeginRead method is simply the asynchronous equivalent to the
Stream.Read method. Just as with the WebRequest class, this method requires an
AsyncCallBack instance and a state object instance.
Networking Functions
305
CHAPTER 8

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

respStream.BeginRead(respStream.BufferRead, 0, 1024, New _


AsyncCallback(AddressOf ReadStream)
8
A new subroutine will be needed to process the results of the stream read where we again will
use the passed in IAsyncResult object to get at the state object, and thus the stored stream. At

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

Dim respStream As Stream = retState.respStream

Dim dataBytes As Integer = respStream.EndRead(rslt)


If dataBytes > 0 Then
‘ Data still left in the stream… parse it out
‘ Then issue another BeginRead…
Else
‘all done
End If

‘close the stream


responseStream.Close()
.
.
.
End Sub
Working with the .NET Namespaces
306
PART II

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

Application 3. EndGetResponse WebRequest

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.

To summarize the steps once more:


1. Call the BeginGetResponse method.
The calling application first makes a call to the BeginGetResponse method off an instan-
tiated WebRequest object.
2. The WebRequest object places the callback.
Once the WebRequest object has received a response from the server, it will call back to
the routine that we passed it in Step 1. During this callback, it will pass an instance of an
IAsyncResult—more on this in the next step.

3. Call the EndGetResponse method.


The callback routine will now want to place a call to the EndGetResponse method. This
will take the IAsyncResult object that was passed over from the WebRequest object. The
EndGetResponse method is the async equivalent of the GetResponse method, and effec-
tively completes the circle by returning a WebResponse object (which, if you recall, is
what we can use to get access to the actual data sent in response to our request).
4. The WebRequest creates a WebResponse object.
The WebRequest object will return a WebResponse object to us after we call the
EndGetResponse method. We’ll use this response to get access to that actual raw data
sent from the server.
Networking Functions
307
CHAPTER 8

5. Call the GetResponseStream method.


From this point our pattern looks like the previous code, which we looked at for dealing
with WebResponse objects. We will first get a Stream object representation of the
response.
6. The WebResponse creates the Stream object.
From the GetResponseStream, we will get a Stream object that we will use as our final
interaction point with the response. Again, this is identical to the previous code, which
we examined in our section on the WebRequest/WebResponse classes.
7–9. Interactively read the stream with BeginRead.
The async design pattern continues here with the reading of the response stream. This
follows the same pattern as our request/response pattern.
The sample application “ISBNCrawler,” located at the end of this chapter, demonstrates
asynchronous request/response code.

Authentication and Proxies


In all our previous discussions on the network classes, we dealt with accessing URIs and 8
establishing connections in a very simplistic fashion; we have assumed an open and free land-

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”)

With the WebRequest class, our signature is identical: 8


Dim rqst as WebRequest

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.

TABLE 8.9 Networking Classes That Support the Credentials Property


FileWebRequest
HTTPWebRequest
WebClient
WebProxy
WebRequest

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

Optimizing Credentials Through the Cache


The CredentialCache class is actually a collection class: It can maintain multiple credential
entries, each associated with a specific URI and authentication method. Your code can then
extract the appropriate credentials by using the GetCredential method, passing in the URI
and authentication method. The class will then return the first matching set of credentials
through an instance of a NetworkCredential class. If no matching credentials are found, it
returns a null object (Nothing). The following code illustrates the use of a credential cache
with a WebClient object:
Imports System.Net

Module Module1

Sub Main()
‘the WebClient we will use with the cred cache
Dim myClient As WebClient

‘private instance of a credential cache


Dim myCache As New CredentialCache()

‘the first parameter to the credential cache’s


‘add method is a URI object; here we create two
Dim uri1 = New Uri(“www.microsoft.com”)
Dim uri2 = New Uri(“www.samspublishing.com”)

‘The second parameter required is a NetworkCredential


‘instance (login ID and passworD)
Dim myLogin = New NetworkCredential(“John Doe”, “fortknox”)

‘Now, we add the two different sets of credentials to the


‘cache
creds.Add(uri1, “Basic”, myLogin)
creds.Add(uri2, “NTLM”, myLogin)

‘By setting the credentials prop equal to one of the stored


‘credentials in the cache, we are able to carry along our
‘login information with the WebClient object
myClient.Credentials = creds.GetCredential(uri1, “Basic”)
End Sub

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

Dealing with Proxies


Besides credentials, when working with Internet-based connections, you may need to worry
about specifying proxy server information. The WebRequest class and its descendant classes
allow you to specify a proxy server through the Proxy property; you must set this property
equal to an instance of a WebProxy class. The following code shows how this can be done.
‘First create a WebProxy instance
Dim myProxy As WebProxy = New WebProxy(“http://ourproxy:8080”)

‘rqst is a WebRequest instance; set its Proxy prop to route


‘requests appropriately…
rqst.Proxy = myProxy

‘now, use the request object like you normally would.

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

Suggestions for Further Exploration


➲ The ServicePointManager class holds a cache or collection of ServicePoint objects in
much the same way that the CredentialsCache holds instances of NetworkCredential
objects. A ServicePoint is an object that holds connection information for a particular
URI; the ServicePointManager class can return a ServicePoint based on the URI you
Working with the .NET Namespaces
312
PART II

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.”

Learning by Example: A Socket Transmitter


Application
The “SocketTransmitter” application ties together some of the primary concepts that we have
talked about in relationship to communicating using the Socket class. The application has a
single form that allows you to play around with Socket messaging by entering different com-
mands that can be sent to the server address of your choice. The application will then show
you the reply you get back from the server.

Key Concepts Covered


This application showcases the following:
• Creating a socket object
• Binding and connecting a socket instance to its respective end points
• Using the DNS class to resolve host names to an IPAddress instance
• Using the IPAddress.Parse method to derive an IPAddress instance from a string rep-
resentation of an IP address in dotted quad form
• Sending data across a socket with the Socket.Send method
• Receiving data across a socket using the Socket.Receive method
• Catching and responding to SocketException errors
Figure 8.3 shows the Socket Transmitter form.
To take the application for a spin, you must first have a connection established to the Internet.
Try the following:
1. Type either a host name or IP address for the local end point (near side of the connec-
tion). If you type a host name instead of a valid IP address, make sure you have the
“Resolve Local Host” checkbox checked. Click the “Bind” button. If successful, you
should see the status bar indicate that the socket has been bound.
2. Now type in a host name or IP address for the remote end point (the far side of the con-
nection). Again, make sure you have the “Resolve Remote Host” checkbox checked if
you didn’t type in an actual IP address. Click on the “Connect” button. If successful, the
status bar should report that the socket has been connected.
Networking Functions
313
CHAPTER 8

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.

LISTING 8.4 Sample Application: Socket Transmitter


We first reference the three namespaces that will provide us with the objects we need. Note
that at the class scope, we have created a global socket instance called ‘sock’.
Imports System.Text
Imports System.Net
Imports System.Net.Sockets

Public Class SocketTransmitter


Inherits System.Windows.Forms.Form

Dim netSocket As Socket

#Region “ Windows Form Designer generated code “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

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

‘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
Friend WithEvents GroupBox1 As System.Windows.Forms.GroupBox
Friend WithEvents Label1 As System.Windows.Forms.Label
Friend WithEvents textBoxLocal As System.Windows.Forms.TextBox
Friend WithEvents Label2 As System.Windows.Forms.Label
Networking Functions
315
CHAPTER 8

LISTING 8.4 Continued


Friend WithEvents buttonBind As System.Windows.Forms.Button
Friend WithEvents buttonConnect As System.Windows.Forms.Button
Friend WithEvents checkResolveLocal As System.Windows.Forms.CheckBox
Friend WithEvents checkResolveRemote As System.Windows.Forms.CheckBox
Friend WithEvents GroupBox2 As System.Windows.Forms.GroupBox
Friend WithEvents GroupBox3 As System.Windows.Forms.GroupBox
Friend WithEvents textBoxSend As System.Windows.Forms.TextBox
Friend WithEvents buttonSend As System.Windows.Forms.Button
Friend WithEvents StatusBar As System.Windows.Forms.StatusBar
Friend WithEvents buttonClear As System.Windows.Forms.Button
Friend WithEvents textBoxRemote As System.Windows.Forms.TextBox
Friend WithEvents listBoxActivity As System.Windows.Forms.ListBox
Friend WithEvents Label3 As System.Windows.Forms.Label
Friend WithEvents textBoxLocalPort As System.Windows.Forms.TextBox
Friend WithEvents textBoxRemotePort As System.Windows.Forms.TextBox
Friend WithEvents Label4 As System.Windows.Forms.Label

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container
8
‘NOTE: The following procedure is required by the Windows Form Designer

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

LISTING 8.4 Continued


Me.GroupBox3.SuspendLayout()
Me.GroupBox1.SuspendLayout()
Me.SuspendLayout()

‘Label4

Me.Label4.Location = New System.Drawing.Point(12, 120)
Me.Label4.Name = “Label4”
Me.Label4.Size = New System.Drawing.Size(60, 12)
Me.Label4.TabIndex = 0
Me.Label4.Text = “Port:”

‘buttonBind

Me.buttonBind.Location = New System.Drawing.Point(316, 20)
Me.buttonBind.Name = “buttonBind”
Me.buttonBind.Size = New System.Drawing.Size(76, 20)
Me.buttonBind.TabIndex = 2
Me.buttonBind.Text = “Bind”

‘Label1

Me.Label1.Location = New System.Drawing.Point(12, 24)
Me.Label1.Name = “Label1”
Me.Label1.Size = New System.Drawing.Size(60, 12)
Me.Label1.TabIndex = 0
Me.Label1.Text = “Local:”

‘Label2

Me.Label2.Location = New System.Drawing.Point(12, 96)
Me.Label2.Name = “Label2”
Me.Label2.Size = New System.Drawing.Size(60, 12)
Me.Label2.TabIndex = 0
Me.Label2.Text = “Remote:”

‘Label3

Me.Label3.Location = New System.Drawing.Point(12, 48)
Me.Label3.Name = “Label3”
Me.Label3.Size = New System.Drawing.Size(60, 12)
Me.Label3.TabIndex = 0
Me.Label3.Text = “Port:”

‘textBoxLocalPort

Networking Functions
317
CHAPTER 8

LISTING 8.4 Continued


Me.textBoxLocalPort.Location = New System.Drawing.Point(76, 44)
Me.textBoxLocalPort.MaxLength = 5
Me.textBoxLocalPort.Name = “textBoxLocalPort”
Me.textBoxLocalPort.Size = New System.Drawing.Size(40, 20)
Me.textBoxLocalPort.TabIndex = 1
Me.textBoxLocalPort.Text = “”

‘checkResolveRemote

Me.checkResolveRemote.Location = New System.Drawing.Point(76, 144)
Me.checkResolveRemote.Name = “checkResolveRemote”
Me.checkResolveRemote.Size = New System.Drawing.Size(156, 16)
Me.checkResolveRemote.TabIndex = 3
Me.checkResolveRemote.Text = “Resolve Remote Name”

‘GroupBox2

Me.GroupBox2.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.buttonSend, Me.textBoxSend})
Me.GroupBox2.Location = New System.Drawing.Point(8, 188)
8
Me.GroupBox2.Name = “GroupBox2”

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

LISTING 8.4 Continued


Me.GroupBox3.Location = New System.Drawing.Point(8, 248)
Me.GroupBox3.Name = “GroupBox3”
Me.GroupBox3.Size = New System.Drawing.Size(396, 236)
Me.GroupBox3.TabIndex = 2
Me.GroupBox3.TabStop = False
Me.GroupBox3.Text = “View the Activity Log”

‘listBoxActivity

Me.listBoxActivity.HorizontalScrollbar = True
Me.listBoxActivity.Location = New System.Drawing.Point(8, 24)
Me.listBoxActivity.Name = “listBoxActivity”
Me.listBoxActivity.Size = New System.Drawing.Size(380, 173)
Me.listBoxActivity.TabIndex = 2

‘buttonClear

Me.buttonClear.Location = New System.Drawing.Point(316, 208)
Me.buttonClear.Name = “buttonClear”
Me.buttonClear.Size = New System.Drawing.Size(68, 20)
Me.buttonClear.TabIndex = 1
Me.buttonClear.Text = “Clear”

‘checkResolveLocal

Me.checkResolveLocal.Location = New System.Drawing.Point(76, 68)
Me.checkResolveLocal.Name = “checkResolveLocal”
Me.checkResolveLocal.Size = New System.Drawing.Size(156, 16)
Me.checkResolveLocal.TabIndex = 3
Me.checkResolveLocal.Text = “Resolve Host Name”

‘buttonConnect

Me.buttonConnect.Enabled = False
Me.buttonConnect.Location = New System.Drawing.Point(316, 92)
Me.buttonConnect.Name = “buttonConnect”
Me.buttonConnect.Size = New System.Drawing.Size(76, 20)
Me.buttonConnect.TabIndex = 2
Me.buttonConnect.Text = “Connect”

‘textBoxLocal

Me.textBoxLocal.Location = New System.Drawing.Point(76, 20)
Me.textBoxLocal.MaxLength = 1024
Me.textBoxLocal.Name = “textBoxLocal”
Me.textBoxLocal.Size = New System.Drawing.Size(232, 20)
Networking Functions
319
CHAPTER 8

LISTING 8.4 Continued


Me.textBoxLocal.TabIndex = 1
Me.textBoxLocal.Text = “”

‘textBoxRemote

Me.textBoxRemote.Location = New System.Drawing.Point(76, 92)
Me.textBoxRemote.MaxLength = 1024
Me.textBoxRemote.Name = “textBoxRemote”
Me.textBoxRemote.Size = New System.Drawing.Size(232, 20)
Me.textBoxRemote.TabIndex = 1
Me.textBoxRemote.Text = “”

‘StatusBar

Me.StatusBar.Location = New System.Drawing.Point(0, 489)
Me.StatusBar.Name = “StatusBar”
Me.StatusBar.Size = New System.Drawing.Size(416, 20)
Me.StatusBar.TabIndex = 3

‘GroupBox1
8

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

LISTING 8.4 Continued


Me.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.StatusBar, Me.GroupBox3, Me.GroupBox2, Me.GroupBox1})
Me.Name = “SocketTransmitter”
Me.Text = “SocketTransmitter”
Me.GroupBox2.ResumeLayout(False)
Me.GroupBox3.ResumeLayout(False)
Me.GroupBox1.ResumeLayout(False)
Me.ResumeLayout(False)

End Sub

#End Region

Private Sub SocketTransmitter_Load(ByVal sender As System.Object, ByVal e


As System.EventArgs) Handles MyBase.Load

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)

Catch appErr As Exception


‘If we are unable to create the socket, raise the error to the
‘screen and then close the form
WriteStatus(“Error: unable to create socket”)
MsgBox(“Unable to create a socket, closing the application...” & _
vbCrLf & vbCrLf & “Error:” & appErr.Message & vbCrLf & “Stack _
Trace: “ & appErr.StackTrace)
Me.Close()
End Try

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

LISTING 8.4 Continued


Private Sub BindSocket(ByVal useDNS As Boolean, ByVal local As String, _
ByVal port As System.Int32)
Try

‘This will hold our local IPAddress for the bind


Dim localAddress As IPAddress

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

Dim localEP As IPEndPoint = New IPEndPoint(localAddress, port)


8
netSocket.Bind(localEP)

NETWORKING
FUNCTIONS
Catch sockErr As SocketException
‘A socket exception was encountered.

‘Retrieve and then write out the actual winsock error


‘description
Dim convert As WinSockError = New WinSockError()
Dim errDesc As String = convert.GetDescription(sockErr.ErrorCode)

WriteStatus(“Error binding the socket.”)


WriteActivity(“Error: “ & errDesc, “APP”)

‘Re-set the form to its initial state


SetFormState(“INITIAL”)

Catch appErr As Exception


‘This is our catch-all for any other types of errors.
WriteStatus(“Error binding the socket.”)
WriteActivity(“Error: “ & appErr.Message, “APP”)

‘Re-set the form to its initial state


SetFormState(“INITIAL”)

End Try

End Sub
Working with the .NET Namespaces
322
PART II

LISTING 8.4 Continued


The next step, after binding the socket to its local end point, is to connect it to its remote end
point. This routine functions nearly identically to the BindSocket routine.
Private Sub ConnectSocket(ByVal useDNS As Boolean, _
ByVal server As String, ByVal port As Integer)
Try
Dim serverAddress As IPAddress

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

‘Now that we had an IPAddress instance, we can use this


‘to establish our IPEndPoint object
Dim serverEP As IPEndPoint = New IPEndPoint(serverAddress, port)

‘establish the connection


netSocket.Connect(serverEP)

‘Set the form state to “connected”


SetFormState(“CONNECTED”)

‘Write out the success message


WriteStatus(“Connected to “ & server & “.”)
WriteActivity(“Socket connect to “ & server & “.”, “APP”)

Catch sockErr As SocketException


‘A socket exception was encountered.

‘Retrieve and then write out the actual winsock error


‘description
Dim convert As WinSockError = New WinSockError()
Dim errDesc As String = convert.GetDescription(sockErr.ErrorCode)

WriteStatus(“Error binding the socket.”)


WriteActivity(“Error: “ & errDesc, “APP”)

‘Re-set the form to its initial state


SetFormState(“INITIAL”)
Networking Functions
323
CHAPTER 8

LISTING 8.4 Continued


Catch appErr As Exception
‘This is our catch-all for any other types of errors.
WriteStatus(“Error binding the socket.”)
WriteActivity(“Error: “ & appErr.Message, “APP”)

‘Re-set the form to bound state


SetFormState(“BOUND”)

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

‘Send the message to the server


bytesSent = netSocket.Send(sendMsg)

ReceiveFromSocket()

WriteActivity(msg & “(“ & bytesSent & “ bytes)”, “MSG”)

Catch sockErr As SocketException


‘A socket exception was encountered.

‘Retrieve and then write out the actual winsock error


‘description
Dim convert As WinSockError = New WinSockError()
Dim errDesc As String = convert.GetDescription(sockErr.ErrorCode)

WriteStatus(“Error sending across the socket.”)


WriteActivity(“Error: “ & errDesc, “APP”)
Working with the .NET Namespaces
324
PART II

LISTING 8.4 Continued


‘Re-set the form to its connected state
SetFormState(“CONNECTED”)

Catch appErr As Exception


‘This is our catch-all for any other types of errors.
WriteStatus(“Error sending across the socket.”)
WriteActivity(“Error: “ & appErr.Message, “APP”)

‘Re-set the form to bound state


SetFormState(“CONNECTED”)

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

‘For translations between strings and byte arrays


Dim encoder As Encoding

‘Receive the reply message across the socket


bytesRcvd = netSocket.Receive(rcvBuffer, 1024, SocketFlags.None)

‘We need to loop until there is no more data coming across


While bytesRcvd > 0
WriteStatus(“Receiving across the socket (“ & bytesRcvd & “ _
bytes)”)
bytesRcvd = netSocket.Receive(rcvBuffer, rcvBuffer.Length, _
SocketFlags.None)
reply = reply & encoder.GetString(rcvBuffer, 0, bytesRcvd)
End While

‘concatenated response from the server is now stored in the


‘ “reply” variable
WriteActivity(reply, “RESP”)

Catch sockErr As SocketException


‘A socket exception was encountered.
Networking Functions
325
CHAPTER 8

LISTING 8.4 Continued


‘Retrieve and then write out the actual winsock error
‘description
Dim convert As WinSockError = New WinSockError()
Dim errDesc As String = convert.GetDescription(sockErr.ErrorCode)

WriteStatus(“Error sending across the socket.”)


WriteActivity(“Error: “ & errDesc, “APP”)

‘Re-set the form to its connected state


SetFormState(“CONNECTED”)

Catch appErr As Exception


‘This is our catch-all for any other types of errors.
WriteStatus(“Error sending across the socket.”)
WriteActivity(“Error: “ & appErr.Message, “APP”)

‘Re-set the form to bound state


SetFormState(“CONNECTED”)

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

Select Case msgType


Case “APP”
prefix = “--->”
Case “MSG”
prefix = “Msg Sent:”
Case “RESP”
prefix = “Response:”
Case Else
prefix = “”
End Select

listBoxActivity.Items.Add(prefix & msg)

End Sub

Private Sub WriteStatus(ByVal msg As String)


‘this routine just writes status messages
‘to the statusbar control on the form
Working with the .NET Namespaces
326
PART II

LISTING 8.4 Continued


StatusBar().Text = msg
End Sub

Private Sub SetFormState(ByVal state As String)

Select Case state


Case “INITIAL”
textBoxLocal.Enabled = True
buttonBind.Enabled = True
checkResolveLocal.Enabled = True
textBoxRemote.Enabled = False
checkResolveRemote.Enabled = False
textBoxSend.Enabled = False
buttonSend.Enabled = False

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

Private Sub buttonBind_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles buttonBind.Click
BindSocket(checkResolveLocal.Checked, Trim(textBoxLocal.Text), _
Trim(textBoxLocalPort.Text))
End Sub

Private Sub buttonConnect_Click(ByVal sender As System.Object, ByVal e As _


System.EventArgs) Handles buttonConnect.Click
ConnectSocket(checkResolveRemote.Checked, Trim(textBoxRemote.Text), _
Trim(textBoxRemotePort.Text))
End Sub
Networking Functions
327
CHAPTER 8

LISTING 8.4 Continued


Private Sub buttonSend_Click(ByVal sender As System.Object, ByVal e As _
System.EventArgs) Handles buttonSend.Click
SendToSocket(Trim(textBoxSend.Text))
End Sub
End Class

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

‘Return a winsock specific error message based on the passed


‘in errNum integer
Select Case errNum
Case 10013
GetDescription = “Permission denied”
Case 10048
GetDescription = “Address already in use”
Case 10049
GetDescription = “Cannot assign request address”
8
Case 10047

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

LISTING 8.4 Continued


Case 10056
GetDescription = “Socket is already connected”
Case 10024
GetDescription = “Too many open files”
Case 10040
GetDescription = “Message too long”
Case 10050
GetDescription = “Network is down”
Case 10052
GetDescription = “Network dropped connection on reset”
Case 10051
GetDescription = “Network is unreachable”
Case 10055
GetDescription = “No buffer space available”
Case 10042
GetDescription = “Bad protocol option”
Case 10057
GetDescription = “Socket is not connected”
Case 10038
GetDescription = “Socket operation on non-socket”
Case 10045
GetDescription = “Operation not supported”
Case 10046
GetDescription = “Protocol family not supported”
Case 10067
GetDescription = “Too many processes”
Case 10043
GetDescription = “Protocol not supported”
Case 10041
GetDescription = “Protocol wrong type for socket”
Case 10058
GetDescription = “Cannot send after socket shutdown”
Case 10044
GetDescription = “Socket type not supported”
Case 10060
GetDescription = “Connection timed out”
Case 10109
GetDescription = “Class type not found”
Case 10035
GetDescription = “Resource temporarily unavailable”
Case 11001
GetDescription = “Host not found”
Case 10091
GetDescription = “Network subsystem is unavailable”
Case 11002
GetDescription = “Non-authoritative host not found”
Networking Functions
329
CHAPTER 8

LISTING 8.4 Continued


Case 10101
GetDescription = “Graceful shutdown in progress”
Case Else
GetDescription = “unknown”
End Select

End Function
End Class

Learning by Example: ISBNCrawler Application


With this sample application, we’ll spend some time looking at a good design pattern revolving
around the use of the WebClient class and the WebRequest/WebResponse classes to access and
interact with Web-based resources. At the same time, we’ll introduce a key advanced con-
cept—asynchronous processing.
This application will crawl a few different Web sites looking for pricing information on a spe-
cific book (referenced by its ISBN number). Each request, and corresponding response, is per- 8
formed asynchronously. You will see responses coming in by examining the “activity log” on

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!

Key Concepts Covered


Specifically, we will show how to:
• Use the WebRequest class and WebResponse class to retrieve a specific HTML document
from a list of sites.
• Asynchronously download data from the sites.
• Parse the data and present it to the user on the screen as it becomes available.
Working with the .NET Namespaces
330
PART II

Code Walkthrough—A Basic Async Design Pattern


Asynchronous request/response patterns are common when accessing Web-based resources.
For instance, code that blocks while waiting for a reply back from a Web server may not be a
good thing.
The ISBNCrawler application follows a standard design pattern in .NET for writing code that
uses asynchronous callbacks. This is the same pattern we discussed earlier in this chapter (see
Figure 8.4).

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.

LISTING 8.5 Sample Application: ISBN Crawler


Imports System.Net
Imports System.IO
Imports System.Text
Networking Functions
331
CHAPTER 8

LISTING 8.5 Continued


Public Class Form1
Inherits System.Windows.Forms.Form

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()

‘Add any initialization after the InitializeComponent() call


targetSite = “http://www.amazon.com” & AMAZON_QRY
End Sub

‘Form overrides dispose to clean up the component list.


Public Overloads Sub Dispose()
MyBase.Dispose()
If Not (components Is Nothing) Then
components.Dispose()
End If
End Sub

Private WithEvents groupBox1 As System.Windows.Forms.GroupBox


Private WithEvents textBox1 As System.Windows.Forms.TextBox
Private WithEvents button1 As System.Windows.Forms.Button
Private WithEvents label1 As System.Windows.Forms.Label
Private WithEvents label2 As System.Windows.Forms.Label
Private WithEvents listBox2 As System.Windows.Forms.ListBox
Private WithEvents listBox1 As System.Windows.Forms.ListBox
Private WithEvents listView1 As System.Windows.Forms.ListView
Private WithEvents button3 As System.Windows.Forms.Button
Working with the .NET Namespaces
332
PART II

LISTING 8.5 Continued


Private WithEvents textBoxISBN As System.Windows.Forms.TextBox
Private WithEvents buttonGo As System.Windows.Forms.Button
Friend WithEvents GroupBox2 As System.Windows.Forms.GroupBox
Friend WithEvents RadioButton1 As System.Windows.Forms.RadioButton
Friend WithEvents RadioButton2 As System.Windows.Forms.RadioButton
Friend WithEvents RadioButton3 As System.Windows.Forms.RadioButton
Friend WithEvents ReportedPrice As System.Windows.Forms.Label

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub _
InitializeComponent()
Me.buttonGo = New System.Windows.Forms.Button()
Me.label1 = New System.Windows.Forms.Label()
Me.button3 = New System.Windows.Forms.Button()
Me.groupBox1 = New System.Windows.Forms.GroupBox()
Me.textBoxHTML = New System.Windows.Forms.TextBox()
Me.GroupBox2 = New System.Windows.Forms.GroupBox()
Me.RadioButton3 = New System.Windows.Forms.RadioButton()
Me.RadioButton2 = New System.Windows.Forms.RadioButton()
Me.RadioButton1 = New System.Windows.Forms.RadioButton()
Me.listBox2 = New System.Windows.Forms.ListBox()
Me.textBoxISBN = New System.Windows.Forms.TextBox()
Me.ReportedPrice = New System.Windows.Forms.Label()
Me.groupBox1.SuspendLayout()
Me.GroupBox2.SuspendLayout()
Me.SuspendLayout()

‘buttonGo

Me.buttonGo.Location = New System.Drawing.Point(272, 60)
Me.buttonGo.Name = “buttonGo”
Me.buttonGo.Size = New System.Drawing.Size(75, 20)
Me.buttonGo.TabIndex = 2
Me.buttonGo.Text = “Go!”

‘label1

Me.label1.Location = New System.Drawing.Point(236, 24)
Me.label1.Name = “label1”
Me.label1.Size = New System.Drawing.Size(36, 16)
Me.label1.TabIndex = 1
Me.label1.Text = “ISBN:”
Networking Functions
333
CHAPTER 8

LISTING 8.5 Continued



‘button3

Me.button3.Location = New System.Drawing.Point(364, 208)
Me.button3.Name = “button3”
Me.button3.Size = New System.Drawing.Size(75, 20)
Me.button3.TabIndex = 0
Me.button3.Text = “Clear”

‘groupBox1

Me.groupBox1.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.textBoxHTML, Me.button3})
Me.groupBox1.Location = New System.Drawing.Point(16, 132)
Me.groupBox1.Name = “groupBox1”
Me.groupBox1.Size = New System.Drawing.Size(448, 236)
Me.groupBox1.TabIndex = 3
Me.groupBox1.TabStop = False
Me.groupBox1.Text = “Returned HTML”

8
‘textBoxHTML

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

LISTING 8.5 Continued


‘RadioButton2

Me.RadioButton2.Location = New System.Drawing.Point(20, 56)
Me.RadioButton2.Name = “RadioButton2”
Me.RadioButton2.Size = New System.Drawing.Size(172, 16)
Me.RadioButton2.TabIndex = 1
Me.RadioButton2.Text = “BarnesAndNoble.com”

‘RadioButton1

Me.RadioButton1.Checked = True
Me.RadioButton1.Location = New System.Drawing.Point(20, 32)
Me.RadioButton1.Name = “RadioButton1”
Me.RadioButton1.Size = New System.Drawing.Size(172, 16)
Me.RadioButton1.TabIndex = 0
Me.RadioButton1.TabStop = True
Me.RadioButton1.Text = “Amazon.com”

‘listBox2

Me.listBox2.Location = New System.Drawing.Point(244, 72)
Me.listBox2.Name = “listBox2”
Me.listBox2.Size = New System.Drawing.Size(120, 95)
Me.listBox2.TabIndex = 1

‘textBoxISBN

Me.textBoxISBN.Location = New System.Drawing.Point(272, 20)
Me.textBoxISBN.Name = “textBoxISBN”
Me.textBoxISBN.Size = New System.Drawing.Size(172, 20)
Me.textBoxISBN.TabIndex = 0
Me.textBoxISBN.Text = “”

‘ReportedPrice

Me.ReportedPrice.Font = New System.Drawing.Font(“Microsoft Sans _
Serif”, 9!, _
System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, _
CType(0, Byte))
Me.ReportedPrice.ForeColor = System.Drawing.Color.Blue
Me.ReportedPrice.Location = New System.Drawing.Point(352, 60)
Me.ReportedPrice.Name = “ReportedPrice”
Me.ReportedPrice.Size = New System.Drawing.Size(124, 20)
Me.ReportedPrice.TabIndex = 5
Me.ReportedPrice.Text = “Price: <not found>”

‘Form1
Networking Functions
335
CHAPTER 8

LISTING 8.5 Continued



Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(496, 373)
Me.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.ReportedPrice, _
Me.GroupBox2, Me.buttonGo, Me.groupBox1, Me.label1, Me.textBoxISBN})
Me.Name = “Form1”
Me.Text = “ISBNCrawler”
Me.groupBox1.ResumeLayout(False)
Me.GroupBox2.ResumeLayout(False)
Me.ResumeLayout(False)

End Sub

Private Sub MainForm_Load(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles MyBase.Load
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

MsgBox(“You have entered an incorrectly formatted ISBN number. _


The ISBN you enter should be numeric, and should have 10 _
digits without any dashes or spaces.” & vbCrLf & vbCrLf & _
“Please try again.”)
Working with the .NET Namespaces
336
PART II

LISTING 8.5 Continued


End If
End Sub

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.

Private Sub IssueAsyncRequest()


Try
Dim req As WebRequest ‘encapsulates our request
Dim indx As Integer ‘used to loop through the site array

‘Create the request object


req = WebRequest.Create(targetSite)

‘Assign the request object into the state object


aState.HttpRequest = req

‘This kicks off the whole async request


req.BeginGetResponse(New AsyncCallback(AddressOf _
ReceiveResponse), aState)

Catch webErr As WebException ‘catch a WebRequest error


Dim status As WebExceptionStatus = webErr.Status

ResetFormCursor()

MsgBox(“An error occurred while creating the WebRequest->” & _


webErr.Message & “;” & webErr.StackTrace)

Catch appErr As Exception

ResetFormCursor()

MsgBox(“An application error occured->” & appErr.Message & “;” & _


appErr.StackTrace)

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

LISTING 8.5 Continued


Private Sub ReceiveResponse(ByVal rslt As IAsyncResult)
Dim priceGuess As String ‘hold the price string that we have _
attempted to parse

‘Start our block of structured exception handling


Try
‘Indicate that a response has been received
Me.Cursor = System.Windows.Forms.Cursors.WaitCursor
Me.Text = “ISBNCrawler - Response received.”

‘Get the state object from the async result


Dim retState As State = CType(rslt.AsyncState, State)

‘Now, pull the HttpWebRequest object out of the state object


Dim httpRequest As HttpWebRequest = retState.HttpRequest

‘Call EndGetResponse, which will produce the HttpWebResponse object


‘that came from the request issued above
Dim httpResp As HttpWebResponse = httpRequest.EndGetResponse(rslt)
8
‘Grab the stream from the response object

NETWORKING
FUNCTIONS
Dim respStream As Stream = httpResp.GetResponseStream()

‘ Store the reponse stream in State to read


‘ the stream asynchronously.
retState.RespStream = respStream

‘Start the async stream reads;


‘here, we use the StreamBuffer prop and set the callback to the
‘ReadStream routine
respStream.BeginRead(retState.StreamBuffer, 0, 1024, New _
AsyncCallback(AddressOf ReadStream), retState)

Catch webErr As WebException ‘ Catch the error.

Dim status As WebExceptionStatus = webErr.Status()

ResetFormCursor()

MsgBox(“An error was encountered retrieving the server _


response->” & webErr.Message & “;” & webErr.StackTrace)
Working with the .NET Namespaces
338
PART II

LISTING 8.5 Continued


‘Try to present some useful information if a protocol error
‘has occurred.
If status = WebExceptionStatus.ProtocolError Then
MsgBox(“PROTOCOL ERROR: “ & status.ToString)
End If

MessageBox.Show(Err().ToString) ‘ Show friendly error message.

Catch appErr As Exception ‘ Catch the error.

ResetFormCursor()

‘Any other error type should fall into here...


MsgBox(“An application error occurred->” & appErr.Message & “;” & _
appErr.StackTrace)

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)

‘ Retrieve the stream from the state object


Dim respStream As Stream = retState.RespStream

Dim bytesRead As Integer = respStream.EndRead(rslt)

‘If the stream still contains data...


If bytesRead > 0 Then

‘Use the encoder to x-late the byte array into a string


Dim streamStr As String = _
Encoding.ASCII.GetString(retState.StreamBuffer)

‘Concat the string into the stringbuilder in the state object


retState.RqstData.Append(streamStr)
Networking Functions
339
CHAPTER 8

LISTING 8.5 Continued


‘Call for another read
respStream.BeginRead(retState.StreamBuffer, 0, 1024, New _
AsyncCallback(AddressOf ReadStream), retState)

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

‘ Close down the response stream.


respStream.Close()

Me.Cursor = System.Windows.Forms.Cursors.Default

End If

Catch streamErr As IOException

ResetFormCursor()
8

NETWORKING
FUNCTIONS
MsgBox(“An IO error occurred with the stream object->” & _
streamErr.Message & “;” & streamErr.StackTrace)

Catch appErr As Exception

ResetFormCursor()

‘Any other error type should fall into here...


MsgBox(“An application error occurred->” & appErr.Message & “;” & _
appErr.StackTrace)

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

LISTING 8.5 Continued


Const PATTERN_MATCH As String = “Our Price: “

currPos = InStr(resp, PATTERN_MATCH)

priceGuess = Mid(resp, currPos + PATTERN_MATCH.Length, 7)

End Function

Another utility routine performs a rudimentary validation on an ISBN number.


Private Function ValidISBN(ByVal isbn As String) As Boolean
‘assume isbn is accurate...
ValidISBN = True

‘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

Private Sub WriteResult(ByVal price As String)


ReportedPrice.Text = “Price: “ & price
End Sub

Private Sub RadioButton1_CheckedChanged(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles RadioButton1.CheckedChanged
If RadioButton1.Checked Then
Me.targetSite = “http://www.” & RadioButton1.Text & AMAZON_QRY
End If
End Sub

Private Sub RadioButton2_CheckedChanged(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles RadioButton2.CheckedChanged
If RadioButton2.Checked Then
Me.targetSite = “http://www.” & RadioButton2.Text & BARNES_QRY
End If
End Sub

Private Sub RadioButton3_CheckedChanged(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles RadioButton3.CheckedChanged
If RadioButton3.Checked Then
Networking Functions
341
CHAPTER 8

LISTING 8.5 Continued


Me.targetSite = “http://search.” & RadioButton3.Text & BORDERS_QRY
End If

End Sub

Private Sub ResetFormCursor()


Me.Cursor = System.Windows.Forms.Cursors.Default
End Sub

Private Sub button3_Click(ByVal sender As System.Object, ByVal e As _


System.EventArgs) Handles button3.Click
textBoxHTML.Text = “”
End Sub

Friend WithEvents textBoxHTML As System.Windows.Forms.TextBox


End Class

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

Public Class State

‘Object to hold the WebRequest instance


Public HttpRequest As HttpWebRequest

‘Object to hold the stream from the response object


Public RespStream As Stream

‘Buffer for holding our reads into the stream


Public StreamBuffer(1024) As Byte

‘Because our stream reads will be done async, we need to


‘build up the entire response stream content through
‘concatenation - this will hold the concatenated response
‘data:
Public RqstData As New StringBuilder(“”)

Public Sub New()

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

• Drawing with the .NET Namespaces 347

• Drawing Basics 348

• Drawing Basic Shapes 355

• Filling Shapes 361

• Collections of Shapes 367

• Working with Images 372

• Transformations 375

• Learning by Example: A Forms-Based Drawing


Application 378
Working with the .NET Namespaces
344
PART II

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

Key Classes Related to Drawing


The process of drawing with the .NET Framework Class Library involves a whole host of
classes. These classes can be found inside of the System.Drawing namespace and its associated
third-level namespaces. At a glance, the drawing namespace in .NET is made up of the
following:
• System.Drawing: Provides basic graphics functionality. This chapter focuses on this
namespace.
• System.Drawing.Design: Focuses on providing functionality for extending the design
time environment. This namespace is beyond the scope of this book.
Drawing Functions
345
CHAPTER 9

• System.Drawing.Drawing2D: Provides two-dimensional and vector graphics classes and


methods. This namespace is covered within this chapter.
• System.Drawing.Imaging: Exposes advanced imaging functionality. This namespace is
beyond the scope of this book.
• System.Drawing.Printing: Gives you classes to manage output to a print device.
Chapter 6, “Font, Text, and Printing Operations,” covers this namespace.
• System.Drawing.Text: Wraps fonts and type management. Chapter 6 covers this name-
space.
This chapter is focused on the System.Drawing and System.Drawing.Drawing2D namespaces.
These two namespaces contain classes that are fundamental to the execution of common pro-
gramming tasks with .NET. The namespaces Printing and Text are covered elsewhere in the
book, and Design and Imaging are simply beyond the scope of this book as they encapsulate
more specialized features. There are certainly great classes within these namespaces, and we
encourage you to use this chapter as a leaping-off point to your own exploring. Table 9.1 lists
the key classes we will be discussing.

TABLE 9.1 Key Classes of System.Drawing and System.Drawing.Drawing2D


Class Description
Functional Drawing Classes
Graphics The Graphics class is the premier class within the namespace for
executing drawing and filling shapes.
GraphicsState The GraphicsState class is used to save the state of the Graphics
object between calls to transformations and the like. This class is
used with the BeginContainer and EndContainer methods of the
Graphics class.
9
Drawing Basics

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

TABLE 9.1 Continued


Class Description
Working with Images
Bitmap The Bitmap class encapsulates an image of type bitmap.
Icon The Icon class encapsulates a small bitmap image used to repre-
sent an object.
Image The Image class is the abstract base class used for both the Bitmap
and the Icon classes.
Graphic Fills
Brush The abstract Brush class is the base class for the various brush
classes throughout the drawing namespace. Brushes are used to
fill shapes with colors, textures, and patterns.
SolidBrush The SolidBrush class is a brush made of one solid color.
TextureBrush The TextureBrush class is a brush made up of an image. It allows
you to fill shapes with various versions of an image.
HatchBrush The HatchBrush class is used to create a brush based on a prede-
fined pattern, a foreground color, and a background color.
LinearGradientBrush The LinearGradientBrush is used to create brushes that blend
two colors across an object.
PathGradientBrush The PathGradientBrush can create a brush object that can be
used to fill paths.
Graphic Storage
Region The Region class is used to describe the inside of a graphics shape
made of rectangles and paths.
RegionData The RegionData class is used to store the data that makes up a
region.
GraphicsPath The GraphicsPath class groups connected lines and curves for
manipulation as a whole.
PathData The PathData class is used to store data that makes up a
GraphicsPath.
Utility Classes
Point The Point structure allows you to group x and y coordinates as a
single object or point on a 2D plane.
Size Similar to the Point structure, the Size structure groups width
and height.
Matrix The Matrix class is the mathematical foundation used to
transform graphics.
Drawing Functions
347
CHAPTER 9

Drawing with the .NET Namespaces


Windows has a very rich user interface. Users interact with it through a visual display; they
click menus, buttons, and toolbars; they read text and respond to dialog boxes. All of these are
things that must be drawn to the screen. A button is simply a bitmap image—a set of pixels. As
such, it must be drawn, and someone must write the code to draw it.

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

Suggestions for Further Exploration


➲ If you’re familiar with using GDI and want a quick primer on what’s new, check out
“What’s New in GDI+” inside of the MSDN library, “Programming with the .NET
Framework.”
➲ For more information on the Win32 GDI functions, see MSDN: Library/Graphics and
Multimedia/Windows GDI.
➲ For specific Win32 API calls for the Visual Basic programmer, see Dan Appleman’s
Visual Basic Programmer’s Guide to the Win32 API.

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.

Understanding Windows Coordinate Systems


The default coordinate system in Windows has the origin (0, 0) in the upper-left corner of the
drawing surface. The x-axis extends to the right, while the y-axis extends downward. The
pixel is the unit of measurement in the default coordinate system. To draw to a surface, you
specify what pixels you want your monitor to “turn on” to create the graphic. For instance, a
line can be defined by joining the pixels between a start and an end coordinate. Figure 9.1
illustrates these concepts.

origin
x-axis
(0, 0)

start point (3, 3)


y-axis

end point (8, 8)

FIGURE 9.1
Windows default coordinate system.
Drawing Functions
349
CHAPTER 9

The Graphics Class


The Graphics class is used to render the majority of all two-dimensional drawing in .NET. It is
far and away the class you will most often use to execute basic drawing tasks. The class pro-
vides methods for drawing all the basic 2D shapes, including: lines, rectangles, ellipses, poly-
gons, arcs, cardinal splines, and Bèzier splines. For the most part, if you need to draw a shape,
there will be an associated method of the Graphics class. For example, the DrawLine method is
used to draw a line and the DrawRectangle method is used for a rectangle. In fact, you can
often draw several graphic elements with a single method call. To do so, you simply use the
plural version of a given drawing method, such as DrawLines or DrawRectangles. All of these
methods (and the Graphics class itself) are covered in-depth throughout the rest of this chapter.

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.

TABLE 9.2 Pen Constructors

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

‘return the current form as a drawing surface


myGraphics = Graphics.FromHwnd(hwnd:=ActiveForm().Handle)

‘instantiate a new pen object using the color structure


myPen = New Pen(color:=Color.Blue, Width:=4)

‘draw the line on the form using the pen object


myGraphics.DrawLine(pen:=myPen, x1:=1, y1:=1, x2:=25, y2:=50)

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.

Dashes and Caps


What if you want to add an arrow to the end of your line? Or maybe, you need a dotted line to
get your point across. In addition to defining color and width, the Pen class is used to create
Drawing Functions
351
CHAPTER 9

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.

TABLE 9.3 Pen Class Dash and Cap Properties

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

‘dimension a local variable of type Pen


Dim myPen As Pen

‘instantiate a Pen using the color structure and width constructor


myPen = New Pen(color:=Color.Blue, Width:=5)

‘set the Pen’s end cap to be of type arrow


myPen.EndCap = Drawing.Drawing2D.LineCap.ArrowAnchor

‘set the Pen’s dash style to be a dash followed by a dot


myPen.DashStyle = Drawing.Drawing2D.DashStyle.DashDot

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.

TABLE 9.4 LineJoin Enumeration Members

Member Example Description


Bevel The Bevel member indicates a beveled join between the lines.

Miter The Miter member specifies an angled join.

Round The Round member creates a smooth and rounded join.

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)

‘return the current form as a drawing surface


myGraphics = Graphics.FromHwnd(hwnd:=ActiveForm().Handle)

‘add 2 intersecting lines to a path


myPath.AddLine(10, 10, 50, 10)
myPath.AddLine(50, 10, 50, 50)
Drawing Functions
353
CHAPTER 9

‘set the line join property


myPen.LineJoin = Drawing.Drawing2D.LineJoin.Miter

‘draw the line to the form


myGraphics.DrawPath(pen:=myPen, path:=myPath)

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.

TABLE 9.5 Graphics Class Curve Drawing 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

‘declare local variables


Dim myGraphics As Graphics
Dim myPen As Pen
Dim myPoints(2) As Point

‘create a 3-item array of point structures


myPoints(0) = New Point(100, 75)
myPoints(1) = New Point(125, 50)
myPoints(2) = New Point(150, 75)

‘return the current form as a drawing surface


myGraphics = Graphics.FromHwnd(hwnd:=ActiveForm().Handle)

‘instantiate a new pen object using the color structure


myPen = New Pen(color:=Color.Blue, Width:=2)

‘draw curve between the 3 points defined in the array


myGraphics.DrawCurve(pen:=myPen, points:=myPoints)

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

‘declare local variables


Dim myGraphics As Graphics
Dim myPen As Pen
Dim myPoints(3) As Point

‘create a 4-item array of point structures


myPoints(0) = New Point(100, 75)
myPoints(1) = New Point(125, 50)
myPoints(2) = New Point(150, 75)
myPoints(3) = New Point(175, 50)

‘return the current form as a drawing surface


myGraphics = Graphics.FromHwnd(ActiveForm().Handle)

‘instantiate a new pen object using the color structure


myPen = New Pen(Color.Blue, 2)

‘draw bezier using the points defined in the array


myGraphics.DrawBezier(pen:=myPen, pt1:=myPoints(0), pt2:=myPoints(1), _
pt3:=myPoints(2), pt4:=myPoints(3))

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).

Suggestions for Further Exploration


➲ For a complete listing of colors available through the Color structure, check out MSDN:
Visual Studio .NET/.NET Framework Class Library/System.Drawing/Color
Structure/Properties.
➲ Use the curve code examples presented in this section to experiment with the DrawArc
9
and DrawClosedCurve methods.

FUNCTIONS
DRAWING
➲ Use DrawBeziers method to create a series of Bèzier curves.

Drawing Basic Shapes


We continue our exploration of the namespace by drawing some basic shapes. Again, the vari-
ety and features contained in the namespace are astounding; we’ll try to take a path that
focuses on the shapes and illustration details you’re most likely to employ in building your
application.
Working with the .NET Namespaces
356
PART II

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.

LISTING 9.1 DrawRectangle

Private Sub Button1_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles Button1.Click

‘dimension variables of local scope


Dim myGraphics As Graphics
Dim myRectangle As Rectangle
Dim myPen As New Pen(Color.Blue)

‘return the current form as a drawing surface


myGraphics = Graphics.FromHwnd(ActiveForm().Handle)

‘create a rectangle based on x,y coordinates, width, & height


myRectangle = New Rectangle(x:=5, y:=5, Width:=10, Height:=40)

‘draw rectangle from pen and rectangle objects


myGraphics.DrawRectangle(pen:=myPen, rect:=myRectangle)

‘create a rectangle based on Point and Size objects


myRectangle = New Rectangle(Location:=New Point(10, 10), _
Size:=New Size(Width:=20, Height:=60))

‘draw another rectangle from Pen and new Rectangle object


myGraphics.DrawRectangle(pen:=myPen, rect:=myRectangle)

‘draw a rectangle from a Pen object, a rectangle’s x & y,


‘ width, & height
myGraphics.DrawRectangle(pen:=myPen, x:=20, y:=20, _
Width:=30, Height:=80)

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

‘return the current form as a drawing surface


myGraphics = Graphics.FromHwnd(ActiveForm().Handle)

‘draw an ellipse inside a bounding rectangle with a Pen instance


myGraphics.DrawEllipse(pen:=New Pen(color.Blue), _
rect:=New Rectangle(x:=5, y:=5, width:=70, height:=25))

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.

LISTING 9.2 DrawPolygon

Private Sub Button1_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles Button1.Click

‘declare variables of local scope


Dim myGraphics As Graphics
Dim myPoints() As Point

‘return the current form as a drawing surface


myGraphics = Graphics.FromHwnd(hwnd:=ActiveForm().Handle)

‘create a 7-item array of point structures in order of connection


ReDim myPoints(6)
myPoints(0) = New Point(15, 20)
myPoints(1) = New Point(25, 15)
myPoints(2) = New Point(35, 20)
myPoints(3) = New Point(35, 40)
myPoints(4) = New Point(25, 45)
myPoints(5) = New Point(15, 40)
myPoints(6) = New Point(15, 20)

‘draw the polygon (connect the dots between the points in the array)
myGraphics.DrawPolygon(pen:=New Pen(Color.Blue, Width:=2), _
points:=myPoints)

‘create a new 7-item array (y + 50 for offset)


Drawing Functions
359
CHAPTER 9

LISTING 9.2 Continued


ReDim myPoints(6)
myPoints(0) = New Point(15, 70)
myPoints(1) = New Point(25, 65)
myPoints(2) = New Point(35, 70)
myPoints(3) = New Point(15, 90)
myPoints(4) = New Point(25, 95)
myPoints(5) = New Point(35, 90)
myPoints(6) = New Point(15, 70)

‘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.

LISTING 9.3 DrawPie

Private Sub Button1_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles Button1.Click

‘dimension variables of local scope


Dim myGraphics As Graphics
Dim myRectangle As Rectangle
Dim myPen As New Pen(color:=Color.Blue, Width:=1)

‘return the current form as a drawing surface


myGraphics = Graphics.FromHwnd(ActiveForm().Handle)

‘create a rectangle based on x,y coor. width & height


myRectangle = New Rectangle(x:=5, y:=5, Width:=50, Height:=50)

‘draw the upper-right pie section


myGraphics.DrawPie(pen:=myPen, rect:=myRectangle, _
startAngle:=0, sweepAngle:=-90)

‘draw the upper-left pie section


myGraphics.DrawPie(pen:=myPen, rect:=myRectangle, _
startAngle:=-90, sweepAngle:=-90)

‘draw the bottom-left pie section


myGraphics.DrawPie(pen:=myPen, rect:=myRectangle, _
startAngle:=-180, sweepAngle:=-90)

‘draw the bottom-right pie section


myGraphics.FillPie(New SolidBrush(Color.Chartreuse), _
rect:=New Rectangle(x:=8, y:=8, Width:=50, Height:=50), _
startAngle:=-270, sweepAngle:=-90)

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.

Suggestions for Further Exploration


➲ Experiment with the following methods of the Rectangle structure: inflate,
intersects, and contains.

➲ Check out the Graphics class methods: DrawRectangles, DrawLines, FillRectangle,


and FillPolygon.

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

‘create a new instance of TextureBrush


myBrush = New TextureBrush(imageList1().Images.Item(0), _
Drawing.Drawing2D.WrapMode.TileFlipXY)

Table 9.6 demonstrates how you can use the WrapMode property to tile an image inside the
TextureBrush object to create a desired effect.

TABLE 9.6 TextureBrush WrapMode Enumeration

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

LISTING 9.4 LinearGradientBrush

Private Sub Button1_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles Button1.Click

‘local scope
Dim myGraphics As Graphics
Dim myBrush As System.Drawing.Drawing2D.LinearGradientBrush
Dim myRectangle As Rectangle

‘return the current form as a drawing surface


myGraphics = Graphics.FromHwnd(hwnd:=ActiveForm().Handle)

‘create a rectangle object


myRectangle = New Rectangle(x:=5, y:=5, Width:=40, Height:=50)

‘draw the rectangle to the surface


myGraphics.DrawRectangle(pen:=New Pen(Color.Black), rect:=myRectangle)

‘create the gradient brush


myBrush = New System.Drawing.Drawing2D.LinearGradientBrush( _
rect:=myRectangle, _
color1:=Color.White, _
color2:=Color.DarkSlateBlue, _
LinearGradientMode:= _
System.Drawing.Drawing2D.LinearGradientMode.Vertical)

‘fill the rectangle using the gradient brush


myGraphics.FillRectangle(brush:=myBrush, rect:=myRectangle)

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.

TABLE 9.7 LinearGradientMode Enumeration

Member Effect Description


Vertical The Vertical member indicates a fill pattern
from the top of the object to the bottom.
Horizontal The Horizontal member indicates a fill pattern
from the left of an object to its right.
Working with the .NET Namespaces
364
PART II

TABLE 9.7 Continued


Member Effect Description

ForwardDiagonal The ForwardDiagonal member indicates a fill


pattern from the upper-left corner of an object
to the lower-right corner.
BackwardDiagonal The BackwardDiagonal member indicates a fill
pattern from the upper-right corner of an object
to the lower-left corner.

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.

LISTING 9.5 HatchBrush

Private Sub Button1_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles Button1.Click

‘local scope
Dim myGraphics As Graphics
Dim myBrush As System.Drawing.Drawing2D.HatchBrush
Dim myRectangle As Rectangle

‘return the current form as a drawing surface


myGraphics = Graphics.FromHwnd(hwnd:=ActiveForm().Handle)

‘create a rectangle object to bind the ellipse


myRectangle = New Rectangle(x:=5, y:=5, Width:=70, Height:=60)

‘draw the ellipse to the surface


myGraphics.DrawEllipse(pen:=New Pen(Color.Black), rect:=myRectangle)

‘create the hatch brush


myBrush = New System.Drawing.Drawing2D.HatchBrush( _
hatchstyle:=Drawing.Drawing2D.HatchStyle.LargeCheckerBoard, _
ForeColor:=Color.Black, _
BackColor:=Color.White)
Drawing Functions
365
CHAPTER 9

LISTING 9.5 Continued


‘fill the ellipse using the hatch brush
myGraphics.FillEllipse(brush:=myBrush, rect:=myRectangle)

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.

TABLE 9.8 HatchStyle Enumeration

Member Effect Member Effect

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

TABLE 9.8 Continued


Member Effect Member Effect

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

LISTING 9.6 GraphicsPath

Private Sub Button1_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles Button1.Click

‘dimension variables of local scope


Dim myGraphics As Graphics
Dim myPen As New Pen(color:=Color.Blue, Width:=2)
Dim myPath As System.Drawing.Drawing2D.GraphicsPath
Dim myPoints(2) As Point

‘create a new GraphicsPath instance with default values


myPath = New System.Drawing.Drawing2D.GraphicsPath()

‘start the figure


myPath.StartFigure()

‘add an ellipse for the head


myPath.AddEllipse(x:=0, y:=0, Width:=50, Height:=70)

‘add 2 ellipses to the eyes


myPath.AddEllipse(x:=10, y:=10, Width:=10, Height:=8)
myPath.AddEllipse(x:=30, y:=10, Width:=10, Height:=8)

‘add bezier for the nose


myPath.AddBezier( _
pt1:=New Point(x:=25, y:=30), _
pt2:=New Point(x:=15, y:=30), _
pt3:=New Point(x:=20, y:=40), _
pt4:=New Point(x:=25, y:=40))

‘add a points to make a curve for the mouth


myPath.StartFigure()
myPoints(0) = New Point(x:=10, y:=50)
myPoints(1) = New Point(x:=25, y:=60)
myPoints(2) = New Point(x:=40, y:=50)
myPath.AddCurve(points:=myPoints)

‘return the current form as a drawing surface


myGraphics = Graphics.FromHwnd(hwnd:=ActiveForm().Handle)

‘output the Path to the drawing surface of the form


myGraphics.DrawPath(pen:=myPen, path:=myPath)

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.

Regions and Clipping


A region is a section of the screen defined by a given path or rectangle. Regions allow you to
define clip areas and do hit-testing based on a graphics area. Clipping involves one shape
defining the border, or area, of another shape. Additional items drawn within a defined region
are constrained by the region; that is, a line with a width of 50 drawn within a rectangular
region whose width is 20 will be cropped to 20 for display. Hit-testing simply allows your
application to know when the user has placed the mouse over a given region or if another
shape is contained within the area defined by the region. For example, if you define a region
based on a rectangle, you can trap when a user clicks on the rectangle or when his or her
mouse travels over the rectangle.
You use the Region class to create regions with the namespace. An instance of the Region
class can be created with either a valid Rectangle instance or a Path object. To hit-test, you
use the IsVisible method of the Region class. Once a region has been defined, you can pass a
point or a rectangle and a valid graphics surface as parameters to the IsVisible method. This
method simply returns True if the given point or rectangle is contained within the Region; oth-
erwise, it returns False. The following is an example of this method call; it displays the return
of the IsVisible method to a message box.
9
MsgBox(prompt:=myRegion.IsVisible(x:=75, y:=75, g:=myGraphics))

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

LISTING 9.7 Clipping with the Region Class


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

‘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)

‘add triangle to the path


myPath.AddPolygon(points:=myPoints)

‘create a region based on the path


myRegion = New Region(path:=myPath)

‘return the current form as a drawing surface


myGraphics = Graphics.FromHwnd(hwnd:=ActiveForm().Handle)

‘draw the region’s outline to the screen


myGraphics.DrawPath(pen:=myPen, path:=myPath)

‘set the clipping region


myGraphics.SetClip(Region:=myRegion, _
combineMode:=Drawing.Drawing2D.CombineMode.Replace)

‘draw the string multiple times


For i = 20 To 100 Step 20

‘draw clipped text


myGraphics.DrawString(s:=”Clipping Region”, _
Font:=New Font(familyName:=”Arial”, emSize:=18, _
style:=FontStyle.Regular, _
unit:=GraphicsUnit.Pixel), _
brush:=New SolidBrush(color:=Color.Red), _
x:=50, y:=i)

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).

TABLE 9.9 Region Combine Modes


Enumeration Member Example Output Description
This is an example of the two objects that we
are combining. The rectangle represents the
clipping region.
Complement The Complement member indicates that only
the portion of the second region that does not
intersect with the first should be updated.
Exclude The Exclude member indicates that the
intersecting portion of the second region should
be excluded from the first region. The result is
only points that belong to the first region
specifically.
Intersect The Intersect member indicates that only
points common to both regions should be valid.
Replace The Replace member indicates that the second
region replaces the internal region defined by
the first. That is, the first region now contains, 9
or defines the area for, the second region.
The Union member indicates that both regions

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

‘return the current form as a drawing surface


myGraphics = Graphics.FromHwnd(hwnd:=ActiveForm().Handle)

‘create the first path and region


myPath.StartFigure()
myPath.AddRectangle(rect:=New Rectangle(x:=50, y:=50, Width:=50, _
Height:=20))
myRegion = New Region(path:=myPath)

‘create the first path and region


myPath2.StartFigure()
Dim myRec As New Rectangle(x:=35, y:=45, Width:=50, Height:=30)
myPath2.AddEllipse(rect:=myRec)
myRegion2 = New Region(path:=myPath2)

‘add the paths together


myRegion.Complement(Region:=myRegion2)

‘fill the region


myGraphics.FillRegion(brush:=New SolidBrush(Color.Black), _
Region:=myRegion)

Suggestions for Further Exploration


➲ Write code using the following methods of the GraphicsPath class: Warp and Widen.
➲ Use the interior of text to outline a clipping region. Create a Path based on a string and
use the path to create the Region.

Working with Images


In this section, you will see how to use two of the most common graphic types a programmer
interacts with: bitmaps and icons. As stated earlier in the chapter, all user-interface objects in
Windows are some form of a bitmap; a bitmap is simply a collection of pixels set to various
colors.
Drawing Functions
373
CHAPTER 9

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.

LISTING 9.8 Scale and Crop


Protected Overrides Sub OnClick(ByVal e As System.EventArgs)

‘local scope
Dim myBitmap As System.Drawing.Bitmap
Dim myGraphics As Graphics
Dim mySource As Rectangle
Dim myDestination As Rectangle

‘create an instance of bitmap based on a file


myBitmap = New System.Drawing.Bitmap(fileName:=”dotnet.gif”)

‘return the current form as a drawing surface


myGraphics = Graphics.FromHwnd(ActiveForm().Handle)

‘define a rectangle as the size of the original image (source)


mySource = New Rectangle(x:=0, y:=0, Width:=81, Height:=45)

‘draw the original bitmap to the source rectangle


myGraphics.DrawImage(image:=myBitmap, rect:=mySource)

‘create a destination rectangle


myDestination = New Rectangle(x:=90, y:=0, Width:=162, Height:=90)

‘output the image to the dest. rectangle (scale)


myGraphics.DrawImage(image:=myBitmap, rect:=myDestination)

‘output a cropped portion of the source


myGraphics.DrawImage(image:=myBitmap, _
destRect:=New Rectangle(x:=0, y:=100, Width:=30, Height:=30), _
srcRect:=New Rectangle(x:=0, y:=35, Width:=14, Height:=14), _
srcUnit:=GraphicsUnit.Pixel)

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)

Rotating, Scaling, Skewing, and Flipping


Transformation in GDI+—rotating, scaling, skewing, and flipping—happens with the aid of
matrices. A matrix is a set of numbers arranged in rows and columns. For instance, the point
(2, 3) on a 2D plane can be represented as a 1 × 2 matrix. That is, there is one row of data
(2 and 3) and a column for both x and y.
Drawing Functions
377
CHAPTER 9

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.

TABLE 9.10 Transformation Examples


Transformation Code
myGraphics.RotateTransform(angle:=45)
Effect

Description This example uses the RotateTransform method to rotate an image at a


45° angle.

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

TABLE 9.10 Continued


Transformation Code
myBitmap.RotateFlip(RotateFlipType.RotateNoneFlipX)
Effect

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.”

Suggestions for Further Exploration


➲ For more information on working with matrices, read MSDN: Visual Studio .NET/.NET
Framework/Programming with the .NET Framework/Drawing and Editing Images/About
GDI+ Managed Code/Coordinate Systems and Transformations.

Learning by Example: A Forms-Based Drawing


Application
In this example, we build upon what you’ve learned throughout the chapter to create a simple,
forms-based drawing application. As an application, it is not very useful; the Paint application
that ships with all copies of Windows has more features. However, as a learning tool, it should
give you a nice test harness in which to experiment with writing your own code and to watch
code execute.
Drawing Functions
379
CHAPTER 9

Key Concepts Covered


The following represents the key concepts covered by this sample application:
• Creating an MDI application
• Managing a drawing surface
• Maintaining drawing state with the GraphicsState class
• Using the Color structure
• Drawing, filling, and transforming shapes with the Graphics class

MDI Parent and Child Forms


To define the drawing surface in our application, we will use the multiple document interface
(MDI) paradigm. This paradigm includes a parent, or container, form that provides the events
and management of child forms. This is similar to Microsoft Word or Excel. The MDI form
has a simple menu bar that provides the basic functionality for the form. Figure 9.6 shows the
MDI form and its child form in their initial state.

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

LISTING 9.9 The Code for the MDI Form


Public Class frmMDI

Inherits System.Windows.Forms.Form

Private WithEvents menuItemDraw As System.Windows.Forms.MenuItem


Private WithEvents menuItemSurface As System.Windows.Forms.MenuItem
Private WithEvents menuItemExit As System.Windows.Forms.MenuItem
Private WithEvents menuItemNew As System.Windows.Forms.MenuItem

#Region “ Windows Form Designer generated code “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call

End Sub

‘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

Private WithEvents menuItem1 As System.Windows.Forms.MenuItem


Private WithEvents menuItem2 As System.Windows.Forms.MenuItem
Private WithEvents menuItem3 As System.Windows.Forms.MenuItem
Private WithEvents menuItem4 As System.Windows.Forms.MenuItem
Private WithEvents mainMenu1 As System.Windows.Forms.MainMenu

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
Drawing Functions
381
CHAPTER 9

LISTING 9.9 Continued


<System.Diagnostics.DebuggerStepThrough()> Private Sub _
InitializeComponent()
Me.mainMenu1 = New System.Windows.Forms.MainMenu()
Me.menuItemSurface = New System.Windows.Forms.MenuItem()
Me.menuItemExit = New System.Windows.Forms.MenuItem()
Me.menuItemDraw = New System.Windows.Forms.MenuItem()
Me.menuItemNew = New System.Windows.Forms.MenuItem()
Me.mainMenu1.MenuItems.AddRange(New System.Windows.Forms.MenuItem() _
{Me.menuItemNew, Me.menuItemSurface, Me.menuItemDraw, _
Me.menuItemExit})
Me.menuItemSurface.Index = 1
Me.menuItemSurface.Text = “&Surface”
Me.menuItemExit.Index = 3
Me.menuItemExit.Text = “&Exit”
Me.menuItemDraw.Index = 2
Me.menuItemDraw.Text = “&Draw”
Me.menuItemNew.Index = 0
Me.menuItemNew.Text = “&New”
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(395, 288)
Me.IsMdiContainer = True
Me.Menu = Me.mainMenu1
Me.Text = “System.Drawing Example”

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

‘purpose: initialize the application, create the child form

‘create a new instance of the child form


m_myChild = New frmChild()

‘set the parent of the child form to the MDI form


m_myChild.MdiParent = Me

‘show the form to the user


m_myChild.Show()
Working with the .NET Namespaces
382
PART II

‘set form to default values


Call resetChildForm()

‘instantiate a graphics object with the child’s handle


m_myGraphics = Graphics.FromHwnd(m_myChild.Handle)

End Sub

The resetChildForm procedure allows us to create new forms based on the default values for
the application.
Private Sub resetChildForm()

‘purpose: clear the child form

‘set the form back to default values


m_myChild.Left = m_LeftPos
m_myChild.Top = m_TopPos
m_myChild.Width = m_Width
m_myChild.Height = m_Height

‘set the background and foreground colors of the child form


‘ to their defaults
m_myChild.BackColor = Color.FromName(m_ChildColor)
m_myChild.ForeColor = Color.FromName(m_ChildColor)

‘refresh the form


m_myChild.Refresh()

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

‘note: no new form is actually created, we simply clear and reset


‘ current form
Drawing Functions
383
CHAPTER 9

‘call the method used to reset the child form to its defaults
Call resetChildForm()

End Sub

Private Sub menuItemExit_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles menuItemExit.Click

‘purpose: close the app. when the user clicks the EXIT menu item

‘kill the application


End

End Sub

Private Sub menuSuface_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles menuItemSurface.Click

‘purpose: show the surface dialog when the user clicks the
‘ SURFACE menu item

‘local scope
Dim mySurface As frmSuface

‘create new suface form


mySurface = New 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

Private Sub menuItemDraw_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles menuItemDraw.Click

‘purpose: show the draw dialog when the user clicks


‘ the DRAW menu item

‘local scope
Dim myDraw As frmDraw
Working with the .NET Namespaces
384
PART II

‘create new suface form


myDraw = New frmDraw()

‘set the start position of the modal dialog to the center


‘ positions of its parent
myDraw.StartPosition = FormStartPosition.CenterParent

‘show the form as modal


myDraw.ShowDialog(Me)

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.

LISTING 9.10 System.Drawing Child Form (formChild.vb)

Public Class formChild


Inherits System.Windows.Forms.Form

#Region “ Windows Form Designer generated code “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call

End Sub
Drawing Functions
385
CHAPTER 9

‘Form overrides dispose to clean up the component list.


Public Overrides Sub Dispose()
MyBase.Dispose()
If Not (components Is Nothing) Then
components.Dispose()
End If
End Sub

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
Private Sub <System.Diagnostics.DebuggerStepThrough()> _
InitializeComponent()

‘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.

LISTING 9.11 System.Drawing Surface Form (formSurface.vb)

The listing starts by defining the form.


Public Class frmSuface
Inherits System.Windows.Forms.Form

#Region “ Windows Form Designer generated code “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call

End Sub

‘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

Private WithEvents groupBox1 As System.Windows.Forms.GroupBox


Private WithEvents groupBox2 As System.Windows.Forms.GroupBox
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 label4 As System.Windows.Forms.Label
Drawing Functions
387
CHAPTER 9

LISTING 9.11 Continued


Private WithEvents comboBoxColors As System.Windows.Forms.ComboBox
Private WithEvents buttonCancel As System.Windows.Forms.Button
Private WithEvents textBoxWidth As System.Windows.Forms.TextBox
Private WithEvents textBoxHeight As System.Windows.Forms.TextBox
Private WithEvents buttonOk As System.Windows.Forms.Button
Private WithEvents buttonDefaults As System.Windows.Forms.Button

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub _
InitializeComponent()
Me.groupBox2 = New System.Windows.Forms.GroupBox()
Me.comboBoxColors = New System.Windows.Forms.ComboBox()
Me.buttonCancel = New System.Windows.Forms.Button()
Me.buttonDefaults = New System.Windows.Forms.Button()
Me.textBoxHeight = New System.Windows.Forms.TextBox()
Me.buttonOk = New System.Windows.Forms.Button()
Me.label4 = New System.Windows.Forms.Label()
Me.groupBox1 = New System.Windows.Forms.GroupBox()
Me.textBoxWidth = New System.Windows.Forms.TextBox()
Me.label1 = New System.Windows.Forms.Label()
Me.label2 = New System.Windows.Forms.Label()
Me.label3 = New System.Windows.Forms.Label()
Me.groupBox2.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.comboBoxColors})
Me.groupBox2.Location = New System.Drawing.Point(8, 12)
9
Me.groupBox2.Size = New System.Drawing.Size(176, 68)

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

LISTING 9.11 Continued


Me.buttonCancel.Text = “Cancel”
Me.buttonDefaults.Location = New System.Drawing.Point(196, 76)
Me.buttonDefaults.TabIndex = 6
Me.buttonDefaults.Text = “Defaults”
Me.textBoxHeight.Location = New System.Drawing.Point(56, 56)
Me.textBoxHeight.MaxLength = 4
Me.textBoxHeight.Size = New System.Drawing.Size(56, 20)
Me.textBoxHeight.TabIndex = 3
Me.textBoxHeight.Text = “textBoxHeight”
Me.buttonOk.Location = New System.Drawing.Point(196, 12)
Me.buttonOk.TabIndex = 4
Me.buttonOk.Text = “Ok”
Me.label4.Location = New System.Drawing.Point(116, 30)
Me.label4.Size = New System.Drawing.Size(48, 16)
Me.label4.TabIndex = 7
Me.label4.Text = “pixels”
Me.groupBox1.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.label4, Me.label3, Me.label2, Me.label1, Me.textBoxHeight, _
Me.textBoxWidth})
Me.groupBox1.Location = New System.Drawing.Point(8, 92)
Me.groupBox1.Size = New System.Drawing.Size(176, 92)
Me.groupBox1.TabIndex = 0
Me.groupBox1.TabStop = False
Me.groupBox1.Text = “Dimensions”
Me.textBoxWidth.Location = New System.Drawing.Point(56, 24)
Me.textBoxWidth.MaxLength = 4
Me.textBoxWidth.Size = New System.Drawing.Size(56, 20)
Me.textBoxWidth.TabIndex = 2
Me.textBoxWidth.Text = “textBoxWidth”
Me.label1.Location = New System.Drawing.Point(8, 28)
Me.label1.Size = New System.Drawing.Size(48, 16)
Me.label1.TabIndex = 6
Me.label1.Text = “Width”
Me.label2.Location = New System.Drawing.Point(8, 60)
Me.label2.Size = New System.Drawing.Size(48, 16)
Me.label2.TabIndex = 7
Me.label2.Text = “Height”
Me.label3.Location = New System.Drawing.Point(116, 62)
Me.label3.Size = New System.Drawing.Size(48, 16)
Me.label3.TabIndex = 7
Me.label3.Text = “pixels”
Me.AcceptButton = Me.buttonOk
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle
Me.CancelButton = Me.buttonCancel
Drawing Functions
389
CHAPTER 9

LISTING 9.11 Continued


Me.ClientSize = New System.Drawing.Size(283, 192)
Me.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.groupBox2, Me.buttonOk, Me.buttonCancel, Me.buttonDefaults, _
Me.groupBox1})
Me.MaximizeBox = False
Me.MinimizeBox = False
Me.ShowInTaskbar = False
Me.Text = “Drawing Surface”

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.

Private Sub frmSuface_Load(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles MyBase.Load

‘purpose: load the drawing surface form whose purpose is


‘ to set the properties of the drawing suface (child form) 9
‘local scope

FUNCTIONS
DRAWING
Dim myColor As System.Drawing.Color
Dim myProps() As System.Reflection.PropertyInfo
Dim myType As System.Type
Dim count As Integer

‘return the type of the color structure


myType = myColor.GetType()

‘return the property information of the structure


myProps = myType.GetProperties()

‘iterate the properties and add to the combo box


For count = 0 To UBound(myProps)
Working with the .NET Namespaces
390
PART II

‘make sure we only get valid colors


If myProps(count).PropertyType.ToString() = “System.Drawing.Color” _
And myProps(count).Name <> “Transparent” Then

‘add the property name (color) to the combo box


comboBoxColors().Items.Add(myProps(count).Name)

End If
Next

‘select the current child bg color in the properties dialog


comboBoxColors().SelectedIndex = comboBoxColors().FindString( _
m_myChild.BackColor.Name())

‘set the current values of the active drawing surface


textBoxWidth().Text = CStr(m_myChild.Width())
textBoxHeight().Text = CStr(m_myChild.Height())

‘set the ok button as the default button


Me.AcceptButton = buttonOk()
Me.CancelButton = buttonCancel()

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

‘kill the form


Me.Close()

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

‘purpose: set the properties of the drawing surface


‘ equal to the application’s default values

‘set drawing surface property boxes to their defaults


textBoxWidth().Text = CStr(m_Width)
textBoxHeight().Text = CStr(m_Height)
Drawing Functions
391
CHAPTER 9

‘select the default color of white


comboBoxColors().SelectedIndex = _
comboBoxColors().FindString(m_ChildColor)

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

‘purpose: reset the properties of the drawing suface

‘validate the textBox myects befor submission


If Not IsNumeric(textBoxWidth().Text) Or _
Not IsNumeric(textBoxHeight().Text) Then

‘we would want to do more if this were a production app ...


Beep()

Else

‘set the dimensions of the drawing surface


m_myChild.Width = CInt(textBoxWidth().Text)
m_myChild.Height = CInt(textBoxHeight().Text)

‘set the background color 9


m_SurfaceBackground = comboBoxColors().SelectedItem.ToString
m_myChild.BackColor = Color.FromName(m_SurfaceBackground)

FUNCTIONS
DRAWING
m_myChild.Refresh()

‘settings applied, close form and return processing back to MDI


Me.Close()

‘re-get the form to the graphics object


m_myGraphics = Graphics.FromHwnd(m_myChild.Handle)

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.

LISTING 9.12 System.Drawing Draw Form (formDraw)

Public Class frmDraw


Inherits System.Windows.Forms.Form

#Region “ Windows Form Designer generated code “


Drawing Functions
393
CHAPTER 9

LISTING 9.12 Continued


Public Sub New()
MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call

End Sub

‘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

‘form and control property assigments


Private WithEvents groupBox1 As System.Windows.Forms.GroupBox
Private WithEvents groupBox2 As System.Windows.Forms.GroupBox
Private WithEvents groupBox3 As System.Windows.Forms.GroupBox
Private WithEvents groupBox4 As System.Windows.Forms.GroupBox
Private WithEvents comboBoxShape As System.Windows.Forms.ComboBox
Private WithEvents label1 As System.Windows.Forms.Label
Private WithEvents label2 As System.Windows.Forms.Label
Private WithEvents label3 As System.Windows.Forms.Label 9
Private WithEvents label4 As System.Windows.Forms.Label
Private WithEvents label5 As System.Windows.Forms.Label

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

LISTING 9.12 Continued


Private WithEvents comboBoxPattern As System.Windows.Forms.ComboBox
Private WithEvents comboBoxSolidColor As System.Windows.Forms.ComboBox
Private WithEvents comboBoxOutlineColor As System.Windows.Forms.ComboBox
Private WithEvents buttonApply As System.Windows.Forms.Button
Private WithEvents numericUpDownWeight As _
System.Windows.Forms.NumericUpDown
Private WithEvents radioButtonSolid As System.Windows.Forms.RadioButton
Private WithEvents radioButtonBlend As System.Windows.Forms.RadioButton
Private WithEvents radioButtonNone As System.Windows.Forms.RadioButton
Private WithEvents radioButtonPattern As System.Windows.Forms.RadioButton
Private WithEvents label11 As System.Windows.Forms.Label
Private WithEvents label12 As System.Windows.Forms.Label
Private WithEvents comboBoxPattFore As System.Windows.Forms.ComboBox
Private WithEvents comboBoxBackFore As System.Windows.Forms.ComboBox
Private WithEvents label13 As System.Windows.Forms.Label
Private WithEvents comboBoxBlendStyle As System.Windows.Forms.ComboBox
Private WithEvents comboBoxPattBack As System.Windows.Forms.ComboBox
Private WithEvents label14 As System.Windows.Forms.Label
Private WithEvents numericUpDownRotate As _
System.Windows.Forms.NumericUpDown
Private WithEvents buttonClear As System.Windows.Forms.Button
Private WithEvents label15 As System.Windows.Forms.Label

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub _
InitializeComponent()
Me.buttonApply = New System.Windows.Forms.Button()
Me.radioButtonNone = New System.Windows.Forms.RadioButton()
Me.numericUpDownRotate = New System.Windows.Forms.NumericUpDown()
Me.textBoxHeight = New System.Windows.Forms.TextBox()
Me.radioButtonSolid = New System.Windows.Forms.RadioButton()
Me.comboBoxBlendTo = New System.Windows.Forms.ComboBox()
Me.textBoxY = New System.Windows.Forms.TextBox()
Me.textBoxWidth = New System.Windows.Forms.TextBox()
Me.buttonClear = New System.Windows.Forms.Button()
Me.radioButtonBlend = New System.Windows.Forms.RadioButton()
Me.comboBoxShape = New System.Windows.Forms.ComboBox()
Me.comboBoxPattern = New System.Windows.Forms.ComboBox()
Me.comboBoxPattBack = New System.Windows.Forms.ComboBox()
Me.comboBoxBlendStyle = New System.Windows.Forms.ComboBox()
Drawing Functions
395
CHAPTER 9

LISTING 9.12 Continued


Me.label15 = New System.Windows.Forms.Label()
Me.label14 = New System.Windows.Forms.Label()
Me.label11 = New System.Windows.Forms.Label()
Me.label10 = New System.Windows.Forms.Label()
Me.label13 = New System.Windows.Forms.Label()
Me.label12 = New System.Windows.Forms.Label()
Me.comboBoxOutlineColor = New System.Windows.Forms.ComboBox()
Me.buttonOk = New System.Windows.Forms.Button()
Me.label8 = New System.Windows.Forms.Label()
Me.label9 = New System.Windows.Forms.Label()
Me.comboBoxSolidColor = New System.Windows.Forms.ComboBox()
Me.buttonCancel = New System.Windows.Forms.Button()
Me.label4 = New System.Windows.Forms.Label()
Me.label5 = New System.Windows.Forms.Label()
Me.label6 = New System.Windows.Forms.Label()
Me.label7 = New System.Windows.Forms.Label()
Me.label2 = New System.Windows.Forms.Label()
Me.label3 = New System.Windows.Forms.Label()
Me.comboBoxBlendFrom = New System.Windows.Forms.ComboBox()
Me.radioButtonPattern = New System.Windows.Forms.RadioButton()
Me.textBoxX = New System.Windows.Forms.TextBox()
Me.comboBoxPattFore = New System.Windows.Forms.ComboBox()
Me.groupBox1 = New System.Windows.Forms.GroupBox()
Me.groupBox2 = New System.Windows.Forms.GroupBox()
Me.groupBox3 = New System.Windows.Forms.GroupBox()
Me.groupBox4 = New System.Windows.Forms.GroupBox()
Me.groupBox5 = New System.Windows.Forms.GroupBox()
Me.numericUpDownWeight = New System.Windows.Forms.NumericUpDown()
Me.label1 = New System.Windows.Forms.Label()
CType(Me.numericUpDownRotate, _
9
System.ComponentModel.ISupportInitialize).BeginInit()

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

LISTING 9.12 Continued


Me.numericUpDownRotate.Minimum = New Decimal(New Integer() _
{360, 0, 0, -2147483648})
Me.numericUpDownRotate.Size = New System.Drawing.Size(52, 20)
Me.numericUpDownRotate.TabIndex = 1
Me.textBoxHeight.Location = New System.Drawing.Point(72, 88)
Me.textBoxHeight.MaxLength = 4
Me.textBoxHeight.Size = New System.Drawing.Size(48, 20)
Me.textBoxHeight.TabIndex = 3
Me.radioButtonSolid.Location = New System.Drawing.Point(12, 52)
Me.radioButtonSolid.TabIndex = 1
Me.radioButtonSolid.Text = “Solid”
Me.comboBoxBlendTo.DropDownStyle = _
System.Windows.Forms.ComboBoxStyle.DropDownList
Me.comboBoxBlendTo.DropDownWidth = 132
Me.comboBoxBlendTo.Location = New System.Drawing.Point(68, 136)
Me.comboBoxBlendTo.Size = New System.Drawing.Size(132, 21)
Me.comboBoxBlendTo.TabIndex = 8
Me.textBoxY.Location = New System.Drawing.Point(32, 52)
Me.textBoxY.MaxLength = 4
Me.textBoxY.Size = New System.Drawing.Size(48, 20)
Me.textBoxY.TabIndex = 5
Me.textBoxWidth.Location = New System.Drawing.Point(72, 60)
Me.textBoxWidth.MaxLength = 4
Me.textBoxWidth.Size = New System.Drawing.Size(48, 20)
Me.textBoxWidth.TabIndex = 2
Me.buttonClear.Location = New System.Drawing.Point(12, 432)
Me.buttonClear.TabIndex = 7
Me.buttonClear.Text = “Clear”
Me.radioButtonBlend.Location = New System.Drawing.Point(12, 80)
Me.radioButtonBlend.TabIndex = 3
Me.radioButtonBlend.Text = “Blend”
Me.comboBoxShape.AllowDrop = True
Me.comboBoxShape.DropDownStyle = _
System.Windows.Forms.ComboBoxStyle.DropDownList
Me.comboBoxShape.DropDownWidth = 121
Me.comboBoxShape.Location = New System.Drawing.Point(68, 28)
Me.comboBoxShape.Size = New System.Drawing.Size(121, 21)
Me.comboBoxShape.TabIndex = 1
Me.comboBoxPattern.DropDownStyle = _
System.Windows.Forms.ComboBoxStyle.DropDownList
Me.comboBoxPattern.DropDownWidth = 132
Me.comboBoxPattern.Location = New System.Drawing.Point(308, 24)
Me.comboBoxPattern.Size = New System.Drawing.Size(132, 21)
Me.comboBoxPattern.TabIndex = 10
Drawing Functions
397
CHAPTER 9

LISTING 9.12 Continued


Me.comboBoxPattBack.DropDownStyle = _
System.Windows.Forms.ComboBoxStyle.DropDownList
Me.comboBoxPattBack.DropDownWidth = 132
Me.comboBoxPattBack.Location = New System.Drawing.Point(328, 80)
Me.comboBoxPattBack.Size = New System.Drawing.Size(132, 21)
Me.comboBoxPattBack.TabIndex = 14
Me.comboBoxBlendStyle.DropDownStyle = _
System.Windows.Forms.ComboBoxStyle.DropDownList
Me.comboBoxBlendStyle.DropDownWidth = 132
Me.comboBoxBlendStyle.Location = New System.Drawing.Point(68, 80)
Me.comboBoxBlendStyle.Size = New System.Drawing.Size(132, 21)
Me.comboBoxBlendStyle.TabIndex = 4
Me.label15.Location = New System.Drawing.Point(110, 26)
Me.label15.TabIndex = 0
Me.label15.Text = “degrees”
Me.label14.Location = New System.Drawing.Point(12, 28)
Me.label14.TabIndex = 0
Me.label14.Text = “Rotate”
Me.label11.Location = New System.Drawing.Point(256, 56)
Me.label11.TabIndex = 11
Me.label11.Text = “Foreground”
Me.label10.Location = New System.Drawing.Point(12, 64)
Me.label10.TabIndex = 1
Me.label10.Text = “Color”
Me.label13.Location = New System.Drawing.Point(28, 140)
Me.label13.Size = New System.Drawing.Size(40, 23)
Me.label13.TabIndex = 7
Me.label13.Text = “To”
Me.label12.Location = New System.Drawing.Point(256, 84)
Me.label12.TabIndex = 13
9
Me.label12.Text = “Background”

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

LISTING 9.12 Continued


Me.label9.Size = New System.Drawing.Size(52, 16)
Me.label9.TabIndex = 0
Me.label9.Text = “Weight”
Me.comboBoxSolidColor.DropDownStyle = _
System.Windows.Forms.ComboBoxStyle.DropDownList
Me.comboBoxSolidColor.DropDownWidth = 132
Me.comboBoxSolidColor.Location = New System.Drawing.Point(68, 52)
Me.comboBoxSolidColor.Size = New System.Drawing.Size(132, 21)
Me.comboBoxSolidColor.TabIndex = 2
Me.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel
Me.buttonCancel.Location = New System.Drawing.Point(420, 432)
Me.buttonCancel.TabIndex = 6
Me.buttonCancel.Text = “Cancel”
Me.label4.Location = New System.Drawing.Point(128, 64)
Me.label4.Size = New System.Drawing.Size(52, 16)
Me.label4.TabIndex = 2
Me.label4.Text = “pixels”
Me.label5.Location = New System.Drawing.Point(128, 92)
Me.label5.Size = New System.Drawing.Size(52, 16)
Me.label5.TabIndex = 2
Me.label5.Text = “pixels”
Me.label6.Location = New System.Drawing.Point(12, 28)
Me.label6.Size = New System.Drawing.Size(52, 16)
Me.label6.TabIndex = 2
Me.label6.Text = “X”
Me.label7.Location = New System.Drawing.Point(12, 56)
Me.label7.Size = New System.Drawing.Size(52, 16)
Me.label7.TabIndex = 2
Me.label7.Text = “Y”
Me.label2.Location = New System.Drawing.Point(12, 92)
Me.label2.Size = New System.Drawing.Size(52, 16)
Me.label2.TabIndex = 2
Me.label2.Text = “Height”
Me.label3.Location = New System.Drawing.Point(12, 64)
Me.label3.Size = New System.Drawing.Size(52, 16)
Me.label3.TabIndex = 2
Me.label3.Text = “Width”
Me.comboBoxBlendFrom.DropDownStyle = _
System.Windows.Forms.ComboBoxStyle.DropDownList
Me.comboBoxBlendFrom.DropDownWidth = 132
Me.comboBoxBlendFrom.Location = New System.Drawing.Point(68, 108)
Me.comboBoxBlendFrom.Size = New System.Drawing.Size(132, 21)
Me.comboBoxBlendFrom.TabIndex = 6
Me.radioButtonPattern.Location = New System.Drawing.Point(240, 24)
Me.radioButtonPattern.TabIndex = 9
Drawing Functions
399
CHAPTER 9

LISTING 9.12 Continued


Me.radioButtonPattern.Text = “Pattern”
Me.textBoxX.Location = New System.Drawing.Point(32, 24)
Me.textBoxX.MaxLength = 4
Me.textBoxX.Size = New System.Drawing.Size(48, 20)
Me.textBoxX.TabIndex = 4
Me.comboBoxPattFore.DropDownStyle = _
System.Windows.Forms.ComboBoxStyle.DropDownList
Me.comboBoxPattFore.DropDownWidth = 132
Me.comboBoxPattFore.Location = New System.Drawing.Point(328, 52)
Me.comboBoxPattFore.Size = New System.Drawing.Size(132, 21)
Me.comboBoxPattFore.TabIndex = 12
Me.groupBox1.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.groupBox5, Me.label5, Me.label4, Me.textBoxHeight, _
Me.textBoxWidth, Me.label3, Me.label2, Me.label1, _
Me.comboBoxShape})
Me.groupBox1.Location = New System.Drawing.Point(12, 12)
Me.groupBox1.Size = New System.Drawing.Size(304, 124)
Me.groupBox1.TabIndex = 0
Me.groupBox1.TabStop = False
Me.groupBox1.Text = “Shape”
Me.groupBox2.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.comboBoxBlendStyle, Me.label13, Me.comboBoxBlendTo, _
Me.comboBoxBlendFrom, Me.comboBoxPattBack, Me.comboBoxPattFore, _
Me.label12, Me.label11, Me.label8, Me.comboBoxPattern, _
Me.comboBoxSolidColor, Me.radioButtonBlend, Me.radioButtonSolid, _
Me.radioButtonPattern, Me.radioButtonNone})
Me.groupBox2.Location = New System.Drawing.Point(12, 148)
Me.groupBox2.Size = New System.Drawing.Size(484, 176)
Me.groupBox2.TabIndex = 2
Me.groupBox2.TabStop = False
9
Me.groupBox2.Text = “Fill”

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

LISTING 9.12 Continued


Me.groupBox4.Text = “Transform”
Me.groupBox5.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.textBoxX, Me.textBoxY, Me.label7, Me.label6})
Me.groupBox5.Location = New System.Drawing.Point(200, 24)
Me.groupBox5.Size = New System.Drawing.Size(92, 84)
Me.groupBox5.TabIndex = 4
Me.groupBox5.TabStop = False
Me.groupBox5.Text = “Position”
Me.numericUpDownWeight.Location = New System.Drawing.Point(64, 28)
Me.numericUpDownWeight.Maximum = New Decimal(New Integer() _
{999, 0, 0, 0})
Me.numericUpDownWeight.Size = New System.Drawing.Size(64, 20)
Me.numericUpDownWeight.TabIndex = 1
Me.label1.Location = New System.Drawing.Point(12, 32)
Me.label1.Size = New System.Drawing.Size(52, 16)
Me.label1.TabIndex = 2
Me.label1.Text = “Shape”
Me.AcceptButton = Me.buttonOk
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle
Me.CancelButton = Me.buttonCancel
Me.ClientSize = New System.Drawing.Size(509, 462)
Me.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.buttonClear, Me.buttonApply, Me.buttonCancel, Me.buttonOk, _
Me.groupBox2, Me.groupBox3, Me.groupBox4, Me.groupBox1})
Me.MaximizeBox = False
Me.MinimizeBox = False
Me.Text = “Draw”
CType(Me.numericUpDownRotate, _
System.ComponentModel.ISupportInitialize).EndInit()
CType(Me.numericUpDownWeight, _
System.ComponentModel.ISupportInitialize).EndInit()

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

‘purpose: close the form, cancel any pending actions


Drawing Functions
401
CHAPTER 9

‘cancel the form


Me.Close()

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

‘purpose: initialize the form, set the initial values of controls

‘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()

‘return the property information of the structure


myProps = myType.GetProperties()

‘iterate the properties and add to the combo box


For count = 0 To UBound(myProps)

‘make sure we only get valid colors


If myProps(count).PropertyType.ToString() = “System.Drawing.Color” _
And myProps(count).Name <> “Transparent” Then
Working with the .NET Namespaces
402
PART II

‘get the color’s name


colorName = myProps(count).Name

‘add the property name (color) to the color combo boxes


comboBoxOutlineColor().Items.Add(colorName)
comboBoxBlendFrom().Items.Add(colorName)
comboBoxBlendTo().Items.Add(colorName)
comboBoxSolidColor().Items.Add(colorName)
comboBoxPattFore().Items.Add(colorName)
comboBoxPattBack().Items.Add(colorName)

End If

Next

‘get a type object that represents the hatchStyle enum


myType = myHatchStyle.GetType()

‘fill an array with the hatchStyle’s member names


myArray = myHatchStyle.GetNames(myType)

‘loop through the array


For count = 0 To UBound(myArray)

‘fill the pattern dialog


comboBoxPattern().Items.Add(myArray(count))

Next

‘get a type object based on the linear gradient enumeration


myType = myLinGradMode.GetType()

‘fill an array with the linear gradient’s member names


myArray = myLinGradMode.GetNames(myType)

‘loop through the array


For count = 0 To UBound(myArray)

‘fill the blend style drop-down


comboBoxBlendStyle().Items.Add(myArray(count))

Next

‘add some basic shape values for users to select from


comboBoxShape().Items.Add(“Line”)
comboBoxShape().Items.Add(“Rectangle”)
comboBoxShape().Items.Add(“Ellipse”)
Drawing Functions
403
CHAPTER 9

‘select first item in each list by default


‘(saves enabling and disabling controls)
comboBoxPattern().SelectedIndex = 0
comboBoxShape().SelectedIndex = 0
comboBoxOutlineColor().SelectedIndex = 0
comboBoxBlendFrom().SelectedIndex = 0
comboBoxBlendTo().SelectedIndex = 0
comboBoxSolidColor().SelectedIndex = 0
comboBoxPattFore().SelectedIndex = 0
comboBoxPattBack().SelectedIndex = 0
comboBoxBlendStyle().SelectedIndex = 0

‘set the ok button on the form to respond to the enter key


Me.AcceptButton = buttonOk()

‘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

‘of course we would want to do more than beep ... 9


‘I assume a production application would capture user keystrokes
‘ and disallow invalid entries ...

FUNCTIONS
DRAWING
Beep()

Else

‘call the centralized routine that call the drawing module


Call submitForm()

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

Private Function validateForm() As Boolean

‘purpose: quick check of field values (IsNumeric)

‘validate some of the form fields


If Not IsNumeric(textBoxWidth().Text) Or _
Not IsNumeric(textBoxHeight().Text) Or _
Not IsNumeric(textBoxX().Text) Or _
Not IsNumeric(textBoxY().Text) Then

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()

‘purpose: send the form values to the draw method of modDrawing

‘local scope
Dim strFillType As String

‘set the fill pattern selected


If radioButtonBlend().Checked = True Then
strFillType = “BLEND”
ElseIf radioButtonSolid().Checked = True Then
strFillType = “SOLID”
ElseIf radioButtonPattern().Checked = True Then
strFillType = “PATTERN”
Else
strFillType = “NONE”
End If

‘call the draw method


Call draw(shape:=comboBoxShape().SelectedItem.ToString, _
Width:=CInt(textBoxWidth().Text), _
Height:=CInt(textBoxHeight().Text), _
x:=CInt(textBoxX().Text), _
y:=CInt(textBoxY().Text), _
Drawing Functions
405
CHAPTER 9

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

‘purpose: event triggered when users click the ok button


‘ draw the item and close the form

‘validate the form


If Not validateForm() Then

‘of course we would want to do more than beep ...


Beep()

Else

‘call the centralized routine that call the drawing module 9


Call submitForm()

FUNCTIONS
DRAWING
‘close the form
Me.Close()

End If

End Sub

Private Sub buttonClear_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles buttonClear.Click

‘purpose: provide the illusion of clearing the form


‘ this allows users to keep drawing without closing the dialog
Working with the .NET Namespaces
406
PART II

‘set the background color to the default


m_myChild.BackColor = Color.FromName(m_SurfaceBackground)
m_myChild.Refresh()

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.

LISTING 9.13 Drawing Module (modDrawing)


Option Strict Off

Module modDrawing

‘set defaults for child form


Public m_LeftPos As Integer = 5
Public m_TopPos As Integer = 5
Public m_Width As Integer = 256
Public m_Height As Integer = 256
Public m_ChildColor As String = “White”
Public m_SurfaceBackground As String = “White”

‘set a reference to a graphics object to be used globally


Public m_myGraphics As Graphics

‘set a reference to the child form to be used by the application


Public m_myChild As frmChild

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

Dim myState As Drawing2D.GraphicsState. The GraphicsState class allows you to save,


or maintain, a copy of the Graphics object at a point in time. You do this by calling its save
method.
This becomes important as you apply transforms to the Graphics object. Each call to a trans-
form method sets the Graphics object’s state to the given value. Therefore, subsequent calls to
a transform method actually transform the already-transformed object. For instance, the line
m_myGraphics.RotateTransform(angle:=rotateAngle), where rotateAngle is 5°, sets the
graphics object to rotate shapes that it draws by 5°. A subsequent call to the RotateTranform
property, where rotateAngle is 10°, actually results in a rotation of 15° from the base shape.
You can see why the Save method is so important. Of course we could also call
Graphics.ResetTransform. Finally, after the transforms are complete and the objects are
drawn to the screen, you call m_myGraphics.Restore(myState). This restores the Graphics
object to its original state.
Public Sub draw( _
ByVal shape As String, _
ByVal width As Integer, _
ByVal height As Integer, _
ByVal x As Integer, _
ByVal y As Integer, _
ByVal fillType As String, _
ByVal outlineWeight As Single, _
ByVal outlineColor As String, _
ByVal solidColor As String, _
ByVal blendFrom As String, _
ByVal blendTo As String, _
ByVal pattern As String, _
ByVal patternForeColor As String, _
ByVal patternBackColor As String, _ 9
ByVal blendStyle As String, _
ByVal rotateAngle As Single)

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

‘get the current state of the graphics object (pre-tranform)


myState = m_myGraphics.Save()
Working with the .NET Namespaces
408
PART II

‘set the rotation transformation value


m_myGraphics.RotateTransform(angle:=rotateAngle)

‘check what shape to draw


Select Case shape
Case “Line”

‘create a new pen instance


myPen = New Pen(color.FromName(name:=outlineColor), _
width:=outlineWeight)

‘draw the line to the surface


m_myGraphics.DrawLine(pen:=myPen, x1:=x, y1:=y, _
x2:=x + width, y2:=y + height)

Case “Rectangle”, “Ellipse”

‘note: the rectangle and ellipse are very similar


‘ they can use the same code

‘create a new pen myect for the outline of the shape


myPen = New Pen(color.FromName(name:=outlineColor), _
width:=outlineWeight)

‘create the rectangle object


myRectangle = New Rectangle(x:=x, y:=y, width:=width, _
height:=height)

‘draw the rectangle to the surface


If shape = “Ellipse” Then

‘draw the ellipse


m_myGraphics.DrawEllipse(pen:=myPen, _
rect:=myRectangle)

Else

‘draw a rectangle
m_myGraphics.DrawRectangle(pen:=myPen, _
rect:=myRectangle)

End If

‘fill the rectangle/ellipse


If fillType <> “NONE” Then
Drawing Functions
409
CHAPTER 9

‘determine brush type to create


Select Case fillType
Case “SOLID”

‘create a new solid brush


myBrush = New SolidBrush( _
color:=color.FromName(name:=solidColor))

Case “PATTERN”

‘create the hatch brush


‘note: we use the type object and the parse
‘ method of the enum to return the
‘ value of the enum’s member from its
‘ name (string)
myType = myHatchStyle.GetType()
myBrush = New System.Drawing.Drawing2D.HatchBrush _
(myHatchStyle.Parse(enumType:=myType, _
value:=pattern),
➥foreColor:=Color.FromName(name:=patternForeColor),
➥backColor:=color.FromName(name:=patternBackColor))

Case “BLEND”

‘create a blend brush


‘note: we use the type object and the parse
‘ method of the enum to return the
‘ value of the enum’s member from its
‘ name (string)
myType = myBlendStyle.GetType
myBrush = New _ 9
System.Drawing.Drawing2D.LinearGradientBrush( _
rect:=myRectangle, _

FUNCTIONS
DRAWING
color1:=color.FromName(name:=blendFrom), _
color2:=color.FromName(name:=blendTo), _
LinearGradientMode:=myBlendStyle.Parse( _
enumType:=myType, value:=blendStyle))

End Select

‘fill the shape


If shape = “Ellipse” Then

‘draw the ellipse with the correct brush


m_myGraphics.FillEllipse(brush:=myBrush, _
rect:=myRectangle)
Working with the .NET Namespaces
410
PART II

Else

‘fill the rectangle with the correct brush


m_myGraphics.FillRectangle(brush:=myBrush, _
rect:=myRectangle)

End If

End If

End Select

‘reset the state of the graphics object


m_myGraphics.Restore(myState)

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

• Markup Languages 413

• The Anatomy of an XML Document 416

• Parsing XML Documents 421

• Introducing the XmlNodeReader Class 443

• Writing XML Documents 444

• XML Schemas 450

• Validating XML Documents 463

• 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.

Key Classes Related to XML


This chapter covers the classes and related structures listed in Table 10.1.

TABLE 10.1 Key Classes of the System.Xml and System.Xml.Schema Namespaces


Class/Structure Description
System.Xml The System.Xml namespace is used to process XML files in
accordance with published World Wide Web Consortium stan-
dards.
XmlDocument The XmlDocument class is used as an abstract of an entire XML
document.
XmlException The XmlException class is used to encapsulate and return infor-
mation specifically related to XML exceptions that can occur
while reading, writing, or validating XML documents.
XmlNode The XmlNode class represents a node in an XML document.
XmlNodeReader The XmlNodeReader class provides a way to parse XML data
inside an XML node.
XmlReader The XmlReader abstract class provides a basis for concrete
implementations of classes designed to read XML documents.
XmlTextReader The XmlTextReader class provides a way to parse XML data
stored inside a file or stream.
Reading and Writing XML
413
CHAPTER 10

TABLE 10.1 Continued


Class/Structure Description
XmlTextWriter The XmlTextWriter class provides ways to programmatically
write well-formed XML.
XmlValidatingReader The XmlValidatingReader class is used to simultaneously
parse and validate XML documents against a predefined
schema (either DTD, XDR, or XSD).
System.Xml.Schema The System.Xml.Schema namespace exposes a variety of
objects that can be used to construct and process XML schema
documents.
ValidationEventArgs The ValidationEventArgs class is used in conjunction with the
XmlValidatingReader class to provide validation-specific
information to event handlers.
XmlSchema The XmlSchema class represents an instance of an XML schema
document.
XmlSchemaAttribute The XmlSchemaAttribute class represents a schema attribute
element.
XmlSchemaComplexType The XmlSchemaComplexType class represents a schema complex
type element.
XmlSchemaElement The XmlSchemaElement class is used as a parent or container
object for XML schema elements.
XmlSchemaSequence The XmlSchemaSequence class represents an XML schema
sequence element.
XmlSchemaSimpleType The XmlSchemaSimpleType class represents an instance of an
XML schema simple type element.

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.

Markups with HTML


HTML is also a markup language. HTML attempts to fix some of the problems of earlier
markup languages by enforcing structure onto a document. If you glance at the following
HTML code, it is immediately obvious that there are some standard structural rules being
used. Each “tag” has a beginning and an end to it, some tags are nested inside of others, and
each tag has a specific, designated place in the document with header tags coming first, fol-
lowed by body tags. Figure 10.2 shows the previously formatted RTF document as HTML
inside Internet Explorer.
Reading and Writing XML
415
CHAPTER 10

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

Suggestions for Further Exploration


➲ Although the site is dense and not easy to digest, it will be worth your time to visit
http://www.w3.org to view the actual specifications, proposals, and standards that
describe some of the languages that we have talked about in this section, including
HTML and XML.
➲ A group portal on the Web for understanding all things related to XML is
http://www.xml.com. The articles at this site will help the casual XML developer under-
stand the role of XML in a deeper context and also will help answer questions on XML’s
origin and uses.

The Anatomy of an XML Document


An XML document is a text document. It has readable characters that are arranged in a way
that causes the document to conform to the W3C specifications for an XML document. The
fact that an XML document is readable to humans as well as computers is important; data that
doesn’t require a machine to be understood helps to alleviate needless complexity in a system.
To better understand the XML functionality inside the System.XML namespace and its children
namespaces, this section briefly discusses the various components of XML documents.
So that you have something concrete to refer back to as we discuss the various pieces of an
XML document, Listing 10.1 shows a piece of an XML document intended to hold guest reg-
istrations for a hotel.

LISTING 10.1 XML Hotel Registration File


<?xml version=”1.0” encoding=”utf-8” ?>
<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>

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


The hotel register XML file that we have been looking at has only one of the items allowed in
a prolog, the XML declaration:
<?xml version=”1.0” encoding=”utf-8” ?>

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”?>

The DOCTYPE Declaration 10


The purpose of the DOCTYPE declaration is twofold: It provides a way for you to explicitly iden-
WRITING XML
READING AND

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>

Suggestions for Further Exploration


➲ Many more intricacies are involved with the XML structure than we have been able to
cover in this chapter. Again, for a complete review, refer to the W3C’s XML standards,
located at http://www.w3.org.
➲ Technically, XML is actually a subset of another specification called the Standard
Generalized Markup Language (SGML). So is HTML. For an authoritative treatment of
SGML and its various implementations, see http://www.oasis-open.org/cover/
general.html.
Reading and Writing XML
421
CHAPTER 10

Parsing XML Documents


The System.XML namespace has a variety of classes dedicated to implementing read and write
functions for XML documents. This section discusses those classes that are capable of reading
and parsing XML documents, tells how they are used, and shows in which situations they are
most useful.

The Reader and Writer Base Classes


The System.XML namespace established two very important base classes, XMLReader and
XMLWriter (see Figure 10.3). These base classes are inherited from to provide three classes
dedicated to reading XML: XmlTextReader, XmlNodeReader, and XmlValidatingReader.
There is only one concrete implementation of the XmlWriter base class: XmlTextWriter.

XML Reader Classes

XmlNodeReader
XmlReader

XmlTextReader

XmlValidatingReader

XML Writer Classes


XmlWriter

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.

Forward-Only XML Text Parsing


The XMLTextReader is a specific implementation of the XMLReader abstract class. Its sole pur-
pose is to provide programmers with the capability to quickly read in XML from a file, stream,
or URL. It does not attempt to validate the source XML document against a schema.
The simplest way to start down the road of reading in an XML document is to specify its name
in the XmlTextReader constructor, like this:
reader = New XmlTextReader(fileName)

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”)

‘read from a URL


reader = New XmlTextReader(“http://www.brilliantstorm/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.

LISTING 10.2 Reading in an XML File


Imports System.Xml

Module dataReader
Reading and Writing XML
423
CHAPTER 10

LISTING 10.2 Continued


Sub Main()
Const fileName = “hotel_register.xml”

Dim reader As XmlTextReader

Try
‘Read in an XML file (location stored in fileName)
reader = New XmlTextReader(fileName)

reader.WhitespaceHandling = WhitespaceHandling.None

‘Write each node in the file out to the console window:


‘The Read method tells the class to read in the next node
‘in the document; if there are no more nodes, it will return
‘false
While reader.Read()
Console.WriteLine(reader.ReadOuterXml())
End While

‘Catch any thrown exceptions and display an error dialog...

Catch xmlErr As XmlException


MsgBox(“An XML error has occurred (“ & xmlErr.Message & “).” & _
vbCrLf & vbCrLf & “ Xml Source Line Nbr: “ & _
reader.LineNumber & vbCrLf & “ Xml Source Position: “ & _
reader.LinePosition)

Catch appErr As Exception


MsgBox(“An error occurred (“ & appErr.Message & “). Stack info:” & _
appErr.StackTrace)

Finally
If Not (reader Is Nothing) Then
reader.Close()
End If

End Try

Console.WriteLine(“<hit ‘Enter’ to exit>”) 10


Console.ReadLine()
WRITING XML

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).

LISTING 10.3 The hotel_register.xml File


<?xml version=”1.0” encoding=”utf-8” ?>
<hotel id=”DC-4RIVERS”>
<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>
<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>
<guest id=”d b6620103”>
<firstname>Dorsa</firstname>
<middlename></middlename>
<lastname>Brevia</lastname>
<roomnbr>408</roomnbr>
<checkindate>6/18/2001</checkindate>
<numnights>1</numnights>
<preferred>Yes</preferred>
</guest>
<guest id=”jgm9111447”>
Reading and Writing XML
425
CHAPTER 10

LISTING 10.3 Continued


<firstname>Jackie</firstname>
<middlename>G</middlename>
<lastname>Mendelin</lastname>
<roomnbr>223</roomnbr>
<checkindate>6/15/2001</checkindate>
<numnights>5</numnights>
<preferred>No</preferred>
</guest>
</guests>
</hotel>

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

TABLE 10.2 The ReadState Enumeration


Name Description
Closed The reader has been closed through the XmlTextReader.Close method.
EndofFile The reader has reached the end of the file (there are no more nodes to be
processed).
Error An error has occurred during a read; the read operation has been halted.
Initial The reader has been instantiated but has not been primed with an initial
Read call.
Interactive The reader is currently reading a node (the Read method has been called).

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.

TABLE 10.3 XmlTextReader Properties 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

TABLE 10.3 Continued


Name Description
IsEmptyElement Returns a Boolean value indicating whether the current node is
empty (devoid of data)
LocalName Returns the name of the current node, minus any namespace pre-
fixes (if any)
Name Returns the fully qualified name of the current node, including
any namespace prefixes (if any)
NodeType Returns the type of the current node (XmlNodeType enumeration)
Prefix Returns the namespace prefix of the current node
Value Returns the text value of the current node

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.

LISTING 10.4 Examining Node Properties


Imports System.Xml

Module dataReader

Sub Main()
Const fileName = “hotel_register.xml”

Dim reader As XmlTextReader


Dim keyPressed As Integer

Try
‘Read in an XML file (location stored in fileName)
reader = New XmlTextReader(fileName)
Dim nodeType As XmlNodeType

reader.WhitespaceHandling = WhitespaceHandling.None

‘Write each node in the file out to the console window:


‘The Read method tells the class to read in the next node
10
‘in the document; if there are no more nodes, it will return
WRITING XML
READING AND

‘false
While reader.Read()

Console.WriteLine(“-------->Node: “ & reader.Name)


Working with the .NET Namespaces
428
PART II

LISTING 10.4 Continued


Console.WriteLine(“Type: “ & _
nodeType.GetName(nodeType.GetType(), _
reader.NodeType()))
Console.WriteLine(“Number of Attributes: “ & _
reader.AttributeCount)
Console.WriteLine(“Depth in Document: “ & reader.Depth)
Console.WriteLine(“Attributes?: “ & reader.HasAttributes)
Console.WriteLine(“Value?: “ & reader.HasValue)
Console.WriteLine(“Empty?: “ & reader.IsEmptyElement)
Console.WriteLine(“Name: “ & reader.Name)
Console.WriteLine(“Value: “ & reader.Value)

End While

Catch xmlErr As XmlException


‘Catch instances of XmlException

MsgBox(“An XML error has occurred (“ & xmlErr.Message & “).” & _
vbCrLf & vbCrLf & “ Xml Source Line Nbr: “ & _
reader.LineNumber & vbCrLf & “ Xml Source Position: “ & _
reader.LinePosition)

Catch appErr As Exception


‘Catch generic application exceptions

MsgBox(“An error occurred (“ & appErr.Message & “). Stack info:” & _
appErr.StackTrace)

End Try

Console.WriteLine(“<hit ‘Enter’ to exit>”)


Console.ReadLine()
End Sub

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.

TABLE 10.4 The XmlNodeType Enumeration


Name Description
Attribute The node is an XML attribute. An Attribute node can have
the following child node types: Text and EntityReference.
The Attribute node does not appear as the child node of any
other node type; note that it is not considered a child node of
an Element.
CDATA The node is a CDATA section. CDATA sections are used to
escape blocks of text that would otherwise be recognized as
markup. A CDATASection node cannot have any child nodes.
The CDATASection node can appear as the child of the
DocumentFragment, EntityReference, and Element nodes. 10
Comment The node is a comment. A Comment node cannot have any
WRITING XML
READING AND

child nodes. The Comment node can appear as the child


of the Document, DocumentFragment, Element, and
EntityReference nodes.
Working with the .NET Namespaces
430
PART II

TABLE 10.4 Continued


Name Description
Document The node is a document object, which, as the root of the doc-
ument tree, provides access to the entire XML document. A
Document node can have the following child node types:
Element (maximum of one), ProcessingInstruction,
Comment, and DocumentType. The Document node cannot
appear as the child of any node types.
DocumentFragment The node is a document fragment. The DocumentFragment
node associates a node or subtree with a document with-
out actually being contained within the document. A
DocumentFragment node can have the following child node
types: Element, ProcessingInstruction, Comment, Text,
CDATASection, and EntityReference. The
DocumentFragment node cannot appear as the child of any
node types.
DocumentType The node is a document type declaration. A DocumentType
node can have the following child node types: Notation and
Entity. The DocumentType node can appear as the child of
the Document node.
Element The node is an Element. An Element node can have the
following child node types: Element, Text, Comment,
ProcessingInstruction, CDATA, and EntityReference.
The Element node can be the child of the Document,
DocumentFragment, EntityReference, and Element nodes.
EndElement The node is an end-of-element node (such as </item>).
EndEntity The node is an end-of-entity node (could be returned when
XmlReader gets to the end of the entity replacement as a result
of a call to ResolveEntity).
Entity The node is an entity declaration. An Entity node can have
child nodes that represent the expanded entity (for example,
Text and EntityReference nodes). The Entity node can
appear as the child of the DocumentType node.
EntityReference The node is a reference to an entity. An EntityReference
node can have the following child node types: Element,
ProcessingInstruction, Comment, Text, CDATASection, and
EntityReference. The EntityReference node can appear as
the child of the Attribute, DocumentFragment, Element, and
EntityReference nodes.
Reading and Writing XML
431
CHAPTER 10

TABLE 10.4 Continued


Name Description
None The node is returned by the XmlReader if a Read method has
not been called.
Notation The node is a notation in the document type declaration. A
Notation node cannot have any child nodes. The Notation
node can appear as the child of the DocumentType node.
ProcessingInstruction The node is a processing instruction (PI). A PI node cannot
have any child nodes. The PI node can appear as the child of
the Document, DocumentFragment, Element, and
EntityReference nodes.
SignificantWhitespace The node is whitespace between markup in a mixed-content
model or whitespace within the xml:space= “preserve”
scope.
Text The node is the text content of an element. A Text node can-
not have any child nodes. The Text node can appear as the
child node of the Attribute, DocumentFragment, Element,
and EntityReference nodes.
Whitespace The node is whitespace between markup.
XmlDeclaration The node is an XML declaration node. This has to be the first
node in the document. It can have no children. It is a child of
the root node. It can have attributes that provide version and
encoding information.

Parsing Only the Important Stuff


The process of reading in XML documents a node at a time and then examining each node’s
XmlNodeType to see if it is relevant to the parsing activity is a laborious process. The
XmlReader base class establishes two methods specifically designed to help ease the task
of pinpointing actual data that you are interested in parsing.
The MoveToContent method is the first of these methods. It enables you to quickly move the
scrolling node viewport to the next occurrence of a significant content node. In other words, it
skips over and does not read the following types of nodes:
10
• Comments
WRITING XML
READING AND

• 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).

LISTING 10.5 Skipping Nodes


Imports System.Xml

Module dataReader

Sub Main()
Const fileName = “hotel_register.xml”
Reading and Writing XML
433
CHAPTER 10

LISTING 10.5 Continued


Dim reader As XmlTextReader
Dim keyPressed As Integer

Try
‘Read in an XML file (location stored in fileName)
reader = New XmlTextReader(fileName)
Dim nodeType As XmlNodeType

reader.WhitespaceHandling = WhitespaceHandling.None

‘Write each node in the file out to the console window:


‘The Read method tells the class to read in the next node
‘in the document; if there are no more nodes, it will return
‘false

‘Move to the content nodes


reader.MoveToContent()

‘Parse the file starting with the second book node.


While reader.Read()
‘We are only interested in element start tags...
If reader.NodeType = XmlNodeType.Element And reader.Name = _
“guest” Then
Do
If reader.GetAttribute(“id”) = “d b6620103” Then
Console.WriteLine(“Guest record found at line “ _
& reader.LineNumber & “...”)
Console.WriteLine(“Type: “ & _
nodeType.GetName(nodeType.GetType(), _
reader.NodeType()))
Console.WriteLine(“Number of Attributes: “ & _
reader.AttributeCount)
Console.WriteLine(“Depth in Document: “ & _
reader.Depth)
Console.WriteLine(“Attributes?: “ & _
reader.HasAttributes)
Console.WriteLine(“Value?: “ & reader.HasValue)
Console.WriteLine(“Empty?: “ & _
reader.IsEmptyElement) 10
Console.WriteLine(“Name: “ & reader.Name)
WRITING XML

Exit While
READING AND

Else
reader.Skip()
Working with the .NET Namespaces
434
PART II

LISTING 10.5 Continued


End If
Loop
End If
End While

Catch xmlErr As XmlException


‘Catch instances of XmlException

MsgBox(“An XML error has occurred (“ & xmlErr.Message & “).” & _
vbCrLf & vbCrLf & “ Xml Source Line Nbr: “ & _
reader.LineNumber & vbCrLf & “ Xml Source Position: “ & _
reader.LinePosition)

Catch appErr As Exception


‘Catch generic application exceptions

MsgBox(“An error occurred (“ & appErr.Message & “). Stack info:” & _
appErr.StackTrace)

Finally
If Not (reader Is Nothing) Then
reader.Close()
End If

End Try

Console.WriteLine(“<hit ‘Enter’ to exit>”)


Console.ReadLine()
End Sub

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.

Parsing XML Document Trees


The XmlDocument class abstracts XML documents through a tree representation in memory.
You can traverse this tree in a forward or backward manner, or you can jump to any node
directly. Let’s look at how you would go about reading an XML file into this in-memory tree.
In the code that follows, we create our XmlTextReader and then immediately pass it into an
XmlDocument object through the XmlDocument.Load method:

Dim reader As XmlTextReader


reader = New XmlTextReader(“test.xml”)
Dim document As XmlDataDocument = New XmlDocument()
document.Load(reader)

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

Public Sub RecurseNodes(currNode As XmlNode)


If node.HasChildNodes Then
currNode = currNode.FirstChild
Working with the .NET Namespaces
436
PART II

While Not IsNothing(currNode)


RecurseNodes(currNode)
currNode = currNode.NextSibling
End While
End If
End sub

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.

LISTING 10.6 Navigating Nodes


Imports System.Xml

Module dataReader

Sub Main()
‘Path and name of the XML file to parse
Const fileName = “..\hotel_register.xml”

‘XmlTextReader is our principal engine to read


‘the XML file
Dim reader As XmlTextReader

Try
‘Read in an XML file (location stored in fileName)
reader = New XmlTextReader(fileName)
Reading and Writing XML
437
CHAPTER 10

LISTING 10.6 Continued

‘don’t want any whitespace processed


reader.WhitespaceHandling = WhitespaceHandling.None

‘This is the object that builds an in-memory tree


‘of the XML document
Dim document As XmlDataDocument = New XmlDataDocument()

‘The XmlDocument object is loaded by using the reader


‘object
document.Load(reader)

‘We want to explicitly start at the outermost “root”


‘node of the document
Dim startNode As XmlNode
startNode = document.DocumentElement

‘Recurse through all of the nodes


RecurseNodes(startNode)

Catch xmlErr As XmlException


‘Catch instances of XmlException

MsgBox(“An XML error has occurred (“ & xmlErr.Message & “).” & _
vbCrLf & vbCrLf & “ Xml Source Line Nbr: “ & _
reader.LineNumber & vbCrLf & “ Xml Source Position: “ & _
reader.LinePosition)

Catch appErr As Exception


‘Catch generic application exceptions

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

LISTING 10.6 Continued


Public Sub RecurseNodes(ByVal currNode As XmlNode)

‘this holds the lineage for each element


Dim tree As String

‘temp storage of the currNode


Dim tempNode As XmlNode
tempNode = currNode

‘If there is a parent node to the current node...


If Not IsNothing(tempNode.ParentNode) Then

‘Reach back through all of its parents,


‘building a string that shows us the
‘lineage
While Not IsNothing(tempNode.ParentNode)
tree = GetLineage(tempNode) & tree
tempNode = tempNode.ParentNode
End While

‘Write the lineage out to the console


Console.WriteLine(tree)
Else
‘If there are not parents, just write out the
‘name of the current node
Console.WriteLine(tempNode.Name)
End If

‘If the current node has children...


If currNode.HasChildNodes Then

‘go to the first child...


currNode = currNode.FirstChild

‘While we still have nodes to work with,


‘call back into this sub
While Not IsNothing(currNode)
RecurseNodes(currNode)

‘Move to the next sibling node


currNode = currNode.NextSibling

End While
End If
Reading and Writing XML
439
CHAPTER 10

LISTING 10.6 Continued


End Sub

Public Function GetLineage(ByVal currNode As XmlNode) As String

‘Get the name of the node that is a parent to the current


‘node and return it through the function
GetLineage = “->” & currNode.ParentNode.Name
End Function
End Module

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

Dim doc As New XmlDocument()


doc.Load(“inventory.xml”)
Working with the .NET Namespaces
440
PART II

Dim nodes As XmlNodeList = doc.GetElementsByTagName(“product”)


Dim i As Integer
For i = 0 To nodes.Count - 1
Console.WriteLine(nodes(i).InnerXml)
Next I

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.

LISTING 10.7 Parsing Elements by Their Tag Name


Imports System.Xml

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

‘to do “pretty-printing” of the element XML, we will


‘want the XmlDocument instance to preserve the whitespace
‘of the source document
xmlDoc.PreserveWhitespace = True

‘load the XML file into the XmlDocument instance


xmlDoc.Load(fileName)

‘get the node list of all nodes matching the tag name “guest”
Dim nodes As XmlNodeList = xmlDoc.GetElementsByTagName(“guest”)

‘For each node returned in the XmlNodeList instance,


‘write out its XML content to the console
For i = 0 To nodes.Count - 1
Console.WriteLine(nodes(i).InnerXml)
Next i

‘Catch any thrown exceptions and display an error dialog...


Catch e As Exception
Reading and Writing XML
441
CHAPTER 10

LISTING 10.7 Continued


MsgBox(“An error occurred (“ & e.Message & “). Stack info:” & _
e.StackTrace)
End Try

Console.WriteLine(“<hit ‘Enter’ to exit>”)


Console.ReadLine()
End Sub

End Module

The console output is presented in Figure 10.8.

FIGURE 10.8
Parsing elements by their tag name.

Mapping to the W3C DOM


The XmlNodeList class is a part of what the W3C considers a fundamental base class for XML
support. The XmlDocument class and its brethren are actually a fairly direct implementation of
the W3C concept of the XML Document Object Model (DOM). The W3C DOM specification
breaks up XML functionality into two categories: those things that are absolutely needed to 10
parse and write XML, and those that aren’t needed but are helpful (that is, they make the
WRITING XML
READING AND

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.

TABLE 10.5 DOM Fundamental Classes in the Framework


Class Name Description
XmlNode A representation of a node in an XML document
XmlNodeList A representation of a collection of nodes in an XML document
XmlNamedNodeMap A representation of a collection of nodes in an XML document;
can be accessed directly by node name

TABLE 10.6 DOM Extended Classes in the Framework


Class Name Description
XmlDocument A representation of the top node and all children nodes of
an XML document
XmlAttribute A representation of an XML element attribute
XmlAttributeCollection A representation of a collection of attributes associated
with an XML element
XmlCDataSection A representation of a CDATA section in an XML
document
XmlCharacterData A class used to support various text-manipulation opera-
tions; principally a utility class used by other classes in the
System.Xml namespace
XmlComment A representation of a comment in an XML document
XmlDeclaration A representation of an XML declaration in an XML
document
XmlDocumentFragment A representation of a portion of an XML document
XmlDocumentType A representation of a DOCTYPE declaration in an XML
document
XmlElement A representation of an element in an XML document
XmlEntity —
XmlEntityReference —
XmlImplementation —
XmlLinkedNode A representation of a node in an XML document that
immediately precedes or follows the referenced node
XmlNotation A representation of a notation declaration inside a DTD or
XSD file
Reading and Writing XML
443
CHAPTER 10

TABLE 10.6 Continued


Class Name Description
XmlNamedNodeMap A representation of a collection of nodes in an XML docu-
ment; can be accessed directly by node name
XmlProcessingInstruction A representation of a processing instruction in an XML
document
XmlSignificantWhitespace A representation of whitespace in an element or attribute
XmlText A representation of the text content of an element or
attribute

Introducing the XmlNodeReader Class


The XmlNodeReader class is very similar to the XmlTextReader class—something that isn’t so
surprising when you consider that they share the same parent class. Like XmlTextReader, the
XmlNodeReader class allows for forward-only, noncached traversal of XML; where they differ
is in their source. The XmlNodeReader is implemented across the node of an existing
XmlDocument instance. Creating a node reader instance will look familiar to you. First, we
instance an XmlDocument object and load a file into it. Then we pass the XmlDocument instance
into the constructor for the XmlNodeReader object:
‘Create an XmlNodeReader to read the XmlDocument.
Dim doc As New XmlDocument()
doc.Load(filename)
reader = New XmlNodeReader(doc)

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.

Suggestions for Further Exploration


➲ The XmlReader class, while serving as a base class for the .NET Framework, can also 10
serve as a base class for your own code constructs. Investigate the Framework documen-
WRITING XML
READING AND

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.

Writing XML Documents


To programmatically create and write XML documents, the System.XML namespace provides
the XmlTextWriter class.

Reader and Writer Similarities


Before we dig into the guts of writing XML, let’s first quickly visit the similarities between the
writer class and its reader siblings.
For one, XmlTextWriter is a concrete class that inherits from an abstract parent class called
XmlWriter (refer back to Figure 10.3). In a similar manner to XmlReader, the XmlWriter class
implements a base set of functionality that can be aggregated through inheritance to implement
specialized writers.
Like XmlTextReader, the XmlTextWriter exposes a state property, WriteState, which you can
use to query the current state of the writer. XmlTextWriter.WriteState returns a WriteState
enumeration. Table 10.7 shows the possible WriteState values.

TABLE 10.7 The WriteState Enumeration


Name Description
Attribute The writer currently is writing an element attribute.
Closed The writer has been closed through a call to the Close method.
Content The writer currently is writing the content of an element.
Element The writer currently is writing an element start tag.
Prolog The writer currently is writing the XML document’s prolog section.
Start The writer has been instantiated, but none of the write methods have
been called yet.

Now that the stage is set, let’s see how to write a simple XML file.

Creating a Simple XML Document


The first step in writing an XML document is to determine where the document will go. In
other words, will you be writing the document to a file? To a stream? The XmlTextWriter con-
structor is overloaded to give you three different options for specifying the output channel for
the XML document. Here are their definitions:
Reading and Writing XML
445
CHAPTER 10

Public Sub New(ByVal filename As String, encoding As Encoding)


Public Sub New(ByVal w As Stream, encoding As Encoding)
Public Sub New(ByVal w As TextWriter)

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.

TABLE 10.8 XmlTextWriter Methods for Generation of XML

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

WriteEndAttribute Writes out the end of an attribute (used in conjunction


with WriteStartAttribute)
Working with the .NET Namespaces
446
PART II

TABLE 10.8 Continued


Name Description
WriteEndDocument Writes out closing tags for any currently open tags, leav-
ing the writer in the Start state
WriteEndElement Writes out the closing tags for an element (used in con-
junction with WriteStartElement)
WriteEntityRef Writes out an entity reference
WriteFullEndElement Same as WriteEndElement
WriteName Writes out the supplied text as a name that corresponds
to the XML specification
WriteNmToken Same as WriteName
WriteNode Copies an entire node from an associated reader;
advances the reader to the next sibling
WriteProcessingInstruction Writes out a processing instruction
WriteQualifiedName Writes out a namespace qualified name
WriteRaw Writes raw XML into the output channel
WriteStartAttribute Writes out the start of an attribute
WriteStartDocument Writes out the XML declaration
WriteStartElement Writes out an opening tag for an element (used in con-
junction with WriteEndElement)
WriteString Writes out the supplied text to the output channel
WriteSurrogateCharEntity Generates and writes out the surrogate character entity
for the surrogate character pair (see
http://www.w3.org/TR/REC-xml#charsets)
WriteWhitespace Writes out whitespace

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

LISTING 10.8 Writing an XML File


Imports System.Xml

Module dataReaderWriter

Sub Main()
Const FILE_NAME_IN = “..\hotel_register.xml”
Const FILE_NAME_OUT = “..\hotel_register_out.xml”

Dim reader As XmlTextReader


Dim writer As XmlTextWriter

‘Used in the For loop to get attributes...


Dim i As System.Int32

Try
‘Read in an XML file (location stored in fileName)
reader = New XmlTextReader(FILE_NAME_IN)

‘Spin up a new writer, pointed at the file in FILE_NAME_OUT;


‘the same encoding sensed by the reader will be used to write
‘the new file
writer = New XmlTextWriter(FILE_NAME_OUT, reader.Encoding)

‘Ignore whitespaces
reader.WhitespaceHandling = WhitespaceHandling.None

‘Not necessary, but this will make the resulting XML


‘file easier on the eyes...
writer.Formatting = Formatting.Indented

Console.WriteLine(“Starting read operations on “ & FILE_NAME_IN _


& “...”)

‘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

LISTING 10.8 Continued


Select Case reader.NodeType
Case XmlNodeType.XmlDeclaration
Console.WriteLine(“Writing: start of document”)
writer.WriteStartDocument(False)

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

‘Close the reader


reader.Close()

‘Close up the writer


Reading and Writing XML
449
CHAPTER 10

LISTING 10.8 Continued


writer.WriteEndDocument()
writer.Close()

Catch xmlErr As XmlException


‘Catch instances of XmlException

MsgBox(“An XML error has occurred (“ & xmlErr.Message & “).” & _
vbCrLf & vbCrLf & “ Xml Source Line Nbr: “ & _
reader.LineNumber & vbCrLf & “ Xml Source Position: “ & _
reader.LinePosition)

Catch appErr As Exception


‘Catch generic application exceptions

MsgBox(“An error occurred (“ & appErr.Message & “). Stack info:” & _
appErr.StackTrace)

End Try

Console.WriteLine(“<hit ‘Enter’ to exit>”)


Console.ReadLine()
End Sub

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 and XDR Documents


DTDs, while powerful enough in their capability to impose structure, do have their limitations.
For one thing, you can’t specify a data type for attributes. You cannot, for instance, specify that
“price” should be a decimal number, or that “title” should be a string. DTDs also introduce an
element of complexity into the design of XML applications because they require you to know
yet another set of rules—those that define the actually allowed syntax inside a DTD file.
There is a solution to this: XML schemas. XML schemas are meant to replace DTDs as a
mechanism for specifying structure in an XML document. They offer a few advantages over
DTDs because they allow for the typing of attributes and because they are expressed, them-
selves, in XML.
XSD files share the same goal as DTDs: They are used to describe a schema used by XML
documents. But XSDs offer a much cleaner approach to the problem of describing XML struc-
ture: Instead of using a “custom” syntax (as we have seen in the DTD), the XSD is itself
XML. Listing 10.9 shows the same DTD that you glimpsed previously, this time in XSD
format.

LISTING 10.9 books.dtd Expressed as an XSD Document


<?xml version=”1.0” encoding=”utf-8”?>
<xsd:schema id=”Schema” targetNamespace=”” xmlns=”” _
xmlns:xsd=”http://www.w3.org/2001/XMLSchema” _
xmlns:msdata=”urn:schemas-microsoft-com:xml-msdata”>
<xsd:element name=”Schema”>
<xsd:complexType>
<xsd:choice maxOccurs=”unbounded”>
<xsd:element name=”books”>
<xsd:complexType>
<xsd:sequence>
<xsd:element name=”book” minOccurs=”0” maxOccurs=”unbounded”>
<xsd:complexType>
Reading and Writing XML
453
CHAPTER 10

LISTING 10.9 Continued


<xsd:sequence>
<xsd:element name=”title” type=”xsd:string” _
minOccurs=”0” msdata:Ordinal=”0” />
<xsd:element name=”summary” type=”xsd:string” _
minOccurs=”0” msdata:Ordinal=”1” />
<xsd:element name=”price” type=”xsd:string” _
minOccurs=”0” msdata:Ordinal=”2” />
<xsd:element name=”chapters” minOccurs=”0” _
maxOccurs=”unbounded”>
<xsd:complexType>
<xsd:sequence>
<xsd:element name=”chapter” maxOccurs=”unbounded” _
minOccurs=”0”>
<xsd:complexType>
<xsd:attribute name=”chapters_Id” _
type=”xsd:int” use=”prohibited” />
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name=”chapters_Id” _
msdata:AutoIncrement=”true” type=”xsd:int” _
msdata:AllowDBNull=”false” use=”prohibited” />
<xsd:attribute name=”book_Id” type=”xsd:int” _
use=”prohibited” />
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name=”book_Id” msdata:AutoIncrement=”true” _
type=”xsd:int” msdata:AllowDBNull=”false” _
use=”prohibited” />
<xsd:attribute name=”books_Id” type=”xsd:int” _
use=”prohibited” />
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name=”books_Id” msdata:AutoIncrement=”true” _
type=”xsd:int” msdata:AllowDBNull=”false” use=”prohibited” />
</xsd:complexType>
</xsd:element> 10
</xsd:choice>
WRITING XML

</xsd:complexType>
READING AND

</xsd:element>
</xsd:schema>

XSD commonly is referred to simply as XML schemas.


Working with the .NET Namespaces
454
PART II

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.

LISTING 10.10 books.dtd as an XDR Document


<Schema name=”BOOKS” xmlns=”urn:schemas-microsoft-com:xml-data”>
<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”/>
<element type=”title”/>
<element type=”chapters”/>
<element type=”summary”/>
<element type=”price”/>
</ElementType>
<ElementType name=”books” content=”eltOnly” model=”closed”>
<element type=”book” maxOccurs=”*”/>
</ElementType>
</ElementType>
</Schema>

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

<ElementType name=”books” content=”eltOnly” model=”closed”>


<element type=”book” maxOccurs=”*”/>
</ElementType>
</Schema>
Working with the .NET Namespaces
456
PART II

Programmatically Building Schemas


The System.Xml.Schema namespace contains classes that enable you to build, edit, read, and
write XML schema files. At the core of this namespace lies the XmlSchema class. This class
encapsulates the definition of a schema. The namespace also contains a bevy of classes used to
represent the different types of XML elements that can exist inside an XML schema document
(such as complex types, sequences, and groups).

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

LISTING 10.11 Continued


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>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name=”id” form=”unqualified” type=”xsd:string” />
</xsd:complexType>
</xsd:element>
</xsd:schema>

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.

Adding Elements into the Schema Document


We have already said that the XmlSchema class is used to contain XSDs. Now let’s see how we
can use this class to create the schema presented in Listing 10.11. To start creating your own
schema, you would first start out with a new instance of XmlSchema:
Dim mySchema As New XmlSchema()

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

‘----create the schema element for “hotel”


Dim elementHotel As New XmlSchemaElement()
elementHotel.Name = “hotel”

To create the complex type and sequence groupings, we need to use the
XmlSchemaComplexType class and the XmlSchemaSequence class:

‘Hotel is a complex type


Dim complexTypeHotel As New XmlSchemaComplexType()
elementHotel.SchemaType = complexTypeHotel

‘Hotel is also a sequence


Dim seqHotel As New XmlSchemaSequence()
complexTypeHotel.Particle = seqHotel

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:

‘Add the element to the schema


seqHotel.Items.Add(elementGuests)

Listing 10.12 shows a console application that builds the XSD file that we witnessed in
Listing 10.11.

LISTING 10.12 Building the Hotel Register XSD File


Imports System.Xml
Imports System.Xml.Schema
10
Module SchemaBuilder
WRITING XML
READING AND

Sub Main()
Try
Working with the .NET Namespaces
460
PART II

LISTING 10.12 Continued


‘mySchema represents the root XSD document
Dim mySchema As New XmlSchema()

‘----create the schema element for “hotel”


Dim elementHotel As New XmlSchemaElement()
elementHotel.Name = “hotel”

‘Hotel is a complex type


Dim complexTypeHotel As New XmlSchemaComplexType()
elementHotel.SchemaType = complexTypeHotel

‘Hotel is also a sequence


Dim seqHotel As New XmlSchemaSequence()
complexTypeHotel.Particle = seqHotel

‘Add the element to the schema


mySchema.Items.Add(elementHotel)

‘----create the schema element for “guests”


Dim elementGuests As New XmlSchemaElement()
elementGuests.Name = “guests”

‘Guests is a complex type


Dim complexTypeGuests As New XmlSchemaComplexType()
elementGuests.SchemaType = complexTypeGuests

‘Guests is also a sequence


Dim seqGuests As New XmlSchemaSequence()
complexTypeGuests.Particle = seqGuests

‘Add the guests element to the hotel sequence element


seqHotel.Items.Add(elementGuests)

‘----create the schema element for “guest”


Dim elementGuest As New XmlSchemaElement()
elementGuest.Name = “guest”

‘Guests is a complex type


Dim complexTypeGuest As New XmlSchemaComplexType()
elementGuest.SchemaType = complexTypeGuest

‘Guests is also a sequence


Dim seqGuest As New XmlSchemaSequence()
complexTypeGuest.Particle = seqGuest
Reading and Writing XML
461
CHAPTER 10

LISTING 10.12 Continued


‘Add the guest element to the guests sequence element
seqGuests.Items.Add(elementGuest)

‘----create the schema element for “firstname”


Dim elementFirstName As New XmlSchemaElement()
elementFirstName.Name = “firstname”
elementFirstName.SchemaTypeName = New XmlQualifiedName(“string”, _
“http://www.w3.org/2001/XMLSchema”)

‘Add the element to the guest sequence


seqGuest.Items.Add(elementFirstName)

‘----create the schema element for “middlename”


Dim elementMiddleName As New XmlSchemaElement()
elementMiddleName.Name = “middlename”
elementMiddleName.SchemaTypeName = New XmlQualifiedName(“string”, _
“http://www.w3.org/2001/XMLSchema”)

‘Add the element to the guest sequence


seqGuest.Items.Add(elementMiddleName)

‘----create the schema element for “lastname”


Dim elementLastName As New XmlSchemaElement()
elementLastName.Name = “lastname”
elementLastName.SchemaTypeName = New XmlQualifiedName(“string”, _
“http://www.w3.org/2001/XMLSchema”)

‘Add the element to the guest sequence


seqGuest.Items.Add(elementLastName)

‘----create the schema element for “roomnbr”


Dim elementRoomNbr As New XmlSchemaElement()
elementRoomNbr.Name = “roomnbr”
elementRoomNbr.SchemaTypeName = New XmlQualifiedName(“int”, _
“http://www.w3.org/2001/XMLSchema”)

‘Add the element to the guest sequence


seqGuest.Items.Add(elementRoomNbr) 10
WRITING XML

‘----create the schema element for “checkindate”


READING AND

Dim elementCheckInDate As New XmlSchemaElement()


elementCheckInDate.Name = “checkindate”
elementCheckInDate.SchemaTypeName = New XmlQualifiedName(“string”, _
“http://www.w3.org/2001/XMLSchema”)
Working with the .NET Namespaces
462
PART II

LISTING 10.12 Continued


‘Add the element to the guest sequence
seqGuest.Items.Add(elementCheckInDate)

‘----create the schema element for “numnights”


Dim elementNumNights As New XmlSchemaElement()
elementNumNights.Name = “numnights”
elementNumNights.SchemaTypeName = New XmlQualifiedName(“int”, _
“http://www.w3.org/2001/XMLSchema”)

‘Add the element to the guest sequence


seqGuest.Items.Add(elementNumNights)

‘----create the schema element for “checkindate”


Dim elementPreferred As New XmlSchemaElement()
elementPreferred.Name = “preferred”
elementPreferred.SchemaTypeName = New XmlQualifiedName(“string”, _
“http://www.w3.org/2001/XMLSchema”)

‘Add the element to the guest sequence


seqGuest.Items.Add(elementPreferred)

mySchema.Write(Console.Out)

Catch schemaErr As XmlSchemaException


MsgBox(“An error with the schema occurred with the following _
object: “ & schemaErr.SourceSchemaObject.ToString)

Catch appErr As Exception


MsgBox(“An error was encountered generating the schema: “ & _
appErr.Message & vbCrLf & vbCrLf & “Stack trace: “ & _
appErr.StackTrace)

Finally

Console.WriteLine()
Console.WriteLine(“Hit ‘Enter’ to exit.”)
Console.ReadLine()

End Try

End Sub
Reading and Writing XML
463
CHAPTER 10

Suggestions for Further Exploration


➲ A great way to research the various schema elements supported by XSD documents is to
quickly run through all the XmlSchemaXXX classes in the MSDN Framework documenta-
tion. Each one represents a unique element type.
➲ Visit http://www.w3.org/XML/Schema for a detailed explanation of the W3C’s work on
schemas and also an index of schema materials.

Validating XML Documents


When you deal with XML in your code, you are concerned with two primary processes: read-
ing XML and writing XML. When reading an XML document, you might be concerned about
whether the XML that you are consuming is “correct.” That is, is the XML compliant with the
W3C XML specification, and does it correctly implement the defined grammar and vocabulary
delineated by a DTD, XDR, or XSD document? XML documents that correctly adhere to the
XML specification are said to be well formed. XML documents that adhere to the structure
described in a schema document are said to be valid.
This section examines the System.Xml support for validating XML documents against a spe-
cific schema by using the XmlValidatingReader class.

Creating Validation Event Handlers


The XmlValidatingReader class looks very similar to the XmlTextReader class. It still
processes an XML document one node at a time through its Read method. However, instead of
pointing the validating reader directly at the physical XML document as we do with the text
reader, the XmlValidatingReader expects to leverage an existing reader instance to act as its
source like this:
Dim reader As XmlTextReader = New XmlTextReader(fileName)
Dim validator As XmlValidatingReader = New XmlValidatingReader(reader)
After creating an XmlValidatingRead instance, the first task is to tell it which type of valida-
tion needs to be done. As mentioned before, the .NET Framework Class Library currently sup-
ports XSDs, XDRs, and DTDs for XML validation. To indicate which of these formats the
validating reader should use, set the ValidationType property. This property is used in con-
junction with the ValidationType enumeration to specify exactly how the validation should be
performed. The ValidationType enumeration is documented in Table 10.9. 10
WRITING XML
READING AND
Working with the .NET Namespaces
464
PART II

TABLE 10.9 The ValidationType Enumeration


Member Name Description
Auto Validates the XML document against either DTD or XML schema
information, depending on the schema or DOCTYPE specified in the
source XML document.
DTD Validates the XML document against the DTD specified in the DOC-
TYPE directive.
None Performs no validation. (Essentially creates a reader that has the
capability to use default attributes and general entities that may be
specified in a schema, but no actual validation is performed.)
Schema Validates the XML document according to the schema pointed at by
the xmlns URI.
XDR Validates the XML document according to the XDR schema pointed
at by any referenced x-schema namespaces.

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()

‘create a validating reader object


Dim reader As XmlTextReader = New XmlTextReader(fileName)
Dim validator as XmlValidatingReader = new XmlValidatingReader(reader)
Reading and Writing XML
465
CHAPTER 10

‘add a previously created XmlSchema object to the schema collection


schemaCollection.Add(mySchema)

‘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.

Handling Validation Errors with Events


To set up an event-handler design pattern for use with XmlValidatingReader, you use the
ValidationEventHandler delegate (defined in System.Xml.Schema) and add an event handler
in your code that maps to that delegate. Here is a quick example. First, write a subroutine to
handle the event callback:
‘Display the validation error.
Public Sub HandleValidationError(sender As Object, args As _
ValidationEventArgs)

MsgBox(“Error validating source XML document: “ & args.Message))


End Sub

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

TABLE 10.10 The XmlSeverityType Enumeration


Member Name Description
Error An error occurred indicating that the XML document does not con-
form to the supplied DTD, XDR, or XSD Schema document.
Warning The validator was incapable of locating a corresponding schema ele-
ment for the current XML document element. In other words, no
schema item exists that will validate the current XML document
element. The Warning value can be returned only if the
ValidationType is set to Schemas.

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:

AddHandler validator.ValidationEventHandler, AddressOf _


HandleValidationError

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.

Handling Validation Errors with Exceptions


If you do not define any event handler and attach it to XmlValidatingRead.
ValidationEventHandler, the reader instead raises exceptions of XmlException type. The
caveat here is that the first time the exception is thrown, the read process essentially is termi-
nated and cannot be restarted.
Here is an example of a validating reader that relies on exception handling:
Try
reader = New XmlTextReader(fileName)
validator = New XmlValidatingReader(reader)

While validator.Read()
.
.
.
End While
Reading and Writing XML
467
CHAPTER 10

Catch validationErr As XmlException


‘deal with the validation error here

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.

Suggestions for Further Exploration


➲ We have only touched on instantiating validating readers from other XML readers.
However, you can create a validating reader that will read over an XML fragment. For
more information, consult the Framework SDK documentation on the
XmlValidatingReader class and examine the different overloaded constructors available
to you.
➲ The XmlDocument class can “load” a validating reader object, enabling you to validate in-
memory representations of XML documents. See the XmlDocument.Load method docu-
mentation for more information.

Learning by Example: The Hotel Reservations Desk


To reinforce the topics discussed in this chapter, this “learning by example” application pre-
sents an XML reading “wrapper” for a fictitious hotel chain. The application’s goal is to allow
customer service representatives to look up current hotel reservations in a data store (an XML
file). The program first forces you to constrain your search to a specific hotel and then allows
searching by last name, first name, or check-in date.
After the search is executed, a list of guest IDs that match the supplied criteria will show up in
a list box. The user can then select one of these guests to view detailed information.
Figure 10.9 shows the application in action.
10
WRITING XML
READING AND
Working with the .NET Namespaces
468
PART II

FIGURE 10.9
The main window.

Key Concepts Covered


This program showcases the following:
• Use of the XmlValidatingReader to validate an XML document against a schema
• Loading XML documents into memory
• Parsing XML documents using the XmlNodeReader class

Code Walkthrough
Listing 10.13 shows a sample XML file that you can use for testing the ReservationsDesk
application.

LISTING 10.13 XML Document for Testing


<?xml version=”1.0” standalone=”no” ?>
<register>
<hotel id=”DC-4RIVERS”>
<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>
Reading and Writing XML
469
CHAPTER 10

LISTING 10.13 Continued


<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>
<guest id=”d b6620103”>
<firstname>Dorsa</firstname>
<middlename />
<lastname>Brevia</lastname>
<roomnbr>408</roomnbr>
<checkindate>6/18/2001</checkindate>
<numnights>1</numnights>
<preferred>Yes</preferred>
</guest>
<guest id=”jgm9111447”>
<firstname>Jackie</firstname>
<middlename>G</middlename>
<lastname>Mendelin</lastname>
<roomnbr>223</roomnbr>
<checkindate>6/15/2001</checkindate>
<numnights>5</numnights>
<preferred>No</preferred>
</guest>
</guests>
</hotel>
<hotel id=”LA-MONTAGE”>
<guests>
<guest id=”pkc0010710”>
<firstname>Peter</firstname>
<middlename>K</middlename>
<lastname>Ceton</lastname>
<roomnbr>107</roomnbr>
<checkindate>2/12/2000</checkindate>
<numnights>1</numnights>
<preferred>No</preferred> 10
</guest>
WRITING XML

<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

LISTING 10.13 Continued


<checkindate>2/12/2000</checkindate>
<numnights>1</numnights>
<preferred>No</preferred>
</guest>
</guests>
</hotel>
</register>

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.

LISTING 10.14 ReservationsDesk


Imports System.Xml

Public Class Main


Inherits System.Windows.Forms.Form

Public resFile As String


Public xmlDoc As XmlDocument

Friend WithEvents buttonSearch As System.Windows.Forms.Button


Friend WithEvents Label2 As System.Windows.Forms.Label
Friend WithEvents Label3 As System.Windows.Forms.Label
Friend WithEvents Label4 As System.Windows.Forms.Label
Friend WithEvents Label5 As System.Windows.Forms.Label
Friend WithEvents Label6 As System.Windows.Forms.Label
Friend WithEvents Label7 As System.Windows.Forms.Label
Friend WithEvents Label8 As System.Windows.Forms.Label
Friend WithEvents RadioButtonByName As System.Windows.Forms.RadioButton
Friend WithEvents RadioButtonByDate As System.Windows.Forms.RadioButton
Friend WithEvents TextBoxName As System.Windows.Forms.TextBox
Friend WithEvents DateTimeCheckin As System.Windows.Forms.DateTimePicker
Friend WithEvents ListBoxGuests As System.Windows.Forms.ListBox
Friend WithEvents ComboHotel As System.Windows.Forms.ComboBox
Friend WithEvents TextBoxPreferred As System.Windows.Forms.TextBox
Friend WithEvents TextBoxNumNights As System.Windows.Forms.TextBox
Friend WithEvents TextBoxCheckinDate As System.Windows.Forms.TextBox
Friend WithEvents TextBoxRoomNbr As System.Windows.Forms.TextBox
Friend WithEvents TextBoxLastName As System.Windows.Forms.TextBox
Friend WithEvents TextBoxMiddleName As System.Windows.Forms.TextBox
Friend WithEvents TextBoxFirstName As System.Windows.Forms.TextBox
Reading and Writing XML
471
CHAPTER 10

LISTING 10.14 Continued


#Region “ Windows Form Designer generated code “

Public Sub New()


MyBase.New()

‘This call is required by the Windows Form Designer.


InitializeComponent()

‘Add any initialization after the InitializeComponent() call


xmlDoc = New XmlDataDocument()
End Sub

‘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
Friend WithEvents MainMenu1 As System.Windows.Forms.MainMenu
Friend WithEvents MenuItem1 As System.Windows.Forms.MenuItem
Friend WithEvents MenuItem2 As System.Windows.Forms.MenuItem
Friend WithEvents MenuItem3 As System.Windows.Forms.MenuItem
Friend WithEvents OpenFileDialog1 As System.Windows.Forms.OpenFileDialog
Friend WithEvents GroupBox1 As System.Windows.Forms.GroupBox
Friend WithEvents GroupBox2 As System.Windows.Forms.GroupBox
Friend WithEvents Label1 As System.Windows.Forms.Label

‘Required by the Windows Form Designer


Private components As System.ComponentModel.Container

‘NOTE: The following procedure is required by the Windows Form Designer


‘It can be modified using the Windows Form Designer.
‘Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub _
InitializeComponent()
Dim resources As System.Resources.ResourceManager = New _ 10
System.Resources.ResourceManager(GetType(Main))
WRITING XML

Me.Label4 = New System.Windows.Forms.Label()


READING AND

Me.Label5 = New System.Windows.Forms.Label()


Me.Label7 = New System.Windows.Forms.Label()
Me.Label1 = New System.Windows.Forms.Label()
Me.Label2 = New System.Windows.Forms.Label()
Working with the .NET Namespaces
472
PART II

LISTING 10.14 Continued


Me.Label3 = New System.Windows.Forms.Label()
Me.Label6 = New System.Windows.Forms.Label()
Me.TextBoxMiddleName = New System.Windows.Forms.TextBox()
Me.ListBoxGuests = New System.Windows.Forms.ListBox()
Me.OpenFileDialog1 = New System.Windows.Forms.OpenFileDialog()
Me.GroupBox2 = New System.Windows.Forms.GroupBox()
Me.Label8 = New System.Windows.Forms.Label()
Me.TextBoxPreferred = New System.Windows.Forms.TextBox()
Me.TextBoxNumNights = New System.Windows.Forms.TextBox()
Me.TextBoxCheckinDate = New System.Windows.Forms.TextBox()
Me.TextBoxRoomNbr = New System.Windows.Forms.TextBox()
Me.TextBoxLastName = New System.Windows.Forms.TextBox()
Me.TextBoxFirstName = New System.Windows.Forms.TextBox()
Me.ComboHotel = New System.Windows.Forms.ComboBox()
Me.MenuItem1 = New System.Windows.Forms.MenuItem()
Me.MenuItem2 = New System.Windows.Forms.MenuItem()
Me.MenuItem3 = New System.Windows.Forms.MenuItem()
Me.RadioButtonByName = New System.Windows.Forms.RadioButton()
Me.DateTimeCheckin = New System.Windows.Forms.DateTimePicker()
Me.RadioButtonByDate = New System.Windows.Forms.RadioButton()
Me.TextBoxName = New System.Windows.Forms.TextBox()
Me.buttonSearch = New System.Windows.Forms.Button()
Me.MainMenu1 = New System.Windows.Forms.MainMenu()
Me.GroupBox1 = New System.Windows.Forms.GroupBox()
Me.GroupBox2.SuspendLayout()
Me.GroupBox1.SuspendLayout()
Me.SuspendLayout()

‘Label4

Me.Label4.Location = New System.Drawing.Point(184, 76)
Me.Label4.Name = “Label4”
Me.Label4.Size = New System.Drawing.Size(76, 16)
Me.Label4.TabIndex = 4
Me.Label4.Text = “Last Name:”

‘Label5

Me.Label5.Location = New System.Drawing.Point(184, 100)
Me.Label5.Name = “Label5”
Me.Label5.Size = New System.Drawing.Size(76, 16)
Me.Label5.TabIndex = 4
Me.Label5.Text = “Room Nbr:”

‘Label7

Reading and Writing XML
473
CHAPTER 10

LISTING 10.14 Continued


Me.Label7.Location = New System.Drawing.Point(184, 148)
Me.Label7.Name = “Label7”
Me.Label7.Size = New System.Drawing.Size(76, 16)
Me.Label7.TabIndex = 4
Me.Label7.Text = “# Nights:”

‘Label1

Me.Label1.Location = New System.Drawing.Point(128, 24)
Me.Label1.Name = “Label1”
Me.Label1.Size = New System.Drawing.Size(40, 12)
Me.Label1.TabIndex = 0
Me.Label1.Text = “Hotel:”

‘Label2

Me.Label2.Location = New System.Drawing.Point(184, 28)
Me.Label2.Name = “Label2”
Me.Label2.Size = New System.Drawing.Size(76, 16)
Me.Label2.TabIndex = 4
Me.Label2.Text = “First Name:”

‘Label3

Me.Label3.Location = New System.Drawing.Point(184, 52)
Me.Label3.Name = “Label3”
Me.Label3.Size = New System.Drawing.Size(76, 16)
Me.Label3.TabIndex = 4
Me.Label3.Text = “Middle Name:”

‘Label6

Me.Label6.Location = New System.Drawing.Point(184, 124)
Me.Label6.Name = “Label6”
Me.Label6.Size = New System.Drawing.Size(76, 16)
Me.Label6.TabIndex = 4
Me.Label6.Text = “Check-In:”

‘TextBoxMiddleName 10

WRITING XML

Me.TextBoxMiddleName.Location = New System.Drawing.Point(264, 48)


READING AND

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

LISTING 10.14 Continued


Me.TextBoxMiddleName.Text = “”

‘ListBoxGuests

Me.ListBoxGuests.Enabled = False
Me.ListBoxGuests.Location = New System.Drawing.Point(12, 24)
Me.ListBoxGuests.Name = “ListBoxGuests”
Me.ListBoxGuests.Size = New System.Drawing.Size(160, 173)
Me.ListBoxGuests.TabIndex = 0

‘GroupBox2

Me.GroupBox2.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.Label8, Me.TextBoxPreferred, Me.TextBoxNumNights, Me.Label7, _
Me.TextBoxCheckinDate, Me.Label6, Me.TextBoxRoomNbr, Me.Label5, _
Me.TextBoxLastName, Me.Label4, Me.TextBoxMiddleName, Me.Label3, _
Me.Label2, Me.TextBoxFirstName, Me.ListBoxGuests})
Me.GroupBox2.Location = New System.Drawing.Point(4, 140)
Me.GroupBox2.Name = “GroupBox2”
Me.GroupBox2.Size = New System.Drawing.Size(444, 208)
Me.GroupBox2.TabIndex = 1
Me.GroupBox2.TabStop = False
Me.GroupBox2.Text = “Reservation Records”

‘Label8

Me.Label8.Location = New System.Drawing.Point(184, 172)
Me.Label8.Name = “Label8”
Me.Label8.Size = New System.Drawing.Size(76, 16)
Me.Label8.TabIndex = 4
Me.Label8.Text = “Preferred:”

‘TextBoxPreferred

Me.TextBoxPreferred.Location = New System.Drawing.Point(264, 168)
Me.TextBoxPreferred.Name = “TextBoxPreferred”
Me.TextBoxPreferred.ReadOnly = True
Me.TextBoxPreferred.Size = New System.Drawing.Size(44, 20)
Me.TextBoxPreferred.TabIndex = 3
Me.TextBoxPreferred.Text = “”

‘TextBoxNumNights

Me.TextBoxNumNights.Location = New System.Drawing.Point(264, 144)
Me.TextBoxNumNights.Name = “TextBoxNumNights”
Reading and Writing XML
475
CHAPTER 10

LISTING 10.14 Continued


Me.TextBoxNumNights.ReadOnly = True
Me.TextBoxNumNights.Size = New System.Drawing.Size(44, 20)
Me.TextBoxNumNights.TabIndex = 3
Me.TextBoxNumNights.Text = “”

‘TextBoxCheckinDate

Me.TextBoxCheckinDate.Location = New System.Drawing.Point(264, 120)
Me.TextBoxCheckinDate.Name = “TextBoxCheckinDate”
Me.TextBoxCheckinDate.ReadOnly = True
Me.TextBoxCheckinDate.Size = New System.Drawing.Size(168, 20)
Me.TextBoxCheckinDate.TabIndex = 3
Me.TextBoxCheckinDate.Text = “”

‘TextBoxRoomNbr

Me.TextBoxRoomNbr.Location = New System.Drawing.Point(264, 96)
Me.TextBoxRoomNbr.Name = “TextBoxRoomNbr”
Me.TextBoxRoomNbr.ReadOnly = True
Me.TextBoxRoomNbr.Size = New System.Drawing.Size(44, 20)
Me.TextBoxRoomNbr.TabIndex = 3
Me.TextBoxRoomNbr.Text = “”

‘TextBoxLastName

Me.TextBoxLastName.Location = New System.Drawing.Point(264, 72)
Me.TextBoxLastName.Name = “TextBoxLastName”
Me.TextBoxLastName.ReadOnly = True
Me.TextBoxLastName.Size = New System.Drawing.Size(168, 20)
Me.TextBoxLastName.TabIndex = 3
Me.TextBoxLastName.Text = “”

‘TextBoxFirstName

Me.TextBoxFirstName.Location = New System.Drawing.Point(264, 24)
Me.TextBoxFirstName.Name = “TextBoxFirstName”
Me.TextBoxFirstName.ReadOnly = True
Me.TextBoxFirstName.Size = New System.Drawing.Size(168, 20)
Me.TextBoxFirstName.TabIndex = 3 10
Me.TextBoxFirstName.Text = “”
WRITING XML


READING AND

‘ComboHotel

Me.ComboHotel.DropDownStyle = _
System.Windows.Forms.ComboBoxStyle.DropDownList
Working with the .NET Namespaces
476
PART II

LISTING 10.14 Continued


Me.ComboHotel.DropDownWidth = 152
Me.ComboHotel.Enabled = False
Me.ComboHotel.Location = New System.Drawing.Point(168, 20)
Me.ComboHotel.Name = “ComboHotel”
Me.ComboHotel.Size = New System.Drawing.Size(152, 21)
Me.ComboHotel.TabIndex = 1

‘MenuItem1

Me.MenuItem1.Index = 0
Me.MenuItem1.MenuItems.AddRange(New System.Windows.Forms.MenuItem() _
{Me.MenuItem2, Me.MenuItem3})
Me.MenuItem1.Text = “&File”

‘MenuItem2

Me.MenuItem2.Index = 0
Me.MenuItem2.Text = “&Open”

‘MenuItem3

Me.MenuItem3.Index = 1
Me.MenuItem3.Text = “E&xit”

‘RadioButtonByName

Me.RadioButtonByName.Location = New System.Drawing.Point(32, 52)
Me.RadioButtonByName.Name = “RadioButtonByName”
Me.RadioButtonByName.RightToLeft = System.Windows.Forms.RightToLeft.Yes
Me.RadioButtonByName.Size = New System.Drawing.Size(128, 16)
Me.RadioButtonByName.TabIndex = 2
Me.RadioButtonByName.Text = “Search By Name”

‘DateTimeCheckin

Me.DateTimeCheckin.CustomFormat = “”
Me.DateTimeCheckin.Format = _
System.Windows.Forms.DateTimePickerFormat.Custom
Me.DateTimeCheckin.Location = New System.Drawing.Point(168, 76)
Me.DateTimeCheckin.Name = “DateTimeCheckin”
Me.DateTimeCheckin.Size = New System.Drawing.Size(192, 20)
Me.DateTimeCheckin.TabIndex = 4
Me.DateTimeCheckin.Value = New Date(2000, 2, 12, 0, 0, 0, 0)

‘RadioButtonByDate

Reading and Writing XML
477
CHAPTER 10

LISTING 10.14 Continued


Me.RadioButtonByDate.Checked = True
Me.RadioButtonByDate.Location = New System.Drawing.Point(8, 80)
Me.RadioButtonByDate.Name = “RadioButtonByDate”
Me.RadioButtonByDate.RightToLeft = System.Windows.Forms.RightToLeft.Yes
Me.RadioButtonByDate.Size = New System.Drawing.Size(152, 16)
Me.RadioButtonByDate.TabIndex = 2
Me.RadioButtonByDate.TabStop = True
Me.RadioButtonByDate.Text = “Search By Check-in Date”

‘TextBoxName

Me.TextBoxName.Location = New System.Drawing.Point(168, 48)
Me.TextBoxName.Name = “TextBoxName”
Me.TextBoxName.Size = New System.Drawing.Size(192, 20)
Me.TextBoxName.TabIndex = 3
Me.TextBoxName.Text = “< last, first >”

‘buttonSearch

Me.buttonSearch.Enabled = False
Me.buttonSearch.Location = New System.Drawing.Point(168, 104)
Me.buttonSearch.Name = “buttonSearch”
Me.buttonSearch.Size = New System.Drawing.Size(76, 20)
Me.buttonSearch.TabIndex = 5
Me.buttonSearch.Text = “&Search”

‘MainMenu1

Me.MainMenu1.MenuItems.AddRange(New System.Windows.Forms.MenuItem() _
{Me.MenuItem1})

‘GroupBox1

Me.GroupBox1.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.buttonSearch, Me.DateTimeCheckin, Me.TextBoxName, _
Me.RadioButtonByDate, Me.RadioButtonByName, Me.ComboHotel, _
Me.Label1})
Me.GroupBox1.Location = New System.Drawing.Point(4, 4)
Me.GroupBox1.Name = “GroupBox1” 10
Me.GroupBox1.Size = New System.Drawing.Size(444, 132)
WRITING XML

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

LISTING 10.14 Continued


Me.ClientSize = New System.Drawing.Size(460, 361)
Me.Controls.AddRange(New System.Windows.Forms.Control() _
{Me.GroupBox2, Me.GroupBox1})
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog
Me.Icon = CType(resources.GetObject(“$this.Icon”), System.Drawing.Icon)
Me.MaximizeBox = False
Me.Menu = Me.MainMenu1
Me.Name = “Main”
Me.Text = “ReservationsDesk (no file loaded)”
Me.GroupBox2.ResumeLayout(False)
Me.GroupBox1.ResumeLayout(False)
Me.ResumeLayout(False)

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

‘Show a File Open dialog; this is for selecting the source


‘XML document...
OpenFileDialog1.AddExtension = True

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

LISTING 10.14 Continued


buttonSearch.Enabled = True
ComboHotel.Enabled = True
ListBoxGuests.Enabled = True
End If

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

‘First, create a XmlTextReader against the supplied file


reader = New XmlTextReader(file)

‘Then, use the XmlTextReader to create a XmlValidatingReader


validator = New XmlValidatingReader(reader)
validator.ValidationType = ValidationType.Auto

‘And finally, load up our global XmlDocument


xmlDoc.Load(validator)

‘We are done with the validator, so close it


If Not IsNothing(validator) Then
validator.Close()
End If

‘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.”)

Catch appErr As XmlException 10


MsgBox(“The application was unable to load the specified XML file _
WRITING XML

‘“ & resFile & “‘. The file may be corrupt, or may be from an _
READING AND

invalid source. Please select a new file.”)