- •Using Your Sybex Electronic Book
- •Acknowledgments
- •Contents at a Glance
- •Introduction
- •Who Should Read This Book?
- •How About the Advanced Topics?
- •The Structure of the Book
- •How to Reach the Author
- •The Integrated Development Environment
- •The Start Page
- •Project Types
- •Your First VB Application
- •Making the Application More Robust
- •Making the Application More User-Friendly
- •The IDE Components
- •The IDE Menu
- •The Toolbox Window
- •The Solution Explorer
- •The Properties Window
- •The Output Window
- •The Command Window
- •The Task List Window
- •Environment Options
- •A Few Common Properties
- •A Few Common Events
- •A Few Common Methods
- •Building a Console Application
- •Summary
- •Building a Loan Calculator
- •How the Loan Application Works
- •Designing the User Interface
- •Programming the Loan Application
- •Validating the Data
- •Building a Math Calculator
- •Designing the User Interface
- •Programming the MathCalculator App
- •Adding More Features
- •Exception Handling
- •Taking the LoanCalculator to the Web
- •Working with Multiple Forms
- •Working with Multiple Projects
- •Executable Files
- •Distributing an Application
- •VB.NET at Work: Creating a Windows Installer
- •Finishing the Windows Installer
- •Running the Windows Installer
- •Verifying the Installation
- •Summary
- •Variables
- •Declaring Variables
- •Types of Variables
- •Converting Variable Types
- •User-Defined Data Types
- •Examining Variable Types
- •Why Declare Variables?
- •A Variable’s Scope
- •The Lifetime of a Variable
- •Constants
- •Arrays
- •Declaring Arrays
- •Initializing Arrays
- •Array Limits
- •Multidimensional Arrays
- •Dynamic Arrays
- •Arrays of Arrays
- •Variables as Objects
- •So, What’s an Object?
- •Formatting Numbers
- •Formatting Dates
- •Flow-Control Statements
- •Test Structures
- •Loop Structures
- •Nested Control Structures
- •The Exit Statement
- •Summary
- •Modular Coding
- •Subroutines
- •Functions
- •Arguments
- •Argument-Passing Mechanisms
- •Event-Handler Arguments
- •Passing an Unknown Number of Arguments
- •Named Arguments
- •More Types of Function Return Values
- •Overloading Functions
- •Summary
- •The Appearance of Forms
- •Properties of the Form Control
- •Placing Controls on Forms
- •Setting the TabOrder
- •VB.NET at Work: The Contacts Project
- •Anchoring and Docking
- •Loading and Showing Forms
- •The Startup Form
- •Controlling One Form from within Another
- •Forms vs. Dialog Boxes
- •VB.NET at Work: The MultipleForms Project
- •Designing Menus
- •The Menu Editor
- •Manipulating Menus at Runtime
- •Building Dynamic Forms at Runtime
- •The Form.Controls Collection
- •VB.NET at Work: The DynamicForm Project
- •Creating Event Handlers at Runtime
- •Summary
- •The TextBox Control
- •Basic Properties
- •Text-Manipulation Properties
- •Text-Selection Properties
- •Text-Selection Methods
- •Undoing Edits
- •VB.NET at Work: The TextPad Project
- •Capturing Keystrokes
- •The ListBox, CheckedListBox, and ComboBox Controls
- •Basic Properties
- •The Items Collection
- •VB.NET at Work: The ListDemo Project
- •Searching
- •The ComboBox Control
- •The ScrollBar and TrackBar Controls
- •The ScrollBar Control
- •The TrackBar Control
- •Summary
- •The Common Dialog Controls
- •Using the Common Dialog Controls
- •The Color Dialog Box
- •The Font Dialog Box
- •The Open and Save As Dialog Boxes
- •The Print Dialog Box
- •The RichTextBox Control
- •The RTF Language
- •Methods
- •Advanced Editing Features
- •Cutting and Pasting
- •Searching in a RichTextBox Control
- •Formatting URLs
- •VB.NET at Work: The RTFPad Project
- •Summary
- •What Is a Class?
- •Building the Minimal Class
- •Adding Code to the Minimal Class
- •Property Procedures
- •Customizing Default Members
- •Custom Enumerations
- •Using the SimpleClass in Other Projects
- •Firing Events
- •Shared Properties
- •Parsing a Filename String
- •Reusing the StringTools Class
- •Encapsulation and Abstraction
- •Inheritance
- •Inheriting Existing Classes
- •Polymorphism
- •The Shape Class
- •Object Constructors and Destructors
- •Instance and Shared Methods
- •Who Can Inherit What?
- •Parent Class Keywords
- •Derived Class Keyword
- •Parent Class Member Keywords
- •Derived Class Member Keyword
- •MyBase and MyClass
- •Summary
- •On Designing Windows Controls
- •Enhancing Existing Controls
- •Building the FocusedTextBox Control
- •Building Compound Controls
- •VB.NET at Work: The ColorEdit Control
- •VB.NET at Work: The Label3D Control
- •Raising Events
- •Using the Custom Control in Other Projects
- •VB.NET at Work: The Alarm Control
- •Designing Irregularly Shaped Controls
- •Designing Owner-Drawn Menus
- •Designing Owner-Drawn ListBox Controls
- •Using ActiveX Controls
- •Summary
- •Programming Word
- •Objects That Represent Text
- •The Documents Collection and the Document Object
- •Spell-Checking Documents
- •Programming Excel
- •The Worksheets Collection and the Worksheet Object
- •The Range Object
- •Using Excel as a Math Parser
- •Programming Outlook
- •Retrieving Information
- •Recursive Scanning of the Contacts Folder
- •Summary
- •Advanced Array Topics
- •Sorting Arrays
- •Searching Arrays
- •Other Array Operations
- •Array Limitations
- •The ArrayList Collection
- •Creating an ArrayList
- •Adding and Removing Items
- •The HashTable Collection
- •VB.NET at Work: The WordFrequencies Project
- •The SortedList Class
- •The IEnumerator and IComparer Interfaces
- •Enumerating Collections
- •Custom Sorting
- •Custom Sorting of a SortedList
- •The Serialization Class
- •Serializing Individual Objects
- •Serializing a Collection
- •Deserializing Objects
- •Summary
- •Handling Strings and Characters
- •The Char Class
- •The String Class
- •The StringBuilder Class
- •VB.NET at Work: The StringReversal Project
- •VB.NET at Work: The CountWords Project
- •Handling Dates
- •The DateTime Class
- •The TimeSpan Class
- •VB.NET at Work: Timing Operations
- •Summary
- •Accessing Folders and Files
- •The Directory Class
- •The File Class
- •The DirectoryInfo Class
- •The FileInfo Class
- •The Path Class
- •VB.NET at Work: The CustomExplorer Project
- •Accessing Files
- •The FileStream Object
- •The StreamWriter Object
- •The StreamReader Object
- •Sending Data to a File
- •The BinaryWriter Object
- •The BinaryReader Object
- •VB.NET at Work: The RecordSave Project
- •The FileSystemWatcher Component
- •Properties
- •Events
- •VB.NET at Work: The FileSystemWatcher Project
- •Summary
- •Displaying Images
- •The Image Object
- •Exchanging Images through the Clipboard
- •Drawing with GDI+
- •The Basic Drawing Objects
- •Drawing Shapes
- •Drawing Methods
- •Gradients
- •Coordinate Transformations
- •Specifying Transformations
- •VB.NET at Work: Plotting Functions
- •Bitmaps
- •Specifying Colors
- •Defining Colors
- •Processing Bitmaps
- •Summary
- •The Printing Objects
- •PrintDocument
- •PrintDialog
- •PageSetupDialog
- •PrintPreviewDialog
- •PrintPreviewControl
- •Printer and Page Properties
- •Page Geometry
- •Printing Examples
- •Printing Tabular Data
- •Printing Plain Text
- •Printing Bitmaps
- •Using the PrintPreviewControl
- •Summary
- •Examining the Advanced Controls
- •How Tree Structures Work
- •The ImageList Control
- •The TreeView Control
- •Adding New Items at Design Time
- •Adding New Items at Runtime
- •Assigning Images to Nodes
- •Scanning the TreeView Control
- •The ListView Control
- •The Columns Collection
- •The ListItem Object
- •The Items Collection
- •The SubItems Collection
- •Summary
- •Types of Errors
- •Design-Time Errors
- •Runtime Errors
- •Logic Errors
- •Exceptions and Structured Exception Handling
- •Studying an Exception
- •Getting a Handle on this Exception
- •Finally (!)
- •Customizing Exception Handling
- •Throwing Your Own Exceptions
- •Debugging
- •Breakpoints
- •Stepping Through
- •The Local and Watch Windows
- •Summary
- •Basic Concepts
- •Recursion in Real Life
- •A Simple Example
- •Recursion by Mistake
- •Scanning Folders Recursively
- •Describing a Recursive Procedure
- •Translating the Description to Code
- •The Stack Mechanism
- •Stack Defined
- •Recursive Programming and the Stack
- •Passing Arguments through the Stack
- •Special Issues in Recursive Programming
- •Knowing When to Use Recursive Programming
- •Summary
- •MDI Applications: The Basics
- •Building an MDI Application
- •Built-In Capabilities of MDI Applications
- •Accessing Child Forms
- •Ending an MDI Application
- •A Scrollable PictureBox
- •Summary
- •What Is a Database?
- •Relational Databases
- •Exploring the Northwind Database
- •Exploring the Pubs Database
- •Understanding Relations
- •The Server Explorer
- •Working with Tables
- •Relationships, Indices, and Constraints
- •Structured Query Language
- •Executing SQL Statements
- •Selection Queries
- •Calculated Fields
- •SQL Joins
- •Action Queries
- •The Query Builder
- •The Query Builder Interface
- •SQL at Work: Calculating Sums
- •SQL at Work: Counting Rows
- •Limiting the Selection
- •Parameterized Queries
- •Calculated Columns
- •Specifying Left, Right, and Inner Joins
- •Stored Procedures
- •Summary
- •How About XML?
- •Creating a DataSet
- •The DataGrid Control
- •Data Binding
- •VB.NET at Work: The ViewEditCustomers Project
- •Binding Complex Controls
- •Programming the DataAdapter Object
- •The Command Objects
- •The Command and DataReader Objects
- •VB.NET at Work: The DataReader Project
- •VB.NET at Work: The StoredProcedure Project
- •Summary
- •The Structure of a DataSet
- •Navigating the Tables of a DataSet
- •Updating DataSets
- •The DataForm Wizard
- •Handling Identity Fields
- •Transactions
- •Performing Update Operations
- •Updating Tables Manually
- •Building and Using Custom DataSets
- •Summary
- •An HTML Primer
- •HTML Code Elements
- •Server-Client Interaction
- •The Structure of HTML Documents
- •URLs and Hyperlinks
- •The Basic HTML Tags
- •Inserting Graphics
- •Tables
- •Forms and Controls
- •Processing Requests on the Server
- •Building a Web Application
- •Interacting with a Web Application
- •Maintaining State
- •The Web Controls
- •The ASP.NET Objects
- •The Page Object
- •The Response Object
- •The Request Object
- •The Server Object
- •Using Cookies
- •Handling Multiple Forms in Web Applications
- •Summary
- •The Data-Bound Web Controls
- •Simple Data Binding
- •Binding to DataSets
- •Is It a Grid, or a Table?
- •Getting Orders on the Web
- •The Forms of the ProductSearch Application
- •Paging Large DataSets
- •Customizing the Appearance of the DataGrid Control
- •Programming the Select Button
- •Summary
- •How to Serve the Web
- •Building a Web Service
- •Consuming the Web Service
- •Maintaining State in Web Services
- •A Data-Driven Web Service
- •Consuming the Products Web Service in VB
- •Summary
956 Chapter 21 BUILDING DATABASE APPLICATIONS WITH ADO.NET
Listing 21.10: Retrieving the Selected Customer’s Orders
Private Sub ListBox1_SelectedIndexChanged(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles ListBox1.SelectedIndexChanged
Dim row As Integer = ListBox1.SelectedIndex
Dim CustID As String = DsCustomers1.Customers(row).CustomerID Orders.SelectCommand.Parameters(“CustomerID”).Value = CustID DsOrders1.Clear()
Orders.Fill(DsOrders1) End Sub
We must also detect the selection of a row in the DataGrid control with the Orders, retrieve the detail lines of the selected order and display them on the second DataGrid control. When a row (or cell) is selected on the DataGrid control, the CurrentCellChanged event is fired. Listing 21.11 shows the code that displays the selected order’s details on the second DataGrid control.
Listing 21.11: Retrieving the Selected Order’s Details
Private Sub DataGrid1_CurrentCellChanged(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles DataGrid1.CurrentCellChanged Dim row As Integer = DataGrid1.CurrentRowIndex
Dim OrdID As Integer = DataGrid1.Item(row, 0) Details.SelectCommand.Parameters(“OrderID”).Value = OrdID DsDetails1.Clear()
Details.Fill(DsDetails1) End Sub
This is all the code required by the application, and it’s not complicated either. We determine the parameter required by the query we want to call and then pass it to the query through the Parameters collection of the SelectCommand object. Of course, you must set the data-binding properties of the two DataGrid controls, so that they will update their contents when the underlying DataSets change. To bind the two DataGrid controls, set their DataSource property to the name of the corresponding DataSet.
The Command and DataReader Objects
Sometimes, we want to retrieve data from the database and read them sequentially. If you don’t plan to edit the data and submit any changes back to the client, you can use the DataReader object to read the rows sequentially. The DataReader is an object that lets you iterate through the rows retrieved by a query. It’s faster than storing all the rows to a DataSet, but you can’t move back and forth in the rows. Moreover, the connection to the database is open while you iterate through the rows, so the processing should be as quick as possible. For example, you can’t prompt the user between rows; this
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
THE COMMAND AND DATAREADER OBJECTS 957
would tie the connection for too long. It goes without saying that you can’t use the DataReader object to update the underlying table. The DataReader object returns a forward-only, read-only result.
As with the other major ADO.NET objects, there are two flavors of the DataReader object: the SqlDataReader and the OleDbDataReader objects. Use the SqlDataReader object for SQL Server databases and the OleDbDataReader for OLEDB-compliant databases. To create a DataReader object, you must execute a query against a database through a Command object. You’ve already set up Command objects, even though you didn’t do so explicitly. This time we’ll create a Command object and set its Connection and CommandText properties from within our code. Once the Command object has been set up, you can execute it by calling one of the following methods:
ExecuteReader Executes the command and returns a DataReader object, which you can use to read the results, one row at a time.
ExecuteXMLReader Executes the command and returns a XMLDataReader object, which you can use to read the results, one row at a time.
ExecuteScalar Executes the command, returns the first column of the first row in the result, and ignores all other rows.
ExecuteNonQuery Executes a SQL command against the database and returns the number of rows affected. Use this method to execute a command that updates the database.
The first two methods return a DataReader object, the ExecuteNonQuery method returns an integer (the number of rows affected), and the ExecuteScalar method returns an object (the first column of the first row in the result set). The DataReader is an abstract class and can’t be used in an application. Instead, use the SqlDataReader or the OleDbDataReader object, depending on the database you’re connected to.
VB.NET at Work: The DataReader Project
This section’s project demonstrates the simplest possible use of a SqlDataReader object. We’ll read the category names from the Categories table and place them on a ListBox control. To test this code, place an instance of the SqlConnection and SqlCommand controls on a new form. These two controls must be selected from the Data tab of the Toolbox. They’re not configured, because they’re not associated with any objects in the database. Let’s configure them.
Select the SqlConnection1 object on the designer and open its Properties window. Locate the ConnectionString property and, from the drop-down list, select the Northwind database. Then select the SqlCommand1 object on the designer, open its Properties window, and locate the Connection property. Expand the list of available connections and set it to SqlConnection1 (your project contains a single Connection object). Then locate the CommandText property and click the button with the ellipsis. This action will start the Query Builder, where you can create a query with the rows you want to retrieve. Add the Categories table to the query and select the fields CategoryID and CategoryName. Then click OK to close the Query Builder.
So far, you’ve established a connection to the Northwind database and created a command to retrieve the names of all categories. All you have do now is to execute the command and process its results.
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
958 Chapter 21 BUILDING DATABASE APPLICATIONS WITH ADO.NET
In our code we’ll use the ExecuteReader method to retrieve textual information. Place a Button and a ListBox control on the form, and enter the statements from Listing 21.12 in the button’s Click event handler.
Listing 21.12: Iterating the Rows of a SqlDataReader Object
Private Sub Button1_Click(ByVal sender As System.Object,_
ByVal e As System.EventArgs) Handles Button1.Click
SqlConnection1.Open()
Dim SQLReader As System.Data.SqlClient.SqlDataReader
SQLReader = SqlCommand1.ExecuteReader()
While SQLReader.Read
ListBox1.Items.Add(SQLReader.Item(“CategoryID”) & vbTab & _
SQLReader.Item(“CategoryName”))
End While
SqlConnection1.Close()
End Sub
First, the code opens the connection. Normally, the connection is opened by the DataAdapter object when the application requests data, but this time we don’t have a DataAdapter object. Then it calls the Command object’s ExecuteReader method and assigns the result to a SqlDataReader object. The data isn’t stored in this object. Instead, we’ll use this object to iterate through the rows returned by the query. This must take place from within a loop, which must go quickly through the data and then close the connection.
The Read method of the SqlDataReader object returns true while there is more data to be read. Each time you call the Read method, the DataReader moves to the next row, and you can read the current row’s fields through the Item property. The Item property accepts as argument the name or index of a column and returns its value. At each iteration of the loop, we read the two fields of the current row and add them to the ListBox control.
VB.NET at Work: The StoredProcedure Project
The example of this section shows a slightly different method of retrieving just the information you need to present to the user and no more. This time we’ll use one of the stored procedures we developed in Chapter 20. The OrdersPerCustomer stored procedure retrieves the number of orders placed by a customer and the grand total of these orders. This stored procedure doesn’t place any computational burden on the database server, because it applies to a single customer. What if the user wanted to see the totals for several, or even many, customers? Would you write another procedure that calculates the same totals for all customers? In a real database with thousands of customers and many orders per customer, the computational burden is no longer insignificant.
To avoid having the computer perform unnecessary operations and return results that the user may not even see, we’ll force users to select the customer whose totals they want. Figure 21.18 shows the interface of the application. The user is expected to select a customer from the list on the left, and then the selected customer’s totals will appear on the form. The totals are calculated as requested, which may lead to many requests to the database server. However, each request is serviced in no time
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
THE COMMAND AND DATAREADER OBJECTS 959
at all, and it’s certainly more efficient than calculating the totals for all customers and moving the information to the client; the user may only look at a few totals and just ignore the rest.
Figure 21.18
Retrieving information from the database with stored procedures
Start a new project and place on its form the controls you see in Figure 21.18. The ListBox control must be populated with the names of all the companies in the Customers table. Drop the Customers table from the Server Explorer onto the form and a new DataAdapter object will be added to the project. Rename the DataAdapter to DACustomers and configure it so that it retrieves only the CustomerID and CompanyName fields from the database. Here’s the DataAdapter’s SELECT statement:
SELECT CustomerID, CompanyName FROM dbo.Customers
Then generate the Customers DataSet and use it to populate the ListBox control. Set the control’s DataSource property to Customers1.Customers, its DisplayMember property to CompanyName, and ValueMember to CustomerID. Then call the DataAdapter’s Fill method from within the form’s Load event handler:
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
DACustomers.Fill(Customers1)
End Sub
If you run the application now, the ListBox will be populated with the customers’ names. In the control’s SelectedIndexChanged event handler, we must execute the OrdersPerCustomer procedure to retrieve the two totals for the selected customer. To execute the stored procedure, add a SqlCommand object to the form.
If you haven’t followed the examples in the previous chapter, you must add OrdersPerCustomer to the Northwind database (the application expects that the stored procedure is part of the database and will call it by name). To add a new stored procedure to a database, start Enterprise Manager, locate the Stored Procedures section under the Northwind database, and from its context menu select New Stored Procedure. Then add the definition shown in Listing 21.13 to the new stored procedure and save it as OrdersPerCustomer.
Listing 21.13: The OrdersPerCustomer Stored Procedure
ALTER PROCEDURE dbo.OrdersPerCustomer @CustomerID nchar(5)=’ALFKI’
AS
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
960 Chapter 21 BUILDING DATABASE APPLICATIONS WITH ADO.NET
SELECT |
dbo.Customers.CompanyName, |
|
COUNT(dbo.Orders.OrderID) AS [Total Orders], |
|
dbo.Customers.CustomerID, |
|
CAST(SUM((dbo.[Order Details].UnitPrice * |
|
dbo.[Order Details].Quantity) * |
|
(1 - dbo.[Order Details].Discount)) AS money) AS [Total Amount] |
FROM |
dbo.Customers |
|
INNER JOIN dbo.Orders |
|
ON dbo.Customers.CustomerID = dbo.Orders.CustomerID |
|
INNER JOIN dbo.[Order Details] |
|
ON dbo.Orders.OrderID = dbo.[Order Details].OrderID |
GROUP BY dbo.Customers.CompanyName, dbo.Customers.CustomerID |
|
HAVING |
(dbo.Customers.CustomerID = @CustomerID) |
|
|
Select the SqlCommand object from the Data tab of the Toolbox and drop it on the form. To execute the stored procedure, set the SqlCommand object’s CommandText property to the name of the stored procedure, set its CommandType to CommandType.StoredProcedure, and then create a new parameter and set it to the ID of the selected customer.
To set up a parameter, you must first create a Parameter object and add it to the Parameters collection of the SqlCommand1 object. The Parameter must be added to the collection only once. Then, you must set the properties of the Parameter object. The two most important properties of the Parameter object are the Direction and Value properties. The Direction property determines whether the parameter passes a value to the stored procedure, receives a value from the stored procedure, or both. Its value can be one of the members of the ParameterDirection enumeration: Input, Output, InputOutput, and ReturnValue. The parameter’s Value is the ID of the selected customer, which is given by the SelectedValue property of the ListBox control. The SelectedValue property returns the value of the field specified with the ValueMember property.
The last step is to execute the command and accept the results into a SqlReader object. Listing 21.14 shows the code that’s executed when the user selects a different customer in the ListBox control.
Listing 21.14: Executing a Stored Procedure with the SqlCommand Object
Private Sub ListBox1_SelectedIndexChanged(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles ListBox1.SelectedIndexChanged
SqlConnection1.Open()
SqlCommand1.CommandType = CommandType.StoredProcedure SqlCommand1.CommandText = “OrdersPerCustomer”
If SqlCommand1.Parameters.Count = 0 Then
SqlCommand1.Parameters.Add(New _ System.Data.SqlClient.SqlParameter(“@CustomerID”, SqlDbType.NChar))
SqlCommand1.Parameters(0).Direction = ParameterDirection.Input End If
SqlCommand1.Parameters(0).Value = ListBox1.SelectedValue.ToString Dim SQLReader As System.Data.sqlclient.SqlDataReader
SQLReader = SqlCommand1.ExecuteReader
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |