.NET neophytes routinely ask how to bind data to Windows Forms controls. This is a valid beginner question that seasoned .NET users don't ask because they either aren't binding data to controls or they already know how to use DataBindings. No matter which category you fall into, Visual Basic 2005 has evolved and now offers some new features related to data binding.
Visual Studio 2005 and .NET 2.0 support binding to traditional data sources and custom objects. This technology is based on the capabilities of the CodeDOM, typed DataSets, and changes to Forms designers. Collectively, these changes make it possible to define a data source, generate a Windows Form with controls, run the form, and edit the data without writing a single line of code. If the data source is based on a known provider like the SQL provider, the code to populate the data set and write changes is generated automatically. If you use a custom data source, you will have to write a little code to populate the custom objects and a little more code to write changes back to the data source.
This article walks you through the data source's capabilities and demonstrates how the binding source and binding navigator controls are used to provide navigation and binding to WinForms controls. The end of the article demonstrates how you can use reflection to generate controls on the fly and use data bindings programmatically to bind data objects to controls.
Prerequisites for the Article
This article uses Visual Studio .NET 2005 beta 2—its launch is about three weeks away, SQL Server Express 2005, and a copy of the physical SQL Server Northwind database file northwnd.mdf. (SQL Server Express supports a direct connection to physical SQL Server files.)
Tip: Spend a few extra bucks on a beefy computer and install Virtual PC. Virtual PC is an invaluable tool for installing and testing beta software and is an inexpensive way to turn one PC into two or more.
Generating a Form Using a Data Source
Generally, Developer.com doesn't publish articles about wizards because readers are capable enough to walk through the wizards and the wizards are easy enough to follow correctly. However, .NET 2.0 is still new, so you may not know about its wizard-based features, which this article discusses. And, it wraps up with some cool data binding and reflection code that isn't wizard-based.
To create a data source from a SQL Server physical file and generate a data-bound form that is a complete and functional application, take the following steps:
- Open Visual Studio 2005.
- Create a new Windows Forms project.
- Select Data|Add New Data Source.
- In the first step of the wizard (entitled Choose a data source type, see Figure 1), select Database.
- Click Next.
- In step 2—choose your data connection—click on New Connection (see Figure 2).
- In the Change Data Source dialog, select the Microsoft SQL Server Database File (see Figure 3).
- Click OK.
- Browse to your copy of the SQL Server instance of the Northwind database file. (The file will have an .MDF extension, and you can use Windows Explorer to help you find the file. See Figure 4.)
- To wrap up, choose the database objects (see Figure 5). For the example, select all of the database objects.
- Click Finish to generate the data source.
Figure 1: Select Database to Create a Data Source from an Existing Database
Figure 2: Click New Connection to Create a Connection to the Data Source
Figure 3: Select the Microsoft SQL Server Database File to Connect Directly to a SQL Server Database Store
Figure 4: Browse to the Physical Data File, Something Like Northwnd.mdf
Figure 5: Choose All of the Database Objects for the Example
The designer did a lot of work when you clicked Finish; it created a very large custom-typed DataSet. (This DataSet contains about 23,000 lines of code, because you selected the entire Northwind database.) This DataSet has code that makes it very easy to initialize, bind, modify, and store changes. Additionally, the designer added a data source to the Data Sources explorer (see Figure 6). From this data source, you can easily create as many bound forms you want without a stitch of code.
Figure 6: The Northwind Data Source Is a Visual Representation of the Typed DataSet
Creating the Data Bound Form
By default, the Data Sources explorer elements are set to generate a DataGridView. Simply click on the Customers table (see Figure 6), drag it to a Windows Form, and you have a running application. To generate a detailed view with individual controls (see Figure 7), take the following steps:
- Make sure you've selected a Form and the Form is in Designer mode, not code mode. (Make sure you see the Form, not the Form's code.)
- Click the drop-down list for the Customers table and select Details.
- Drag and drop the Customers table from the Data Sources explorer to a Form.
- Press F5.
Step 3 added controls to the form, generated code to populate the Customers data table in the Form_Load event, and bound the controls to a binding source and binding navigator. These controls work together to manage the DataSet and track changes.
Figure 7: The Form Generated by the Data Sources Explorer and Customers Table Set
When you selected Details from the Customers table, you told the designer that you want to generate a details-oriented form with a pair of controls for each column. When you dragged and dropped the Customers table to the form, the designer added the Labels and TextBoxes, a DataSet component, a TableAdapter, a BindingSource, and a BindingNavigator. The BindingNavigator (shown at the top of Figure 7) is used to navigate through the DataSet and it provides controls for managing rows and saving changes. The BindingNavigator talks to the BindingSource, and the BindingSource is data-aware because it has a reference to the DataSet. The TableAdapter is a custom, generated control that is analogous to a DataAdapter but is not derived from the SqlDataAdapter.
All of these components, controls, and code are added or generated for you by the designer. The heavy lifting occurs in the custom DataSet. The Form itself has code for binding columns to controls, handling the initialization, and updating the database from the form. This code is shown in Listing 1.
Listing 1: Some Lightweight Code Generated by the Designer
Public Class Form1 Private Sub bindingNavigatorSaveItem_Click(ByVal sender As _ System.Object, _ ByVal e As System.EventArgs) Handles _ bindingNavigatorSaveItem.Click If Me.Validate Then Me.CustomersBindingSource.EndEdit() Me.CustomersTableAdapter.Update(Me.NorthwndDataSet.Customers) Else System.Windows.Forms.MessageBox.Show(Me, "Validation _ errors occurred.", _ "Save", System.Windows.Forms.MessageBoxButtons.OK, _ System.Windows.Forms.MessageBoxIcon.Warning) End If End Sub Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load ' TODO: This line of code loads data into the ' NorthwndDataSet.Customers table. You can move, ' or remove it, as needed. Me.CustomersTableAdapter.Fill(Me.NorthwndDataSet.Customers) End Sub End Class
If you want to view the binding statements, open the *.designer.vb file. For example, if the Form is named Form1.vb, Form1.Designer.vb will contain the code managed by the Forms designer. (Splitting a class into multiple parts is supported by the Partial Classes technology, also new in .NET 2.0.) The code in the last section demonstrates binding objects.
Finally, if you want to manage the dataset using menus or the equivalent of a custom navigator, you can implement event handlers on the binding source component and remove the binding navigator altogether.
Binding to Controls Programmatically
The previous section demonstrated how to create a data-bound form at design time. You can use the same features the IDE uses to create a custom designer or generate forms dynamically at runtime using reflection and data-binding statements. The Form in Figure 8 was generated from the code in Listing 2. The data bindings statements are shown in bold font in the listing.
Figure 8: A Dynamically Generated Data Bound Form
Listing 2: Code for Dynamically Generating a Data-Bound Form
Imports System.Configuration Imports System.Data Imports System.Data.SqlClient Imports System.Reflection Public Class Form2 Private Sub Form2_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Dim table As DataTable = Nothing Dim connectionString As String = _ My.Settings.northwndConnectionString Using connection As SqlConnection = _ New SqlConnection(connectionString) connection.Open() Dim command As SqlCommand = _ New SqlCommand("SELECT * FROM CUSTOMERS", connection) Dim adapter As SqlDataAdapter = New SqlDataAdapter(command) table = New DataTable() adapter.Fill(table) End Using GenerateForm(table) End Sub Private Sub GenerateForm(ByVal table As DataTable) Dim label As Label Dim textBox As TextBox Dim I As Integer For I = 0 To table.Columns.Count - 1 label = New Label() label.Location = New Point(10, I * 22 + 10) label.AutoSize = True label.Text = table.Columns(I).ColumnName + ":" Controls.Add(label) textBox = New TextBox textBox.Location = New Point(100, I * 22 + 10) textBox.Width = 200 textBox.DataBindings.Add("Text", _ table, table.Columns(I).ColumnName) Controls.Add(textBox) Next End Sub End Class
All you need to do to bind a control to a property in something like a DataTable is specify the controls property (Text), the origin of the data (table), and the property in the data source that defines the property that contains the source of data (the column name).
All that remains to complete the sample generated form is to add some navigation controls, and the dynamic form works as well as one that might have taken hours or days to create by hand.
.NET Capabilities for You and Me
A good framework is one that provides the same features to the consumers (we programmers) as it does to the producers (Microsoft programmers). In VB6, all kinds of features existed in the IDE that were very hard—if not impossible—for the consumer to reproduce. However, the Visual Studio IDE uses the same capabilities in the .NET Framework that are available to you and me. This means if the Microsoft programmers can do it, we can too, and groups (Microsoft developers and you and I) are using the same technologies.
The gap is only increasing in power and flexibility between VB6 and VB.NET. Data-binding capabilities, reflection, and generics are just a few productivity capabilities that represent the tip, not the iceberg.
About the Author
Paul Kimmel is the "VB Today" columnist for www.codeguru.com and has written several books on object-oriented programming and .NET. Look for his upcoming books UML DeMystified from McGraw-Hill/Osborne (October 31st, 2005) and C# Express from Addison Wesley (Spring 2006). Paul is an architect for Tri-State Hospital Supply Corporation. You may contact him for technology questions at firstname.lastname@example.org.
If you are interested in joining or sponsoring a .NET Users Group, check out www.glugnet.org.
Copyright © 2005 by Paul T. Kimmel. All Rights Reserved.