Windows Forms 2.0 Programming (Microsoft .NET Development Series)
Logically, data binding is the association of object properties with control properties, facilitated by a data binding engine. Conceivably, you could create a data binding engine using your own code. For example, consider the following RaceCarDriver class: // RaceCarDriver.cs class RaceCarDriver { string name; int wins; public RaceCarDriver(string name, int wins) { this.name = name; this.wins = wins; } public string Name { get { return this.name; } set { this.name = value; } } public int Wins { get { return this.wins; } set { this.wins = value; } } }
The idea is to allow editing of the RaceCarDriver object's properties via two text box controls on the form. To do that, we need to maintain synchronicity between the values of the Text properties of both text controls and the Name and Wins properties of the RaceCarDriver object. First, we copy the RaceCarDriver object's initial state to the text box controls: // TheLandBeforeDataBindingForm.cs partial class TheLandBeforeDataBindingForm : Form { RaceCarDriver raceCarDriver = new RaceCarDriver("M Schumacher", 500); public TheLandBeforeDataBindingForm() { InitializeComponent(); // Copy initial RaceCarDriver state to text box controls this.nameTextBox.Text = this.raceCarDriver.Name; this.winsTextBox.Text = this.raceCarDriver.Wins.ToString(); } }
Because TextBox.Text is a string and RaceCarDriver.Wins is an integer, we must also coerce the Wins data from an integer to a string. The RaceCarDriver object's initial values are displayed, as shown in Figure 16.1. Figure 16.1. Text Boxes Displaying Initial RaceCarDriver Object State
Second, because it's possible to change the text box values after they're loaded, we need to notice when those changes are made and copy the new values back to the RaceCarDriver object. Because we're using text box controls, we can notice the changes via the TextChanged event: // TheLandBeforeDataBindingForm.cs partial class TheLandBeforeDataBindingForm : Form { ... public TheLandBeforeDataBindingForm() { ... // Detect changes to text box controls this.nameTextBox.TextChanged += this.nameTextBox_TextChanged; this.winsTextBox.TextChanged += this.winsTextBox_TextChanged; } void nameTextBox_TextChanged(object sender, EventArgs e) { this.raceCarDriver.Name = this.nameTextBox.Text; } void winsTextBox_TextChanged(object sender, EventArgs e) { this.raceCarDriver.Wins = int.Parse(this.winsTextBox.Text); } } This step also requires coercing the Wins data, this time from a string to an integer, as the data is copied from the text box to the RaceCarDriver object. Now, consider the following scenario, in which the RaceCarDriver object is changed directly: // TheLandBeforeDataBindingForm.cs partial class TheLandBeforeDataBindingForm : Form { ... void addWinButton_Click(object sender, EventArgs e) { ++this.raceCarDriver.Wins; // Don't forget to update the Wins text box! this.winsTextBox.Text = raceCarDriver.Wins.ToString(); } }
In this example, we manually update our view of the object's state in the text boxes whenever the RaceCarDriver object's state is changed. Unfortunately, that doesn't work if the RaceCarDriver object's state is changed by the object itself or by other code that doesn't know about the text boxes. To combat this problem, we update the Text property's value on the relevant text box controls in the event that the RaceCarDriver object's state changes. The preferred way to do this is for a type to implement INotifyPropertyChanged (from the System.ComponentModel namespace):[1] [1] .NET also supports change notification through the implementation of PropertyNameChanged events, although this technique doesn't scale as well as implementing INotifyPropertyChanged. // RaceCarDriver.cs class RaceCarDriver : INotifyPropertyChanged { ... public event PropertyChangedEventHandler PropertyChanged; public string Name { get { return this.name; } set { this.name = value; this.OnPropertyChanged("Name"); } } public int Wins { get { return this.wins; } set { this.wins = value; this.OnPropertyChanged("Wins"); } } // Helper void OnPropertyChanged(string propertyName) { if( this.PropertyChanged != null ) { this.PropertyChanged( this, new PropertyChangedEventArgs(propertyName)); } } }
The PropertyChanged event is passed a PropertyChangedEventArgs object through which you specify which property changed. The form then handles the PropertyChanged event to update the text boxes without building internal knowledge of how a RaceCarDriver object manages its state or when it is changed by a third party beyond our control: // TheLandBeforeDataBindingForm.cs partial class TheLandBeforeDataBindingForm : Form { ... public TheLandBeforeDataBindingForm() { ... // Detect changes to text box controls this.nameTextBox.TextChanged += this.nameTextBox_TextChanged; this.winsTextBox.TextChanged += this.winsTextBox_TextChanged; // Detect changes to RaceCarDriver object this.raceCarDriver.PropertyChanged += raceCarDriver_PropertyChanged; } ... void nameTextBox_TextChanged(object sender, EventArgs e) { this.raceCarDriver.Name = this.nameTextBox.Text; } void winsTextBox_TextChanged(object sender, EventArgs e) { this.raceCarDriver.Wins = int.Parse(this.winsTextBox.Text); } void raceCarDriver_PropertyChanged( object sender, PropertyChangedEventArgs e) { switch( e.PropertyName ) { case "Name": this.nameTextBox.Text = this.raceCarDriver.Name; break; case "Wins": this.winsTextBox.Text = this.raceCarDriver.Wins.ToString(); break; } } }
Now, when the Add Win button is pressed, we simply increase the RaceCarDriver object's Wins property: // TheLandBeforeDataBindingForm.cs partial class TheLandBeforeDataBindingForm : Form { ... void addWinButton_Click(object sender, EventArgs e) { // Causes the RaceCarDriver object's WinsChanged event to fire, // which is used to keep the Wins text box up-to-date ++this.raceCarDriver.Wins; } ... }
In turn, this fires the PropertyChanged event, which is finally caught by the form to update its Wins text box. At this point, we've written code that synchronizes and converts data between the text boxes and the RaceCarDriver object bidirectionally, both initially and in the face of subsequent changes on either side of the relationship, as illustrated in Figure 16.2. Figure 16.2. Text Box Controls Bidirectionally Synchronized with a RaceCarDriver Object
In this way, we have logically bound the Text properties of the Name and Wins text boxes to the Name and Wins properties of the RaceCarDriver object, as illustrated in Figure 16.3. Figure 16.3. Text Box Controls Logically Bound to a RaceCarDriver Object
Unfortunately, we have spent quite a lot of time writing our binding code, and the result requires intimate knowledge of RaceCarDriver. What we need is a generic way to bind any controls to any type of object, which automatically handles both bidirectional synchronization and data conversion. Happily for all, the data binding engine that's built right into Windows Forms provides exactly this support. |
Категории