Windows Forms 2.0 Programming (Microsoft .NET Development Series)

BindingList<T>

BindingList<T>, from the System.ComponentModel namespace, is a generic implementation of IBindingList:

class BindingList<T> : IBindingList, ... {...}

BindingList<T> nicely implements the list management (AllowEdit, AllowNew, AllowRemove, and AddNew) and change notification (SupportsChangeNotification, ListChanged) functional subsets of IBindingList.[5] And because it's generic, it can turn any type into a strongly typed list data source with data-binding-savvy list management and change notification using something like the following code:

[5] Note that BindingList<T> does not implement the sorting and searching functional subsets of IBindingList. Data Binding with Windows Forms 2.0 (Addison-Wesley) by Brian Noyes has the answers.

// ComplexBindingDataGridViewForm.cs partial class ComplexBindingDataGridViewForm : Form { // Create strongly typed list data source with list management // and change notification BindingList<RaceCarDriver> raceCarDrivers = new BindingList<RaceCarDriver>(); public ComplexBindingDataGridViewForm() { InitializeComponent(); // Populate list data source with data items this.raceCarDrivers.Add(new RaceCarDriver("M. Schumacher", 500)); this.raceCarDrivers.Add(new RaceCarDriver("A. Senna", 1000)); this.raceCarDrivers.Add(new RaceCarDriver("A. Prost", 400)); this.racingCarDriversDataGridView.DataSource = this.raceCarDrivers; ... } ... }

Two-Way List Change Synchronization

Figure 16.12 illustrates BindingList<T>'s automatic support for broadcasting list change notifications when an item is added or deleted directly to or from the list.

Figure 16.12. DataGridView Automatically Reflecting List Data Source Changes Via BindingList <T>

You can see that when the Add button is clicked to add a new RaceCarDriver instance to the list data source, the change is broadcast to all bound controls (in this case, DataGridView). When DataGridView receives the notification, it repaints its UI to show the change. The same applies when an item is deleted from the list data source.

Just as both of these notifications are broadcast when the list data source is changed, BindingList<T> broadcasts similar notifications when the values of an individual item it contains are updated. Further, data binding notifications work both ways; as changes are made to cells in DataGridView or as rows are added to or removed from DataGridView, DataGridView notifies the list data source to reflect those changes. BindingList<T> responds to both DataGridView operations, but we can't add new items until we go one step further.

When a new row is added to DataGridView, no values are added to the row because the user obviously hasn't had the opportunity to enter them. Consequently, DataGridView relies on the item type of the list data source to provide the default state, which is used to populate the new row. We instantiate the item type using the default constructor. If the item type implements a default constructor, DataGridView automatically supports adding a new row; in contrast, if the default constructor is not implementedas is the case with RaceCarDriverthen DataGridView disables this capability (although DataGridView supports the ability to delete rows in either case ).

Solving this problem is simple: We make sure that our item type implements the default constructor (a constructor that takes no parameters):

// RaceCarDriver.cs class RaceCarDriver { ... // Needed so DGV can add new rows public RaceCarDriver() { // Provide default values, if needed ... } public RaceCarDriver(string name, int wins) { this.name = name; this.wins = wins; } ... }

After the default constructor has been added to the RaceCarDriver class, new items can be added via DataGridView, as shown in Figure 16.13.

Figure 16.13. Visually Adding New List Data Source Items via DataGridView

You can see how DataGridView takes advantage of the data source. First, DataGridView determines whether the bound list data source allows the addition of new items; to do that, DataGridView inspects its Boolean IBindingList.AllowNew property. In the case of BindingList<T>, AllowNew returns true if the type T has a default constructor, or false otherwise. If AllNew returns true, DataGridView adds the placeholder row.

Second, DataGridView calls IBindingList.AddNew to create a new RaceCarDriver object when a placeholder row is selected (by tabbing or mouse clicking) and adds it to the list.[6]

[6] You can derive from BindingList<T> to override AddNew and AllowNew to allow the creation of default item instances irrespective of whether the item type implements a default constructor. This technique can be handy when you don't control the item type you are working withfor example, if it is provided by a web service.

Third, DataGridView adds a new placeholder row when the new RaceCarDriver object is edited.

Two-Way Item Change Synchronization

When the values in a DataGridView row are changed, DataGridView automatically replicates the changes to the bound list data source. Similarly, when changes are made to an item in the list data source of BindingList<T>, an item change notification is broadcast to all bound controls.

For example, consider the following code, which increments the number of wins for the currently selected race car driver in the list data source:

// ComplexBindingDataGridViewForm.cs partial class ComplexBindingDataGridViewForm : Form { ... void updateCurrentButton_Click(object sender, EventArgs e) { int current = this.BindingManager.Position; ++((RaceCarDriver)this.raceCarDrivers[current]).Wins; } ... BindingManagerBase BindingManager { get { return this.BindingContext[this.raceCarDrivers]; } } ... }

By default, BindingList<T> broadcasts item change notifications. However, DataGridView visually reflects changes only to the current itemunless the changed item in the list data source implements INotifyPropertyChange. In this case, DataGridView reflects the changes appropriately. If INotifyPropertyChange is not implemented, and if an item other than the current item is changed, those changes aren't reflected in bound controls unless the controls are repainted.[7] As you would expect, the same applies to simple-bound UIs, but that is not important because you can't see anything other than the current row.

[7] If you need changes to list data source items to be reflected automatically, current or otherwise, Brian Noyes provides an implementation in his book.

Категории