5

 

Data Handling

 

a.cs

using System;

using System.ComponentModel;

using System.Drawing;

using System.Windows.Forms;

using System.Data;

using System.Data.OleDb;

using System.IO;

using System.Collections;

public class CustomerList : System.Collections.CollectionBase

{

public static CustomerList GetCustomers()

{

CustomerList cl = new CustomerList();

Customer a = Customer.ReadCustomer1();

IList b = cl.List;

b.Add(a);

Customer c = new Customer("246-12-5645");

c.FirstName = "Vijay";

c.DateOfBirth = DateTime.Parse("5/3/1933");

cl.Add1(c);

cl.Add1(Customer.ReadCustomer2());

return cl;

}

public int Add1(Customer value)

{

return List.Add(value);

}

}

public class Customer : Component

{

public string id,FirstName ;

public DateTime dateOfBirth;

public static Customer ReadCustomer1()

{

Customer cust = new Customer("536-45-1245");

cust.FirstName = "Sonal";

cust.DateOfBirth = DateTime.Parse("9/9/1941");

return cust;

}

public static Customer ReadCustomer2()

{

Customer cust = new Customer("651-27-8117");

cust.FirstName = "Manish";

cust.DateOfBirth = DateTime.Parse("3/25/1942");

return cust;

}

public Customer(string ID): base()

{

id = ID ;

}

public string ID

{

get 

{

return id ;

}

}

public string FirstName1

{

get 

{

return FirstName ;

}

}

public DateTime DateOfBirth

{

get 

{

return dateOfBirth ;

}

set

{

dateOfBirth = value ;

}

}

}

public class zzz : Form

{

TextBox textBoxPosition;

CustomerList custList;

void textBoxDOB_FormatDate(object sender, ConvertEventArgs e)

{

if (e.DesiredType != typeof(string))

return ;

if (e.Value.GetType() != typeof(DateTime))

return ;

DateTime dt = (DateTime)e.Value;

e.Value = dt.ToLongDateString();

}

void textBoxDOB_ParseDate(object sender, ConvertEventArgs e)

{

if (e.DesiredType != typeof(DateTime))

return ;

if (e.Value.GetType() != typeof(string))

return ;

string value = (string)e.Value;

try

{

e.Value = DateTime.Parse(value);

}

catch(Exception ex)

{

MessageBox.Show(ex.Message);

}

}

void buttonMoveFirst_Click(object sender, System.EventArgs e)

{

BindingContext[custList].Position = 0 ;

}

void buttonMoveLast_Click(object sender, System.EventArgs e)

{

BindingContext[custList].Position = custList.Count - 1;

}

void buttonMoveNext_Click(object sender, System.EventArgs e)

{

if (BindingContext[custList].Position < custList.Count - 1)

{

BindingContext[custList].Position++;

}

}

void buttonMovePrev_Click(object sender, System.EventArgs e)

{

if (BindingContext[custList].Position > 0)

{

BindingContext[custList].Position--;

}

}

void customers_PositionChanged(object sender, System.EventArgs e)

{

textBoxPosition.Text = "Record " + (BindingContext[custList].Position + 1) + " of " + custList.Count;

}

public zzz() {

Text = "Customer Details";

ClientSize = new System.Drawing.Size(368, 413);

MinimumSize = new Size(368, (413 + SystemInformation.CaptionHeight));

Label labelFirstName,labelID,labelDOB;

labelID = new Label();

labelID.Location = new System.Drawing.Point(8, 32);

labelID.Text = "ID:";

labelID.Size = new System.Drawing.Size(64, 16);

labelFirstName = new Label();

labelFirstName.Location = new System.Drawing.Point(8, 112);

labelFirstName.Text = "&First Name:";

labelFirstName.Size = new System.Drawing.Size(64, 16);

labelDOB = new Label();

labelDOB.Location = new System.Drawing.Point(8, 194);

labelDOB.Text = "&Date of Birth:";

labelDOB.Size = new System.Drawing.Size(92, 16);

TextBox textBoxDOB,textBoxFirstName,textBoxID;

textBoxID = new TextBox();

textBoxID.Location = new System.Drawing.Point(88, 30);

textBoxID.ReadOnly = true;

textBoxID.Enabled = false;

textBoxID.Size = new System.Drawing.Size(203, 20);

textBoxFirstName = new TextBox();

textBoxFirstName.Location = new System.Drawing.Point(88, 112);

textBoxFirstName.Size = new System.Drawing.Size(243, 20);

textBoxDOB = new TextBox();

textBoxDOB.Location = new System.Drawing.Point(88, 192);

textBoxDOB.Size = new System.Drawing.Size(243, 20);

textBoxPosition = new TextBox();

textBoxPosition.Location = new System.Drawing.Point(88, 14);

textBoxPosition.ReadOnly = true;

textBoxPosition.Enabled = false;

textBoxPosition.Size = new System.Drawing.Size(88, 20);

Button buttonMoveFirst,buttonMovePrev,

buttonMoveNext,buttonMoveLast;

buttonMoveNext = new Button();

buttonMoveNext.Location = new System.Drawing.Point(184, 8);

buttonMoveNext.FlatStyle = FlatStyle.Flat;

buttonMoveNext.Size = new System.Drawing.Size(32, 32);

buttonMoveNext.Text = ">";

buttonMoveNext.Click += new System.EventHandler(buttonMoveNext_Click);

buttonMovePrev = new Button();

buttonMovePrev.Location = new System.Drawing.Point(48, 8);

buttonMovePrev.FlatStyle = FlatStyle.Flat;

buttonMovePrev.Size = new System.Drawing.Size(32, 32);

buttonMovePrev.Text = "<";

buttonMovePrev.Click += new System.EventHandler(buttonMovePrev_Click);

buttonMoveFirst = new Button();

buttonMoveFirst.Location = new System.Drawing.Point(8, 8);

buttonMoveFirst.FlatStyle = FlatStyle.Flat;

buttonMoveFirst.Size = new System.Drawing.Size(32, 32);

buttonMoveFirst.Text = "|<";

buttonMoveFirst.Click += new System.EventHandler(buttonMoveFirst_Click);

buttonMoveLast = new Button();

buttonMoveLast.Location = new System.Drawing.Point(224, 8);

buttonMoveLast.FlatStyle = FlatStyle.Flat;

buttonMoveLast.Size = new System.Drawing.Size(32, 32);

buttonMoveLast.Text = ">|";

buttonMoveLast.Click += new System.EventHandler(buttonMoveLast_Click);

Panel panelVCRControl;

panelVCRControl = new Panel();

panelVCRControl.Location = new System.Drawing.Point(88, 344);

panelVCRControl.Size = new System.Drawing.Size(264, 48);

panelVCRControl.Text = "panel1";

panelVCRControl.Controls.AddRange(new Control[]

{textBoxPosition,buttonMoveFirst,buttonMovePrev,

buttonMoveNext,buttonMoveLast});

Controls.AddRange(new Control[]

{textBoxDOB,labelDOB,panelVCRControl,textBoxFirstName,

textBoxID,labelFirstName,labelID});

custList = CustomerList.GetCustomers();

ControlBindingsCollection a = textBoxID.DataBindings;

a.Add("Text", custList, "ID");

textBoxFirstName.DataBindings.Add("Text", custList, "FirstName1");

Binding dobBinding = new Binding("Text", custList, "DateOfBirth");

dobBinding.Format += new ConvertEventHandler(textBoxDOB_FormatDate) ;

dobBinding.Parse += new ConvertEventHandler(textBoxDOB_ParseDate) ;

textBoxDOB.DataBindings.Add(dobBinding);

BindingManagerBase c = BindingContext[custList];

c.PositionChanged += new EventHandler(customers_PositionChanged);

textBoxPosition.Text = "Record " + (BindingContext[custList].Position + 1) + " of " + custList.Count;

}

public static void Main() {

Application.Run(new zzz());

}

}

 

Before submerging deep into the topic of Data Binding with Windows Forms Controls, let us first address the issues of the User Interface.

 

Screen 5.1

 

In the zzz constructor, the Text property of the Form class, which is initialized to 'Customer Details', dons the mantle of the window title. The ClientSize property determines the size of the Form. The MinimumSize property ensures that the size is not reduced beyond the value specified. We have dealt with these properties quite a while ago. The User Interface code is more germane for creating a visually appealing window, than for ameliorating our understanding of the core concepts of Data Binding.

 

We intend to display three text labels in our window. To facilitate this, it is essential to create three label controls viz., labelFirstName, labelID and labelDOB. The text property of each is initialized to ID, First Name and Date of Birth respectively. We have deliberately not altered the names of the controls provided in the original sample.  Furthermore, we have not modified any of the label control codes, in order to facilitate effortless comprehension. Besides Text, the other properties of the label control that we modify are the Location and the Size.

 

This user interface code is usually written by a utility called the Screen Painter and not by the programmer. This is because, it is very irksome and arduous for a programmer to supply coordinates, in pixels, for Properties such as the Location etc.

 

We will display data employing the services of a textbox. Thus, three textboxes named textBoxDOB, textBoxFirstName and textBoxID, have been provided, to store the date of birth, first name of customer and the customer ID, respectively. The Size of each textbox is defined by modifying the value contained in the Size property. Thereafter, a new location is specified using the Location property. The textbox displaying the customer ID has the ReadOnly property set to true, so that its contents cannot be altered.  Moreover, setting the Disabled property to false disables the field. One more textbox control called textBoxPosition is introduced. It displays the current active record and the total number of records in the recordset. We shall not discuss the textbox properties any further in the forthcoming programs.

 

We now need buttons in our window to enable the user to navigate between records. To attain this, the four button controls and their corresponding actions are given below:

•     buttonMovePrev          : Moves to the previous record.

•     buttonMoveNext          : Moves to the next record.

•     buttonMoveFirst         : Jumps to the first record.

•     buttonMoveLast          : Jumps to the last record.

 

We can set the properties of Location, Size and Text of the button controls to suit our requirements. For an enhanced visual appeal, the FlatStyle property is also altered. We use the Click event to wire up each button to a corresponding method having a similar name. This is done to ensure that every time we click on a button, the desired code gets executed. The code that gets activated will be dealt with subsequently.

 

A Panel control, by itself, is worthless, since it does nothing. Its role becomes consequential only when it aggregates or collects other controls. By placing controls within a Panel control, we can deal with all of them simultaneously. Thus, with a single line of code, we can disable a panel, thereby effectively, disabling all the controls contained therein. So, by using a panel control, a large number of controls can be treated as a single control and all their properties can be changed in unison. The Location and Size properties of the Panel control named panelVCRControl are set to certain specific co-ordinates. The Text property is initialized, but it does not get displayed on the screen.

 

The Panel control, like any other control, has a Controls property having a data type of Control.ControlCollection. Using the AddRange method that requires an array, all controls are added in a single action to the ControlCollection. This is analogous in functionality to the Add function, which adds only a single control to the Controls Collection. Thus, internally, AddRange repetitively calls the Add function, and in each iteration, supplies it with a single member of the array, till it has passed all the members of the entire array as parameters.

 

To summarize, we add the four textboxes and one label control to the panel, so that we can treat them as a single entity. In this program, we however, are not utilizing this property. The rest of the controls, including the panel control, are finally added to the main Form using the above AddRange function.

 

The object custList is of user-defined data type CustomerList, which is derived from CollectionBase. This class contains a static function called GetCustomers. This function creates an object c1, which is an instance of CustomerList. By using New and Finally, the value in this object is returned to custList in the zzz constructor.

 

We have another class Customer, which is derived from the Component class (this is optional). It represents a single Customer. The CustomerList class symbolizes a list or an assemblage of customers. The Customer class has a static function ReadCustomer1 that creates an instance of class Customer and passes the customer ID to the constructor. The constructor initializes the field id with this value.

 

The Customer object is represented by three variables, viz., id, FirstName and dateofBirth. Thus, an object is identified by its fields or variables, and not by the methods it employs. The programmers at Microsoft chose to initialize the id field through the constructor and the other fields separately. They could instead have initialized all three fields through the constructor or initialized none at all.

 

The most noteworthy thing here is that the FirstName field can be directly accessed, whereas, the dateofBirth field is accessible only through the property DateofBirth, using its set accessor. It is sensible and prudent to prevent access to a field directly, and allow access only through a property. The id field, however, is accessed through the constructor and not through the property id. We shall delve upon this, before long.

 

Object a represents the first customer. This object is stored in the CustomerList class since it is derived from class CollectionBase. It has the ability to store multiple objects. The CollectionBase class has a property called List of data type lList, which represents the collection. An IList object named b has a method Add, which adds any object to the Collection and returns the position where the object has been added. Thus, we have added a Customer object to the list.

 

To add the second customer, we create a Customer object in the same class, and initialize the members directly. This customer is also added to the CustomerList by calling a function Add1, which uses the List property to Add the customer.

 

The third customer is added to the List, using a more compact form. Each one of us possesses a distinct style of writing code. Thus, it is ineffectual to debate over the issue of why a static function has been used by us to create an object, instead of creating it directly.

 

The CustomerList class, which is derived from Collections, can store any arbitrary object. The Add1 function is not essential, but it facilitates the addition of objects to the collection. Thus, the CustomerList class is a simple collection of objects. Any other entity that can represent a collection could also have been used instead.

 

Reverting back to our constructor zzz, the Custlist object now provides access to the three customer objects through a collection object. Every control has a ReadOnly property called DataBindings, which is of the data type ControlBindingsCollection. ControlBindingsCollection in turn, is derived from class BindingsCollection. This class is used to bind a control to the data source, since it represents a collection of all data bindings for a control.

 

 

The Add function accepts three parameters and returns a Binding object.

•     The first parameter is a string representing the name of the property of the control that we want to bind to. In our program, we have used the property named Text.

•     The second parameter is of type object, which represents the data i.e. the collection object or data source. In this case, it is custlist.

•     The third parameter is the name of the field or property that we need to bind to. Here, the field name is id.

 

Thus, we are binding a field called id in the data source custList to the first textbox's Text property. Using the above mechanism, we could bind any column in the data source to any valid control property, such as, backcolor or forecolor.

 

In the next set, we bind the Text property of the second textbox control, textBoxFirstName, to the column FirstName1 in the data source custlist.

 

The third column is added with the help of a class called Binding, which only understands Binding. This class represents a simple relationship between the property of a control and that of any object. The constructor of the Binding class is given the same three parameters as those of the Add function, and they also have the same significance.

 

We shall now explain the second parameter, i.e. a data source. The second parameter could be any class that derives from interfaces, IBindingList or ITypedList. These comprise of the DataSet, DataTable, DataView, or DataViewManager classes. These classes implement the IList interface. In effect, there are a large number of classes (over 20), which implement from this interface.  We have used CollectionBase in our present example. The only safeguard to be kept in mind is that, an IList object has to be created first, and only then can it be used in any of the bindings functions. The object in the list must be of the same data type, or else, an exception will get thrown. The last type permitted as a data source is a strongly typed IList such as an array.

 

One commendable feature about the Binding class is that it permits the user to determine the display patterns of data. It also acts as the validating authority, whenever the user makes any alteration to the data. The Binding manager calls the Format event when it has to display some data in the control and calls the Parse event when it has to retrieve data. Thus, we can build our own custom formats.

 

The Format event is attached to a function called textBoxDOB_FormatDate, using the delegate ConvertEventHandler. This function supplies the custom format in which the date is to be displayed. We use the same principles to call the method textBoxDOB_ParseDate, which parses the date and checks for errors. We shall make an endeavor to grasp these functions, in a short while.

 

The Add function used earlier, was overloaded to accept either of the following:

•     two strings and an object parameter.

•     two strings and a Binding object.

 

Thus, the only difference between the two data bindings is that, by using a Binding object, we are able to customize the display of data.

 

The Form class has a BindingContext property that returns a BindingContext object. The indexer returns a BindingManagerBase object, which represents all data-bound controls, which are bound to the same data source, and keeps them synchronized. It is this BindingManagerBase object that facilitates movement from one record to another. As of now, we initialize its PositionChanged event to a function customers_PositionChanged, which will be called each time the Position property changes. The secret of how this is done shall be revealed at a later date.

 

Any class derived from Collections has a member called Count, which returns the number of objects present in the Collection. In our case, the count is shown as 3. The BindingManagerBase has a member called Position that exposes the object (which is the current object in the list or data source), to which the control is bound. This index is zero based i.e. the first object in the list is numbered zero. And since it is zero based, the Text property of the label in the panel is initialized to a string containing the value of the Position property + 1. It is followed by the string 'of', and finally by the value returned by the Count property of the custList object. The text is finally displayed as '1 of 3'.

 

Screen 5.2

 

When the form loads on, the first object is the active object. The BindingManagerBase class now ensures that the Text property of the three textboxes is initialized to the appropriate value.

 

The first textbox is bound to a field called ID. Thus, the Customer class is searched for a property called ID. The 'get' accessor is called. The value returned by it is the value displayed in the textbox. Thus, the Customer class needs a property called ID with a 'get' accessor. If the name of the property is modified, say to ID1, or if the get accessor is removed, an exception is generated at run time; however, no errors will be generated at compile time.

 

The same holds true for the FisrtName1 property and the DateofBirth. The DateofBirth differs, in that, the function textBoxDOB_FormatDate is called after the get accessor is called. This is done to facilitate display of the date in the format approved by us. The second parameter 'e' in textBoxDOB_FormatDate, which is of data type ConvertEventArgs, is well acquainted with the object that is to be displayed in the textbox. The DesiredType property of the parameter class contains the original data type of the property that is bound in the data source. If its type is not DateTime, the program exits from the function gracefully using Return. This parameter 'e' has a property called Value, which contains the actual unformatted value that exists in the data source. As the return value of the property is object, we use the GetType function to retrieve the type of this unformatted value. If the type is a string, the program continues execution, or else, it exits. Yet another error check!

 

The fact that the program has been able to pass beyond the above two error checks successfully establishes that the value in hand can now be formatted to the type we desire. Therefore, we first cast this value into a DateTime object dt, and then, use the ToLongDateString function from the class, to convert the date into a string using the long form. This value is stored back into the Value property of the parameter 'e' and is displayed as the Text property of the textbox.

 

Whenever we attempt at altering the date into a valid or an invalid one, the function textBoxDOB_ParseDate or the Parse event gets called. This function runs the same two error checks on the date, and thereafter, stores the value of the Value property in a string. Thereafter, the string is converted into a datetime object. If this process does not score a success for any reason, an exception is thrown and a MesssageBox is displayed. This modified value in the Value property of the parameter, is stored back in the data source by the framework.

 

Screen 5.3

 

When the form loads on, we notice the first record from the data source. To see the next record, we have to press the button with the display of the > arrow. This is the buttonMoveNext control. This action consecutively, calls function buttonMoveNext_Click that uses the BindingManagerBase object and the BindingContext to increment the Position property by a value of 1. This process is encapsulated in an 'if' statement, so that a check can be performed on whether the record is the last one in the list or not. The last record is retrieved, using the Count property of the data source.

 

The subtraction of 1 is mandatory, since the Position property is zero based. To move backwards, the Position property is decremented by 1, and the 'if' statement verifies whether the value is greater than zero or not. To move to the first record, we set the Position property to 0, and to move to the last record, we set the Position property to Count-1. Each time we change the Position property, the function customers_PositionChanged gets called. Here, the textbox was updated in a manner similar to what was done earlier to change the position of the record pointer.

 

The program is considerably extensive, but it exhibits the data binding properties of a control, in order to display objects from a data source.

 

a.cs

using System;

using System.ComponentModel;

using System.Drawing;

using System.Windows.Forms;

using System.Data;

using System.IO;

using System.Collections;

public class zzz : Form {

DataTable t;

int cnt;

TextBox textBoxPosition;

void buttonMoveFirst_Click(object sender, System.EventArgs e)

{

BindingContext[t].Position = 0 ;

}

void buttonMoveLast_Click(object sender, System.EventArgs e)

{

BindingContext[t].Position = cnt - 1;

}

void buttonMoveNext_Click(object sender, System.EventArgs e)

{

if (BindingContext[t].Position < cnt - 1)  {

BindingContext[t].Position++;

}

}

void buttonMovePrev_Click(object sender, System.EventArgs e)

{

if (BindingContext[t].Position > 0)

{

BindingContext[t].Position--;

}

}

void customers_PositionChanged(object sender, System.EventArgs e)

{

textBoxPosition.Text = "Record " + (BindingContext[t].Position + 1) + " of " + cnt;

}

public zzz() {

Text = "Customer Details";

ClientSize = new System.Drawing.Size(368, 413);

MinimumSize = new Size(368, (413 + SystemInformation.CaptionHeight));

TextBox textBoxFirstName;

textBoxFirstName = new TextBox();

textBoxFirstName.Location = new System.Drawing.Point(88, 112);

textBoxFirstName.Size = new System.Drawing.Size(243, 20);

textBoxPosition = new TextBox();

textBoxPosition.Location = new System.Drawing.Point(88, 14);

textBoxPosition.ReadOnly = true;

textBoxPosition.Enabled = false;

textBoxPosition.Size = new System.Drawing.Size(88, 20);

Button buttonMoveFirst,buttonMovePrev,buttonMoveNext,buttonMoveLast;

buttonMoveNext = new Button();

buttonMoveNext.Location = new System.Drawing.Point(184, 8);

buttonMoveNext.FlatStyle = FlatStyle.Flat;

buttonMoveNext.Size = new System.Drawing.Size(32, 32);

buttonMoveNext.Text = ">";

buttonMoveNext.Click += new System.EventHandler(buttonMoveNext_Click);

buttonMovePrev = new Button();

buttonMovePrev.Location = new System.Drawing.Point(48, 8);

buttonMovePrev.FlatStyle = FlatStyle.Flat;

buttonMovePrev.Size = new System.Drawing.Size(32, 32);

buttonMovePrev.Text = "<";

buttonMovePrev.Click += new System.EventHandler(buttonMovePrev_Click);

buttonMoveFirst = new Button();

buttonMoveFirst.Location = new System.Drawing.Point(8, 8);

buttonMoveFirst.FlatStyle = FlatStyle.Flat;

buttonMoveFirst.Size = new System.Drawing.Size(32, 32);

buttonMoveFirst.Text = "|<";

buttonMoveFirst.Click += new System.EventHandler(buttonMoveFirst_Click);

buttonMoveLast = new Button();

buttonMoveLast.Location = new System.Drawing.Point(224, 8);

buttonMoveLast.FlatStyle = FlatStyle.Flat;

buttonMoveLast.Size = new System.Drawing.Size(32, 32);

buttonMoveLast.Text = ">|";

buttonMoveLast.Click += new System.EventHandler(buttonMoveLast_Click);

Panel panelVCRControl;

panelVCRControl = new Panel();

panelVCRControl.Location = new System.Drawing.Point(88, 344);

panelVCRControl.Size = new System.Drawing.Size(264, 48);

panelVCRControl.Text = "panel1";

panelVCRControl.Controls.AddRange(new Control[]

{textBoxPosition,buttonMoveFirst,buttonMovePrev,

buttonMoveNext,buttonMoveLast});

Controls.AddRange(new Control[]

{panelVCRControl,textBoxFirstName});

t = MakeTable();

textBoxFirstName.DataBindings.Add("Text", t, "Text");

textBoxFirstName.DataBindings.Add("BackColor", t, "BackColor");

textBoxFirstName.DataBindings.Add("ForeColor", t, "ForeColor");

BindingManagerBase c = BindingContext[t];

c.PositionChanged += new EventHandler(customers_PositionChanged);

cnt = t.Rows.Count;

textBoxPosition.Text = "Record " + (BindingContext[t].Position + 1) + " of " + cnt;

}

private DataTable MakeTable() {

DataTable t = new DataTable("Control");

t.Columns.Add("BackColor", typeof(Color));

t.Columns.Add("ForeColor", typeof(Color));

t.Columns.Add("Text");

DataRow r;

r = t.NewRow();

r["BackColor"] = Color.Blue;

r["ForeColor"] = Color.Yellow;

r["Text"] = "Yellow on Blue";

t.Rows.Add(r);

r = t.NewRow();

r["BackColor"] = Color.White;

r["ForeColor"] = Color.Green;

r["Text"] = "Green on white";

t.Rows.Add(r);

r = t.NewRow();

r["BackColor"] = Color.Orange;

r["ForeColor"] = Color.Black;

r["Text"] = "Black on Orange";

t.Rows.Add(r);

return t;

}

public static void Main() {

Application.Run(new zzz());

}

}

 

Screen 5.4

 

This example borrows sufficiently from the previous example. The user interface too is almost similar to the earlier one.

 

This program has one textbox called textBoxFirstName, which has 3 data bindings on properties of Text, BackColor and ForeColor. The data source is a DataTable object t. The properties in the DataTable have the same name as the bound textbox properties.

 

To create a DataTable object, we create a new instance of DataTable and pass a string, Control, that signifies the name of the table. Every data table desires columns. Hence, there is a Columns property that is a collection. The Columns collection has an Add method that adds a column. The Column names specified must correspond with those given in the Bindings.

 

The first two columns are of data type Color, and the last column called Text, is of the data type String. After having added 3 columns, we need to add a row. The NewRow function returns a blank DataRow object. So, we use the indexer of the data row and pass the column name as a parameter to the indexer, in order to initialize the columns. Finally, we use the Add function of the Rows Collection to add a new row. This process is repeated thrice, once for each column.

 

Screen 5.5

 

The difference here is that, the data source has changed from a Collection to a DataTable object. The column names correspond to the property names specified in the Bindings function. Thus, they can now replace the property names. We can bind multiple properties of a control to fields from a data source. The Binding manager handles this internally. Thus, each time we move from one row to another in the DataTable, three properties of the textbox get modified, and we get to see a colorful output.

 

The Rows Collection has a member called Count, which signifies the number of records or rows present in the DataTable.

 

So far, we have discovered that one or more properties of a control can be bound or associated with a field, column or property of a data source. Thereafter, the Binding Context is employed to move the record pointer in the data source. The framework then ensures that the properties of the control get updated automatically.

 

We can bind as many properties of a control as we desire, and the data source too can be of different types, with strings attached. If it is a DataTable, we need to bind to column names, however, if it is a Collections object, we require a property with a get accessor.

 

a.cs

using System;

using System.Data;

using System.Drawing;

using System.Globalization;

using System.Windows.Forms;

public class zzz : Form {

Button button1,button2,button3,button4;

TextBox text1,text2,text3,text4;

BindingManagerBase bmCustomers,bmOrders;

DataSet ds;

DateTimePicker DateTimePicker1;

void DecimalToCurrencyString(object sender, ConvertEventArgs cevent)

{

if(cevent.DesiredType != typeof(string)) return;

cevent.Value = ((decimal) cevent.Value).ToString("c");

}

void CurrencyStringToDecimal(object sender, ConvertEventArgs cevent)

{

if(cevent.DesiredType != typeof(decimal)) return;

cevent.Value = Decimal.Parse(cevent.Value.ToString(),NumberStyles.Currency, null);

}

protected void button1_Click(object sender, System.EventArgs e)

{

bmCustomers.Position -= 1;

}

protected void button2_Click(object sender, System.EventArgs e)

{

bmCustomers.Position += 1;

}

protected void button3_Click(object sender, System.EventArgs e)

{

bmOrders.Position-=1;

}

protected void button4_Click(object sender, System.EventArgs e)

{

bmOrders.Position+=1;

}

protected void BindControls()

{

text1.DataBindings.Add(new Binding("Text", ds, "customers.custName"));

text2.DataBindings.Add(new Binding("Text", ds, "customers.custID"));

DateTimePicker1.DataBindings.Add(new Binding("Value", ds, "customers.CustToOrders.OrderDate"));

Binding b = new Binding("Text", ds, "customers.custToOrders.OrderAmount");

b.Parse+=new ConvertEventHandler(CurrencyStringToDecimal);

b.Format+=new ConvertEventHandler(DecimalToCurrencyString);

text3.DataBindings.Add(b);

text4.DataBindings.Add(new Binding("Text", ds, "customers.CustToOrders.custID"));

bmCustomers = BindingContext [ds, "Customers"];

bmOrders = BindingContext[ds, "customers.CustToOrders"];

}

void MakeDataSet()

{

ds = new DataSet("myDataSet");

DataTable tCust = new DataTable("Customers");

DataTable tOrders = new DataTable("Orders");

DataColumn cCustID = new DataColumn("CustID");

DataColumn cCustName = new DataColumn("CustName");

tCust.Columns.Add(cCustID);

tCust.Columns.Add(cCustName);

DataColumn cID = new DataColumn("CustID");

DataColumn cOrderDate = new DataColumn("orderDate",typeof(DateTime));

DataColumn cOrderAmount = new DataColumn("OrderAmount", typeof(decimal));

tOrders.Columns.Add(cOrderAmount);

tOrders.Columns.Add(cID);

tOrders.Columns.Add(cOrderDate);

ds.Tables.Add(tCust);

ds.Tables.Add(tOrders);

DataRelation dr = new DataRelation("custToOrders", cCustID , cID);

ds.Relations.Add(dr);

DataRow newRow1,newRow2;

for(int i = 1; i < 4; i++)

{

newRow1 = tCust.NewRow();

newRow1["custID"] = "Cust " + i;

tCust.Rows.Add(newRow1);

}

tCust.Rows[0]["custName"] = "Vijay";

tCust.Rows[1]["custName"] = "Sonal";

tCust.Rows[2]["custName"] = "Manish";

for(int i = 1; i < 4; i++)

{

for(int j = 1; j < 6; j++)

{

newRow2 = tOrders.NewRow();

newRow2["CustID"]=  "Cust " + i;

newRow2["orderDate"]= new DateTime(2001, i, j * 2);

newRow2["OrderAmount"] = i * 10 + j  * .1;

tOrders.Rows.Add(newRow2);

}

}

}

public zzz() {

Text = "Binding Sample";

ClientSize = new System.Drawing.Size(450, 200);

button1 = new Button();

button1.Location = new System.Drawing.Point(24, 16);

button1.Size = new System.Drawing.Size(64, 24);

button1.Text = "<";

button1.Click+=new System.EventHandler(button1_Click);

button2 = new Button();

button2.Location = new System.Drawing.Point(90, 16);

button2.Size = new System.Drawing.Size(64, 24);

button2.Text = ">";

button2.Click+=new System.EventHandler(button2_Click);

button3 = new Button();

button3.Location = new System.Drawing.Point(90, 100);

button3.Size = new System.Drawing.Size(64, 24);

button3.Text = "<";

button3.Click+=new System.EventHandler(button3_Click);

button4 = new Button();

button4.Location = new System.Drawing.Point(150, 100);

button4.Size = new System.Drawing.Size(64, 24);

button4.Text = ">";

button4.Click+=new System.EventHandler(button4_Click);

text1= new TextBox();

text1.Location = new System.Drawing.Point(24, 50);

text1.Size = new System.Drawing.Size(150, 24);

text2= new TextBox();

text2.Location = new System.Drawing.Point(190, 50);

text2.Size = new System.Drawing.Size(150, 24);

text3= new TextBox();

text3.Location = new System.Drawing.Point(290, 150);

text3.Size = new System.Drawing.Size(150, 24);

text4= new TextBox();

text4.Location = new System.Drawing.Point(9, 150);

text4.Size = new System.Drawing.Size(70, 24);

DateTimePicker1 = new DateTimePicker();

DateTimePicker1.Location = new System.Drawing.Point(90, 150);

DateTimePicker1.Size = new System.Drawing.Size(200, 800);

Controls.Add(button1);

Controls.Add(button2);

Controls.Add(button3);

Controls.Add(button4);

Controls.Add(text1);

Controls.Add(text2);

Controls.Add(text3);

Controls.Add(text4);

Controls.Add(DateTimePicker1);

MakeDataSet();

BindControls();

}

public static void Main()

{

Application.Run(new zzz());

}

}

The above example utilizes more controls as compared to the earlier ones. It also exploits a more complicated data source.

 

Screen 5.6

 

On the screen, we see 4 buttons in sets of two, which assist the record pointer in navigating within the data source.  We also have three textboxes and a control named DateTimePicker control, which activates a calendar. We have already enlightened you on the subject of this control, in the previous chapter. We add these controls using the Add function. The function MakeDataSet is then employed to create a complicated relationship between the data.

 

Let us take a steal peek into the function MakeDataSet.

 

In this function, we create a DataSet object and then pass a string called myDataSet to the constructor. This string is used to provide a name to the root document element in the XML representation. Since there is little utility in assigning a name to the data in this program currently, it can be edged out and abandoned. A DataSet is a collection of tables in the memory, which can be related to each other. We can write volumes on the DataSet concept. Microsoft has pulled out all stops in integrating a large number of features in a DataSet.

 

The next task in hand is to associate two DataTables with our DataSet object. Whenever we require more than one of a similar kind, we need to assign a name to that entity. So, we create two tables tCust and tOrders with Customers and Orders.

We create two DataColumn objects named cCustID and cCustName, and add them to the Columns Collection of our DataTable Customer, using the Add member. The two columns represent the unique ID and the customer name.

 

We then add the following three columns to our table called Orders:

•     CustID: the id of the customer who bought the order.

•     orderDate: the date on which the order was placed. 

•     OrderAmount: the amount of the order placed.

 

Once this has been accomplished, we add the two empty tables to the DataSet, using the Add function from the Tables collection, in the DataSet class.

 

We now need to relate the two tables, i.e. Customers and Orders. The field custID in the Customers table is unique for every customer record, and thus, is called the Primary Key. In the Orders table, the field cID is not unique, as a customer may place multiple orders. Thus, for every single customer in the Customer table, we may have multiple records in the Orders table. This type of relationship is called a parent-child relationship, or a primary key-foreign key relationship. A field in a table is called a foreign key, only if it is a primary key in another table, and if both the fields belong to the same domain. A DataRelation class recognizes a parent-child relationship.

 

The constructor of the DataRelation class requires three parameters:

1) The name of the DataRelation, which may be null.

2) The DataColumn object that represents the parent column. In our case, it is the field cCustID in the Customer table.

3) The child column, i.e. the field cID in the Orders table.

 

We now use the property named Relations in the DataSet class of type DataRelationCollection, to Add the relation to the Dataset. From now on, the DataSet class will relate each customer id from the Customer table to the multiple customer ids in the Orders table.

 

Now, the tables are required to be populated with some data. So, we start by creating a DataRow object, with the help of which, we shall populate the tables. Since we want to add three customers with IDs Cust 1, Cust 2 and Cust 3, we first call the NewRow function, which creates the DataRow object, and then we use the indexer with a column name to store the data. Using the Add member of the Row Collection, the row is then added.

 

Alternatively, we could have initialized the column custName in a for loop. This approach has been sidestepped or avoided since Microsoft samples have employed a different methodology. The Rows Collection object, denoted by the property Rows, has an indexer that facilitates access to each row.  For e.g. tCust.Rows[0] accesses the first row. The DataRow objects indexer can be utilized to change the field custName.

 

For each of the three customers, we would now want to add five records to the Order table. The Custid and the year of the order date, remain the same. The month number is suffixed with 1, 2 or 3, depending upon the customer. And the day is increased by 2, in consideration of each order. The amount on the order is, the month number multiplied by 10, plus the value of j in the inner for loop, multiplied by 0.1. This expression generates a unique value for each order. With the help of the for loop, the process of populating tables can become reasonably simpler, as against, writing the values individually. Thus, we have 3 records in the Customer table and 15 records in the Orders table, i.e. 5 per customer.

 

Finally, the controls are bound to the columns in the data table, using the function BindControls. In the first textbox, we display the field custName from the Customer table, which is present in the DataSource ds. Currently, it happens to be a DataSet and not a DataTable or a Collection Object.

 

The last parameter to the Binding object Constructor is tablename.fieldname. It is not merely a field name. It is because a DataSet consists of a collection of tables, and the same field name could be present in more than one table. Thus, we need to clearly identify the table from which the column has been obtained. The second textbox is bound to the customer id from the customer table. The DateTimePicker control behaves akin to other controls, when it comes to data binding. Instead of the Text property, we bind the control to the Value property.

 

The point of greater significance is that, while displaying orderdate, instead of specifying order.orderdate, we use customers.CustToOrders.OrderDate, which is the parent-table-name.relation-name.field-name. The motive behind this will be explained in a short while from now. The date is displayed using a more sophisticated control than a simple textbox.

 

Like before, we want to call the code for the fourth textbox, which displays data that can be edited. So, firstly we are required to create a Binding object b, and associate two functions with the Format and Parse events. The delegates are added before the Add function is called, to facilitate the addition of the bindings, since no formatting takes place when we change the current object in the DataSource.

 

The BindingContext object takes more than one indexer. Therefore, the datasource, i.e. a DataSet or a navigational path, is used as an indexer to refer to a specific BindingManagerBase. The above-mentioned object is mandatory in order to update the record pointer and to display all the data in the data source. This parameter may either contain merely a table name or a table name followed by a relation name, e.g. customers.custtoOrders. The first BindingMangerBase object, bmCustomers, facilitates movement through the three customer records, because we have supplied the table name Customer. The second one, bmOrders, will move through those records using the relation, since we have supplied the relation name. Thus, even though we have 15 records in the Orders table, we can see only 5 of them.

 

Thus, whenever we click on the first two buttons, we use the bmCustomers object's Position property to move from one record to another, up or down, without checking for errors. The next two buttons also use the Position property of the bmOrders object, depending upon the active customer id, thus, reducing the number of records.

 

Screen 5.7

 

The method DecimalToCurrencyString is called every time we desire to display a new value in the last textbox. The value returned by the DesiredType member of the ConvertEventArgs parameter, is checked with a string. If they do not correspond with each other, the program exits out. This is the only error check performed. Thus, we first ascertain whether we are allowed to convert from a decimal type in the original value, to a string or not. If the conversion is allowed, we call the ToString function with the formatting character 'c', to carry out the actual conversion to a Currency.

 

The method CurrencyStringToDecimal is called whenever we modify the value in the textbox. As earlier, we perform the same error check on the conversion to string, and then we use the Parse event to convert it into the original decimal type. If we change the display to 20.001, we may see it as 20.00. But the original value still remains at 20.001. The WriteLine function can be used to display the unformatted value.

 

Finally, the textbox control text4 displays the current customer id. If we move the top two buttons, the value contained in this textbox changes. However, when we alter the Position property on the relation, the customer id remains constant, while the other two fields change. This example illustrates how the data source can be made extremely complex using the BindingManagerBase class, which has its work cut out for it.

 

Screen 5.8

 

a.cs

using System;

using System.Drawing;

using System.Windows.Forms;

using System.Data;

using System.Data.SqlClient;

public struct State1

{

string shortName, longName;

public State1(string longName , string shortName)

{

this.shortName = shortName ; this.longName = longName ;

}

public string ShortName

{

get

{

return shortName;

}

}

public string LongName

{

get

{

return longName;

}

}

}

public class zzz : Form

{

int cnt;

DataSet customersDataSet1;

ComboBox comboBoxState;

TextBox textBoxPosition;

Button buttonMoveFirst,buttonMovePrev,buttonMoveNext,

buttonMoveLast;

TextBox textBoxID,textBoxRegion;

Label labelID;

Panel panelVCRControl;

// Washington not there

public State1[] States  = new State1[]

{

new State1("Alaska","AK"),new State1("California" ,"CA"),new State1("Idaho","ID"),new State1("Montana" ,"MT"),

new State1("New Mexico" ,"NM"),new State1("Oregon"  ,"OR")

,new State1("Wyoming" ,"WY")

} ;

public zzz()

{

buttonMoveLast = new Button();

customersDataSet1 = new DataSet();

buttonMoveFirst = new Button();

textBoxID = new TextBox();

textBoxRegion = new TextBox();

textBoxPosition = new TextBox();

buttonMovePrev = new Button();

panelVCRControl = new Panel();

comboBoxState = new ComboBox();

labelID = new Label();

buttonMoveNext = new Button();

buttonMoveNext.Click += new System.EventHandler(buttonMoveNext_Click);

buttonMoveNext.FlatStyle = FlatStyle.Flat;

buttonMoveNext.Location = new System.Drawing.Point(280, 8);

buttonMoveNext.Size = new System.Drawing.Size(32, 32);

buttonMoveNext.Text = ">";

customersDataSet1.DataSetName = "CustomersDataSet";

buttonMoveFirst.FlatStyle = FlatStyle.Flat;

buttonMoveFirst.Click += new System.EventHandler(buttonMoveFirst_Click);

buttonMoveFirst.Location = new System.Drawing.Point(8, 8);

buttonMoveFirst.Size = new System.Drawing.Size(32, 32);

buttonMoveFirst.Text = "|<";

textBoxID.Enabled = false;

textBoxID.Location = new System.Drawing.Point(88, 16);

textBoxID.ReadOnly = true;

textBoxID.Size = new System.Drawing.Size(299, 20);

textBoxRegion.Location = new Point(88, 116);

textBoxRegion.Size = new System.Drawing.Size(299, 20);

ClientSize = new System.Drawing.Size(464, 357);

Text = "Customer Details";

textBoxPosition.Enabled = false;

textBoxPosition.Location = new System.Drawing.Point(88, 14);

textBoxPosition.ReadOnly = true;

textBoxPosition.Size = new System.Drawing.Size(184, 20);

buttonMovePrev.Click += new System.EventHandler(buttonMovePrev_Click);

buttonMovePrev.FlatStyle = FlatStyle.Flat;

buttonMovePrev.Location = new System.Drawing.Point(48, 8);

buttonMovePrev.Size = new System.Drawing.Size(32, 32);

buttonMovePrev.Text = "<";

panelVCRControl.Location = new System.Drawing.Point(88, 288);

panelVCRControl.Size = new System.Drawing.Size(360, 48);

comboBoxState.Location = new System.Drawing.Point(88, 208);

comboBoxState.Size = new System.Drawing.Size(176, 20);

comboBoxState.Text = "";

labelID.Location = new System.Drawing.Point(16, 16);

labelID.Size = new System.Drawing.Size(64, 16);

labelID.Text = "ID:";

buttonMoveLast.Click += new System.EventHandler(buttonMoveLast_Click);

buttonMoveLast.FlatStyle = FlatStyle.Flat;

buttonMoveLast.Location = new System.Drawing.Point(320, 8);

buttonMoveLast.Size = new System.Drawing.Size(32, 32);

buttonMoveLast.Text = ">|";

panelVCRControl.Controls.AddRange(new Control[] {textBoxPosition,buttonMoveFirst,buttonMovePrev,

buttonMoveNext,buttonMoveLast});

Controls.AddRange(new Control[] {comboBoxState,panelVCRControl,textBoxID,labelID,textBoxRegion});

 

SqlConnection con = new SqlConnection("server=(local)\\NetSDK;

uid=QSUser;pwd=QSPassword;database=northwind");

SqlDataAdapter cmd = new SqlDataAdapter("Select * from Customers where country='USA'", con);

cmd.Fill(customersDataSet1, "Customers");

comboBoxState.DataSource=States;       

comboBoxState.DisplayMember="LongName";    

comboBoxState.ValueMember="ShortName";     

comboBoxState.DataBindings.Add("SelectedValue", customersDataSet1, "Customers.Region");

textBoxID.DataBindings.Add("Text", customersDataSet1, "Customers.CustomerID");

textBoxRegion.DataBindings.Add("Text", customersDataSet1, "Customers.Region");

BindingContext[customersDataSet1,"Customers"].PositionChanged += new System.EventHandler(customers_PositionChanged);

DataTableCollection tc = customersDataSet1.Tables;

DataTable t = tc[0];

cnt = t.Rows.Count;

textBoxPosition.Text = "Record " + (BindingContext[customersDataSet1,"Customers"].Position + 1) + " of " + cnt;

}

void buttonMoveFirst_Click(object sender, System.EventArgs e)

{

BindingContext[customersDataSet1,"Customers"].Position = 0 ;

}

void buttonMoveLast_Click(object sender, System.EventArgs e)

{

BindingContext[customersDataSet1,"Customers"].Position = cnt - 1;

}

void buttonMoveNext_Click(object sender, System.EventArgs e)

{

if (BindingContext[customersDataSet1,"Customers"].Position < cnt - 1)

{

BindingContext[customersDataSet1,"Customers"].Position++;

}

}

 

 

void buttonMovePrev_Click(object sender, System.EventArgs e)

{

if (BindingContext[customersDataSet1,"Customers"].Position > 0)

{

BindingContext[customersDataSet1,"Customers"].Position--;

}

}

void customers_PositionChanged(object sender, System.EventArgs e)

{

textBoxPosition.Text = "Record " + (BindingContext[customersDataSet1,"Customers"].Position + 1) + " of " + cnt;

}

public static void Main()

{

Application.Run(new zzz());

}

}

 

Screen 5.9

 

Continuing with our saga on data handling, let us launch a few more intriguing twists and turns. As before, let us tackle the User Interface issues first. We possess one label, three textboxes, one combo box and four buttons. There is nothing novel or innovative about this. In the earlier programs, we had entered the data in our program itself, using a DataTable or a DataSet, and this data was eventually displayed. In this program, we source the data from a database.

 

When we install the .NET framework, a large number of databases are brought into existence and installed in SQL Server. To access the data within a database, we have to use the SqlConnection class. The constructor is given a string that identifies the machine on which the database server resides. The default in our case is server=(local)\\NetSDK, where 'local' represents the machine we are currently working on. The installation program creates NetSDK. The word following server is 'uid', which denotes the user name, and the word 'pwd' implies the password. The values supplied are QSUser and QSPassword, respectively.

 

All data is stored in tables that reside in a database. We are interested in a list of customers, which is stored in a table called Customers, residing in the northwind database. Therefore, in the connection string, we specify database = northwind.

 

The SqlConnection class is merely capable of comprehending the wherewithal of connecting to a database. It is clueless about the word SQL or Structured Query Language. SQL is a language used to extricate data from one or more tables. Thus, we introduce a new class named SqlDataAdapter that understands SQL, and then, we pass the SQL statement to its constructor along with the connection object. The SQL statement "Select * from Customers where country = 'USA' ", selects all the fields, since the symbol * represents all fields of the Customers table. The 'where' condition restricts/filters records whose country field has the value of 'USA'. As of now, no data gets retrieved. The class stores this information internally.

 

It is the Fill command of the SqlDataAdapter class, which is responsible for filling up the DataSet customersDataSet1. The first parameter supplied is the DataSet and the second parameter is a tablename whose fields are to be mapped. The tablename has to be a valid table name, or else, a run time exception will be generated. The return value is the number of rows that are present in the data source. Earlier, we had used the 'for' statements to provide data; whereas, currently we are using real life data from a database to load a table. The WriteLine function if given here would display the number of records in the DataSet, which is 13 in this case.

 

The DataSource property in the Combo Box control is of type object. This property determines what the combo box displays. Here, we have specified an array called States, of data type State1. Our array contains seven members of type State1.

 

The State1 object has two members, viz. longName and shortName. LongName stores the actual name of the state and shortName stores the two-character abbreviations. The constructor of the class initializes these two members.

 

It is our misfortune that, despite being tantalized by being offered the exciting privilege of selecting names for parameters, the same old names as assigned to the fields, are being ascribed to the parameters as well. Therefore, to access the field shortName from within the constructor, we need to preface it with the word 'this'. The 'this' keyword is optional in situations where we use different names for the parameters. Thus, the combo box shall display one of the values present in the States array, which encompasses the short and the long names of seven different states. To authenticate this, you may click on the down arrow and see the names of the seven states.

 

Screen 5.10

 

The question that comes to the fore at this stage is that, 'How does the combo box come to a decision on the values to be displayed?' The combo box or any control that displays a list, takes two different values, namely, DisplayMember and ValueMember. DisplayMember takes a decision on the data that the user sees in the list box, while ValueMember is the actual value of the selected item.

 

Thus in our case, we get to see the full name of the state, when the DisplayMember property is initialized to longName. The ValueMember is equated to the shortName. So, the value obtained from this list box is the abbreviated name of a state. These two have to be properties in the class State1.

 

The combo box, like all other controls, has a Binding property. The first two textboxes are bound to the CustomerID field and the Region field. They are preceded by the table-name. The name of the DataSet is also specified. The field from the dataset is the Region field that contains the abbreviation, but we get to see the full name, since the DisplayMember is longName.

 

Let us now work under the assumption that the current value of the region field is NM. So, the combo box shall display New Mexico, and not NM.

 

Screen 5.11

 

The framework embarks on its search from the beginning of the States array, and then calls the property shortName. It verifies every entry in the array, in order to confirm whether the value is 'NM' or otherwise. Once the value matches, it calls the property longName to display the full name in the combo box.

 

Thus, if the desired shortName were at a position that is deep down the array, it would entail summoning the shortName property numerous times. The long name property, for reasons unknown, gets called only twice. The state Washington is not present in the array. In a situation like this, the framework moves through all the members in the array. If no match is found, it displays the first member of the array, which in this case is Alaska.

 

To unravel the number of records in the table, we first need to access the DataTableCollection object. This is accomplished using the Tables property, which results in a collection. Thereafter, the tables are accessed, using the indexer. In order to access the table Customers, tc[0] is used. Subsequently, the Count property of the Rows collection is used to return the number of rows in the table.

 

The Binding Manager code, which is essential to move the active object, remains the same. So, we shall not delve upon it any further. All that we are trying to explain through the above example is that, by using one value in a database, we can display another value.

 

You need to commit it to your memory that, we are binding to a combo box for the first time ever. The combo box is bound to the SelectedValue property. If you comment out this binding, you will observe that the values remain impervious or unaltered. Further, if you click in the combo box, you will witness the display of the names of all the states.

 

Data Grids

 

a.cs

using System;

using System.Data;

using System.Drawing;

using System.Windows.Forms;

using System.Data.SqlClient;

public class zzz : Form

{

DataGrid d;

DataSet c;

public zzz()

{

d = new DataGrid();

d.Size = new Size(584, 336);

d.DataMember = "Customers1";

ClientSize = new Size(600, 413);

SqlConnection con = new SqlConnection("server=(local)\\NetSDK;

uid=QSUser;pwd=QSPassword;database=northwind");

SqlDataAdapter Cust = new SqlDataAdapter ("Select * from Customers", con);

c = new DataSet();

d.DataSource = c;

Cust.Fill(c, "Customers1");

d.DataMember = "Customers1";

Controls.Add(d);

}

public static void Main()

{

Application.Run(new zzz());

}

}

 

One of the most common uses of data is to position it in a tabular form. This format can be achieved by using a DataGrid control, which is a collection of columns and rows. The above program displays data from the Customer table in a data grid object.

 

Screen 5.12

 

We commence by creating a DataGrid object d, and assign it a certain size using the Size property. As earlier, we create a SqlConnection object to connect to the database server on a machine, and then, create a SqlDataAdaptor object Cust to represent all the data from the Customers table.

 

The DataSource property of the DataGrid is initialized to a freshly created, albeit, empty DataSet object, c. This is because a DataGrid displays data from a source, and therefore, the DataSource property is specifically introduced to identify the source. Dataset is not the only medium, since the data source can obtain a value from seven different entities. We shall explore this in greater detail in the next example.

 

Using the Fill function of the SqlDataAdaptor class, the DataSet 'c' is packed with data. You can assign any name to it. We have used Customer1 mainly to facilitate source mapping. Bear in mind that the DataSet, by itself, is devoid of any data. It comprises of other sources, which in turn, contain data. This highlights the fact that a DataSet contains supplementary data sources. This function is obtained from the DbDataAdapter class.

 

Finally, the DataSource for the DataGrid class is to be specified. As we have only one source i.e. Customers1, we have initialized the DataMember property to it. Had the DataMember property not been supplied with a value, i.e. d.DataMember = "", we would not have seen any data on start up. At this stage, a plus sign would be displayed in the empty grid. Clicking on the plus sign would then show the name of our solitary source, Customer1. This is displayed as a hyperlink. When we click on the link, in addition to the new dataset being displayed in the heading of the data grid, the previous data is also displayed.

 

Screen 5.13

Screen 5.14

 

If we add the line Cust.Fill(c, "Customers2") immediately after the first Fill function, it results in the creation of a second source. Thus, at this stage, two sources are present.

 

Screen 5.15

 

Therefore, clicking on the + sign in the DataGrid at this stage would display two different sources as hyperlinks. Clicking on either of the hyperlinks will exhibit the same set of data. The point worthy of notice is that, we can let the user dynamically choose the source that he wishes to work with.

 

a.cs

using System;

using System.Data;

using System.Drawing;

using System.Windows.Forms;

using System.Data.SqlClient;

public class zzz : Form 

{

DataGrid d;

public zzz()

{

d = new DataGrid();

d.Size = new Size(584, 336);

ClientSize = new Size(600, 413);

DataTable t = new DataTable("Control");

t.Columns.Add("Name");

t.Columns.Add("City");

DataRow r;

r = t.NewRow();

r["Name"] = "Vijay";

r["City"] = "Bombay";

t.Rows.Add(r);

r = t.NewRow();

r["Name"] = "Sonal";

r["City"] = "Delhi";

t.Rows.Add(r);

d.DataSource = t;

Controls.Add(d);

}

public static void Main()

{

Application.Run(new zzz());

}

}

 

As we had mentioned earlier, the data source should essentially be dynamic since data comes in different shapes and sizes.

 

In this program, we create a simple DataTable t with two columns named Name and City. This DataTable is then supplied as the DataSource. Since we are making use of an entity that contains data, we do not have to specify the DataMember explicitly.

 

Screen 5.16

 

This results in the display of two records in the DataGrid control.

 

The DataTable could also have been initialized by sourcing data from a database using the SqlDataAdaptor class.

 

a.cs

using System;

using System.Data;

using System.Drawing;

using System.Windows.Forms;

using System.Data.SqlClient;

using System.Collections;

public class CustomerList : System.Collections.CollectionBase {

public static CustomerList GetCustomers() {

CustomerList cl = new CustomerList();

Customer a = Customer.ReadCustomer1();

IList b = cl.List;

b.Add(a);

Customer c = new Customer("246-12-5645");

c.FirstName = "Vijay";

c.DateOfBirth = DateTime.Parse("5/3/1933");

cl.Add1(c);

cl.Add1(Customer.ReadCustomer2());

return cl;

}

public int Add1(Customer value) {

return List.Add(value);

}

}

public class Customer {

public string id,FirstName ;

public DateTime dateOfBirth;

public static Customer ReadCustomer1()

{

Customer cust = new Customer("536-45-1245");

cust.FirstName = "Sonal";

cust.DateOfBirth = DateTime.Parse("9/9/1941");

return cust;

}

public static Customer ReadCustomer2()

{

Customer cust = new Customer("651-27-8117");

cust.FirstName = "Manish";

cust.DateOfBirth = DateTime.Parse("3/25/1942");

return cust;

}

public Customer(string ID): base()

{

id = ID ;

}

public string ID

{

get  

{

return id ;

}

}

public string FirstName1

{

get 

{

return FirstName ;

}

}

public DateTime DateOfBirth

{

get 

{

return dateOfBirth ;

}

set

{

dateOfBirth = value ;

}

}

}

public class zzz : Form {

DataGrid d;

public zzz() {

d = new DataGrid();

d.Size = new Size(584, 336);

ClientSize = new Size(600, 413);

CustomerList custList;

custList = CustomerList.GetCustomers();

d.DataSource = custList ;

Controls.Add(d);

}

public static void Main() {

Application.Run(new zzz());

}

}

 

A DataSource for a DataGrid can comprise of seven different types of Data sources. These are as follows:

•     DataTable

•     DataView

•     DataSet

•     DataViewManager

•     Single dimensional array

•     IList interface

•     An object that implements the IListSource

 

Screen 5.17

 

The above example is merely a replica of the first example of this Chapter, where we had used a class derived from CollectionBase. The same rules as mentioned in the first example shall be applicable here also.

 

For e.g. the presence of properties that represent column names, etc. As we have only three properties, only three columns are displayed in the DataGrid. This program also illustrates the utilization of the varied data sources in a data grid.

 

a.cs

using System;

using System.Data;

using System.Drawing;

using System.Windows.Forms;

using System.Data.SqlClient;

public class zzz : Form {

DataGrid d;

DataSet c;

public zzz()

{

d = new DataGrid();

d.Size = new Size(584, 336);

d.DataMember = "Customers1";

ClientSize = new Size(600, 413);

SqlConnection con = new SqlConnection("server=(local)\\NetSDK;

uid=QSUser;pwd=QSPassword;database=northwind");

SqlDataAdapter Cust = new SqlDataAdapter ("Select * from Customers", con);

c = new DataSet();

d.DataSource = c;

Cust.Fill(c, "Customers1");

d.DataMember = "Customers1";

d.AlternatingBackColor = Color.Red;

d.BackColor = Color.Blue;

d.BackgroundColor = Color.Green;

Rectangle r = d.Bounds;

System.Console.WriteLine(r);

r = new Rectangle(1,100,200,400);

d.Bounds = r;

Controls.Add(d);

}

public static void Main()

{

Application.Run(new zzz());

}

}

 

Screen 5.18

Screen 5.19

 

In the above example, we have a large number of properties whose values can be altered. Within a DataGrid, if we click on a column, a sort is performed on the values in the column. If the column is clicked again, the sort order is reversed. The column which decides the sorting has an arrow displayed next to it.

 

Sorting is enabled by default and can be disabled by initializing the property AllowSorting to False. However, there is no way of disabling the facility to sort on a single column. Provision is available to sort on an expression.

 

The property AlternatingBackColor bestows a ledger-like appearance to our Grid. The background color of every alternate row is of a specific shade. In this case, it is red. The BackColor property of the DataGrid control, which bestows every row with the same background color, is set to blue. The default color for this property is the system color of Windows. Setting the BackColor property to Color.Empty switches the color mode to the default color.  Thus, we observe that the first row and every alternate row thereafter are blue in color, and the remaining rows are red in color.

 

There is a narrow tract at the bottom of the grid, which is displayed in green color. This color is determined by the value assigned to the property BackgroundColor. This narrow strip, which is part of the non-row area of the grid, is distinctly visible when the grid comprises of only a few rows, or when there is no table to be displayed in the grid.

 

The size of the DataGrid control can be controlled programmatically. The Bounds property in the DataGrid is a read-write property. Hence, the default rectangle structure, which contains the DataGrid, can be displayed using the WriteLine function. The X and Y co-ordinates are specified as 0,0, the Width is 584 pixels and the Height is 336 pixels.

 

Thus, it is evident that we have the discretion to determine the size of the DataGrid, since we have to share real estate on our Window with other controls.

 

a.cs

using System;

using System.Data;

using System.Drawing;

using System.Windows.Forms;

using System.Data.SqlClient;

public class zzz : Form

{

DataGrid d;

DataSet c;

public zzz()

{

d = new DataGrid();

d.Size = new Size(584, 336);

d.DataMember = "Customers1";

ClientSize = new Size(600, 413);

SqlConnection con = new SqlConnection("server=(local)\\NetSDK;uid=QSUser;

pwd=QSPassword;database=northwind");

SqlDataAdapter Cust = new SqlDataAdapter ("Select * from Customers", con);

c = new DataSet();

d.DataSource = c;

Cust.Fill(c, "Customers1");

d.DataMember = "Customers1";

d.CaptionForeColor = Color.Blue;

d.CaptionBackColor = Color.Red;

d.CaptionText = "Vijay Muhki";

Font f = new Font("Arial",10);

d.CaptionVisible = true;

Rectangle r = d.ClientRectangle;

System.Console.WriteLine(r);

Size s = d.ClientSize;

System.Console.WriteLine(s);

System.Console.WriteLine(d.Height + " " + d.Width);

System.Console.WriteLine(d.Left + " " + d.Right);

Point p = d.Location;

System.Console.WriteLine(p.X + " " + p.Y);

System.Console.WriteLine(d.Size);

d.ColumnHeadersVisible = false;

System.Console.WriteLine(d.CompanyName);

Control.ControlCollection cc =  d.Controls;

System.Console.WriteLine(cc.Count);

Control c1,c2;

c1 = cc[0]; c2 = cc[1];

System.Console.WriteLine(c1);

System.Console.WriteLine(c2);

System.Console.WriteLine(d.HasChildren);

Controls.Add(d);

}

public static void Main()

{

Application.Run(new zzz());

}

}

 

Output

{X=0,Y=0,Width=584,Height=336}

{Width=584, Height=336}

336 584

0 584

0 0

{Width=584, Height=336}

Microsoft Corporation

2

System.Windows.Forms.HScrollBar, Minimum: 0, Maximum: 100, Value: 0

System.Windows.Forms.VScrollBar, Minimum: 0, Maximum: 0, Value: 0

True

 

A caption, which is similar to a Windows title, is displayed above the column names. It furnishes additional information to the user. The text to be displayed is decided by the CaptionText property, which by default is an empty string.

 

Screen 5.20

 

The CaptionForeColor property determines the foreground color of the caption, while the CaptionBackColor decides on the background color. The CaptionFont property refers to the font in which the text is to be displayed. The boolean value in the property CaptionVisible exhibits or suppresses the display of the caption.

 

There are a large number of properties for a Caption since it is derived from the base class of Control. The documentation specifies whether the property is read-write or not, thereby signifying whether change is permitted at the design stage or not.

 

The ClientRectangle property, which is read-only, displays the same results as that of the Bounds property. The co-ordinates are relative to the upper left corner of the client area or window. Thus, they start at X=0 and Y=0. The width and height are used as the drawing surface within which data of the grid is to be placed.

 

The ClientSize property returns a Size object that contains the Height and Width of the DataGrid. These dimensions are akin to those returned by the Bounds property. The Height and Width properties also return the same values. The Left property denotes the leftmost edge and returns zero. The Right property denotes the rightmost edge of the data grid, which happens to be 584. It therefore returns this value. The Location property returns a point whose X and Y co-ordinates have the value 0,0 since that is where the left edge of the DataGrid commences. Finally, the Size property once again returns the same data containing the width and height.

Thus, we have a large number of properties, which return the same information. The property ColumnHeadersVisible is similar to CaptionVisible. It is boolean and decides whether the column/field names are to be displayed above the data or not. This row is also called the parent row.

 

The CompanyName property divulges the name of the company that created the control. The answer, quite obviously, is Microsoft Corporation.

 

Every control has a property called Controls that returns a ControlCollection object. The collection constitutes of a list of controls, which form the DataGrid. The Count property in the Collection reports that the DataGrid control is made up of 2 controls. Using the indexer, we access the two individual controls, c1 and c2. The WriteLine function displays these two controls as the vertical and horizontal scrollbars.

 

For the ones who tuned in late, every class has a ToString function that discloses relevant information about the class. The output of the function ToString varies, depending upon what the class wants to reveal about itself.

 

In order to verify that the DataGrid object is fabricated from other controls, we display the value contained in the property HasChildren. The return value is True.

 

DataGrid Events

 

a.cs

using System;

using System.Data;

using System.Drawing;

using System.Windows.Forms;

using System.Data.SqlClient;

public class zzz : Form {

DataGrid d;

DataSet c;

public zzz() {

d = new DataGrid();

d.Size = new Size(584, 336);

d.DataMember = "Customers1";

ClientSize = new Size(600, 413);

SqlConnection con = new SqlConnection("server=(local)\\NetSDK;

uid=QSUser;pwd=QSPassword;database=northwind");

SqlDataAdapter Cust = new SqlDataAdapter ("Select * from Customers", con);

c = new DataSet();

d.DataSource = c;

Cust.Fill(c, "Customers1");

d.DataMember = "Customers1";

d.MouseDown += new MouseEventHandler(abc);

Controls.Add(d);

}

void abc(object s, MouseEventArgs e){

System.Console.WriteLine(e.X + " " + e.Y + " " + e.Clicks + " " + e.Button) ;

}

public static void Main() {

Application.Run(new zzz());

}

}

 

Output

87 66 1 Left

90 82 1 Right

84 101 1 Middle

 

 

Screen 5.21

 

Event handling is the arena, wherein the true ability of a Control comes into focus. We desire that our own event-handling function should be called, every time an event occurs in the DataGrid control. So far, whenever the control has been displayed, none of our code has come into play.

 

In the above example, we desire that whenever the user clicks in the DataGrid, our code should be executed. To accomplish this, we trap one of the Events using the MouseEventHandler delegate, and ensure that the function abc is called every time someone clicks in the DataGrid.

 

The handler functions is always given the control that generated the event as the first parameter. In our case, it is the DataGrid. You can verify this by executing the WriteLine function, which displays the name of the control as System.Windows.Forms.DataGrid.

 

The second parameter to abc is an object that contains the X and Y co-ordinates i.e the location at which the mouse was clicked; the number of times the mouse was clicked; and finally, the button that was employed.

 

You are at liberty to insert the code that you wish to execute, when the event is called. The basic concept is that, certain events that occur in the DataGrid can be trapped and, accordingly, specific user-defined functions can be called.  We will focus on some of these events in the forthcoming programs.

 

a.cs

using System;

using System.Data;

using System.Drawing;

using System.Windows.Forms;

using System.Data.SqlClient;

public class zzz : Form

{

DataGrid d;

DataSet c;

public zzz()

{

d = new DataGrid();

d.Size = new Size(584, 336);

d.DataMember = "Customers1";

ClientSize = new Size(600, 413);

SqlConnection con = new SqlConnection("server=(local)\\NetSDK;

uid=QSUser;pwd=QSPassword;database=northwind");

SqlDataAdapter Cust = new SqlDataAdapter ("Select * from Customers", con);

c = new DataSet();

d.DataSource = c;

Cust.Fill(c, "Customers1");

d.DataMember = "Customers1";

d.CurrentCellChanged += new EventHandler(abc);

Controls.Add(d);

}

void abc(object s, EventArgs e)

{

DataGridCell g =  d.CurrentCell;

int c = g.ColumnNumber ;

int r = g.RowNumber;

System.Console.WriteLine( "Column " + c + " Row " + r + " " + d[r,c]);

d[r,c + 1] = "hi " + r;

}

public static void Main() {

Application.Run(new zzz());

}

}

 

Output

Column 0 Row 1 ANATR

Column 1 Row 2 Antonio Moreno Taquer‘a

Column 2 Row 3 Thomas Hardy

 

It is more constructive to trap the event CurrentCellChanged, which gets activated whenever a cell is selected from the DataGrid. The function abc gets called with similar parameters as identified by the event in the earlier program.

 

In function abc, using the DataGrid property of CurrentCell of type DataGridCell, we retrieve information such as the Column number, RowNumber etc. of the current cell. The row number is stored in the variable r, whereas the column number is stored in the variable c.

 

The indexer in the DataGrid aids us in accessing the contents of the cell that has been clicked on. Since the indexer requires the row number followed the column number, we supply it with variables r and c.

 

This indexer is read-write. Therefore, it allows us to change the value of any cell in the grid. Here, we have altered the value of the column to the right of the current cell to contain the string 'hi', followed by the row number. This demonstrates the flexibility provided by a DataGrid control.

 

Screen 5.22

 

Every minuscule aspect of the grid can be altered at run time, but all properties are not available at design time. The events that a grid can respond to are ever so many to be listed here. The only event that it does not presently respond to is the 'End of the World' event, which as per our conviction, Microsoft is likely to redress in the next version.

 

Master-Detail or Parent-Child Relationship

 

a.cs

using System;

using System.Data;

using System.Drawing;

using System.Windows.Forms;

using System.Data.SqlClient;

public class zzz : Form {

ccc c;

DataGrid d;

public zzz() {

d = new DataGrid();

c = new ccc();

d.BeginInit();

d.PreferredRowHeight = 16;

d.Size = new System.Drawing.Size(584, 336);

d.DataSource = c;

d.DataMember = "Customers";

d.ForeColor = System.Drawing.Color.Navy;

d.Location = new System.Drawing.Point(8, 8);

d.BackColor = System.Drawing.Color.Gainsboro;

d.AlternatingBackColor = System.Drawing.Color.WhiteSmoke;

ClientSize = new Size(600, 413);

c.DataSetName = "CustomersDataSet";

Controls.Add(d);

d.EndInit();

SqlConnection con = new SqlConnection("server=(local)\\NetSDK;

uid=QSUser;pwd=QSPassword;database=northwind");

SqlDataAdapter Cust = new SqlDataAdapter ("Select * from Customers", con);

SqlDataAdapter Ord = new SqlDataAdapter ("Select * from Orders", con);

Cust.Fill(c, "Customers");

Ord.Fill(c, "Orders");

}

public static void Main() {

Application.Run(new zzz());

}

}

public class ccc : DataSet {

cus tc;

Orders to;

DataRelation r;

public ccc()

{

tc= new cus("Customers");

Tables.Add(this.tc);

to= new Orders("Orders");

Tables.Add(this.to);

r = new DataRelation("custord",tc.cID,to.oID);

//r = new DataRelation("custord",new DataColumn[]{tc.cID},new DataColumn[]{to.oID});

Relations.Add(r);

}

}

public class cus : DataTable

{

public DataColumn cID;

public cus(string name) : base(name)

{

cID = new DataColumn("CustomerID");

Columns.Add(cID);

PrimaryKey = new System.Data.DataColumn[] {cID};

}

}

public class Orders : DataTable

{

public DataColumn oID;

public Orders(string name) : base(name)

{

oID = new DataColumn("CustomerID");

Columns.Add(oID);

}

}

 

First, let us steal a look at what occurs when the above program is run.

 

Screen 5.23

 

When the form loads on, we see a list of customers from the Customers table, with a plus sign displayed on the left.

 

Screen 5.24

 

Clicking on the plus sign will exhibit a hyperlink with the word 'custord'. When we click on the hyperlink, a list of orders placed by this customer along with the customer details is displayed on the first row. The topmost right hand corner has a back button, which takes us back to the Customers table.

 

Screen 5.25

 

This is a perfect example of a master-detail relationship. What follows next, is an explanation of the code that implements this parent-child relationship.

 

At the outset, the DataGrid must be populated with data from a database. At times, it may take considerable time to fetch this data. While this process is on, we would obviously not want the user to interact with or use the control. In order to forbid interference by the user while the data is being retrieved, the DataGrid control provides us with two functions, viz., BeginInit and EndInit. The function BeginInit informs the DataGrid that initialization has begun and the function EndInit signals that the process of data retrieval has been accomplished. The EndInit function is placed at the absolute end of the code handling the database. If we comment out the EndInit function, the DataGrid behaves similarly, but with one small difference, i.e. it now assumes a read-only state.

 

The DataSource property is an object that is normally derived from DataSet. In our program this property is initialized to c, which is an instance of class ccc. Class ccc is derived from DataSet. The constructor of this class performs many interesting actions.

 

We begin by creating an object tc, which is an instance of class cus. This class in turn, is derived from class DataTable. In the constructor of class cus, the parameter 'Customers' is supplied to the constructor of the DataTable, using the base keyword. Thus, the DataTable is now called Customers. We also create a DataColumn object cID, which is named CustomerID, and we use the Add function in the Columns collection of the DataTable class, to add this column to the Customers DataTable.

 

Every table should essentially consist of one or more columns, which can uniquely identify a row of the table. This set of columns is called a Primary Key. The PrimaryKey property is set to an array of DataColumn objects, which constitute the columns comprising the primary key. Normally, the primary key is a single column. Most modern databases would not create a table without first coercing the user to specify the primary key. In this program, the setting of the PrimaryKey property is optional. However, creating the column called CustomerID, is mandatory.

 

Now that we have created a DataTable object, we need to add it to the DataSet. This is achieved by employing the Add function in the Tables collection. An instance of class Orders that is derived from class DataTable, is created and named as Orders. This table has one column with the same name CustomerID. The Orders table is then added to the DataSet. You may note that this keyword is optional.

 

The very quintessence of the application is the DataRelation object. During the creation of the object r, the constructor of the DataRelation object is called with three parameters. The first parameter is the name of the data relation. A name is to be provided, whenever more than one entity of the same type is created. In this case, the name custord becomes a hyperlink, and gets displayed whenever we click on the plus sign. The next two parameters are the parent and child columns, which are related to each other. The column CustomerID from the Customers table is related to the CustomerID column in the Orders table, in order to establish a one-to-many relationship. So, we specify these DataColumn objects as the next two parameters.

 

The DataRelation object can also be related with a DataColumn object, expressed as an array, in case there are multiple DataColumn objects within the relation. The commented line displays the same relation expressed as an array of DataColumn objects. We finally add this freshly minted Relation object to the Relation collection. 

 

It is obligatory to derive from the DataSet class, since both the DataTable objects, Customers and Orders, need to be added. Since a DataRelation had to be created between two columns, one each from these tables, we had to create two Data Column objects in classes derived from the DataTable class.

 

This proves that data has not been physically added to the DataTable. Presently, the DataTable comprises of only two columns and a relation.

 

The utility of the second parameter to the Fill function, which is the name of the mapping table, will now become apparent. At this juncture, we associate the data from the database with the tables Customer and Order, through the SqlDataAdapter objects Cust and Ord.

 

As the DataRelation is created through these tables, the DataGrid displays the plus sign. The second parameter to the Fill function is called the Mapping Tables parameter. This parameter is needed, unless we wish to build the relations in the database on our own, which is, by all odds, not a straightforward task. If the AllowNavigation property is set to False, the links to the child tables shall no longer be visible.

 

This program clearly demonstrates how we can impose our own relationships on data contained in databases, which have been created by others.

 

a.cs

using System;

using System.Data;

using System.Drawing;

using System.Windows.Forms;

using System.Data.SqlClient;

public class zzz : Form

{

StatusBar s;

CustomersDataSet customersDataSet1;

Button b;

DataGrid d;

public zzz()

{

d = new DataGrid();

s = new StatusBar();

customersDataSet1 = new CustomersDataSet();

b = new Button();

d.BeginInit();

d.PreferredRowHeight = 16;

d.Size = new System.Drawing.Size(584, 336);

d.DataSource = customersDataSet1;

d.DataMember = "Customers";

d.ForeColor = System.Drawing.Color.Navy;

d.Location = new System.Drawing.Point(8, 8);

d.BackColor = System.Drawing.Color.Gainsboro;

d.AlternatingBackColor = System.Drawing.Color.WhiteSmoke;

AcceptButton = b;

ClientSize = new Size(600, 413);

s.BackColor = System.Drawing.SystemColors.Control;

s.Size = new System.Drawing.Size(600, 16);

s.Text = "Click on Load";

s.Location = new System.Drawing.Point(0, 397);

customersDataSet1.DataSetName = "CustomersDataSet";

b.FlatStyle = FlatStyle.Flat;

b.Size = new Size(112, 32);

b.Text = "&Load";

b.Location = new Point(480, 352);

b.Click += new System.EventHandler(abc);

Controls.Add(s);

Controls.Add(b);

Controls.Add(d);

d.EndInit();

}

void abc(object sender, System.EventArgs e)

{

Cursor cu = Cursor.Current;

try

{

Cursor.Current = Cursors.WaitCursor;

SqlConnection con = new SqlConnection("server=(local)\\NetSDK;uid=QSUser;pwd=QSPassword;database=northwind");

SqlDataAdapter Cust = new SqlDataAdapter ("Select * from Customers", con);

SqlDataAdapter Ord = new SqlDataAdapter ("Select * from Orders", con);

SqlDataAdapter OrdD = new SqlDataAdapter ("Select * from [Order Details]", con);

s.Text ="Loading Customers...";

Cust.Fill(customersDataSet1, "Customers");

s.Text ="Loading Orders...";

Ord.Fill(customersDataSet1, "Orders");

s.Text ="Loading Order Details...";

OrdD.Fill(customersDataSet1, "Order_Details");

s.Text ="Updating Grid...";

}

finally

{

s.Text ="Done";

Cursor.Current = cu;

}

}

public static void Main()

{

Application.Run(new zzz());

}

}

public class CustomersDataSet : DataSet

{

Customers tc;

Orders to;

Order_Details td;

DataRelation ro;

DataRelation rd;

public CustomersDataSet()

{

tc = new Customers("Customers");

Tables.Add(tc);

to = new Orders("Orders");

Tables.Add(to);

td = new Order_Details("Order_Details");

Tables.Add(td);

ro = new DataRelation("CustomersOrders", new DataColumn[] {tc.cID}, new DataColumn[] {to.columnCustomerID}, false);

Relations.Add(ro);

rd = new DataRelation("OrdersOrder_Details", new DataColumn[] {to.columnOrderID}, new DataColumn[] {td.odID}, false);

Relations.Add(rd);

}

}

public class Customers : DataTable

{

public DataColumn cID;

public Customers(string name) : base(name)

{

cID = new DataColumn("CustomerID");

Columns.Add(cID);

}

}

public class Orders : DataTable

{

public DataColumn columnOrderID;

public DataColumn columnCustomerID;

public Orders(string name) :  base(name)

{

columnOrderID = new DataColumn("OrderID ", typeof(int));

Columns.Add(this.columnOrderID);

columnCustomerID = new DataColumn("CustomerID", typeof(string));

Columns.Add(this.columnCustomerID);

}

}

public class Order_Details : DataTable

{

public DataColumn odID;

public Order_Details(string name) : base(name)

{

odID= new DataColumn("OrderID", typeof(int));

Columns.Add(odID);

}

}

 

On executing the program, we witness an empty DataGrid with a star symbol and the word 'CustomerID' displayed. The status bar perceptibly advises us to click on the button labeled 'Load'.

 

Screen 5.26

Screen 5.27

 

After we have done so, a list of customers is displayed, along with a plus sign. If we click on the plus sign, a hyperlink on CustomerOrders will be displayed. A click on this link, would lead to a list of orders placed by this customer.

 

There has been an addition to the program. A plus sign is displayed with every order, which expands to a hyperlink OrdersOrder detail. If we click on this hyperlink, the actual items included in the particular order, are to be displayed.

 

Screen 5.28

Screen 5.29

 

The first line of the DataGrid also reveals both, the customer details, as well as, the order details. If we click just once on the Back button, it takes us back to the orders. Clicking on it again, takes us further back to the list of customers.

 

We add a StatusBar, as is customary in most applications, to display user interface messages. The constructor of the class ccc, performs the same tasks as explained in the earlier program. Previously, we had created two tables; but now, we create three tables, i.e. Customers, Orders and Order_Details.

 

The Customers table has one column called CustomerID; the Orders table has two columns named CustomerID and OrderID; and the Order_Details table has only one column called OrderID. We now create two relations. The first one is called CustomersOrders that relates the CustomerID columns in the two tables, as before. The second relation is named OrdersOrder_Details, which relates the OrderID column from the Orders table, to the OrderID column from the OrderDetails table. Thus, the only difference between this program and its predecessor is, the inclusion of an additional table, column and relation.

 

The DataSource that represents a DataSet is made up of three tables. The question that is expected to surface in our minds is: Which of the three tables should be used in the initial display? The DataSource property decides the initial table. Since we have specified Customers, we see only one column, i.e. CustomerID. If we comment out the line d.DataMember = "Customers", only a plus sign would be displayed without any column name. Clicking on the plus sign would demonstrate the list of three tables. Finally, when we click on the link that displays Orders, we shall spot the two columns that we have created.

 

Screen 5.30

Screen 5.31

 

The property DataSetName can be ignored for the moment. If we click on the button, the function abc gets called. In this function, we first save the current cursor in a Cursor object called cu. This current cursor is stored in the property Current of the Cursor object. Then, we modify the current cursor to the Wait Cursor, since it is extremely time consuming to write the code, in order to retrieve data from the database.

 

We use the Fill function in the similar manner as used before, and then change the text displayed in the Status Bar, depending upon the table that is being filled up from the database. It is advisable to place database-handling code in a try catch statement for error handling. Code placed in the finally clause, resets user interface widgets like the cursor, status bar, etc.

 

We can have as many data relations as we yearn for, and we can build as many logical relations between tables as we covet. In the above program, we have two levels; but surely, many more levels are realizable !