5. Generating Bound Controls with the Visual Studio Designer

By now, you are hopefully getting a firm grasp on the code involved for setting up data binding between controls and data sources using BindingSource components or Binding objects as middlemen. However, writing all that code can become tedious and error prone, especially for the more common scenarios. It would be really nice if someone else could write that code and you could just focus on the core logic of your application. Thanks to a number of new designer features in Visual Studio 2005, you will rarely have to write the data-binding code by hand for common scenarios. The Data Sources window combined with the Properties window, Smart Tags, and wizards in the designer let you tell Visual Studio what you want in an intuitive and declarative point-and-click manner, and it will write all the tedious data-binding code for you.

Working with the Data Sources Window

The Data Sources window is a new designer support window in the Visual Studio 2005 IDE. This window lets you quickly set up data binding between controls and data sources using just a few selection and drag-and-drop mouse gestures in the designer. You can create new data sources, generate bound controls from those data sources, and bind data sources to existing controls. You can use the Data Sources window to create data sources from databases, Web services, or objects. Figure 5.1 shows the Data Sources window with the following data sources:

•    A data set data source based on the MoneyDB database used for the currency exchange samples in Chapter 4.

•    A Web service data source named localhost that references the CustomersService Web service that is included in the download samples for this chapter.

•    An object data source based on a CustomersBusinessLayer object hierarchy included in the download samples.

FIGURE 5.1: Binding to Various Data Sources

Binding to Various Data Sources

If you have multiple projects in a solution, the contents of the Data Sources window reflect the current project based on the current selection in Solution Explorer. To view the data sources for another project within the solution, select the project or any of its child items in Solution Explorer, then switch back to the Data Sources window. The view that is presented will be slightly different depending on whether the current document in the editor is a form in Design view. The Data Sources window is designed to generate data-bound controls in Windows Forms through the designer, so you usually won’t work with the Data Sources window except when the Forms designer is displayed.

Adding Data Sources to a Project

Visual Studio automatically adds any typed data set definitions that are part of your project to the Data Sources window. It also adds objects returned from any Web references that are part of your project to the Data Sources window. However, you don’t necessarily have to add these things to your project before you start using the Data Sources window, because you can create connections to data sources directly from the window itself.

When you add a data source through the Data Sources window to a Web service or object data source, you aren’t actually creating the data source itself; you are just creating an association or connection to the data source type that you can use for data-binding purposes in your project. When you add a database data source to your project, you are in fact generating a new type definition that will be part of your project. To add a data source, you launch the Data Source Configuration wizard. There are three ways to do this:

•    If no data sources exist in your project yet, click the Add New Data Source link in the Data Sources window.

•    Click the Add New Data Source icon in the Data Sources window (see Figure 5.2).

FIGURE 5.2: Data Sources Toolbar

Data Sources Toolbar

•    From the Data menu in Visual Studio, select Add New Data Source.

In addition to letting you create new data sources, if the data source is a typed data set, the Data Sources window toolbar lets you edit the data set in the designer or configure it with the Data Source Configuration wizard (which we’ll be stepping through shortly). The toolbar also lets you refresh the data source if something about it has changed while the Data Sources window is displayed.

Choosing the Type of Data Source

The first step in the wizard lets you choose the type of data source you want to create: database, Web service, or object types (see Figure 5.3).

FIGURE 5.3: Choose a Data Source Type Step

Choose a Data Source Type Step

Let’s start with databases, since they are easier to understand if you have worked with data binding in the past in .NET.

Adding a Database Data Source

When you select Database for the data source type, what you really are doing is adding a typed data set definition to your project that is ready to contain data from the database objects that you select in the wizard. You could accomplish the same thing by selecting a data set from the Add New Item dialog, then dragging and dropping database objects on the designer surface from Server Explorer. The Data Source Configuration wizard steps you through the process in a little different way. The following procedure outlines this process.

1.   Choose your database connection (see Figure 5.4).

FIGURE 5.4: Choose Your Database Connection Step

Choose Your Database Connection Step

      The drop-down list shows the data connections that have been configured in Visual Studio through this wizard or through the Server Explorer window. These same items appear in the Data Connections node in the Server Explorer tree.

      Depending on the options selected when the connection was created, the section below the drop-down list may indicate that the connection string contains sensitive information (specifically, a username and password). If so, it gives you the option to leave that information out of the connection string so that the sensitive information doesn’t get embedded in your configuration file. You can also view the resulting connection string by clicking on the plus sign next to the Connection String group header. This displays the connection string that will be used in a selectable text box. You can select the string (in case you need to copy it to the Clipboard), but you cannot edit the string directly.

2.   If you click the New Connection button, what you see depends on whether you have configured any connections before. The first time you add a connection, you are prompted to select a data source and provider (see Figure 5.5). You also get the same dialog if you click the Change button from the Add Connection dialog (see Figure 5.6). The Add Connection dialog lets you create a new connection based on any of the available providers, and once you do, it too will be added to the list of data connections in Server Explorer.

FIGURE 5.5: Data Source and Provider Selection

Data Source and Provider Selection

FIGURE 5.6: Add Connection Dialog

Add Connection Dialog

     The Add Connection dialog is new in Visual Studio 2005, but it’s very similar to the one that existed in previous versions. The items presented in this dialog change based on the provider selected. Figure 5.6 shows the settings for the managed SQL Server provider. To configure a SQL Server connection, you specify a server name—this can be a SQL Server instance name on the network or an IP address. If you are referring to the local machine’s default instance of SQL Server, you can use one of three shorthand addresses: localhost, (local), or just the dot (.) character. You also provide authentication information and specify the database name. This configures the connection string that is used to connect to the database.

     If you had selected a SQL Server database file as the data source in Figure 5.5, the dialog would be different and would only let you specify a path to the database file and authentication information. This is the way to specify a data connection for a SQL Server 2005 Express Edition database.

3.   Choose whether to save the connection information in your application configuration file (Figure 5.7). Doing so lets you easily modify the connection string when you deploy your application without needing to change any of the source code.

FIGURE 5.7: Save Connection String Step

Save Connection String Step

     Visual Studio adds code to the table adapter definitions to read in the connection string from the .config file if it can be found; otherwise, the table adapter tries to use a hard-coded default (the one specified at design time through this process).

4.   In the next window you select the objects from the database that you want to include in the typed data set that will be generated as the output of this wizard process (see Figure 5.8). As mentioned earlier in the book, you can include tables, views, stored procedures, or functions in the data set simply by checking the boxes next to them in the tree of database objects. This is analogous to dragging these objects onto the data set designer surface from Server Explorer. At the bottom of the dialog you can specify the type name for the generated typed data set class.

FIGURE 5.8: Choose Database Objects Step

Choose Database Objects Step

5.   When you click the Finish button, the typed data set definition will be added to your project as an XML Schema Definition (XSD) file with an associated typed data set definition source file that is hidden by default. You can view the actual typed data set code by expanding the Solution Explorer tree underneath the XSD file. Underneath the XSD file is a .Designer.cs file for the data set that contains the autogenerated class definitions for the data set and its associated table adapters. Additionally, the Data Sources window will update to show the objects in your data set.

Adding a Web Service Data Source

Windows Forms applications are often just a user interface for front-end functionality that resides on a middle-tier server somewhere out on the network. As the use of the Internet and widely distributed networks grow, a common model for distributing functionality and data is through Web services. As such, consuming data returned from a Web service is a common need for Windows Forms applications, and one way to consume data is to bind controls to it. The Data Sources window makes this much easier by generating all the appropriate code for you based on a Web reference that you add to your project.

As mentioned earlier, if you add a Web reference to your project directly, the object types returned by methods on that Web service will show up in the Data Sources window automatically. If you need to add a Web reference for the purposes of data binding and you haven’t already added the reference, you can easily do so by selecting a Web service as the data source type when you are in the Data Source Configuration wizard. This displays the Add Web Reference dialog as the next step in the wizard (see Figure 5.9). Note that this is the same dialog you get when you select Add Web Reference from Solution Explorer.

FIGURE 5.9: Add Web Reference Dialog

Add Web Reference Dialog

You can type in a URL to a Web service or browse for one using the available links in the embedded browser window. When you enter or select a Web service, the browser window displays information about the Web service. If you provide a name for the Web service in the Web Reference Name box after selecting a Web service, this will be used as the namespace that wraps the Web service proxy class and type information within the project. This namespace will be generated as a child namespace of the default project namespace. In addition to the proxy class that lets you make calls to the Web service, class declarations are also generated for all the types that the Web service methods return. It is these type definitions that the Data Sources window lets you use for data binding.

Adding an Object Data Source

In addition to working with database objects and Web service return types, you also often want to perform data binding against objects returned from your business layer or data access layer classes. Note that these include typed data set definitions that reside in a separate assembly, which should be the normal way you design your data access components.

1.   To set up data binding to these objects through the designer, select the Object data source type in the wizard’s first step (see Figure 5.3).

2.   Select the object type that you would like to bind to (see Figure 5.10). The wizard displays the objects in a tree view that lets you drill down from the assembly level, through the namespaces, and to the types defined in those namespaces.

FIGURE 5.10: Object Type Selection Step

Object Type Selection Step

3.   If the type you want to bind to resides in an assembly that you don’t yet have a reference to, click the Add Reference button to display the corresponding dialog that lets you add a reference to the assembly to your project. This is the same Add Reference dialog that you use from Solution Explorer. When you select an assembly in it, a normal reference is added to your project.

All public types in a referenced assembly are displayed in the tree of bindable objects, and they can be displayed in the Data Sources window if selected. However, if the object type doesn’t expose any public properties, the type will be useless in the Data Sources window. By design, the Data Sources window only lets you set up data binding to public properties on an object, even if fields (member variables) are exposed as public. This is deliberate—to encourage you to follow the .NET design guidance that fields should always be private and should be wrapped by properties if they are meant for external consumption. Additionally, if there is no way to obtain an instance of an object type that has been populated with data through methods on that type or other types in the external library, then it may not be very useful for data binding anyway, unless you are going to programmatically populate instances of that type.

You can use the Data Sources window from any kind of project, including a class library project. However, since the main purpose of the Data Sources window is to generate data-bound controls or to generate the data-binding code for existing controls on a form, the window has limited utility in non-Windows Forms projects.

When you add an object data source through the Data Sources window, a couple of things happen in your project. Behind the scenes, Visual Studio adds a .datasource file under the Properties subfolder. This file contains type information about each data source object type that has been added to the project. That file is just an XML file that complies with a schema defined by Microsoft. A separate file will be created for each referenced assembly and type within it that is added as a data source. The file naming convention for these files is <namespace>.<typename>.datasource, and the content looks like the following:

<GenericObjectDataSource DisplayName="Customer" Version="1.0"
   xmlns="urn:schemas-microsoft-com:xml-msdatasource">
   <TypeInfo>CustomersBusinessLayer.Customer, CustomersBusinessLayer,
      Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</
TypeInfo>
</GenericObjectDataSource>

If you perform your data access from a data access layer class library—which I recommend for any serious application—you will need to set up data binding for controls in your Windows Forms application based on the data that your data access layer will return. That data might be in the form of typed data sets, custom business objects, data readers, or untyped data sets. The only object types that the Data Sources window can work with are those that provide enough type information about the data that they contain to generate the appropriate data-binding code. Thus, these can only be typed data sets and custom business objects that define public properties to expose the contained data.

Even if you are dealing with a typed data set, when it comes from outside your Windows Forms project, the Data Sources window considers it just another object. So to get a typed data set definition from your data access layer to display in the Data Sources window for data-binding purposes, do the following:

1.   Add a reference to the data access layer assembly.

2.   Use the Data Source Configuration wizard to add a new data source.

3.   Select the Object data source type.

4.   Select the typed data set out of the type tree shown in Figure 5.10.

Once you do this, you can use the table and field information in that data set to set up control bindings using the Data Sources window.

However, when it comes time to use that data type, you will have to populate it with data from somewhere, so you will have to write a little bit of code to obtain an instance of the object and set it as the data source on the control or component you are bound to.

Generating Bound Controls from Data Sources

One of the most tedious things to do in Windows Forms programming is to lay out a bunch of controls on a form that you intend to bind to data sources, name all the controls, and then get all the binding code wired up correctly. Luckily, because this is such a common thing you need to do, the Data Sources window automates most of it for you. You saw an example of this in Chapter 1 when we generated an entire data-bound details form for Employees through a single drag-and-drop operation from the Data Sources window.

Once you have added data sources to your project as described in the preceding sections, they display as icons in the Data Sources window. The icons displayed depend on what the current document is in the editor window of Visual Studio. You can’t really do anything with the items in the Data Sources window except browse through them if you are in a code window. It can be a handy way to refresh your memory about the data members for a particular table or object that you are coding against without having to declare a member to get IntelliSense assistance. But the main purpose of the Data Sources window is to work with the Windows Forms design surface. If you display a form in the designer, the icons next to the data sources and members change to represent the kind of controls that they will generate if you drag data items from the Data Sources tree onto a form.

There are default control associations for different types, and those associations can be customized. At the collection level (such as a data table, list of objects, or array), the default control association is a DataGridView control. At the individual data member level, the default control depends on the type of the member. Things like strings, numeric types, and Guids default to a control type of a TextBox; DateTime types default to a DateTimePicker control; and Booleans default to a CheckBox control. Custom types don’t default to a specific control, but you can set them to use a specific control type as long as there is a suitable format conversion for the default property on that control type from the data member type.

If you look back at Figure 5.1, you will see the icons for these controls depicted for the MoneyDB tables and fields, and for the parent and child objects covered earlier in this chapter. These built-in control icons used in the Data Sources window are the same as their corresponding control icon from the Toolbox window. Table 5.1 illustrates these icons and describes them.

TABLE 5.1: Data Sources Control Icons

Image

Image

When you drag an item from the Data Sources window tree of controls, a number of controls and components are generated as members of the form in the designer. First off, if the item you are dragging is a data table or a field from a data table, a typed data set member is added to the form and displayed in the component tray. Additionally, a table adapter and a binding source are generated and added to the form as well.

If the data source was created as an object data source type, then only a binding source will be generated. The binding source has its DataSource property set a Type class instance describing the actual bound object type (using the typeof operator). This gives the designer enough information about the bound object type’s metadata to let you configure data member bindings in the designer. Specifically, the metadata that matters are which public properties the object exposes. Additionally, the first collection data source that is dragged onto the form will have a binding navigator created for it, and that navigator will be associated with the binding source.

It is a little difficult to describe all of this with static pictures and text because it is a very dynamic process. Figure 5.11 shows a form in the designer after the ExchangeRates table has been dragged from the Data Sources window onto the form.

FIGURE 5.11: Generated Form Controls and Components from a Data Table

Generated Form Controls and Components from a Data Table

From that single mouse gesture, the following things happen:

•    A MoneyDBDataSet instance is added to the form to contain the data for the table.

•    An ExchangeRatesTableAdapter is added to the form to fill the data set with data.

•    A BindingSource is added to the form to bind the data set to the grid control, its data source set to the data set, and its data member set to the ExchangeRates table within the data set.

•    A BindingNavigator is added to the form and associated with the binding source.

•    A DataGridView control is added to the form because the control mapping set in the Data Sources window for the ExchangeRates data table defaults to a DataGridView control because it is a list-based data source. The DataSource property for the grid is also set to the binding source.

•    Columns are added to the grid for each of the child data item properties, and their column types and column names are set according to the type of the data item property.

•    All of the instances of controls and components are given names based on the name of the data source item it was generated from (e.g., exchangeRatesBindingSource).

•    A line of code is added to the Form.Load event handler in the main form code file to fill the data table using the table adapter.

•    Finally, an event handler is added for the Save button in the binding navigator to persist changes to the data set back to the database through a call to the Update method on the table adapter.

As a result of that single mouse gesture and all the resulting designer code generated, you have a fully functioning, complex data-bound application with running data—without writing a single line of code yourself. That’s pretty cool!

Because we haven’t covered the DataGridView control in any detail yet, I’ll save describing that example and how to customize it until the next chapter. For now, let’s look at the other kind of generated controls for data collection—the Details view. You saw a quick example of using the Details view control mapping in Chapter 1.

When you set the control type to Details for a business object or a data table in the Data Sources window, and then drag the item out onto the designer surface, a collection of controls are generated: one control for each data member that has a control mapping set for it in the Data Sources window. You also get a Label control for each data member, with its Text property set to the name of the data member. This gives you a form view like that shown in Figure 5.12.

FIGURE 5.12: Details View Generated Controls for Custom Business Object

Details View Generated Controls for Custom Business Object

Each data member gets a control generated based on the control mapping in the Data Sources window, and the name of the control is set to correspond to the data member name (e.g., someBitmapPictureBox for the SomeBitmap property on the SomeBusinessObject class in the figure, someStringTextBox for the SomeString property, someIntegerNumeric-UpDown for the SomeInteger property, and so on). Basically, what you get is a fully laid-out form with bound controls for each of the data members. This saves a ton of time dragging and dropping controls from the Toolbox and setting their properties to get them all wired up. The form will also automatically resize to fit the controls when you drag-and-drop the Details view, which keeps all of the controls nicely aligned and easy to grab within the form. Immediately after the drop, all of the generated controls are selected so that clicking and dragging on any of them will drag the entire bunch, letting you get them all positioned in the form as you like.

Like with the DataGridView mapping, you also get other objects created to manage the data source. In this case, since the data source was a business object instead of a data table, the generated components include a binding source and binding navigator, but no data set or table adapter. The binding source is only created if needed. So if a binding source already exists that is bound to the same data source, the existing binding source will be used for the data source for creating the Binding objects for the generated controls. The binding navigator is only created for the first collection that is dragged onto the form from the Data Sources window. You can always add and configure your own binding navigators later from the Toolbox.

When you set up bindings to custom objects, the DataSource of the binding source is set using the object’s type information, so it knows what the data members and types of the properties are on the object for code generation and designer purposes. However, to actually use the binding source at runtime, you have to write the code to obtain an instance of the object and set that instance as the data source of the binding source. The bound controls will then display the data contained in that object. This is true whether the object represents a business object, an object returned by a Web service, or a typed data set that is defined in another assembly.

You can also generate the controls one at a time from the Data Sources window if you prefer. If you drag an individual property from a custom object, or an individual column from a data table’s data source onto the form, only the individual corresponding control and its associated label will be created. A binding source and navigator will also be created if needed, along with a data set and table adapter if the bound data property is a member from a data table.

Selecting the Bound Control Type

As discussed earlier, the control types you first see for individual data members are defaults. You can change the control type in the Data Sources window by selecting the data member and selecting a different control type from the drop-down list (see Figure 5.13).

FIGURE 5.13: Selecting the Bound Control Type for a Data Member

Selecting the Bound Control Type for a Data Member

The list of controls presented is based on the data member’s type. Visual Studio ships with a predefined set of mappings, but you can customize those mappings. You can also set the control mapping to [None], so when you perform a drag-and-drop of that member or its parent object, no controls or columns will be generated for that member.

Customizing the Bound Control Types

The built-in controls and mappings give you a lot of flexibility for creating data-bound controls, but inevitably there will be situations where you want something different than what is set up as the defaults, or you will want to plug in your own data-bound controls. The details of how to create custom data-bound controls are discussed later in this book; let’s focus here on how to change the list of controls presented as options for items in the Data Sources window and how you can make your own controls capable of being plugged in for use in the Data Sources window.

If you select the Customize option at the bottom of the list shown in Figure 5.13, the Visual Studio Options dialog displays (see Figure 5.14). (This is the same as choosing Tools > Options in Visual Studio.) The Data UI Customization node of the Windows Forms Designer is selected in the tree of option categories. This is where you can set the mappings between data member types and the control options presented by the Data Sources window. As you can see in Figure 5.14, when you select a built-in .NET type on the left, you can select from a list of associated controls on the right. The controls selected on the right will be the ones presented as control mapping options in the Data Sources window (see Figure 5.13) for any data members of the associated type. The list also shows which control type is set as the default (indicated by (default) next to the control type name), and you can change this by using the Set Default and Clear Default buttons to the right of the list.

FIGURE 5.14: Customizing Control Mappings

Customizing Control Mappings

There is also an item in the Data type drop-down list at the top named [List], which displays the list of controls available for collections of data, such as data tables or object types that contain properties as their data members (specifically, objects that implement the IList interface). This list only contains the DataGridView control by default, because the Details view isn’t really a control type, but a Data Sources-specific control generation mechanism that will always be available for any complex type that exposes public properties.

Additionally, the Data type drop-down list contains a type of [Other], which lets you specify a list of controls that should be presented for data members that are a custom type. When you use the [Other] type and add control types to the list of options presented in the Data Sources window, you could potentially generate data-binding code that won’t compile. The type of the default binding property on the control type you select (determined by the DefaultBindingProperty attribute set on the control class definition) needs to match the data type of the bound data member to ensure that data binding will successfully compile and run. If there is a suitable type converter between the data member type and the bound control property type, you should also be okay.

To have your own custom controls available for selection in the list of control options, there are several things you have to do. First, you have to develop a custom Windows Forms control that supports either simple or complex data binding. Simple binding means that the control will have individual properties bound to individual data members. These controls will also need to support a default binding property. You can think of complex data binding being broken into two types for this discussion: lookup and table-oriented complex binding. Lookup complex data binding is the kind that ComboBox and ListBox controls support, including a data source, display member, value member, and selected item. Table-oriented complex data binding assumes the whole collection is going to be rendered in some form, so the control only needs the data source and data member. Developing controls that support these aspects is discussed in detail in Chapter 10.

If you have a custom or third-party control that is decorated with the appropriate attributes that allow it to work correctly with the Data Sources window (specifically, the control has a DefaultBindingProperty, Lookup-BindingProperties, or ComplexBindingProperties attribute on the class), you will need to add it to the Toolbox in Visual Studio before it becomes available for selection in the Data UI Customization settings shown in Figure 5.14.

You add a control to the Toolbox in one of two ways.

•   The easier way is to simply drag the DLL that contains the controls onto the Toolbox from a Windows Explorer window.

•   The second way is to right-click in the Toolbox and select Choose Items. This displays the Choose Toolbox Items dialog shown in Figure 5.15. Click on the Browse button and find the assembly containing your custom controls. Once you select that assembly, the controls within it will be added to the list and checked for inclusion in the Toolbox.

FIGURE 5.15: Customizing the Toolbox Using the Choose Toolbox Items Dialog

Customizing the Toolbox Using the Choose Toolbox Items Dialog

Once your control has been added to the Toolbox, it should show up in the list of available controls in the Data UI Customization options. You can then select the control for inclusion in the drop-down list of controls in the Data Sources window for a data member of the appropriate type.

Binding Existing Controls to Data Sources

Using the Data Sources window to generate bound controls is one easy way to set up your form and have the designer write the data-binding code for you. However, your development team may have user interface designers who lay out your forms and don’t know a thing about data binding, and other programmers who will do the coding to hook up the functionality for the forms. Or you may just prefer to do the UI design step first, getting all your controls laid out, named, and so on, and then worry about connecting them to the data later. Perhaps the data access and business layers from which your data is going to come are being developed in parallel, and the type definitions are not yet available to generate your controls from them. Whatever the case, the Data Sources window also supports generating the data-binding code for controls that already exist on a form.

The process for using this capability is very simple. If you have an existing control on the form, such as a text box or grid, you can drag an item from the Data Sources window onto that control. If the item you are dragging is of a compatible type with the binding properties of the control, the designer generates the data-binding code to bind that control’s default binding property to the data item. This process works pretty much the same when the designer creates the control too, but it uses the existing control instead of generating the control instance on the form too. So the designer still generates all the supporting components to support that data-binding scenario, including a binding source, binding navigator, as well as a data set and table adapter if the data item being dragged is a data table.

When you drag an item from the Data Sources window onto a control on the form, the mouse cursor changes to indicate what the resulting drop operation will do. When you drag a Data Sources window item onto a blank area of the form, as discussed in the beginning of this chapter, the mouse cursor shows a little plus sign to indicate that controls will be added to the form. When you drag a data item onto an existing control on the form, and that data item is compatible with the control for the purposes of data binding, the mouse cursor will display a small shortcut arrow, indicating that the drop will result in linking the data source item to the control. Finally, if the data item being dragged is not compatible with the control the mouse is over, the mouse cursor will change to a circle with a slash to indicate that drop isn’t allowed and will result in no action. Compatibility is again driven by the type of the data item or member combined with what the default data-binding properties of the control are.

For example, if you drag a data member that is a string, numeric field, or property from a data source object onto a TextBox control that is already on the form, the designer will generate a binding source with the DataSource property set to the data source object, as well as a binding navigator tied to the binding source. The designer will also generate the code to create a binding object tying the TextBox.Text property to the data member using the binding source as the data source and will add that binding object to the textbox’s DataBindings collection. If the data member’s parent object is a data table, a data set and table adapter will also be added to the form, along with the line of code in the form’s Load event handler to fill the data set using the table adapter.

If you try to drag that same field onto a DataGridView control, you’ll see a circle with a slash mouse cursor, indicating that a single-valued data member isn’t compatible for data binding with a control intended for displaying collections of data. However, if you drag the parent data table onto the grid, all the same supporting components described earlier in this section would be generated, and the grid’s DataSource property would be set to the binding source that was generated and bound to the table.

Behind the Scenes: Designer Code and Data Sources Files

Understanding what is going on behind the scenes is always important, especially when the designer-generated code isn’t doing exactly what you want. The first thing to understand is that all of the controls and components generated by the designer actions, along with the supporting code to set up data-binding objects and properties, are part of the standard Windows Forms designer-generated code. With Visual Studio 2005, this code is now placed in a separate code file using partial classes, and it isn’t displayed by default in the Solution Explorer project tree. To inspect the code that was generated as a result of designer interactions, you need to click the plus sign (+) next to the form class file in your project. Under that item, you will see a file named <formname>.Designer.cs (or .vb, depending on your language flavor), which contains all the designer-generated code. Most of the code is inside a collapsed region within that file, inside the definition for the InitializeComponent method.

The other thing that is going on behind the scenes is that any time you add a data source from outside your project using the Web server or object data source types, a data source file is added to your project to provide the ties between the type information for the object(s) in that data source and the Data Sources window. Additionally, if you customize which controls are mapped to a particular data member by selecting another control type from the drop-down list of bound control types, Visual Studio has to save that information somewhere so that you don’t have to keep reselecting it. This information is saved in those same data source files.

Data source files are XML files that are added to several different places in your project, depending on the kind of data source you are customizing the UI mappings for. If the data source type is an object, the data source files get created under a DataSources folder under the Properties folder in your Visual Studio project. The files are named based on the namespace and type of the data source object, and they have a .datasource file extension.

•    If the data source type is a Web service, the .datasource files are placed in the subfolder of the Web References folder that is created to contain the Web service proxy code.

•    If the data source is a typed data set definition that is part of your project, a .xsc file is generated and associated with the .schema definition that defines the data set.

To access this information for Web service data sources, select the Show All Files button, expand the Web reference node, drill down to the Reference.map item, and you will see the sub-items in the tree for that reference. In addition to the Reference.cs (or .vb) file that contains the generated proxy class for calling the Web service, there will also be a collection of .datasource files for each of the return types of the Web methods that are part of that service. These types also may be complex types with child object collections and data members, but only the top-level object types defined as return values from the Web service methods get data source files generated. The type definitions themselves get added to the Reference.cs file where the proxy class is placed. The Data Sources window can infer the rest of the type information about the child object types through reflection.

The following XML shows an example of a simple .datasource file for a Web service data source type:

<GenericObjectDataSource DisplayName="LonLatPt"
   Version="1.0" xmlns="urn:schemas-microsoft-com:xml-msdatasource">
   <TypeInfo>DataSources101.net.terraservice.LonLatPt</TypeInfo>
</GenericObjectDataSource>

The .datasource file contains an XML document whose root element is named GenericObjectDataSource. This is the case whether you set up the data source as an Object data source type or a Web service type. The only difference is where the .datasource file gets placed in your project. As you can see, the root element can contain several attributes that provide versioning information, a display name for the Data Sources window, and the schema information that specifies the allowable schema of the .datasources XML. The main thing that a data source file contains is a TypeInfo element, which specifies the fully qualified type name of the object that is being treated as a top-level data source. This shows up as a root node in the tree presented by the Data Sources window. Child objects and properties are determined through reflection by the Data Sources window when you expand a data source.

In addition to the TypeInfo information contained in a data source file, the file can also contain control mapping information for those data items or members that have had their control mapping changed from the default. To support this, a TypeUISetting element is added to the data source root element. A fairly complex XML schema underneath this element allows each data item or member that has a custom control mapping to have the information describing that mapping specified. This schema consists of a collection element named PropertyUISettings that contains individual PropertyUISetting elements. One of these elements is specified for each data member that has a custom control mapping set up. Underneath the PropertyUISetting element is a collection of control settings and bindable control information that sets up the mapping:

<GenericObjectDataSource DisplayName="SomeBusinessObject"
Version="1.0"
   xmlns="urn:schemas-microsoft-com:xml-msdatasource">
   <TypeInfo>DataSources101.SomeBusinessObject, DataSources101,
     Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</TypeInfo>
   <TypeUISetting>
      <PropertyUISettings>
         <PropertyUISetting Name="SomeInteger" SimpleProperty="True">
            <ControlSettings>
               <ControlSetting
                ArtifactName="Microsoft:System.Windows.Forms:Form">
                  <BindableControlInfo Name="NumericUpDown"
                   Type="System.Windows.Forms.NumericUpDown"
                   AssemblyName="System.Windows.Forms,
                   Version=2.0.0.0, Culture=neutral,
                   PublicKeyToken=b77a5c561934e089" />
               </ControlSetting>
            </ControlSettings>
         </PropertyUISetting>
      </PropertyUISettings>
   </TypeUISetting>
</GenericObjectDataSource>

If the data source is a typed data set within your Windows Forms project, you can select Show All Files in Solution Explorer, expand the typed data set (.xsd) file node, and you will see an .xsc file under it. If you have customized the control mappings for any of the data members of that data set definition through the Data Sources window, that file will contain XML similar to the following code to record those custom control mappings as part of your project:

<DataSetUISetting Version="1.00"
   xmlns="urn:schemas-microsoft-com:xml-msdatasource">
  <TableUISettings>
    <TableUISetting Name="Countries">
      <ColumnUISettings>
        <ColumnUISetting Name="Flag">
          <ControlSettings>
            <ControlSetting
             ArtifactName="Microsoft:System.Windows.Forms:Form">
              <BindableControlInfo Name="PictureBox"
               Type="System.Windows.Forms.PictureBox"
               AssemblyName="System.Windows.Forms,
               Version=2.0.0.0, Culture=neutral,
               PublicKeyToken=b77a5c561934e089" />
            </ControlSetting>
          </ControlSettings>
        </ColumnUISetting>
      </ColumnUISettings>
    </TableUISetting>
  </TableUISettings>
</DataSetUISetting>

You should never have to hand-code this stuff, though you may want to look at it if things aren’t working correctly so you can understand where the Data Sources window is getting the information that is driving the display. Usually regenerating the data source reference through the Data Source Configuration wizard is a better way to go than trying to hand-modify the XML yourself. In the case of the .xsc file, you can simply delete the file, and it will be regenerated the next time you customize the UI control mappings for any of the data set members.

Other Designer Data-Binding Code Generation

You can perform several other designer interactions that generate data-binding code but that don’t involve the Data Sources window, for example, using the Properties window and using Smart Tags on the designer form. The Properties window lets you declaratively set properties on controls in the designer, which is ultimately an interactive way of writing the code that sets those properties. But some of the interactions in the Properties window actually do a little more than just set a property; they end up writing a few lines of code that create and manage the binding objects as well. Smart Tags provide direct access to commonly used features and properties of individual controls and components.

Setting Control Data Binding Through the Properties Window

If you select a control such as a TextBox in the Forms designer, and then show the Properties window (using the View menu or the F4 default keyboard shortcut), all of the properties that can be set at design time in the designer are available in that window. There are also a number of pseudo-properties presented in the Properties window that don’t correspond one-to-one with individual properties of a control, but that let you generate or modify code through a similar interaction. For example, when you set the (Name) pseudo-property for a control in the Properties window, you aren’t actually changing a property on the instance of the class, but changing the name of the instance member variable itself. This is why it is surrounded by parentheses in the Properties window, because it doesn’t correspond to a true property on the control instance. Setting the (Name) pseudo-property results in the designer changing every designer-generated line of code using that member variable to reflect the new name. In fact, Visual Studio 2005 goes beyond that and uses its refactoring features to also locate the use of that named instance in all other code in the project and update it there as well.

Likewise, you can set several properties in the Properties window that give you a rich design experience and that also generate more than one line of code in many cases. These properties include the DataSource and DataMember properties for any complex bound control, the Display-Member and ValueMember properties for a lookup bound control like a ComboBox or ListBox, and the (DataBindings) set of pseudo-properties for any control. When you select the DataSource, DataMember, Display-Member, or ValueMember properties of a control in the Properties window, a pop-up window that looks like a miniature version of the Data Sources window displays. This window is docked to the property you are setting in the Properties window (see Figure 5.16).

FIGURE 5.16: Setting the Data Source in the Properties Window

Setting the Data Source in the Properties Window

This property editor lets you navigate through the data sources in the project much like you do in the Data Sources window. However, what is presented in the data sources tree is tailored based on the property that you are setting and what the compatible data members are within the data source. For example, Figure 5.16 shows setting the DataSource property of a ComboBox control, which is designed to be bound only to list data sources. So the tree only presents those items that implement the appropriate IList interface to be bound to that property. Notice that this includes the foreign key relationships for related data tables in a data set, as well as child object collections on other data objects. You can even create new data sources through the Data Source Configuration wizard directly from the link control at the bottom of the pop-up property editor window as well.

The current selection in other data-binding properties will further restrict what is presented while setting a particular data-binding property. For example, the DisplayMember property in Figure 5.17 only presents the individual columns or properties of the list source selected for the DataSource property of that control.

FIGURE 5.17: Setting the DisplayMember in the Properties Window

Setting the DisplayMember in the Properties Window

When you select items from the data sources tree, the designer performs similar actions to when you drag items to the form from the Data Sources window. Specifically, it creates a binding source for the data source if one doesn’t already exist. If that data source is a typed data set, the designer creates an instance of the data set as a member of the form, along with a table adapter to fill the table that was bound to the control. If a suitable binding source is already present on the form, it shows up in the data sources tree as well, and you can select that binding source as the source to prevent a second (and redundant) binding source instance from being added to the form.

Another pseudo-property that is useful for setting up data binding, especially for simple bound controls like text boxes, labels, and picture boxes, is the (DataBindings) pseudo-property. When you expand (DataBindings) in the Properties window with a control selected in the designer, the Properties window will determine what the default bound property is for the control and display that Property as a subproperty under (DataBindings) in the Properties window (see Figure 5.18).

FIGURE 5.18: (DataBindings) Properties in the Properties Window

(DataBindings) Properties in the Properties Window

If you click on the bound property, or on the Tag property that is also displayed as a subproperty under (DataBindings), a window for selecting data source items (similar to the one shown in Figure 5.17) displays. Here you can select a different data member for the binding.

You can also click on the (Advanced) subproperty, and a button with ellipses (...) will display in the subproperty. When you click that button, the Formatting and Advanced Binding dialog (shown in Figure 5.19) displays. You can use this dialog to customize the current binding or add other bindings for that control to other properties. The Binding drop-down list in the middle of the dialog allows you to select the data member for a binding using the same drop-down window that is used for DataMember properties in the Properties window. The Data Source Update Mode drop-down list lets you pick when formatting occurs; it uses the same values from the DataSourceUpdateMode enumeration that was discussed in Table 4.3 for automatic formatting using the Binding class. Depending on the type of the data member selected in the Binding drop-down list, you can select a format type from the list in the middle at the bottom, along with a corresponding type from the list on the right. This is basically setting up a formatting string for the format provider for the selected data member type. Finally, you can provide a value in the Null value text box that will be used if the data member value is null or DBNull at runtime. Ultimately, all these settings are a designer-oriented way to provide the information for the automatic formatting capabilities of the Binding class that were discussed in detail in Chapter 4. When you accept any changes made in this dialog with the OK button, code will be added to the designer code file to create appropriate Binding objects, and you should set the corresponding formatting properties on those bindings before adding them to the control’s DataBindings collection.

FIGURE 5.19: Formatting and Advanced Binding Dialog

Formatting and Advanced Binding Dialog

Generating Data Bindings with Smart Tags

Smart Tags, a new feature in the Visual Studio 2005 Forms designer, let you access commonly used property settings and actions on a control or component in the designer, instead of having to go through the Properties window or a menu item. You access a Smart Tag by clicking on the small triangular glyph that displays on the upper right edge of the control or component when you click on it (see Figure 5.20).

FIGURE 5.20: Binding Source Smart Tag

Binding Source Smart Tag

Depending on the control or component type that you select, the Smart Tag may be simple, complex, or the control may have no Smart Tag at all. The Smart Tag in Figure 5.20 shows two options at the bottom that are available in the Smart Tag of any control or component whose data source is set directly or indirectly to a typed data set. The Add Query item displays the Search Criteria Builder dialog (shown in Figure 5.21); this dialog lets you add a new query to the typed data set to which the control is bound.

FIGURE 5.21: Search Criteria Builder Dialog

Search Criteria Builder Dialog

When you enter a new query and accept it using this dialog, a new query is added to the bound table’s table adapter. You can use this query to fill the table. This query lets you specify parameterized queries that will return a set of rows to fill the table based on search criteria. If you try to enter a query that doesn’t return rows, you will get an error when you click OK that says it failed to get the schema for the query. This means that it saw that there wasn’t any data returned based on the query that you entered. You can also design the query using the Query Builder described in Chapter 2 by clicking the Query Builder button in the Search Criteria Builder dialog.

When you enter a new query using the Search Criteria Builder dialog, a new ToolStrip control is added to the form, and some supporting code is added to your source file for the form (e.g., Form1.cs). This lets you enter search criteria and execute the search, and the results are displayed in the bound control that you generated the new query from (see Figure 5.22). The generated ToolStrip includes a label for the name of the search criteria field, a text box to enter the criteria into, and a button to execute the search.

FIGURE 5.22: Form with Search ToolStrip Added by the Designer

Form with Search ToolStrip Added by the Designer

The Add Query functionality only works for typed data sets that are part of your Windows Forms projects. If you want to set up the same functionality for a data set that is defined in another assembly, such as a data access layer assembly, you will have to manually add the query to the table adapter (as described in Chapter 3), add the ToolStrip and its controls to the form manually, and then write the code to execute the query method on the table adapter and put the results in the corresponding binding source on the form. In other words, the designer is only going to help you on this one if you follow the bad design practice of putting your data access code inside your Windows Forms project. Bummer, huh?

The other item at the bottom of the Smart Tag for any control or component bound to a typed data set within the Windows Forms project is a Preview Data option. When you click on this, the Preview Data dialog shown in Figure 5.23 is presented. When you click the Preview button in the middle of the dialog, the corresponding GetData method of the table adapter will be executed and the data will be displayed in the Results section so you can preview it.

FIGURE 5.23: Preview Data Dialog

Preview Data Dialog

ComboBox and ListBox controls have Smart Tags on them that you can use to set up their data-binding properties (see Figure 5.24). You first have to check the Use data bound items box, and then the four combo boxes below that display. These each let you open the Data Sources window, as described for the DataSource and DataMember properties in the Properties window earlier.

FIGURE 5.24: ListBox Smart Tag

ListBox Smart Tag

Generating Master-Details Data-Bound Controls with the Designer

You learned how to set up master-details data binding in code in previous chapters. The designer will write all that code for you based on a couple of simple drag-and-drop gestures from the Data Sources window. Let’s step through an example to demonstrate.

1.   Start a new Windows Forms project in Visual Studio 2005.

2.   After the project is created, add a new Database data source from the Data Sources window.

3.   Select the existing Northwind database connection created earlier in the chapter, and select the Customers and Orders tables within that database in the wizard.

4.   Accept NorthwindDataSet as the data set type name in the last step of the wizard. (Review the steps at the beginning of this chapter if you need help stepping through that wizard.)

     This adds a typed data set to the project that is suitable for master-details data binding, because the Orders table has a foreign key relationship (details) to the Customers table (master), and both of those tables now display in the Data Sources window under Northwind-DataSet.

5.   If you expand the Customers table in the tree, you will see that in addition to each of the columns of the table being shown with the corresponding control type that will be generated for them (all TextBoxes in this case), there is another DataGridView item for the Orders table.

6.   Drag the Customers table from the Data Sources window onto the form, about a half-inch down and in from the top left of the form. This will generate a DataGridView control, and BindingSource, BindingNavigator, CustomersTableAdapter, and Northwind-DataSet members on the form with their data-binding code all hooked up in the designer code file.

      It also adds code to the Load event handler to fill the data set, as you have seen several times by now. This grid will be bound to the binding source, and the binding source will be bound to the Customers table in the NorthwindDataSet instance through its DataSource and DataMember properties.

7.   Grab the Orders table that is shown as a child member of the Customers table in the Data Sources window (note that there is an Orders table at the same level as Customers in the Data Sources tree, but that isn’t the one you want; you want the one that is shown as a child item under Customers, because this is the one that is exposed through the data relation linking child rows in Orders to parent rows in Customers). Drag the table onto an empty area at the bottom of the form.

This adds another DataGridView control to the form for the Orders table, and another BindingSource and OrdersTableAdapter will be added to the form as well. The second grid will be bound to the second binding source, and the second binding source will have its DataSource property set to the first binding source, which was bound to the Customers table. The DataMember for the second binding source will be set to Orders, the name of the child collection within the first binding source. An additional line of code was also added to the form Load event handler to fill the Orders table as well as the Customers table.

The net result is that by simply dragging two related items from an object hierarchy onto the form, the designer wrote master-details data-binding code for you. The same thing would happen if instead of using Customers and Orders from a typed data set, you were using a custom collection as the master, and the details were a child collection property exposed by the objects in the parent collection. Once again, this is pretty cool! Before Visual Studio 2005, you had to write a fair amount of unintuitive code to get this kind of thing working. Now, the designer writes this all for you.

Where Are We?

In the last few chapters, you have read about the basics of the data-binding mechanisms in Windows Forms and the underlying details. You have seen a number of examples of binding sets of data to grids, combo boxes, text boxes, and PictureBox controls. You learned to use binding sources to decouple your controls from the specific data sources they are bound to, giving a number of benefits for maintaining and evolving your application and for controlling the context of the data binding on the form. You saw how to set up the bindings to individual controls manually and how to configure master-details bindings by chaining together binding sources. You also learned how to avoid having to write most of that code by using the designer and the Data Sources window to generate data-binding code and controls through a few simple gestures and interactions in Visual Studio.

Some key takeaways from this chapter are:

•    The Data Sources window is your new drag-and-drop control panel for setting up Windows Forms data binding.

•    The Data Sources Configuration wizard lets you define database, Web service, and object data sources, and it creates the appropriate glue code in your Windows Forms project to be able to set up data binding to those object types.

•    Avoid using the database data source type in any large real-world project because it embeds data access code in your Windows Forms project, which tightly couples your presentation tier application to the data tier—a bad idea from a design perspective.

•    To add custom controls to the controls presented in the Data Sources window, you must first add them to the Toolbox, then customize the Data UI Customization settings by choosing Tools > Options.

I kept deferring getting into any detail on the DataGridView control because it is a complex and powerful enough control to warrant a dedicated chapter, which is where we are headed next.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.134.229