Sample Content from Programming Microsoft ADO.NET 2.0 ... · The DataGridView control is used on...

41
Programming Microsoft ® ADO.NET 2.0 Applications Advanced Topics Glenn Johnson To learn more about this book, visit Microsoft Learning at http://www.microsoft.com/MSPress/books/7720.aspx 9780735621411 Publication Date: November 2005

Transcript of Sample Content from Programming Microsoft ADO.NET 2.0 ... · The DataGridView control is used on...

Programming Microsoft®

ADO.NET 2.0 Applications Advanced Topics

Glenn Johnson

To learn more about this book, visit Microsoft Learning at http://www.microsoft.com/MSPress/books/7720.aspx

9780735621411 Publication Date: November 2005

A05T621411.fm Page vii Tuesday, November 22, 2005 8:50 PM

Table of Contents

Foreword. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv

Acknowledgments. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvii

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix

Who This Book Is For . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix

How This Book Is Organized. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix

Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix

System Requirements. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xx

SQL Server 2005 vs. SQL Server 2005 Express Edition . . . . . . . . . . . . . . . . . . . . xx

Configuring SQL Server 2005 Express Edition . . . . . . . . . . . . . . . . . . . . . . . . . . xxii

Prerelease Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiii

Technology Updates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiii

Code Samples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiv

Support for This Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiv

Questions and Comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiv

1 Overview of ADO.NET Disconnected Classes. . . . . . . . . . . . . . . . . . . . . . . .1

Getting Started with the DataTable Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2

Adding DataColumn Objects to Create a Schema . . . . . . . . . . . . . . . . . . . . . . . . 2

Creating Primary Key Columns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

Creating DataRow Objects to Hold Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

Enumerating the DataTable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

Copying and Cloning the DataTable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

Using the DataTable with XML Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

Using the DataView as a Window into a DataTable . . . . . . . . . . . . . . . . . . . . 15

Using a DataSet Object to Work with Lots of Data . . . . . . . . . . . . . . . . . . . . . . 18

Being More Specific with Typed DataSet Objects . . . . . . . . . . . . . . . . . . . . . . . 20

Navigating the Family Tree with DataRelation Objects . . . . . . . . . . . . . . . . . . . 21

Serializing and Deserializing DataSet Objects . . . . . . . . . . . . . . . . . . . . . . . . . . 25

Using Merge to Combine DataSet Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

Looping Through Data with the DataTableReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

vii

What do you think of this book?We want to hear from you!

Microsoft is interested in hearing your feedback about this publication so we can continually improve our books and learning resources for you. To participate in a brief online survey, please visit: www.microsoft.com/learning/booksurvey/

viii Table of Contents

A05T621411.fm Page viii Tuesday, November 22, 2005 8:50 PM

2 Overview of ADO.NET Connected Classes . . . . . . . . . . . . . . . . . . . . . . . . 39

Using Providers to Move Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

Getting Started with the DbConnection Object . . . . . . . . . . . . . . . . . . . . . . . . . 40

DbCommand Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

DbDataReader Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

Using Multiple Active Result Sets (MARS) to Execute Multiple Commands on a Connection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

Performing Bulk Copy Operations with the SqlBulkCopy Object . . . . . . . . . . 58

DbDataAdapter Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60

DbProviderFactory Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

DbProviderFactories Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69

Enumerating Data Sources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71

Using DbException to Catch Provider Exceptions . . . . . . . . . . . . . . . . . . . . . . . 73

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73

3 ADO.NET Trace Logging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

Setting Up Tracing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

Using the logman.exe Utility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

Performance Logs And Alerts Snap-in . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80

Working with Event Trace Log Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82

Using the LogParser Utility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85

Using Tracing as a Diagnostic Tool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87

4 Advanced Connectivity to the Data Store . . . . . . . . . . . . . . . . . . . . . . . . 89

Building Accurate Connection Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89

Provider-Independent Data Access. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92

Connection Pooling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

Creating and Opening Connections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

Where’s the Pool? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96

When Is the Pool Created?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96

How Long Will the Connection Stay in the Pool? . . . . . . . . . . . . . . . . . . . . . . . 96

Exceeding the Pool Size . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97

When to Turn Off Pooling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100

Clearing the Pool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100

Working with a Failover Partner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103

Asynchronous Data Access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

Synchronous vs. Asynchronous Access. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

Table of Contents ix

A05T621411.fm Page ix Tuesday, November 22, 2005 8:50 PM

Working with SQL Server Provider Statistics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113

5 Working with Disconnected Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115

Understanding Concurrency Issues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115

Resolving Concurrency Conflicts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117

Designing for Disconnected Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

What Data Should Be Loaded? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

Choosing the Primary Key . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120

Who’s Afraid of the Big, Bad GUID?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126

Copying/Pasting GUIDs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126

Using the Same Name for the Primary Key Column on Non-Join Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127

Finding a GUID in the Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127

Finding All Usages of a GUID in the Database . . . . . . . . . . . . . . . . . . . . . . . . 128

Building a Conflict Resolution Screen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129

Creating the Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129

Extending the Typed DataSet (CustomerDataSet) Class . . . . . . . . . . . . . . . . . 131

Extending the TableAdapter (TblCustomerTableAdapter) Class to Expose the ContinueUpdateOnError Property . . . . . . . . . . . . . . . . . . . . . . . 132

Synchronizing the Disconnected DataSet with the Database Server . . . . . . 133

Creating the Conflict Resolution Screen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134

Calling the Conflict Resolution Screen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139

Correcting Concurrency Errors with the Conflict Resolution Screen . . . . . . . 141

Building a Better Conflict Resolution Screen . . . . . . . . . . . . . . . . . . . . . . . . . 144

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145

6 Working with Relational Disconnected Data . . . . . . . . . . . . . . . . . . . . . 147

Navigating Relationships. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147

Creating Constraints. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149

Updating Data: The Beginning of the Data Access Layer . . . . . . . . . . . . . . . . . . . . . . 151

Retrieving the Relationships . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151

Retrieving the List of Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154

Ordering the Table List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154

Using the OrderedTableList to Perform Updates . . . . . . . . . . . . . . . . . . . . . . . 162

Testing the Relational Update . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167

DAL Update Caveats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169

x Table of Contents

A05T621411.fm Page x Tuesday, November 22, 2005 8:50 PM

7 Working with the Windows Data Grid Control . . . . . . . . . . . . . . . . . . . 171

Understanding the DataGridView Control. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171

Formatting with Styles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173

DataGridView Modes of Operation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173

Binding to a Data Source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173

Resource Sharing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174

DataGridView Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175

Working with Cell Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177

Working with DataGridViewColumn Objects . . . . . . . . . . . . . . . . . . . . . . . . . . 181

Working with DataGridViewRow Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190

Implementing Virtual Mode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202

8 Working with the Web Data Grid Control . . . . . . . . . . . . . . . . . . . . . . . 203

Understanding the GridView Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203

Formatting with Styles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204

Binding to a Data Source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205

GridView Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207

Viewing the Declarative Markup in the HTML Source. . . . . . . . . . . . . . . . . . . 211

Creating the GridView Object Programmatically . . . . . . . . . . . . . . . . . . . . . . . 213

Working with the GridView Object Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219

Working with Column Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239

9 Working with the SQLCLR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241

Does the SQLCLR Replace T-SQL? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241

Creating a Stored Procedure Without Visual Studio . . . . . . . . . . . . . . . . . . . . . . . . . . 243

Enabling the SQLCLR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243

Creating the Source Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244

Using the Context Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244

Compiling the Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245

Loading the Assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245

Changing the Execution Permission . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246

Registering the Stored Procedure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246

Executing the Stored Procedure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247

Refreshing the Assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248

Viewing Installed Assemblies and Their Permissions . . . . . . . . . . . . . . . . . . . . 248

Using Parameters to Transfer Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248

Table of Contents xi

A05T621411.fm Page xi Tuesday, November 22, 2005 8:50 PM

Creating a Stored Procedure by Using Visual Studio . . . . . . . . . . . . . . . . . . . . . . . . . 250

Passing Rowset Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252

Passing Data as a Produced Rowset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252

Passing Data from a Database Rowset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259

Creating User-Defined Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261

Using Scalar Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262

Using a Streaming Table-Valued Function (TVF) . . . . . . . . . . . . . . . . . . . . . . . 264

Working with User-Defined Aggregates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268

Working with Triggers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271

Transactions in Triggers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273

Working with User-Defined Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274

When Not to Use a UDT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280

When to Use a UDT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280

Accessing SQLCLR Features from the Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287

10 Understanding Transactions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289

What Is a Transaction? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289

Concurrency Models and Database Locking . . . . . . . . . . . . . . . . . . . . . . . . . . 289

Transaction Isolation Levels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290

Single Transactions and Distributed Transactions . . . . . . . . . . . . . . . . . . . . . . 291

Creating a Transaction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292

Creating a Transaction Using T-SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292

Creating a Transaction Using the ADO.NET DbTransaction Object . . . . . . . . 292

Setting the Transaction Isolation Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294

Introducing the System.Transactions Namespace . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296

Creating a Transaction Using the TransactionScope Class . . . . . . . . . . . . . . . . 296

Setting the Transaction Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298

Working with Distributed Transactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300

Building Your Own Transactional Resource Manager . . . . . . . . . . . . . . . . . . . 305

Using System.Transactions with the SQLCLR . . . . . . . . . . . . . . . . . . . . . . . . . . . 316

Best Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317

11 Retrieving Metadata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319

Getting Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320

Retrieving the Metadata Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323

Navigating the Schema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325

Navigating a Metadata Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326

xii Table of Contents

A05T621411.fm Page xii Tuesday, November 22, 2005 8:50 PM

Working with the Restrictions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328

Changing and Extending the Metadata. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334

Understanding the Unique Identifier Parts . . . . . . . . . . . . . . . . . . . . . . . . . . . 338

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339

12 Data Caching for Performance. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341

Using the SqlDependency Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341

What to Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341

Is the SqlDependency Class for You? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342

How Does SqlDependency Work? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343

Query Considerations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344

SqlDependency Setup in SQL Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344

Using the SqlDependency Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345

Selecting the Communication Transport . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349

ASP.NET SQL Cache Invalidation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349

Cache Invalidation by Polling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349

Preparing SQL Server for Polling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349

Creating a Web Site That Uses Polling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351

Testing the Application Before Enabling Polling. . . . . . . . . . . . . . . . . . . . . . . . 352

Enabling Polling in the Web Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352

Testing the Application with Polling Enabled . . . . . . . . . . . . . . . . . . . . . . . . . . 353

Cache Invalidation by Command Notification . . . . . . . . . . . . . . . . . . . . . . . . . 354

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357

13 Implementing Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359

Application Security Overview. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359

Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359

Authorization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360

Impersonation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361

Delegation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361

Role-Based Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363

Code Access Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365

SQL Server Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382

SQL Server Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383

SQL Server Authorization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385

ADO.NET Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386

Partial Trust Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386

Storing Encrypted Connection Strings in Web Applications. . . . . . . . . . . . . . 390

Table of Contents xiii

A05T621411.fm Page xiii Tuesday, November 22, 2005 8:50 PM

Preventing SQL Injection Attacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392

Using Stored Procedures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399

14 Working with Large Objects (LOBs, BLOBs, and CLOBs) . . . . . . . . . . . 401

What Are LOBs, BLOBs, and CLOBs? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401

Where Should LOBs Be Stored? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402

Working with LOBs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402

Reading BLOB Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402

Writing BLOB Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412

15 Working with XML Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413

Introducing XPath and XQuery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413

Why Store XML Data in SQL Server 2005? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414

The xml Data Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415

Using the Schema Collection to Implement “Typed” xml Columns . . . . . . . 415

Retrieving and Modifying XML Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416

Indexing the xml Column . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416

Getting Started with the xml Data Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417

Using the query Method with XPath . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418

Using the query Method with XQuery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425

Using the exist Method with XQuery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447

Using the modify Method to Change Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . 448

Using the nodes Method to Change Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . 453

Indexing the xml Column . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 460

Using XML with ADO.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461

Getting Started with the SqlXml Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 462

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473

Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475

What do you think of this book?We want to hear from you!

Microsoft is interested in hearing your feedback about this publication so we can continually improve our books and learning resources for you. To participate in a brief online survey, please visit: www.microsoft.com/learning/booksurvey/

A05T621411.fm Page xiv Tuesday, November 22, 2005 8:50 PM

C07621411.fm Page 171 Tuesday, November 22, 2005 8:58 PM

Chapter 7

Working with the Windows Data Grid Control

We’re always looking for a way to display data in a manner that will allow the user to quickly browse and change data. Every version of Microsoft Visual Studio has had a data grid control that is meant to provide easy browse and change capability with minimal effort by the devel-oper. This chapter will examine the Windows data grid control, called the DataGridView, which is available in Visual Studio 2005; the next chapter will examine the Web data grid con-trol, called the GridView.

Understanding the DataGridView ControlThe DataGridView control is used on Windows Forms applications to display data in a tabu-lar, rows-and-columns format. Earlier grid controls were difficult to customize without diving deep into Windows handles and messages, but the DataGridView object exposes more than 160 events to make it much simpler to access the key area of the grid.

The basic structure of the DataGridView object is shown in Figure 7-1. The DataGridView object consists of collections of rows and columns. The Rows property provides a collection of DataGridViewRow objects. Each DataGridViewRow object contains a Cells collection, which is a collection of objects that derive from the DataGridViewCell class. The Columns property pro-vides a collection of objects that derive from the DataGridViewColumn class. Although the DataGridViewRow object holds the collection of cells, each DataGridViewColumn provides an instance of a specific cell that is used as a template. The DataGridViewRow object can access the cell template to create clones of the correct cell type.

171

172 Programming Microsoft ADO.NET 2.0 Applications: Advanced Topics

C07621411.fm Page 172 Tuesday, November 22, 2005 8:58 PM

Figure 7-1 The basic structure of the DataGridView object

What do I mean by correct cell type? Figure 7-2 shows the class hierarchy of the DataGrid-ViewCell. With the exception of the header types, the class hierarchy of the DataGridViewCol-umn class is similar to that of the DataGridViewCell class. You don’t define cell types for your DataGridView object—you define columns, and each column object supplies a cell object to the row; the row uses the cells as templates for creating the proper cell types for the row.

Figure 7-2 The DataGridViewCell class hierarchy

Column

Rows

Cell

Cell

Cell

Cell

Cell

Cell

Cell

Cell

Cell

Cell

Cell

Cell

Column Column

Columns

Cells

Cells

Cells

Cells

DataGridView

Chapter 7: Working with the Windows Data Grid Control 173

C07621411.fm Page 173 Tuesday, November 22, 2005 8:58 PM

Formatting with Styles

You use styles to format the DataGridView object. You can assign styles to individual cells, rows, or columns or to the whole DataGridView object. The effective style of a cell is derived by evaluating from the least specific style (the DataGridView object) to the most specific style (the cell style). The style properties are additive, which means that when the BackColor prop-erty is set to red on the DataGridView object and the Font is set to Arial on the cell, the cell ren-ders with the Arial font and a red background. When there is a conflict, such as different BackColor settings, the most specific setting prevails.

You can use the CellFormatting event to control the style programmatically. The benefit is that you can apply business rules to specify whether a cell should stand out from other cells (for example, making the negative “quantity on hand” number red, but only when the inventory item sells more than one item per month).

DataGridView Modes of Operation

The DataGridView supports three modes of operation: bound, unbound, and virtual. You are in bound mode when you bind, or connect, the DataGridView object to a data source. You are in unbound mode when you simply populate the row data programmatically. It is also possi-ble to be in a “mixed” mode, where you bind to a data source but also add one or more unbound columns to the DataGridView object.

Virtual mode allows you to control the DataGridView object’s interaction with your data cache. The primary benefit of virtual mode is that you can implement just-in-time data loading when you have large amounts of data to make available to the user but the user can select the data to be viewed. In other words, rather than filling a DataSet object with a couple of gigabytes of data, you can simply populate the DataSet with the data that is necessary when the user’s interaction causes a request for specific data.

Binding to a Data Source

The DataGridView can be easily bound to a data source at design time; Visual Studio will read your data source and populate the columns collection. You can select or create the data source by using the Choose Data Source dialog box.

The data source can be one of the following:

■ Database You can bind to an ODBC, OLEDB, SQL Server, Oracle, Microsoft Access, or other data source. You can even attach a SQL Server database file by simply including it in your project.

■ Web Service You can browse to a Web service on your machine, you can locate UDDI (Universal Description, Discovery, and Integration) servers on the local area network, or you can use Microsoft’s UDDI servers on the Web. This option opens the Add Web Ref-

174 Programming Microsoft ADO.NET 2.0 Applications: Advanced Topics

C07621411.fm Page 174 Tuesday, November 22, 2005 8:58 PM

erence dialog box, where you can create a connection to a Web service that returns the data for your application.

■ Object This option lets you choose an object that can be used to bind to a DataGrid-View object or other data-bound controls.

Resource Sharing

The DataGridView object attempts to minimize resource usage by sharing DataGridViewRow instances wherever possible. A row can be shared if the state of all of its cells can be deter-mined based on the state of the rows and columns that contain the cells. The state of a cell refers to a cell’s appearance and behavior, which is typically derived from the column and row that the cell belongs to. Changing the state of a cell at the cell level so that its state cannot be determined via the row and column it belongs to causes a shared row to become unshared.

Here are some reasons that one or more rows can be unshared or can become unshared.

■ Editing a cell in a row.

■ Selecting a single cell in a row, where the cell’s column is not selected.

■ Assigning a ToolTipText or a ContextMenuStrip to a cell in the row.

■ Populating the Items property of a DataGridViewComboBoxCell in the row.

■ Creating an unbound row will always create an unshared row because the cell data is contained in the cell. You create unbound rows by calling the DataGridViewRowCollec-tion.Add(System.Object[]) method or the DataGridViewRowCollection.Insert(Sys-tem.Object[]) method.

■ Using a “for each” command to enumerate the Rows collection of a DataGridView object causes the rows to become unshared. Use a “for” loop with an index number instead.

■ Accessing a cell by using the Cells collection on the DataGridView object. When you work with the DataGridView events, the event arguments pass cell information that you can use to access the cell without causing the row to become unshared.

■ Setting the ReadOnly property of a cell to false when the ReadOnly property of its column is set to true causes all rows to become unshared.

■ Accessing the List property of the DataGridViewRowCollection causes all rows to become unshared.

■ Calling the Sort method on the DataGridView with a custom IComparer causes all rows to become unshared. If you don’t use a custom IComparer, calling the Sort method is OK.

Chapter 7: Working with the Windows Data Grid Control 175

C07621411.fm Page 175 Tuesday, November 22, 2005 8:58 PM

■ Calling the AreAllCellsSelected method on the DataGridView object using the overload that accepts a Boolean to include invisible cells causes all rows to become unshared. Calling other versions of AreAllCellsSelected is OK.

■ Accessing the SelectedCells property of a DataGridView object when the selection mode is set to FullColumnSelect, ColumnHeaderSelect, FullRowSelect, or RowHeaderSelect causes all rows to become unshared.

DataGridView Setup

The following examples use the Employees table in the Northwind database; the Northwind database base files (Northwnd.mdf and Northwnd.ldf) were simply included in the project to create a typed DataSet.

Before working with a DataGridView object, you must configure a DataSource. For example, you can configure a DataSource by including a database in your project. When the Northwind database is added to the project, the Data Source Configuration Wizard starts. The first page prompts for the list of tables, views, functions, and stored procedures that should be included in the DataSource. Figure 7-3 shows all of the tables being selected except the sysdiagrams table because the sysdiagrams table holds metadata that is used by the database diagram tool. Also notice that the name of the DataSet to be created has been changed from northwnd-DataSet to NorthwindDs.

Figure 7-3 To set up the Northwind DataSource, you simply add the Northwind database to the project, which will start the Data Source Configuration Wizard.

After you create the DataSource, you can simply drag and drop the Employees table onto the form, which will add a DataGridView object to the form. The other objects that have been added to the form are as follows. These objects are shown in Figure 7-4.

176 Programming Microsoft ADO.NET 2.0 Applications: Advanced Topics

C07621411.fm Page 176 Tuesday, November 22, 2005 8:58 PM

■ northwindDs An instance of the NorthwindDs typed DataSet that contains a populated Employees DataTable object.

■ employeesBindingSource An instance of the BindingSource class, which acts as a Data-Source for the Employees data. The BindingSource object simplifies the connection, or binding, of the data to one or more controls on the Windows Form.

■ employeesTableAdapter An instance of the EmployeesTableAdapter class, which is used to populate the Employees DataTable object. The employeesTableAdapter object is also used to send changes back to the database server.

■ employeesBindingNavagator An instance of the BindingNavigator class. The BindingNav-igator object is a GUI control that allows you to move through a data source.

When the DataGridView is dropped onto a Windows form, the DataGridView Tasks window is displayed (Figure 7-4). This window gives you quick access to some of the common tasks for setting up the DataGridView quickly. Notice the little arrow button attached to the upper left corner of the DataGridView Tasks window, which you can use to hide or display the window.

Figure 7-4 These objects are added to the tray when the Employees table is dragged onto the form.

Chapter 7: Working with the Windows Data Grid Control 177

C07621411.fm Page 177 Tuesday, November 22, 2005 8:58 PM

Working with Cell Events

A cell can generate many events, which are bubbled up to the DataGridView object. These events substantially increase the flexibility of the DataGridView over data grid controls of the past. Table 7-1 lists the available cell events.

Table 7-1 Cell Events Available on the DataGridView Object

Event Description

CellBeginEdit A cell has entered edit mode.

CellBorderStyleChanged The border style was changed for a cell.

CellClick Any part of the cell has been clicked using the left mouse button.

CellContentClicked The content of the cell has been clicked.

CellContentDoubleClick The content of the cell has been double-clicked.

CellContextMenuStripChanged The shortcut menu associated with a cell has been changed.

CellContextMenuStripNeeded A shortcut menu is needed for a cell.

CellDoubleClick Any part of a cell has been double-clicked using the left mouse button.

CellEndEdit The currently selected cell has exited edit mode.

CellEnter A cell has received focus and is becoming the currently se-lected cell.

CellErrorTextChanged The error text of a cell has changed.

CellErrorTextNeeded The error text of a cell is needed. At this time, the error text can be read or changed before it is displayed.

CellFormatting A cell that is to be displayed needs to be formatted.

CellLeave A cell has lost focus and is no longer the currently selected cell.

CellMouseClick A mouse click has taken place anywhere on a cell using any mouse button. Use the CellClick event if you are only inter-ested in left mouse button clicks.

CellMouseDoubleClick A mouse double-click has taken place anywhere on a cell using any mouse button. Use the CellDoubleClick event if you are only interested in left mouse button double-clicks.

CellMouseDown A mouse button was pressed while the mouse pointer was within a cell.

CellMouseEnter The mouse pointer just entered a cell.

CellMouseLeave The mouse pointer just left a cell.

CellMouseMove The mouse pointer was moved over a cell.

178 Programming Microsoft ADO.NET 2.0 Applications: Advanced Topics

C07621411.fm Page 178 Tuesday, November 22, 2005 8:58 PM

You can view a list of the DataGridView object’s events by clicking the DataGridView object and then clicking the event lightning bolt icon in the Properties window (Figure 7-5). You can add event handler stub code for any of the events by simply double-clicking the event.

CellMouseUp A mouse button was released while the mouse pointer was within a cell.

CellPainting A cell needs to be drawn.

CellParsing A cell (either modified or not) is leaving edit mode.

CellStateChanged A cell state has changed, typically to Selected or None.

CellStyleChanged A style has been assigned to a cell. Note that this event does not fire if a property of the style, such as BackColor, chang-es.

CellStyleContentChanged One of the properties of the style, such as BackColor, has changed.

CellToolTipTextChanged A cell’s ToolTipText has changed.

CellToolTipTextNeeded A cell’s ToolTipText is needed. At this time, the ToolTipText can be viewed or changed before it is displayed.

CellValidated A cell has finished validation.

CellValidating A cell is being validated, and the validation can be canceled.

CellValueChanged The value of the cell has been changed.

CellValueNeeded A value is needed for the cell to be formatted and dis-played. This event fires only if DataGridView.VirtualMode is true.

CellValuePushed A cell value has changed and needs to be stored in the un-derlying data source. This event fires only if DataGrid-View.VirtualMode is true.

CurrentCellChanged A different cell is becoming the current cell.

CurrentCellDirtyStateChanged The current cell’s value has changed (for example, if a cell is in edit mode and a single character is typed). This event fires again when the dirty state has been cleared.

DefaultCellStyleChanged A style has been assigned to the DataGridView object’s DefaultCellStyle property.

Table 7-1 Cell Events Available on the DataGridView Object

Event Description

Chapter 7: Working with the Windows Data Grid Control 179

C07621411.fm Page 179 Tuesday, November 22, 2005 8:58 PM

Figure 7-5 DataGridView object events

Event Sequence

Here are the events that take place when a cell is entered (the Current Cell) and edited, and then another cell is clicked (the Next Cell).

1. CellStateChanged (to Selected): Current Cell

2. CellEnter: Current Cell

3. CellFormatting: Current Cell

4. CellClick: Current Cell

5. CellMouseClick: Current Cell

6. CellFormatting: Current Cell

7. CellBeginEdit: Current Cell

8. CellFormatting: Current Cell

Click to view object events

Double-click to addhandler stub code

180 Programming Microsoft ADO.NET 2.0 Applications: Advanced Topics

C07621411.fm Page 180 Tuesday, November 22, 2005 8:58 PM

9. EditingControlShowing: Current Cell

10. CellDirtyStateChanged (to dirty): A character was typed into Current Cell

11. CellMouseLeave: Current Cell

12. CellMouseEnter: Next Cell

13. CellFormatting: Next Cell

14. CellMouseDown: Next Cell

15. CellLeave: Current Cell

16. CellValidating: Current Cell

17. CellParsing: Current Cell

18. CellValueChanged: Current Cell

19. CellDirtyStateChanged (to clear): Current Cell

20. CellValidated: Current Cell

21. CellFormatting: Current Cell

22. CellStateChanged (to None): Current Cell

23. CellStateChanged (to Selected): Next Cell

24. CellEndEdit: Current Cell

Now that you have a table of the cell events (Table 7-1) and the preceding sequential list of events, you can code the necessary event handlers to customize the DataGridView object. For example, if you want to keep a user from changing from one cell to another when the user does not enter an appropriate value into the first cell, you can set the Cancel property to true in the CellValidating event handler, as shown in the following code snippet.

Visual BasicPrivate Sub EmployeesDataGridView_CellValidating( _

ByVal sender As System.Object, _

ByVal e As System.Windows.Forms.DataGridViewCellValidatingEventArgs) _

Handles EmployeesDataGridView.CellValidating

Debug.WriteLine("Cell Validating: " _

+ e.RowIndex.ToString() + ", " + e.ColumnIndex.ToString())

'Check Last Name to see if last name has at least 1 character

If (e.ColumnIndex = 1 _

And e.FormattedValue.ToString().Trim().Length = 0) Then

e.Cancel = True

End If

End Sub

C#private void employeesDataGridView_CellValidating(

object sender, DataGridViewCellValidatingEventArgs e)

{

Chapter 7: Working with the Windows Data Grid Control 181

C07621411.fm Page 181 Tuesday, November 22, 2005 8:58 PM

Debug.WriteLine("Cell Validating: "

+ e.RowIndex.ToString() + ", " + e.ColumnIndex.ToString());

//Check to see if last name has at least 1 character

if (e.ColumnIndex==1 //Last Name

&& e.FormattedValue.ToString().Trim().Length==0)

{

e.Cancel = true;

}

}

When the code is run and the user attempts to change the last name to an empty string or a string that consists of spaces, the Cancel property will be set to true and the user will not be allowed to leave the cell. Notice that the events will take place in the following order.

1. CellLeave: Current Cell

2. CellFormatting: Current Cell

3. CellValidating: Current Cell

4. CellEnter: Current Cell

You can see that the CellLeave event fired, and after CellValidating set the Cancel property to true, the CellEnter event fired as the user was placed back into the cell.

Working with DataGridViewColumn Objects

The DataGridView object has a Columns collection, which contains objects that inherit from the DataGridViewColumn class. The Microsoft .NET Framework contains several column types, but you can also create your own column types. Figure 7-6 shows the DataGridView-Column class hierarchy, with the available column types.

Figure 7-6 The DataGridViewColumn class hierarchy

182 Programming Microsoft ADO.NET 2.0 Applications: Advanced Topics

C07621411.fm Page 182 Tuesday, November 22, 2005 8:58 PM

Editing the Column List

The DataGridView designer lets you add and remove columns by using the Edit Columns dia-log box. You can also populate the columns collection at runtime using your own code.

Working with Column Events

Many column-related events are available on the DataGridView object, as shown in Table 7-2. Notice that some of the events are based on individual column objects, while other events are based on a global column-related property being changed on the DataGridView object.

Table 7-2 Column Events on the DataGridView Object

Event Description

AllowUserToOrderColumnsChanged The AllowUserToOrderColumns property of the Data-GridView object has been changed.

AllowUserToResizeColumnsChanged The AllowUserToResizeColumns property of the Data-GridView object has been changed.

AutoSizeColumnModeChanged The AutoSizeMode property on a column object has been changed.

AutoSizeColumnsModeChanged The AutoSizeColumnsMode property of the DataGrid-View object has been changed.

ColumnAdded A column has been added to the DataGridView object.

ColumnContextMenuStripChanged A column object’s ContextMenuStrip property has been changed.

ColumnDataPropertyNameChanged The DataPropertyName property of a column object has been changed.

ColumnDefaultCellStyleChanged The DefaultCellStyle property of a column object has been changed.

ColumnDisplayIndexChanged The DisplayIndex property of a column object has been changed.

ColumnDividerDoubleClick A user double-clicked the divider between two col-umns.

ColumnDividerWidthChanged The width of a column divider changed by setting the DividerWidth property on the column.

ColumnHeaderCellChanged A column object’s header cell contents have been changed.

ColumnHeaderMouseClick A user clicked a column object’s header.

ColumnHeaderMouseDoubleClick A user double-clicked a column object’s header.

ColumnHeadersBorderStyleChanged The ColumnHeadersBorderStyle property of the Data-GridView object has been changed.

ColumnHeadersDefaultCellStyleChanged The ColumnHeadersDefaultCellStyle property of the DataGridView object has been changed.

Chapter 7: Working with the Windows Data Grid Control 183

C07621411.fm Page 183 Tuesday, November 22, 2005 8:58 PM

Using the DataGridViewTextBoxColumn

The default column type for string and numeric data is the DataGridViewTextBoxColumn, which supplies a DataGridViewTextBoxCell, as a cell template, to a row that is being created. The row queries the column for the cell template and clones the cell, which ensures that the template is not modified by the row.

The DataGridViewTextBoxCell simply renders data as text and accepts text input. For a given DataGridViewTextBoxColumn object, there is one DataGridViewTextBoxCell object for each Data-GridViewRow object in the DataGridView object. When a cell becomes activated, a DataGridView-TextBoxEditingControl object is supplied so that the application user can edit the cell, provided that the cell’s ReadOnly property is set to false.

Using the DataGridViewCheckBoxColumn

The default column type for Boolean data (Bit data type in SQL Server) is the DataGrid-ViewCheckBoxColumn, which supplies a DataGridViewCheckBoxCell, as a cell template, to a row that is being created.

The DataGridViewCheckBoxCell renders data in a CheckBox and lets the user edit the value by selecting or deselecting the CheckBox. For a given DataGridViewCheckBoxColumn object, there is one DataGridViewCheckBoxCell object for each DataGridViewRow object in the DataGrid-View object. The DataGridViewCheckBoxColumn has a property called ThreeState that can be set to true to allow the CheckBox to have a selected (true), deselected (false), or indeterminate (null) state.

ColumnHeaderHeightChanged The ColumnHeaderHeight property of the DataGrid-View object has been changed.

ColumnHeaderHeightSizeModeChanged The ColumnHeaderHeightSizeMode property of the DataGridView object has been changed.

ColumnMinimumWidthChanged The ColumnMinimumWidth property on a column ob-ject has been changed.

ColumnNameChanged The Name property on a column has been changed.

ColumnRemoved A column object has been removed from the DataGrid-View object.

ColumnSortModeChanged The SortMode property of a column object has been changed.

ColumnStateChanged A column object’s state has changed (for example, from Displayed or None to indicate whether the column is currently being displayed).

ColumnToolTipTextChanged The ToolTipText property of a column object has been changed.

ColumnWidthChange The Width property of a column has been changed.

Table 7-2 Column Events on the DataGridView Object

Event Description

184 Programming Microsoft ADO.NET 2.0 Applications: Advanced Topics

C07621411.fm Page 184 Tuesday, November 22, 2005 8:58 PM

Probably the most common event you will be interested in is the CellContentClick event, which fires when the user clicks in the CheckBox to change the selected state.

Using the DataGridViewImageColumn

The DataGridViewImageColumn can be used to display images and icons in the DataGridView object. By default, the Visual Studio Data Source designer does not assign a data source image field to a DataGridView column, so images are not displayed, but a DataGridViewImageColumn can be added to the DataGridView object for displaying image and icon data. The DataGrid-ViewImageColumn supplies a DataGridViewImageCell as a cell template to a row that is being created.

The DataGridViewImageCell renders data directly into the cell. Figure 7-7 shows the photos from the Employees table rendered using a DataGridViewImageColumn object. In this example, the AutoResizeRowsMode of the EmployeeDataGridView object and the AutoSizeMode of the DatagridViewImageColumn have been set to DisplayedCells.

Figure 7-7 The Employees table contains a Photo column that can be rendered using the DataGridViewImageCell.

Loading a New Image into the DataGridViewImageCell from a File The DataGrid-ViewImageColumn easily displays an image, but if you want to replace the image with a new image, or if you are adding a new employee, you’ll need to add some code to get the ability to replace the image. Using the current example project as shown in Figure 7-7, add a Context-MenuStrip to the form and set its Name property to PhotoMenu. Next, add a menu item and set the Text property to “&Insert Photo From File”. Assign the PhotoMenu to the ContextMenuStrip property of the Photo column.

Add an event handler for the click event of the “&Insert Photo From File” menu item. Add the following code, which prompts the user for an image file and loads it.

Chapter 7: Working with the Windows Data Grid Control 185

C07621411.fm Page 185 Tuesday, November 22, 2005 8:58 PM

Visual BasicDim currentPhotoCell as DataGridViewImageCell

Private Sub InsertPhotoFromFileToolStripMenuItem_Click( _

ByVal sender As System.Object, ByVal e As System.EventArgs) _

Handles InsertPhotoFromFileToolStripMenuItem.Click

dim dlg as new OpenFileDialog()

dlg.Multiselect = false

dlg.Filter = "GIF|*.gif|BMP|*.bmp|JPEG|*.jpg;*.jpeg|All Files|*.*"

dlg.FilterIndex = 3

dlg.Title = "Select a photo to insert"

if Windows.Forms.DialogResult.OK <> dlg.ShowDialog() then return

Dim bmp as new Bitmap(dlg.FileName)

currentPhotoCell.Value = bmp

End Sub

C#DataGridViewImageCell currentPhotoCell;

private void insertPhotoToolStripMenuItem_Click(object sender, EventArgs e)

{

OpenFileDialog dlg = new OpenFileDialog();

dlg.Multiselect = false;

dlg.Filter = "GIF|*.gif|BMP|*.bmp|JPEG|*.jpg;*.jpeg|All Files|*.*";

dlg.FilterIndex = 3;

dlg.Title = "Select a photo to insert";

if (DialogResult.OK != dlg.ShowDialog()) return;

Bitmap bmp = new Bitmap(dlg.FileName);

currentPhotoCell.Value = bmp;

}

Most of this code simply sets up the dialog box to prompt for an image file. The file is loaded into a new Bitmap object, and the Bitmap object is assigned to the currentPhotoCell object’s Value property.

The only problem with this code is that the currentPhotoCell variable has not been set. You can make currentPhotoCell point to the cell that was right-clicked, by adding an event handler for the CellContextMenuStripNeeded event of the employeesDataGridView object and adding the following code.

Visual BasicPrivate Sub EmployeesDataGridView_CellContextMenuStripNeeded( _

ByVal sender As System.Object, _

ByVal e As _

System.Windows.Forms.DataGridViewCellContextMenuStripNeededEventArgs) _

Handles EmployeesDataGridView.CellContextMenuStripNeeded

Dim dg as DataGridView = CTYpe(sender,DataGridView)

if typeof dg.Columns(e.ColumnIndex) is DataGridViewImageColumn then

currentPhotoCell = _

ctype(dg.Rows(e.RowIndex).Cells(e.ColumnIndex),DataGridViewImageCell)

end if

End Sub

186 Programming Microsoft ADO.NET 2.0 Applications: Advanced Topics

C07621411.fm Page 186 Tuesday, November 22, 2005 8:58 PM

C#private void employeesDataGridView_CellContextMenuStripNeeded(

object sender, DataGridViewCellContextMenuStripNeededEventArgs e)

{

DataGridView dg = (DataGridView)sender;

if (dg.Columns[e.ColumnIndex] is DataGridViewImageColumn)

{

currentPhotoCell =

(DataGridViewImageCell)dg.Rows[e.RowIndex].Cells[e.ColumnIndex];

}

}

Run the project and right-click a photo, which will display the shortcut menu. Click the “Insert Item From File” menu item. Select an image file on your computer and click OK. The image will load into the cell as shown in Figure 7-8. Be sure to click the Save Data icon to store the new image into the database.

Figure 7-8 You can upload images to the DataGridView with a bit of code.

Saving an Image from the DataGridViewImageCell to a File To save an image that is in a DataGridViewImageCell object to a file, you can add a bit more code to your project. For start-ers, add another menu item to the PhotoMenu object and set its Text property to “Save Photo to File”. Add a click event handler and add the following code.

Visual BasicPrivate Sub SavePhotoToFileToolStripMenuItem_Click( _

ByVal sender As System.Object, _

ByVal e As System.EventArgs) _

Handles SavePhotoToFileToolStripMenuItem.Click

Const oleOffset As Integer = 78

Const oleTypeStart As Integer = 20

Const oleTypeLength As Integer = 12

Dim imageBytes() As Byte = CType(currentPhotoCell.Value, Byte())

Chapter 7: Working with the Windows Data Grid Control 187

C07621411.fm Page 187 Tuesday, November 22, 2005 8:58 PM

If (imageBytes Is Nothing Or imageBytes.Length = 0) Then Return

Dim dlg As New SaveFileDialog()

dlg.AddExtension = True

dlg.Filter = "GIF|*.gif|BMP|*.bmp|JPG|*.jpg"

dlg.FilterIndex = 2

dlg.Title = "Enter a file name for this photo"

dlg.FileName = "EmployeePhoto.bmp"

If (System.Windows.Forms.DialogResult.OK <> dlg.ShowDialog()) Then

Return

End If

Dim tempStream As MemoryStream

Dim type As String = System.Text.Encoding.ASCII.GetString( _

imageBytes, oleTypeStart, oleTypeLength)

If (type = "Bitmap Image") Then

tempStream = New MemoryStream( _

imageBytes, oleOffset, imageBytes.Length - oleOffset)

Else

tempStream = New MemoryStream( _

imageBytes, 0, imageBytes.Length)

End If

Dim bmp As New Bitmap(tempStream)

bmp.Save(dlg.FileName, ParseImageFormat(dlg.FileName))

End Sub

Public Function ParseImageFormat(ByVal fileName As String) As ImageFormat

Dim ext As String = Path.GetExtension(fileName).ToLower()

Select Case ext

Case "bmp"

Return ImageFormat.Bmp

Case "jpg"

Case "jpeg"

Return ImageFormat.Jpeg

Case "gif"

Return ImageFormat.Gif

Case Else

Return ImageFormat.Bmp

End Select

Return Nothing

End Function

C#private void savePhotoToFileToolStripMenuItem_Click(

object sender, EventArgs e)

{

const int oleOffset = 78;

const int oleTypeStart = 20;

const int oleTypeLength = 12;

byte[] imageBytes = (byte[])currentPhotoCell.Value;

if (imageBytes == null || imageBytes.Length == 0) return;

SaveFileDialog dlg = new SaveFileDialog();

dlg.AddExtension = true;

188 Programming Microsoft ADO.NET 2.0 Applications: Advanced Topics

C07621411.fm Page 188 Tuesday, November 22, 2005 8:58 PM

dlg.Filter = "GIF|*.gif|BMP|*.bmp|JPG|*.jpg";

dlg.FilterIndex = 2;

dlg.Title = "Enter a file name for this photo";

dlg.FileName = "EmployeePhoto.bmp";

if (DialogResult.OK != dlg.ShowDialog()) return;

MemoryStream tempStream;

string type = System.Text.Encoding.ASCII.GetString(

imageBytes, oleTypeStart, oleTypeLength);

if (type == "Bitmap Image")

{

tempStream = new MemoryStream(

imageBytes, oleOffset, imageBytes.Length - oleOffset);

}

else

{

tempStream = new MemoryStream(

imageBytes, 0, imageBytes.Length);

}

Bitmap bmp = new Bitmap(tempStream);

bmp.Save(dlg.FileName, ParseImageFormat(dlg.FileName));

}

public ImageFormat ParseImageFormat(string fileName)

{

string ext = Path.GetExtension(fileName).ToLower();

switch (ext)

{

case "bmp":

return ImageFormat.Bmp;

case "jpg":

case "jpeg":

return ImageFormat.Jpeg;

case "gif":

return ImageFormat.Gif;

default:

return ImageFormat.Bmp;

}

}

There is a small problem with the employee photos that are embedded in the Northwind data-base. These photos were originally in a Microsoft Access database, which stored the photos with an OLE header. The OLE header occupies the first 78 bytes of the image byte array. If you want to save these as reuseable images, you need to strip off the OLE header. That’s OK, but in the previous section, I showed you how to insert new images that were saved to the data-base into the DataGridView object. This means that some images may have the OLE header, while others don’t, so you need a means for determining whether the OLE header exists. As it turns out, bytes 20 through 31 (12 total bytes) will hold the string “Bitmap Image” if the OLE header exists.

With that information, let’s see what this code is doing. First, constants are declared that relate to the OLE header information. Next, a byte array variable called imageBytes is created to

Chapter 7: Working with the Windows Data Grid Control 189

C07621411.fm Page 189 Tuesday, November 22, 2005 8:58 PM

simplify access to the value that is in the currentPhotoCell. The imageBytes variable is tested to see if it contains an image. If it does not have an image, there is no need to go further.

The code then prompts the user to enter a filename. Notice that you have the option to save the image as a GIF, BMP, or JPG file, even if the image was originally stored in a different for-mat. By the way, the original employee photos are in BMP format.

This code then looks for the “Bitmap Image” string, and if it exists, the OLE header will be stripped by creating a MemoryStream object from the imageBytes, starting at the OLE header offset.

Finally, a Bitmap object is created from the MemoryStream object. If the MemoryStream object contains a valid image, the Bitmap object will be successfully created in memory. If the Mem-oryStream object does not contain a valid image, or you didn’t strip off the OLE header, an ArgumentException would be thrown with the message “Parameter is not valid”. The Bitmap object is then saved to the file, using the format that you selected. Notice that a helper func-tion was created to get the image format based on the file extension.

Using the DataGridViewButtonColumn

The DataGridViewButtonColumn supplies a DataGridViewButtonCell as a cell template to a row that is being created. The DataGridViewButtonCell renders the whole cell as a button. For a given DataGridViewButtonColumn object, there is one DataGridViewButtonCell object for each DataGridViewRow object in the DataGridView object. The CellClick and CellContentClick events fire when the button is clicked.

Using the DataGridViewLinkColumn

The DataGridViewLinkColumn functions like the DataGridViewButtonColumn but looks like a hyperlink instead of a button. It supplies a DataGridViewLinkCell as a cell template to a row that is being created. For a given DataGridViewLinkColumn object, there is one DataGrid-ViewLinkCell object for each DataGridViewRow object in the DataGridView object. The CellClick and CellContentClick events fire when the button is clicked.

Using the DataGridViewComboBoxColumn

The DataGridViewComboBoxColumn supplies a DataGridViewComboBoxCell as a cell template to a row that is being created. For a given DataGridViewComboBoxColumn object, there is one Data-GridViewComboBoxCell object for each DataGridViewRow object in the DataGridView object. The DataGridViewComboBoxCell renders the whole cell as a ComboBox. You can set the Display-StyleForCurrentCellOnly property to true, which causes these cells to render as TextBox objects until you select one, and then the cell renders with a ComboBox (as shown in Figure 7-9). The DataGridViewComboBoxColumn can receive its pick list from another data source if you make assignments to the DataSource, DisplayMember, and ValueMember properties.

190 Programming Microsoft ADO.NET 2.0 Applications: Advanced Topics

C07621411.fm Page 190 Tuesday, November 22, 2005 8:58 PM

The CellClick fires every time the user activates the drop-down list; the CellContentClick event fires only when the user first clicks in the cell. The CellValueChanged event fires if the user changes the contents of the cell and leaves the cell.

Figure 7-9 The DisplayStyleForCurrentCellOnly is set to false in the CustomerID column and is set to true in the EmployeeID column.

Working with DataGridViewRow Objects

A DataGridViewRow object is created for each row of data that needs to be rendered in the DataGridView object. Remember that the DataGridViewRow object contains a collection of cell objects that inherit from the DataGridViewCell class, where each of the columns supplies a cell template that the row clones when the row is created.

Table 7-3 shows the events that are related to the DataGridViewRow.

Table 7-3 Row Events on the DataGridView Object

Event Description

AllowUserToAddRowsChanged The AllowUserToAddRows property of the DataGrid-View object has been changed.

AllowUserToDeleteRowsChanged The AllowUserToDeleteRows property of the Data-GridView object has been changed.

AllowUserToResizeRowsChanged The AllowUserToResizeRows property of the Data-GridView object has been changed.

AlternatingRowsDefaultCellStyleChanged The AlternatingRowsDefaultCellStyle property of the DataGridView object has been changed.

AutoSizeRowsModeChanged The AutoSizeRowsMode property of the DataGrid-View object has been changed.

CancelRowEdit The row edit is being canceled (fires only if the Data-GridView object is in virtual mode).

NewRowNeeded The user has navigated to the new row on the bottom of the DataGridView object (fires only if the Data-GridView object is in virtual mode).

Chapter 7: Working with the Windows Data Grid Control 191

C07621411.fm Page 191 Tuesday, November 22, 2005 8:58 PM

RowContextMenuStripChanged The ContextMenuStrip property of the DataGridView object has changed.

RowContextMenuStripNeeded A shortcut menu strip needs to be displayed for the row.

RowDefaultCellStyleChanged The DefaultGridViewBand object’s DefaultCellStyle property has been changed. This event is typically triggered when the RowTemplate of the DataGrid-View object is changed.

RowDirtyStateNeeded The DataGridView object is attempting to determine whether the row has uncommitted changes (fires only if the DataGridView is in virtual mode).

RowDividerDoubleClick The row divider has been double-clicked.

RowDividerHeightChanged The row divider’s height has been changed.

RowEnter A row gets the focus and becomes the current row.

RowErrorTextChanged The error text of a row has changed.

RowErrorTextNeeded The row error text is about to be displayed (fires only if the DataGridView object is in virtual mode).

RowHeaderCellChanged A row object’s header cell contents have been changed.

RowHeaderMouseClick The user has clicked the row header.

RowHeaderMouseDoubleClick The user has double-clicked the row header.

RowHeaderBorderStyleChanged The RowHeaderBorderStyle property of the DataGrid-View object has been changed.

RowHeaderDefaultCellStyleChanged The RowHeaderDefaultCellStyle property of the DataGridView object has been set.

RowHeadersWidthChanged The RowHeadersWidth property of the DataGridView object has been changed.

RowHeadersWidthSizeModeChanged The RowHeadersWidthSizeMode property of the DataGridView object has been changed.

RowHeightChanged The Height property of a DataGridViewRow object has been changed.

RowHeightInfoNeeded The Height property of a DataGridViewRow has been requested.

RowHeightInfoPushed The user has changed the height of a row.

RowLeave The user has left the row, causing the row to lose fo-cus and no longer be the current row.

RowMinimumHeightChanged The MinimumHeight property on a DataGridView-Row object has been changed.

RowPostPaint The row object’s cells have been painted.

RowPrePaint This event fires before any of the row object’s cells are painted.

Table 7-3 Row Events on the DataGridView Object

Event Description

192 Programming Microsoft ADO.NET 2.0 Applications: Advanced Topics

C07621411.fm Page 192 Tuesday, November 22, 2005 8:58 PM

Implementing Virtual Mode

You can implement virtual mode by setting the VirtualMode property of the DataGridView object to true and implementing event handlers to populate and edit cells as necessary. In a read-only scenario, the only event that needs to be handled is CellValueNeeded. Table 7-4 shows the other events that are available for use only when virtual mode is enabled.

RowsAdded One or more rows have been added to the DataGrid-View object’s Rows collection.

RowsDefaultCellStyleChanged The RowsDefaultCellStyle property of the DataGrid-View object has been set.

RowsRemoved One or more rows have been removed from the DataGridView object’s Rows collection.

RowStateChanged The state of the row has changed, typically to indi-cate that the row is being displayed.

RowUnshared The row object’s state has been changed from shared to unshared.

RowValidated The row has been validated.

RowValidating The row is being validated. This event also allows val-idation to fail by setting the Cancel property of e (the DataGridViewCellCancelEventArgs) to true.

UserAddedRow The user has added a row.

UserDeletedRow The user has deleted a row.

UserDeletingRow The user is attempting to delete a row. The delete can be aborted by setting the Cancel property of e (the DataGridViewRowCancelEventArgs) to true.

Table 7-3 Row Events on the DataGridView Object

Event Description

Table 7-4 Virtual Mode Events on the DataGridView Object

Event Description

CellValueNeeded A request has been made for cell data to be displayed from the data cache.

CellValuePushed Used to commit modified cell data back to the data cache.

NewRowNeeded A request has been made for row data to be displayed from the data cache.

RowDirtyStateNeeded A request has been made to retrieve the dirty state of a row from the data cache. A dirty row is a row with uncommitted changes.

CancelRowEdit A request has been made to roll back changes in the cell to the orig-inal data cache values.

RowErrorTextNeeded A request has been made for row error text. If the error text has changed, be sure to call the UpdateRowErrorText method to ensure that the proper error text is displayed in the DataGridView object.

Chapter 7: Working with the Windows Data Grid Control 193

C07621411.fm Page 193 Tuesday, November 22, 2005 8:58 PM

To keep this example simple, we will work with a Fruit class and a Fruit collection. The Fruit class is shown in the following code snippet.

Visual BasicPublic Class Fruit

Public Property Name() As String

Get

Return _name

End Get

Set(ByVal value As String)

_name = value

End Set

End Property

Private _name As String

Public Property Calories() As Decimal

Get

Return _calories

End Get

Set(ByVal value As Decimal)

_calories = value

End Set

End Property

Private _calories As Decimal

Public Property Carbs() As Decimal

Get

Return _carbs

End Get

Set(ByVal value As Decimal)

_carbs = value

End Set

End Property

Private _carbs As Decimal

Public Sub New(ByVal name As String,

ByVal calories As Decimal, _

ByVal carbs As Decimal)

Me.Name = name

Me.Calories = calories

Me.Carbs = carbs

End Sub

End Class

C#namespace WinFormDataGridView

{

public class Fruit

{

public string Name

{

get { return _name; }

set { _name = value; }

194 Programming Microsoft ADO.NET 2.0 Applications: Advanced Topics

C07621411.fm Page 194 Tuesday, November 22, 2005 8:58 PM

}

private string _name;

public decimal Calories

{

get { return _calories; }

set { _calories = value; }

}

private decimal _calories;

public decimal Carbs

{

get { return _carbs; }

set { _carbs = value; }

}

private decimal _carbs;

public Fruit(string name, decimal calories, decimal carbs)

{

this.Name = name;

this.Calories = calories;

this.Carbs = carbs;

}

}

}

The following steps are required to implement virtual mode.

1. Add a DataGridView object to a Windows form and set its VirtualMode property to true. Add three DataGridViewTextBoxColumn objects for FruitName, Calories, and Carbs. Rather than code this, right-click the DataGridView control in Visual Studio’s form designer and choose Add Column to configure these settings.

2. Be sure to add the Fruit class as shown in the previous code example. Add an instance variable to the form that is a fruit list object. In the form constructor, add code to popu-late the fruit list. Set the RowCount of the DataGridView object to the count of items in the fruit list, plus one to provide the ability to add a new row. Lastly, add instance vari-ables to hold a reference to the fruit being edited and its index number.

3. Add an event handler for the DataGridView object’s CellValueNeeded event. This event fires when a cell needs to be painted, and this code populates the grid cell in a just-in-time fashion. What this means is that the DataGridView object will request this informa-tion when a cell needs to be displayed. This essentially lets the DataGridView object act as a sliding window into your data. The code for these steps is as follows:

Visual BasicPublic Class Form4

Private fruitList As New List(Of Fruit)

Private rowInEdit As Integer = -1

Private fruitInEdit As Fruit = Nothing

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

ByVal e As System.EventArgs) Handles MyBase.Load

Chapter 7: Working with the Windows Data Grid Control 195

C07621411.fm Page 195 Tuesday, November 22, 2005 8:58 PM

'populate sample data

fruitList.Add(New Fruit("Apple", 44, 10.5))

fruitList.Add(New Fruit("Banana", 107, 26))

fruitList.Add(New Fruit("Orange", 35, 8.5))

'Add 1 for new row

DataGridView1.RowCount = fruitList.Count + 1

End Sub

'occurs when a cell needs to be painted

Private Sub DataGridView1_CellValueNeeded(

ByVal sender As System.Object, _

ByVal e As _

System.Windows.Forms.DataGridViewCellValueEventArgs) _

Handles DataGridView1.CellValueNeeded

'do not return anything on the "*" row

If e.RowIndex = DataGridView1.RowCount – 1 Then

'not needed for new row

Return

End If

Dim tmpFruit As Fruit = Nothing

' if the row is being edited

' get the fruitInEdit else get

' the appropriate fruitList

' reference.

If e.RowIndex = rowInEdit Then

tmpFruit = Me.fruitInEdit

Else

tmpFruit = fruitList(e.RowIndex)

End If

' Set the cell value based on mapping the

' column name to the property.

Select Case Me.DataGridView1.Columns(e.ColumnIndex).Name

Case "FruitName"

e.Value = tmpFruit.Name

Case "Calories"

e.Value = tmpFruit.Calories

Case "Carbs"

e.Value = tmpFruit.Carbs

End Select

End Sub

End Class

C#using System;

using System.Collections.Generic;

using System.Windows.Forms;

namespace WinFormDataGridView

{

public partial class Form4 : Form

{

private List<Fruit> fruitList = new List<Fruit>();

196 Programming Microsoft ADO.NET 2.0 Applications: Advanced Topics

C07621411.fm Page 196 Tuesday, November 22, 2005 8:58 PM

private int rowInEdit = -1;

private Fruit fruitInEdit = null;

public Form4()

{

InitializeComponent();

}

private void Form4_Load(object sender, EventArgs e)

{

//populate sample data

fruitList.Add(new Fruit("Apple", 44M, 10.5M));

fruitList.Add(new Fruit("Banana", 107M, 26M));

fruitList.Add(new Fruit("Orange", 35M, 8.5M));

// Add 1 for new row

DataGridView1.RowCount = fruitList.Count + 1;

}

//occurs when a cell needs to be painted

private void DataGridView1_CellValueNeeded(object sender,

DataGridViewCellValueEventArgs e)

{

//don't return anything on the "*" row

if (e.RowIndex == DataGridView1.RowCount - 1)

{

// not needed for new row

return;

}

Fruit tmpFruit = null;

// if the row is being edited

// get the fruitInEdit else get

// the appropriate fruitList

// reference. if (e.RowIndex == rowInEdit)

{

tmpFruit = fruitInEdit;

}

else

{

tmpFruit = fruitList[e.RowIndex];

}

// Set the cell value based on mapping the

// column name to the property.

switch (DataGridView1.Columns[e.ColumnIndex].Name)

{

case "FruitName":

e.Value = tmpFruit.Name; break;

case "Calories":

e.Value = tmpFruit.Calories; break;

case "Carbs":

e.Value = tmpFruit.Carbs; break;

}

}

}

}

Chapter 7: Working with the Windows Data Grid Control 197

C07621411.fm Page 197 Tuesday, November 22, 2005 8:58 PM

With this code entered, the DataGridView object can be displayed with its contents, as shown in Figure 7-10.When the CellValueNeeded event handler is called, this code will return nothing if the cell to be painted is in the “*” (new) row. If the cell to be painted is in a row that is being edited, the column value from the fruitInEdit variable will be returned. If this is a column in a row that is not being edited, the fruit will be retrieved from the fruitList collection and its col-umn value will be returned.

Figure 7-10 The DataGridView object was populated using the CellValueNeeded event.

The next step is to add code to handle the addition of a new row to the DataGridView object. The NewRowNeeded event must be implemented to create a new Fruit instance when a new row is created in the DataGridView object.

Code must also be added to commit cell changes back to the fruit list. This requires imple-mentation of the CellValuePushed event and the RowValidated event. These events are imple-mented as shown in the following code snippets:

Visual BasicPrivate Sub DataGridView1_NewRowNeeded( _

ByVal sender As System.Object, _

ByVal e As System.Windows.Forms.DataGridViewRowEventArgs) _

Handles DataGridView1.NewRowNeeded

' Create a new Fruit object when the user

' moves the cursor into the "*" row

fruitInEdit = New Fruit("", 0, 0)

rowInEdit = DataGridView1.Rows.Count - 1

End Sub

Private Sub DataGridView1_CellValuePushed( _

ByVal sender As System.Object, _

ByVal e As _

System.Windows.Forms.DataGridViewCellValueEventArgs) _

Handles DataGridView1.CellValuePushed

Dim tmpFruit As Fruit = Nothing

' Store a reference to the Fruit object for the row.

If e.RowIndex < fruitList.Count Then

' If the user has started editing an

198 Programming Microsoft ADO.NET 2.0 Applications: Advanced Topics

C07621411.fm Page 198 Tuesday, November 22, 2005 8:58 PM

' existing row, create a clone to edit.

If fruitInEdit Is Nothing Then

fruitInEdit = New Fruit( _

fruitList(e.RowIndex).Name, _

fruitList(e.RowIndex).Calories, _

fruitList(e.RowIndex).Carbs)

End If

tmpFruit = fruitInEdit

rowInEdit = e.RowIndex

Else

' get the row that's being edited

tmpFruit = fruitInEdit

End If

' Set the appropriate Fruit property to the cell

' value entered.

Dim newValue As String = TryCast(e.Value, String)

' Set the appropriate Fruit property to the cell value entered.

Select Case DataGridView1.Columns(e.ColumnIndex).Name

Case "FruitName"

tmpFruit.Name = newValue

Case "Calories"

tmpFruit.Calories = decimal.Parse(newValue)

Case "Carbs"

tmpFruit.Carbs = decimal.Parse(newValue)

End Select

End Sub

Private Sub DataGridView1_RowValidated( _

ByVal sender As System.Object, _

ByVal e As _

System.Windows.Forms.DataGridViewCellEventArgs) _

Handles DataGridView1.RowValidated

' Save row changes if any were made and

' release edited Fruit object.

If e.RowIndex >= fruitList.Count And _

e.RowIndex <> DataGridView1.Rows.Count - 1 Then

' Add the new fruit object to the data store.

fruitList.Add(fruitInEdit)

fruitInEdit = Nothing

rowInEdit = -1

ElseIf Not (fruitInEdit Is Nothing) And _

e.RowIndex < fruitList.Count Then

' Overwrite existing Fruit object

' with modified fruit.

fruitList(e.RowIndex) = fruitInEdit

fruitInEdit = Nothing

rowInEdit = -1

Else

'clear the edit

fruitInEdit = Nothing

rowInEdit = -1

End If

End Sub

Chapter 7: Working with the Windows Data Grid Control 199

C07621411.fm Page 199 Tuesday, November 22, 2005 8:58 PM

C#private void DataGridView1_NewRowNeeded(object sender,

DataGridViewRowEventArgs e)

{

// Create a new Fruit object when the user

// moves the cursor into the "*" row

fruitInEdit = new Fruit("", 0, 0);

rowInEdit = DataGridView1.Rows.Count - 1;

}

private void DataGridView1_CellValuePushed(object sender,

DataGridViewCellValueEventArgs e)

{

Fruit tmpFruit = null;

// Store a reference to the Fruit object for the row.

if (e.RowIndex < fruitList.Count)

{

// If the user is editing a new row, create

// a new Fruit object.

if (fruitInEdit == null)

{

fruitInEdit = new Fruit(

fruitList[e.RowIndex].Name,

fruitList[e.RowIndex].Calories,

fruitList[e.RowIndex].Carbs);

}

tmpFruit = fruitInEdit;

rowInEdit = e.RowIndex;

}

else

{

tmpFruit = fruitInEdit;

}

// Set the appropriate Fruit property to the

// cell value entered.

string newValue = e.Value as string;

switch (DataGridView1.Columns[e.ColumnIndex].Name)

{

case "FruitName":

tmpFruit.Name = newValue; break;

case "Calories":

tmpFruit.Calories = decimal.Parse(newValue); break;

case "Carbs":

tmpFruit.Carbs = decimal.Parse(newValue); break;

}

}

private void DataGridView1_RowValidated(object sender,

DataGridViewCellEventArgs e)

{

// Save row changes if any were made and

// release edited Fruit object.

if (e.RowIndex >= fruitList.Count &&

e.RowIndex != DataGridView1.Rows.Count - 1)

{

200 Programming Microsoft ADO.NET 2.0 Applications: Advanced Topics

C07621411.fm Page 200 Tuesday, November 22, 2005 8:58 PM

// Add the new fruit object to the data store.

fruitList.Add(fruitInEdit);

fruitInEdit = null;

rowInEdit = -1;

}

else if ((fruitInEdit != null) &&

(e.RowIndex < fruitList.Count))

{

// Overwrite existing Fruit object

// with modified fruit.

fruitList[e.RowIndex] = fruitInEdit;

fruitInEdit = null;

rowInEdit = -1;

}

else

{

// clear the edit

fruitInEdit = null;

rowInEdit = -1;

}

}

The NewRowNeeded event handler fires when you move your cursor into the “*” row, which will create an empty Fruit object that can be edited.

This CellValuePushed event handler fires when you leave a cell, and this handler is responsible for saving a cell value into the appropriate property of the Fruit object that is being edited. This code tests to see if you have just started to edit an existing row, and if you have, the exist-ing row is cloned. The clone is copied over the existing Fruit object when you leave the row, but if you press the Esc key twice, you will be able to cancel all column edits on the row, and the clone will be discarded without being committed.

The RowValidated event handler is used to commit an edited Fruit object to the fruitList collec-tion. If the edited Fruit object was an existing Fruit object, the old Fruit object is overwritten. If the edited Fruit object is a new Fruit object, it’s added to the fruitList collection.

If you run this code, adding rows and editing rows will appear to work, but more work is needed. To be able to delete rows and cancel edits, you must implement the UserDeletingRow event and the CancelRowEdit event, as shown in the following code.

Visual BasicPrivate Sub DataGridView1_UserDeletingRow( _

ByVal sender As System.Object, _

ByVal e As _

System.Windows.Forms.DataGridViewRowCancelEventArgs) _

Handles DataGridView1.UserDeletingRow

If e.Row.Index < fruitList.Count Then

' If the user has deleted an existing row, remove the

' corresponding Fruit object from the data cache.

fruitList.RemoveAt(e.Row.Index)

End If

If e.Row.Index = Me.rowInEdit Then

Chapter 7: Working with the Windows Data Grid Control 201

C07621411.fm Page 201 Tuesday, November 22, 2005 8:58 PM

' If the user has deleted a newly created row,

' simply release the corresponding Fruit object.

rowInEdit = -1

fruitInEdit = Nothing

End If

End Sub

Private Sub DataGridView1_CancelRowEdit( _

ByVal sender As System.Object, _

ByVal e As System.Windows.Forms.QuestionEventArgs) _

Handles DataGridView1.CancelRowEdit

If rowInEdit = DataGridView1.Rows.Count - 2 And _

rowInEdit = fruitList.Count Then

' If user canceled the edit of a new row,

' replace the corresponding Fruit object

' with a new empty Fruit.

fruitInEdit = New Fruit("", 0, 0)

Else

' If user cancels existing row edit,

' release the edited Fuit object.

fruitInEdit = Nothing

rowInEdit = -1

End If

End Sub

C#private void DataGridView1_UserDeletingRow(object sender,

DataGridViewRowCancelEventArgs e)

{

if (e.Row.Index < fruitList.Count)

{

// If the user has deleted an existing row, remove the

// corresponding Fruit object from the data cache.

fruitList.RemoveAt(e.Row.Index);

}

if (e.Row.Index == rowInEdit)

{

// If the user has deleted a newly created row,

// simply release the corresponding Fruit object.

rowInEdit = -1;

fruitInEdit = null;

}

}

private void DataGridView1_CancelRowEdit(object sender,

QuestionEventArgs e)

{

if ((rowInEdit == DataGridView1.Rows.Count - 2) &&

(rowInEdit == fruitList.Count))

{

// If user canceled the edit of a new row,

// replace the corresponding Fruit object

// with a new empty Fruit.

fruitInEdit = new Fruit("", 0, 0);

}

202 Programming Microsoft ADO.NET 2.0 Applications: Advanced Topics

C07621411.fm Page 202 Tuesday, November 22, 2005 8:58 PM

else

{

// If user cancels existing row edit,

// release the edited Fuit object.

fruitInEdit = null;

rowInEdit = -1;

}

}

You should now have a functioning DataGridView object that has virtual mode enabled and implemented. You might want to implement other events to expand the functionality of this example, but the example should give you a good idea of what is required to get an editable DataGridView object to operate in virtual mode.

Summary■ The DataGridView object represents a tabular (row-and-column format) display of data.

■ DataGridViewColumn objects provide a DataGridViewCell object template for the Data-GridViewRow objects.

■ You use styles to change the look of the DataGridView object. You can assign styles to individual cells, rows, or columns, or to the complete DataGridView object.

■ The DataGridView object has three modes of operation: bound mode, unbound mode, and virtual mode.

■ In virtual mode, you can implement just-in-time data loading when large amounts of data need to be available to the user and the user will select the data to be viewed.

■ The DataGridView object can be bound to a database, Web service, or object.

■ Resource usage is minimized by sharing DataGridViewRow instances, but you must be careful with the calls that you make in your code because you can cause one or more rows to become unshared.

■ The DataGridView object provides many events that are related to cells, columns, and rows.