Showing posts with label data binding in c#. Show all posts
Showing posts with label data binding in c#. Show all posts

Monday, September 19, 2011

Windows Forms Data Binding - Data Binding Interfaces


.NET provides a standard set of interfaces related to data binding. Each of these interfaces is described next:
IList
Any class that implements the IList interface must support a list of homogenous types. That is, all list items must be of the same type. The first item in the list always determines the type. Some of the base classes that implement IList include Array, ArrayList, CollectionBase, DataView, and DataViewManager.
Typed IList
Similar to IList, the list must be of homogenous types. However, the type of the items in the list must be known at compile time.
IList and IComponent
When a class implements both IList and IComponent, the class can be data bound at design time.
IListSource
This interface allows an object to “act” like a list for data binding purposes. The implemented object is not an instance of IList, but it should be able to provide one. The DataSet and DataTable classes both implement this interface. IListSource provides a single property and a single method, which are described next:
ContainsListCollection: Indicates whether the collection is a collection of IList objects. For the DataSet implementation, this property returns True, because the DataSet class contains a collection of collections. For the DataTable implementation, this property returns False, because the DataTable class contains a collection of objects. In simple terms, implement this property to indicate how deep to go for returning a bindable list.

GetList: Returns the IList object that will be data-bound. The DataSet class uses this property to return a DataViewManager object. The DataTable class uses this property to return a DataView object.
ITypedList
This interface allows a collection object to expose its items’ properties. This interface is useful in situations where the public properties of the collection object should be different from the properties available for data binding. This interface is also necessary during complex binding when a list is empty but you still need to know the property names of the list items. (Remember, the IList class alone uses the data type of the first item in the list.) This is useful when columns headers should be created for empty lists.
IBindingList
This interface offers change notification when the list or list items have changed. There is one property, SupportsChangeNotification, which indicates whether this interface’s ListChanged event should be raised. The ListChangedEventArgs class contains a property named ListChangedType that describes the type of change that occurred. The available ListChangedType enumeration members are as follows:
ItemAdded: An item has been added to the list. The index of the new item is the value of the NewIndex property of the ListChangedEventArgs class.
ItemChanged: An item in the list has been changed. The index of the changed item is the value of the NewIndex property of the ListChangedEventArgs class.
ItemDeleted: An item has been removed from the list. The index of the deleted item is the value of the NewIndex property of the ListChangedEventArgs class.

ItemMoved: An item has been moved to another location within the list. The previous index is the value of the OldIndex property of the ListChangedEventArgs class. The new index is the value of the NewIndexproperty of the ListChangedEventArgs class.
PropertyDescriptorAdded: A PropertyDescriptor object has been added.
PropertyDescriptorChanged: A PropertyDescriptor object has been changed.
PropertyDescriptorDeleted: A PropertyDescriptor object has been deleted.
Reset: The list has a lot of changes and controls should refresh themselves.
IEditableObject
This interface supports transaction-like operations. It allows objects to specify when changes should be made permanent. Hence, it allows changes to be rolled back. The DataGrid control is one control that opts to call methods of this interface. The following methods are defined in this interface:
BeginEdit: Signals that an edit operation has started. Any changes to the object should be temporarily stored after this method has been called. When implementing this method, be sure that back-to-back calls are non-destructive. That is, the method itself should not cause any changes to any temporary objects.
CancelEdit: Cancels any changes made after the BeginEdit call. In other words, all temporary objects can be destroyed when this method is called.
EndEdit: Commits any changes made after the BeginEdit call. Once this method is called, changes cannot and should not be rolled back.

The following example illustrates the
IEditableObject class with an implementation of a Customer class.
using System.ComponentModel;
public class Customer : IEditableObject
{
 
    private bool _transactionStarted = false;
 
    private string _originalFirstName;
 
    private string _originalLastName;
 
    private string _originalPhoneNumber;
 
    public string FirstName
    {
        get
        {
            return _firstName;
        }
        set
        {
            _firstName = value;
        }
    }
 
    public string LastName
    {
        get
        {
            return _lastName;
        }
        set
        {
            _lastName = value;
        }
    }
 
    public string PhoneNumber
    {
        get
        {
            return _phoneNumber;
        }
        set
        {
            _phoneNumber = value;
        }
    }
 
    public void BeginEdit()
    {
        if (!_transactionStarted)
        {
            _transactionStarted = true;
            _originalFirstName = _firstName;
            _originalLastName = _lastName;
            _originalPhoneNumber = _phoneNumber;
        }
    }
 
    public void CancelEdit()
    {
        if (_transactionStarted)
        {
            _transactionStarted = false;
            _firstName = _originalFirstName;
            _lastName = _originalLastName;
            _phoneNumber = _originalPhoneNumber;
        }
    }
 
    public void EndEdit()
    {
        if (_transactionStarted)
        {
            _transactionStarted = false;
            _originalFirstName = "";
            _originalLastName = "";
            _originalPhoneNumber = "";
        }
    }
}
IDataErrorInfo
This interface offers custom error information to which controls can bind. During data binding, this allows controls to retrieve specific error information from the data source itself. For example, if a particular column in a DataTable object has an Integer type, setting a field to a string value for this column will cause the data source to return an appropriate error. This interface provides the following two properties:
Error: Returns an error message indicating what is wrong.
Item: An indexer that gets the error message for the specified column name or property name.

Windows Forms Data Binding - Simple Binding Example

With simple binding, the property on a control is bound to a single data source member. The data source will typically be a collection, an array, or a DataTable object. If the data source is a collection or an array, binding will occur with a property of an item in the collection or array. If the data source is a DataTable object, binding will occur with a DataColumn object of the DataTable object.

In this example, I will walk through the implementation of binding customer data to controls on a form. First, create a new Windows Application project in Visual Studio .NET 2003. Drag a GroupBox control to the form. Then drag three Label controls and three TextBox controls to the form inside the GroupBox control. Finally, drag four Button controls to the form outside.

the GroupBox control. Arrange and label the controls like the ones shown in the following
figure:


Name your controls as follows:
Group Box: _groupBox1
First Name Label : _firstNameLabel
Last Name Label: _lastNameLabel
Phone Number Label: _phoneNumberLabel
First Name Textbox: _firstNameTextBox
Last Name Textbox: _lastNameTextBox
Phone Number Textbox: _phoneNumberTextBox;
First Button: _firstButton
Previous Button: _previousButton
Next Button: _nextButton
Last Button: _lastButton

For each of the four buttons, add an event handler named Button_Navigate for the Click event. To dynamically associate the event handler with the events, use the following syntax:

_firstButton.Click += new EventHandler(this.Button_Navigate);

To do this declaratively, ensure that the Button controls are declared using the WithEvents keyword, and add a Handles clause that specifies each Button control’s Click event to the declaration of the Button_Navigate event handler, as shown here:

 private void Button_Navigate(object sender, EventArgs e)
        {
        }
Now, you need to define a Customer class. Create a new Visual Basic .NET class file and add the following code to it:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class Customer
{
    private string _firstName;
    private string _lastName;
    private string _phoneNumber;
 
    public Customer(string firstName, string lastName, string phoneNumber)
    {   
        _firstName = firstName;
        _lastName = lastName;
        _phoneNumber = phoneNumber;
    }
    public string FirstName
    {
        get { return _firstName; }
    }
 
    public string LastName
    {
        get { return _lastName; }
    }
 
    public string PhoneNumber
    {
        get { return _phoneNumber; }
    }
}

For simplicity, this example only stores a customer’s name and phone number. In a production application, more properties would likely need to be implemented.

Inside the constructor of the Form1 class after the call to InitializeComponent, initialize an array of customers with some arbitrary values. Declare the array as Readonly since it will not be reassigned after initialization. Here is the code:

Private ReadOnly Customer[] _customers;
   
    public 
DummyClass() {
        
// This call is required by the Windows Form Designer.
        
InitializeComponent();
        
// Add any initialization after the InitializeComponent() call
        
_customers = new Customer[] {
                
new Customer("James", "Henry", "123-123-1234"),
                
new Customer("Bill", "Gates", "234-234-2345"),
                
new Customer("Tupac", "Shakur", "345-345-3456")};
    
}


Now bind all TextBox controls to the array, as shown here:

_customers = new Customer[] {
        
new Customer("James", "Henry", "123-123-1234"),
        
new Customer("Bill", "Gates", "234-234-2345"),
        
new Customer("Tupac", "Shakur", "345-345-3456")};
_firstNameTextBox.DataBindings.Add("Text", _customers, "FirstName");
_lastNameTextBox.DataBindings.Add("Text", _customers, "LastName");
_phoneNumberTextBox.DataBindings.Add("Text", _customers, "PhoneNumber");

Finally, you need to handle the Click event raised by the Button controls to provide navigation functionality, as shown here:

private void Button_Navigate(object sender, EventArgs e) {
        BindingManagerBase manager 
= _groupBox1.BindingContext(_customers);
        if 
((sender == _firstButton)) {
            manager.Position 
= 0;
        
}
        
else if ((sender == _previousButton)) {
            manager.Position 
= (manager.Position - 1);
        
}
        
else if ((sender == _nextButton)) {
            manager.Position 
= (manager.Position + 1);
        
}
        
else if ((sender == _lastButton)) {
            manager.Position 
= (manager.Count - 1);
        
}
    }


The Button_Navigate event handler handles the Click event for all four navigation buttons. This code first retrieves the BindingManagerBase object from the BindingContext object of _groupBox1. The  BindingManagerBase object is actually an instance of CurrencyManager. The code basically asks the BindingContext object to “give me the CurrencyManager for the _customers data source.” The CurrencyManger object changes its Position property when a button is clicked. As the Position property is changed, all bound controls are updated automatically.

Now, to understand the purpose of the BindingContext object, add another GroupBox control to the form and three more TextBox controls to this GroupBox control. Name these controls as follows:

Group Box: _groupBox2
First Name Label: _firstNameLabel2
Last Name Label: _lastNameLabel2
Phone Number Label: _phoneNumberLabel2
First Name Textbox: _firstNameTextBox2
Last Name Textbox: _lastNameTextBox2
Phone Number Textbox: _phoneNumberTextBox2
First Button: _firstButton2
Previous Button: _previousButton2
Next Button: _nextButton2
Last Button: _lastButton2

Attach the
Click event of each Button control to the same Button_Navigate event handler as before. Now data bind the second set of TextBox controls to the array, as shown here:

_customers = new Customer[] {
        
new Customer("James", "Henry", "123-123-1234"),
        
new Customer("Bill", "Gates", "234-234-2345"),
        
new Customer("Tupac", "Shakur", "345-345-3456")};
_firstNameTextBox.DataBindings.Add("Text", _customers, "FirstName");
_lastNameTextBox.DataBindings.Add("Text", _customers, "LastName");
_phoneNumberTextBox.DataBindings.Add("Text", _customers, "PhoneNumber");
_firstNameTextBox2.DataBindings.Add("Text", _customers, "FirstName");
_lastNameTextBox2.DataBindings.Add("Text", _customers, "LastName");
_phoneNumberTextBox2.DataBindings.Add("Text", _customers, "PhoneNumber");


Before you can actually see the advantage of the BindingContext object, you need to ensure that each set of TextBox controls “lives” in its own data binding context. To do this, you need to create a BindingContext object for each GroupBox control. Remember that by default, the form automatically creates a single BindingContext object for itself and all child controls. Here is the constructor after creating two new BindingContext objects:

_customers = new Customer[] {
        
new Customer("James", "Henry", "123-123-1234"),
        
new Customer("Bill", "Gates", "234-234-2345"),
        
new Customer("Tupac", "Shakur", "345-345-3456")};
_groupBox1.BindingContext = new BindingContext();
_groupBox2.BindingContext = new BindingContext();
_firstNameTextBox.DataBindings.Add("Text", _customers, "FirstName");
_lastNameTextBox.DataBindings.Add("Text", _customers, "LastName");
_phoneNumberTextBox.DataBindings.Add("Text", _customers, "PhoneNumber");
_firstNameTextBox2.DataBindings.Add("Text", _customers, "FirstName");
_lastNameTextBox2.DataBindings.Add("Text", _customers, "LastName");
_phoneNumberTextBox2.DataBindings.Add("Text", _customers, "PhoneNumber");


Now, each GroupBox control and any child controls have their own context for data binding. Even though the controls contained in both GroupBox controls may bind to the same data source, they will be bound using different CurrencyManager objects.

You can visualize how the TextBox controls are data bound and synchronized in the following diagram:


From the previous diagram, you can see that each GroupBox control has its own
CurrencyManager object for the Customers data source. Therefore, changing the Position property on the first CurrencyManager object will have no effect on the TextBox controls contained in the second GroupBox control. Similarly, changing the Position property on the second CurrencyManager object will have no effect on the TextBox controls contained in the first GroupBox control. Therefore, add the following code to the Button_Navigate event handler to manipulate the second CurrencyManager object:

private void Button_Navigate(object sender, EventArgs e) {
        BindingManagerBase manager 
= _groupBox1.BindingContext(_customers);
        if 
((sender == _firstButton)) {
            manager.Position 
= 0;
        
}
        
else if ((sender == _previousButton)) {
            manager.Position 
= (manager.Position - 1);
        
}
        
else if ((sender == _nextButton)) {
            manager.Position 
= (manager.Position + 1);
        
}
        
else if ((sender == _lastButton)) {
            manager.Position 
= (manager.Count - 1);
        
}
    }


Next :Windows Forms Data Binding - Data Binding Interfaces