6. The C# code in .Net
Applications
In the previous chapter, we had
explored all that was required to be learnt and discovered about C#, in order to
understand the code generated by the Framework. A crucial point that screams
for attention is, that any application built in Visual Studio.Net with Project
Type as Visual C#, eventually is converted into C# code. In this chapter, we
will build applications in Visual Studio.Net, and at every stage thereon, we
shall explain the code generated by the .Net Framework. We could have written
the same code manually, but it certainly gives our productivity a boost, if the
code is automatically generated by the system.
Let us revert back to Visual
Studio.Net, to create a new application. As always, click on menu File - New -
Project. In the dialog box, select Visual C# Project in Project Type pane, and
Windows Application in the Template pane. Enter the name z10, and ensure that
the location is set to c:\mukhi. On pressing F5 to run the application, you
will come across a completely blank window.
When you navigate into the
folder c:\mukhi\z10, you will encounter a large number of files, created by
this framework. One of the many files created, is named as Form1.cs. This is
similar to the .cs file, which was created in the previous chapter. Now, run
the compiler using the command
>Csc Form1.cs
The compiler creates an exe file
named Form1.exe, which when executed, reveals the same window, as is seen with
the F5 command. The program Form1.cs has been generated by Visual Studio.Net,
which internally calls the C# compile and creates the executable file. Pressing
F5 runs the executable. Let us now
attempt at understanding the code, that gets generated in Form1.cs.
Form1.cs
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
namespace z10
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.Size = new System.Drawing.Size(300,300);
this.Text = "Form1";
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
}
}
We hope to be able to refresh your
memory by reiterating that, the above program has been written by the C#
compiler, and not by us. The file reveals a large number of statements
beginning with the term 'using'. For those of you who tuned in late, the
'using' statements are inserted to ease the typing effort. The name of a
namespace is provided after the 'using' keyword, so that the namespace is not
required to be inserted before each 'class.function name' combination. As the
project has been named z10, the entire C# program is depicted as enclosed
within the namespace of z10. Since the solitary role of a namespace is to
ensure that the class names in one project, do not clash with class names in
the other projects, it can safely be overlooked.
You may recollect that in the
last chapter, we had gathered that any line beginning with three slashes, is an
XML comment. This program appears very large, due to the comments inserted at
every possible nook and corner. More often than not, comments are not of much
utility, since they are computer generated; and thus, lack the essential human
touch. In future, we will strip-off all comments, before displaying the code.
A class known as Form1, which is
derived from the class Form, contains all the code required for the current project.
In spite of employing the term 'using', which includes System.Windows.Forms,
the code generated, still contains the full name of the class. It is absurd and
illogical, but then; computers are not expected to generate code that makes
perfect sense. In the first place, an instance variable called 'components' in
the class, has been initialized to 'null', which represents no value. The
'private' modifier limits the access only to the members of the current class.
The Run function, located in the
Main function, is called after passing it an object, which is an instance of
Form1. The newly created object calls the constructor of the Form1 class, and
then, it calls the Run function. The constructor simply calls a function named
InitializeComponent.
But prior to this, study the
square brackets above the Main function closely. It represents an 'attribute'.
Attributes have a range of functions, which undertake certain tasks, such as,
determining the wherewithal of generating the code with the aid of the compiler,
or documenting the classes in the executable file, etc. We shall not venture
into the details of the attribute [STAThread], however, for those burning with
curiosity, it suffices to say that the full form of STA is Single Threaded
Apartment. It implies that, once a window is created in a thread, it will not
be permitted to switch to another thread. If you are unable to comprehend the
finer nuances of the above statement, then cheer up, you have company!
It is in this function, that all
the form widgets get initialized. We employ this function, only in the event of
initializing the object components. The computer program that generated the
code, prefixed all the instance variables with the term 'this'. In this case,
eliminating the term 'this', does not affect the outcome. The class Form has a
large number of instance variables, such as Size and Text, which we are
entitled to be used in the class Form1.
The Size variable or Size
property, determines the size of the initial window, while the Text property
decides the title of the window. The 'this' keyword is optional. A point to be
noted is that, all properties have a default value. No sooner do we change a
single property in the Property window, a line gets inserted in the
InitializeComponent function. The Size property is initialized to a Size
object, which is passed the width and height of the window in pixels. Thus, a
GUI is used to build applications and all actions get converted into C# code.
The next function called
Dispose, can only be called by the system, as and when it is required. A
programmer does not enjoy the facility of calling it manually. We are
completely aware about the circumstances under which a constructor gets called.
Further, we also know unequivocally, that the Dispose function gets called,
when the resources in the program are to be disposed off. But, we are not aware
as to when this disposal takes place; in other words, when is an object going
to die. In C#, unlike C++, there is no control at all, over the termination of an
object. The Garbage Collector is solely responsible for removing an object from
memory; thereby, shielding the programmer from carrying out any memory
management chores. The Dispose function can also be ignored, since it merely
calls the Dispose function from the Components object and the base class.
Hence, the use of keyword 'base' can be explained. In our next display, we do
not intend to expose you to the Dispose function any longer.
Now, its time we got back to
Visual Studio.Net. Double click on the form. As is always the case, the Code
Painter gets displayed. Now, press Ctrl+Home to move to the beginning of the
page, or scroll up till the first line is visible.
|
Screen 6.1 |
Screen 6.1 presents the code
that is displayed in our window. Here, we discern a list of 'using' statements
and a minus sign, extending over an entire span of six 'using' statements. Now,
click on the minus sign, and observe your screen change its display, as shown
in screen 6.2.
|
Screen 6.2 |
To our utter astonishment, all
the 'using' statements vanish, and what is remnant is a plus sign and the term
'using', with a series of dots as residues. Thus, the editor is conscious of
how it has to work with the C# language. It is not merely a word processor, but
a word processor that knows and discerns the C# programming language, better
than we do. The words, also known as keywords, that are a part of the C#
language, are depicted in a different color. Also, every separate entity begins
with a minus sign. Now, click on the minus sign in front of class. Screen 6.3
displays the Code Painter.
|
Screen 6.3 |
Clicking on the minus sign of
the class, compresses it into a series of dots. This signifies that it is the
class that encapsulates all the code. We can compress any entity in C# and
thereby, remain at the level of detail we are comfortable with. With a small
screen, we need to be very selective about the level of detail that can be
displayed. Now, incorporate a button onto the Form Design and double click on
it. The addition of a button, results in a variation in the code also. The
program is shown below.
Form1.cs
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
namespace z10
{
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.Button button1;
private System.ComponentModel.Container components = null;
public Form1()
{
InitializeComponent();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
this.button1.Location = new System.Drawing.Point(40, 64);
this.button1.Name = "button1";
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.Click += new System.EventHandler(this.button1_Click);
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.AddRange(new System.Windows.Forms.Control[] { this.button1});
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);
}
#endregion
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private void Form1_Load(object sender, System.EventArgs e)
{
}
private void button1_Click(object sender, System.EventArgs e)
{
}
}
}
The above program displays the
code that is generated after the button is clicked. A large portion of the code
remains the same, as was noticed in the earlier program. Therefore, we shall
desist from harping upon it any longer. Besides, from now onwards, only
relevant parts of the code will be revealed, to avoid redundancy and confusion,
and also to conserve paper.
The newly added button in the
form is called button1. Therefore, an instance variable is created with the
same name, i.e. button1. The data type is Button, which is a class in the Forms
namespace. Any GUI code that needs to be executed before the window is
displayed, is always placed in the InitializeComponent function.
Any line that originates with
the # symbol, is known as a pre-processor directive. The pre-processor is a program that executes
before the C# compiler commences its work. The #region command displays a minus
sign. If we click on it, the region which extends from this sign upto the
#endregion, collapses. The words displayed in the box, remain the same, as
shown after the #region directive. This is amply evident in screen 6.4.
|
Screen 6.4 |
The InitializeComponent function
contains a large number of statements that deal with the Button object. First
and foremost, an instance of the button object is created, with the help of the
'new' keyword. The function SuspendLayout, as the name itself suggests, does
not execute any code that deals with placement of controls or layout, until the
function ResumeLayout is called. Invariably, this is the very last function to
be called. The Location property is initialized to the position where the
control is placed, and the name is the text displayed on the button. The Click
property is called a 'delegate' in C#, which is associated with a function that
gets called, each time the button is clicked. The name of the function is
passed as a parameter to the constructor of class named EventHandler.
There could be multiple buttons
present in a form. Each of them would necessitate a separate function, which is
to be called when the button is clicked. Therefore, the approach adopted by the
programmers for determining the function name, is as follows: The name of the
button, followed by an underscore, followed by the word 'Click'. In this
manner, distinct names are assigned to each of the functions, since the names
of the controls are always unique.
All this discussion proves to be
inefficacious, unless we display a button on the screen. To achieve this, the
services of the AddRange function, belonging to the Controls object in the
Forms class, are employed. This function accepts an array of the Control
datatype, which holds widgets to be displayed. In this case, there is only a
single button; however, in real life, there could be numerous controls. This
function is used to add multiple controls, simultaneously.
Thus, by using the Graphical
interface, life becomes a lot more simpler, since we can eschew the drudgery of
manually entering a horde of new changes, embodied in the form. Imagine how
cumbersome it would be to write and re-write the code, each time the button is
shifted, even a little. A new value would have to be assigned to the Location
property, every time the button is moved.
Each time we double click on the
Form, we are directed to a function called Form1_Load. This is due to the fact
that the property Load, belonging to the Form class, is assigned this value.
The code in the above function, is executed before the window is displayed, but
after the execution of the function InitializeComponent.
Everything about Visual
Studio.Net is dynamic. Any change incorporated in the value of a property in
the Code Painter, gets reflected instantaneously. Locate the property called
Text in the Code Painter, which is shown as:
this.button1.Text = "button1";
Now, change it to the
following:
this.button1.Text = "vijay";
Then, click on the Design Tab. Some
processing takes place for a while, before the Form is activated. In the form,
the Text property changes to 'vijay' immediately. Now, add the following line
in the function InitializeComponent, just below the Location property:
this.button1.Size = new System.Drawing.Size(72, 80);
The moment we click on the
Design Tab, the size of the button undergoes a transformation. The Form Design
and the Properties window also change. In the Properties window, the value of
Size changes immediately. In the Design, delete the button by selecting it, and
then, pressing the Delete key. Switch to the Code Painter, and you will witness
that, all references to the button named button1, have been removed from the
code, including the ones entered by us. However, the function button1_Click,
does not get erased. There appears to be a bug in the Beta copy of the product.
We presume that this bug will be fixed in the final copy of the product.
We have not saved anything so
far. We would advise you against changing anything in the Code Painter.
Instead, you may use the Properties Window for this purpose. The C# compiler
couldn't care less about who has actually entered the code. It merely validates
the syntax and creates an exe file. This exe file is then executed, using the F5
option.
The next example relates to the
code generated by the Framework when data is displayed from a database. So,
select the Data tab from the toolbox, and bring an OleDbDataAdapter into the
form. In case you are unable to recollect the steps, peruse through Chapter 2
of this book. Select the data connection of VMUKHI.Northwind.dbo, and insert
the following SQL statement:
SELECT CustomerID, CompanyName, ContactName FROM Customers
In the Form, the
OleDbDataAdapter control also adds another control called oleDbConnection1.
Switch to the Code Painter and observe the modifications made to the code.
private System.Data.OleDb.OleDbDataAdapter oleDbDataAdapter1;
private System.Data.OleDb.OleDbCommand oleDbSelectCommand1;
private System.Data.OleDb.OleDbCommand oleDbInsertCommand1;
private System.Data.OleDb.OleDbCommand oleDbUpdateCommand1;
private System.Data.OleDb.OleDbCommand oleDbDeleteCommand1;
private System.Data.OleDb.OleDbConnection oleDbConnection1;
At first, six instance variables
are created. Apart from the oleDbDataAdapter1 and oleDbConnection1, four more
variables are created, each belonging to the data type OleDbCommand. Now,
expand the region 'Windows Form Designer generated code', and view the revised
code in the InitializeComponent function.
private void InitializeComponent()
{
this.oleDbDataAdapter1 = new System.Data.OleDb.OleDbDataAdapter();
this.oleDbSelectCommand1 = new System.Data.OleDb.OleDbCommand();
this.oleDbInsertCommand1 = new System.Data.OleDb.OleDbCommand();
this.oleDbUpdateCommand1 = new System.Data.OleDb.OleDbCommand();
this.oleDbDeleteCommand1 = new System.Data.OleDb.OleDbCommand();
this.oleDbConnection1 = new System.Data.OleDb.OleDbConnection();
this.oleDbDataAdapter1.DeleteCommand = this.oleDbDeleteCommand1;
this.oleDbDataAdapter1.InsertCommand = this.oleDbInsertCommand1;
this.oleDbDataAdapter1.SelectCommand = this.oleDbSelectCommand1;
this.oleDbDataAdapter1.TableMappings.AddRange(new System.Data.Common.DataTableMapping[] {
new System.Data.Common.DataTableMapping("Table", "Customers", new System.Data.Common.DataColumnMapping[] {
new System.Data.Common.DataColumnMapping("CustomerID", "CustomerID"),new System.Data.Common.DataColumnMapping("CompanyName", "CompanyName"), new System.Data.Common.DataColumnMapping("ContactName", "ContactName")})});
this.oleDbDataAdapter1.UpdateCommand = this.oleDbUpdateCommand1;
this.oleDbSelectCommand1.CommandText = "SELECT CustomerID, CompanyName, ContactName FROM Customers";
this.oleDbSelectCommand1.Connection = this.oleDbConnection1;
this.oleDbInsertCommand1.CommandText = "INSERT INTO Customers(CustomerID, CompanyName, ContactName) VALUES (?, ?, ?);"+
"SELECT CustomerID, CompanyName, ContactName FROM Customers WHERE (CustomerID = ?)";
this.oleDbInsertCommand1.Connection = this.oleDbConnection1;
this.oleDbInsertCommand1.Parameters.Add(new System.Data.OleDb.OleDbParameter("CustomerID", System.Data.OleDb.OleDbType.WChar, 5, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "CustomerID", System.Data.DataRowVersion.Current, null));
this.oleDbInsertCommand1.Parameters.Add(new System.Data.OleDb.OleDbParameter("CompanyName", System.Data.OleDb.OleDbType.VarWChar, 40, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "CompanyName", System.Data.DataRowVersion.Current, null));
this.oleDbInsertCommand1.Parameters.Add(new System.Data.OleDb.OleDbParameter("ContactName", System.Data.OleDb.OleDbType.VarWChar, 30, System.Data.ParameterDirection.Input, true, ((System.Byte)(0)), ((System.Byte)(0)), "ContactName", System.Data.DataRowVersion.Current, null));
this.oleDbInsertCommand1.Parameters.Add(new System.Data.OleDb.OleDbParameter("Select_CustomerID", System.Data.OleDb.OleDbType.WChar, 5, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "CustomerID", System.Data.DataRowVersion.Current, null));
this.oleDbUpdateCommand1.CommandText = @"UPDATE Customers SET CustomerID = ?, CompanyName = ?, ContactName = ? WHERE (CustomerID = ?) AND (CompanyName = ?) AND (ContactName = ? OR ? IS NULL AND ContactName IS NULL); SELECT CustomerID, CompanyName, ContactName FROM Customers WHERE (CustomerID = ?)";
this.oleDbUpdateCommand1.Connection = this.oleDbConnection1;
this.oleDbUpdateCommand1.Parameters.Add(new System.Data.OleDb.OleDbParameter("CustomerID", System.Data.OleDb.OleDbType.WChar, 5, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "CustomerID", System.Data.DataRowVersion.Current, null));
this.oleDbUpdateCommand1.Parameters.Add(new System.Data.OleDb.OleDbParameter("CompanyName", System.Data.OleDb.OleDbType.VarWChar, 40, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "CompanyName", System.Data.DataRowVersion.Current, null));
this.oleDbUpdateCommand1.Parameters.Add(new System.Data.OleDb.OleDbParameter("ContactName", System.Data.OleDb.OleDbType.VarWChar, 30, System.Data.ParameterDirection.Input, true, ((System.Byte)(0)), ((System.Byte)(0)), "ContactName", System.Data.DataRowVersion.Current, null));
this.oleDbUpdateCommand1.Parameters.Add(new System.Data.OleDb.OleDbParameter("Original_CustomerID", System.Data.OleDb.OleDbType.WChar, 5, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "CustomerID", System.Data.DataRowVersion.Original, null));
this.oleDbUpdateCommand1.Parameters.Add(new System.Data.OleDb.OleDbParameter("Original_CompanyName", System.Data.OleDb.OleDbType.VarWChar, 40, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "CompanyName", System.Data.DataRowVersion.Original, null));
this.oleDbUpdateCommand1.Parameters.Add(new System.Data.OleDb.OleDbParameter("Original_ContactName", System.Data.OleDb.OleDbType.VarWChar, 30, System.Data.ParameterDirection.Input, true, ((System.Byte)(0)), ((System.Byte)(0)), "ContactName", System.Data.DataRowVersion.Original, null));
this.oleDbUpdateCommand1.Parameters.Add(new System.Data.OleDb.OleDbParameter("Original_ContactName1", System.Data.OleDb.OleDbType.VarWChar, 30, System.Data.ParameterDirection.Input, true, ((System.Byte)(0)), ((System.Byte)(0)), "ContactName", System.Data.DataRowVersion.Original, null));
this.oleDbUpdateCommand1.Parameters.Add(new System.Data.OleDb.OleDbParameter("Select_CustomerID", System.Data.OleDb.OleDbType.WChar, 5, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "CustomerID", System.Data.DataRowVersion.Current, null));
this.oleDbDeleteCommand1.CommandText = "DELETE FROM Customers WHERE (CustomerID = ?) AND (CompanyName = ?) AND"+ "(ContactName = ? OR ? IS NULL AND ContactName IS NULL)";
this.oleDbDeleteCommand1.Connection = this.oleDbConnection1;
this.oleDbDeleteCommand1.Parameters.Add(new System.Data.OleDb.OleDbParameter("CustomerID", System.Data.OleDb.OleDbType.WChar, 5, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "CustomerID", System.Data.DataRowVersion.Original, null));
this.oleDbDeleteCommand1.Parameters.Add(new System.Data.OleDb.OleDbParameter("CompanyName", System.Data.OleDb.OleDbType.VarWChar, 40, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "CompanyName", System.Data.DataRowVersion.Original, null));
this.oleDbDeleteCommand1.Parameters.Add(new System.Data.OleDb.OleDbParameter("ContactName", System.Data.OleDb.OleDbType.VarWChar, 30, System.Data.ParameterDirection.Input, true, ((System.Byte)(0)), ((System.Byte)(0)), "ContactName", System.Data.DataRowVersion.Original, null));
this.oleDbDeleteCommand1.Parameters.Add(new System.Data.OleDb.OleDbParameter("ContactName1", System.Data.OleDb.OleDbType.VarWChar, 30, System.Data.ParameterDirection.Input, true, ((System.Byte)(0)), ((System.Byte)(0)), "ContactName", System.Data.DataRowVersion.Original, null));
this.oleDbConnection1.ConnectionString = "Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Initial Catalog=Northw" +
"ind;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation" +
" ID=VMUKHI;Use Encryption for Data=False;Tag with column collation when possible" +
"=False";
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
}
The code for the function
InitializeComponent has been inserted without us having to make any changes at
all. By now, you would have realized, as to why we are so fascinated with
Visual Studio.Net. It generates all the code required for a certain task,
without a single error. Let us spend a
little time, trying to construe the above code. As there are six instance
variables, each of them will have a new statement, which will initialize them.
The first variable is of type
OleDbDataAdapter class, which is used to fetch data from a database. Then, we
need SQL commands, which will help us in working with the records from a table
in the database. Thus, the 4 property statements of type OleDbCommand,
oleDbSelectCommand1, oleDbInsertCommand1, oleDbUpdateCommand1 and oleDbDeleteCommand1,
represent SQL statements, which are to be used while working with records.
The columns in our table,
require to be mapped to the columns in the data source. In order to accomplish
this, the TableMappings property of type DataTableMappingCollection, is
employed. This class contains a function called AddRange, which accepts an
array of DataTableMapping objects. A DataTableMapping class has a constructor,
which accepts three parameters. The first parameter is a string, which
represents a Table. The second parameter is Customers, which is the name of the
table in the dataset, to map to. The third parameter is an array of Column
names.
The column names furnished in
the last parameter are represented with the help of a DataColumnMapping class. The
constructor to this class is passed two parameters. The first is the name of
the column in the data source, while the second is the name of the column in
the dataset to map to. We have maintained both of these identical, although they could have easily varied.
The SQL statement that we have
written, generates this AddRange function. It associates or maps the three
chosen columns, with the columns in the Customers table. The
oleDbSelectCommand1 object requires an SQL select statement, which will determine
the data to be retrieved. The CommandText property is therefore, initialized to
the Select statement entered earlier in the Query Builder. The Connection
property is initialized to the OleDbConnection control, for each of the select,
update, delete or insert objects.
For inserting data, the SQL
statement has a? symbol, or a placeholder to hold text, which is to be entered
at run time. The syntax starts with the reserved word 'Insert into', followed
by the name of the table, followed by the field names, followed by the reserved
word Values, and finally, followed by a set of brackets, containing the actual
data. This data can also be the resultant data of a Select statement. Either of
the three, i.e. ? or placeholder or parameter, is filled with text supplied by
the user at runtime. This is done for reasons of efficiency. The Insert or
Select statements are first parsed, and then, verified for various types of
errors. This is a protracted and time-consuming exercise. Therefore, the
product of this action is stored for reuse, thereby speeding things up. Thus,
by the use parameters, execution can be speeded up, since the database merely
needs add the data, without checking the syntax. Three parameters are to be
added to the Insert object, since there are three column names, viz.
CustomerID, CompanyName and ContactName. The rules for adding a parameter
remain the same, while implementing all the three parameters.
The Parameters collection takes an
OleDbParameter object, whose constructor accepts 10 parameters, which are as
follows:
• The first parameter is the name of the parameter, and in our case, it is termed as CustomerID.
• The second parameter is the data type of the parameter, which is of type OleDbType. It may be any one of the numerous data types that are available. One such data type is wchar, which is a series of Unicode characters, ending in a null. It maps to a string data type in C#.
• The third parameter is the size or width of the column, which is 5 characters long.
• The fourth parameter, of type ParameterDirection, can contain only four values, which specify whether the parameter is used for input or output; or both; or for the return value of some code being executed on the server.
• The fifth parameter is a Boolean, which accepts a value of either True or False. In a database, you can specify whether a field can contain a Null value or not. Null cannot be equated to zero, since it specifically implies that the field is bereft of any value whatsoever. A value of False necessitates the presence of a value in this field.
• The sixth parameter stands for 'precision', which indicates the exact number of decimal places that a number can possibly have.
• The seventh parameter is the 'scale' parameter, which is also responsible for the number of decimal places that the value can hold.
• The eighth parameter is the name of the source column in the table.
• The ninth parameter is of type DataRowVersion, and specifies the version of the DataRow object being retrieved. The type Current represents the current or present value. It can have three other values, viz. Default, Original and Proposed.
• The tenth parameter is Null. It stands for the value of any of the parameters. We may use any data type, to assign a value to the parameter.
It would be so cumbersome if we
had to write the above code by hand, for each of the parameters in the Insert
statement. This is surely a dreary job! Since someone has to do this job, why
not let the computer do it?
The 'update' command
necessitates a similar treatment. So, we use the Update statement, which starts
with the reserved word Update, followed by the name of the table i.e.
Customers, and the 'set' keyword, which specifies the fields that need to be
updated. There are three placeholders or ? signs, since three fields have been
chosen. The term 'where' is like a filter, since in its absence, the update
statement will update all records in the database. The eight parameters that
need to be annexed for the Update statement, are responsible for the extremely
lengthy code. The Delete statement is considerably more tractable than the code
specified above. It starts with the Delete keyword, followed by the name of the
table, and a 'where' condition, which filters the number of records. The four
placeholders require four parameter statements.
Next comes the Connection
object, oleDbConenction1. It is this particular object, which understands how
to aptly communicate with a database. The data that specifies how to
communicate with a database, is passed in the ConenctionString property. The
Connection string starts with the word 'provider', with the name of the
database that we wish to talk to, followed by a semi-colon, which acts as a separator.
Then, we have the User ID of 'sa', since 'sa' was entered in the dialog box.
Next, we have the Catalog as the name of the database, NorthWind. We shall
explore the others, a little later.
The visible controls get added
to the Forms collection, whereas the invisible controls do not. The only
difference between a visible control and an invisible control is that, the
former shows up in the form, while the latter shows up at the bottom. These
concepts are divulged only after the code that is generated, has been
deciphered thoroughly. Otherwise, we shall just be groping in the dark. Thus,
we endeavor to understand the code from the absolutely basic fundamentals.
Next, we generate the Data Set
object, using the menu option Data- Generate Dataset, and we name the Datatset
as 'ddd'. The screen 6.5 represents the dataset dialog box.
|
Screen 6.5 |
Once this is done, switch to the
Code Painter to view the new sets of commands. An instance variable, called
ddd1, gets created with the data type of z10.ddd. This is because, we named our
DataSet as 'ddd', and the dataset object in Visual Studio.Net was named 'ddd1'.
The next thing that we need to
do is, to discover where the class ddd has been created. It does not show up in
the current file. We plan to unveil this class mystery, in just a short while.
But for now, we will take a look
at the code of the dataset, which was created in the function
InitializeComponent.
private z10.ddd ddd1;
private void InitializeComponent ()
{
this.ddd1 = new z10.ddd();
((System.ComponentModel.ISupportInitialize)(this.ddd1)).BeginInit();
this.ddd1.DataSetName = "ddd";
this.ddd1.Locale = new System.Globalization.CultureInfo("en-US");
this.ddd1.Namespace = "http://www.tempuri.org/ddd.xsd";
}
As always, a new instance is
created, and thereafter, a function called BeginInit is called. This function
is present in the interface IsupportInitialize, which explains the use of the
cast operator. This function merely enlightens the whole world with the news,
that the object is about to initialize itself. Unless the program reaches the
EndInit function, initialization will not be completed.
This is akin to fixing a 'Do Not
Disturb' sign outside your door. The cast that we have above, is really
unnecessary, since the DataSet class implements the interface. Whenever
computer programs write code, they are extremely specific. The DataSetName is
set to 'ddd', since we opted for this name in the dialog box, during the
creation of the dataset. The Locale property is used to compare strings present
in the table. The Namespace property is set to the name of an xsd file. File
ddd.xsd is shown below.
ddd.xsd
<xsd:schema id="ddd" targetNamespace="http://www.tempuri.org/ddd.xsd" xmlns="http://www.tempuri.org/ddd.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" attributeFormDefault="qualified" elementFormDefault="qualified">
<xsd:element name="ddd" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="Customers">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="CustomerID" type="xsd:string" />
<xsd:element name="CompanyName" type="xsd:string" />
<xsd:element name="ContactName" type="xsd:string" minOccurs="0" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
<xsd:unique name="Constraint1" msdata:PrimaryKey="true">
<xsd:selector xpath=".//Customers" />
<xsd:field xpath="CustomerID" />
</xsd:unique>
</xsd:element>
</xsd:schema>
This file displays a large
number of tags, which we shall explicate while discussing XML. The line of code
in which the table name Customers is stored, is the most significant one.
Within the tag sequence, the three field names are present along with their
data types. There is also an attribute called PrimaryKey, which specifies that
the CustomerID field is unique. The above program named Form1.cs, cannot be
compiled at this stage, since it does not contain any code for the ddd class.
The only way out is, to create this class from the xsd file. So, we use the xsd
program for this purpose. We run it as follows:
C:\mukhi\z10>xsd /d /n:z10 ddd.xsd
Output
Microsoft (R) Xml Schemas/DataTypes support utility
[Microsoft (R) .NET Framework, Version 1.0.2914.16]
Copyright (C) Microsoft Corp. 1998-2001. All rights reserved.
Writing file 'C:\mukhi\z10\ddd.cs'.
The /d option creates a class
named ddd, derived from DataSet. The /n option places all the code in the
namespace z10. Finally, a file called ddd.cs gets created, from which, we have
extracted the initial few lines. Remember that, none of this 10K sized code,
has been written by us.
namespace z10
{
public class ddd : System.Data.DataSet
{
Now that the class ddd is
available, compile the Form program, using the command
>csc Form1.cs ddd.cs
The compiler can be passed as
many C# program files as you desire. We are not out of the woods yet, since we
have to introduce a DataGrid, which shall display all the data.
The moment we incorporate a
DataGrid object, an instance variable named dataGrid1 gets created.
private System.Windows.Forms.DataGrid dataGrid1;
Double click on the datagrid and
you will see the following code in the function InitializeComponent
this.dataGrid1 = new System.Windows.Forms.DataGrid();
((System.ComponentModel.ISupportInitialize)(this.dataGrid1)).BeginInit();
this.dataGrid1.DataMember = "";
this.dataGrid1.Location = new System.Drawing.Point(56, 40);
this.dataGrid1.Name = "dataGrid1";
this.dataGrid1.TabIndex = 0;
this.dataGrid1.Navigate += new System.Windows.Forms.NavigateEventHandler(this.dataGrid1_Navigate);
this.Controls.AddRange(new System.Windows.Forms.Control[] {this.dataGrid1});
((System.ComponentModel.ISupportInitialize)(this.dataGrid1)).EndInit();
private void dataGrid1_Navigate(object sender, System.Windows.Forms.NavigateEventArgs ne)
{
}
The Navigate event gets fired,
whenever we move into a new table in the data grid, since the DataMember
property is blank by default. The whole world appears to be pre-occupied with
calling the BeginInit and EndInit
functions. They all behave in a similar manner.
We finally set DataSource to
ddd1 and DataMember to Customers. These are the two most important properties
in the Properties window.
this.dataGrid1.DataSource = this.ddd1;
this.dataGrid1.DataMember = "Customers";
The Fill function is placed in
the Form1_Load function before running the program, as the dataset needs to be
filled up with data.
private void Form1_Load(object sender, System.EventArgs e)
{
oleDbDataAdapter1.Fill(ddd1);
}
The next application will
selectively retrieve data from the customers table. To be precise, we will provide
a customer number, and instantly, specific details about that customer will be
displayed. But prior to this, we intend to display a single record from the
customers table in the textboxes.
Save all the files, and close
the current Solution. Then, click on File - New - Project, and choose Visual C#
Project in the first pane and Windows Application in the second pane. Assign
the name z14 to the project, and locate it in C:\mukhi. Then, click on the OK
button. Do ensure that the Properties and the ToolBox windows are visible.
Click on the Data tab in the
toolbox, and then usher-in the OleDbDataAdapter control. This activates the
wizard. On the first screen, simply click on the Next button. To obtain the
name of Data Connection, click on the drop down listbox, and select the one
named Northwind database. Then, click on the Next button, leaving the SQL
statements selected; and then, write the following SQL statement:
SELECT CustomerID, CompanyName, ContactName FROM Customers
Finally, click on the Finish
button. The two invisible objects get created in the lower pane. We are also
aware of the fact that, great many lines of code have been generated.
Click on menu Data - Generate
Dataset. Change the name of the dataset to ddd, and then, click on OK. Thereafter,
drag-and-drop three textboxes onto the form, from the ToolBox in the Windows
Form tag.
Select the first textbox, and in
the Properties Window, click on the plus sign in front of DataBindings. This
will bring up the DataBindings options. Click on the drop down listbox for the
property Text. This will reveal the dataset ddd1, with a plus sign in front of
it. A dataset contains a large number of tables. Hence, we see a plus sign
along with it, in the Properties Window. Click on the plus sign with ddd, to
display a list of tables present in it. Since there is only one table present,
we see only the Customers table listed, but with a plus sign. Every table
contains columns. Clicking on the plus sign, will display the three column
names contained in customers. Select CustomerID, as shown in screen 6.6.
|
Screen 6.6 |
By selecting the column CustomerID
for the first textbox, we confine the display of all values related to
CustomerID, to this particular textbox alone. Follow the same procedure to
select CompanyName for the second textbox, and finally, to select ContactName
for the third textbox. After effecting this, double click on the form, to enter
the Fill function in the function Form1_Load.
oleDbDataAdapter1.Fill(ddd1);
Then, press F5 to run the
application.
|
Screen 6.7 |
In screen 6.7, the three fields
of the first record are displayed in the three textboxes.
Let us now introduce a button
from the Windows Form tab so that the record pointer moves to the next record
when clicked on the button. Change the text property in the Properties window
to Next, and then, double click on the button. You will be transported to the
function button1_Click. Now, enter the following line of code:
BindingContext[ddd1, "Customers"].Position += 1;
The Form class has a property
called BindingContext, of data type BindingContext. This property, in turn, has
an Indexer that accepts two parameters. As was explained in the previous
chapter, an Indexer looks like an array, walks like an array and talks like an
array; but it is not an array!
The two parameters that this
Indexer accepts are:
• The name of the dataset, i.e. ddd1.
• The name of the table, which is within that DataSet, i.e. Customers.
This indexer returns a
BindingManagerBase object, which has a property called Position. This property
contains the current record pointer.
The record pointer or active record,
is an abstract entity, which marks a single row of data in the dataset that
becomes visible to everyone. The value contained in the Position property
activates the specific row in the dataset. The initial value starts at zero.
Each time we click on the
button, the value is incremented by 1, using the += syntax.
|
Screen 6.8 |
The above code can be re-written
effortlessly, as follows:
BindingManagerBase a;
a= BindingContext[ddd1, "Customers"];
a.Position = a.Position + 1;
This code is a simplified
version of the earlier one. However, it is infested with a small bug. If the
value of the Position parameter exceeds the number of records, no error is
generated. In such a situation, the last record will be displayed. So, how do
we redress this problem?
Introduce another button, and
change the Text property to Prev. Then, double click on this button, and in the
Code Generator for the function button2_Click, add the following line of code.
BindingContext[ddd1, "Customers"].Position -= 1;
Now, on running the application,
two buttons will be visible, as shown in screen 6.9.
|
Screen 6.9 |
Each time we click on the Next
button, the record pointer moves to the next record; whereas, every time we
click on the Prev button, it moves to the previous record.
Thus, each and every record can
be accessed sequentially, in the forwards or backwards direction.
Now, we add one more button,
which moves the record pointer directly to the first record. Change its text to
'First', and then, double click on it, to add the following line of code:
BindingContext[ddd1, "Customers"].Position = 0;
Once this is done, build the
project, and press F5 to run the program. Click on the Next button a couple of times,
and then, click on the button labeled First. On doing so, the record pointer
jumps directly to the first record, and it gets displayed. This occurs on
account of the fact that, when the Position property is set to zero, the first
row in the dataset becomes the active row. Finally, select a button in the
Toolbox, and change its Text property to Last. Then, add the following lines of
code to it:
int i;
i = BindingContext[ddd1, "Customers"].Count;
BindingContext[ddd1, "Customers"].Position = i-1;
The BindingManagerBase class has
a member called Count, which provides the total count of rows or record sets
available in the dataset. The variable i is set to the value of this property.
Then, the Position parameter is initialized to one less than the value of i,
since the index of the Position parameter begins with a value of zero. Thus, if
we have 10 rows, the value of the count property will be 10. The index value of
the first record will be 0, and that of the last record will be 9.
There is yet another feature
remaining, which is a textbox that reveals the current position, or the current
record number, in the dataset. Each time the Position property changes, the new
record number must be displayed. In fact, the position changes whenever any
button is clicked. The code required to
display the new record numbers, remains unchanged. So, instead of repeating the
same code four times, once for each of the four different buttons, we shall
enclose this code within a function. This function will then be called by each
of the four buttons.
So, select the textbox from the
Toolbox window. The textbox will be named textBox4.
Then, enter the function abc, as
shown below, after the last function. The four buttons shall now call this
function.
|
Screen 6.10 |
void abc()
{
int cnt,pos;
cnt = BindingContext[ddd1, "Customers"].Count;
pos = BindingContext[ddd1, "Customers"].Position;
pos = pos+1;
textBox4.Text = "Record No " + pos + " of Record " + cnt;
}
Also, call the abc function
after the Fill function in Form_Load.
private void Form1_Load(object sender, System.EventArgs e)
{
oleDbDataAdapter1.Fill(ddd1);
abc();
}
In the function abc, we
initialize the variables 'cnt' and 'pos' to the total number of rows and
current record position, respectively. The 'pos' variable must be increased by
one, since the index is zero-based, i.e. it starts at zero.
The Text property of the textbox
named textBox4, is initialized to the new values. The strings are placed within
double quotes, whereas, the integers are automatically converted to strings, so
that no extra effort is required on our part. The plus sign is employed to join
two strings, and is known as the 'string concatenation operator'.
|
Screen 6.11 |
There are numerous records in the
dataset. So, we shall now essay towards displaying only those records that meet
certain criteria. To do so, select the OleDbDataAdapter object in the form, and
then, for the property SelectCommand, click on the 'plus' sign, to view the
Dynamic properties.
When the button for the property
CommandText having 3 dots is clicked, it displays the screen as reflected
below.
|
Screen 6.12 |
Here, we notice the same screen
that was displayed in the wizard. The SQL statement is modified to the
following:
SELECT CustomerID, CompanyName, ContactName FROM Customers WHERE City = ?
Addition of the WHERE clause results
in the display of customers belonging to a specific city only. The name of the
city will be provided by the user, during program execution. The ? sign is a
parameter or placeholder, which accommodates the name of the city.
Before building and running the
application, introduce a textbox and a button.
Change the Text property of the
button to 'Show', and then, double click on the button to enter the following
lines of code:
oleDbDataAdapter1.SelectCommand.Parameters["city"].Value = textBox5.Text;
ddd1.Clear();
oleDbDataAdapter1.Fill(ddd1);
abc();
There is just one more
assignment to complete, before we wind up. Empty the function Form1_Load. Now,
run the program, and in the newly inserted textbox, enter 'London'. Then, click
on the Show button. The output reveals that the city of London has six
customers. Screen 6.13 provides the proof.
|
Screen 6.13 |
Now, if you change 'London' to
'Paris', you will see only two customers from this city. Thus, from amongst the
91 customers, we can selectively view only those who belong to a certain city.
Now let us try to appreciate the
'behind the scene' activities.
The SelectCommand property in
the oleDbDataAdapter, has a property named Parameters. It is actually an
indexer that requires a string parameter to reference the parameter 'name'. The
Value property of the indexer is initialized to the city name provided in the
Text property of the textBox5. It is indeed a good idea to clear the dataset of
all records, using the clear function. Then, the same Fill function is called,
followed by the abc function. Thus, the presence of parameters makes our
program extremely generic. The above code can be re-written in a simple format,
as shown below.
System.Data.OleDb.OleDbCommand a;
a= oleDbDataAdapter1.SelectCommand;
System.Data.OleDb.OleDbParameterCollection p;
p = a.Parameters;
System.Data.OleDb.OleDbParameter p1;
p1 = p["city"];
p1.Value = textBox5.Text;
ddd1.Clear();
oleDbDataAdapter1.Fill(ddd1);
abc();
The property SelectCommand is of
data type OleDbCommand. It belongs to the System.Data.OleDb namespace. If the
'using' clause is not provided for this namespace, we have to write the entire
name of the namespace. This can be avoided by employing the 'using' keyword.
The OleDbCommand has a property named Parameters, which is of data type
OleDbParameterCollection. This property, in turn, has an indexer that takes the
parameter name as a string parameter, and returns an OleDbParameter. The Value
member of this object p1, is initialized to the Text property of the textbox.
Finally, you can change the Text
property of all the textboxes to empty strings, because the initial display
lacks in aesthetic appeal. There are no bounds to the number of enhancements
that we can effect to the above program.
The presence of four copies of
the function abc, one for each of the above four click functions, also does not
appeal to us. So, we eliminate the calls to the function abc from the four
click functions, and add the following code to the click function of button5:
BindingManagerBase z;
z= BindingContext[ddd1, "Customers"];
z.PositionChanged += new EventHandler(abc);
Also, the abc function requires
to be amended, to read as follows:
void abc( object s, System.EventArgs e)
The BindingManagerBase object z
is first initialized, and then, the PositionChanged delegate is assigned to the
function abc. Thus, each time the Position property changes, the abc function
gets called. There is no need for us to call the abc function explicitly, since
the system calls it, whenever there is a change in the Position property.
Every Windows Application
comprises of a menu. So, how do we place menus in our Windows Application? As
is generally done, we shall examine this process, one step at a time. We shall
also display the code generated by Visual Studio.Net, so that you are able to
grasp the concept of Menu Handling in C#.
As always, save all the files,
and start with a new project, using the File - New - Project menu option. In
the dialog box, select the Project type as Visual C# Project, and in the
Template pane, select the template as Windows Application. Name the project as
s2, and locate it at c:\mukhi. Then, click on OK. You also need to ensure that
the Properties and the Toolbox windows are visible. From the ToolBox, under the
Windows From tab, select the MainMenu control and drag-and-drop it into the
Form. We merely witness a message Type Here. This is shown in screen 6.14. When we insert the word File, the menu
changes to the one shown in screen 6.15.
|
|
Screen 6.14 |
Screen 6.15 |
On building and running the
program, the screen displays the word File, as shown in screen 6.16.
|
Screen 6.16 |
On clicking this menu item,
nothing gets displayed, and in effect, nothing transpires. We now go back to the
Code Generator, and have a look at the code that is generated.
private System.Windows.Forms.MainMenu mainMenu1;
private System.Windows.Forms.MenuItem menuItem1;
There are two instance
variables, mainMenu1 of data type MainMenu, and menuItem1 of data type
MenuItem.
If you click on the word File in
the Design Mode, the properties window will display the properties for this
menu item object. This is shown in screen 6.17.
|
Screen 6.17 |
private void InitializeComponent()
{
this.mainMenu1 = new System.Windows.Forms.MainMenu();
this.menuItem1 = new System.Windows.Forms.MenuItem();
this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
this.menuItem1});
this.menuItem1.Index = 0;
this.menuItem1.Text = "File";
this.Menu = this.mainMenu1;
}
The function InitializeComponent
contains the entire menu-generated code. Here, we first create an instance for
the two menu objects. The mainMenu1 object has the MenuItems property, which in
turn, calls the AddRange function. This function adds an array of MenuItem
objects. As we are in possession of only one such object, the array displays
this solitary object named menuItem1. This procedure is similar to the one used
earlier for adding controls to the Form.
The Index property shows 0,
since it is the first menu item. The text property displays File. The Form
class has a property called Menu, which is initialized to the object MainMenu1.
This main menu object knows all about the other menu items that are to be
displayed.
To add another menu item, click
on the word File. This will open up an additional box on the right. Enter the text
'Edit', as shown in screen 6.18.
|
|
Screen 6.18 |
Screen 6.19 |
Build and run the application. A
screen is displayed, containing two menu items named File and Edit, as is
clearly seen in screen 6.19.
With the introduction of one more
menu item, the default name assigned to it becomes menuItem2. An instance
variable of the same name is created in the code file.
private System.Windows.Forms.MenuItem menuItem2;
private void InitializeComponent()
{
this.menuItem2 = new System.Windows.Forms.MenuItem();
this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
this.menuItem1, this.menuItem2});
this.menuItem2.Index = 1;
this.menuItem2.Text = "Edit";
}
In the InitializeComponent
function, additional code gets added for the second menu item. The object
menuItem2 is first initialized and then, it is added to the array, which is to
be provided to the AddRange function. We would have had to add them
individually, if the array did not exist. Thus, adding 10 menu items would have
entailed calling the function 10 times.
The index property for this menu
item is shown as 1, since it is the second menu item. The text property shows
the newly entered text as Edit.
Now, we shall add an item to the
File menu. Click on the word File, and enter the word New, as displayed in
screen 6.20.
|
|
Screen 6.20 |
Screen 6.21 |
Then, build and run the
application. Click on the File menu, and it will display a popup menu, containing
the word New. This is presented in screen 6.21.
Let us now look at the code,
which generates the popup menu.
private System.Windows.Forms.MenuItem menuItem3;
private void InitializeComponent()
{
this.menuItem3 = new System.Windows.Forms.MenuItem();
this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {this.menuItem1, this.menuItem2});
this.menuItem1.Index = 0;
this.menuItem1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
this.menuItem3});
this.menuItem1.Text = "File";
this.menuItem3.Index = 0;
this.menuItem3.Text = "New";
}
In order to store the word New,
one more MenuItem named menuItem3, gets added. A new instance is also created
for this purpose. This menu item is not added to the array provided in the
AddRange function of mainMenu1.MenuItems. However, the menuItem1 object has a
similar property named MenuItems, which has an AddRange function. This function
also accepts an array of MenuItems.
Since only a single MenuItem named
New is to be added, this array holds only one member. The Index property of
menuItem3 is assigned a value of zero, since it is the first menu item under
File.
Similarly, if we add one more
item named Open just below New, another variable named menuItem4 will be
created, and the Addrange function will now read as follows:
this.menuItem1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
this.menuItem3,this.menuItem4});
Thus, there will be two menu
item objects in the array, which will be passed to the AddRange function of the
menuItem1 object. Now, each time that the menu item Open is clicked, we desire
that some specific code should be called. Click on the left side of the word
Open, with the left mouse button. You will arrive at the screen shown in 6.22.
Here, we see a tick-mark on the menu.
|
|
Screen 6.22 |
Screen 6.23 |
Build and run the program. Now,
click on the File menu. You will see the tick-mark displayed on the menu item
Open. This is shown in screen 6.23.
The tick-mark indicates that the
menu has already been checked. This exists primarily for the purpose of
reminding the user that the menu had been selected earlier.
this.menuItem4.Checked = true;
this.menuItem4.Index = 1;
this.menuItem4.Text = "Open";
The MenuItem Open is called
menuItem4, and has a property called Checked. This property when set to True,
merely checks the menu.
Now, we desire that each time we
click on the Open menu, the menu labeled New should get disabled. To make this
happen, double click on the word Open and enter the following code in the Code
Painter.
private void menuItem4_Click(object sender, System.EventArgs e)
{
menuItem3.Enabled = false;
}
The above function gets called
only because the Click event of the menu item object menuItem4, has been set to
the function MenuItem4_Click. As before, the name of the object is followed by
the underscore sign, and then, by the magic word Click. The above function
disables the menu item labeled New or menuItem3.
Build and run the program. Then,
Click on File - Open. On doing so, the above function gets called; thus
disabling the New option. Therefore, you are in complete control of the code
that is to be executed, when the menu option is clicked.
|
Screen 6.24 |
Save all the files and create a
new project. In the dialog box, select the project type as Visual C# Project, and
the Template as a Windows Application. Name the project as s3, and locate it at
c:\mukhi. Then, click on OK. As before, the Properties Window and the Toolbox
must be activated. Move down the scroll bar in the toolbox, and select the
control ContextMenu. Drag-and-drop it into the form.
Screen 6.25 shows the screen
that encompasses the introduction of the ContextMenu.
|
Screen 6.25 |
The generated code contains the
instance variable contextMenu1, of data type ContextMenu.
private System.Windows.Forms.ContextMenu contextMenu1;
private void InitializeComponent ()
{
this.contextMenu1 = new System.Windows.Forms.ContextMenu();
}
Also, in the function
InitializeComponent, the instance variable contextMenu1 is initialized to a
ContextMenu object. No additional code is required, since a ContextMenu is
always added to a control. Clicking with the right mouse button on the control,
displays this menu. Hence, it is termed as a ContextMenu.
Now, we shall add two menu items
to this Context Menu. Make sure that the Context Menu object is highlighted at
the bottom of the screen. Then, click on the words ContextMenu, as exhibited in
screen 6.26.
|
Screen 6.26 |
Replace the words 'Type Here'
with the word 'New'. Then, click on the words 'Type Here' given below, and replace
it with 'Open'.
Thus, we now obtain two menu
items in the context menu. But, the moment we click on the Form, the context
menu disappears, since it is required to be displayed only when the context
menu control is selected. This is unlike a main menu object. The extra code
that is required for adding the items to a context menu, is as follows:
private System.Windows.Forms.MenuItem menuItem1;
private System.Windows.Forms.MenuItem menuItem2;
private void InitializeComponent ()
{
this.menuItem1 = new System.Windows.Forms.MenuItem();
this.menuItem2 = new System.Windows.Forms.MenuItem();
this.contextMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
this.menuItem1,this.menuItem2});
this.menuItem1.Index = 0;
this.menuItem1.Text = "New";
this.menuItem2.Index = 1;
this.menuItem2.Text = "Open";
}
For the two menu items New and
Open, there exist two instance variables, menuItem1 and menuItem2. In the
function InitializeComponent, the two menu item objects are initialized; then,
the AddRange function is used, which is present in the MenuItems collection
property of the context menu object contextMenu1. The array passed to this
function contains the two menu items as its members. Thus, a context menu is no
different from any other menu.
Let us return back to the Design
Mode and incorporate a button and a textbox in the Form. Select the button, and
in the Properties Window, move to the property called ContextMenu. Click on the
drop down listbox to arrive at a list of context menus, which are currently
available for the Form. Since only one contextmenu1 is present, a solitary
option is revealed. Select this option. Then, choose the textbox, and in the
drop down listbox of the property ContextMenu, pick the context menu named
contextMenu1.
On having done so, nothing
earth-shattering occurs. If you cast a glance at the code, you will notice that
the property ContextMenu of the Textbox, and that of the Button, has been
initialized to contextmenu1. Press F5 to run the program, and then, right click
with the mouse on the Button. And there you go! The menu is now visible on the
screen, containing two items, viz. New and Open. This is shown in screen 6.27.
|
Screen 6.27 |
We now incorporate a few
amendments to the application, by adding a few more menu items to the context
menu. We also activate a function, when the control associated with the context
menu is clicked.
To do so, add the following line
of code to the InitializeComponent function:
this.contextMenu1.Popup += new System.EventHandler(abc);
The ContextMenu class has an
event called Popup, which is associated with the function abc. This event gets
triggered, whenever we click on the right mouse button. It is a good practice
to place similar code together. This is merely a suggestion. Accordingly, we
have placed all code related to the ContextMenu together.
We have placed the above code
immediately after the AddRange function. The exact location does not matter, as
long as it is placed inside the function, and is located after an instance has
been created.
We also need to create the
function abc, and call the Show function in it. The code specified below is
placed after the Main function.
private void abc(object sender, System.EventArgs e)
{
MessageBox.Show("hi");
}
Press F5 to run the program, and
then, click with the right mouse button. Before the popup springs up, we notice
a Message Box, as shown in screen 6.28.
|
|
Screen 6.28 |
Screen 6.29 |
Close the application. Select
the context menu and double click on the menu item Open. In the function
menuItem2_Click, call the MessageBox.Show function, as demonstrated below.
private void menuItem2_Click(object sender, System.EventArgs e)
{
MessageBox.Show("bye");
}
This function gets called,
because the Click event of the menuItem2 has been added to our code as follows:
this.menuItem2.Click += new System.EventHandler(this.menuItem2_Click);
Build and run the program. Then,
click on either the button or the textbox, with the right mouse button. We
first click on the Message Box displaying "hi", and then, on the Open
menu item in the popup. This will bring up the Message Box displaying
"bye". Thus, a menu item located in a popup behaves akin to an
ordinary menu.
When we click on the popup, we
desire to add one more menu item. So, all that we need to do is, add the
following line of code in the function abc:
contextMenu1.MenuItems.Add("mukhi");
The Add function in the
MenuItems will add a menu with the text mukhi. Remove the Show function, since
it is of no use at all.
Now, build and run the
application, and then right click on the button. The menu that pops up, contains
the menu item of "mukhi". Then, right click on the textbox. This will
display the menu item "mukhi" twice.
|
|
Screen 6.30 |
Screen 6.31 |
We want to make a slight
modification here. We intend that the menu item be added only when the button is
selected. We also want specific code to be called. To achieve this, make the
following changes in the function abc:
private void abc(object sender, System.EventArgs e)
{
contextMenu1.MenuItems.Add("mukhi");
if ( ActiveControl is Button)
contextMenu1.MenuItems.Add("sonal",new System.EventHandler(pqr));
}
The 'if' statement makes use of
the property ActiveControl, which belongs to the Form class. This is a dynamic
property of data type Control. It contains the currently active control or the
control that has 'focus'. If it happens to be a Button, the 'if' statement
evaluates to True. The term 'is' is a keyword in the C# programming language.
It has a literal meaning, just as in English. Thus, we use the Add function of
the MenuItems collection, to add a menu called 'sonal'. This is the first
parameter, and the Eventhandler is the second parameter, representing a
function named pqr.
private void pqr(object sender, System.EventArgs e)
{
MessageBox.Show("end");
}
The pqr function merely displays
a Message Box.
On running the application, we
see the button already selected as it is the first control on the form, or more
precisely has the tab index of 0. A selected button adds sonal to the list of
menuoptions, so right clicking on any of the control will display sonal along
with mukhi.
Click on the textbox to select
the textbox control. As the button looses focus, the if statement with the
ActiveControl property check on Button results in false. As a result, sonal is not added again to the
menu options. This can be verified by right clicking on any control. Clicking
on 'sonal' will display a MessageBox, containing the text "end".
|
|
Screen 6.32 |
Screen 6.33 |
Thus, we can change the state of
any property that we desire, since we have access to the menu objects.
Now, save all the files, and
start afresh with a new project. Name the C# Windows Application as s4, and
select the location as c:\mukhi. Then, click on OK. The Properties window and the
Toolbox should be visible.
Now, incorporate the MainMenu
control, and for the first menu item, enter "Vijay", and for the one
below it, enter "Mukhi". The screen 6.34 displays these two menu
items.
|
Screen 6.34 |
Now, usher-in another MainMenu
object on the form, and for the first menu item, assign the text
"Sonal", and for the one below it, enter "File". The screen
6.35 displays the outcome of these actions.
|
Screen 6.35 |
We may acquire as many MainMenu objects
as desired, but only one of them will be active at any given time. So, the form
will initially show the first mainMenu1 to be active. The second menu object
mainMenu2 will become visible, only when it is clicked on, in the bottom frame.
Now introduce two buttons into
the Form.
Change the Text property of the
first button to 'First', and then, double click on the button. In the function button1_Click, enter the
following code:
Menu = mainMenu1;
Similarly, change the Text property
of the second button to 'Second', and double click on the button, to enter the
following line of code:
Menu = mainMenu2;
Once this is achieved, build and
run the program. The first menu called "vijay", will be visible by
default. Clicking on the button labeled "Second", will immediately
result in a switch to "Sonal". This is shown in screen 6.36.
|
Screen 6.36 |
Clicking on the button labeled
"First", will again revert the menu back to the original, i.e. to
"Vijay".
Thus, we are empowered to change
the main menu at any given time. We are also permitted to have as many menu
objects as we like. Further, we can point the Menu property to any Menu object
that we desire. Thus, at startup, a user sees a solitary menu, and after
accomplishing certain tasks, the menu switches to a more complex layout.
Thus, you may have realized by now, that it is much more productive to work with a Menu Painter, than to manually write lines of tedious code.