Data binding is designed to make your life easier when it comes to building UIs. Standing on the shoulders of data binding and related designer support are two core styles of UI that you can use in their entirety or mix and match to build other styles of UI. These are commonly referred to as item UIs and list UIs. Item UIsWhen users need the most intuitive and informative experience possible, you should consider using an item UI. Item UIs display one data item at a time, whether from an item or list data source, and are composed using the standard form and control support you've seen throughout this book. To create an item UI, we choose the Details option for the data source from the Data Sources window, as shown in Figure 17.6. Figure 17.6. Creating an Item UI for a Data Source
For any data source with a nontrivial number of properties, such as the Northwind database's Products table, the generated UI is quite plain, as illustrated in Figure 17.7. Figure 17.7. Default Details View UI for the Products Table
But consider that the whole UI is nicely bound, and it automatically loads and saves itself. Additionally, Windows Forms has a rich set of controls and layout support that allow us to easily transform the generated original into something a little more like the UI users expect, as shown in Figure 17.8. Figure 17.8. Designed Item UI
Straightaway, you can see the whole form at once, and the large number of controls has been categorized into two tabs using a tab control. This should help users locate data quickly. Furthermore, key informationthe Product ID (top left) and the Product Name (top right)is called out using two label controls. Because the Windows Forms control and component suite is rich, there are many directions you can take a UI, although this demonstrates the basic principle. The UI is nicely laid out and functional, in a broad sense, but data binding also comes to the rescue when you face other problems. Formatting and ParsingOne of the problems with using drag-and-drop data sources is that the Windows Forms Designer has no idea how you want your data properties to be formatted, beyond the default formatting applied by bound controls. Controls like DateTimePicker allow you to specify a data format and take care of converting between what is displayed and what is stored, the latter typically being the property that's bound to the data source. Controls like Label, however, offer no such support. Consider the Product ID in our example, which is displayed in its raw integer format. Unfortunately, this format probably doesn't comply with the crazy types of formats that the accounting department is likely to come up with, which probably look more like "#0-00-00." To change the displayed format of a data source's property, you need the Formatting and Advanced Binding dialog, shown in Figure 17.9. You open it by selecting the bound control and then (DataBindings) | (Advanced) from the Properties window.[5]
Figure 17.9. Specifying a Format for a Simple BindingIn the formatting group box on this dialog, you can specify no formatting, formatting for well-known types, or custom formatting. The latter is useful when you need to mix and match formatting and alphanumeric characters in ways that aren't supported by the other options. Whichever option you choose, a sample output is displayed. Additionally, you can specify what to display in the event that the data source's property is null. When you click OK, the Windows Forms Designer updates the simple-binding code it generates into InitializeComponent. The updated code uses an overload of the Binding object's constructor that accepts a Boolean to specify whether formatting is enabled (true by default) and both a null value and a format string: // ItemUIForm.Designer.cs partial class ItemUIForm { ... void InitializeComponent() { ... this.productIDLabel.DataBindings.Add( new Binding( "Text", // Bound property this.productsBindingSource, // Data source "ProductID", // Data source property true, // Formatting enabled? DataSourceUpdateMode.Never, // Label is read-only, so no updates "<No Product ID>", // Null value "\\#0-00-00"));// Format string ... } ... } The result is shown in Figure 17.10. Figure 17.10. Format String Applied to Product ID
Formatting the Product ID declaratively is easy, because it is exposed from the data source as a simple type and is displayed via a read-only label. In some scenarios, however, your data format may be more complex than you can process with a simple formatting string. For example, consider the UnitPrice property of the Products data source. Depending on which currency you are dealing with, this property may be equal in worth to bananas and formatted accordingly: // ItemUIForm.Designer.cs partial class ItemUIForm { ... void InitializeComponent() { ... this.unitPriceTextBox.DataBindings.Add( new Binding( "Text", this.productsBindingSource, "UnitPrice", true, DataSourceUpdateMode.OnValidation, "No Bananas", // Null value "0.00 Bananas")); // Format string ... } ... } This yields Figure 17.11. Figure 17.11. Format String Applied to UnitPrice
Figure 17.11 shows the problem: UnitPrice is displaying the current value as a plural (Bananas), even though it should be singular (Banana). Because formatting support available from the Formatting and Advanced Binding dialog doesn't support the degree of complexity we're after, we need to write our own code. This task is made easier if we handle Format, a special event implemented by the Binding object for this purpose. The Format event is fired while data is being shuttled from the data source to the bound control, and it's at this moment and from this event that we execute our custom formatting code to ensure that UnitPrice is either singular or plural as required. Specifically, we register and handle the Format event for the UnitPrice text box's bound Text property: // ItemUIForm.cs partial class ItemUIForm : Form { public ItemUIForm() { InitializeComponent(); this.unitPriceTextBox.DataBindings["Text"].Format += unitPriceTextBox_Format; } void unitPriceTextBox_Format(object sender, ConvertEventArgs e) { // Bail if data source's property is null if( e.Value == DBNull.Value ) return; // Format data source value and concatenate with " Banana" // and pluralize if necessary string unitPrice = string.Format("{0:0.00 Banana}", e.Value); if( (decimal)e.Value != 1 ) unitPrice += "s"; e.Value = unitPrice; } ... } The Format event handler is passed ConvertEventArgs, which allows you to alter the value pulled from the data source if needed.[6] In this case, we check to see what the UnitPrice value is and pluralize if appropriate, as shown in Figure 17.12.
Figure 17.12. Custom Formatting to Display Plurals Correctly
If you do apply a custom format using the Advanced Formatting and Binding dialog, the Formatting and Advanced Binding dialog shown earlier in Figure 17.9 warns you of the potential for data conversion issues. As long as users enter a value that .NET can convert to the list data source property's type, such as a decimal for UnitPrice, the entered value is converted when data binding copies it to the list data source. However, if the value can't be converted to the underlying data source's type, the data is considered invalid. To cope with this, we transform the entered value into a type that matches the type of the data source property on its way back to the data source. For this, we have the Parse event: // ItemUIForm.cs using System.Text.RegularExpressions; ... partial class ItemUIForm : Form { public ItemUIForm() { ... this.unitPriceTextBox.DataBindings["Text"].Parse += unitPriceTextBox_Parse; } ... void unitPriceTextBox_Parse(object sender, ConvertEventArgs e) { // Bail if value not entered if( string.IsNullOrEmpty((string)e.Value) ) return; // Extract first number from value and convert to decimal string unitPrice = (string)e.Value; Match match = Regex.Match(unitPrice, @"\d+(.\d{1,2})?"); e.Value = decimal.Parse(match.Value); } } Here we use regular expressions to extract a numeric value from the value entered into the unit price text box, if one was entered, before sending it back to the data source. The entered value is passed to the Parse event handler in the second argument, which, as with the Format event handler, is of type ConvertEventArgs. You don't need to handle the Format event to handle the Parse event; if a custom format is simple enough that it can be specified in the Formatting and Advanced DataBinding dialog, you can set it there and simply handle the Parse event on the way for the return trip, as required. Furthermore, you don't have to handle the Parse event for read-only values because the only values that need parsing are those that users can change. If your data source is created from a custom type, you can bundle formatting and parsing functionality into the type itself by using a custom type converter, which is discussed later in this chapter. ValidationBefore data is parsed and sent back to the data source, it should be validated to ensure it's of a certain type, in a certain range and, possibly, formatted in a certain way. To provide this certainty, your form should be validating itself using the techniques described in Chapter 3: Dialogs. You also need to make sure that the data source is not updated until the control's value has been validated. When a data source is updated is determined by the Binding object's DataSourceUpdateMode property, which can be one of three possible DataSourceUpdateMode enumeration values: enum DataSourceUpdateMode { Never = 2, // Never update OnPropertyChanged = 1, // Update when control property changes OnValidation = 0 // Update after control Validated is fired (Default) } By default, this property is set to OnValidation, which means that the data source is only updated after a bound control's validated event is raised.[7] You can set the DataSourceUpdateMode to be OnPropertyChanged to eschew validation and update as soon as the bound control property is changed, or you can set it to None to prevent any data source updates at all.
DataSourceUpdateMode can be configured declaratively from the Formatting and Advanced Binding dialog for the binding, as shown in Figure 17.13. Figure 17.13. Setting a Binding's Data Source Update ModeWhichever value you specify, the Windows Forms Designer generates the code that passes the DataSourceUpdateMode value to the appropriate Binding object's constructor: // ItemUIForm.Designer.cs partial class ItemUIForm { ... void InitializeComponent() { ... this.unitPriceTextBox.DataBindings.Add( new Binding( "Text", this.productsBindingSource, "UnitPrice", true, DataSourceUpdateMode.OnValidation, "No Bananas", "0.00 Bananas")); ... } ... } Choosing OnValidation means that we should handle the Validating event and, if the data is invalid, signal the Parsing event appropriately, like so: // ItemUIForm.cs using System.Text.RegularExpressions; ... partial class ItemUIForm : Form { ... void unitPriceTextBox_Validating(object sender, CancelEventArgs e) { // Check if unit price is a number string unitPrice = this.unitPriceTextBox.Text; Match match = Regex.Match(unitPrice, @"\d+(.\d{1,2})?"); // If not correctly formatted, show error string message = null; decimal result; if (!decimal.TryParse(match.Value, out result)) { message = "Unit Price must be in this format: 0.00 Bananas"; e.Cancel = true; } this.errorProvider.SetError(this.unitPriceTextBox, message); } void unitPriceTextBox_Parse(object sender, ConvertEventArgs e) { // Bail if value is invalid if (this.errorProvider.GetError(this.unitPriceTextBox) != "") { return; } // Extract first number from value and convert to decimal string unitPrice = (string)e.Value; Match match = Regex.Match(unitPrice, @"\d+(.\d{1,2})?"); e.Value = decimal.Parse(match.Value); } } Entering data into text boxes should be a free-form experience, which is why validation is absolutely required when you use them, although you may also consider masked text boxes as a way to ensure data is entered in a specific format. However, if a data source property can only be one of several values, you can use lookup lists in the UI to allow the user to choose just those values. LookupsWhere specific values are required, as compared with specifically formatted values, drop-down lists of valid options come in handy. For example, the Products table has a SupplierID field that accepts a number that uniquely identifies the supplier of a particular product. The trouble is that unless users have savant talents, they are unlikely to remember numeric identifiers for all possible suppliers. We can help by displaying human-readable supplier names and converting the selected one into a number that can be stored in the underlying data source; the result is commonly known as a lookup. To create a lookup, we first need a data source. Northwind comes complete with a Suppliers table, so we simply use the Data Source Configuration Wizard to turn the table into a data source, as shown in Figure 17.14. Figure 17.14. Suppliers Data Source
The Product table's SupplierID column is a foreign key to the Suppliers table, as reflected by the Suppliers data source. Thus, we can leverage the relationship to display the human-readable Suppliers.CompanyName field while actually storing Suppliers.SupplierID. We begin by dropping a BindingSource component onto a form and setting its DataSource property to the Suppliers data source. A typed data set for the Northwind data source already exists on the form, but configuring BindingSource adds a Suppliers table adapter to the form, and the table adapter fills the Northwind data source's Suppliers data member. The result is shown in Figure 17.15. Figure 17.15. Creating the BindingSourceBy now, you should be familiar with the code generated by the Windows Forms Designer to hook the BindingSource to a data source and automatically fill it with data from the database: // ItemUIForm.Designer.cs partial class ItemUIForm { ... void InitializeComponent() { ... // SuppliersBindingSource this.SuppliersBindingSource.DataMember = "Suppliers"; this.SuppliersBindingSource.DataSource = this.NorthwindDataSet; ... } } // ItemUIForm.cs partial class ItemUIForm : Form { ... void ItemUIForm_Load(object sender, EventArgs e) { // TODO: This line of code loads data into the // 'NorthwindDataSet.Suppliers' table. You can move // or remove it, as needed. this.suppliersTableAdapter.Fill(this.NorthwindDataSet.Suppliers); ... } ... } Because a combo box control is a great way to present a list of options, we first replace the default drag-and-drop text box with a combo box. To then turn the combo box into a lookup, we perform the following steps, using the combo box Properties window or smart tag:
As a shortcut, you can drag the data source (Suppliers) from the Data Sources window directly onto the combo box; the Windows Forms Designer ensures that the combo box is configured to be filled appropriately, starting with setting the combo box control's DataSource property. Further, the ValueMember field is set to the first field in the data source that's part of a primary key, or, if no primary key exists, the first field is used. DisplayMember is automatically configured to be the first string field that's not part of the primary key; otherwise, it is set to the first field of any type that's not part of the primary key or, failing that, the first column if no primary key exists. The Windows Forms Designer leaves one real step for us, which is to bind the SelectedValue property to the desired property on the data BindingSource whose value will be updated when an item is selected in the combo box (productsBindingSource.SupplierID).[8]
The combo box's smart tag in Figure 17.16 shows the resulting configuration. Figure 17.16. Turning a Combo Box into a Lookup
Note that if the Windows Forms Designer's defaults for DisplayMember and ValueMember are not to your liking, you can change them from the smart tag or Properties window for the combo box. To do that, you select the desired field from the respective drop-down lists. The Windows Forms Designer produces the following code to hook up the bindings as specified: // ItemUIForm.Designer.cs partial class ItemUIForm { ... void InitializeComponent() { ... // productsBindingSource this.productsBindingSource.DataMember = "Products"; this.productsBindingSource.DataSource = this.NorthwindDataSet; ... // SuppliersBindingSource this.SuppliersBindingSource.DataMember = "Suppliers"; this.SuppliersBindingSource.DataSource = this.NorthwindDataSet; ... // suppliersComboBox this.suppliersComboBox.DataBindings.Add( new Binding( "SelectedValue", // Bound property this.productsBindingSource, // Data source "SupplierID", // Data source property true)); // Formatting enabled? this.suppliersComboBox.DataSource = this.SuppliersBindingSource; this.suppliersComboBox.DisplayMember = "CompanyName"; this.suppliersComboBox.ValueMember = "SupplierID"; ... } } Figure 17.17 illustrates the binding relationships between the combo box and the products binding source, the source of the actual data. Figure 17.17. Turning a Combo Box into a Lookup: Binding to the Actual DataFigure 17.18 illustrates the binding relationships between the combo box and the suppliers binding source, the source of the lookup data. Figure 17.18. Turning a Combo Box into a Lookup: Binding to Lookup DataNote that binding the combo box's SelectedValue property to the Country property on the Suppliers BindingSource has the added advantage of making sure that only legal valuesthose sourced from LookupItem.Valueset the bound property value. On that note, you might also consider setting the combo box control's DropDownStyle property to DropDownList to prevent user entry in the combo box's text box. Figure 17.19 shows the combo box lookup in action. Figure 17.19. ComboBox Lookup Table in Action
Controls that provide lookup support include ComboBox and ListBox. List UIsItem UIs, as you've just seen, allow you to present complex data with a consistent Windows user experience by leveraging all that Windows Forms has to offer. By their nature, item UIs allow you to focus on a single record at a time. However, if your UI needs to support mass data entry, such as maintaining a product list, you should consider a list UI, a UI style that's designed around a grid style of control to maximize keyboard use for efficient navigation and data entry. Recall from Chapter 16 that the default Data Sources window drag-and-drop binding option is to establish a bound list UI, as shown in Figure 17.20. Figure 17.20. Specifying a Data Source to be List View
After dragging the data source onto a form, and with a bit of layout jiggling, you end up with the form shown in Figure 17.21. Figure 17.21. Products List UI FormThis form is wide, but users can quickly navigate through the various columns, enter new rows, and change values, all without leaving the keyboard. A side effect of using the list UI model is that you can't leverage standard Windows Forms support for formatting, parsing, validation, and lookups. Fortunately, DataGridView offers alternative support for all these features. Formatting and ParsingYou can declaratively set the format of any field by configuring the DataGridView column that's bound to the desired data source property. The easiest way to configure the format is to select DataGridView in the Windows Forms Designer, open its smart tag, and choose Edit Columns | DesiredColumnName | DefaultCellStyle to open the CellStyle Builder dialog. This dialog allows you to specify a variety of column-specific information, including entering a format string straight into the Format property. Or you can use the Format String dialog, shown in Figure 17.22, which you open using the Format property's ellipses button. Figure 17.22. Format String Dialog for DataGridView Column Formatting
As with the Formatting and Advanced Bindings dialog you saw earlier, you can provide a string to be displayed if the data source property is null and select from a range of off-the-shelf formatting choices. Additionally, you can specify both a custom format and a null value. Your null value and formatting choices are applied by the Windows Forms Designer to InitializeComponent: // ListUIForm.Designer.cs partial class ListUIForm { ... void InitializeComponent() { ... // unitPriceColumn this.unitPriceColumn.DataPropertyName = "UnitPrice"; unitPriceColumnCellStyle.Format = "$0.00 Bananas"; unitPriceColumnCellStyle.NullValue = "No Bananas"; this.unitPriceColumn.DefaultCellStyle = unitPriceColumnCellStyle; this.unitPriceColumn.HeaderText = "UnitPrice"; this.unitPriceColumn.Name = "UnitPrice"; ... } } DataGridView column style and formatting are specified with a special DataGridViewCellStyle object that's attached to the DataGridView column. In this case, both the format string and the null value are stored in DataGridViewCellStyle's Format and NullValue properties, respectively. As with item UI formatting, you need to consider taking over when the approach specified by the Windows Forms Designer doesn't handle unusual situations. Recall our example that formatted the UnitPrice as bananas and correctly pluralized it. To apply the same thing to a bound DataGridView column, you handle DataGridView's CellFormatting event: // ListUIForm.cs partial class ListUIForm : Form { ... void productsDataGridView_CellFormatting( object sender, DataGridViewCellFormattingEventArgs e) { // Don't format if value is null if( e.Value == null ) return; // Get DataGridView column DataGridViewColumn clm = this.productsDataGridView.Columns[e.ColumnIndex]; // If unit price column if( clm.DataPropertyName == "UnitPrice" ) { // Format data source value and concatenate with " Banana" // and pluralize if necessary string unitPrice = string.Format("{0:0.00 Banana}", e.Value); if( (decimal)e.Value != 1 ) unitPrice += "s"; e.Value = unitPrice; // Signal that we've formatted this value e.FormattingApplied = true; } } } CellFormatting is passed a DataGridViewCellFormattingEventArgs object, which exposes the ColumnIndex property that identifies the column in which data was entered. Because the format is the same for all the cells in a column, you can use ColumnIndex to determine which format needs to be applied to which column, if at all. If it turns out to be the right column, you can format that value as necessary and pass it to the cell via DataGridViewCellFormattingEventArgs.Value.[9]
Additionally, you need to set DataGridViewCellFormattingEventArgs.FormattingApplied to prevent any further formatting. If you set this property to false, the default format string overrides your programmatic efforts. Figure 17.23 shows the results of our custom formatting code. Figure 17.23. Custom Formatting to Display Plurals Correctly
If you do use a custom format, the Format String dialog from Figure 17.22 warns you of the potential data-conversion issues. As long as the user enters either a raw integer or a decimal value for the Unit Price (or any value that conforms to the format), the entered value is nicely converted to the underlying data source's decimal type when the value of the data grid view column cell is copied to it. However, if the value cannot be converted to the underlying data source's type, you receive a truly gruesome exception shown in Figure 17.24. Figure 17.24. The Exception That's Displayed When Unconvertible Data Is EnteredThis error is likely because users will try to enter a value that looks like the formatted value rather than a value that needs to be converted. In this sense, formatting seems counterintuitive, but, as with item UIs, you can increase intuitiveness by using CellParsing, DataGridView's version of Binding.Parsing. // ListUIForm.cs partial class ListUIForm : Form { ... void productsDataGridView_CellParsing( object sender, DataGridViewCellParsingEventArgs e) { // Get DataGridView column DataGridViewColumn clm = this.productsDataGridView.Columns[e.ColumnIndex]; // If unit price column if( clm.DataPropertyName == "UnitPrice" ) { // Extract first number from value and convert to decimal string unitPrice = (string)e.Value; Match match = Regex.Match(unitPrice, @"\d+(.\d{1,2})?"); e.Value = decimal.Parse(match.Value); // Signal that we've parsed this value e.ParsingApplied = true; } } ... } A DataGridViewCellParsingEventArgs object is passed to CellParsing to help you determine whether the value is from a column that you are trying to parse. If the correct column is found, you parse the value entered into DataGridView's cellin this case, the first numeric value in the user-provided valueand convert it to the data source property's decimal type. You also signal that you've parsed the value by setting the ParsingApplied property of the DataGridViewCellParsingEventArgs object to true, because it's false by default. If you don't set this property to true, DataGridView treats the value as unparsed even if you parsed it, and the exception is raised. As with any custom parsing, it helps to ensure that the value being parsed is something we can actually parse. This is where validation comes into play. ValidationDataGridView fires the CellValidating event, which you can handle to validate a cell before it is parsed: // ListUIForm.cs partial class ListUIForm : Form { ... void productsDataGridView_CellValidating( object sender, DataGridViewCellValidatingEventArgs e) { // Don't format if value is null if( e.FormattedValue == null ) return; // Get DataGridView column DataGridViewColumn clm = this.productsDataGridView.Columns[e.ColumnIndex]; // If unit price column if( clm.DataPropertyName == "UnitPrice" ) { string unitPrice = (string)e.FormattedValue; // Check whether unitPrice is a number Match match = Regex.Match(unitPrice, @"\d+(.\d{1,2})?"); // If not correctly formatted, show error and // prevent focus leaving cell decimal result; if( !decimal.TryParse(match.Value, out result) ) { MessageBox.Show( "Unit Price must be in this format: 0.00 Bananas"); e.Cancel = true; } } } } As with most DataGridViewCellXxx events, the CellValidating event handler is passed an argument that exposes a ColumnIndex for column identification, this time via a DataGridViewCellValidatingEventArgs object. This example uses regular expression to check the formatting. If the formatting is invalid, we let the user know and set the DataGridViewCellValidatingEventArgs object's Cancel property to true, indicating that the cell value is invalid. This also prevents the user from leaving the cell, and avoid displaying the ghastly exception dialog. LookupsAs with item UIs, one way to ensure that users provide the right data is to give them a list of options using a lookup. Again, human-readable text is easier for users to deal with than raw numeric identifiers like Supplier ID. The first step in creating a lookup column for a DataGridView is to ensure that the desired lookup column is of type DataGridViewComboBoxColumn. You specify this in DataGridView's Edit Columns dialog, shown in Figure 17.25, which you open via DataGridView's smart tag. Figure 17.25. Replacing DataGridViewTextBoxColumn with DataGridViewComboBoxColumnYou next set the desired column's ColumnType property to DataGridViewComboBoxColumn. Then, you configure the column much as you did when establishing lookups for item UIs: You specify a lookup BindingSource (Suppliers), a display member, and a value member, as shown in Figure 17.26.[10]
Figure 17.26. Configuring the ComboBox to Be a Lookup
When you create a DataSource, you can either choose a BindingSource that's already on the form or refer to a project data source. In the latter case, the Windows Forms Designer creates the BindingSource for you. When you click OK to commit the lookup configuration, the Windows Forms Designer generates the following code to InitializeComponent to make sure that the data grid view column and lookup BindingSource are hooked up appropriately: // ListUIForm.Designer.cs partial class ListUIForm { ... void InitializeComponent() { ... // NorthwindDataSet this.NorthwindDataSet.DataSetName = "NorthwindDataSet"; ... // suppliersBindingSource this.suppliersBindingSource.DataMember = "Suppliers"; this.suppliersBindingSource.DataSource = this.NorthwindDataSet; ... // productsDataGridView this.productsDataGridView.DataSource = this.productsBindingSource; this.productsDataGridView.Columns.Add(this.supplierIDColumn); ... // supplierIDColumn this.supplierIDColumn.DataSource = this.suppliersBindingSource; this.supplierIDColumn.DataPropertyName = "SupplierID"; this.supplierIDColumn.DisplayMember = "CompanyName"; this.supplierIDColumn.ValueMember = "SupplierID"; ... } } Figure 17.27 shows how the generated code binds the DataGridView to the products binding source, with the SupplierID DataGridView column bound to the SupplierID data source property. Figure 17.27. Turning a DataGridView Combo Box Column into a Lookup: Binding to the Actual DataFigure 17.28 illustrates the binding relationships between the SupplierID combo box column and the suppliers binding source, the lookup data source. Figure 17.28. Turning a DataGridView Combo Box Column into a Lookup: Binding to Lookup DataAdditionally, if the data source is a typed data set, the typed table adapter for the data source is also placed onto a form. Code is injected into your form's Load event to populate the data sourceand, implicitly, the bound drop-downsfrom the database. // ListUIForm.Designer.cs partial class ListUIForm : Form { ... void ListUIForm_Load(object sender, EventArgs e) { ... // TODO: This line of code loads data into the // 'NorthwindDataSet.Suppliers' table. You can move, // or remove, it, as needed. this.suppliersTableAdapter.Fill(this.NorthwindDataSet.Suppliers); } } However, because we've configured the column to be a read-only lookup, the Windows Forms Designer is smart enough not to generate the code to update the data source. Additionally, the DataGridView is smart enough to use the drop-down's selected value, specified by ValueMember, as the bound property value that's passed back to the data source. Figure 17.29 shows a lookup in operation. Figure 17.29. A Fully Operational DataGridView Column Lookup
Unlike item UI lookups built from the ComboBox control, DataGridViewComboBoxColumn doesn't support text editing, whether you select its display style to be DropDownButton (the default) or ComboBox. This means that users can select data only from the lookup. You can also specify a display style as None, hiding the drop-down button until the column is edited. When a DataGridView column becomes editable depends on DataGridView's EditMode property, which is of type DataGridViewEditMode: namespace System.Windows.Forms { enum DataGridViewEditMode { EditOnEnter = 0, EditOnF2 = 3, EditOnKeystroke = 1, EditOnKeystrokeOrF2 = 2, // Default EditProgrammatically = 4 } } You'll likely be happy with the default, although you may need to use visual aids or thorough help documentation to ensure that your users are aware of the less obvious F2 option. |