C Language Help, C Language Tutorials, C Language Programming, C Language Tricks { The C# Language }
C Language Help, C Language Tutorials, C Language Programming, C Language Tricks
XML Documentation
XML is used extensively to document program code. Apart from its status of a
mere acronym for eXtensible Markup Language, XML has a bounden duty to achieve
miracles. However, in this chapter, we would be utilizing XML for the temporal
task of documenting our code.
a.cs
class zzz
{
public static void Main() {
}
}
Run the compiler as
>csc a.cs /doc:a.xml
a.xml
<?xml version=”1.0″?>
<doc>
<assembly>
<name>a</name>
</assembly>
<members>
</members>
</doc>
This is the smallest possible XML file that can be effectively generated by the
compiler. The compiler with the /doc option, followed by a colon and filename,
creates the XML file, which documents the C# program.
Anything placed within the <> brackets is called a tag. At the expense of
sounding tediously repetitive, we once again reiterate that, each and every XML
file must necessarily start with a tag beginning with <?xml>. This has to be
followed by an attribute called version. Presently, the only permissible value
that can be assigned to version is 1.0. There may never be a version 1.1 or 2.0.
Subsequent to this, a root tag called ‘doc’ is specified, which envelopes all
other tags within the file. We have a tag called ‘assembly’ which owns another
tag called ‘name’ that encloses our assembly name ‘a’. The name of our program
file is ‘a’. There are no members to be documented, therefore, no names are
noticeable within the member tags. This is the basic structure of our XML file.
It is the bare minimum, no frills structure, created by the compiler. Now, let
us refurbish and garnish it through our own contribution.
a.cs
class zzz
{
/// <vijay>
public static void Main()
{
}
}
We have added an XML tag in the .cs file. The documentation clearly states that
an XML tag in a .cs file cannot begin with two slashes, since they are reserved
for comments. It should instead have three slashes. The use of the two slashes
generates the following warning:
Output
a.cs(4,3): warning CS1570: XML comment on ‘zzz.Main()’ has badly formed XML –
‘End tag ‘member’ does not match the start tag ‘vijay’.’
From now on, we shall only display the pertinent portions of the XML file, while
ignoring the portion that is invariant, such as, the doc and the /doc tags. So
in the file created as:
a.xml
<?xml version=”1.0″?>
<doc>
<assembly>
<name>a</name>
</assembly>
<members>
<!– Badly formed XML comment ignored for member “M:zzz.Main” –>
</members>
</doc>
we will display only
xml fragment:
<members>
<!– Badly formed XML comment ignored for member “M:zzz.Main” –>
</members>
In the next program, a closing tag of ‘vijay’ is specified.
a.cs
class zzz
{
/// <vijay>
/// Function Main gets called first
/// </vijay>
public static void Main()
{
}
}
xml
<members>
<member name=”M:zzz.Main”>
<vijay>
Function Main gets called first
</vijay>
</member>
</members>
The earlier program generated an error, since there was no corresponding end tag
to the tag ‘vijay’.
Every tag in XML must necessarily have a start tag and an end tag. We are at
liberty to choose any name for the tag. Therefore, we have chosen the name
‘vijay’.
The compiler creates a tag called ‘member’ under the tag members, and adds an
attribute called ‘name’. This attribute is initialized to the name of the C#
entity, which is the position where the tag is placed. The value assigned to
name is, as follows:
• The letter M,
• Followed by a colon separator,
• Followed by the name of the class zzz,
• Followed by a dot separator
• Finally, followed by the name of the function i.e. Main.
Thus, it displays M:zzz.Main.
The tag ‘vijay’ and the text enclosed between the start and end tags of ‘vijay’
subsequently, get enclosed within the members tag.
a.cs
/// <mukhi>
/// Our first class
/// </mukhi>
class zzz
{
/// <vijay>
/// Function Main gets called first
/// </vijay>
public static void Main()
{
}
}
xml
<members>
<member name=”T:zzz”>
<mukhi>
Our first class
</mukhi>
</member>
<member name=”M:zzz.Main”>
<vijay>
Function Main gets called first
</vijay>
</member>
</members>
Now, we have added a tag called ‘mukhi’ to the class. It is however, not added
to the function. So presently, there are two member tags, and each has been
assigned a name within the members tag. The value assigned to the member name,
which encloses ‘mukhi’ is, T:zzz. This is created in a manner akin to that of a
function, with the exception of the prefix M, which has now been replaced by T.
The rationale behind introducing XML tags is that, a program known as an XML
Parser can interpret the above XML file, and subsequently, create legible
output. But, the prime impediment that we have to confront and surmount is, the
reluctance of programmers to document code in the first place.
a.cs
class zzz
{
/// <mukhi>
/// Our Instance Variable
/// </mukhi>
public int i;
public static void Main()
{
/// <vijay>
/// Our first variable ii
/// </vijay>
int ii;
}
}
Output
a.cs(12,5): warning CS0168: The variable ‘ii’ is declared but never used
a.cs(9,1): warning CS1587: XML comment is not placed on a valid language element
a.cs(6,12): warning CS0649: Field ‘zzz.i’ is never assigned to, and will always
have its default value 0
xml
<members>
<member name=”F:zzz.i”>
<mukhi>
Our Instance Variable
</mukhi>
</member>
</members>
Oops! The tags that we give within a function have not been reflected in the XML
file. The instance variable i is flagged with an F, but the local variable ii is
not reflected in the XML file at all.
We have carried this out on purpose, with the intention of demonstrating that it
is not permissible to write XML tags within functions. Even though our preceding
action would not produce any error, the compiler shall, in any case, ignore our
work of art.
We will now dwell upon the names or string ids, which the compiler generates for
names of entities. They are as follows:
• N stands for a Namespace.
• T represents a type, class, interface, struct, enum and delegate.
• F denotes a field.
• P is indicative of either properties or indexers.
• M indicates all methods
• E represents an event.
• ! is indicative of an error.
a.cs
/// <vijay>
/// Our first variable ii
/// </vijay>
namespace aaa
{
class zzz
{
public static void Main()
{
}
}
}
Output
a.cs(1,1): warning CS1587: XML comment is not placed on a valid language element
We do not enjoy the license to place tags wherever we desire. Thus, a warning is
issued when a tag is placed on a namespace, although no error is generated. In
the earlier case too, a similar warning was issued, when we had placed the tags
in a function.
a.cs
class zzz
{
/// <vijay>
/// Our function <c>Main</c> is the starting point
/// </vijay>
public static void Main()
{
}
}
xml
<member name=”M:zzz.Main”>
<vijay>
Our function <c>Main</c> is the starting point
</vijay>
</member>
If we place the tag <c> and </c> around some text in a tag, the tag gets copied
verbatim, into the XML file. It is indicative of the fact that the text
represents some code. To mark multiple lines of code, the code tag may be used
instead. The example tag also works in a similar manner.
a.cs
/// <exception cref=”System.Exception”> Our exception class </exception>
class zzz
{
public static void Main()
{
}
}
xml
<member name=”T:zzz”>
<exception cref=”T:System.Exception”> Our exception class </exception>
</member>
The exception tag works as demonstrated above, with a single difference in that,
the attribute cref is inspected for accuracy. This attribute should represent a
valid C# entity, otherwise, the following warning is displayed:
/// <exception cref=”System.Exceptions”> Our exception class </exception>
Output
a.cs(2,22): warning CS1574: XML comment on ‘zzz’ has cref attribute
‘System.Exceptions’ that could not be found.
xml
<member name=”T:zzz”>
<exception cref=”!:System.Exceptions”> Our exception class </exception>
</member>
All the tags expounded by us so far, have a similar functionality. This surely
must have kindled your curiosity to discover the rationale behind employing so
many tags.
This is so, because each of these tags, documents different features of the
language. Let us assume that we yearn for a bulleted list in our final
documentation. We would have to use the following tags to accomplish it:
a.cs
/// <list type=”bullet”>
/// <item>
/// <description>One</description>
/// </item>
/// <item>
/// <description>Two</description>
/// </item>
/// </list>
class zzz
{
public static void Main() {
}
}
xml fragment
<member name=”T:zzz”>
<list type=”bullet”>
<item>
<description>One</description>
</item>
<item>
<description>Two</description>
</item>
</list>
</member>
The attribute type can also be assigned the values of bullet, number or table.
Bear in mind, that an external program will interpret the XML tags. Thus, the
designers of the language standardized tags for describing different things. If
the same tag had been utilized for documenting every feature, a single program
would have sufficed to document the entire C# code. We have employed tags such
as ‘vijay’ and ‘mukhi’ to document our classes. Chaos would reign supreme, if
everyone decided to use his or her own names. Appended below is a brief summary
of the various tags that can be employed in the XML file:
• The param tag is used to document the parameters to a function.
• The paramref tag is used to document a parameter name.
• The permission tag is used to document the access allowed to a member.
• The remarks tag is used to specify the overall information for a class.
• The return tag has been introduced to document the return values of functions.
• The seealso tag helps in implementing the ‘see also’ section at the bottom of
help sections.
• The summary tag documents all members of a type.
• The value tag documents all the properties.
C Language Help, C Language Tutorials, C Language Programming, C Language Tricks { The C# Language }
C Language Help, C Language Tutorials, C Language Programming, C Language Tricks
XML Data Document
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlDocument d = new XmlDocument();
XmlTextReader r = new XmlTextReader(“books.xml”);
r.WhitespaceHandling = WhitespaceHandling.None;
//r.MoveToContent();
d.Load(r);
d.Save(Console.Out);
}
}
books.xml
<?xml version=’1.0′?>
<!– This file represents a fragment of a book store inventory database –>
<bookstore>
<book genre=”autobiography” publicationdate=”1981″ ISBN=”1-861003-11-0″>
<title>The Autobiography of Benjamin Franklin</title>
<author>
<first-name>Benjamin</first-name>
<last-name>Franklin</last-name>
</author>
<price>8.99</price>
</book>
<book genre=”novel” publicationdate=”1967″ ISBN=”0-201-63361-2″>
<title>The Confidence Man</title>
<author>
<first-name>Herman</first-name>
<last-name>Melville</last-name>
</author>
<price>11.99</price>
</book>
<book genre=”philosophy” publicationdate=”1991″ ISBN=”1-861001-57-6″>
<title>The Gorgias</title>
<author>
<name>Plato</name>
</author>
<price>9.99</price>
</book>
</bookstore>
Output
<?xml version=”1.0″?>
<!– This file represents a fragment of a book store inventory database –>
<bookstore>
..
The XmlDocument class represents an XML document. The W3C has two standards, one
called the Document Object Model DOM Level 1, and the other called the Core DOM
Level 2. The XmlDocument class implements these standards. This class is derived
from an abstract class called XmlNode.
The XML Document is loaded into memory as a tree structure, thereby facilitating
the navigation within and the editing of the document. The representation of the
document in memory is known as a DOM.
The XML file books.xml represents a bookstore , which stores a large number of
books. This file shall not be displayed in future since it is pretty lengthy.
This file is utilised by most of the XML samples supplied by Microsoft.
The tag ‘books’ represents a book, and has attributes such as, type of book,
genre, the year of publication or publication date etc; and finally, a unique
number, ISBN, assigned to the book. Each book has a title as well as an author.
The attribute author contains a few tags that disclose details such as, the
first name and the last name of the author. The price of the book is the last
element. It is not imperative for these elements of a book to be present in a
specific order, i.e. price can come before the title, and so on.
The object r of type XmlTextReader class represents the XML file books.xml. As
always, the WhiteSpaceHandling is set to None. D is an object that looks like
XmlDocument. The Load function of this class can handle four overloads. One of
the overloads that the function can take is an XmlReader. As we are supplying an
XmlTextReader to this function, it eventually scales down to an XmlReader.
The Load function is responsible for loading the XML file into the XmlDocument
object. If the property PreserveWhitespace is set to True, then and only then,
are the Whitespace nodes created. In this case, since we have set Whitespaces to
None in the XML file, no white space nodes can ever be created, regardless of
property PreserveWhitespace being set to True. The Load function cannot change
the Whitespace Handling present in the reader. The Load method does not perform
any validations. In case validations are imperative, the XmlValidatingReader
class must be utilised in place of the plain Jane XmlTextReader class. The same
argument holds good when entities have to be resolved.
The Save function writes the XML document associated with the XmlDocument,
either to disk or to the console. We have displayed only a section of the
document, since the entire books.xml shall occupy space needlessly. We acquire
the entire file because the ReadState is set to Initial. This is so because the
state has not been changed yet. Let us now position the reader and see what
happens.
We remove the comment marks associated with MoveToContent, thereby, making it
available. As a consequence, both, the XmlDeclaration and the content are
skipped, and the reader is positioned at the first node ‘Bookstore’. The output
is as displayed below:
Output
<bookstore>
<book genre=”autobiography” publicationdate=”1981″ ISBN=”1-861003-11-0″>
…
</bookstore>
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlDocument d = new XmlDocument();
XmlTextReader r = new XmlTextReader(“books.xml”);
r.WhitespaceHandling = WhitespaceHandling.None;
r.MoveToContent();
r.Read();
r.Skip();
r.Skip();
d.Load(r);
d.Save(Console.Out);
}
}
Output
<book genre=”philosophy” publicationdate=”1991″ ISBN=”1-861001-57-6″>
<title>The Gorgias</title>
<author>
<name>Plato</name>
</author>
<price>9.99</price>
</book>
The Read function takes the liberty to position itself at the book tag, the
first Skip function skips over the first book, and the second skip function
skips over the second book. The Load function of the XmlDocument now loads the
node from the reader’s current position, i.e. from the third book onwards. Thus,
the XmlDocument contains the third book, which is about Philosophy, and not the
other two books. The skip function skips over nodes that are at the same level.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlDocument d = new XmlDocument();
d.LoadXml(“<aa bb=’hi’> bad </aa>”);
d.Save(“d.xml”);
}
}
d.xml
<aa bb=”hi”> bad </aa>
There are different ways of associating XML with an XmlDocument object. The
XmlDocument class has a function called LoadXml that converts a string
representing XML into an XmlDocument object. The Save function writes the
contents to a file on disk named d.xml.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlDocument d = new XmlDocument();
d.CreateElement(“vijay”);
d.Save(“d.xml”);
}
}
Output
Unhandled Exception: System.Xml.XmlException: This an invalid XML document. The
document does not have a root element.
The CreateElement function, as the name suggests, creates an element with the
name passed as a parameter. We encounter an impediment when we attempt to write
it to disk. An exception is thrown, since we have not added the element to the
XmlDocument. Had we used the Save overload that writes to Console.Out, no
exceptions would have been thrown, but neither would any output have been
displayed on the screen.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static void Main() {
XmlDocument d = new XmlDocument();
XmlElement e;
e = d.CreateElement(“vijay”);
d.AppendChild(e);
d.Save(“d.xml”);
}
}
d.xml
<vijay />
The CreateElement function returns an XmlElement object, which is stored in e.
This object is then passed on to the AppendChild function, which creates a child
node. Thus, when we write the XmlDocument to disk, d.xml will display a single
tag of ‘vijay’.
We shall now examine a series of programs, which shall create each and every
type of node and shall write them all to disk.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlDocument d = new XmlDocument();
XmlElement e;
e = d.CreateElement(“vijay”);
XmlAttribute a = d.CreateAttribute(“a1″);
e.SetAttributeNode(a);
e.SetAttribute(“a1″, “howdy”);
d.AppendChild(e);
d.Save(“d.xml”);
}
}
d.xml
<vijay a1=”howdy” />
CreateElement creates an element called ‘vijay’. Thereafter, a function called
CreateAttribute creates an attribute called a1. This function returns an
XmlAttribute object that is associated with the element e, using the function
SetAttributeNode, off the XmlElement class. If we comment the line
e.SetAttribute(“a1″, “howdy”); we shall see the file d.xml containing the
following:
d.xml
<vijay a1=”" />
The attribute gets created, but it is devoid of any value at this stage. It is
the SetAttribute function from the XmlElement class that will assign the value
to the attribute. Hence, it accepts two parameters.
The first parameter is a1, which is the name of the attribute, and the second
parameter ‘howdy’ is the value of the attribute.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlDocument d = new XmlDocument();
XmlElement e;
e = d.CreateElement(“vijay”);
d.AppendChild(e);
XmlCDataSection c;
c = d.CreateCDataSection(“sonal mukhi”);
d.AppendChild(c);
d.Save(“d.xml”);
}
}
Output
Unhandled Exception: System.InvalidOperationException: The specified node cannot
be inserted as the valid child of this node, because the specified node is the
wrong type.
We first create an element ‘vijay’ by using the CreateElement function.
Thereafter, using the Append Child function, we add the element to the
XmlDocument. No errors are generated upto this point.
Now, we wish to add a CDATA section to the file. To achieve this, the
CreateCDataSection is used with a string parameter ‘sonal mukhi’, which returns
an XmlCDataSection object. Employing the familiar AppendChild function, the
section is added to the file.
Since an XML document cannot have a CDATA section by itself, the above exception
is generated.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlDocument d = new XmlDocument();
XmlElement e;
e = d.CreateElement(“vijay”);
d.AppendChild(e);
XmlCDataSection c;
c = d.CreateCDataSection(“sonal mukhi”);
XmlElement r = d.DocumentElement;
r.AppendChild(c);
d.Save(“d.xml”);
}
}
d.xml
<vijay><![CDATA[sonal mukhi]]></vijay>
The error now disappears, as the AppendChild function is called from the
XmlElement class, and not from the XmlDocument class.
The DocumentElement property represents the root or the first element of
theXMLdocument. The CDATA section now gets added to the element ‘vijay’, since
the root element is ‘vijay’.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static void Main() {
XmlDocument d = new XmlDocument();
d.LoadXml(“<vijay> hi </vijay>”);
XmlCDataSection c;
c = d.CreateCDataSection(“sonal mukhi”);
XmlElement r = d.DocumentElement;
r.AppendChild(c);
d.Save(“d.xml”);
}
}
d.xml
<vijay> hi <![CDATA[sonal mukhi]]></vijay>
An alternative approach would be to use the trustworthy LoadXml function, in
lieu of CreateElement. Any of the two approaches may be employed to add a root
node, thereby, displaying the d.xml file. As we have been harping repeatedly,
there are many ways to skin a cat.
a.cs
using System.Xml;
public class Sample
{
public static void Main()
{
XmlDocument d = new XmlDocument();
d.LoadXml(“<!DOCTYPE book > <book /> “);
XmlDocumentType t;
t = d.DocumentType;
System.Console.WriteLine(t.OuterXml);
System.Console.WriteLine(t.Name);
}
}
Output
<!DOCTYPE book[]>
book
This program demonstrates our ability to access a specific node while processing
an XML file.
The DocumentType property returns an XmlDocumentType t, which represents the
singular DOCTYPE node present in an XML file. This object t has a large number
of properties that facilitate access to all the details regarding the node. In
the program, only two properties are displayed:
• The first property OuterXml represents the entire node as a string. Since no
external DTD file is present, by default, the internal DTD, which does not have
any contents, is displayed.
• The second property Name presents the name of the root node.
a.cs
using System;
using System.Xml;
public class Sample
{
public static void Main()
{
XmlDocument d = new XmlDocument();
d.LoadXml(“<book> <title>None</title> <author>Me</author> </book>”);
XmlNode r = d.FirstChild;
XmlNode n = r.FirstChild;
Console.WriteLine(r);
Console.WriteLine(n.OuterXml);
Console.WriteLine(r.OuterXml);
}
}
Output
System.Xml.XmlElement
<title>None</title>
<book><title>None</title><author>Me</author></book>
The FirstChild property of the XmlDocument class or XmlNode class retrieves the
first child of the current node in the document i.e. XmlElement. The value is
stored in both r and n. It can be displayed using the WriteLine function. The
OuterXml property contains the tags, including the child nodes.
The FirstChild property of r is an XmlNode, whose OuterXml is the first child
node or tag title within the tag ‘book’. If no child node exists, the value is
null.
a.cs
using System;
using System.Xml;
public class Sample
{
public static void Main()
{
XmlDocument d = new XmlDocument();
d.LoadXml(“<book> <title>None</title> <author>Me</author> </book>”);
XmlNode r = d.LastChild;
XmlNode n = r.LastChild;
Console.WriteLine(r);
Console.WriteLine(n.OuterXml);
Console.WriteLine(r.OuterXml);
}
}
Output
System.Xml.XmlElement
<author>Me</author>
<book><title>None</title><author>Me</author></book>
Comparable to the FirstChild property is the LastChild property, which merely
returns the last child in the node. Therefore, as the ‘author’ tag is the last
tag, the LastChild property returns a reference to ‘Me’. Since title is the only
tag in the file, the use of FirstChild or LastChild achieves the same outcome,
in this case.
a.cs
using System;
using System.Xml;
public class Sample
{
public static void Main()
{
XmlDocument d = new XmlDocument();
d.LoadXml(“<aa><book> hi<title>None</title> </book> </aa>”);
XmlDocument de = (XmlDocument) d.CloneNode(true);
Console.WriteLine(de.ChildNodes.Count);
for (int i=0; i<de.ChildNodes.Count; i++)
Console.WriteLine(de.ChildNodes[i].OuterXml);
Console.WriteLine(de.Name + de.OuterXml);
Console.WriteLine(“Shallow”);
XmlDocument sh = (XmlDocument) d.CloneNode(false);
Console.WriteLine(sh.ChildNodes.Count);
for (int i=0; i<sh.ChildNodes.Count; i++)
Console.WriteLine(sh.ChildNodes[i].InnerXml);
Console.WriteLine(sh.Name + sh.OuterXml);
}
}
Output
1
<aa><book> hi<title>None</title></book></aa>
#document<aa><book> hi<title>None</title></book></aa>
Shallow
0
#document
We start with an XML fragment that has a root node aa, which has a child node of
book, containing another child node called title. The CloneNode function in
XmlDocument takes a boolean parameter, where True refers to cloning the nodes
within the node aa, which includes the nodes ‘book’ and ‘title’. It behaves akin
to a copy constructor for nodes.
The cloned node does not have a parent. Therefore, when we exploit the property
ParentNode to ascertain the value of the parent node, it returns a value of
null. The ParentNode property returns a handle to the Parent node of the node.
The return value of the CloneNode function is an XmlNode class. As the
XmlDocument derives from XmlNode, the ‘cast’ operator is used. The XmlNode class
has a property called ChildNodes, which returns an XmlNodeList object that
refers to all the child nodes. This object is of a Collection data type. The
Count property in the collection can be used to render a count of the number of
entities present in the clone.
The Name property returns the qualified name of #document and the OuterXml
property provides the entire element. This function at variance with the
LocalName function, which returns the name of an attribute, #cdata-section for
the CDATA section, and so on. Thus, with the help of this function, we can
identify the different names for different node types.
The CloneNode function is called again, but with a value of false. Thus, a
shallow node with no ChildNodes is returned. This is confirmed using the count
property, which shows a count of zero. This proves that only the node has been
cloned and not the content.
Using a ‘for’ statement, we iterate through all the child nodes, and display the
OuterXml property using the indexer. The value returned is the content of the
node, which includes text such as ‘hi’. If you read about the CloneNode function
in the documentation, it would apprise you of the impending eventuality, if an
attempt is made to clone nodes of different types.
a.cs
using System;
using System.IO;
using System.Xml;
public class Sample
{
public static void Main()
{
XmlDocument d = new XmlDocument();
d.LoadXml(“<!DOCTYPE book [<!ENTITY bb 'vijay'>]> <book> <misc /> </book>”);
XmlEntityReference e = d.CreateEntityReference(“bb”);
Console.WriteLine(e.ChildNodes.Count);
d.DocumentElement.LastChild.AppendChild(e);
Console.WriteLine(e.FirstChild.InnerText + “. ” + e.ChildNodes.Count + ” ” +
e.Name);
d.Save(Console.Out);
XmlEntityReference e1 = d.CreateEntityReference(“bbb”);
Console.WriteLine(e1.ChildNodes.Count);
d.DocumentElement.LastChild.AppendChild(e1);
Console.WriteLine(e1.FirstChild.InnerText + “. ” + e1.ChildNodes.Count + ” ” +
e1.Name);
d.Save(Console.Out);
}
}
Output
0
vijay. 1 bb
<!DOCTYPE book[<!ENTITY bb 'vijay'>]>
<book>
<misc>&bb;</misc>
</book>0
. 1 bbb
<!DOCTYPE book[<!ENTITY bb 'vijay'>]>
<book>
<misc>&bb;&bbb;</misc>
</book>
The LoadXml function has a DOCTYPE declaration, and an entity reference called
bb having a value of ‘vijay’. Now, using the function CreateEntityReference, we
create an entity reference called bb. This function returns an
XmlEntityReference object, which is a class derived from XmlLinkedNode, which,
in turn, is derived from XmlNode. The number of child nodes is zero at this
stage, because the node has not yet been expanded.
The function AppendChild is then used to append the child to the node, returned
by the LastChild property.
Since the node has been appended to the document, its parent node is set and the
entity reference bb is expanded to ‘vijay’. Thus, the file has a child node that
contains ‘vijay’, the entity reference text. The InnerText property of the
FirstChild gives the replacement text. The Count of the child nodes is one,
since we have added one entity, and the Name property of the Entity Reference is
the name bb, which we have created.
Finally, the Save function, which prints out the XML fragment, displays the
entity reference bb, starting with an ampersand symbol & and ending with a
semi-colon, with the LastChild node named misc.
We now add another entity reference bbb, but lay it aside, undefined. The rules,
which applied earlier to the entity reference, are also relevant now. The only
significant difference is that the InnerText property does not have any value
when the reference node is expanded. Thus, the child is an empty text node. The
entity refs of bb and bbb are placed one after the other.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static void Main() {
XmlDocument d = new XmlDocument();
d.LoadXml(“<book></book>”);
XmlNode n=d.CreateNode(“element”, “vijay”, “”);
n.InnerText = “hi”;
XmlElement r = d.DocumentElement;
r.AppendChild(n);
//d.LastChild.AppendChild(n);
//d.AppendChild(n);
Console.WriteLine(d.OuterXml);
d.Save(Console.Out);
}
}
Output
<book><vijay>hi</vijay></book>
<book>
<vijay>hi</vijay>
</book>
The XML fragment shows the root tag as ‘book’ because of the LoadXml function.
The CreateNode function creates a new node or element called ‘vijay’. This
function returns an object of type XmlNode and accepts the following parameters:
The first parameter decides on the type of node. Exactly 10 types of nodes can
be created with this function: element, attribute, text, cdatasection,
entityreference, processinginstruction, comment, document, documenttype and
documentfragmnet. This parameter is case sensitive.
The second parameter is the name of the new node. If there is a colon in the
name, it is parsed into a prefix and a LocalName.
The third parameter refers to the namespace URI. The InnerText property is
another mechanism for adding content between tags. The tag ‘vijay’ now embodies
the word ‘hi’. This node in memory is added to the XML fragment with the help of
the AppendChild function, which is called off the XmlElement, returned by the
DocumentElement property.
Calling AppendChild off the XmlDocument class, throws an exception, since a
DocumentElement node already exists within the document. If we uncomment the
line d.AppendChild(n), it will result in the following exception:
Output
Unhandled Exception: System.InvalidOperationException: This document already has
a DocumentElement node.
If we uncomment the line d.LastChild.AppendChild(n), it would be similar to
first gaining a handle to the last child and then adding the node. In this case,
whether the node is the first child or the last child, it does not make any
difference at all. The documentation for the CreateNode function, offers a table
that very distinctly specifies as to which nodes can be placed within other
nodes.
a.cs
using System;
using System.Xml;
public class zzz {
public static void Main()
{
XmlDocument d = new XmlDocument();
d.Load(“b.xml”);
XmlImplementation i;
i = d.Implementation;
XmlDocument d1 = i.CreateDocument();
d.Save(Console.Out);
d1.Save(Console.Out);
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay />
Output
<?xml version=”1.0″?>
<!DOCTYPE vijay[]>
<vijay />
Every XmlDocument has an XmlImplementation object associated with it, which can
be accessed using the Implementation property. XmlDocument objects created from
the same XmlImplementation, share the same name table. Thus, it is permissible
to compare attribute and element names as objects, instead of strings.
We create an empty XmlDocument d by using the constructor as before, and
thereafter, use the Load function to associate anXMLfile named b.xml with the
XmlDocument. Earlier we had used the Reader object.
To create an XmlDocument object that shares the same XmlNameTable, we use the
function CreateDocument from the XmlImplementation object. Then, the Save
function is used to write out both the XmlDocument objects to the Console. The
first XmlDocument object d, displays a replica of the file b.xml, while the
second one is empty because we have not associated any XML content with it.
Thus, the CreateDocument function creates an empty XML document, but it does not
copy any content to it. It ensures that the names are shared, and not
duplicated.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextReader r = new XmlTextReader (“books.xml”);
r.WhitespaceHandling = WhitespaceHandling.None;
XmlDocument d = new XmlDocument();
d.Load (r);
XmlNode n;
n = d.DocumentElement;
Console.WriteLine(n.Name + “<” + n.Value + “>”);
n = n.FirstChild;
Console.WriteLine(n.Name + “<” + n.Value + “>”);
while (n != null)
{
XmlNamedNodeMap m = n.Attributes;
foreach (XmlNode a in m)
Console.Write(” ” + a.Name + “<” + a.Value + “> “);
n = n.NextSibling;
Console.WriteLine();
}
}
}
Output
bookstore<>
book<>
genre<autobiography> publicationdate<1981> ISBN<1-861003-11-0>
genre<novel> publicationdate<1967> ISBN<0-201-63361-2>
genre<philosophy> publicationdate<1991> ISBN<1-861001-57-6>
First, we start by loading the XML file books.xml, and then, positioning the
DocumentElement at the root tag bookstore. Then, using the FirstChild property,
we make the first child node ‘book’ as the active node and display it. As we
have no clue about the number of child book nodes that are present in the file,
we use a while loop that repeats itself until the XmlNode object n returns a
null.
The Attributes property returns an XmlNamedNodeMap object that represents a
collection of nodes. These nodes can be represented by a name or an index. So,
using the foreach loop, we fetch one attribute at a time in an XmlNode object
and display the Name and the Value contained in it. The <> signs are used as a
talisman.
Once this is accomplished, we move to the next book at the same level. Tags at
the same level are called siblings. The property NextSibling moves to the next
book or tag at the same level, thereby, displaying one book tag after another.
a.cs
using System;
using System.Xml;
public class Sample
{
public static void Main()
{
XmlTextReader r = new XmlTextReader (“books.xml”);
r.WhitespaceHandling = WhitespaceHandling.None;
XmlDocument d = new XmlDocument();
d.Load (r);
XmlNode n = d.DocumentElement;
Console.WriteLine(n.Name);
XmlNode b = n.LastChild;
Console.WriteLine(b.Name);
Console.WriteLine(b.OuterXml + “\n”);
b = n.LastChild.PreviousSibling;
Console.WriteLine(b.OuterXml);
}
}
Output
bookstore
book
<book genre=”philosophy” publicationdate=”1991″ ISBN=”1-861001-57-6″><title>The
Gorgias</title><author><name>Plato </name></author><price>9.99</price></book>
In the previous example, we first displayed the root node bookstore, followed by
the LastChild and not the FirstChild node. Both are expected to return the same
name, since the first and last child tags refer to the same node book. The XML
associated with this node was displayed using the OuterXml function, which
displayed the last book, which deals with Philosophy. You may recollect that the
OuterXml function displays the child tags also.
In this example, we proceed backwards. Hence, we use the PreviousSibling
function to display the second last book. Thus, we can either proceed in the
forward direction, like we did in the last example, or in the backward
direction, as shown in the current example.
a.cs
using System;
using System.IO;
using System.Xml;
public class Sample
{
public static void Main()
{
XmlDocument d = new XmlDocument();
d.LoadXml(“<book> hi </book>”);
XmlComment c;
c = d.CreateComment(“Comment 1″);
XmlElement r = d.DocumentElement;
d.InsertBefore(c, r);
d.Save(Console.Out);
Console.WriteLine(“\n—”);
XmlComment c1;
c1 = d.CreateComment(“Comment 2″);
d.InsertAfter(c1, r);
d.Save(Console.Out);
}
}
Output
<!–Comment 1–>
<book> hi </book>
—
<!–Comment 1–>
<book> hi </book>
<!–Comment 2–>
LoadXml creates a node called book, which encompasses ‘hi’. Thereafter, a
Comment object is created by calling the CreateComment function. The function
merely requires the string to be displayed as a comment. The extra comment
characters are placed by the function in the XML file.
The next dilemma is with regard to the position of the comment i.e. should the
comment be placed before or after the node book. The InsertBefore function
inserts the required node. It takes two parameters, i.e. a comment, followed by
the node before which the comment is to be inserted. As we want it to be
inserted before the book node, we use the handle returned by DocumentElement
property, which contains the handle to the book node. The Save function then
displays the comment before the book node.
The InsertAfter function inserts the comment node after the reference node,
which has been passed as the second parameter. Thus, the second comment comes
after the book node.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz {
public static void Main()
{
XmlDocument d = new XmlDocument();
d.LoadXml(“<?xml version=’1.0′ ?><book> <title>Vijay</title> </book>”);
XmlNode n = d.DocumentElement;
n.RemoveChild(n.FirstChild);
d.Save(Console.Out);
d.RemoveAll();
Console.WriteLine(“\n=========”);
d.Save(Console.Out);
}
}
Output
<?xml version=”1.0″?>
<book>
</book>
=========
The XML fragment that is loaded using the LoadXml function has an XML
Declaration, which is a tag book, containing an element of title enclosing
‘Vijay’.
The DocumentElement property returns a handle to the node book, which is stored
in n. Then, the RemoveChild function of node n is called with a single
parameter, which is the node title, since the FirstChild returns this value.
This function removes the child from the tag.
Saving to the Console displays the remaining XML fragment. Thus, we can see the
XML declaration and the tag book, but without any content, since the tag title
has been removed.
The next function that is implemented is, RemoveAll from the XmlDocument class.
On displaying the XML file, we witness no output since the RemoveAll function
erases everything from the Document.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz {
public static void Main()
{
XmlNode n;
XmlDocument d = new XmlDocument();
d.LoadXml(“<zz xml:space=\”preserve\”><aa>Vijay</aa><bb>Mukhi</bb></zz>”);
Console.WriteLine(d.DocumentElement.InnerText);
n=d.DocumentElement;
XmlSignificantWhitespace s=d.CreateSignificantWhitespace(” “);
d.Save(Console.Out);
Console.WriteLine();
n.InsertAfter(s, n.FirstChild);
Console.WriteLine(d.DocumentElement.InnerText);
d.Save(Console.Out);
}
}
Output
VijayMukhi
<zz xml:space=”preserve”>
<aa>Vijay</aa>
<bb>Mukhi</bb>
</zz>
Vijay Mukhi
<zz xml:space=”preserve”>
<aa>Vijay</aa> <bb>Mukhi</bb></zz>
The DocumentElement property returns a handle to the zz tag, whereas, the
InnerText property in the DocumentElement, refers to the Text present within the
tags aa and bb. Thus, we see ‘VijayMukhi’ displayed without any spaces displayed
between the two words.
We also create significant whitespace, using the function
CreateSignificantWhitespace, after initializing the node n to tag zz. This
function accepts only one string parameter, which could be any one of the
following four: and . It does not augment theXMLfragment.
Finally, we write the XML fragment using the Save function. The attribute
space=preserve is visible. This attribute is optional.
Using the InsertAfter function, we add significant whitespace after the
FirstChild node. Thus, we see significant space between the nodes aa and bb. The
InnerText also displays the spaces between the words ‘Vijay’ and ‘ Mukhi’.
Output
VijayMukhi
<zz xml:space=”preserve”>
<aa>Vijay</aa>
<bb>Mukhi</bb>
</zz>
VijayMukhi
<zz xml:space=”preserve”> <aa>Vijay</aa><bb>Mukhi</bb></zz>
The above output is obtained on adding the significant space before the first
child, using the InsertBefore function, in lieu of the InsertAfter function. The
spaces are inserted before the FirstChild aa, i.e. before the text ‘Vijay’ and
not after.
a.cs
using System;
using System.Collections;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlDocument d = new XmlDocument();
d.Load(“books.xml”);
XmlNode n = d.DocumentElement;
IEnumerator i = n.GetEnumerator();
XmlNode b;
while (i.MoveNext())
{
b = (XmlNode)i.Current;
Console.WriteLine(b.OuterXml);
}
}
}
Output
<book genre=”autobiography” publicationdate=”1981″
ISBN=”1-861003-11-0″><title>The Autobiography of Benjamin
Franklin</title><author><first-name>Benjamin</first-name><last-name>
Franklin</last-name></author><price>8.99</price></book>
<book genre=”novel” publicationdate=”1967″ ISBN=”0-201-63361-2″><title>The
Confidence Man</title><author><first-name>
Herman</first-name><last-name>Melville</last-name></author>
<price>11.99</price></book><book genre=”philosophy” publicationdate=”1991″
ISBN=”1-861001-57-6″><title>The
Gorgias</title><author><name>Plato</name></author><price> 9.99</price></book>
The last example in the series, displays various parts of an XML file in a
different manner.
Every XML node object has a function called GetEnumerator, which returns an
IEnumerator interface object. This interface has a MoveNext function that moves
from one node to the next. In our program, we move from one book to another,
since the XmlNode is on a tag book.
The Current property accesses the current book node, since it happens to be the
first one. Then, we use the OuterXml property to display the entire contents of
this node.
The MoveNext function then activates the next node. If no more nodes exist, it
returns False, or else it returns True. In this manner, we can iterate through
all the nodes. The foreach instruction has a similar functionality. Whenever we
want to navigate through a collection, the IEnumerator interface is used. This
is the conventional way of moving sequentially through a list of objects or a
collection in C#.
C Language Help, C Language Tutorials, C Language Programming, C Language Tricks { The C# Language }
C Language Help, C Language Tutorials, C Language Programming, C Language Tricks
The DTD
Validations in XML
So far, we have only read an XML file, without catering to special cases,
wherein, either an entity has been used, or data has to be validated as per the
element. The XmlTextReader class is the most optimum choice for reading an XML
file, barring the cases where data has to be validated, or in cases where an
entity has to be replaced with a value. For such purposes, the
XmlValidatingReader class is more suited. This class is derived from XmlReader,
and it conducts three types of validations- DTD, XDR and XSD schema validations.
This class is used when the primary task is either to conduct data validations
or to resolve general entities or to provide support for default entities.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlValidatingReader r = null;
XmlParserContext p;
p = new XmlParserContext(null, null, “vijay”, null, null, “<!ENTITY pr
’100′>”,”",”", XmlSpace.None);
r = new XmlValidatingReader (“<vijay mukhi=’great’ price=’Rs ≺’></vijay>”,
XmlNodeType.Element, p);
r.ValidationType = ValidationType.None;
r.MoveToContent();
while (r.MoveToNextAttribute())
{
Console.WriteLine(“{0} = {1}”, r.Name, r.Value);
}
r.Close();
}
}
Output
mukhi = great
price = Rs 100
To create the object p of type XmlParserContext, the constructor with nine
parameters of XmlParserContext class is called. The nine parameters are as
follows:
• The first parameter refers to the NameTable type. It has a value of null.
• The second parameter refers to NamespaceManager type. It also has a value of
null.
• The third Parameter is the DocType, i.e. the root tag ‘vijay’.
• The fourth parameter is the pubid for the external DTD file.
• The fifth parameter is the sysid for the external DTD file.
• The sixth parameter is the internal DTD, where an ENTITY declaration <!ENTITY
pr ’100′> has been created. This simply states that the word ‘pr’ is preceded by
a ‘&’ and followed by a semi-colon must be replaced with the string ’100′.
• The seventh parameter in sequence is the location from where the fragment is
to be loaded, i.e. the base URI.
• The eighth parameter stands for the xml:lang scope.
• The ninth parameter stands for the xml:space scope.
The parameters to the constructor of XmlValidatingReader class are similar to
those of the XmlTextReader, which we had encountered earlier. This class is
derived from the XmlTextReader as well as the IXmlLineInfo interface.
There are five different values that a Validationtype can be initialized to:
1. The first is Auto, which validates only when the DTD or schema information is
found.
2. The second is DTD, which validates based on the instructions found in the DTD.
3. The third option, which creates an XML 1.0 non-validation parser, validates
the default attributes and resolves entities without using the DOCTYPE. Thus, if
the root tag is changed from ‘vijay’ to ‘vijay1′, no errors will be generated.
Placing the ValidationType statement within comments will generate the following
exception:
“Unhandled Exception: System.Xml.Schema.XmlSchemaException: The root element
name must match the DocType name. An error occurred at (1, 2).”
4. The fourth option is XSD, which validates as per the XSD schemas.
5. The fifth option is XDR, which validates as per the XDR schemas. In our
program we have set this property to a value of None.
Once the required properties are set, the MoveToContent function is used to move
to the first element, ‘vijay’. The next function, MoveToNextAttribute returns a
value of True when there are attributes remaining to be read. Otherwise, it
returns a value of False. In our case, it is similar to the MoveToFirstElement
function.
The while loop repeats twice, since there are two attributes. The Name and Value
properties for the first attribute are displayed as ‘mukhi’ and ‘great’. This is
very similar to what we have observed in the earlier program. The name for the
second attribute is displayed as ‘price’. However, its value is not the same,
because it has an entity ≺. The XmlValidatingReader replaces the entity pr
with the string ’100′, prior to displaying the value. Therefore, the output is
displayed as ‘price’ and ‘Rs. 100′.
a.cs
using System;
using System.IO;
using System.Xml;
using System.Xml.Schema;
class zzz
{
public static void Main()
{
XmlTextReader r = new XmlTextReader(“b.xml”);
XmlValidatingReader v = new XmlValidatingReader(r);
v.ValidationType = ValidationType.DTD;
v.ValidationEventHandler += new ValidationEventHandler (abc);
while(v.Read());
}
public static void abc(object s, ValidationEventArgs a)
{
Console.WriteLine(“Severity:{0}”, a.Severity);
Console.WriteLine(“Message:{0}”, a.Message);
}
}
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE vijay1 >
<vijay>
</vijay>
Output
Severity:Error
Message:The root element name must match the DocType name. An error occurred at
file:///c:/csharp/b.xml(3, 2).
Severity:Error
Message:The ‘vijay’ element is not declared. An error occurred at file:///c:/csharp/b.xml(3,
2).
In the above program, to begin with, an object r that looks like XmlTextReader
is created, and then, it is passed to the constructor of XmlValidatingReader,
while object v is being created. The ValidationType of the object v is modified
to DTD. The ValidationEventHandler event is set to the function abc, which gets
called whenever an error occurs. Under the aegis of the Read function, the
entire XML file is validated, using the while loop, and the function abc is
notified whenever an error is chanced upon.
In the function abc, the values contained in the properties – Severity and
Message, of the ValidationEventArgs parameter ‘a’, are printed. The Severity
property reveals whether it is an error or warning, whereas, the Message
property contains the precise text of the error or warning.
In the above case, an error is generated because the DOCTYPE expects the root
element to be ‘vijay1′, whereas, it has been specified as ‘vijay’. When no error
message is displayed, it may be inferred that no errors have been found.
The DTD
Using the above C# program, we shall now create our own DTD file. Therefore, we
shall modify only the b.xml and b.dtd files.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE vijay SYSTEM “b.dtd” >
<vijay />
b.dtd
<!ELEMENT vijay >
A DTD is generally very protracted. So, an internal DTD is rarely used. If it is
used, its contents have to be placed within [] brackets. To use an external DTD,
we use the words SYSTEM followed by the name of the DTD file, which is b.dtd, in
this case.
In b.dtd, an element ‘vijay’ is created by inserting the reserved characters
‘<!’, followed by ELEMENT, and finally by the element name ‘vijay’. When we run
the C# program ‘a’, the following error is generated:
Output
Unhandled Exception: System.Xml.XmlException: This is an invalid content model.
Line 1, position 17.
An error in the DTD file has resulted in the generation of an un-handled
exception. The error occurred due to an incomplete ELEMENT statement.
b.dtd
<!ELEMENT vijay EMPTY>
The addition of the word EMPTY salvages the situation. By specifying the word
EMPTY, it is amply clear that the element named ‘vijay’ is an empty element.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE vijay SYSTEM “b.dtd” >
<vijay>
</vijay>
Output
Severity:Error
Message:Element ‘vijay’ has invalid child element ‘#PCDATA’. An error occurred
at file:///c:/csharp/b.xml(3, 8).
The DTD file states, with absolute clarity, that the ELEMENT ‘vijay’ is EMPTY.
However, an open tag <vijay> and a close tag </vijay>have been added to the XML
file. Therefore, an error message is generated, which, as usual, is
unintelligible.
Instead of using tags such as ‘vijay’, let us consider a DTD that has been
implemented in real life. This one is used for the WML, or the Wireless Markup
Language. The rules or syntax of WML are available as a DTD.
In our book titled ‘WML and WMLScript’, we have endeavoured to elucidate the
concept of a DTD. You are at liberty to refer to the book. However, we must
caution you that, the approach and the explanation used here is entirely at
variance with the one used in the earlier book.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
</wml>
b.dtd
<!ELEMENT wml EMPTY>
Output
Severity:Error
Message:Element ‘wml’ has invalid child element ‘#PCDATA’. An error occurred at
file:///C:/csharp/b.xml(3, 6).
The word ‘vijay’ has merely been replaced by the word ‘wml’. The error generated
is akin to the earlier one. At this juncture, we introduce a ‘card’ into the DTD
file.
b.dtd
<!ELEMENT wml (card)>
Output
Severity:Error
Message:Element ‘wml’ has incomplete content. Expected ‘card’. An error occurred
at file:///c:/csharp/b.xml(4, 3).
Every WML document must commence with the root tag ‘wml’. In the DTD file, we
have placed the word ‘card’ within round brackets, along with wml. This
signifies that the wml tag must contain a tag or an element called ‘card’. Since
there is no card in the XML file, an error is reported, stating that a card is
expected, and on account of its unavailability, the wml element is incomplete.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card />
</wml>
Output
Severity:Error
Message:The ‘card’ element is not declared. An error occurred at file:///c:/csharp/b.xml(4,
2)
We add the card tag as a single tag to our XML file, in an endeavour to
eliminate the error. But, as we have not specified ‘card’ as a valid element in
the DTD file, yet another error message is displayed. Unless ‘card’ appears as
an ELEMENT in the DTD file, it is not possible to use it in the XML file.
Therefore, we now include ‘card’ as an EMPTY element in b.dtd
b.dtd
<!ELEMENT wml (card)>
<!ELEMENT card EMPTY>
Now, all the errors just vanish. In the DTD file, we had affirmed that the
element ‘card’ shall be empty i.e. it will not have any content.
The XML file depicted below displays an error, because the ‘card’ tag is not a
single tag any longer.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card>
</card>
</wml>
Output
Severity:Error
Message:Element ‘card’ has invalid child element ‘#PCDATA’. An error occurred at
file:///C:/csharp/b.xml(4, 7).
The error message displayed here is very similar to the one seen with the wml
tag.
The element ‘wml’ has an invalid child element ‘#PCDATA’
A slight modification to the XML file is desirable, before we endeavour to
eliminate the error.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card>
hi
</card>
</wml>
Output
Severity:Error
Message:Element ‘card’ has invalid child element ‘Text’. An error occurred at
file:///c:/csharp/b.xml(4, 7).
Inserting the word ‘hi’ between the card tags results in a slightly altered
error messages. In place of PCDATA, we get to see Text. Resorting to the
following modifications to the DTD file, both the error messages can be
eliminated.
b.dtd
<!ELEMENT wml (card)>
<!ELEMENT card (#PCDATA)>
To eradicate the errors, the EMPTY word is replaced with #PCDATA, enclosed
within round brackets. The word PCDATA is an acronym for Parseable Character
Data. In plain English, it represents text that can be entered from the
keyboard. Thus, we are at liberty to write as many lines of text as we want,
within the card tag. Even if the word ‘hi’ is removed from within the tags, no
error is generated.
Our DTD expects a root tag or starting tag of wml. Only a card tag can be
inserted amidst within this tag, which is capable of containing limitless
content. Insertion of anything else in this tag is a sure recipe for disaster.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card>
</card>
<card>
</card>
</wml>
Output
Severity:Error
Message:Element ‘wml’ has invalid content. Expected ”. An error occurred at
file:///c:/csharp/b.xml(6, 2).
The above error has occurred because, the DTD clearly specifies that the root
tag wml must have one, and only one, occurrence of the tag called ‘card’ within
it. Here, we have created two tags, thereby, causing the error.
b.dtd
<!ELEMENT wml (card)*>
<!ELEMENT card (#PCDATA)>
The * symbol, placed after the round brackets, is indicative of the fact that,
it can be replaced with zero to infinite values. Thus, the XML file can now
either have zero or countless card elements. If you do not give credence to this
statement of ours, you may either delete all the card elements from the XML
file, or add numerous cards. Either way, no error will be generated.
b.dtd
<!ELEMENT wml (card)+>
<!ELEMENT card (#PCDATA)>
Replacing the symbol * with a + transforms the meaning from ‘zero to infinity’
to ‘one to infinity’. The only difference between the * symbol and the + symbol
is that, the + sign mandates at least one occurrence of the element whereas, the
* signs makes it optional. Thus, in the aboveXMLfile, at least a single card
element is required.
b.dtd
<!ELEMENT wml (card)?>
<!ELEMENT card (#PCDATA)>
The last of the special characters is the symbol ? that specifies the number of
elements to be from ‘zero or one’. Thus in the XML file, we may have either one
card element or none at all. The presence of two or more cards will generate an
error. You should try out various possible combinations for each of the symbols
*, + and?.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card>
<p> hi </p>
</card>
</wml>
b.dtd
<!ELEMENT wml (card)*>
<!ELEMENT card (p)>
<!ELEMENT p (#PCDATA)>
No error is generated because, in the DTD file, we have now stated that, the
card element can have a tag p, which can contain any text. We have, however,
done away with the provision of placing any text within the card tag.
Add in a new modification to the file.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card>
<p> <b/> </p>
</card>
</wml>
b.dtd
<!ELEMENT wml (card)*>
<!ELEMENT card (p)>
<!ELEMENT p (br | b)>
<!ELEMENT br EMPTY>
<!ELEMENT b EMPTY>
The DTD appears extensively complicated. The p tag is now competent of
containing only two tags, br and b. Text is not allowed any more. The | sign
signifies the OR condition, which implies that either tag b or tag br is
allowed. The two aforesaid tags are defined as EMPTY tags. To summarise, our DTD
states that the p tag can contain a single tag of either b or br.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card>
<p> <b/> <br/></p>
</card>
</wml>
Output
Severity:Error
Message:Element ‘p’ has invalid content. Expected ”. An error occurred at
file:///c:/csharp/b.xml(5, 11).
All is not well, because we are allowed to place either a ‘b’ or a ‘br’ at a
time, but not both together. To remedy the situation, we place a * symbol after
the p tag.
b.dtd
<!ELEMENT wml (card)*>
<!ELEMENT card (p)*>
<!ELEMENT p (br | b)*>
<!ELEMENT br EMPTY>
<!ELEMENT b EMPTY>
The above DTD provides us the flexibility of having multiple p tags within n
number of cards. These, in turn, may have as many b or br tags as desired.
By replacing the b tag with #PCDATA, a p tag is in a position to accommodate
multiple br tags, as well as an indefinite amount of text.
<!ELEMENT p (br | #PCDATA)*>
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card />
<head />
</wml>
b.dtd
<!ELEMENT wml (card,head)>
<!ELEMENT card EMPTY>
<!ELEMENT head EMPTY>
The above DTD file permits the wml tag to contain a card tag, which is then to
be strictly followed by a head tag. The comma signifies that one tag is to be
followed by the other. If we refrain from using the head tag in the XML file,
the following error message will be generated:
Output
Severity:Error
Message:Element ‘wml’ has incomplete content. Expected ‘head’. An error occurred
at file:///C:/csharp/b.xml(5, 3).
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<head />
<card />
</wml>
Output
Severity:Error
Message:Element ‘wml’ has invalid content. Expected ‘card’. An error occurred at
file:///c:/csharp/b.xml(4, 2).
If the order of the tags is interchanged, an error is thrown. The card tag must
be followed by the head tag. Besides, there is a restriction imposed that there
can be only one insertion of each tag. If there are multiple insertions, it will
result in an error.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card />
<card />
<head />
</wml>
b.dtd
<!ELEMENT wml (card+,head?)>
<!ELEMENT card EMPTY>
<!ELEMENT head EMPTY>
When the plus sign is inserted after the card, it allows the use of more that
one card tag in the file. The ? sign denotes ‘zero or one’ insertions of the
head tag. Thus, we can have more than one card tag and have either a single head
tag or none at all. If the head tag is present, it must be placed after the card
tag, since the order of the tags is sacrosanct.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card />
<head />
<card />
</wml>
Output
Severity:Error
Message:Element ‘wml’ has invalid content. Expected ”. An error occurred at
file:///c:/csharp/b.xml(6, 2).
The Draconian restrictions imposed by the DTD file prohibit us from altering the
sequence of the above tags. The card tag has to come first, followed by the head
tag. We cannot interchange a head tag with a card tag. So, the only solution to
this problem is to abide by the stipulated sequence.
b.dtd
<!ELEMENT wml (card+,head?,template*)*>
<!ELEMENT card EMPTY>
<!ELEMENT head EMPTY>
<!ELEMENT template EMPTY>
In the DTD file, we have added a * symbol to the entire set of tags, which make
up the wml element. The set consists of the following individual elements in a
sequential order:
• More than one card tags.
• Zero or one head tag.
• Zero to many template tags.
This set can constitute of numerous permutations and combinations of the above
conditions, in the specified order. Thus, the card and head can appear together,
or the card can appear by itself without the head tag, or the template tag may
not be present at all, and so on. Every occurrence, however, needs to begin with
a card tag.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card aa=”hi”/>
</wml>
b.dtd
<!ELEMENT wml (card)>
<!ELEMENT card EMPTY>
<!ATTLIST card aa CDATA #IMPLIED>
In the above example, the card tag has an attribute called aa initialized to
‘hi’. To implement an attribute, we include the word ATTLIST, which is a short
form for ‘a list of attributes’, in the DTD file. This is followed by the name
of the tag that the attribute is associated with. Then, the actual name of the
attribute aa is specified, followed by the datatype it will hold, which is
character data, in our case. The last parameter, #IMPLIED permits the attribute
aa to be optional. Therefore, even if you remove it, no error will be generated.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card />
</wml>
b.dtd
<!ELEMENT wml (card)>
<!ELEMENT card EMPTY>
<!ATTLIST card aa CDATA #IMPLIED bb CDATA #REQUIRED>
Output
Severity:Error
Message:The required attribute ‘bb’ is missing. An error occurred at file:///c:/csharp/b.xml(4,
2).
The error message clearly mentions that the attribute bb is missing. The
#REQUIRED demands the presence of attribute bb, along with the card, whenever
the card tag is used. Further, the attributes are to be placed one after the
other. However, the order of placement is not significant.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card bb=”no”/>
</wml>
No errors are generated since the attribute bb, which is mandatory, has been
specified. You can avoid aa, since it is implied.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card aa=”no”/>
</wml>
b.dtd
<!ELEMENT wml (card)>
<!ELEMENT card EMPTY>
<!ATTLIST card aa (hi | bye ) “bye”>
Output
Severity:Error
Message:’no’ is not in the enumeration list. An error occurred at file:///c:/csharp/b.xml(4,
7).
The values assigned to attributes can be restricted to specific values. This can
be achieved by specifying the values along with ATTLIST in the DTD file and
using the OR sign (|) as the separator. The attribute aa can only be assigned
the value of either ‘hi’ or ‘bye’. Specifying any other value would result in an
error.
If the attribute is not initialized, it assumes the default value of ‘bye’.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card aa=”hi”/>
</wml>
The error disappears because the attribute has been assigned a value of ‘hi’.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card aa=”hi”/>
</wml>
b.dtd
<!ELEMENT wml (card)*>
<!ELEMENT card EMPTY>
<!ATTLIST card aa ID #IMPLIED>
We have created an attribute aa, with a data type of ID. This does not result in
any error.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card aa=”hi”/>
<card aa=”hi”/>
</wml>
Output
Severity:Error
Message:’hi’ is already used as an ID. An error occurred at file:///c:/csharp/b.xml(5,
7).
The card tag can be used multiple times, due to the presence of the * sign in
the DTD file. By associating the type of ID to the attribute aa, it is
guaranteed that the same value of ‘hi’ is not assigned to the attribute. The
error message conveys that ‘hi’ has already been assigned as an ID to the
attribute aa, and hence, it cannot be used again.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card aa=”hi”/>
<card aa=”hi1″/>
</wml>
If we assign a different value to the attribute, the error is dispensed with.
Thus, a data type of ID guarantees that the attribute shall never have a
duplicate value.
b.xml
<?xml version=”1.0″ ?>
<!DOCTYPE wml SYSTEM “b.dtd” >
<wml>
<card>
Hi &sonal;
</card>
</wml>
b.dtd
<!ELEMENT wml (card)*>
<!ELEMENT card (#PCDATA)*>
<!ENTITY sonal “hi” >
Entities have been touched upon earlier. Here, the word ‘sonal’ will be replaced
with ‘hi’. This is called an Entity Reference. The DTD file requires an ENTITY
word with the variable ‘sonal’, and the value ‘hi’.
C Language Help, C Language Tutorials, C Language Programming, C Language Tricks { The C# Language }
C Language Help, C Language Tutorials, C Language Programming, C Language Tricks
XML Classes
eXtensible Markup Language i.e. XML is a subset of the Standard Generalized
Markup Language
(SGML), which is an ISO standard numbered ISO 8879. SGML was perceived to be
remarkably colossal
and extremely convoluted to be put to any pragmatic use. Thus, a subset of this
language, XML,
was developed to work seamlessly with both SGML and HTML. XML may be considered
as a restricted
form of SGML, since it conforms to the rules of an SGML document.
XML was created in the year 1996 under the auspices of the World Wide Web
Consortium (W3C),
under the chairmanship of Jon Bosak. This group spelt out 10 ground rules for
XML, with ‘ease of
use’ as its fundamental philosophy. From thereon, the expectations reached a
threshold wherein,
XML was expected to eradicate world poverty and generally rid the world of all
its tribulations.
To be precise, XML was overvalued, way beyond realistic levels. There are people
who appear to
be extremely infatuated by XML, even though they may not have read through a
single rule or
specification of the language.
The specifications of XML laid down by its three primary authors- Tim Bray, Jean
Paoli and C. M.
Sperberg-McQueen, are accessible at the web site http://www.w3.org/XML.
XML documents consists entities comprising of Characters or Markups. An XML file
is made up of a
myriad components, which shall be unravelled one at a time, after we have
discerned the basic concepts of this language. We commence this chapter by introducing a program
that generates an
XML file.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main() {
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.Flush();
a.Close();
}
}
In this program, we use a class called XmlTextWriter, which comes from the
System.Xml namespace.
An instance ‘a’ of the XmlTextWriter class is created, by passing two parameters
to the
constructor:
• The first parameter, b.xml, is a string and represents the name of the file to
be
created. If the file exists in the current directory, it gets deleted and then
recreated, but
with zero bytes.
• The second parameter is null. It represents the Encoding type used.
Unicode is a standard whereby each character is assigned 16 bits. All the
languages in the
world can now be easily represented by this standard. In the .Net world, we are
furnished with
classes whose methods facilitate conversion of arrays and strings made up of
Unicode characters,
to and from arrays made up of bytes alone.
The System.Text namespace has a large number of Encoding implementations, such
as the following:
• The ASCII Encoding encodes the Unicode characters as 7-bit ASCII.
• The UTF8 Encoding class encodes Unicode characters using UTF-8 encoding.
UTF-8 stands for UCS Transformation Format 8 bit. It supports all Unicode
characters. It is
normally accessed as code page 65001. UTF-8 is the default value and represents
all the letters
from the English alphabet. Here, since we have specified the second parameter as
null, the
default value of UTF-8 encoding is taken.
If we execute the program at this stage, you would be amazed by the fact that no
file by the
name of b.xml will be displayed. To enable this to happen, a function named
Flush needs to be
called.
Each time we ask the class XmlTextWriter to write to a file, it may not oblige
immediately, but
may place the output in a buffer. Only when the buffer becomes full, will it
write to the file.
This approach is pursued to avoid the overhead of accessing the file on the disk
repetitively.
This improves efficiency. The Flush function flushes the buffer to the file
stream, but it does
not close the file.
The Close function has to be employed to execute the twin tasks of flushing the
buffer to the
file, and closing the file. It is sagacious to call Flush, and then call Close,
even though
Close is adequate to carry out both these tasks.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main() {
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
Here, we have called a function called WriteStartDocument from the XmlTextWriter
class, which
does not take any parameters. It produces the line <?xml version=”1.0″?>, in the
file b.xml.
Any line that begins with <?xml is called an XML declaration. Every entity in
XML is described
as a node. Every XML file must begin with an XML Declaration node. There can be
only one such
node in our XML file and it must be placed on the first line. Following it is an
attribute
called version, which is initialized to a value of 1.0.
The XML specifications lucidly stipulate that there would be no attribute called
version in the
next version of the software. Even if there is, its value would be
indeterminate. In other
words, in the foreseeable future, the only mandatory attribute would be
version=1.0.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.WriteDocType(“vijay”, null, null ,null);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?><!DOCTYPE vijay>
The next vital declaration is the DOCTYPE declaration. Every XML file must have
one DOCTYPE
declaration, as it specifies the root tag. In our case, the root tag would be ‘vijay’.
An XML file is made up of tags, which are words enclosed within angular
brackets. The file also
contains rules, which bind the tags. The next three parameters of the function
WriteDocType are
presently specified as null. You may refer to the documentation to decipher the
remaining
values, since these may be used in place of null. If this does not appeal to
you, you may have
to hold your horses, till we furnish the explanation at an appropriate time.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main() {
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
In the earlier example, all the nodes were displayed on the same line. We would
indubitably
desire that every node be displayed on a new line. The property Formatting in
XmlTextWriter, is
used to accomplish this task. Formatting can be assigned only one of the
following two values:
Indented or None. By default, the value assigned is None.
The Indented option indents the child elements by 2 spaces. The magnitude of the
indent may be
altered, by stipulating a new value for the Indentation field. In our program,
we want the
indent to be 3 spaces deep. Hence, we stipulate the value as 3. As is evident,
all nodes do not
get indented. For example, the DOCTYPE node does not get indented; instead, it
is placed on a
new line.
The IndentChar property may be supplied with the character that is to be
employed for
indentation. By default, a space character is used for this purpose.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay />
The function WriteStartElement accepts a single parameter, which is the tag
name, to be written
to the XML file. This is an oft-repeated instruction, to be iterated in almost
every program,
since an XML file basically comprises of tags. A tag normally has a start point
and an end
point, and it confines entities within these two extremities. However, there are
tags that do
not accept any entities. Such tags end with a / symbol.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteAttributeString (“wife”,”sonal”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay wife=”sonal” />
The newly added function WriteAttributeString accepts two parameters, which it
writes in the
form of a name-value pair. Thus, along with ‘vijay’, we see the attribute named
‘wife’, having a
value of ‘sonal’. An attribute is analogous to an adjective of the English
language, in that, it
describes the object. In our case, it describes the tag ‘vijay’. It divulges
additional
information about the properties of a tag.
XML does not interpret the contents of these tags. The word ‘wife’ or the value
‘sonal’, have no
special significance for XML, which is absolutely unconcerned about the
information provided
within the tags.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main() {
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteAttributeString (“wife”,”sonal”);
a.WriteElementString(“surname”, “mukhi”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay wife=”sonal”>
<surname>mukhi</surname>
</vijay>
An element represents entities within a tag. We have a tag surname containing
the value ‘mukhi’.
We can have multiple tags within the root tag.
We have been reiterating the fact that we need to adhere to specific rules. You
may steer clear
of the beaten path and interchange the following two newly added functions as
follows:
a.WriteElementString(“surname”, “mukhi”);
a.WriteAttributeString (“wife”,”sonal”);
As a fallout of this interchange, the following exception will be thrown:
Unhandled Exception: System.InvalidOperationException: Token StartAttribute in
state Content
would result in an invalid XML document.
This exception is triggered off due to the fact that the attribute must be
specified first.
Then, and only then, should the child tags within the tag, be specified.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteAttributeString (“wife”,”sonal”);
a.WriteAttributeString (“friend”,”two”);
a.WriteElementString(“surname”, “mukhi”);
a.WriteElementString(“books”, “67″);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay wife=”sonal” friend=”two”>
<surname>mukhi</surname>
<books>67</books>
</vijay>
To summarize, the WriteDocType function specifies the root tag, the
WriteStartElement the tag,
the WriteAttributeString, the attributes for the active tag and
WriteElementString function, a
tag within a tag. We can enumerate as many attributes as we desire. They will
eventually be
clustered together. The WriteElementString function is also capable of creating
as many tags, as
are needed under a tag.
In the file b.xml, we see two attributes and two tags, under the root tag ‘vijay’.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteAttributeString (“friend”,”two”);
a.WriteStartElement(“mukhi”);
a.WriteAttributeString (“wife”,”sonal”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay friend=”two”>
<mukhi wife=”sonal” />
</vijay>
In the above example, ‘vijay’ is the root tag, with the attribute ‘friend’,
which is assigned a
value of 2. It also has a child tag ‘mukhi’ having the attribute of ‘wife’
initialized to
‘sonal’. Both the tags, ‘vijay’ and ‘mukhi’, are created using the function
WriteStartElement.
Unlike function WriteElementString, which creates a start and end tag,
WriteStartElement creates
only a start tag.
A tag too can be endowed with attributes. The active tag is the last inserted by
the
WriteStartElement function. Functions such as WriteAttributeString, act on the
active tag. Thus,
we notice that the attribute of ‘wife’ has the tag ‘mukhi’ and not ‘vijay’.
Finally, since the
tag ‘mukhi’ is devoid of any contents, it ends with a / symbol on the same line.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteAttributeString (“friend”,”two”);
a.WriteStartElement(“mukhi”);
a.WriteAttributeString (“wife”,”sonal”);
a.WriteFullEndElement();
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay friend=”two”>
<mukhi wife=”sonal”>
</mukhi>
</vijay>
The function WriteFullEndElement marks the end of the active tag. Therefore, the
single tag
‘mukhi’, does not end with a / symbol on the same line. It has an ending tag
instead. Both these
possibilities are equally valid in this case. But, if the tags embody any
contents, then both
the start and the end tags are mandatory. In such situations, a single empty tag
would just not suffice.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
//a.WriteComment(“comment 1″);
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteStartDocument();
a.WriteComment(“comment 1″);
a.WriteDocType(“vijay”, null, null ,null);
a.WriteComment(“comment 2″);
a.WriteStartElement(“vijay”);
a.WriteAttributeString (“wife”,”sonal”);
a.WriteComment(“comment 3″);
a.WriteElementString(“surname”, “mukhi”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!–comment 1–>
<!DOCTYPE vijay>
&<!–comment 2–>
<vijay wife=”sonal”>
<!–comment 3–>
<surname>mukhi</surname>
</vijay>
Every programming language extends the facility of writing comments, even though
it may be a
seldom used feature. Programmers insert comments amidst their code to document
or explain the
functioning of their programs. At times, comments assist in deciphering the code
from the
programmer’s perspective. Practically, it may be easier to teach an elephant how
to tap-dance,
than to convince a programmer to write comments.
In the XML world, comments begin with <!-, and end with –>. This is somewhat
similar to the
HTML syntax. In fact, the rules of HTML are written in XML.
Comments are like a liquid, since they can be moulded to fit-in anywhere, except
on the first
line of a program. The first line in an XML file has to be a declaration. If you
dispense with
the comments given with the function WriteComment, an exception will be thrown
with the
following message:
Unhandled Exception: System.InvalidOperationException: WriteStartDocument should
be the first
call.
Thus, functions such as WriteComment, can be used to insert comments anywhere in
the code,
primarily for the purpose of documentation, which would enable even an alien
from outer space to
decipher the code better.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteProcessingInstruction (“sonal”, “mukhi=no”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay>
<?sonal mukhi=no?>
</vijay>
A line beginning with <?, Is called a Processing Instruction (PI). This line is
inserted using
the function WriteProcessingInstruction, and is passed two parameters:
• the first is the name of the processing instruction.
• the second is the text that is to be inserted for the processing instruction.
A Procession Instruction is used by XML to communicate with other programs
during the
performance of certain tasks. XML does not have the wherewithal to execute
instructions. It
therefore delegates this task to the XML processor. The processor is a program
that is able to
recognise an XML file. When it encounters the processing instruction, and if it
is able to
understand it, it executes it. In cases where it cannot comprehend it, the
processor simply
ignores the instruction. This is the methodology by which XML communicates with
external
programs.
In our program, the instruction ‘sonal’ is ignored, as it does not provide any
meaningful input
to the processor.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteString(“mukhi”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay>mukhi</vijay>
An XML file mainly consists of strings and tags. The WriteString function is
very extensively
exploited, since it writes content/strings between tags.
In the above example, the text ‘mukhi’ is enclosed within the tags of ‘vijay’.
Even though we
have not explicitly asked the XmlTextWriter class to close the tag, the ending
tag has been used
because there exists some content after the opening tag.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteAttributeString (“friend”,”two”);
a.WriteString(“hi”);
//a.WriteAttributeString (“friend”,”three”);
a.WriteStartElement(“mukhi”);
a.WriteAttributeString (“friend”,”two”);
a.WriteString(“bye”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay friend=”two”>hi<mukhi friend=”two”>bye</mukhi></vijay>
The function WriteString can be inserted almost anywhere in the program. The
first WriteString
function writes ‘hi’ between the tags of ‘vijay’, while the second WriteString
function writes
‘bye’ between the tags of ‘mukhi’. The WriteString is aware of the active tag.
Therefore, it
inserts the text accordingly. Here also, if we uncomment the line,
a.WriteAttributeString(“friend”,”three”), the following exception will be
generated.
Unhandled Exception: System.InvalidOperationException: Token StartAttribute in
state Content
would result in an invalid XML document.
XML is very strict and meticulous in the sense that, it expects a certain order
to be
maintained, or else, it throws an exception. For instance, an element or a tag
has to be created
first. Only then, can all the attributes be written; and finally, the text or
content has to be
supplied. We are not permitted to write the text first and enter the attributes
later. In the
XmlTextWriter class, there is no going back. It is a one-way path, which only
moves in the
forward direction.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteCharEntity (‘A’);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay>A</vijay>
During our exploratory journey of XML, we shall discuss a large number of
characters that are
‘reserved’. They have a special significance and cannot be used literally. These
Unicode
characters have to be written in a hex format. The function WriteCharEntity
performs this task.
It accepts a char or a Unicode character as a parameter and returns a number in
hex, prefaced
with the &# symbol.
For those who do not understand hexadecimal and consider it Greek and Latin, 41
hex is equal to
ASCII 65, which is the ASCII value for the capital letter A. You can pass
different characters
to this function and see their equivalent hex values.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteCData(“mukhi & <sonal>”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay><![CDATA[mukhi & <sonal>]]></vijay>
The above program introduces a new function called WriteCData, which creates a
node called
CDATA. The parameter passed to this function is placed as it is, but is enclosed
within square
brackets.
A CDATA section is used whenever we want to use characters such as <, >, & and
the likes, in
their literal sense, which would otherwise be mistaken for Markup characters.
Thus, in the above
program, the CDATA section that contains the symbol &, interprets it as the
literal character &,
and not as a special character. Also, <sonal> is not recognized as a tag in this
section. A
CDATA section cannot be nested within another CDATA section.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteString(“<A>&”);
a.WriteCData(“<A>&”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay><A>&
<![CDATA[<A>&]]>
</vijay>
This program illustrates certain characters that are special to XML. These are
the obvious
characters, such as <, > and &, since they are used whilst an XML file is being
created. Thus,
whenever XML comes across the following symbols, it replaces them with the
symbols depicted
against each:
• < is replaced with ‘<’
• > is replaced with ‘>’
• & is replaced with ‘&’.
If the same string that contains the above mentioned special characters is
placed within a CDATA
statement, gets written verbatim, without any conversions.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteEntityRef(“Hi”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay>&Hi;</vijay>
The entity ref is very straightforward to understand. The string passed to the
function
WriteEntityRef is placed in the XML file, preceded by a ‘&’ sign and followed by
a semi-colon.
An entity ref in XML is equivalent to a variable. It is included to provide
flexibility to the
program.
Thus in the above code, a variable called ‘hi’ is created. The task of stating
what ‘hi’
signifies, can be defined in the XML file.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteRaw(“<A>&”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay><A>&</vijay>
The WriteRaw function writes the characters passed to it, without carrying out
any conversions.
The above XML file is obviously erroneous, as no end tag has been specified for
the tag A. Also,
no name has been specified after the & sign.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
Boolean b = true;
a.WriteElementString(“Logical”, XmlConvert.ToString(b));
Int32 c = -2147483648;
a.WriteElementString(“SmallInt”, XmlConvert.ToString(c));
Int64 d = 9223372036854775807;
a.WriteElementString(“Largelong”, XmlConvert.ToString(d));
Single e = ((Single)22)/((Single)7);
a.WriteElementString(“Single”, XmlConvert.ToString(e));
Double f = 1.79769313486231570E+308;
a.WriteElementString(“Double”, XmlConvert.ToString(f));
DateTime h = new DateTime(2001, 07, 08 ,22, 0, 30, 500);
a.WriteElementString(“DateTime”, XmlConvert.ToString(h));
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay>
<Logical>true</Logical>
<SmallInt>-2147483648</SmallInt>
<Largelong>9223372036854775807</Largelong>
<Single>3.142857</Single>
<Double>1.7976931348623157E+308</Double>
<DateTime>2001-07-08T22:00:30.5000000+05:30</DateTime>
</vijay>
The above example contains a plethora of data types such as, boolean, int,
double and Data Time.
The XmlConvert class has a large number of static functions that help us convert
one data type
to another. One such function is the ToString function. For types such as int or
long, the
smallest and the largest values are used, in order to check the veracity of the
ToString
function.
The ToString function is overloaded to handle many more data types than we have
shown. The point
here is that, it is possible for us to convert any data type into a string and
write it to disk.
This factor gains immense importance when data is being received from a
database, and requires
to be converted into a string in an XML file.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteStartAttribute(“hi”, “mukhi”, “xxx:yyy”);
a.WriteString(“1-861003-78″);
a.WriteEndAttribute();
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay hi:mukhi=”1-861003-78″ xmlns:hi=”xxx:yyy” />
In the above example, we have introduced the WriteStartAttribute function. As is
apparent from
its name, it starts an attribute. The first parameter to this function is ‘hi’,
which is the
namespace, to which the prefix of the attribute belongs. The second parameter
‘mukhi’ is the
name of the attribute.
The names assigned to attributes and tags may not always result in a unique
name. A programmer
may inadvertently create a tag or an attribute with a name that already exists.
How then does
XML decide what the tag denotes?
To help resolve such potential conflicts, each tag or entity is prefaced with a
name known as
the namespace. This is followed by a colon sign. Normally, meaningful names are
assigned, rather
than words like ‘hi’. Prefixes or namespaces like xmlns, are reserved by XML.
The concept of
namespaces in XML is identical to the concept of namespaces in C#.
The third parameter is a Uniform Resource Identifier (URI). This parameter
reveals greater
details about the location of the namespace. It informs XML that somewhere
within the document,
additional information about the namespace ‘hi’ is available. In this case it is
at xxx:yyy. As
the WriteStartAttribute function does not specify any value for the attribute,
the WriteString
function is employed to assign the value 1-861003-78, to the attribute ‘mukhi’
in the namespace
‘hi’.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main() {
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteAttributeString(“xmlns”, “bk”, null, “sonal:wife”);
string p = a.LookupPrefix(“sonal:wife”);
a.WriteStartAttribute(p, “mukhi”, “sonal:wife”);
a.WriteString(“sonal”);
a.WriteEndAttribute();
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay xmlns:bk=”sonal:wife” bk:mukhi=”sonal” />
Here, the function WriteAttributeString is called with four parameters. The
first, as always, is
the name of the namespace, i.e. xmlns. The second is the name of the attribute
i.e. bk, which is
suffixed to the name of the namespace, as xmlns:bk. The third parameter is the
namespace URI. In
the earlier program, we had specified the value of xxx:yyy for the URI. For this
program, since
the namespace xmlns is a reserved namespace, the URI parameter is specified as
null. The last
parameter is the value of the attribute.
As a consequence, the above function takes the form of an attribute consisting
of
xmlns:bk=sonal:wife. The next function LookupPrefix, accepts a namespace URI and
returns the
prefix. As the parameter supplied to this function is sonal:wife, the prefix
returned is bk,
which is stored in p.
The WriteStartAttribute then uses the following:
• ‘bk’ as the namespace,
• ‘mukhi’ as the name of the attribute, and
• ‘sonal:wife’ as the namespace URI.
Thus, the attribute ‘mukhi’ is prefaced with the namespace ‘bk’. Finally, the
WriteString
function assigns the value of ‘sonal’ to the attribute bk:mukhi.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteAttributeString(“xmlns”, “bk”, null, “sonal:wife”);
a.WriteAttributeString(“jjj”, “bk”, “kkk”, “sonal:wife”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay xmlns:bk=”sonal:wife” jjj:bk=”sonal:wife” xmlns:jjj=”kkk” />
In this version of the WriteAttributeString function, the namespace is jjj and
the attribute
name is bk, with the value sonal:wife. Thus, the attribute becomes
jjj:bk=sonal:wife. The third
parameter to the function is the namespace URI, which is now assigned a value of
kkk, instead of
null.
Thus, one more attribute xmlns:jjj gets added, which indicates that the
namespace URI is kkk. We
notice that this attribute does not get added for the xmlns namespace. We have
chosen the
attribute name ‘bk’ again, just to demonstrate that they belong to different
namespaces.
Therefore, this bk is considered to be a different attribute from the earlier
bk.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteStartAttribute(null,”sonal”, null);
a.WriteQualifiedName(“mukhi”, “http://vijaymukhi.com”);
a.WriteEndAttribute();
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay sonal=”n1:mukhi” xmlns:n1=”http://vijaymukhi.com” />
In the WriteStartAttribute function, only the second parameter out of the three
parameters, has
a value ‘sonal, which is the name of the attribute. The first parameter, which
is the name of
the namespace and the third parameter, which is the URI of the namespace, are
both assigned null
values.
The next function, WriteQualifiedName assigns a value to the attribute ‘sonal’.
This function
takes two parameters, the value ‘mukhi’ and the namespace URI for the value.
The value ‘mukhi’ gets prefaced by a namespace n1, which is created dynamically
by XML. The name
n1 belongs to the reserved xmlns namespace and the URI to n1 is specified in the
second
parameter, http://vijaymukhi.com. The method WriteQualifiedName, then looks up
the prefix within
the scope for the given namespace.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.WriteStartElement(“vijay”);
a.WriteAttributeString(“xmlns”,”mukhi”,null,”xxx:yyy”);
a.WriteString(“Hi “);
a.WriteQualifiedName(“sonal”,”xxx:yyy”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<vijay xmlns:mukhi=”xxx:yyy”>Hi mukhi:sonal</vijay>
In this example, we first create an attribute ‘mukhi’ in the reserved namespace
xmlns. This
attribute is then rendered a value of xxx:yyy. The WriteString function writes
‘Hi’ as the
content and then, the WriteQualifiedName writes the string ‘sonal’. However,
since ‘sonal’ is a
Qualified name, it is prefaced by ‘mukhi’ and not by xxx:yyy, because ‘mukhi’ is
equated to
xxx:yyy.
The prefix in the scope for the namespace is given precedence.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main() {
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.WriteStartElement(“vijay”);
a.WriteElementString(“vijay”,”mukhi”);
a.WriteElementString(“vijay”,”sonal”,”mukhi”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<vijay>
<vijay>mukhi</vijay>
<vijay xmlns=”sonal”>mukhi</vijay>
</vijay>
As we have just observed, the WriteElementString function had only two
parameters in the earlier
program. However, here it has three parameters. The first and the third
parameters are the same,
i.e. the tag name and the value. The newly inducted second parameter indicates
the namespace
‘sonal’. The tag in the first parameter ‘vijay’, has the namespace of sonal.
Thus, the XML file
contains the tag with the attribute of xmlns=sonal.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main() {
XmlTextWriter a = new XmlTextWriter (Console.Out);
a.WriteStartDocument();
a.WriteStartElement(“vijay”);
a.Close();
}
}
Output
<?xml version=”1.0″ encoding=”IBM437″?><vijay />
The XmlTextWriter class can write to different entities, using the constructor
that accepts a
single parameter. The Console class has a static property out of datatype
TextWriter that
represents the console. Thus, the output is now displayed on the console. By
default, the
encoding attribute is assigned a value of IBM437.
One of the primary reasons for designing XML was to introduce validation of the
tags in order to
produce a well-evolved XML file.
There are a few validations that need to be performed in an XML file, such as:
• It should be ensured that the basic rules of XML as well as our indigenous
rules are
followed.
• Certain tags should be placed only within specified tags and cannot be used
independently.
• The number of times a tag is being used can be regulated, since it cannot be
used
infinite times.
• A check should be placed on the name and the number of times an attribute is
used within
a tag.
All such rules that need to be enforced are enunciated in XML parlance and then,
placed in a DTD
or a Document Type Description. The DTD may either be placed in a separate file
or may be made
part of the DOCTYPE declaration. In the XML file shown below, the DTD is
internal.
Thus, a DTD stores the grammar that is permissible in an XML file. The entity
refs are also
defined in a DTD. One of the reasons why HTML is also reffered to as XHTML is
that, the rules of
well-formed html are available in the form of a DTD.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
String s = “<!ELEMENT vijay (#PCDATA)>”;
a.WriteDocType(“vijay”, null, null, s);
a.WriteStartElement(“vijay”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay[<!ELEMENT vijay (#PCDATA)>]>
<vijay />
The WriteDocType function accepts four parameters. The first parameter is the
starting or root
tag ‘vijay’. Hence, it must contain a value. The last parameter is the subset
(as referred to by
the documentation), which follows the root tag ‘vijay’. If you observe the
DOCTYPE statement
carefully, you will notice that an extra pair of square brackets [], have been
added.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.WriteDocType(“vijay”, null, “a.dtd”, null);
a.WriteStartElement(“vijay”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay SYSTEM “a.dtd”>
<vijay />
The third parameter to WriteDocType function specifies the name of the DTD file.
In other words,
it states the URI of the DTD. The second parameter is assigned the value of
null. Hence, the
word SYSTEM is displayed before the name of the file, in the XML file.
Whenever XML wishes to ensure the validity of an XML file, it ascertains the
rules from a.dtd.
If both internal and external DTDs are present, both of them are checked.
However, the internal
DTD is accorded priority.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.WriteDocType(“vijay”, “mmm”, “a.dtd”, null);
a.WriteStartElement(“vijay”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay PUBLIC “mmm” “a.dtd”>
<vijay />
In the earlier program, SYSTEM was added in the XML file, since the second
parameter had been
specified as null. However, in this program, the second parameter is not null.
Hence, the word
PUBLIC gets added. Thereafter, the string or the id specified in the second
parameter is added.
And then, the dtd in the third parameter is specified.
Therefore, it is either the PUBLIC identifier or the SYSTEM identifier, which
would be present.
The XML program or the processor scanning the XML file, uses the PUBLIC
identifier to retrieve
the content for the entities that use the URI. If it fails, it falls back upon
to the SYSTEM
literal.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main() {
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument(false);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″ standalone=”no”?>
The WriteStartDocument can take a boolean parameter that adds an attribute which
could either be
‘standalone = yes’ or ‘standalone=no’, depending upon the value specified. This
attribute
determines whether the DTD is in an external file or it is internal to the XML
file. If the
standalone has a value of ‘yes’, it is suggestive of the fact that there is no
external DTD, and
therefore, all the grammatical rules have to be placed within the XML file
itself.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main() {
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
System.Console.WriteLine(a.WriteState);
a.WriteStartDocument();
System.Console.WriteLine(a.WriteState);
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
System.Console.WriteLine(a.WriteState);
a.WriteStartElement(“vijay”);
System.Console.WriteLine(a.WriteState);
a.WriteAttributeString (“wife”,”sonal”);
System.Console.WriteLine(a.WriteState);
a.WriteStartAttribute(“hi”, “mukhi”, “xxx:yyy”);
System.Console.WriteLine(a.WriteState);
a.WriteString(“1-861003-78″);
a.WriteElementString(“surname”, “mukhi”);
a.Flush();
System.Console.WriteLine(a.WriteState);
a.Close();
System.Console.WriteLine(a.WriteState);
}
}
Output
Start
Prolog
Prolog
Element
Element
Attribute
Content
Closed
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay wife=”sonal” hi:mukhi=”1-861003-78″ xmlns:hi=”xxx:yyy”>
<surname>mukhi</surname>
</vijay>
The XmlTextWriter object can be in any one of six different states. The
WriteState property
reveals its current state. When an XmlTextWriter Object is created, it is in the
Start state, as
may be evident from the fact that, no write method has been called so far. After
the Close
function, the Writer is in the Closed state. When the WriteStartDocument and
WriteDocType
functions are called, they reach the Prolog state, because the prolog is being
written.
The WriteStartElement function actually starts writing to the XML file, thereby,
morphing to the
Element state. The element start tag ‘vijay’ begins the XML file. The next
function
WriteAttributeString does not change the state, since the element in focus still
is ‘vijay’. The
WriteStartAttribute function needs the WriteString to complete the attribute.
Thus, after the
WriteStartAttribute function executes, the Text Writer assumes the Attribute
mode. The surname
attribute becomes the content in the XML file. Hence, the state changes to
Content mode.
This goes on to prove that the TextWriter can possibly be in any one of the
above six states,
depending upon the entities written to the file. While the TextWrtier is in the
Attribute state,
it cannot switch to an element state to write an element. Therefore, it throws
an exception.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.Namespaces = false;
a.WriteStartDocument();
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteAttributeString(“jjj”, “bk”, “kkk”, “sonal:wife”);
a.Flush();
a.Close();
}
}
Output
Unhandled Exception: System.ArgumentException: Cannot set the namespace if
Namespaces is
‘false’.
at System.Xml.XmlTextWriter.WriteStartAttribute(String prefix, String localName,
String ns)
at System.Xml.XmlWriter.WriteAttributeString(String prefix, String localName,
String ns, String
value)
at zzz.Main()
The TextWriter class has a Namespaces property that is read-write, and it has a
default value of
true. The Namespace property is turned off, by setting this property to false.
The above runtime
exception is thrown because, we have attempted to introduce a namespace jjj, in
the
WriteAttributeString function.
a.cs
using System;
using System.Xml;
public class zzz {
public static void Main() {
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.QuoteChar = ‘\”;
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteAttributeString(“jjj”, “bk”);
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay jjj=’bk’ />
Various facets of XML can be modified. By using the property QuoteChar, we can
modify the
default quoting character, from double inverted commas to single inverted
commas. Since a single
quote cannot be enclosed within a set of single quotes, we use the backslash to
escape it. All
attributes can now be placed in single quotes instead of double quotes.
a.cs
using System;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextWriter a = new XmlTextWriter (“b.xml”, null);
a.WriteStartDocument();
a.Formatting = Formatting.Indented;
a.Indentation = 3;
a.WriteDocType(“vijay”, null, null ,null);
a.WriteStartElement(“vijay”);
a.WriteStartAttribute(“hi”, “mukhi”, “xxx:yyy”);
a.WriteString(“1-861003-78″);
a.WriteEndAttribute();
a.WriteEndElement();
a.WriteEndDocument();
a.Flush();
a.Close();
}
}
b.xml
<?xml version=”1.0″?>
<!DOCTYPE vijay>
<vijay hi:mukhi=”1-861003-78″ xmlns:hi=”xxx:yyy” />
Good programming style necessitates every ‘open’ to have a corresponding
‘close’. Thus, the
Begin functions for an Element, Attribute and Document have corresponding Close
functions too.
However, if we do not End them, they close by default and no major calamity
befalls them. We are
using them in the above program as an abandon caution.
The WriteEndDocument function puts the Text Writer in the Start mode.
Reading an XML file
b.xml
<?xml version=”1.0″ standalone=”yes”?>
<!DOCTYPE vijay SYSTEM “a.dtd” [<!ENTITY baby "No">]>
<vijay aa=”no”>
<!–comment 2–><?sonal mukhi=no?>
Hi&baby;
<![CDATA[,mukhi>]]><aa>bb</aa>
</vijay>
> copy con a.dtd
Enter
^Z
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static void Main() {
XmlTextReader r;
r = new XmlTextReader(“b.xml”);
while (r.Read())
{
Console.Write(“{0} D={1} L={2} P={3} “, r.NodeType, r.Depth, r.LineNumber,
r.LinePosition );
Console.Write(” name={0} value={1} AC={2}”,r.Name,r.Value,r.AttributeCount);
Console.WriteLine();
}
}
}
Output
XmlDeclaration D=0 L=1 P=3 name=xml value=version=”1.0″ standalone=”yes” AC=2
Whitespace D=0 L=1 P=39 name= value= AC=0
DocumentType D=0 L=2 P=11 name=vijay value=<!ENTITY baby “No”> AC=1
Whitespace D=0 L=2 P=54 name= value= AC=0
Element D=0 L=3 P=2 name=vijay value= AC=1
Whitespace D=1 L=3 P=16 name= value= AC=0
Comment D=1 L=4 P=5 name= value=comment 2 AC=0
ProcessingInstruction D=1 L=4 P=19 name=sonal value=mukhi=no AC=0
Text D=1 L=4 P=36 name= value=Hi AC=0
EntityReference D=1 L=5 P=4 name=baby value= AC=0
Whitespace D=1 L=5 P=9 name= value= AC=0
CDATA D=1 L=6 P=10 name= value=,mukhi> AC=0
Element D=1 L=6 P=21 name=aa value= AC=0
Text D=2 L=6 P=24 name= value=bb AC=0
EndElement D=1 L=6 P=28 name=aa value= AC=0
Whitespace D=1 L=6 P=31 name= value= AC=0
EndElement D=0 L=7 P=3 name=vijay value= AC=0
In this program, we read an XML file and display all the nodes contained
therein. To avoid any
errors from being displayed, you should create an empty file by the name of
a.dtd.
We have a class called XmlTextReader that accepts a filename as a parameter. We
pass the
filename b.xml to it. This file contains most of the entities present in an XML
file. The Read
function in this class picks up a single node or XML entity at a time. It
returns true, if there
are more nodes to be read, or else, it returns false. Thus, when there are no
more nodes to be
read from the file, the while loop ends. The Read function scans the active node
and displays
its contents in the loop.
The NodeType property displays the name of the nodetype. As an XML file normally
starts with a
declaration, the NodeType property displays the NodeType as XMLDeclaration,
using the ToString
function.
The Depth property gets incremented by one, every time an element or a tag is
encountered. At
the Declaration statement, the depth is 0. At the EndElement or at the end of
the tag, its value
reduces by one. Thus, the Depth property reveals the number of open tags in the
file and it can
be used for indentation.
The Line Number indicates the line on which the statement is positioned, while
the LinePosition
property displays the position on the line at which the statement begins. The
Name property in
the class reveals the name of the tag, XML. The output displayed by this
property depends upon
the active node type. On acute observation, you shall notice that the word XML
is not preceded
by the symbol <? in the output.
The value property relates to the name property, in this case, to
XmlDeclaration. It displays
the entire gamut of attributes to the node. As there exist two attributes,
version and
standalone, the property AttributeCount displays a value of 2.
If the enter key is pressed after the node declaration, it is interpreted as a
Whitespace
character. Whitespace characters are separators, which could consist of an
enter, space et al.
The Position property specifies the character position as 39.
The XmlDeclaration has to be the first node in an XML file, and it cannot have
any children. The
DOCTYPE declaration, which is known as a DocumentType Node, displays the name as
vijay, which is
the root node. The value is displayed as <!ENTITY baby “No”>, which includes
everything except
the SYSTEM and a.dtd. Thus, in the case of a DocumentType Node, value is the
internal DTD.
We shall encounter the Whitespace Node very frequently. Hence, we shall not
discuss it
hereinafter. The Attribute Count will be displayed in the next program. This
node can have the
Notation and Entity as child nodes.
The next node in sequence is our very first element or tag ‘vijay’, which is the
same value that
was displayed earlier, with the name property for the DocumentType Node. The
Value property for
this element shows null, since tags are devoid of Values. Instead, they have
Attributes.
The attribute Count displays a value of one. At the following Whitespace node,
the Depth
property gets incremented by one. This is the only way to ascertain whether we
are at the root
node or not. We now stumble upon a comment, which has no name. The value
displayed is the value
of the comment. And yet again, the <!-characters are not displayed along with
the value.
Thereafter, a processing instruction (PI) is encountered. No whitespace is
displayed between the
comment and the PI, since we have not pressed the Enter key. ‘Sonal’ becomes the
name of the
program that runs ‘vijay’. The rest turns into the value property having no
attributes. TextNode
is displayed next because the text ‘Hi’ is displayed in the XML file. This node
too is not
assigned any name and the value is depicted as ‘Hi’.
What follows the text is an Entity Reference. It is assigned the name ‘baby’ and
is devoid of
the ampersand sign. Its value is null and it does not have any attributes. The
CDATA section is
given the name as null. The value is assigned the content of the CDATA, after
stripping away the
square brackets.
The value of the Depth property is incremented by 1. The Text Node follows the
element aa. This
node does not have any name and it displays the value as ‘bb’. In the following
program, we
explore the various attributes.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextReader r;
r = new XmlTextReader(“b.xml”);
r.WhitespaceHandling = WhitespaceHandling.None;
while (r.Read())
{
Console.Write(“{0} D={1} L={2}
P={3}”,r.NodeType,r.Depth,r.LineNumber,r.LinePosition);
Console.Write(” name={0} value={1} AC={2}”,r.Name,r.Value,r.AttributeCount);
Console.WriteLine();
if (r.HasAttributes)
{
for ( int i =0; i < r.AttributeCount; i++)
{
r.MoveToAttribute(i);
System.Console.WriteLine(“Att {0}={1}”,r.Name,r[i]);
}
}
}
}
}
Output
XmlDeclaration D=0 L=1 P=3 name=xml value=version=”1.0″ standalone=”yes” AC=2
Att version=1.0
Att standalone=yes
DocumentType D=0 L=2 P=11 name=vijay value=<!ENTITY baby “No”> AC=1
Att SYSTEM=a.dtd
Element D=0 L=3 P=2 name=vijay value= AC=1
Att aa=no
Comment D=1 L=4 P=5 name= value=comment 2 AC=0
ProcessingInstruction D=1 L=4 P=19 name=sonal value=mukhi=no AC=0
Text D=1 L=4 P=36 name= value=
Hi AC=0
EntityReference D=1 L=5 P=4 name=baby value= AC=0
CDATA D=1 L=6 P=10 name= value=,mukhi> AC=0
Element D=1 L=6 P=21 name=aa value= AC=0
Text D=2 L=6 P=24 name= value=bb AC=0
EndElement D=1 L=6 P=28 name=aa value= AC=0
EndElement D=0 L=7 P=3 name=vijay value= AC=0
A property called WhiteSpaceHandling is initialized to None, as a result of
which, the node
Whitespace is not visible in the output.
The XmlTextReader has a member HasAttributes, which returns a True value if the
node has
attributes and False otherwise. Alternatively, we could also have used the
property
AttributeCount to obtain the number of attributes that the node contains.
If the node has attributes, a ‘for statement’ is used to display all of them. In
the loop, we
first use the function MoveToAttribute to initially activate the attribute. This
is achieved by
passing the number as a parameter to the function. Bear in mind that the index
starts from Zero
and not One.
Thereafter, the Name property is used to display the name of the attribute. If
the attribute is
not activated, the Name property displays the name of the node. This explains
the significance
of the MoveToAttribute function.
As you would recall, the XmlTextReader class has an indexer for the attributes,
and like all
indexers, it is zero based, i.e. r[0] accesses the value of the first attribute.
This is how we
display the details of all attributes of the node.
For the node DOCTYPE, the SYSTEM becomes the name of the attribute and the value
becomes the
name of the DTD file. For an element, the attributes are specified in name-value
pairs.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static XmlTextReader r;
public static void Main()
{
r = new XmlTextReader(“b.xml”);
int declaration=0, pi=0, doc=0, comment=0, element=0, attribute=0, text=0,
whitespace=0,cdata=0,endelement=0,
entityr=0,entitye=0,entity=0,swhitespace=0,notation=0;
while (r.Read())
{
Console.Write(“{0} D={1} L={2}
P={3}”,r.NodeType,r.Depth,r.LineNumber,r.LinePosition);
Console.Write(” name={0} value={1} AC={2}”,r.Name,r.Value,r.AttributeCount);
Console.WriteLine();
if (r.HasAttributes)
{
for ( int i =0; i < r.AttributeCount; i++)
{
r.MoveToAttribute(i);
System.Console.WriteLine(“Att {0}={1}”,r.Name,r[i]);
}
}
switch (r.NodeType)
{
case XmlNodeType.XmlDeclaration:
declaration++;
break;
case XmlNodeType.ProcessingInstruction:
pi++;
break;
case XmlNodeType.DocumentType:
doc++;
break;
case XmlNodeType.Comment:
comment++;
break;
case XmlNodeType.Element:
element++;
if (r.HasAttributes)
attribute += r.AttributeCount;
break;
case XmlNodeType.Text:
text++;
break;
case XmlNodeType.CDATA:
cdata++;
break;
case XmlNodeType.EndElement:
endelement++;
break;
case XmlNodeType.EntityReference:
entityr++;
break;
case XmlNodeType.EndEntity:
entitye++;
break;
case XmlNodeType.Notation:
notation++;
break;
case XmlNodeType.Entity:
entity++;
break;
case XmlNodeType.SignificantWhitespace:
swhitespace++;
break;
case XmlNodeType.Whitespace:
whitespace++;
break;
}
}
Console.WriteLine ();
Console.WriteLine(“XmlDeclaration: {0}”,declaration);
Console.WriteLine(“ProcessingInstruction: {0}”,pi);
Console.WriteLine(“DocumentType: {0}”,doc);
Console.WriteLine(“Comment: {0}”,comment);
Console.WriteLine(“Element: {0}”,element);
Console.WriteLine(“Attribute: {0}”,attribute);
Console.WriteLine(“Text: {0}”,text);
Console.WriteLine(“Cdata: {0}”,cdata);
Console.WriteLine(“EndElement: {0}”,endelement);
Console.WriteLine(“Entity Reference: {0}”,entityr);
Console.WriteLine(“End Entity: {0}”,entitye);
Console.WriteLine(“Entity: {0}”,entity);
Console.WriteLine(“Whitespace: {0}”,whitespace);
Console.WriteLine(“Notation: {0}”,notation);
Console.WriteLine(“Significant Whitespace: {0}”,swhitespace);
}
}
Output
XmlDeclaration D=0 L=1 P=3 name=xml value=version=”1.0″ standalone=”yes” AC=2
Att version=1.0
Att standalone=yes
Whitespace D=0 L=1 P=39 name= value=
AC=0
DocumentType D=0 L=2 P=11 name=vijay value=<!ENTITY baby “No”> AC=1
Att SYSTEM=a.dtd
Whitespace D=0 L=2 P=54 name= value=
AC=0
Element D=0 L=3 P=2 name=vijay value= AC=1
Att aa=no
Whitespace D=1 L=3 P=16 name= value=
AC=0
Comment D=1 L=4 P=5 name= value=comment 2 AC=0
ProcessingInstruction D=1 L=4 P=19 name=sonal value=mukhi=no AC=0
Text D=1 L=4 P=36 name= value=
Hi AC=0
EntityReference D=1 L=5 P=4 name=baby value= AC=0
Whitespace D=1 L=5 P=9 name= value=
AC=0
CDATA D=1 L=6 P=10 name= value=,mukhi> AC=0
Element D=1 L=6 P=21 name=aa value= AC=0
Text D=2 L=6 P=24 name= value=bb AC=0
EndElement D=1 L=6 P=28 name=aa value= AC=0
Whitespace D=1 L=6 P=31 name= value=
AC=0
EndElement D=0 L=7 P=3 name=vijay value= AC=0
Whitespace D=0 L=7 P=9 name= value=
AC=0
XmlDeclaration: 0
ProcessingInstruction: 1
DocumentType: 0
Comment: 1
Element: 1
Attribute: 0
Text: 2
Cdata: 1
EndElement: 2
Entity Reference: 1
End Entity: 0
Entity: 0
Whitespace: 6
Notation: 0
Significant Whitespace: 0
The above program is a continuation from where we left off in the previous
program. The initial
portion of the code is identical. A colossal case statement is introduced in the
program to
check the NodeType.
For each Node Type, there is a corresponding variable, whose value is
incremented by 1 whenever
the Node Type matches. Then, the values contained in these variables are
displayed. For
inexplicable reasons, the NodeType property does not return the following node
types – Document,
DocumentFragment, Entity, EndEntity, or Notation.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextReader r = new XmlTextReader(“b.xml”);
r.WhitespaceHandling = WhitespaceHandling.None;
while (r.Read())
{
if (r.HasValue)
Console.WriteLine(“{0} {1}={2}”, r.NodeType, r.Name, r.Value);
else
Console.WriteLine(“{0} {1}”, r.NodeType, r.Name);
}
}
}
Output
XmlDeclaration xml=version=”1.0″ standalone=”yes”
DocumentType vijay=<!ENTITY baby “No”>
Element vijay
Comment =comment 2
ProcessingInstruction sonal=mukhi=no
Text =
Hi
EntityReference baby
CDATA =,mukhi>
Element aa
Text =bb
EndElement aa
EndElement vijay
The HasValue property simply identifies whether a Node can contain a value or
not. There are
nine nodes that can possess values. These nodes are Attribute, CDATA, Comment,
DocumentType,
ProcessingInstruction, Significant Whitespace, Whitespace, Text and
XmlDeclaration. All the
above nodes must have a value, but they need not necessarily have a name.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static void Main()
{
XmlTextReader r = new XmlTextReader(“b.xml”);
r.MoveToContent();
string s = r["mukhi"];
Console.WriteLine(s);
s = r.GetAttribute(“sonal”);
Console.WriteLine(s);
s = r[2];
Console.WriteLine(s);
}
}
b.xml
<vijay mukhi=”no” sonal=”yes” aaa=”bad” />
Output
no
yes
bad
The MoveToContent function moves to the first element in the XML file.
In this program, we display the attributes using different methods. In the first
approach, the
indexer is passed a string, which is the name of the attribute ‘mukhi’. It
receives ‘no’ as the
return value.
In the second approach, the indexer is passed the integer value 2 as a
parameter, to access the
value of the third attribute, which is ‘bad’.
Alternatively, the WriteAttribue function could have been given the string
‘sonal’ as a
parameter, to return the value of the attribute as ‘yes’. Thus, there are
multiple means to
achieving the same objective.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz
{
public static void Main() {
XmlTextReader r = new XmlTextReader(“b.xml”);
r.MoveToContent();
string s ;
s = r.GetAttribute(“aa:bb”);
Console.WriteLine(s);
s = r.GetAttribute(“bb”);
Console.WriteLine(s);
s = r.GetAttribute(“bb”,”sonal:mukhi”);
Console.WriteLine(s);
s = r.GetAttribute(“bb”,”sonal:mukhi”);
Console.WriteLine(s);
s = r.GetAttribute(“bb”,”aa”);
Console.WriteLine(s);
s = r.GetAttribute(“xmlns:aa”);
Console.WriteLine(s);
}
}
b.xml
<vijay xmlns:aa=”sonal:mukhi” aa:bb=”no” />
Output
no
no
no
sonal:mukhi
The MoveToContent function is used in this program, instead of the Read
function. In the file
b.xml, we have an attribute bb in the namespace aa. It is initialized to a value
of ‘no’. The
namespace aa has a URI, sonal:mukhi, because of the xmlns declaration. Thus, the
full name of
the attribute becomes aa:bb i.e. prefix, followed by the colon, followed by the
actual name. As
a result, specifying aa:bb results in the display of ‘no’, but only specifying
bb as a parameter
to GetAttribute results in a null value.
The full name of an attribute includes the name of the namespace too. So, we can
use the second
form of the GetAttribute function that has an overload of two parameters, where
the second
parameter is the name of the URI and not the namespace. Hence, it is acceptable
to call the
function with the URI sonal:mukhi, but if we use the namespace aa, no output
will be produced.
The last GetAttribute utilizes the full name xmlns:aa to retrieve the URI for
the element. Thus,
we can use this variant of the GetAttribute function with the URI instead of the
namespace:name.
a.cs
using System;
using System.IO;
using System.Xml;
public class zzz {
public static void Main() {
XmlTextReader r = new XmlTextReader(“b.xml”);
r.WhitespaceHandling=WhitespaceHandling.None;
r.MoveToContent();
r.MoveToAttribute(“cc”);
Console.WriteLine(r.Name + ” ” + r.Value);
Console.WriteLine(r.ReadAttributeValue());
Console.WriteLine(r.Name + ” ” + r.Value);
}
}
b.xml
<vijay aa=”hi” bb=”bye” cc=”no” />
Output
cc no
True
No
In this example, we directly focus on the attribute that we are interested in,
i.e. cc. The name
and value properties in XMLTextReader display ‘cc’ and ‘no’ respectively. As
there are numerous
attributes of the node remaining to be read, the ReadAttribute function returns
True. This
function is normally used to read text or entity reference nodes that constitute
the value of
the attribute.
The Name property of the XmlTextReader however becomes null after the function
ReadAttributeValue is called.
C Language Help, C Language Tutorials, C Language Programming, C Language Tricks { The C# Language }
C Language Help, C Language Tutorials, C Language Programming, C Language Tricks
Printing
One facility that every programmer in the world would want to provide through
his program is that of printing. The .NET world has bestowed about ten important
printer classes that ease our job of printing text and graphics to the printer.
We shall grapple with some small programs to grasp the concepts of printing and
conclude this chapter with a small word processor program that is provided along
with the .NET samples. You should create a text file named a.txt in the current
subdirectory, and thereafter, insert the following three lines:
a.txt
hi, how are you
bye, take care
end
a.cs
using System;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
public class zzz {
Font f;
StreamReader sr;
public void abc()
{
sr = new StreamReader (“a.txt”);
f= new Font(“Courier New”, 14);
PrintDocument pd = new PrintDocument();
pd.PrintPage += new PrintPageEventHandler(pqr);
pd.Print();
}
void pqr(object o, PrintPageEventArgs e)
{
float lpp = e.MarginBounds.Height / f.GetHeight(e.Graphics) ;
int c = 0 ;
String s=null;
while (c < lpp && ((s=sr.ReadLine()) != null))
{
float y = e.MarginBounds.Top + (c * f.GetHeight(e.Graphics));
e.Graphics.DrawString (s, f, Brushes.Black, e.MarginBounds.Left, y);
c++;
}
if (s != null)
e.HasMorePages = true ;
else
e.HasMorePages = false ;
}
public static void Main() {
zzz a = new zzz();
a.abc();
}
}
The above program prints the contents of the text file a.txt to the default
printer. In all our programs in this chapter, we shall create an object that is
an instance of class zzz and call function abc off it. The code within this
function will execute the actual task of printing.
We first create an object, sr, which is an instance of the class StreamReader.
The constructor of this class is furnished with the name of the file that we
want to print, i.e. a.txt.
The StreamReader class is derived from the class TextReader, which reads
characters from a stream of bytes that have been encoded in a particular format.
The TextReader class belongs to the System.IO namespace. The Stream class too
forms a part of the same genre. This class is designed for reading byte streams.
The StreamReader class is optimised for reading lines of data from an ASCII or a
text file. By default, the encoding used is UTF-8, but it can be changed to any
other encoding format, if required. The UTF-8 encoding can comprehend Unicode
characters. The StreamReader class, by default, is not thread safe, and neither
does it belong to the Printing classes in the .NET family. The class is used in
this program to read the file, one line at a time.
We want the printer to print our text file in a specific font. So, we create a
Font object f, whose constructor is passed the Font Name ‘Courier New’ along
with the size of ’14 points’. We have plenty of choice of fonts from the
innumerable fonts available. For the neophytes of the publishing world, it would
be a revelation that 72 points makes an inch. If we specify a font name such as
‘Vijay Mukhi’, which does not exist, the printer uses the default font instead,
which in this case, is Microsoft Sans Serif. Thus, the printer always receives a
valid font type to work with.
We create one more object called pd, in our program, which is an instance of
class PrintDocument. This class is the nucleus of all printing activities in the
.NET world. It has an event called PrintPage, which requires an instance of a
delegate PrintPageEventHandler. The delegate represents a method called pqr,
which handles the PrintPage event. This event is generated by the Print
function.
Thus, when we call the Print function, it in turn, calls the pqr function. This
function is passed two parameters. The second parameter is a PrintPageEventArgs
class, which carries data that is useful for printing. The PrintPageEventArgs
class is derived from class EventArgs. The pqr function is not called
explicitly, since this would entail creation of an object like
PrintPageEventArgs.
The Print function is a blocking function. This implies that not a single line
of code will be executed after the Print function has been called, until the pqr
function completes execution. If the pqr function goes into an indefinite loop,
the code succeeding the Print function will never get executed.
As mentioned earlier, the second parameter e of data type PrintPageEventArgs
contains the data that is to be printed. The member MarginBounds, which is of
data type Rectangle, specifies the rectangular portion of the page, which falls
within the margins. In our case, the various values of the Rectangle structure
are as follows:
Height = 900, Width = 650, Left = 100, Right = 750
Top = 100, Bottom = 1000, X = 100, Y = 100.
Before we initiate printing, we need to identify the number of lines that can be
printed on a single page. To estimate this, we use the following calculation:
• Height of our page in pixels divided by the height of the font in pixels.
In this example, it works out to 900 / 22.02691 = 40.85. This value is stored in
the variable lpp, which signifies ‘lines per page’.
We then use another variable c, which has an initial value of zero. It is
incremented in the while loop, once for every line that is printed. As soon as
the value of c exceeds that of lpp, the program quits out of the ‘while’ loop.
The ReadLine function of the StreamReader class fetches a new line from the file
a.txt and stores it within a string variable s. If there are no more lines to be
read from the file, this function returns a null. Thus, the while loop
terminates when the number of lines printed exceed the total number of lines
that can fit on a page or when there are no more lines to be read from the file.
A check must be performed to verify whether we have printed the maximum possible
lines that can fit on a page, before we read the next line from the file. This
is to forestall reading a line from the file and thereafter, realizing that it
cannot be printed on the page due to lack of space. To perform this check, we
determine the y position of each line on the page. All text is printed below the
Top margin. So to calculate the y position of a line, we use the following
calculation:
(Height of the font used MULTIPLIED BY the number of lines printed so far) PLUS
value of the Top margin.
In this case,
The value of the top margin is 100
The height of the font is 22.02691
In the case of the first line to be printed, the number of lines printed so far
is 0.
So, the value of the y co-ordinate for the first line is
=100+(22.02691 X 0) = 100.
The value of the y co-ordinate for the second line is
=100+(22.02691 X 1) = 122.0269.
The value of the y co-ordinate for the third line is
=100+(22.02691 X 2) = 144.0538.
We accessed these values using the WriteLine function.
The Graphics member in class PrintPageEventArgs is employed to write to the
printer. This member contains the function DrawString. This function, which we
had used earlier to write to the screen, is now utilized to write to the
printer.
The DrawString function accepts 5 parameters:
1) A string, s
2) A font, f
3) A color or brush, Black
4) The left margin for the x coordinate, MarginBounds.Left
5) The y co-ordinate
As you may have noticed, the same DrawString function is used for printing to
the printer and writing to the screen. Abstraction, therefore, enables us
exploit and reuse concepts that we have learnt earlier.
The while loop ends when any of the following two conditions is satisfied:
• The number of lines printed exceeds the lines per page i.e. when we need to
print on the next page.
• The file has no more lines to print.
To figure out which of the above conditions has ended the ‘while’ loop, we
verify the value contained in string s. If the value is null, it signifies that
there are no more lines to print.
As soon as we exceed the maximum number of lines that can be printed on a page,
and there are lines remaining to be printed, there has to be a mechanism to
inform the PrintDocument class that the function pqr needs to be called again,
to print the next page. To accomplish this, the HasMorePages member of the
PrintPageEventArgs object e is either set to true or false. If it is set to
true, the function pqr gets called again.
a.cs
using System;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
public class zzz
{
Font f;
public void abc()
{
f= new Font(“Courier New”, 14);
PrintDocument pd = new PrintDocument();
pd.PrintPage += new PrintPageEventHandler(pqr);
pd.PrintPage += new PrintPageEventHandler(xyz);
pd.Print();
}
void pqr(object o, PrintPageEventArgs e)
{
e.Graphics.DrawString (“Vijay Mukhi”, f, Brushes.Black, 100, 200);
}
void xyz(object o, PrintPageEventArgs e)
{
e.Graphics.DrawString (“Sonal Mukhi”, f, Brushes.Black , 200, 300);
}
public static void Main()
{
zzz a = new zzz();
a.abc();
}
}
Through this example, we once again demonstrate the power of events and
delegates. We attach two methods, pqr and xyz, with the printing event. The
first function pqr prints ‘Vijay Mukhi’ at X=100 and Y=200 and then, the method
xyz prints ‘Sonal Mukhi’ at X=200 and Y=300. The HasMorePages property is
initialized to false by the program, even though it has the value of false by
default. Both these lines get printed on the same page.
Each time we set HasMorePages to true, the .NET framework sends a ‘form feed’
signal to the printer that sets up a new page for printing. The only mechanism
of printing text to a printer is by means of the Graphics object.
If the ‘font’ and the ‘Brushes’ parameters are set to null, a run-time exception
is generated. Hence, they must be explicitly specified. Unlike a dot matrix
printer that prints one line at a time, a laser printer first creates an entire
page in memory and then sends it for printing. Thus, in the function pqr, if we
change the Y coordinate from 200 to 400, both lines get printed on the same
page, since the page first gets created in the memory, and then it is sent to
the printer.
The PrintDocument class creates the PrintPageEventArgs object, since that is the
sole mechanism of activating the printer. Then, it calls all the methods
registered through the delegate.
a.cs
using System;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
public class zzz
{
public void abc()
{
yyy y = new yyy();
y.PrintPage += new PrintPageEventHandler(pqr);
y.Print();
}
void pqr(object o, PrintPageEventArgs e)
{
System.Console.WriteLine(“pqr”);
e.Graphics.DrawString (“Sonal Mukhi”, new Font(“Courier New”,10), Brushes.Black,
100, 200);
}
public static void Main()
{
zzz a = new zzz();
a.abc();
}
}
public class yyy : PrintDocument
{
Font f;
protected override void OnQueryPageSettings(QueryPageSettingsEventArgs e)
{
base.OnQueryPageSettings(e) ;
System.Console.WriteLine(“OnQueryPageSettings”);
}
protected override void OnBeginPrint(PrintEventArgs e)
{
base.OnBeginPrint(e) ;
System.Console.WriteLine(“OnBeginPrint”);
f = new Font(“Courier New”,14);
}
protected override void OnEndPrint(PrintEventArgs e)
{
base.OnEndPrint(e) ;
System.Console.WriteLine(“OnEndPrint”);
}
protected override void OnPrintPage(PrintPageEventArgs e)
{
base.OnPrintPage(e) ;
System.Console.WriteLine(“OnPrintPage”);
e.Graphics.DrawString (“Vijay Mukhi”, f, Brushes.Black, 100, 400);
}
}
Output
OnBeginPrint
OnQueryPageSettings
pqr
OnPrintPage
OnEndPrint
In the above example, class yyy is derived from PrintDocument. Thus, all code
embodied in the PrintDocument class is available to yyy. This also implies that
any code in the class can be overridden. The Print method of the PrintDocument
class had earlier called the methods registered with the delegate, whereas now,
it calls a series of methods from the PrintDocument class. In the situation
where we want to call our methods with the same name as in class yyy, the
keyword ‘override’ has to be used.
The output confirms that the first function to be called is OnBeginPrint. This
function is akin to an initialization function. All code that is to be executed
only once must be placed in this function. It is a sage programming practice to
always call the original function from the base class. Thus, we use the keyword
base to call the original OnBeginPrint from the class PrintDocument.
Superior programming style decrees that, initially, the base functions should be
called. It is not mandatory to do so, since even if this is not done, the class
still behaves in a similar manner. The documentation too is silent on this
issue, but we consider it prudent to do so and advise you also to call the
original function first.
The parameter PrintEventArgs passed to the function has no significant role to
play in this release. The documentation clearly states that this PrintEventArgs
has been designed for use in a future release. Thus, it cannot be put to any
consequential use at present. We create a Font object in this function, since it
is a one-time activity.
The second function that gets called is OnQueryPageSettings, which will be
elucidated in the next example. Following it, function pqr gets called, which
facilitates printing. Then, function OnPrintPage gets called with the same
parameters as that of the function pqr. If we so desire, we can print in the
delegate method. Other than the delegate method being called first, there is no
apparent difference.
Finally, the function OnEndPrint is called, in which, we are presented with the
opportunity to clean up any resources created in the method OnBeginPrint.
a.cs
using System;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
public class zzz
{
public void abc()
{
yyy y = new yyy();
y.PrintPage += new PrintPageEventHandler(pqr);
y.Print();
}
void pqr(object o, PrintPageEventArgs e)
{
e.Graphics.DrawString (“Sonal Mukhi”, new Font(“Courier New”,10), Brushes.Black,
100, 200);
}
public static void Main()
{
zzz a = new zzz();
a.abc();
}
}
public class yyy : PrintDocument
{
Font f;
protected override void OnQueryPageSettings (QueryPageSettingsEventArgs e)
{
base.OnQueryPageSettings(e) ;
e.PageSettings.Landscape = true;
}
protected override void OnBeginPrint(PrintEventArgs e)
{
base.OnBeginPrint(e) ;
f = new Font(“Courier New”,14);
}
protected override void OnPrintPage(PrintPageEventArgs e)
{
base.OnPrintPage(e) ;
e.Graphics.DrawString (“Vijay Mukhi”, f, Brushes.Black, 100, 400);
}
}
Each time we use the printer, we may be desirous of customizing the print
settings for each page. To enable this, the framework calls the function
OnQueryPageSettings before printing any page. In this function, we can change
the page settings using the parameter e, which is of type
QueryPageSettingsEventArgs. This class is derived from PrintEventArgs class.
One of the members in QueryPageSettingEventArgs is PageSettings, which contains
a large number of properties that control the printer settings. Here, we have
only used the Landscape property and assigned it a boolean value of true. Had we
assigned it a value of false, the property would have been set to Portrait mode,
which is usually the default mode.
Other properties that can be modified are: the paper tray i.e. the source from
where the paper is fed to the printer, the printer resolution, the page margins,
the color etc. Any property of a printer that can be set manually can now be
done using this function.
a.cs
using System;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
public class zzz
{
public void abc()
{
yyy y = new yyy();
y.PrintPage += new PrintPageEventHandler(pqr);
y.Print();
}
void pqr(object o, PrintPageEventArgs e)
{
e.Graphics.DrawString (“Sonal Mukhi”, new Font(“Courier New”,10), Brushes.Black,
100, 200);
}
public static void Main()
{
zzz a = new zzz();
a.abc();
}
}
public class yyy : PrintDocument
{
Font f;
protected override void OnPrintPage(PrintPageEventArgs e)
{
base.OnPrintPage(e) ;
e.Cancel = true;
f = new Font(“Arial”,13);
e.Graphics.DrawString (“Vijay Mukhi”, f, Brushes.Black, 100, 400);
}
}
In this program, the Cancel property of PrintPageEventArgs is set to true,
resulting in cancellation of the print job. The printing that was to be carried
out by the delegate function also gets cancelled. All print jobs are aborted.
The PageSettings object used in the earlier program is also a property of the
PrintPageEventArgs class, and thus, can be used in this function too.
a.cs
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
public class zzz
{
public void abc()
{
PrintDocument y = new PrintDocument ();
y.PrintPage += new PrintPageEventHandler(pqr);
PrintDialog d = new PrintDialog() ;
d.Document = y;
DialogResult r = d.ShowDialog();
if (r == DialogResult.OK)
y.Print();
}
void pqr(object o, PrintPageEventArgs e)
{
e.Graphics.DrawString (“Sonal Mukhi”, new Font(“Courier New”,10), Brushes.Black,
100, 200);
}
public static void Main()
{
zzz a = new zzz();
a.abc();
}
}
In this example, we would like to display a dialog box to the user where he can
change the printing options. Therefore, firstly we create an object d as an
instance of class PrintDialog. Creating a PrintDialog object assigns default
values to the following eight properties:
• AllowSomePages is set to False, disabling the Pages option button.
• AllowSelection is set to False, disabling the Selection option button.
• AllowPrintToFile is set to True, enabling Print to File check box.
• Document, which is a reference to the PrintDocument object, is set to null.
• PrinterSettings is assigned the value of False, disabling the dialog box.
• PrintToFile decides whether the printer would print to disk or to the printer.
If the value is null, it prints to the printer.
• ShowHelp is set to False, thus disabling the help button.
• ShowNetwork is set to True, thus enabling the network button.
We can modify any of these values to befit our requirements. We can also select
specific parts of the document to print.
Screen 6.1
When we run the program, the normal Windows Printer Dialog box is displayed,
allowing us to modify any of the properties. The ShowDialog function facilitates
the display of the dialog box. This function waits until one of two the buttons,
OK or Cancel, is clicked.The value returned on clicking one of these two buttons
is stored in the DialogResult object r.
If the OK button is selected, it will start the printing, whereas if the Cancel
button is selected, it will cancel or abort the printing. When the OK button is
selected, the function ShowDialog returns an enum DialogResult, with a value OK.
The above enum can have as many as 8 different values, depending upon the type
of buttons available in the Dialog Box. Thereafter, the Print function of the
PrintDialog class is called.
One of the final actions of the function ShowDialog is to initialize the
Document property supplied by PrintDocument object, i.e. y, to the printer
settings chosen in the Dialog box.
a.cs
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
public class zzz
{
public void abc()
{
PrintDocument y = new PrintDocument ();
y.PrintPage += new PrintPageEventHandler(pqr);
PageSetupDialog p = new PageSetupDialog () ;
PageSettings s = new PageSettings();
p.PageSettings = s ;
p.ShowDialog ();
y.DefaultPageSettings = s;
y.Print();
}
void pqr(object o, PrintPageEventArgs e)
{
e.Graphics.DrawString (“Sonal Mukhi”, new Font(“Courier New”,10), Brushes.Black,
100, 200);
}
public static void Main()
{
zzz a = new zzz();
a.abc();
}
}
In the above example we usher in a new class, PageSetupDialog, which allows the
user to change page settings such as the margins and the paper orientation. When
we create a PageSetupDialog object, properties in the object get initialized to
their default values.
Some of the properties are :
• AllowMargins: set to true, enables us to change the margins settings.
• AllowOrentation: set to true, enables the Portrait/Landscape radio buttons.
• AllowPaper: set to true, so that we can choose the paper size and source.
• AllowPrinter: set to true, enables the printer button.
• MinMargins: set to null, so that the default margins of 1 inch are displayed.
• PageSettings: set to null.
• PrinterSettings: set to null.
It is mandatory to initialise the PageSettings property to an actual object
containing the settings selected by the user. Finally the function ShowDialog
initializes the object s.
Screen 6.2
Then, the property DefaultPageSettings of the PrintDocument class is initialised
to the PageSettings object s, so that the printing framework uses the settings
stored in this property. Thus, the two dialog boxes have their own unique
purposes, and each of them fills up an object that is used by the PrintDocument
class. In the Windows world, Dialog boxes are used to fill up
properties/variables in an object.
In the above two programs, the function pqr does not receive any values from the
dialog boxes.
a.cs
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
public class zzz {
public void abc()
{
PrintDocument pd = new PrintDocument();
pd.PrintPage += new PrintPageEventHandler(pqr);
PrintPreviewDialog dlg = new PrintPreviewDialog() ;
dlg.Document = pd;
dlg.ShowDialog();
pd.Print();
}
void pqr(object o, PrintPageEventArgs e)
{
e.Graphics.DrawString (“Sonal Mukhi”, new Font(“Courier New”,10), Brushes.Black,
100, 200);
}
public static void Main() {
zzz a = new zzz();
a.abc();
}
}
Screen 6.3
Before we buy goods, we often like to preview them. The same analogy is true
when we want to print a document. The class PrintPreviewDialog enables us to
preview a document before printing it. The only property that is to be
initialized here is Document property. This is mandatory since there is no other
mechanism by which the Dialog box can receive the contents of the page.
We can safely assume that the ShowDialog function calls Print, which figures out
the text to be printed on a page. It also has the ability to display the content
in different magnification percentages and in multiple pages at a time. The
Dialog contains a PrintPreviewControl, which has several members that can be
toyed around with, in order to transform the appearance.
a.cs
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
public class zzz
{
public void abc()
{
PrintDocument pd = new PrintDocument();
pd.PrintPage += new PrintPageEventHandler(pqr);
pd.Print();
}
void pqr(object o, PrintPageEventArgs e)
{
Image i = Image.FromFile(“Sample.jpg”);
Point p = new Point(100, 100);
e.Graphics.DrawImage(i, p);
e.Graphics.DrawString (“Sonal Mukhi”, new Font(“Courier New”,10), Brushes.Black,
100, 600);
}
public static void Main()
{
zzz a = new zzz();
a.abc();
}
}
A printer has the capability of not only printing text, but also lines,
ellipses, curves and images. This program prints an image. Before you proceed,
ensure that file sample.jpg resides in the current directory.
The Image class constructor is supplied with a jpg file, which we have picked up
from the .NET samples. The Point object p represents the X, Y coordinates where
the image is to be drawn. Then, using the DrawImage function from the Graphics
class, the image is printed at the position specified. Using this procedure, all
images displayed on the screen in the previous chapters can be sent to the
printer for printing.
C Language Help, C Language Tutorials, C Language Programming, C Language Tricks { The C# Language }
C Language Help, C Language Tutorials, C Language Programming, C Language Tricks
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.
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′.
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.
WWhenever 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.
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 uttonMoveNext 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());
}
}
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.
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.
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.
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.
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());
}
}
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.
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.
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.
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.
13 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.
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.
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
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());
}
}
18 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.
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
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.
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.
23
When the form loads on, we see a list of customers from the Customers table,
with a plus sign displayed on the left.
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.
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’.
26 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.
28 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.
0 1
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 !
Controls { The C# Language }
Controls
Windows Forms or Winforms is a contemporary Windows-based forms package that
endows the Windows programmer with an innovative methodology for creating
aesthetic user interfaces and interactive applications. We will not ramble on
about the pros and cons of the package, but commence the creation of the
smallest GUI (Graphical User Interface) application.
a.cs
public class zzz
{
public static void Main()
{
zzz z = new zzz();
System.Windows.Forms.Application.Run(z);
}
}
Run the compiler as
>csc a.cs
Compiler Error
a.cs(6,1): error CS1502: The best overloaded method match for
‘System.Windows.Forms.Application.Run(System.Windows.Forms.Form)’ has some
invalid arguments
a.cs(6,38): error CS1503: Argument ’1′: cannot convert from ‘zzz’ to ‘System.Windows.Forms.Form’
An error is generated because, Run, which is a static function in the
Application class of the System.Windows.Forms namespace, requires a Form object.
The error distinctly states its inability to convert a zzz to
System.Windows.Forms.Form, which proves that, an object that looks like Form is
mandatory here, and not zzz.
a.cs
using System.Windows.Forms;
public class zzz : Form
{
public static void Main()
{
Application.Run(new zzz());
}
}
Screen 4.1
This program is not very dissimilar from the previous one. The using keyword is
employed to avoid the inevitability of writing namespace with every object. The
object z has no efficacy here, since we are passing the zzz object directly to
the Run function. As the class zzz is derived from Form, no error is generated.
When we run the program, a small blank window is displayed. You can click on the
‘x’ symbol to close it. The output is substantial enough for a single line of
code.
a.cs
using System.Windows.Forms;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
zzz()
{
Text = "Vijay Mukhi";
}
}
Screen 4.2
In the constructor of the zzz class, we have initialized a member called Text to
the string value ‘Vijay Mukhi’. When we run the program, to our amazement, our
window, which earlier was without a title, now possesses the title ‘Vijay Mukhi’.
This is the introductory concept of Windows Forms programming. The class called
Form has abundant properties such as Text etc., which have specific relevance in
a window. Any modifications to the properties get reflected immediately in the
window. The changes depend upon the properties that we modify. In this case, the
property of Text changes the Caption, text displayed in the title bar.
A Form represents a window displayed by an application. An application can have
different types of windows, such as a standard, tool bar, borderless or floating
window. The Form class is versatile enough to handle all the above types of
windows, as it is derived from innumerable classes. A Dialog Box, which is used
to accept input from the user, is available in two modes viz. modal and
modeless. Our trustworthy Form class can also handle such Dialog boxes with
equal aplomb. We normally use the Form class as the preliminary class for
building WinForms Applications.
The Main method calls the Run function and gives it the Form object. In the
constructor, we can modify the properties of the Form Class to give the window a
desired appearance. Since these properties are not static, they cannot be
altered in Main, but can be modified in the constructor or in any other
function.
a.cs
using System.Windows.Forms;
public class zzz
{
public static void Main()
{
Application.Run(new yyy());
}
}
class yyy : Form
{
public yyy()
{
Text = "Vijay Mukhi";
}
}
Screen 4.3
In this program, we have created another class yyy that derives from the Form
class. We have used this object as a parameter to the function Run. The rules do
not impel us to derive the class zzz from Form. However, in all our programs, we
shall follow the first approach since we have decided to steer clear of
controversy and stick to the rules.
a.cs
using System.Windows.Forms;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
zzz()
{
ClientSize = new System.Drawing.Size(300,600);
Size = new System.Drawing.Size(100,200);
}
}
Screen 4.4
The above example sets two properties of the Form class. The first, which is
called ClientSize, is used by Windows.Forms to decide how large our initial
window would be. This property has a default value, which can be overwritten by
specifying the width and height. As we need to furnish two values, we use a
class called Size in the namespace System.Drawing, which accepts two values.
This class does not insist on receiving meaningful values. The constructor is
passed the width and height of the desired window in pixels.
A graphics screen is divided into small dots or pixels. Depending upon the
configuration of the monitor and graphics card, a computer can handle and
display a certain number of pixels and colors. The higher the configuration, the
larger are the number of pixels and colors that are available.
The size of the client area of the form is computed as the size of the form
minus the borders and the title bar placed by Windows. They are not of our
concern, since we shall be placing our own controls in our form. ClientSize is a
property with a default value, and it gets updated automatically whenever the
form is resized.
The next property is Size. The user enjoys the flexibility of altering the size
of the window at run time. Size is initialized in manner similar to ClientSize.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form
{
Button b;
public static void Main()
{
Application.Run(new zzz());
}
zzz()
{
b = new Button();
Controls.Add(b);
}
}
Screen 4.5
We now see a small button at the top left hand corner of our Window. How did we
create this button? To do so, we first, create an object b, that looks like a
Button class.
The Form class has a large number of properties such as ClientSize, Size etc.
One of them is called Controls, which is a read-only property since it contains
only a Get. This property returns a Control.Collection object, whose Add
function adds the control to the Client area of the window. We shall be sprucing
up our button shortly.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form
{
Button b;
public static void Main()
{
Application.Run(new zzz());
}
zzz()
{
b = new Button();
b.Location = new Point(100,200);
b.Size = new Size(100,50);
b.Text = "Vijay Mukhi is smart";
Controls.Add(b);
}
}
Screen 4.6
Anything that is placed on a form is called a Control or a Widget. Similar to a
Form, a button control, popularly known as a command button, has numerous
properties. One of them is the Location property, which decides the position on
the Client area where the button will be positioned.
Here, we use the Point class and not Size, even though both are objects that
represent two numbers. By convention, a Size object represents a width and a
height and a Point object has an x and y co-ordinate system, starting from the
upper left corner.
Most properties have a default value. Since this fact about default values has
been reiterated numerous times, we shall not repeat it again. The Size property
determines the initial size of the window and the string assigned to the Text
property is displayed on the button.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form
{
Button b;
public static void Main() {
Application.Run(new zzz());
}
zzz()
{
b = new Button();
b.Location = new Point(100,200);
b.Size = new Size(100,50);
b.Text = "Vijay Mukhi is smart";
b.Click += new System.EventHandler(abc);
Controls.Add(b);
}
public void abc(object s, System.EventArgs e)
{
MessageBox.Show("Hi");
}
}
Screen 4.7
In the earlier example, clicking on the button was an exercise in futility
because the button did not achieve anything. After augmenting the code of the
program, when we click on the button, we see a MessageBox that displays the
greeting ‘Hi’. The rationale behind a button control is that, when we click on
it, some code should get executed, some action should take place.
The button class has an event object called Click, which accepts an object of
type EventHandler. The syntax for events uses the += symbol to add a function
that is to be called when the event handler gets activated. The function name is
given through the EventHandler delegate. This delegate has been specially
created only to handle events that a control will generate.
Thus, the function abc, which is passed as a parameter to the EventHandler
delegate, must have a certain signature. The first parameter is the generic
object that could represent any entity identifying the caller. The second
parameter is an EventArgs object, which we will explain shortly. Thus, each time
we click on the button, the function abc gets called. This function in turn
calls the static function Show from the MessageBox class to display ‘Hi’.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form {
Button b;
public static void Main()
{
Application.Run(new zzz());
}
zzz()
{
b = new Button();
b.Location = new Point(100,200);
b.Size = new Size(100,50);
b.Text = "Vijay Mukhi is smart";
b.Click += new System.EventHandler(abc);
b.Click += new System.EventHandler(pqr);
Controls.Add(b);
}
public void abc(object s, System.EventArgs e){
MessageBox.Show("Hi");
}
public void pqr(object s, System.EventArgs e)
{
MessageBox.Show("Bye");
}
}
Screen 4.8
This program reveals the veritable power of events and delegates. Two functions,
abc and pqr, are called whenever the button is clicked. To achieve this, all
that we need to do in the code is to call the Click event again, using the +=
symbol, followed by the name of the new function. The -= symbol is used if we
change our minds. This is a type safe way of calling code in response to an
event.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form {
Button b;
TextBox t;
public static void Main() {
Application.Run(new zzz());
}
zzz()
{
b = new Button();
b.Location = new Point(100,200);
b.Size = new Size(100,50);
b.Text = "Vijay Mukhi is smart";
b.Click += new System.EventHandler(abc);
t = new TextBox();
t.Text = "Hell";
t.Location = new Point(10,20);
Controls.Add(b);
Controls.Add(t);
}
public void abc(object s, System.EventArgs e)
{
MessageBox.Show(t.Text + " " + ClientSize );
}
}
Screen 4.9
In the Forms Window, we now see two controls: a Button and a TextBox object that
lets us enter some text. The textbox widget also has a large number of
properties associated with it. We shall not be repeating this obvious fact for
all the other controls. The properties Location and Size work in a similar
manner when used with any Control, but the property Text differs, depending upon
the object in use. For a button, it represents the caption, whereas for a text
box, it represents the text that is entered. Thus, some of the properties play
different roles when used in different controls.
Screen 4.10
Each time the button is clicked, we would like to display the text that has been
entered by the user in the text box. In the eventhandler function abc, the
property Text reveals the text entered into the textbox. The MessageBox class is
used to display the value, along with the size of the client area.
You can change the size of the window or change the contents of the text box and
observe the contents of the MessageBox changing dynamically.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
public override void Dispose()
{
base.Dispose();
MessageBox.Show( "hi " + ClientSize );
}
}
This program displays our ability to invoke code at a specific point in time,
which in this case, is at the stage when the user closes the window or when the
application quits out. It is akin to fulfilling the last wishes of the program.
Screen 4.11
As the application is quitting, it calls a function called Dispose. So, if you
ever want code to be called at the point when an application is about to quit
out, you must place it in the Dispose function. This code could be used to close
files or do anything else that the programmer desires.
It is not mandatory to call Dispose of the base class, but it is always a good
programming practice to call the base class function first, and then augment it
with your own code. In this particular case, it is inconsequential, but under
different circumstances, things may go out of hand if this advice is not heeded.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form {
public static void Main()
{
Application.Run(new zzz());
}
Brush b; int ii = 0;
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
b = new SolidBrush(Color.Blue);
ii++;
g.DrawString("Vijay Mukhi " + ii, Font, b ,1,25);
}
}
In this program, we are overriding a function called OnPaint, which is present
in the Form class. The OnPaint function gets called each time the window has to
be redrawn. Therefore, all code that is to be written to the screen must be
written in this function. This code cannot be placed anywhere else in the
program.
Our next endeavor is to create an object that has functions which display text
on the screen or draw a picture. The class that contains these display functions
is called Graphics. Thus, we create an object g that looks like Graphics. As an
object that looks like Graphics cannot be instantiated, WinForms provides us
with an object of type PaintEventArgs with the OnPaint Function. This class
contains members required for graphical display. Hence, g is initialized to the
Graphics member in e.
As mentioned earlier, OnPaint gets called whenever our window has to be redrawn.
Whenever OnPaint gets called, it creates an object that looks like
PaintEventArgs and then passes it as a parameter to the function. This object
has a member called Graphics, which contains functions used for drawing in our
client area. The DrawString function requires the text that is to be displayed
and its Font.
The Form class provides us with the object called Font. Thereafter, the text
color, or to be more precise, the brush is to be specified. Here, we want a
solid Brush like object. So, we create an object b, and give it a color in which
the text should be displayed. There is a static object Blue in the class Color
that stands for the color blue. The spelling of ‘color’ is as per the American
usage. Finally, the x and y co-ordinates on the screen are specified.
This positions the text in the window at these specified co-ordinates.
Thus, the function has a total of 5 parameters:-
• The text to be displayed.
• The font in which the text is to be displayed.
• The text color or the brush.
• The x co-ordinate.
• The y co-ordinate.
Here, we have specified certain values, but every time we use DrawString, we can
conveniently specify different values for these parameters. Thus, the second
DrawString function can display different text and use a different font or
brush. As the system does not have a default brush or font, we call it a
Stateless Model.
Screen 4.12
Along with ‘Vijay Mukhi’, we have used a variable called ii, which has been
initialized to 0. In the OnPaint function, we increment this variable by 1.
Before the window is displayed, function OnPaint gets called. Thereafter,
OnPaint gets called whenever the ‘minimize’ and ‘maximize’ buttons of the window
are clicked.
The function OnPaint gets called whenever our client area has to be redrawn due
to any action carried out by the user. This function has to be marked with the
modifier named protected. This is because the original function in the Form
class is tagged with this modifier. We can override a function of the base
class, provided we do not change any of the modifiers. By making OnPaint
protected, only derived classes can use the OnPaint function.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form {
public static void Main()
{
Application.Run(new zzz());
}
Brush b; int ii = 0;
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
b = new SolidBrush(Color.Blue);
ii++;
g.DrawString("Vijay Mukhi " + ii, Font, b ,1,25);
RectangleF r = new RectangleF(20, 60, 100, 25);
g.FillRectangle(new SolidBrush(Color.Gainsboro), r);
g.DrawString("Sonal Mukhi", Font, new SolidBrush(Color.Red), r);
StringFormat f = new StringFormat();
f.Alignment=StringAlignment.Center;
RectangleF r1 = new RectangleF(20, 100, 100, 25);
g.DrawString("Sonal Mukhi", Font, new SolidBrush(Color.Black), r1,f);
g.RotateTransform(-30);
g.TranslateTransform(0, 100);
g.DrawString("vijay mukhi", Font, new SolidBrush(Color.Orange), 20, 40);
g.ResetTransform();
}
}
Screen 4.13
The output of this program is a window with text displayed haphazardly. This
output is nothing to write home about, but is useful in elucidating numerous
concepts.
A rectangleF structure stores two point objects i.e. it specifies a rectangular
area of the window. We start at one corner, where the x and y co-ordinates are
20 and 60, and the opposite corner where the x and y co-ordinates are 100 and 25
respectively. The function FillRectangle from the Graphics class is used to
create and fill the above rectangular portion of the screen with the color
Gainsboro. The DrawString function is overloaded to take not only x and y as the
last two parameters, but also a rectangular area into which it will draw a
string.
We would now like to center the above string in the rectangular area. This is
easier said than done, because, it entails creation of an object that looks like
StringFormat with the Alignment property set as Center. The documentation
specifies many more options that can be implemented. The StringFormat object is
passed as the last parameter to the DrawString function, resulting in the string
being shown as centered, instead of being Left aligned, which is the default
setting.
If we want to rotate the image by 30 degrees, we just have to call a function
named RotateTransform from the Graphics class and pass as a parameter, the
amount of rotation that is required. You can then watch the image get displayed
at the specified angle. Beware, too acute an angle may sprain your neck! The
next function, named TranslateTransform, is optional. It is used to move the
text around in the client area horizontally or vertically. Whenever we transform
something, it stays in the transformed position. But thereafter, if we do not
want the other objects to be in this form, we need to use the function
ResetTranform to undo the transform. However, it is optional.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
Brush b;
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
b = new SolidBrush(Color.FromArgb(180, Color.Black));
RectangleF r = new RectangleF(20, 20, 50, 50);
g.FillRectangle(b, r);
}
}
Screen 4.14
The topic of Brushes is so exhaustive that a thesis can well be written on it.
In this program, we use a special brush to fill up a rectangular area on our
screen.
Here, we specify not only a color, but also a number, which is the alpha value
and has a range from 0 to 255. The larger the value, the darker will be the
color. To put it technically, the larger the value, the lesser will be the
translucence and vice-versa.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
Brush b;
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
b = new SolidBrush(Color.Black);
Font f = new Font("Times New Roman", 30);
g.DrawString("Vijay Mukhi " , f , b ,1,25);
}
}
Screen 4.15
In this program, we will shed light on Fonts. When you read a newspaper or
magazine, the style of the letters looks different in each of them. This
difference is due to the Font or the Typeface used. There are numerous fonts in
the world of letters.
While displaying text, we can be very specific about the way in which the
letters look. To enhance their visual appeal, we create an object that looks
like Font. Then, in the constructor, the Name of the font is specified along
with the Size in points. Remember that 72 points make an inch. Thus my name,
Vijay Mukhi, now gets displayed in a size that is bigger than normal.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Image i;
i = new Bitmap("sample.jpg");
g.DrawImage(i, 29, 20, 283, 212);
}
}
Screen 4.16
The above program merely displays an image. A file with a jpg or a gif extension
contains images or pictures. To display images, we use a class called Image that
can recognize pictures. Even though i is an image object, we initialize it to an
object that looks like Bitmap. An Image class is an abstract class and the class
Bitmap derives from it.
An Image class could represent a picture, which is not just an image, but could
also be a cursor, icon etc. The DrawImage function accepts an image object as
the first parameter, followed by the screen co-ordinates at which the image has
to be positioned. The above .jpg file is part of the samples offered while
installing the .NET SDK. So, search for the file and copy it to the current
working directory. Like the text sample, this picture can also be rotated,
transformed etc.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Image i = new Bitmap("colorbars.jpg");
Brush b = new TextureBrush(i);
g.DrawString("Vijay Mukhi is very smart" , Font, b ,1,25);
}
}
Screen 4.17
By combining a Brush and an image, we can create a multicolor brush. In one of
the earlier programs, we had used a Solid brush. Here, we are using a Texture
brush. This brush fills the interiors of a shape with a picture.
Thus, the text gets reflected in a brush, which reminds us of a rainbow. You can
enhance the aesthetic appeal of your applications by using this facility.
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form {
public static void Main()
{
Application.Run(new zzz());
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Pen p = new Pen(Color.FromArgb(150, Color.Purple), 20);
p.DashStyle = DashStyle.Dash;
p.StartCap = LineCap.Round;
Point [] pp = new Point[] {new Point(200, 140),new Point(700, 240),new
Point(500, 340)};
g.DrawCurve(p,pp);
}
}
Screen 4.18
The above program introduces freehand drawing. A pen is like an artist’s brush,
which is used to draw any shape that permeates the mind. In our program, we
commence by creating a Pen object p. It is initialized to a particular alpha
color using FromArgb function from the Color class, and to a specified width.
The constructor can also be provided with other parameters, such as a brush. A
pen is used to draw lines and curves.
A Pen can also draw a line of a specified width and style. The default DashStyle
is Continuous. If we change the DashStyle to Dash, the starting point becomes a
rounded edge. The default is a Straight Edge. The line drawn by a pen is very
versatile, and can employ a variety of fill styles, colors and textures. The
DrawCurve function paints a pen object that specifies how to draw a curve. It
has an array of points with the individual three point objects specifying where
the curved line should be drawn.
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form {
public static void Main() {
Application.Run(new zzz());
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Image i= new Bitmap("BoilingPoint.jpg");
Brush pb = new TextureBrush(i);
Pen p= new Pen(pb, 75);
g.DrawLine(p,1,5,150,200);
}
}
Screen 4.19
We can use a brush that looks like an image and create a pen that will draw
lines in the garb of a picture. The DrawLine function accepts two sets of
numbers, the x-y co-ordinates of the starting point and the x-y co-ordinates of
the ending point. It draws a thick line joining these two points. Thus, we can
use this function to draw any possible shape.
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
HatchBrush b = new HatchBrush(HatchStyle.ForwardDiagonal, Color.Green,
Color.FromArgb(100, Color.Yellow));
g.FillEllipse(b, 250, 10, 100, 100);
Rectangle r = new Rectangle(300, 250, 100, 100);
LinearGradientBrush lb = new LinearGradientBrush(r, Color.Red,
Color.Yellow,LinearGradientMode.BackwardDiagonal);
g.FillRectangle(lb, r);
}
}
Screen 4.20
On maximizing the screen, we see two figures; one is a filled circle, while the
other is a rectangular block. We are equipped with a large number of brushes
akin to those in the artistic world. One of them is a HatchBrush. The
constructor of HatchBrush accepts a hatch style and two colors, viz. a
background color and a foreground color.
The first parameter is the hatch style, which can be one of six possible hatch
styles. The foreground color, in this case, green, defines the color of the
lines to be drawn and the background color defines the color for the gaps
between the lines.
The FillEllipse function in Graphics fills up the shape to display the effect of
the brush. We could have used the Rectangle function also, but as we are trying
to be as akin as possible to the samples provided by Microsoft, we have used the
Ellipse function.
A LinearGradientBrush can represent color gradients and multi-color gradients. A
gradient represents a transformation from one color to another. A linear
gradient is defined alongside a line that is specified by the width of a
rectangle or by any two points. Thus, a two-color gradient will commence with a
starting color and conclude with the ending color. The blend from one color to
the next can be customized. First, we specify the object that is to be colored,
which is a rectangle in this case. The gradient starts with the left corner and
ends at the lower right corner. Thereafter, we follow with the starting color
followed by the ending color. Finally, the angle measured in degrees in the
clockwise direction is mentioned, starting from the x-axis. This defines the
orientation of the gradient. You can change the angle and witness the
spectacular effects.
Menus
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
MainMenu m;
public zzz()
{
m = new MainMenu();
MenuItem mi= m.MenuItems.Add("&File");
Menu = m;
}
}
Screen 4.21
Let us now build a menu. We have created an object m, which symbolizes the
MainMenu. MainMenu is called a control and represents the menu structure for a
Form. It is the root of the menu.
A menu consists of various menu items, which are displayed horizontally across
the menu. We want to create a menu item that displays the word ‘File For this,
we need another class called MenuItem. A MenuItem can represent either an
individual menu item depicting a command, or it can cascade to another popup of
menu items.
MenuItems is a read-only property in MainMenu that gives a reference to all the
MenuItems currently available in the menu. We have none so far. This
CollectionObject also has a function called Add, which is used to add menu
items. To do so, the text of the item that is to be displayed must be stated as
the parameter to the Add function. We can also remove any menu item that has
been previously added.
The variable mi stores the MenuItem object returned by the Add function.
Thereafter, Menu, which is an object of type MainMenu, available in Form, is
initialized to the menu that we desire. The appearance of the menu depends upon
the menu object stored in Menu.
When we run the program, we see the word File displayed in the top left corner.
At this stage, nothing happens when we click on it. On pressing the Alt key, F
is displayed as underlined since the symbol & underlines the character it is
preceded with.
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form {
public static void Main() {
Application.Run(new zzz());
}
MainMenu m;
public zzz() {
m = new MainMenu();
MenuItem mi= m.MenuItems.Add("&File");
mi.MenuItems.Add("Hi");
mi.MenuItems.Add("-");
mi.MenuItems.Add("Bye");
Menu = m;
} }
Screen 4.22
Now things look more visually attractive. When we click on File or use the
accelerator Alt-F, a menu pops up with the word ‘Hi’, followed by a separator
and then finally by the word ‘Bye’.
A separator is used to logically group menus together. However, when we click on
‘hi’ or ‘bye’, nothing happens. This situation needs to be redressed, since a
menu should activate some code.
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
MainMenu m;
public zzz()
{
m = new MainMenu();
MenuItem mi= m.MenuItems.Add("&File");
MenuItem m1;
m1 = new MenuItem("Hi", new System.EventHandler(abc), Shortcut.CtrlF11);
mi.MenuItems.Add(m1);
mi.MenuItems.Add("Bye");
Menu = m;
}
void abc(object sender, System.EventArgs e)
{
MessageBox.Show("hell");
}
}
Screen 4.24
Screen 4.23
Now, whether you either click on File and then on the word ‘Hi’, or you press
Control+F11, you will see a message box with the word "hell" displayed in it.
The MenuItem constructor is overloaded. The first parameter is the text to be
displayed. The second parameter is a delegate that encompasses the function to
be called whenever this menu item is activated. Just as the pen is mightier than
the sword, under some circumstances, the keyboard is certainly mightier than the
mouse. At times, it is faster to use a keyboard shortcut, instead of using the
mouse. Thus, the last parameter is the keyboard shortcut key, which is part of
an enumerator. This MenuItem object is passed to the Add function, that either
accepts a string or a MenuItem object.
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
MainMenu m;
public zzz()
{
m = new MainMenu();
MenuItem mi= m.MenuItems.Add("&File");
MenuItem a = new MenuItem("One",new System.EventHandler(abc));
MenuItem b = new MenuItem("two",new System.EventHandler(abc));
mi.MenuItems.Add("hell",(new MenuItem[]{ a, b })
);
Menu = m;
}
void abc(object sender, System.EventArgs e)
{
MessageBox.Show("hell");
}
}
Screen 4.26
Screen 4.25
Here, we have a popup within a popup. When you click on File, you will see the
word ‘hell’ displayed. You will also see an arrow pointing to the right, along
with the menu item. If you move the mouse over the arrow, a popup is displayed,
containing the two menu items ‘one’ and ‘two’. If we click on them, a message
box with the word ‘hell’ gets displayed.
In the program, with a single statement, we have created two menu items, a and
b, followed by an array of menu items. This array is then passed as the last
parameter to the Add function. Thus, all the menus become sub-menus. In this
case, the event handler is associated with the submenu options, since clicking
on the menu item displays the sub-menu.
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
MainMenu m;
public zzz()
{
m = new MainMenu();
MenuItem mi= m.MenuItems.Add("&File");
mi.MenuItems.Add( "hell",new System.EventHandler(abc));
mi.MenuItems.Add( "Bye",new System.EventHandler(abc));
Menu = m;
}
void abc(object s, System.EventArgs e)
{
MenuItem m = (MenuItem) s;
if ( m.Checked)
m.Checked = false;
else
m.Checked = true;
}
}
Screen 4.27 Screen 4.28
We add two menu items, ‘hell’ and ‘Bye’ to our File menu and assign the same
function abc to handle their events. Clicking on any one of the menu options
results in a call to the function abc. This function takes two parameters. The
first parameter s, represents the menu item that was clicked on. If the first
menu option ‘hell’ is clicked, then the parameter s is not an object, but a menu
item representing ‘hell’ and vice versa.
Every MenuItem has an option called Checked, which if True, will display a tick
mark on the menu item. Thus, you can Check or Uncheck a menu option by clicking
on it. You may click on each menu option to observe this effect.
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
MainMenu m;
public zzz()
{
m = new MainMenu();
MenuItem mi= m.MenuItems.Add("&File");
mi.MenuItems.Add( "hell",new System.EventHandler(abc));
mi.MenuItems.Add( "Bye",new System.EventHandler(abc));
Label l = new Label();
ContextMenu lm;
lm = new ContextMenu();
l.ContextMenu = lm;
l.Text = "Vijay Mukhi";
lm.MenuItems.Add(mi.CloneMenu());
Controls.Add(l );
Menu = m;
}
void abc(object s, System.EventArgs e)
{
MenuItem m = (MenuItem) s ;
if ( m.Checked)
m.Checked = false;
else
m.Checked = true;
}
}
The program output will display the same menu – File, as seen in the earlier
program. The text Vijay Mukhi will also be visible. If you place the mouse on
this text and right click the mouse, you will see the same menu as seen with the
File option.
Screen 4.29 Screen 4.30
This is called a Context Sensitive Menu. These two menus, however, are
different. If you Check a menu option in this menu by clicking on it, it does
not carry the tick mark to the other menu.
We first create two objects:
• The first is lm, which looks like a ContextMenu.
• The second is l, which looks like a label.
Screen 4.31
Every label has a member called ContextMenu, wherein we can specify a Context
Sensitive menu. This member is initialized to lm. As we have already created a
menu item mi, we can reuse this menu item.
However, a menu item cannot be used twice. Hence, calling the function CloneMenu
off MenuItem creates a clone. This clone menu is then passed to the Add function
of MenuItems in the ContextMenu.
Writing Controls
Let us start by creating the simplest control that money can buy. We create the
following files:
c.cs
using System.Windows.Forms;
using System.Drawing;
public class yyy : Control
{
}
h.cs
using System.Windows.Forms;
public class zzz : System.Windows.Forms.Form
{
yyy a;
public zzz()
{
a = new yyy();
Controls.Add(a);
}
public static void Main() {
Application.Run(new zzz());
}
}
a.bat
del *.exe
del *.dll
csc.exe /t:library c.cs
csc.exe /r:c.dll h.cs
h
The file c.cs contains our very first custom control. In order to create our own
user-defined control, we create a class yyy and derive it from the Control
class. The Control class implements the basic code required by classes to
implement the behavior of a control or a widget. This code can handle user input
with a keyboard or a pointing device, such as a mouse. Message handling and
security features are also supported.
At the end of the day, all controls are merely child windows. The Control class
defines the area or bounds of a control along with the fonts, colors and images.
This class allows painting, context menus and anchoring with docking behavior.
Earlier, we had displayed scores of controls in our containers, written by the
Microsoft developers. All these controls were derived from the Control class.
Screen 4.32
Thus, a user control like yyy is an instance of a Control class, which is added
to the Form using the Add function off the Controls collection. It can’t get any
simpler. On running the executable, we see no output. Yet, since no error was
generated, we presume that all went well.
The major difference between the Microsoft controls and our controls is, the
file in which the code for the control is finally placed. We have placed our
control code in assembly c.dll, whereas, Microsoft controls are placed in
System.Windows.Forms.dll.
c.cs
using System.Windows.Forms;
using System.Drawing;
public class yyy : Control
{
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawString(Text,Font, new SolidBrush(ForeColor), ClientRectangle);
}
}
h.cs
using System.Windows.Forms;
public class zzz : System.Windows.Forms.Form{
yyy a;
public zzz() {
a = new yyy();
a.Size = new System.Drawing.Size(600, 450);
a.Text = "Vijay Mukhi";
Controls.Add(a);
}
public static void Main() {
Application.Run(new zzz());
}
}
Screen 4.33
In the above example, we have overridden a function called OnPaint in the
Control class. This function gets called whenever a control is to be redrawn on
the screen. It is passed a PaintEventArgs object as a parameter, from where we
summon the DrawString function to paint a string in a specified font and color,
at a particular location. The first parameter, Text, is a property, which refers
to the string to be displayed. The string ‘Vijay Mukhi’ is presently displayed
in the window.
In the container h.cs, we have initialized the property Text contained in the
Control class to ‘Vijay Mukhi’. The Size property is also initialized, so that
our control has a specific size in the container.
h.cs
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form {
Button b;
ccc c;
public zzz() {
b = new Button();
c = new ccc();
b.Anchor = System.Windows.Forms.AnchorStyles.Bottom;
b.DialogResult = System.Windows.Forms.DialogResult.OK;
b.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
b.Size = new System.Drawing.Size(96, 24);
b.Text = "&Save";
b.Location = new System.Drawing.Point(8, 328);
b.Click += new System.EventHandler(abc);
Text = "Sonal Mukhi";
AcceptButton = b;
ClientSize = new System.Drawing.Size(400, 373);
c.Anchor=AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left |
AnchorStyles.Right;
c.AutoScrollMinSize = new System.Drawing.Size(0, 0);
c.Size = new System.Drawing.Size(400, 310);
c.Text = "Vijay Mukhi";
Controls.Add(b);
Controls.Add(c);
c.cust = ddd.rrr();
Size = new Size(400, (373 + SystemInformation.CaptionHeight));
}
void abc(object sender, System.EventArgs e)
{
c.aaa();
MessageBox.Show("vijay "+ c.cust);
}
public static void Main(string[] args)
{
Application.Run(new zzz());
}
}
c.cs
using System;
using System.Windows.Forms;
using System.Drawing;
public class ccc : UserControl
{
TextBox t;
TextBox ID;
Label l;
ddd c;
public ccc()
{
t = new TextBox();
l = new Label();
ID = new TextBox();
Text = "Vijay Mukhi";
Size = new System.Drawing.Size(384, 304);
t.Size = new System.Drawing.Size(88, 20);
t.Location = new System.Drawing.Point(88, 70);
l.Size = new System.Drawing.Size(64, 16);
l.Location = new System.Drawing.Point(8, 32);
l.Text = "ID:";
ID.ReadOnly = true;
ID.Size = new System.Drawing.Size(200, 20);
ID.Location = new System.Drawing.Point(88, 30);
ID.Enabled = false;
Controls.Add(t);
Controls.Add(ID);
Controls.Add(l);
}
public ddd cust
{
get
{
return c;
}
set
{
c=value;
ID.Text = c.ID;
t.Text = c.ti;
}
}
public void aaa()
{
c.ti = t.Text;
}
}
cc.cs
using System;
using System.ComponentModel;
using System.IO;
public class ddd : Component
{
string i ;
string t ;
public static ddd rrr()
{
ddd c = new ddd("111");
c.ti = "Vijay";
return c;
}
internal ddd(string s): base()
{
i = s ;
}
public string ID
{
get
{
return i ;
}
}
public string ti
{
get
{
return t ;
}
set
{
t = value ;
}
}
public override string ToString()
{
StringWriter sb = new StringWriter() ;
sb.WriteLine("Sonal \n");
sb.WriteLine(i);
sb.Write(t);
return sb.ToString();
}
}
a.bat
del *.exe
del *.dll
csc.exe /t:library /out:c.dll c.cs cc.cs
csc.exe /R:c.dll h.cs
h
This program is a rather protracted one. As usual, we start with the container
in h.cs. In the zzz constructor, we first create a button b and an object c that
is an instance of our user control ccc. The control is present in the assembly
c.dll. What this class presently does is not significant. We begin by
initializing a large number of properties in the button.
The Anchor property decides as to which edges of the control are to be anchored
with the edges of the container. Here, we have chosen the Bottom edge.
The DialogResult property is the value that is returned to the parent form when
we click on the button. The value returned is OK.
The FlatStyle property belongs to the ButtonBase class and is one of the
numerous properties that influence the flat style appearance of the button.
Knowledge of GUI programming implies cognizance and comprehension about all the
facets of making your application more comely and pleasing to the eye. The Size,
Text and Location properties were explained earlier.
Each time we click on the button, the function abc gets called. The Text
property decides on the title of the windows. The AcceptButton property requires
an object that represents a button. Every form has a feature, which associates
the Enter key with a button. The resultant effect is that pressing the Enter key
on the keyboard simulates a click on the associated button. Thus, in the above
form, pressing Enter or clicking on the button would result in a call to the
function abc. The ClientSize property decides the size of the windows.
Our User Control too can initialize properties since they belong to the Control
class. In the program, we have set the Anchor, AutoScrollMinSize, Size and Text
properties of our user-defined control class ccc, even though our control may
not have implemented these properties directly.
Using the Add function, we have then added the button and the control ccc to the
form. Finally, we have called a static function rrr from the class ddd that
initializes a property cust from our user-defined control.
When we run this program, we see two text boxes, a label and a button. It is
obvious that other than the button, the other widgets were created by the class
ccc. This provides ample credence to our belief that our user-defined control
can do its biding.
Screen 4.35
Screen 4.34
We shall now endeavor to comprehend what the constructor of class ccc in file
c.cs is attempting to do. The constructor contains two text boxes, one called ID
to store the id of the user, and the other called t to store the user’s name.
The label l is used to display a simple descriptive message. Since we do not
want the user to change the value contained in ID, we assign the value true to
its ReadOnly property and assign the value false to its Enabled property.
Thereafter, we add these three widgets to the form. Hence, we can now see four
widgets on the screen.
In the container h.cs, we call a static function, rrr off class ddd. The ddd
class is created in cc.cs and is derived from Component. In the rrr function, we
create an object c, which looks like ddd and pass a value of 111 to the
constructor. The constructor of class ddd initializes an instance variable i to
the value contained in s. The variable i stands for the user id.
Class ddd has a property called ti, which is initialized to my name, Vijay. This
property ti sets another instance variable t to the value ‘Vijay’. Thus, we have
initialized two instance members of class ddd to specific values.
The value returned on calling the rrr function is stored in the cust property of
the control c. The class ccc contains the property having type ddd.
The property Cust stores the ddd object in the variable c for later use. It also
initializes the Text property of the text boxes to the ID and Name of the user
that the two properties in the class ddd were initialized to. Thus, we see ’111′
and ‘Vijay’ displayed in the text boxes.
When we click on the button labeled ‘Save’, the function abc gets called. This
function first calls the function aaa from class ccc using the object c. In aaa,
we initialize the ti property of the control to the value present in the
textbox. The ID property is dimmed out, and hence its value can never be
changed. The object c represents the ddd object in class ccc.
To display a string, an object, whose data type is not a string, has to call the
ToString function in the datatype. The cust property in class ccc has the type
of ddd, which contains the ToString function. This function uses the
StringWriter class to concatenate the word ‘Sonal’ with the value of the
instance variables i and t, which eventually get displayed in the MessageBox.
The above program demonstrates two crucial points:
(a) All the code that refers to the user has been encapsulated in class ddd.
(b) The user interface code is entered in class ccc.
The container is oblivious to these classes and does not bother to verify
whether there are two classes or one. While the class ccc contains code
pertaining to User Interface interaction only, the class ddd contains code
relating to the actual object.
h.cs
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form {
RadioButton r1,r2;
GroupBox g1;
sss s;
public zzz()
{
r1 = new System.Windows.Forms.RadioButton();
r2 = new System.Windows.Forms.RadioButton();
r1.Location = new System.Drawing.Point(24, 24);
r1.Size = new System.Drawing.Size(128, 24);
r1.Text = "Vijay";
r1.Checked = true;
r1.CheckedChanged += new System.EventHandler(r1f);
r2.Location = new System.Drawing.Point(24, 64);
r2.Size = new System.Drawing.Size(128, 24);
r2.Text = "Mukhi";
r2.CheckedChanged += new System.EventHandler(r2f);
g1 = new System.Windows.Forms.GroupBox();
g1.Size = new System.Drawing.Size(192, 152);
g1.Text = "Sonal";
g1.Location = new System.Drawing.Point(320, 16);
s = new sss();
Text = "Control Example";
ClientSize = new System.Drawing.Size(528, 325);
s.Size = new System.Drawing.Size(304, 328);
s.TabIndex = 0;
s.Anchor = AnchorStyles.Left | AnchorStyles.Right;
s.Font = new System.Drawing.Font("TAHOMA", 16f, System.Drawing.FontStyle.Bold,
System.Drawing.GraphicsUnit.World);
s.Text = "Simple Control";
s.dmc += new System.EventHandler(sf);
Controls.Add(g1);
Controls.Add(s);
g1.Controls.Add(r2);
g1.Controls.Add(r1);
}
void r2f(object sender, System.EventArgs e)
{
if (r2.Checked)
{
s.dm = ddd.a2;
}
}
void r1f(object sender, System.EventArgs e)
{
if (r1.Checked)
{
s.dm = ddd.a1;
}
}
void sf(object sender, System.EventArgs e)
{
if (s.dm == ddd.a1)
MessageBox.Show("hi");
if (s.dm == ddd.a2)
MessageBox.Show("bye");
}
public static void Main()
{
Application.Run(new zzz());
}
}
c.cs
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing;
[DefaultProperty("dm"),DefaultEvent("dmc"),]
public class sss : Control
{
ddd d;
EventHandler eee;
public sss() :base()
{
d = ddd.a1;
ccc();
SetStyle(ControlStyles.ResizeRedraw, true);
}
[Category("Appearance"),Description("Controls how the control
paints"),DefaultValue(ddd.a1),Bindable(true),]
public ddd dm
{
get
{
return d;
}
set
{
d=value;
ccc();
dmf(EventArgs.Empty);
}
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.FillRectangle(new SolidBrush(BackColor), ClientRectangle);
Size textSize = e.Graphics.MeasureString(Text, Font).ToSize();
float x = (ClientRectangle.Width/2) – (textSize.Width/2);
float y = (ClientRectangle.Height/2) – (textSize.Height/2);
e.Graphics.DrawString(Text,Font,new SolidBrush(ForeColor),x, y);
}
protected override void OnTextChanged(EventArgs e) {
base.OnTextChanged(e);
Invalidate();
}
[Description("Raised when the DrawingMode changes")]
public event EventHandler dmc
{
add {
eee += value;
}
remove {
eee -= value;
}
}
protected virtual void dmf(EventArgs e)
{
Invalidate();
if (eee != null)
eee.Invoke(this, e);
}
void ccc()
{
if ( d == ddd.a1)
{
base.BackColor = Color.Yellow ;
base.ForeColor = Color.Green ;
}
if ( d == ddd.a2)
{
base.BackColor = Color.LightSlateGray ;
base.ForeColor = Color.White ;
}
}
}
public enum ddd
{
a1 = 0,
a2 = 1,
}
a.bat
del *.exe
del *.dll
csc /t:library c.cs
csc h.cs /r:c.dll
h
Let us now write a control that is considerably intricate. In the container
h.cs, we start with two radio buttons r1 and r2. Each time we select a radio
button, depending upon the option selected, either of the functions r1f or r2f
will get called. Thereafter, we create a group box called g1. The radio buttons
are added to the group box, and the group box is added to the Controls
collections. Apart from these controls, one more user control named s, which is
an instance of class sss, is added to the Controls collection.
Screen 4.36 Screen 4.37
Prior to this, we initialize various properties of this control such as
TabIndex, Font, Size, Anchor, Text etc. to some meaningful values. Besides
these, a property called dmc in the user control is initialized to an
EventHandler that calls function sf. Thus, whenever the event represented by dmc
is triggered, the function sf gets called.
The code implementing our user control s, resides in the file c.cs. The user
control s is an instance of sss and is derived from the Control class. At the
outset, the constructor of class sss calls the constructor of the base class
using the keyword base, even though this is optional, because the base class
constructor invariably gets called.
The class ddd is an enum, with two members a1 and a2, having values 0 and 1
respectively. We could conveniently have used numbers directly instead of an
enum, but since the original example used an enum, we have also done so. We set
the object d to the value 0 and call function ccc from class sss. The main
objective of placing code in a function is to enable the code to be called
several times.
In the function ccc, we start by checking the value of the object d. If it is a1
i.e. 0, we change the value of the two properties BackColor and ForeColor to
Yellow and Green respectively. If the object has a value of a2, then another
pair of colors is assigned to these properties. The properties are changed in
the base class using the keyword base. The function SetStyle ensures that the
form gets redrawn when it is resized.
We have already learnt that the OnPaint function is called whenever the window
needs to be redrawn. In this function, we first use the property BackColor to
fill the form background. Next, we use the width of the currently selected font,
to calculate the midpoint of our screen, and then, we write the value contained
in the text property in the center of the window.
When the second radio button is selected, function r2f gets called. In this
function, the program checks whether the radio button is already checked. If so,
it initializes the property dm, whose data type is ddd, to a2.
Similarly, when the first radio button is selected, function r1f gets called.
This function first ascertains if the radio button is already checked. If so, it
initializes the property dm to a1.
Now, we shall focus our attention on the property dm. In the set accessor of
property dm, the ddd object named d is initialized to either a1 or a2. Following
this action, a call is made to function ccc, which changes the background and
foreground color, depending on the value contained in d. The effect is observed
when the function OnPaint gets called. A call is made to the function dmf with a
parameter of an Empty event.
In function dmf, we first call Invalidate, which in turn, calls the OnPaint
function. Just as life offers no guarantees whatsoever, in much the same way,
the calls made to the OnPaint function are unpredictable. The Invalidate
function instantly calls the OnPaint function.
You may recall that in h.cs, the property dmc was initialized with the name of
the function sf. This property dmc is an event type that stores the EventHandler
or function sf in an instance object eee. So, the value in the object eee is
checked. If the value is not null, the function Invoke is called off the object
eee with two parameters. The first parameter is a reference to itself, i.e.
‘this’, and the second parameter is a null EventArgs object. The function
Invoke, in turn, calls function sf in the container, h.cs. The function sf
displays a message box, depending upon the value of the ddd object.
The main idea behind this exercise is to demonstrate that clicking on a radio
button in the container initializes a property in the user control. This in
turn, raises a property changed event, thus resulting in a call to a function
registered with a property of the control. The function resides in the container
and not in the user control.
This is a circuitous route for accomplishing results. The Invoke function is not
aware of and could not care less about the functions that it is calling.
All the other attributes in the code can be safely ignored, since they are
mainly meant for external tools or programs that display the metadata.
h.cs
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form
{
TextBox t;
Button b;
hhh h;
public zzz()
{
h = new hhh();
b = new Button();
t = new TextBox();
b.Size = new System.Drawing.Size(104, 40);
b.Text = "Vijay";
b.Location = new System.Drawing.Point(336, 56);
ClientSize = new System.Drawing.Size(448, 157);
t.Location = new System.Drawing.Point(80, 16);
t.Text = "Vijay Mukhi";
h.Dock = System.Windows.Forms.DockStyle.Bottom;
h.Size = new System.Drawing.Size(448, 40);
h.Location = new System.Drawing.Point(0, 117);
h.Text = "none";
h.ppp(t, "TextBox selected");
h.ppp(b, "Button Selected");
Controls.Add(t);
Controls.Add(b);
Controls.Add(h);
}
public static void Main(string[] args)
{
Application.Run(new zzz());
}
}
c.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
public class hhh : Control
{
Hashtable h;
Control a;
public hhh()
{
h = new Hashtable();
BackColor = SystemColors.Info;
}
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) ]
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
}
}
private void ce(object s, EventArgs e)
{
a = (Control)s;
Invalidate();
}
private void cl(object s, EventArgs e)
{
if (s == a)
{
a = null;
Invalidate();
}
}
public void ppp(Control c, string v)
{
if (v == null)
{
v = string.Empty;
}
if (v.Length == 0)
{
h.Remove(c);
c.Enter -= new EventHandler(ce);
c.Leave -= new EventHandler(cl);
}
else
{
h[c] = v;
c.Enter += new EventHandler(ce);
c.Leave += new EventHandler(cl);
}
if (c == a)
{
Invalidate();
}
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
Rectangle rect = ClientRectangle;
Pen borderPen = new Pen(ForeColor);
pe.Graphics.DrawRectangle(borderPen, rect);
borderPen.Dispose();
if (a != null)
{
string te = (string)h[a];
if (te != null && te.Length > 0)
{
rect.Inflate(-2, -2);
Brush brush = new SolidBrush(ForeColor);
pe.Graphics.DrawString(te, Font, brush, rect);
brush.Dispose();
}
}
}
}
In this program, we have three controls: a user control h, contained in class
hhh, a TextBox t and a Button b. The basic properties like Size, Text and
Location for the Button and the Text box are set to the specified initial
values. Thereafter, the properties of the user control h are initialized. The
DockStyle for the Dock property is set to the Bottom of the form, and the Size
and the Location are specified. The Text property is also initialized.
The user control h has a property called ppp, which accepts two parameters, a
control and a string. We call it twice. When it is called for the first time,
the first parameter passed is a text box control. The next time it is called,
the first parameter passed is a button control. The button and the text box are
displayed on the screen and a yellow colored label with the word ‘TextBox
selected’ in the bottom pane.
Screen 4.38
When we click on the button, the words in the label change to ‘Button selected’.
Screen 4.39
Thus, depending upon the control selected, our user control displays a help
message which we have registered using the property ppp.
The user control h in file c.cs, has two instance variables, a HashTable h and a
Control a. The HashTable class stores values based on a certain key. This helps
in retrieving the value efficiently on being provided with the key. In the
constructor of class hhh, an instance h of the HashTable is created. The
property BackColor is initialized to a Read Only Color property from the class
SystemColors. This property represents the tool tip background color. On our
machine, it happens to be Yellow, and thus, we see a yellow color label.
There is a property called Text in the Control class. In order to implement our
property, we must override the existing one. Presently, in our property, we are
merely accessing the original Text in the base class.
The attributes make interesting reading even though they have little use in the
current example. The Browsable attribute with a parameter of False prevents this
property from showing up in the Property Browser. Also, the value assigned to
the property is not saved on disk. We are not doing anything useful in the Text
property at all.
The property ppp is called twice in the control, because the container has two
property initialization statements. Good programming style incorporates
comprehensive error checks.
We first check for a string value in the second parameter v. If it is null, we
initialize the variable v to an empty string. If the string is not empty, we add
the string contained in v to the hash table using the control parameter c as the
key. Thus, in a hash table, any data type can be used as a key to insert or
retrieve values.
The control class has Events called Enter and Leave. We use the += syntax to
register the function ce whenever the Event Enter gets fired. In the same
manner, the function cl gets called whenever the Event Leave gets fired.
If the user calls the property without a string, it signifies that the control
has to be removed from the hash table and the list of functions in the events
has to be called. Thus, we use the Remove function of the HashTable class to
remove the key c from the hash table. The -= syntax of the Events and Delegates
is employed to remove the functions registered with the Enter and Leave events.
In the OnPaint function, we have drawn a label and displayed some text in it.
There is one rule that must never be violated: ‘Call the function in the base
class first.’ This is because we are unaware of what the overridden function
accomplishes.
A Pen is created from the color stored in ForeColor. This color is the default
color stored in the Control class. The Background color is initialized in the
constructor of hhh class. A rectangle that is drawn using this pen displays
yellow as the background color, with the size defaulting to the value stored in
the ClientRectangle property in the control class. The pen is then disposed off,
to enable the system to retrieve the resources consumed by it. A check is
thereafter performed on the value in the Control object. It should be null since
the control has not been initialized in the beginning.
Before the OnPaint function gets called, a lot of activities get executed in the
background, i.e. many functions get called and numerous events get triggered. On
entering a field, the event OnControlEnter gets triggered and the function that
is registered with the Enter event is called. In our case, function ce is
called. The first parameter to this function is a handle to the control that
caused the event. In our case, it is the Text Box. We initialize the object a to
the Text Box control. Thus, in OnPaint, the value of control a is a Text Box.
We then retrieve the string stored in the hash table using the control as the
key. We then ascertain that the value contained in the string te is not null and
its length is greater than zero.
The Rect structure contains the size of the rectangle. We can then inflate or
deflate the rectangle using the Inflate method. As a result, the position and
the size of the Rectangle change. The X and Y properties are changed by the
amount specified and the Width and Height are modified to twice the amount
specified. The final outcome is that the size of the rectangle is inflated
without moving its geometric center. In our case, since the numbers are
negative, the rectangular will be deflated. Following this, a solid brush is
created using the property ForeColor. A string is drawn using the DrawString
function in the Pen class. Thereafter, we dispose of the brush.
When we leave the control, the event handler named Leave is called. The function
associated with this eventhandler is cl, which ascertains whether the control in
‘a’ and the one passed as parameter i.e. ‘s’ is the same. If so, then ‘a’ is
initialized to a Null value and the OnPaint function is called. The value
contained in ‘a’ is the deciding factor on whether we are inside a control or
not.
h.cs
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form
{
fff f;
public zzz()
{
f = new fff();
f.Dock = System.Windows.Forms.DockStyle.Fill;
f.ForeColor = System.Drawing.Color.White;
f.BackColor = System.Drawing.Color.Black;
f.Size = new System.Drawing.Size(600, 450);
f.vvv = 73;
f.Text = "Vijay Mukhi";
ClientSize = new System.Drawing.Size(600, 450);
Controls.Add(f);
}
public static void Main()
{
Application.Run(new zzz());
}
}
c.cs
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class fff : Control {
int vvv1 = 0;
public int Min = 0;
public int Max = 100;
public bool ShowValue = false;
int dv = 0;
bool d = false;
public Color cs = Color.Red;
public Color ce = Color.LimeGreen;
Brush bb = null;
Brush bd = null;
public int vvv
{
get
{
if (d)
{
return dv;
}
return vvv1;
}
set
{
vvv1 = value;
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
bb=new LinearGradientBrush(new Point(0, 0),
new Point(ClientSize.Width, 0),cs,ce);
bd = new SolidBrush(Color.FromArgb(200, Color.Black));
e.Graphics.FillRectangle(bb, ClientRectangle);
Rectangle r = ClientRectangle;
float p = ((float)vvv / ((float)Max – (float)Min));
int a = (int)(p * (float)r.Width);
r.X += a;
r.Width -= a;
e.Graphics.FillRectangle(bd, r);
e.Graphics.Flush();
RectangleF r1 = new RectangleF();
SizeF ts = e.Graphics.MeasureString(Text, Font);
r1.Width = ts.Width;
r1.Height = ts.Height;
r1.X = (ClientRectangle.Width – r1.Width) / 2;
r1.Y = (ClientRectangle.Height – r1.Height) / 2;
e.Graphics.DrawString(Text, Font, new SolidBrush(ForeColor), r1);
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
Capture = true;
d = true;
sss(new Point(e.X, e.Y));
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if ( !d)
return;
sss(new Point(e.X, e.Y));
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if ( !d)
return;
Capture = false;
d = false;
vvv = dv;
}
void sss(Point l) {
Rectangle r = ClientRectangle;
float p = (float)l.X / (float)r.Width;
int a = (int)(p * (float)(Max – Min));
int o = dv;
dv = a;
float op = ((float)o / ((float)Max – (float)Min));
int ol = (int)(op * (float)r.Width);
float np = ((float)dv / ((float)Max – (float)Min));
int nl = (int)(np * (float)r.Width);
int mi = Math.Min(ol, nl);
int ma = Math.Max(ol, nl);
Rectangle r1 = new Rectangle(r.X + mi, r.Y, ma – mi, r.Height);
Invalidate(r1);
}
}
When we run the above program, we see a myriad of colors such as red, green and
black. We also see ‘Vijay Mukhi’ displayed in the center. On clicking the mouse,
more of the color black will be introduced upto the point of the mouse click on
the screen, whereas dragging the mouse will extend or diminish the colors
accordingly. This certainly is a sight for sore eyes!
Screen 4.40 Screen 4.41
We have to admit that the folks at Microsoft who worked on this sample did a
truly marvelous job. Our task here is to explain the program that they have
written, in absolute layman terms.
The h.cs file contains code that is already explained in the earlier program. As
always, along with the properties that are present in Control, we have also
initialized one of our own properties, vvv for control fff. The user-defined
property vvv is assigned a value of 73. The value contained in the Text property
is displayed at the center of the screen.
We now focus the spotlight on the actual code of our control fff that resides in
the file c.cs.
The container sets the value of the property vvv to 73. Therefore, in the
control, the property vvv gets called initially. The set accessor initializes
the int variable vvv1 to 73.
The function OnPaint is responsible for drawing the rainbow of colors on the
screen. In this function, we first call the original OnPaint function. Then, we
create two brushes to obtain the desired effect. The first brush is a
LinearGradientBrush, which can be accessed using the Brush object bb. The last
parameter to the constructor of this brush is the starting color cs, which has
been initialized to Red, and an ending color ce that is initialized to a value
of LimeGreen. The second brush is a SolidBrush bd, which is created to fill up
any shape. The function FromArgb of the Color class creates a new color, where
the first parameter is an alpha value 200 and the second parameter is the base
color.
The Graphics property of PaintEventArgs merely returns a Graphics object when we
call function FillRectangle. The two parameters to this function are : a Brush
that represents a gradient and the Size of the window whose value is stored in
the ClientRectangle property. If we stop at this stage, the red and the green
gradients will stand out glaringly.
Now, let us draw the black rectangle separating the red and green colors. We
create a temporary variable r to store the dimensions of the window.
Two instance variables, Max and Min having a value of 100 and 0 respectively,
are used to decide the factor by which the property vvv should be divided. Since
we need to retrieve the value for the property, the get accessor is called. The
value contained in d decides on the value to be assigned to the property.
Initially d is false, so the value 73 contained in vvv1 is returned.
The float variable p now has a value of .73. The Width of the window in our case
is 600 pixels. Thus, the variable ‘a’ is assigned a value of 438. You can use
the WriteLine function to display these values. The Width of the rectangle is
reduced by this amount and the X position is shifted by 438 pixels. Next, we
draw the black rectangle using the function FillRectangle by providing it with a
black brush and the new Rectangle object r. Modifying the value of the property
vvv from 73 to 3 will result in displaying the entire screen in black color.
Thus, the vvv parameter decides the size of the black rectangle; the smaller its
value, the larger will be the size of the rectangle. The Flush function ensures
that all pending graphic operations on the stack are executed immediately. If we
stop here, the gradient and the rectangle will be displayed, but no text will be
displayed in the center of the screen.
The Rectangular structure r1 stores the size and location of the rectangular
region that our string requires. The MeasureString function takes two
parameters:
• The first is a string value that is stored in our property Text.
• The second is a Font object.
The default Font property is supplied for the second parameter. The function
returns a SizeF object, whose Width and Height determine the display region that
the string requires. The width and height of rectangle r1 is initialized to the
Width and Height of the string. As the string is to be displayed in the center
of the form, objects X and Y, which are the members of rectangle r1, are given
the following values:
• The Width of the form = the width of the string divided by 2.
• The Height of the form = the height of the string divided by 2.
We then use the DrawString function to display the text. This function takes the
following parameters in the specified order:
• A string that is stored in the Text property.
• The default font.
• The color of the brush that is stored in the property ForeColor.
• The region, which is stored in object r1.
We have three mouse events that trigger off three functions. They are as
follows:
• OnMouseMove: When we move the mouse.
• OnMouseDown: When we click the left mouse button, with the mouse cursor placed
on the form.
• OnMouseUp: When we release the left mouse button.
If we move the mouse around, nothing happens. This is because the ‘if’ statement
results in a value of true, as the variable d has a value of false. So, the
function practically achieves nothing. Clicking the left mouse button toggles
the value of the variable d to true. Thus, if the variable d is True, we know
that the user has clicked the left mouse button, and when it reverts back to
false, we know that the left mouse button has been released. The OnMouseMove
function does something constructive and gainful only when the left mouse button
is depressed.
The Capture property reveals whether the control has been able to capture the
mouse or not. This property is optional. When we click within the window, the
framework is informed about our victory over the mouse, and the variable d is
set to true. The next most pertinent action is to call function sss with a Point
object. This object stores the position at which the mouse has been clicked. The
MouseEventArgs parameter e, has two members X and Y, which identify the current
position of the mouse.
The function sss is called yet again when the mouse is moved. The OnMouseUp
function changes the value of Capture, and sets the value of the boolean
variable d to false. It also initializes the value of a property vvv to dv.
The net achievement of the function sss is that, it calls the function OnPaint
using the Invalidate function. The OnPaint function does not draw or paint the
entire form or window, but only the specific part that has been invalidated.
This is done to achieve optimum efficiency, since it is preposterous to waste
effort in redrawing areas that have not been invalidated. The Invalidate
function is also passed a Rect object as a parameter, which takes a decision on
the area of the client rectangle that is to be re-drawn.
Function sss is therefore, given a smaller Rect object as a parameter, which
informs the OnPaint function about the specific part of the form that should be
invalidated. The Point parameter is employed to determine the area of the form,
that must be redrawn.
The co-ordinates of the entire screen are stored in ClientRectangle. We also
calculate a percentage p, depending upon our current position, i.e. l.X, divided
by the Width of the screen. Next, we multiply this percentage by 100 (the
difference between Max and Min). The value of dv is stored in variable o, since
we are initializing dv to ‘a’ in the next line.
Two new percentage values, op and np have been calculated. They are merely the
values of o and dv divided by 100. Thereafter, we calculate the minimum and
maximum values of ol and nl. The new rectangle created is the Client Rectangle,
where the minimum value is added to X, and Y is left untouched. The Width is set
to the difference of ma and mi, and the Height is left unaltered.
The difference between variables ma and mi is negligible. Correspondingly, the
width of the invalidated region too is insignificantly small. Further, mi is
used to decide the position at which the X of the black rectangle should
commence. Thus, if we replace r1 with ClientRectangle, everything would be hunky
dory, but the screen will flicker a great deal, while the mouse is in motion.
This is because the same screen has to be displayed when the movement ceases.
Thus, to thwart this flicker, we redraw only the specific part of the screen
that has been invalidated.
ScrollBar Control
a.cs
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form
{
VScrollBar v;
HScrollBar h;
PictureBox p;
Label l2;
Label l1;
bool d = false;
int ox, oy;
float vm;
float vp;
float hm;
float hp;
public zzz()
{
ClientSize = new System.Drawing.Size(520, 277);
l1 = new Label();
l1.Location = new System.Drawing.Point(408, 160);
l2 = new Label();
l2.Location = new Point(408, 184);
v = new VScrollBar();
v.Location = new Point(200, 24);
v.Size = new Size(16, 152);
v.Scroll += new ScrollEventHandler(vScroll);
v.Minimum = -100;
h = new HScrollBar();
h.Location = new Point(16, 176);
h.Size = new Size(184, 16);
h.Scroll += new ScrollEventHandler(hScroll);
h.Minimum = -100;
p = new PictureBox();
Bitmap b = new Bitmap("water.bmp");
p.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
p.Location = new Point(64, 32);
p.Size = new Size(96, 96);
p.Image = (Image)b;
p.MouseDown += new MouseEventHandler(pDown);
p.MouseUp += new MouseEventHandler(pUp);
p.MouseMove += new MouseEventHandler(pMove);
Controls.Add(l2);
Controls.Add(l1);
Controls.Add(h);
Controls.Add(v);
Controls.Add(p);
v.LargeChange = 20;
h.LargeChange = 20;
v.SmallChange = 1;
h.SmallChange = 1;
vmf();
hmf();
l1.Text = h.Value.ToString();
l2.Text = v.Value.ToString();
p.Cursor = Cursors.SizeAll;
}
void vmf()
{
float hsb = (float)(v.Height – p.Height);
float ticks = (float)(v.Maximum – v.Minimum);
vm = hsb / ticks;
}
void hmf()
{
float hsb = (float)(h.Width – p.Width) ;
float ticks = (float)(h.Maximum – h.Minimum) ;
hm = hsb / ticks ;
}
void vScroll(object sender, ScrollEventArgs e)
{
l2.Text = v.Value.ToString() ;
vp = (float)(v.Value- v.Minimum);
p.Top = v.Bottom – (int)(vm * vp) – p.Height;
}
void hScroll(object sender, ScrollEventArgs e)
{
l1.Text = h.Value.ToString() ;
hp = (float)(h.Value – h.Minimum);
p.Left = h.Right – (int)(hm * hp) – p.Width;
}
void pDown(object s, MouseEventArgs e)
{
d = true;
ox = e.X;
oy = e.Y;
}
void pMove(object s, MouseEventArgs e)
{
if (d)
{
int minY = v.Minimum;
int maxY = v.Maximum;
int minX = h.Minimum;
int maxX = h.Maximum;
int value = (int)(v.Value – (e.Y – oy)/vm);
if (value < minY)
{
v.Value = minY;
}
else if (value > maxY – v.LargeChange + 1)
{
v.Value = maxY – v.LargeChange + 1;
}
else
{
v.Value = value;
}
value = (int)(h.Value – (e.X – ox)/hm);
if (value < minX)
{
h.Value = minX;
}
else if (value > maxX – h.LargeChange + 1)
{
h.Value = maxX – h.LargeChange + 1;
}
else
{
h.Value = value;
}
l1.Text = h.Value.ToString() ;
l2.Text = v.Value.ToString() ;
value = p.Top + (e.Y – oy);
if (value < v.Top)
{
value = v.Top;
}
else if (value > v.Bottom – p.Height)
{
value = v.Bottom – p.Height;
}
p.Top = value;
value = p.Left + (e.X – ox);
if (value < h.Left)
{
value = h.Left;
}
else if (value > h.Right – p.Width)
{
value = h.Right – p.Width;
}
p.Left = value;
}
}
void pUp(object s, MouseEventArgs e)
{
d = false;
}
public static void Main()
{
Application.Run(new zzz());
}
}
>csc a.cs
Before running the program, copy the file water.bmp into the current directory.
This file is provided along with the samples in the installation program. The
singular methodology to comprehend large programs is by first examining their
output, because the output will motivate you to grasp the program.
We see two scrollbars, one vertical and the other horizontal, with a picture
within them. Clicking on the scrollbars will move the picture in the direction
of the scrollbar that is clicked. Let us understand how this is achieved.
Screen 4.42
We start by examining the constructor of class zzz. The default client size is
modified to the desired size. Then, there are two labels l1 and l2, which
display the current values of the two scroll bars. A vertical scrollbar v is an
instance of a class VScrollBar, and a horizontal scrollbar h is an instance of a
class HScrollBar. As is customary, we provide a Location and a Size to the
scrollbar. The Minimum and Maximum properties decide the range of values that
the user can select. Most controls such as a Text box or a Combo box already
have a scroll bar built into them. Hence, they do not require this control. The
Scroll event gets triggered whenever the scroll button moves. The button can be
moved either by using a mouse or by the keyboard. Moving the vertical scroll
button will call the vScroll function, whereas moving the horizontal scroll
button will call the hScroll function.
After setting the properties of the Scroll Bar, we create an object p, which is
an instance of a PictureBox class. Next, we create a Bitmap object, and in the
constructor, we pass the file water.bmp to it. The SizeMode decides on the
display of the image in the Picture Box. The enum value of StretchImage
stretches the image to fit it into the PictureBox, whereas, the enum value of
Normal places it in the upper left corner.
The image property is assigned to the picture or the bitmap that is to be
displayed. Three event handlers are attached to the image:
• pDown : This is called each time we click in the picture.
• pUp : This is called when we release the mouse button.
• pMove : This is called when we move the mouse within the picture.
Finally, all the controls i.e. two labels, two scroll bars and one image, are
appended to the Control class.
The property of LargeChange in the scrollbar control decides on the magnitude of
the change in the value of the scroll bar, when the scroll bar is clicked. The
SmallChange property is associated with the arrows on the scroll bar. We have
set the LargeChange property to 20 and the SmallChange to 1.
To dispel the banality of the somber explanation given above, let us digress
slightly to present a small, albeit important, elucidation on how Windows
handles the art of scrolling.
Screen 4.43
The value of the scrollbar ranges between the minimum and the maximum. The
maximum value is LargeChange+1. The rationale behind this is that, the scrollbar
has a property called Value, which represents the position of the top of the
thumb. The size of the scrollbar’s thumb is equal to the page value or
LargeChange. When we reach the last page, or when the thumb reaches the end of
the scroll bar, whereupon, we cannot scroll any further, the value property will
always be shown less than the maximum.
We shall now beguile you with the concepts of the vertical scroll bars. The
explanation for the horizontal scroll bars is much the same.
The function vmf, which is called from the constructor, initializes 3 variables.
The Height of the Vertical scroll bar is 152 pixels because the Size property of
the scrollbar is initialized to this value. The Height of the picture is 96
pixels because the Size property of the picture is initialized to this value.
The difference between these two heights, which is 56 pixels, is stored in the
variable hsb. The variable named ticks stores a value, which is within a range
that the vertical scroll bar can handle.
A Tick happens to be the smallest increment that a scrollbar can make. In our
case, as the Minimum is -100 and the Maximum is 100, the range of value stored
in ticks becomes 200. This variable represents the ticks that the scroll bar
needs in order to move from one end to the other. In the same vein, the variable
hsb denotes the amount of pixels the image needs to be relocated from one end of
the scroll bar to another. This is calculated by subtracting the height of the
image from the height of the scroll bar.
Screen 4.44
Dividing 56 (the number of pixels to be moved) by 200 (the total number of ticks
available), gives us a value of .28. Thus, every tick moved by the scroll bar
moves the image by .28 pixels. This value, known as the ‘pixels per tick’, is
stored in vm. A similar routine is followed for the hmf function while
implementing a horizontal scroll bar.
The labels initially display zero, since this is the default value stored in the
property value. The cursor property of the picture is changed to SizeAll. As a
result of this, whenever the cursor moves into the picture, it’s shape changes
into a four-headed monster.
At the outset, we want to move the picture downwards. So, we click on the
vertical scroll bar. This movement calls the vScroll function. In this function,
we initialize the Text property of the label l2 to the Value property of the
vertical scroll bar v. Then, variable vp is calculated, whose value is the
current value of the scroll bar, i.e. the value of the Minimum property of the
scroll bar.
Every downward movement of the scroll bar increments the Value property by 1.
This is because, the value of property SmallChange has been initialized to 1.
Thus, the value of vp will commence at 100 and will keep escalating thereafter.
The value stored in the Bottom property of the scroll bar is 176 and the Height
of the picture is 96 pixels. Thus, the picture will start at 52 pixels, and
then, the distance between the picture and the bottom property will start
reducing.
The formula for computing the top position where the picture should be placed is
as follows: the Bottom of the scroll bar, minus the pixels per tick, multiplied
by the current value of the property Value, minus the Height of the picture,
plus the value of Minimum.
Now, let us do the reverse i.e. let us move the picture around and see how the
scroll bars behave. Also, let us observe the corresponding transformation in the
values. To move the picture, we first have to click on it with the left mouse
button. This calls function pDown. In this function, the value of variable d,
which is a boolean, is set to true. Simultaneously, in the function pUp, the
value of variable d is set to false.
The parameter e of MouseEventArgs has two members X and Y, which furnish the
co-ordinates of the mouse pointer with regard to the picture, rather than the
window or form. We save these values in the variables ox and oy, which will be
utilized to calculate the distance that the picture has been dragged.
The function pMove is the focus of all attention since, this is where the real
excitement action lies! We place all the code in a large if statement, which
results in true when the variable d is true. This occurs only when the mouse
button is depressed. Each time the function is called, the minimum and maximum
values of the scroll bars are stored in 4 variables. This is futile, since the
values always remain constant.
We calculate a variable called Value as follows:
The initial Y position of the mouse before its dragging commenced, minus the
current Y position of the mouse. The result is then divided by the multiplier to
convert the pixels into scroll bar ticks. This simulates the scroll bar
scrolling in the opposite direction.
If the new value of variable Value is less than the minimum value allowed for
the scroll bar, the property of the scroll bar v is set to the minimum possible.
If the value is larger than the maximum permissible value, then it is
initialized to the largest possible value. This concept has been explained in
the small note we earlier presented on scrolling. If none of the above hold
true, then we merely change the property Value of the scroll bar to the variable
Value. These are simple error checks. The labels l1 and l2 are also updated.
The above explanation is also relevant for the horizontal scroll bar.
The property Top of the picture has to be updated to take into account the new
position. This is also stored in the variable value and computed as follows:
The original Top property value, plus the current position of the mouse, minus
the position of the mouse before the dragging commenced.
Like before, we make sure that we do not exceed the Top and Bottom limits of the
scroll bar. The picture has to be contained within the scroll bars. If it is
smaller than the Top property of the scroll bar, we change the value to that of
the Top property. The same holds true for the Bottom. Finally, we initialize the
Top property of the picture to Value and do the same for the Left property.
Up Down Control
a.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form {
DomainUpDown u;
NumericUpDown n;
DomainUpDown a;
public zzz() {
ClientSize = new Size(504, 352);
n = new NumericUpDown();
n.Location = new Point(132, 132);
n.Maximum = new System.Decimal(100d);
n.Minimum = new System.Decimal(0d);
n.DecimalPlaces = 2;
n.Text = "0.00";
Controls.Add(n);
a = new DomainUpDown();
a.Location = new Point(152, 32);
a.Size = new Size(120, 23);
a.SelectedItemChanged += new EventHandler(abc);
Controls.Add(a);
u = new DomainUpDown();
u.Location = new Point(152, 64);
u.SelectedItemChanged += new EventHandler(pqr);
Controls.Add(u);
a.Items.Add(new yyy("Center",(int) HorizontalAlignment.Center));
a.Items.Add(new yyy("Left",(int)HorizontalAlignment.Left));
a.Items.Add(new yyy("Right",(int)HorizontalAlignment.Right));
u.Items.Add(new yyy("Left",(int)LeftRightAlignment.Left));
u.Items.Add(new yyy("Right",(int)LeftRightAlignment.Right));
u.SelectedIndex = 1;
}
void pqr(object s, EventArgs e)
{
yyy c = (yyy)(u.Items[u.SelectedIndex]) ;
n.UpDownAlign = (LeftRightAlignment)(c.i);
}
void abc(object s, EventArgs e)
{
yyy c = (yyy)(a.Items[a.SelectedIndex]) ;
n.TextAlign = (HorizontalAlignment)(c.i);
}
public static void Main()
{
Application.Run(new zzz());
}
class yyy
{
public string s;
public int i;
public yyy(string sz, int n)
{
s=sz;
i=n;
}
public override string ToString()
{
return s;
}
}
}
Screen 4.45
In the above program, we have a NumericUpDown control called n. A NumericUpDown
control is used when we have a single numeric value that needs to be incremented
or decremented.
Screen 4.46
This can be done by clicking on the Up and Down buttons of the control
respectively. This control has a property called ReadOnly, which has a value of
False by default.
This allows entry of a value directly in the control if the user finds it too
bothersome to click on the buttons to do so. From this perspective, it operates
akin to a text box. The properties Minimum and Maximum specify the minimum and
maximum values that the control can accept. Under no circumstances will the
control permit us to exceed the range specified by the above two properties.
Thus, error checking has been built into the control.
A NumericUpDown control has a large number of properties such as DecimalPlaces,
Hexadecimal, ThousandsSeparator etc. These properties format the value that is
displayed using the Text property.
Their roles are as follows:
• The DecimalPlaces property controls the number of decimal places.
• The Hexadecimal property displays numbers in hexadecimal format.
• The ThousandsSeparator property decides on the character to be used to
separate the 1000s. This is because the comma is not the universally accepted
separator.
The Increment property of the control which has a default value of 1 decides on
the amount of increase/decrease in the number. each time we click on the up or
down buttons. n.Increment=10; will increase the number by 10. The two functions
ParseEditText and UpdateEditText get called with every change in the number.
Thereafter, two DomainUpDown controls, which behave in a manner similar to a
NumericUpDown control, are created. These controls display a string instead of a
number. Thus, the value passed to this control can be of any class, since all
classes are derived from object. The user can also type in text directly. The
value typed in must obviously match an item in the collection. The ToString
function of the object is called, to display the value in the up-down control.
The DomainUpDown control has a property called Items, that returns a
DomainUpDown.DomainUpDownItems object. This object represents the object
collection. Thus, we can use the Add or Remove methods from the above collection
to add or remove the items individually. The Sort property sorts the collection.
We pass an object like yyy to the Add function, which accepts a string and an
enum called HorizontalAlignment. The enum value is stored in the variable i to
facilitate its retrieval at a later stage.
Each time we click on the control, function abc gets called. This is because the
SelectedItemChanged event has been initialized to this function. In this
function, we use the property SelectedIndex, which returns a number depicting
the item that has been selected. The property is used as an array index. It
returns the yyy object at that index. We store this object in c and then, access
the variable i stored in the object yyy.
Thus, the entire object yyy is stored as part of the collection. The string and
the int can both be accessed together. If the SelectedIndex property is not set,
it will not display any value in the control.
DateTimePicker Control
a.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form {
DateTimePicker d;
public zzz() {
ClientSize = new Size(504, 293);
d = new DateTimePicker();
d.Location = new Point(24, 24);
d.CalendarFont = new Font("Times New Roman", 8f);
d.Size = new Size(200, 20);
d.CalendarForeColor = System.Drawing.SystemColors.WindowText;
d.ShowCheckBox = true;
d.ForeColor = System.Drawing.SystemColors.WindowText;
d.Format = System.Windows.Forms.DateTimePickerFormat.Custom;
d.BackColor = System.Drawing.SystemColors.Window;
d.CustomFormat = "\’Date : \’yy MM d – HH\’:\’mm\’:\’s ddd";
d.Anchor = AnchorStyles.Top|AnchorStyles.Right | AnchorStyles.Left;
DateTime now = DateTime.Now;
d.Value = now;
Controls.Add(d);
}
public static void Main()
{
Application.Run(new zzz());
}
}
Screen 4.47 Screen 4.48
On running the above program, we see a listbox displaying a date. This date is
the system date. You may wonder as to what is the big deal about displaying a
simple date. But, when you click on the down arrow of the list box, a
fine-looking calendar springs up! The current date is shown highlighted in red.
If you click on any date, it will be instantly displayed in the list box. Once
the date has been chosen, the calendar vanishes. You can change the month by
clicking on the arrows, which are on the left and right of the month.
Now, let us get behind the scenes and unravel the mysteries of this program. We
first create an object d as an instance of class DateTimePicker, thus
encapsulating the standard Windows date time picker control. The control has all
the standard properties like Location, Size, Color etc. The default size, in the
case of the DateTimePicker, is a width of 200 pixels and a height of 23 pixels.
The colors of the control can be changed using CalendarForeColor and ForeColor.
The property CalendarFont decides on the font to be used to display the dates.
Screen 4.49
The ShowCheckBox property , which has the default value of false, is set to
true, in order to display a check box on the extreme left of the date displayed
in the control. The checkbox presently is checked.
If the checkbox is checked, the date is valid. If not, the date is said to be
unset.
The Format property of the control, which is initialized to Custom, decides the
display format of the date and time in the control. This property can be
initialized to any four of the following enums: Custom, Short, Long and Time.
The last three values use the operating system’s format options.
Since the Format property has been given the value of Custom, the property
CustomFormat decides on the display format of the date in the control. This
property is initialized as follows: The word Date:, followed by the year, month
and day separated by a space, followed by a minus sign and finally, the time.
The three ‘ddd’ represent the day of the week in words.
The Now property of the DateTime class, that returns the current day, is finally
assigned to the Value property of the picker class, which then gets displayed in
the control.
LinkLabel Control:
a.cs
using System;
using System.Windows.Forms;
using System.Drawing;
public class zzz : Form {
PropertyGrid pg;
LinkLabel l;
Panel p;
GroupBox g;
public zzz() {
l = new LinkLabel();
l.DisabledLinkColor = (Color)System.Drawing.Color.Blue;
l.ForeColor = (Color)System.Drawing.Color.Gainsboro;
l.Location = new System.Drawing.Point(32, 128);
l.BackColor = (Color)System.Drawing.Color.Transparent;
l.LinkArea = new LinkArea(13, 28);
l.Font = new System.Drawing.Font("Tahoma", 12f, System.Drawing.FontStyle.Bold,
System.Drawing.GraphicsUnit.World);
l.Text = "please click on sonal to see a message box";
l.Size = new System.Drawing.Size(136, 96);
l.LinkClicked += new LinkLabelLinkClickedEventHandler(abc);
ClientSize = new System.Drawing.Size(504, 445);
pg = new PropertyGrid();
pg.Dock = System.Windows.Forms.DockStyle.Fill;
pg.Location = new System.Drawing.Point(3, 16);
pg.CommandsVisibleIfAvailable = true;
pg.Text = "propertyGrid1";
pg.Size = new System.Drawing.Size(242, 405);
pg.SelectedObject = l ;
g = new GroupBox();
g.Location = new System.Drawing.Point(248, 16);
g.Anchor = AnchorStyles.Top|AnchorStyles.Right | AnchorStyles.Left;
g.Text = "LinkLabel Properties";
g.Size = new System.Drawing.Size(248, 424);
g.Controls.Add(pg);
Controls.Add(g);
p = new Panel();
p.Size = new System.Drawing.Size(200, 320);
p.Location = new System.Drawing.Point(24, 40);
p.BackgroundImage = (Bitmap) new Bitmap("hikingboot.bmp");
p.Controls.Add(l);
Controls.Add(p);
}
void abc(object sender, LinkLabelLinkClickedEventArgs e)
{
MessageBox.Show("hi") ;
l.LinkVisited = true ;
}
public static void Main() {
Application.Run(new zzz());
}
}
To avoid any exception from being thrown, you should copy the file
hikingboot.bmp to the current directory. Though the above program is not very
sizeable, its achievements are substantial. The output of this program shows our
screen divided into two parts as follows:
(a) The left pane has a picture in the background with some text that behaves
like a hyperlink. Clicking on the hyperlink displays a message box with the
message ‘hi’ and changes the color of the hyper link.
Screen 4.50
(b) On the right hand side, we come across a large number of properties. When we
click on the plus sign in front of the font property, yet more properties get
displayed. On varying the Font, the font of the text displayed in the window on
the left side changes. You can experiment with the other properties too.
Thus, you can achieve a lot without writing tons of code!
l is an instance of LinkLabel, which relates to text that can be displayed as a
hyper link. The DisabledLinkColor property of LinkLabel decides the color of the
hyperlink when it is disabled. The ForeColor and the BackColor specify the
foreground and the background color of the control respectively.
We may not intend to display the entire string as a hyperlink. So, we use the
LinkArea property to ascertain the text that is to be hyper linked. The default
position begins at 0,0.
The GraphicsUnit enumeration in the Font property specifies a unit of
measurement. The value displayed uses 1/75 of an inch, since the unit of measure
selected is World. We could also use any of the other 6 values in the enum, such
as pixel, millimeter etc.
Screen 4.51
The Text property is the most significant property, as it decides the text to be
displayed. The LinkClicked is the event property that will call function abc
each time we click on the link. In this function, we display a Message Box and
change the LinkVisited property to True, which results in a change in the color
of the hyper link. You may observe that this LinkLabel instance l is not added
to the form. Next, we create an instance of a PropertyGrid class and store it in
pg.
The Dock property decides on the edge of the container that this property will
be docked to. The usage of the Fill style allows docking of the property on all
sides.
The property CommandsVisibleIfAvailable displays the command pane only for those
objects that expose verbs. As always, the Text property is initialized to some
text that does not get displayed. The property of SelectedObject is assigned the
LinkLabel control, thereby linking it into the grid and facilitating browsing of
the LinkLable control properties. This property also allows us to browse
multiple objects. Finally, we add this object to the Form, thereby indirectly
adding the hyperlink also.
A group box is merely an anthology of other controls. The Group box control is
the one that contains the PropertyGrid object. We also create an instance of a
Panel object, which, like a GroupBox class, contains other controls. If the
Enabled property in the panels is set to False, all the controls within it will
be disabled. The panel control, by default, is drawn without any Borders. The
BorderStyle property provides us with two-dimensional or three-dimensional
borders, to distinguish a panel from other areas of the form. The Panel class
can also contain scrollbars.
The Panel class has a property called BackgroundImage that selects the picture
to be displayed in the panel. This Panel class has the LinkLabel added to it, to
which we add the Panel. Thus, we have two controls that have been added directly
to the form, the Panel that has the LinkLabel and the GroupBox that has the
PropertyGrid.
ListBox Control
a.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form
{
sss p;
ListBox l;
Button b;
ColorDialog c;
ImageList i;
public zzz()
{
b = new Button();
b.Location = new System.Drawing.Point(16, 200);
b.Text = "Color";
b.Size = new System.Drawing.Size(75, 23);
b.Click += new EventHandler(abc);
Controls.Add(b);
c = new ColorDialog();
p = new sss();
p.Location = new System.Drawing.Point(64, 90);
p.Size = new System.Drawing.Size(64, 168);
Controls.Add(p);
i = new ImageList();
i.ImageSize = new Size(24, 22);
i.Images.Add(new Bitmap("club.bmp"));
i.Images.Add(new Bitmap("diamond.bmp"));
i.Images.Add(new Bitmap("heart.bmp"));
i.Images.Add(new Bitmap("spade.bmp"));
Size = new System.Drawing.Size(512, 320);
l = new ListBox();
l.ForeColor = (Color)System.Drawing.SystemColors.WindowText;
l.Location = new System.Drawing.Point(8, 24);
l.IntegralHeight = false;
l.Size = new System.Drawing.Size(232, 60);
l.ColumnWidth = 144;
l.SelectedIndexChanged += new EventHandler(pqr);
l.Items.AddRange (new object[] {"a1", "a2", "a3", "a4"});
l.SelectionMode = SelectionMode.MultiSimple;
Controls.Add(l);
}
void abc(object sender, EventArgs e)
{
if (c.ShowDialog() == DialogResult.OK)
{
l.ForeColor = c.Color;
}
}
void pqr(object sender, EventArgs e)
{
p.ci();
int[] se = new int[l.SelectedIndices.Count];
l.SelectedIndices.CopyTo(se, 0);
for (int i=0; i<se.Length; i++)
{
int ind = se[i];
object it = l.Items[ind];
string s = it.ToString();
Image im = aaa(s);
p.ai(im);
}
p.Invalidate();
}
Image aaa(string b)
{
if (b.Equals("a1"))
{
return i.Images[0];
}
else if (b.Equals("a2"))
{
return i.Images[1];
}
else if (b.Equals("a3"))
{
return i.Images[2];
}
else if (b.Equals("a4"))
{
return i.Images[3];
}
else
{
return null;
}
}
public static void Main()
{
Application.Run(new zzz());
}
}
public class sss : Panel
{
Image[] i = new Image[4];
int Cnt=0;
public virtual void ai(Image img)
{
i[Cnt++] = img;
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
for (int j=0; j< Cnt; j++)
{
pe.Graphics.DrawImage(i[j], new System.Drawing.Point(0, 30 * j + 5));
}
}
public virtual void ci()
{
Cnt = 0;
}
}
Before running the executable file, the 4 files with the ‘bmp’ extension must be
copied from one of the sample directories into the current directory.
Screen 4.52 Screen 4.53
When we run the above program, we see a list box with 4 values and a button
labeled ‘Color’. When we click on the button, it displays a color dialog box,
which permits us to choose from amongst a plethora of colors. We can select a
color and then, click on the OK button. As a result, the color of the list box
items changes to the selected color. We are also allowed to create our own
custom colors in the dialog box.
Screen 4.54 Screen 4.55
Each time we select an item from the list box, an image representing the item is
displayed. If we reselect an item that we had selected earlier, the currently
selected image gets replaced by the latest selected one.
In the program, a Button b is created. It calls function abc whenever it is
clicked. Then, an instance c of class ColorDialog is created. We have created
the class sss. It is derived from the class Panel. It has a member i, which is
an array of 4 images and an int variable named Cnt. The variable Cnt is used as
an index to access the images in the array, and to store the count of the number
of images. When we add the sss object to our Form, nothing gets displayed in the
window. This is because, there are no controls in the panel.
Next, we create an ImageList object called i. This class stores a collection of
images that can be used by other controls such as the Toolbar or ListView.
Bitmaps or Icons are added to this class so that they are available for
exploitation by other controls. Images is a property of type
ImageList.ImageCollection, whose Add method is used to add a bitmap to the
collection. We add a total of 4 bitmaps to our ImageList object.
The ListBox object l has a property ColumnWidth, that decides on the width, in
pixels, of each column in the ListBox. In a multi-column ListBox, the
ColumnWidth property refers to the width of each column. A value of zero is
connotative of the fact that each column has a default width, taking into
account the data displayed in them. The property IntegralHeight decides whether
partial items can be displayed or not. If the value is set to true, then only
complete items are displayed. In this case, the ListBox will be resized to
ensure that partial items are not displayed.
Each time we select an item, the Event SelectedIndexChanged gets kick started.
This event, in turn, calls function pqr. Every list box has a property called
Items, which is a collection of items displayed in the list box. The data type
of Items is ListBox.ObjectCollection. It has a method called AddRange, which
accepts an array of Child Controls present in the list, sorted on the index.
Since we want the list box to display the text a1, a2, a3 and a4, we initialize
the object array to this array of strings.
The property SelectionMode decides on the number of items that can be chosen or
selected concomitantly.
Four options available:
• None : This means that no items can be selected. In effect, it disables the
ListBox.
• One : This allows us to select only one item at a time,
• MultiSimple : This lets us choose multiple items at the same time
• MultiExtended : This enables us to choose multiple items at a time and allows
us to use keys like SHIFT and CONTROL combinations to select the multiple
options.
We finally add the list box to the Form. Clicking on the button labeled Color
calls ShowDialog from the ColorDialog class, which displays a dialog box with a
zillion colors. Until we click on the OK button or the Cancel button, we cannot
leave the dialog box.
The button returns one of two values:
• DialogResult.OK if we click on OK button.
• DialogResult.CANCEL if we click on the Cancel button.
If the user opts to click on the OK button, the color selected is assigned to
the ForeColor property of the ListBox.
The function pqr is the vortex of action. The function is called when any item
is selected or unselected from the list box. We first call function ci from the
class sss, which initializes the variable Cnt to zero.
The SelectedIndices property returns a collection object named
ListBox.SelectedIndicesCollection, which lists all the items that are currently
selected in the list box. This is so because we can select multiple items from
the list box. First, an array called se of type int is created, depending on the
number of items selected. Then, the selected indices are copied to the array.
The variable ind in the for statement refers to each index selected. The Items
array returns an object that stores the value of each index. As we need the
string representation of the selected item, function ToString is used to convert
the object into a string. This facilitates the possibility of having a list box
containing pictures.
We then call the function aaa that accepts a string representing the ListBox
item selected, which can be either a1, a2, a3 or a4. It returns an image
representing the string. It then checks the value of the string passed and
returns an image stored in the ImageList object. The function ai merely uses the
variable Cnt to index the array of images with a new picture. Thus, if we select
three images, the array i will contain three pictures. When the for loop
terminates, we call the function Invalidate.
As we had learnt earlier, function OnPaint displays all the pictures. This is
achieved using the Cnt variable that contains the number of images stored in the
Array. Thus, we use the ImageList class to store the images and the array of
images in the sss class to store the images that need to be displayed each time.
StatusBar control
a.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form {
System.ComponentModel.Container c;
StatusBar s;
StatusBarPanel s1;
StatusBarPanel s2;
StatusBarPanel s3;
Timer t;
public zzz() {
c = new System.ComponentModel.Container();
s2 = new StatusBarPanel();
t = new Timer(c);
s = new StatusBar();
s3 = new StatusBarPanel();
s1 = new StatusBarPanel();
s2.AutoSize = (StatusBarPanelAutoSize)2;
s2.Alignment = HorizontalAlignment.Right;
s2.Width = 76;
s3.AutoSize = StatusBarPanelAutoSize.Contents;
s3.Width = 20;
s1.BorderStyle = StatusBarPanelBorderStyle.None;
s1.Icon = new Icon("status.ico");
s1.AutoSize = StatusBarPanelAutoSize.Contents;
s1.Width = 62;
s1.Text = "sonal";
Size = new System.Drawing.Size(512, 320);
KeyUp += new KeyEventHandler(abc);
t.Interval = 1000;
t.Enabled = true;
t.Tick += new EventHandler(pqr);
s.Size = new Size(212, 20);
s.Location = new Point(0, 216);
s.BackColor = (Color)SystemColors.Control;
s.Text = "Vijay Mukhi";
s.ShowPanels = true;
s.Panels.AddRange((StatusBarPanel[])new StatusBarPanel[] {s1, s2, s3});
Controls.Add(s);
s3.Text = "OVR";
}
void abc(object se, KeyEventArgs e)
{
if (e.KeyCode == Keys.Insert)
{
string s = this.s3.Text;
if (s.Equals("INS"))
s3.Text = "OVR";
else
s3.Text = "INS";
}
}
void pqr(object se, EventArgs e)
{
DateTime t = DateTime.Now;
string s = t.ToLongTimeString() ;
s2.Text = s ;
}
public static void Main()
{
Application.Run(new zzz());
}
}
Every Windows application displays a StatusBar control. This program is going to
introduce the status bar in our window. In order to accomplish this, the file
status.ico needs to be copied from one of the sample directories into the
current directory.
The above program merely presents a blank window with a status bar at the
bottom. The status bar has the following contents:
• an icon.
• the word ‘sonal’.
• the time ticking away in the middle.
• the status of the Insert key.
Screen 4.56 Screen 4.57
Each time we use the insert key, the text toggles between INS and OVR.
The class Container encapsulates zero to one or more components. We create three
status bar objects s1, s2 and s3, which are instances of class StatusBarPanel.
This class, in turn, is derived from class Component. It stores the StatusBar
control panel information. The object s is an instance of class StatusBar, which
represents a Window status bar control. This control has no panels by default.
The property AutoSize in the StatusBarPanel class regulates the changes
occurring in the panel of a status bar, whenever the status bar is resized. The
values are obtained from the enumeration StatusBarPanelAutoSize that has the
following three values:
• Contents: As is evident from the name, the contents of the status bar decide
how its size will change.
• None: The status bar panel does not change whenever the status bar is resized.
• Spring: The panel shares the available space with all other panels having a
setting of Spring, after yielding space to the panels having either the Contents
or None settings.
Text can be aligned in a status bar panel in 3 ways with reference to the status
bar, i.e. Left, Right and Center. The Alignment property, by default, has a
value of Left. For the status bar panel s2, we have set it to Right. The default
width is 100 pixels for a status bar panel. An Icon can be specified along with
the text that is displayed. To do so, we initialize the Icon property to a .ico
file. An .ico file is a small transparent bitmap image.
The KeyUp Event is fired whenever a key is released in the focused control. In
our case, function pqr is called. The Timer class, that implements a Windows
timer, merely activates an event at a particular time interval. This timer,
which can be used in a window, is designed for a single threaded environment
only, where User Interface threads are being executed. The Interval property
decides the time, in milliseconds, between timer ticks. The Enabled property
sets the timer On. The garbage collector does not interfere with the timer while
it is running. The Event Tick will call the function pqr whenever the time
interval set in the timer elapses. In our case, it is occurs after one second.
The StatusBar is made up of panels. The ShowPanels property displays all the
panels that are added using the AddRange method, i.e. s1, s2 and s3. Finally,
the text of the last panel is set to OVR and the StatusBar object is added to
the form.
The function abc gets called each time we press a key in our form. This function
is called with the parameter e of KeyEventArgs, which has a member KeyCode,
which contains a number corresponding to the key pressed. As it is difficult to
remember the numbers assigned to every key, the enumeration Keys is used to
represent the keys. If the Insert key is pressed, the current Text displayed in
the status bar panel s3 is retrieved and the value gets toggled from OVR to INS.
On completion of the timer interval, we use the Now property of the DateTime
class to provide us with the current time. This is then supplied to s2.Text,
which updates the status bar panel with the current time every second. What you
can display in a status bar is limited by your imagination. It is common to
display the status of keys and the time on the status bar.
Tab Control
a.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form {
GroupBox g2;
GroupBox g1;
ImageList i;
TabPage t1;
TabPage t2;
TabControl t;
public zzz()
{
ClientSize = new Size(546, 293);
g1 = new GroupBox();
g1.Location = new Point(12, 16);
g1.Text = "Sonal";
g1.Size = new Size(202, 144);
g2 = new GroupBox();
g2.Location = new Point(12, 16);
g2.Text = "Vijay mukhi";
g2.Size = new Size(202, 128);
i = new ImageList();
t1 = new TabPage();
t2 = new TabPage();
t = new TabControl();
t1.Text = "Mukhi";
t1.Size = new Size(224, 193);
t1.ImageIndex = 0;
t1.TabIndex = 0;
t2.Text = "Vijay";
t2.ImageIndex = 1;
t2.TabIndex = 1;
t.Location = new Point(24, 32);
t.Size = new Size(232, 220);
t.SelectedIndex = 0;
t.ImageList = i;
i.Images.Add((Bitmap)new Bitmap("calendar.bmp"));
i.Images.Add((Bitmap)new Bitmap("note.bmp"));
t.ImageList = i;
Controls.Add(t);
t1.Controls.Add(g1);
t2.Controls.Add(g2);
t.Controls.Add(t1);
t.Controls.Add(t2);
}
public static void Main()
{
Application.Run(new zzz());
}
}
This program requires two bitmaps called note.bmp and calendar.bmp.
We start by creating two GroupBox controls named g1 and g2, and change the
properties of Locations, Size and Text. The ImageList i stores a list of images.
We next create two TabPage objects t1 and t2. A TabPage class implements a
single page of the TabControl class. It is a panel class having the properties
of a TabItem.
Screen 4.58 Screen 4.59
The string assigned to the Text property is displayed as the tab page text. We
can set it to a certain size. The ImageIndex property is an index into the
ImageList list object, which is associated with the TabControl. The TabPage
objects are added to the TabControl using the Controls collection. The ImageList
property is initialized to the list of images denoted by the ImageList class.
Thus, the ImageIndex member decides on the image that will be displayed along
with the Text in the TabControl.
A TabControl shows a list of TabPages. Clicking on the tab activates them. A
TabControl, in other words, is like a series of Dialog boxes containing
controls, organized in a logical fashion. The controls that would earlier have
appeared in one large dialog box, are now placed in separate dialog boxes.
As mentioned earlier, we add the GroupBox to the Control collection of the
individual TabPage objects t1 and t2. The controls that we require are added to
the GroupBox. The GroupBox, in turn, gets added to the TabPage. From then on,
the TabControl takes over and we can flick between TabPages with ease.
ToolTip Control
a.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form {
System.ComponentModel.Container c;
ToolTip t;
PictureBox p1;
PictureBox p2;
public zzz()
{
c = new System.ComponentModel.Container();
t = new ToolTip(c);
t.Active = true;
t.ShowAlways = true;
t.AutomaticDelay = 100;
t.AutoPopDelay = 100;
t.InitialDelay = 100;
t.ReshowDelay = 100;
p1 = new PictureBox();
p2 = new PictureBox();
Size = new Size(512, 300);
p1.Location = new Point(8, 7);
p1.Size = new Size(20, 20);
p1.Image = new Bitmap("open.bmp");
t.SetToolTip(p1, "vijay");
Controls.Add(p1);
p2.Location = new Point(28, 7);
p2.Size = new Size(20, 20);
p2.Image = new Bitmap("new.bmp");
t.SetToolTip(p2, "mukhi");
Controls.Add(p2);
}
public static void Main()
{
Application.Run(new zzz());
}
}
All of us need help at some point in time. The ToolTip control is just the
antidote for all maladies. Before running the executable, copy the two bitmaps
open.bmp and new.bmp to the current directory.These two bitmaps will be
displayed as two separate images on our form. Whenever we move our cursor close
to them, a yellow colored help text comes into sight.
A ToolTip class gives us a small window that contains a single line of text that
describes what a control stands for. The real productivity of Bitmaps and Icons
can be fully harnessed only if we are able to discern their functionality. The
Active property, if set to false, disables the ToolTip control. The ShowAlways
property will show a ToolTip despite the fact that the control it is assigned
to, is currently disabled.
Screen 4.60 Screen 4.61
The following four properties have been set for the Tooltip control:
• AutomaticDelay: It denotes the time in milliseconds that elapses before the
tool tip appears. The default is 500 milliseconds.
• AutoPopDelay: It is the time in milliseconds for which the ToolTip remains
visible to the user while the mouse is stationary on the control. Its default
value is 10 times the value of the property AutomaticDelay.
• InitialDelay: It is the time in milliseconds for which the mouse must remain
fixed within the control, before the tool tip becomes visible again. The default
value is equal to that of property AutomaticDelay.
• ReshowDelay: It is the time in milliseconds that elapses, as the mouse moves
from one control to another. The default is 1/5 of the property AutomaticDelay.
Any amendment made to the property AutomaticDelay has an impact on the above
properties. Thus, it is advisable to first change the AutomaticDelay property,
followed by the other properties.
Two PictureBox controls have been added in our window. We add these two pictures
to the form. This class can display all types of images, including bitmaps,
icons, JPEG, GIF and many more. The Image property decides on the image that
gets displayed. By default, the image has no borders and can be clipped too.
The ToolTip class has a method called SetToolTip, which accepts a control as the
first parameter and the text of the tool tip as the second. Henceforth, whenever
the mouse is positioned on that control, the tool tip will be displayed.
TrackBar Control
a.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form
{
TrackBar t;
Label l;
public zzz() {
Size = new Size(512, 320);
t = new TrackBar();
t.Location = new Point(8, 24);
t.Size = new Size(200, 42);
t.ValueChanged += new EventHandler(abc);
t.TickFrequency = 5;
t.Minimum = 0;
t.Maximum = 100;
t.SmallChange = 5;
t.LargeChange = 25;
t.Orientation = Orientation.Horizontal;
t.TickStyle = TickStyle.Both;
l = new Label();
l.Location = new Point(112, 192);
l.Text = t.Value.ToString();
Controls.Add(t);
Controls.Add(l);
}
void abc(object source, EventArgs e)
{
l.Text = t.Value.ToString();
}
public static void Main() {
Application.Run(new zzz());
}
}
The TrackBar class resembles a scroll bar in many respects, but its interaction
with the user is distinct. We can configure values that the TrackBar represents
and also define increments for off-button clicks. The TrackBar can be positioned
horizontally or vertically. The number of ticks that are displayed can also be
configured.
Each time that the value of the TrackBar changes, the Event property
ValueChanged calls the function abc. The property TickFrequency specifies the
number of ticks that will be drawn. The lower limit of the range of the TrackBar
is confined to a value decided by the Minimum property, which in our case is 0.
The upper limit of the range of the TrackBar is confined to a value determined
by the Maximum property, which in our case is 100.
Screen 4.62 Screen 4.63
We may not want to draw 100 ticks to represent all the values. So, we specify a
value such as 5 as the TickFrequency. This will draw a tick mark at a spacing of
5, resulting in a total of 20 ticks. Thus, each tick will represent 5 units of
the TrackBar’s range of values.
The property SmallChange decides on the magnitude of change that occurs in the
TrackBar, whenever the user presses the Up arrow key or the Down arrow key to
move the TrackBar thumb. The value assigned to the LargeChange property is used
whenever we click on the side, or use the Page Up and Page Down keys.
The Orientation property can take only two values i.e. horizontal or vertical.
The TickStyle property uses an enumeration that can have four values:
• None: for no tick marks.
• Both: for tick marks on both sides of the control.
• BottomRight: for tick marks at the bottom for a horizontal control, and on the
right for a vertical control .
• TopLeft: this is the reverse of BottomRight.
We then use a label to display the value of the track bar on the form. This
value is stored in the property Value of the label class. Finally, we add the
label and TrackBar. Function abc merely updates the label to depict the current
position of the TrackBar.
Tree-Node Control
a.cs
using System;
using System.IO;
using System.Resources;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
public class zzz : Form
{
TreeView d;
ImageList i;
public zzz()
{
ClientSize = new System.Drawing.Size(502, 293);
i = new ImageList();
i.Images.Add(new Bitmap("clsdfold.bmp"));
i.Images.Add(new Bitmap("openfold.bmp"));
d = new TreeView();
d.ImageList = (ImageList)i;
d.ForeColor = (Color)System.Drawing.SystemColors.WindowText;
d.Location = new System.Drawing.Point(24, 16);
d.AllowDrop = true;
d.Indent = 19;
d.Text = "treeView1";
d.SelectedImageIndex = 1;
d.Size = new System.Drawing.Size(200, 264);
d.AfterSelect += new TreeViewEventHandler(sss);
d.BeforeExpand += new TreeViewCancelEventHandler(eee);
Controls.Add(d);
string[] dr = Environment.GetLogicalDrives();
for (int ii = 0; ii < dr.Length; ii++)
{
if (PlatformInvokeKernel32.GetDriveType(dr[ii]) ==
PlatformInvokeKernel32.DRIVE_FIXED)
{
TreeNode c = new TreeNode(dr[ii]);
d.Nodes.Add(c);
ddd(c);
}
}
}
void ddd(TreeNode n)
{
DirectoryInfo dir = new DirectoryInfo(ppp(n));
DirectoryInfo[] e = dir.GetDirectories ();
for (int i = 0; i < e.Length; i++)
{
string na = e[i].Name;
if (!na.Equals(".") && !na.Equals(".."))
{
n.Nodes.Add(new TreeNode(na));
}
}
}
void sss(object source, TreeViewEventArgs e)
{
Text = "Windows.Forms File Explorer – " + e.Node.Text;
}
void eee(object source, TreeViewCancelEventArgs e)
{
TreeNode n = (TreeNode)e.Node;
for (int i = 0; i < n.Nodes.Count; i++)
{
ddd(n.Nodes[i]);
}
}
string ppp(TreeNode node)
{
if (node.Parent == null)
{
return node.Text;
}
return Path.Combine(ppp(node.Parent), node.Text);
}
[STAThread]
public static void Main(string[] args) {
Application.Run(new zzz());
}
}
public class PlatformInvokeKernel32
{
[DllImport("KERNEL32", CharSet=System.Runtime.InteropServices.CharSet.Auto)]
public static extern int GetDriveType(string lpRootPathName);
public const int DRIVE_FIXED = 3;
}
Copy the two bmp files clsdfold.bmp and openfold.bmp required for this program,
from the samples directory into your current directory.
Here, we first create a TreeView control called d. A TreeView displays a list of
items or nodes in a hierarchical manner. A node consists of a caption and a
bitmap; the bitmap is optional. The user can select a node and collapse it or
expand it by clicking on the plus or minus sign, displayed alongside,
respectively.
We have an ImageList control i that represents two images. We initialize the
ImageList property of the TreeView control to the Image List. Thus, the TreeView
can now use the images stored in the ImageList. The AllowDrop property
facilitates the use of the control for events and Drag and Drop operations.
The property Indent decides the indent in the pixels. The SelectedImageIndex
determines as to which picture is to be used from the ImageList when the user
selects an item in the control.
There are a large number of events that get triggered off, but we are capturing
only the following two:
• AfterSelect, that calls function sss
• BeforeExapnd, that calls functions eee.
The EventHandler functions use different delegate types.
The class Environment has a static function GetLogicalDrives that returns an
array of strings, which denotes the logical drives on our machine. If machine
has three drives, C:\, D:\and E:\, the for loop is repeated thrice.
Using the Platform invoke API, we can call any code in any dll. So, we first
call the external function GetDriveType and compare it with the value retrieved
for DRIVE_FIXED, which is 3 in this case.
If there are 3 fixed drives then the if statement is true for 3 of them,
resulting in the display of 3 nodes only. If you have only one fixed drive i.e.
C, you will see only one node on your screen.
Screen 4.64 Screen 4.65
A TreeNode object is created, which is used in the TreeView. The constructor is
passed the drive name to enable it to be displayed as a label for the node. A
TreeView is made up of Nodes. It has a property called Nodes, which is of type
TreeNodeCollection. Using the Add member of the collection, we add the tree
node. Thereafter, function ddd is called with this newly created and empty tree
node object.
Function ppp is called with an empty tree node. If the node has no parent, the
text associated with the node is returned. But if a parent exists, we use the
static function Combine, of the Path class, to combine the two strings together.
The Combine function calls the ppp function recursively.
To begin with, the value of ppp(n) is C:\. The object dir represents directories
in the C drive. To obtain all the directories present on the drive, we call
function GetDirectories, which returns an array of DirectoryInfo objects. The
Name member returns the name of the directory. If the return value is not a . or
.., we add the directory name to the list of Nodes.
In the AfterSelect event, function sss gets called. In this function, we change
the Text of the form to the node selected. This is possible because the node
label is passed as a parameter to TreeViewEventArgs. The function eee recovers
the current active node from the parameter passed to it. Using the Count
property, we calculate the number of nodes contained in the active node and
thereafter, iterate through each of these nodes. The same function ddd is called
with every node.
This is how we can display the sub-directories contained within directories and
the files displayed within the directories. Data is sent to the control, which
merely returns the same data in a tree like form. When we click on the first
plus (+) sign of C:\, the function ppp(n) returns the entire path name of each
directory in a recursive fashion.
Docking
a.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class zzz :Form {
Panel p1;
Panel p2;
GroupBox g;
Button b;
RadioButton r1;
RadioButton r2;
RadioButton r3;
RadioButton r4;
RadioButton r5;
RadioButton r6;
RadioButton rs;
Splitter s;
public zzz () {
g = new GroupBox();
rs = new RadioButton();
r5 = new RadioButton();
s = new Splitter();
b = new Button();
r6 = new RadioButton();
r4 = new RadioButton();
r1 = new RadioButton();
r3 = new RadioButton();
p1 = new Panel();
p2 = new Panel();
r2 = new RadioButton();
Location = new Point(100, 100);
SizeGripStyle = SizeGripStyle.Show;
ClientSize = new Size(448, 400);
g.Location = new Point(16, 152);
g.TabStop = false;
g.Text = "&Dock";
g.Size = new Size(88, 176);
s.BackColor = Color.Blue;
s.Dock = DockStyle.Right;
s.Location = new Point(325, 0);
s.Size = new Size(3, 400);
b.BackColor = SystemColors.Control;
b.FlatStyle = FlatStyle.Popup;
b.Anchor = AnchorStyles.None;
b.Text = "Demo Button";
rs.Text = "rs";
rs.Size = new Size(100, 23);
r5.Location = new Point(8, 120);
r5.Text = "&Right";
r5.Size = new Size(72, 24);
r5.Click += new System.EventHandler(abc);
r6.Location = new Point(8, 144);
r6.Text = "&Fill";
r6.Size = new Size(72, 24);
r6.Click += new System.EventHandler(abc);
r4.Location = new Point(8, 96);
r4.Text = "&Bottom";
r4.Size = new Size(72, 24);
r4.Click += new System.EventHandler(abc);
r1.Checked = true;
r1.Location = new Point(8, 24);
r1.Text = "&None";
r1.Size = new Size(72, 24);
r1.Click += new System.EventHandler(abc);
r3.Location = new Point(8, 72);
r3.Text = "&Left";
r3.Size = new Size(72, 24);
r3.Click += new System.EventHandler(abc);
p1.Text = "ButtonPanel";
p1.Size = new Size(325, 400);
p1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
p1.Dock = DockStyle.Fill;
p1.BackColor = Color.Green;
p2.Location = new Point(328, 0);
p2.Text = "ControlsPanel";
p2.Size = new Size(120, 400);
p2.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
p2.Dock = DockStyle.Right;
r2.Location = new Point(8, 48);
r2.Text = "&Top";
r2.Click += new System.EventHandler(abc);
Controls.Add(p2);
Controls.Add(s);
Controls.Add(p1);
p1.Controls.Add(b);
p2.Controls.Add(g);
g.Controls.Add(r4);
g.Controls.Add(r3);
g.Controls.Add(r1);
g.Controls.Add(r5);
g.Controls.Add(r6);
g.Controls.Add(r2);
rs = r1;
aaa();
}
void aaa() {
if (rs == r1)
b.Dock = DockStyle.None;
else if (rs == r2)
b.Dock = DockStyle.Top;
else if (rs == r3)
b.Dock = DockStyle.Left;
else if (rs == r4)
b.Dock = DockStyle.Bottom;
else if (rs == r5)
b.Dock = DockStyle.Right;
else
b.Dock = DockStyle.Fill;
}
protected void abc(object s, EventArgs e)
{
rs = (RadioButton)s;
aaa();
}
public static void Main() {
Application.Run(new zzz());
}
}
This program may be generously proportioned, however it is very simple to
comprehend. We have created seven radio buttons, one GroupBox, two panels, one
splitter and one button. The SizeGripStyle property can have three values:
• Show: that always shows the sizing grip.
• Hide : that always hides the sizing grip.
• Auto : that shows and hides the sizing grip as necessary.
The sizing handle is always shown at the right hand corner of the form.
The button has a FlatStyle of Popup that determines the appearance of the
button. Clicking on any of the Radio Buttons results in a call to the function
abc. This is as a consequence of the Event property Click having been set to
abc. Next, we add all the controls to the Form. The repetitive code for adding
the controls results in a lengthy program.
Screen 4.66 Screen 4.67
The function abc gets called. Its first parameter is an object type. The object
stands for the control that calls the function. In function abc, the RadioButton
that was clicked on, is represented by s. The value contained in s is stored in
another RadioButton called rs. The function aaa is called thereafter. It
compares the value contained in rs with the 5 radio buttons r1 to r5. Depending
upon the value that matches, the Dock property of the button changes.
Screen 4.68 Screen 4.69
When a control is docked to the edge of a container, it will always take up a
position flush against the edge, if the control is resized.
The Left, Right, Top and Bottom styles position the control towards the
respective edge of the container. Fill will fill upto all the sides of the
control.
The control is resized to fill the container’s edges. The First dock style named
None, ensures that the control does not get docked at all.
C Language Help, C Language Tutorials, C Language Programming, C Language Tricks { The C# Language }
C Language Help, C Language Tutorials, C Language Programming, C Language Tricks
Controls
Windows Forms or Winforms is a contemporary Windows-based forms package that
endows the Windows programmer with an innovative methodology for creating
aesthetic user interfaces and interactive applications. We will not ramble on
about the pros and cons of the package, but commence the creation of the
smallest GUI (Graphical User Interface) application.
a.cs
public class zzz
{
public static void Main()
{
zzz z = new zzz();
System.Windows.Forms.Application.Run(z);
}
}
Run the compiler as
>csc a.cs
Compiler Error
a.cs(6,1): error CS1502: The best overloaded method match for
‘System.Windows.Forms.Application.Run(System.Windows.Forms.Form)’ has some
invalid arguments
a.cs(6,38): error CS1503: Argument ’1′: cannot convert from ‘zzz’ to ‘System.Windows.Forms.Form’
An error is generated because, Run, which is a static function in the
Application class of the System.Windows.Forms namespace, requires a Form object.
The error distinctly states its inability to convert a zzz to
System.Windows.Forms.Form, which proves that, an object that looks like Form is
mandatory here, and not zzz.
a.cs
using System.Windows.Forms;
public class zzz : Form
{
public static void Main()
{
Application.Run(new zzz());
}
}
Screen 4.1
This program is not very dissimilar from the previous one. The using keyword is
employed to avoid the inevitability of writing namespace with every object. The
object z has no efficacy here, since we are passing the zzz object directly to
the Run function. As the class zzz is derived from Form, no error is generated.
When we run the program, a small blank window is displayed. You can click on the
‘x’ symbol to close it. The output is substantial enough for a single line of
code.
a.cs
using System.Windows.Forms;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
zzz()
{
Text = “Vijay Mukhi”;
}
}
Screen 4.2
In the constructor of the zzz class, we have initialized a member called Text to
the string value ‘Vijay Mukhi’. When we run the program, to our amazement, our
window, which earlier was without a title, now possesses the title ‘Vijay Mukhi’.
This is the introductory concept of Windows Forms programming. The class called
Form has abundant properties such as Text etc., which have specific relevance in
a window. Any modifications to the properties get reflected immediately in the
window. The changes depend upon the properties that we modify. In this case, the
property of Text changes the Caption, text displayed in the title bar.
A Form represents a window displayed by an application. An application can have
different types of windows, such as a standard, tool bar, borderless or floating
window. The Form class is versatile enough to handle all the above types of
windows, as it is derived from innumerable classes. A Dialog Box, which is used
to accept input from the user, is available in two modes viz. modal and
modeless. Our trustworthy Form class can also handle such Dialog boxes with
equal aplomb. We normally use the Form class as the preliminary class for
building WinForms Applications.
The Main method calls the Run function and gives it the Form object. In the
constructor, we can modify the properties of the Form Class to give the window a
desired appearance. Since these properties are not static, they cannot be
altered in Main, but can be modified in the constructor or in any other
function.
a.cs
using System.Windows.Forms;
public class zzz
{
public static void Main()
{
Application.Run(new yyy());
}
}
class yyy : Form
{
public yyy()
{
Text = “Vijay Mukhi”;
}
}
Screen 4.3
In this program, we have created another class yyy that derives from the Form
class. We have used this object as a parameter to the function Run. The rules do
not impel us to derive the class zzz from Form. However, in all our programs, we
shall follow the first approach since we have decided to steer clear of
controversy and stick to the rules.
a.cs
using System.Windows.Forms;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
zzz()
{
ClientSize = new System.Drawing.Size(300,600);
Size = new System.Drawing.Size(100,200);
}
}
Screen 4.4
The above example sets two properties of the Form class. The first, which is
called ClientSize, is used by Windows.Forms to decide how large our initial
window would be. This property has a default value, which can be overwritten by
specifying the width and height. As we need to furnish two values, we use a
class called Size in the namespace System.Drawing, which accepts two values.
This class does not insist on receiving meaningful values. The constructor is
passed the width and height of the desired window in pixels.
A graphics screen is divided into small dots or pixels. Depending upon the
configuration of the monitor and graphics card, a computer can handle and
display a certain number of pixels and colors. The higher the configuration, the
larger are the number of pixels and colors that are available.
The size of the client area of the form is computed as the size of the form
minus the borders and the title bar placed by Windows. They are not of our
concern, since we shall be placing our own controls in our form. ClientSize is a
property with a default value, and it gets updated automatically whenever the
form is resized.
The next property is Size. The user enjoys the flexibility of altering the size
of the window at run time. Size is initialized in manner similar to ClientSize.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form
{
Button b;
public static void Main()
{
Application.Run(new zzz());
}
zzz()
{
b = new Button();
Controls.Add(b);
}
}
Screen 4.5
We now see a small button at the top left hand corner of our Window. How did we
create this button? To do so, we first, create an object b, that looks like a
Button class.
The Form class has a large number of properties such as ClientSize, Size etc.
One of them is called Controls, which is a read-only property since it contains
only a Get. This property returns a Control.Collection object, whose Add
function adds the control to the Client area of the window. We shall be sprucing
up our button shortly.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form
{
Button b;
public static void Main()
{
Application.Run(new zzz());
}
zzz()
{
b = new Button();
b.Location = new Point(100,200);
b.Size = new Size(100,50);
b.Text = “Vijay Mukhi is smart”;
Controls.Add(b);
}
}
Screen 4.6
Anything that is placed on a form is called a Control or a Widget. Similar to a
Form, a button control, popularly known as a command button, has numerous
properties. One of them is the Location property, which decides the position on
the Client area where the button will be positioned.
Here, we use the Point class and not Size, even though both are objects that
represent two numbers. By convention, a Size object represents a width and a
height and a Point object has an x and y co-ordinate system, starting from the
upper left corner.
Most properties have a default value. Since this fact about default values has
been reiterated numerous times, we shall not repeat it again. The Size property
determines the initial size of the window and the string assigned to the Text
property is displayed on the button.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form
{
Button b;
public static void Main() {
Application.Run(new zzz());
}
zzz()
{
b = new Button();
b.Location = new Point(100,200);
b.Size = new Size(100,50);
b.Text = “Vijay Mukhi is smart”;
b.Click += new System.EventHandler(abc);
Controls.Add(b);
}
public void abc(object s, System.EventArgs e)
{
MessageBox.Show(“Hi”);
}
}
Screen 4.7
In the earlier example, clicking on the button was an exercise in futility
because the button did not achieve anything. After augmenting the code of the
program, when we click on the button, we see a MessageBox that displays the
greeting ‘Hi’. The rationale behind a button control is that, when we click on
it, some code should get executed, some action should take place.
The button class has an event object called Click, which accepts an object of
type EventHandler. The syntax for events uses the += symbol to add a function
that is to be called when the event handler gets activated. The function name is
given through the EventHandler delegate. This delegate has been specially
created only to handle events that a control will generate.
Thus, the function abc, which is passed as a parameter to the EventHandler
delegate, must have a certain signature. The first parameter is the generic
object that could represent any entity identifying the caller. The second
parameter is an EventArgs object, which we will explain shortly. Thus, each time
we click on the button, the function abc gets called. This function in turn
calls the static function Show from the MessageBox class to display ‘Hi’.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form {
Button b;
public static void Main()
{
Application.Run(new zzz());
}
zzz()
{
b = new Button();
b.Location = new Point(100,200);
b.Size = new Size(100,50);
b.Text = “Vijay Mukhi is smart”;
b.Click += new System.EventHandler(abc);
b.Click += new System.EventHandler(pqr);
Controls.Add(b);
}
public void abc(object s, System.EventArgs e){
MessageBox.Show(“Hi”);
}
public void pqr(object s, System.EventArgs e)
{
MessageBox.Show(“Bye”);
}
}
Screen 4.8
This program reveals the veritable power of events and delegates. Two functions,
abc and pqr, are called whenever the button is clicked. To achieve this, all
that we need to do in the code is to call the Click event again, using the +=
symbol, followed by the name of the new function. The -= symbol is used if we
change our minds. This is a type safe way of calling code in response to an
event.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form {
Button b;
TextBox t;
public static void Main() {
Application.Run(new zzz());
}
zzz()
{
b = new Button();
b.Location = new Point(100,200);
b.Size = new Size(100,50);
b.Text = “Vijay Mukhi is smart”;
b.Click += new System.EventHandler(abc);
t = new TextBox();
t.Text = “Hell”;
t.Location = new Point(10,20);
Controls.Add(b);
Controls.Add(t);
}
public void abc(object s, System.EventArgs e)
{
MessageBox.Show(t.Text + ” ” + ClientSize );
}
}
Screen 4.9
In the Forms Window, we now see two controls: a Button and a TextBox object that
lets us enter some text. The textbox widget also has a large number of
properties associated with it. We shall not be repeating this obvious fact for
all the other controls. The properties Location and Size work in a similar
manner when used with any Control, but the property Text differs, depending upon
the object in use. For a button, it represents the caption, whereas for a text
box, it represents the text that is entered. Thus, some of the properties play
different roles when used in different controls.
Screen 4.10
Each time the button is clicked, we would like to display the text that has been
entered by the user in the text box. In the eventhandler function abc, the
property Text reveals the text entered into the textbox. The MessageBox class is
used to display the value, along with the size of the client area.
You can change the size of the window or change the contents of the text box and
observe the contents of the MessageBox changing dynamically.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
public override void Dispose()
{
base.Dispose();
MessageBox.Show( “hi ” + ClientSize );
}
}
This program displays our ability to invoke code at a specific point in time,
which in this case, is at the stage when the user closes the window or when the
application quits out. It is akin to fulfilling the last wishes of the program.
Screen 4.11
As the application is quitting, it calls a function called Dispose. So, if you
ever want code to be called at the point when an application is about to quit
out, you must place it in the Dispose function. This code could be used to close
files or do anything else that the programmer desires.
It is not mandatory to call Dispose of the base class, but it is always a good
programming practice to call the base class function first, and then augment it
with your own code. In this particular case, it is inconsequential, but under
different circumstances, things may go out of hand if this advice is not heeded.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form {
public static void Main()
{
Application.Run(new zzz());
}
Brush b; int ii = 0;
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
b = new SolidBrush(Color.Blue);
ii++;
g.DrawString(“Vijay Mukhi ” + ii, Font, b ,1,25);
}
}
In this program, we are overriding a function called OnPaint, which is present
in the Form class. The OnPaint function gets called each time the window has to
be redrawn. Therefore, all code that is to be written to the screen must be
written in this function. This code cannot be placed anywhere else in the
program.
Our next endeavor is to create an object that has functions which display text
on the screen or draw a picture. The class that contains these display functions
is called Graphics. Thus, we create an object g that looks like Graphics. As an
object that looks like Graphics cannot be instantiated, WinForms provides us
with an object of type PaintEventArgs with the OnPaint Function. This class
contains members required for graphical display. Hence, g is initialized to the
Graphics member in e.
As mentioned earlier, OnPaint gets called whenever our window has to be redrawn.
Whenever OnPaint gets called, it creates an object that looks like
PaintEventArgs and then passes it as a parameter to the function. This object
has a member called Graphics, which contains functions used for drawing in our
client area. The DrawString function requires the text that is to be displayed
and its Font.
The Form class provides us with the object called Font. Thereafter, the text
color, or to be more precise, the brush is to be specified. Here, we want a
solid Brush like object. So, we create an object b, and give it a color in which
the text should be displayed. There is a static object Blue in the class Color
that stands for the color blue. The spelling of ‘color’ is as per the American
usage. Finally, the x and y co-ordinates on the screen are specified.
This positions the text in the window at these specified co-ordinates.
Thus, the function has a total of 5 parameters:-
• The text to be displayed.
• The font in which the text is to be displayed.
• The text color or the brush.
• The x co-ordinate.
• The y co-ordinate.
Here, we have specified certain values, but every time we use DrawString, we can
conveniently specify different values for these parameters. Thus, the second
DrawString function can display different text and use a different font or
brush. As the system does not have a default brush or font, we call it a
Stateless Model.
Screen 4.12
Along with ‘Vijay Mukhi’, we have used a variable called ii, which has been
initialized to 0. In the OnPaint function, we increment this variable by 1.
Before the window is displayed, function OnPaint gets called. Thereafter,
OnPaint gets called whenever the ‘minimize’ and ‘maximize’ buttons of the window
are clicked.
The function OnPaint gets called whenever our client area has to be redrawn due
to any action carried out by the user. This function has to be marked with the
modifier named protected. This is because the original function in the Form
class is tagged with this modifier. We can override a function of the base
class, provided we do not change any of the modifiers. By making OnPaint
protected, only derived classes can use the OnPaint function.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form {
public static void Main()
{
Application.Run(new zzz());
}
Brush b; int ii = 0;
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
b = new SolidBrush(Color.Blue);
ii++;
g.DrawString(“Vijay Mukhi ” + ii, Font, b ,1,25);
RectangleF r = new RectangleF(20, 60, 100, 25);
g.FillRectangle(new SolidBrush(Color.Gainsboro), r);
g.DrawString(“Sonal Mukhi”, Font, new SolidBrush(Color.Red), r);
StringFormat f = new StringFormat();
f.Alignment=StringAlignment.Center;
RectangleF r1 = new RectangleF(20, 100, 100, 25);
g.DrawString(“Sonal Mukhi”, Font, new SolidBrush(Color.Black), r1,f);
g.RotateTransform(-30);
g.TranslateTransform(0, 100);
g.DrawString(“vijay mukhi”, Font, new SolidBrush(Color.Orange), 20, 40);
g.ResetTransform();
}
}
Screen 4.13
The output of this program is a window with text displayed haphazardly. This
output is nothing to write home about, but is useful in elucidating numerous
concepts.
A rectangleF structure stores two point objects i.e. it specifies a rectangular
area of the window. We start at one corner, where the x and y co-ordinates are
20 and 60, and the opposite corner where the x and y co-ordinates are 100 and 25
respectively. The function FillRectangle from the Graphics class is used to
create and fill the above rectangular portion of the screen with the color
Gainsboro. The DrawString function is overloaded to take not only x and y as the
last two parameters, but also a rectangular area into which it will draw a
string.
We would now like to center the above string in the rectangular area. This is
easier said than done, because, it entails creation of an object that looks like
StringFormat with the Alignment property set as Center. The documentation
specifies many more options that can be implemented. The StringFormat object is
passed as the last parameter to the DrawString function, resulting in the string
being shown as centered, instead of being Left aligned, which is the default
setting.
If we want to rotate the image by 30 degrees, we just have to call a function
named RotateTransform from the Graphics class and pass as a parameter, the
amount of rotation that is required. You can then watch the image get displayed
at the specified angle. Beware, too acute an angle may sprain your neck! The
next function, named TranslateTransform, is optional. It is used to move the
text around in the client area horizontally or vertically. Whenever we transform
something, it stays in the transformed position. But thereafter, if we do not
want the other objects to be in this form, we need to use the function
ResetTranform to undo the transform. However, it is optional.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
Brush b;
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
b = new SolidBrush(Color.FromArgb(180, Color.Black));
RectangleF r = new RectangleF(20, 20, 50, 50);
g.FillRectangle(b, r);
}
}
Screen 4.14
The topic of Brushes is so exhaustive that a thesis can well be written on it.
In this program, we use a special brush to fill up a rectangular area on our
screen.
Here, we specify not only a color, but also a number, which is the alpha value
and has a range from 0 to 255. The larger the value, the darker will be the
color. To put it technically, the larger the value, the lesser will be the
translucence and vice-versa.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
Brush b;
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
b = new SolidBrush(Color.Black);
Font f = new Font(“Times New Roman”, 30);
g.DrawString(“Vijay Mukhi ” , f , b ,1,25);
}
}
Screen 4.15
In this program, we will shed light on Fonts. When you read a newspaper or
magazine, the style of the letters looks different in each of them. This
difference is due to the Font or the Typeface used. There are numerous fonts in
the world of letters.
While displaying text, we can be very specific about the way in which the
letters look. To enhance their visual appeal, we create an object that looks
like Font. Then, in the constructor, the Name of the font is specified along
with the Size in points. Remember that 72 points make an inch. Thus my name,
Vijay Mukhi, now gets displayed in a size that is bigger than normal.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Image i;
i = new Bitmap(“sample.jpg”);
g.DrawImage(i, 29, 20, 283, 212);
}
}
Screen 4.16
The above program merely displays an image. A file with a jpg or a gif extension
contains images or pictures. To display images, we use a class called Image that
can recognize pictures. Even though i is an image object, we initialize it to an
object that looks like Bitmap. An Image class is an abstract class and the class
Bitmap derives from it.
An Image class could represent a picture, which is not just an image, but could
also be a cursor, icon etc. The DrawImage function accepts an image object as
the first parameter, followed by the screen co-ordinates at which the image has
to be positioned. The above .jpg file is part of the samples offered while
installing the .NET SDK. So, search for the file and copy it to the current
working directory. Like the text sample, this picture can also be rotated,
transformed etc.
a.cs
using System.Windows.Forms;
using System.Drawing;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Image i = new Bitmap(“colorbars.jpg”);
Brush b = new TextureBrush(i);
g.DrawString(“Vijay Mukhi is very smart” , Font, b ,1,25);
}
}
Screen 4.17
By combining a Brush and an image, we can create a multicolor brush. In one of
the earlier programs, we had used a Solid brush. Here, we are using a Texture
brush. This brush fills the interiors of a shape with a picture.
Thus, the text gets reflected in a brush, which reminds us of a rainbow. You can
enhance the aesthetic appeal of your applications by using this facility.
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form {
public static void Main()
{
Application.Run(new zzz());
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Pen p = new Pen(Color.FromArgb(150, Color.Purple), 20);
p.DashStyle = DashStyle.Dash;
p.StartCap = LineCap.Round;
Point [] pp = new Point[] {new Point(200, 140),new Point(700, 240),new
Point(500, 340)};
g.DrawCurve(p,pp);
}
}
Screen 4.18
The above program introduces freehand drawing. A pen is like an artist’s brush,
which is used to draw any shape that permeates the mind. In our program, we
commence by creating a Pen object p. It is initialized to a particular alpha
color using FromArgb function from the Color class, and to a specified width.
The constructor can also be provided with other parameters, such as a brush. A
pen is used to draw lines and curves.
A Pen can also draw a line of a specified width and style. The default DashStyle
is Continuous. If we change the DashStyle to Dash, the starting point becomes a
rounded edge. The default is a Straight Edge. The line drawn by a pen is very
versatile, and can employ a variety of fill styles, colors and textures. The
DrawCurve function paints a pen object that specifies how to draw a curve. It
has an array of points with the individual three point objects specifying where
the curved line should be drawn.
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form {
public static void Main() {
Application.Run(new zzz());
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Image i= new Bitmap(“BoilingPoint.jpg”);
Brush pb = new TextureBrush(i);
Pen p= new Pen(pb, 75);
g.DrawLine(p,1,5,150,200);
}
}
Screen 4.19
We can use a brush that looks like an image and create a pen that will draw
lines in the garb of a picture. The DrawLine function accepts two sets of
numbers, the x-y co-ordinates of the starting point and the x-y co-ordinates of
the ending point. It draws a thick line joining these two points. Thus, we can
use this function to draw any possible shape.
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
HatchBrush b = new HatchBrush(HatchStyle.ForwardDiagonal, Color.Green,
Color.FromArgb(100, Color.Yellow));
g.FillEllipse(b, 250, 10, 100, 100);
Rectangle r = new Rectangle(300, 250, 100, 100);
LinearGradientBrush lb = new LinearGradientBrush(r, Color.Red,
Color.Yellow,LinearGradientMode.BackwardDiagonal);
g.FillRectangle(lb, r);
}
}
Screen 4.20
On maximizing the screen, we see two figures; one is a filled circle, while the
other is a rectangular block. We are equipped with a large number of brushes
akin to those in the artistic world. One of them is a HatchBrush. The
constructor of HatchBrush accepts a hatch style and two colors, viz. a
background color and a foreground color.
The first parameter is the hatch style, which can be one of six possible hatch
styles. The foreground color, in this case, green, defines the color of the
lines to be drawn and the background color defines the color for the gaps
between the lines.
The FillEllipse function in Graphics fills up the shape to display the effect of
the brush. We could have used the Rectangle function also, but as we are trying
to be as akin as possible to the samples provided by Microsoft, we have used the
Ellipse function.
A LinearGradientBrush can represent color gradients and multi-color gradients. A
gradient represents a transformation from one color to another. A linear
gradient is defined alongside a line that is specified by the width of a
rectangle or by any two points. Thus, a two-color gradient will commence with a
starting color and conclude with the ending color. The blend from one color to
the next can be customized. First, we specify the object that is to be colored,
which is a rectangle in this case. The gradient starts with the left corner and
ends at the lower right corner. Thereafter, we follow with the starting color
followed by the ending color. Finally, the angle measured in degrees in the
clockwise direction is mentioned, starting from the x-axis. This defines the
orientation of the gradient. You can change the angle and witness the
spectacular effects.
Menus
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
MainMenu m;
public zzz()
{
m = new MainMenu();
MenuItem mi= m.MenuItems.Add(“&File”);
Menu = m;
}
}
Screen 4.21
Let us now build a menu. We have created an object m, which symbolizes the
MainMenu. MainMenu is called a control and represents the menu structure for a
Form. It is the root of the menu.
A menu consists of various menu items, which are displayed horizontally across
the menu. We want to create a menu item that displays the word ‘File For this,
we need another class called MenuItem. A MenuItem can represent either an
individual menu item depicting a command, or it can cascade to another popup of
menu items.
MenuItems is a read-only property in MainMenu that gives a reference to all the
MenuItems currently available in the menu. We have none so far. This
CollectionObject also has a function called Add, which is used to add menu
items. To do so, the text of the item that is to be displayed must be stated as
the parameter to the Add function. We can also remove any menu item that has
been previously added.
The variable mi stores the MenuItem object returned by the Add function.
Thereafter, Menu, which is an object of type MainMenu, available in Form, is
initialized to the menu that we desire. The appearance of the menu depends upon
the menu object stored in Menu.
When we run the program, we see the word File displayed in the top left corner.
At this stage, nothing happens when we click on it. On pressing the Alt key, F
is displayed as underlined since the symbol & underlines the character it is
preceded with.
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form {
public static void Main() {
Application.Run(new zzz());
}
MainMenu m;
public zzz() {
m = new MainMenu();
MenuItem mi= m.MenuItems.Add(“&File”);
mi.MenuItems.Add(“Hi”);
mi.MenuItems.Add(“-”);
mi.MenuItems.Add(“Bye”);
Menu = m;
} }
Screen 4.22
Now things look more visually attractive. When we click on File or use the
accelerator Alt-F, a menu pops up with the word ‘Hi’, followed by a separator
and then finally by the word ‘Bye’.
A separator is used to logically group menus together. However, when we click on
‘hi’ or ‘bye’, nothing happens. This situation needs to be redressed, since a
menu should activate some code.
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
MainMenu m;
public zzz()
{
m = new MainMenu();
MenuItem mi= m.MenuItems.Add(“&File”);
MenuItem m1;
m1 = new MenuItem(“Hi”, new System.EventHandler(abc), Shortcut.CtrlF11);
mi.MenuItems.Add(m1);
mi.MenuItems.Add(“Bye”);
Menu = m;
}
void abc(object sender, System.EventArgs e)
{
MessageBox.Show(“hell”);
}
}
Screen 4.24
Screen 4.23
Now, whether you either click on File and then on the word ‘Hi’, or you press
Control+F11, you will see a message box with the word “hell” displayed in it.
The MenuItem constructor is overloaded. The first parameter is the text to be
displayed. The second parameter is a delegate that encompasses the function to
be called whenever this menu item is activated. Just as the pen is mightier than
the sword, under some circumstances, the keyboard is certainly mightier than the
mouse. At times, it is faster to use a keyboard shortcut, instead of using the
mouse. Thus, the last parameter is the keyboard shortcut key, which is part of
an enumerator. This MenuItem object is passed to the Add function, that either
accepts a string or a MenuItem object.
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
MainMenu m;
public zzz()
{
m = new MainMenu();
MenuItem mi= m.MenuItems.Add(“&File”);
MenuItem a = new MenuItem(“One”,new System.EventHandler(abc));
MenuItem b = new MenuItem(“two”,new System.EventHandler(abc));
mi.MenuItems.Add(“hell”,(new MenuItem[]{ a, b })
);
Menu = m;
}
void abc(object sender, System.EventArgs e)
{
MessageBox.Show(“hell”);
}
}
Screen 4.26
Screen 4.25
Here, we have a popup within a popup. When you click on File, you will see the
word ‘hell’ displayed. You will also see an arrow pointing to the right, along
with the menu item. If you move the mouse over the arrow, a popup is displayed,
containing the two menu items ‘one’ and ‘two’. If we click on them, a message
box with the word ‘hell’ gets displayed.
In the program, with a single statement, we have created two menu items, a and
b, followed by an array of menu items. This array is then passed as the last
parameter to the Add function. Thus, all the menus become sub-menus. In this
case, the event handler is associated with the submenu options, since clicking
on the menu item displays the sub-menu.
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
MainMenu m;
public zzz()
{
m = new MainMenu();
MenuItem mi= m.MenuItems.Add(“&File”);
mi.MenuItems.Add( “hell”,new System.EventHandler(abc));
mi.MenuItems.Add( “Bye”,new System.EventHandler(abc));
Menu = m;
}
void abc(object s, System.EventArgs e)
{
MenuItem m = (MenuItem) s;
if ( m.Checked)
m.Checked = false;
else
m.Checked = true;
}
}
Screen 4.27 Screen 4.28
We add two menu items, ‘hell’ and ‘Bye’ to our File menu and assign the same
function abc to handle their events. Clicking on any one of the menu options
results in a call to the function abc. This function takes two parameters. The
first parameter s, represents the menu item that was clicked on. If the first
menu option ‘hell’ is clicked, then the parameter s is not an object, but a menu
item representing ‘hell’ and vice versa.
Every MenuItem has an option called Checked, which if True, will display a tick
mark on the menu item. Thus, you can Check or Uncheck a menu option by clicking
on it. You may click on each menu option to observe this effect.
a.cs
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
public class zzz: Form
{
public static void Main()
{
Application.Run(new zzz());
}
MainMenu m;
public zzz()
{
m = new MainMenu();
MenuItem mi= m.MenuItems.Add(“&File”);
mi.MenuItems.Add( “hell”,new System.EventHandler(abc));
mi.MenuItems.Add( “Bye”,new System.EventHandler(abc));
Label l = new Label();
ContextMenu lm;
lm = new ContextMenu();
l.ContextMenu = lm;
l.Text = “Vijay Mukhi”;
lm.MenuItems.Add(mi.CloneMenu());
Controls.Add(l );
Menu = m;
}
void abc(object s, System.EventArgs e)
{
MenuItem m = (MenuItem) s ;
if ( m.Checked)
m.Checked = false;
else
m.Checked = true;
}
}
The program output will display the same menu – File, as seen in the earlier
program. The text Vijay Mukhi will also be visible. If you place the mouse on
this text and right click the mouse, you will see the same menu as seen with the
File option.
Screen 4.29 Screen 4.30
This is called a Context Sensitive Menu. These two menus, however, are
different. If you Check a menu option in this menu by clicking on it, it does
not carry the tick mark to the other menu.
We first create two objects:
• The first is lm, which looks like a ContextMenu.
• The second is l, which looks like a label.
Screen 4.31
Every label has a member called ContextMenu, wherein we can specify a Context
Sensitive menu. This member is initialized to lm. As we have already created a
menu item mi, we can reuse this menu item.
However, a menu item cannot be used twice. Hence, calling the function CloneMenu
off MenuItem creates a clone. This clone menu is then passed to the Add function
of MenuItems in the ContextMenu.
Writing Controls
Let us start by creating the simplest control that money can buy. We create the
following files:
c.cs
using System.Windows.Forms;
using System.Drawing;
public class yyy : Control
{
}
h.cs
using System.Windows.Forms;
public class zzz : System.Windows.Forms.Form
{
yyy a;
public zzz()
{
a = new yyy();
Controls.Add(a);
}
public static void Main() {
Application.Run(new zzz());
}
}
a.bat
del *.exe
del *.dll
csc.exe /t:library c.cs
csc.exe /r:c.dll h.cs
h
The file c.cs contains our very first custom control. In order to create our own
user-defined control, we create a class yyy and derive it from the Control
class. The Control class implements the basic code required by classes to
implement the behavior of a control or a widget. This code can handle user input
with a keyboard or a pointing device, such as a mouse. Message handling and
security features are also supported.
At the end of the day, all controls are merely child windows. The Control class
defines the area or bounds of a control along with the fonts, colors and images.
This class allows painting, context menus and anchoring with docking behavior.
Earlier, we had displayed scores of controls in our containers, written by the
Microsoft developers. All these controls were derived from the Control class.
Screen 4.32
Thus, a user control like yyy is an instance of a Control class, which is added
to the Form using the Add function off the Controls collection. It can’t get any
simpler. On running the executable, we see no output. Yet, since no error was
generated, we presume that all went well.
The major difference between the Microsoft controls and our controls is, the
file in which the code for the control is finally placed. We have placed our
control code in assembly c.dll, whereas, Microsoft controls are placed in
System.Windows.Forms.dll.
c.cs
using System.Windows.Forms;
using System.Drawing;
public class yyy : Control
{
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawString(Text,Font, new SolidBrush(ForeColor), ClientRectangle);
}
}
h.cs
using System.Windows.Forms;
public class zzz : System.Windows.Forms.Form{
yyy a;
public zzz() {
a = new yyy();
a.Size = new System.Drawing.Size(600, 450);
a.Text = “Vijay Mukhi”;
Controls.Add(a);
}
public static void Main() {
Application.Run(new zzz());
}
}
Screen 4.33
In the above example, we have overridden a function called OnPaint in the
Control class. This function gets called whenever a control is to be redrawn on
the screen. It is passed a PaintEventArgs object as a parameter, from where we
summon the DrawString function to paint a string in a specified font and color,
at a particular location. The first parameter, Text, is a property, which refers
to the string to be displayed. The string ‘Vijay Mukhi’ is presently displayed
in the window.
In the container h.cs, we have initialized the property Text contained in the
Control class to ‘Vijay Mukhi’. The Size property is also initialized, so that
our control has a specific size in the container.
h.cs
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form {
Button b;
ccc c;
public zzz() {
b = new Button();
c = new ccc();
b.Anchor = System.Windows.Forms.AnchorStyles.Bottom;
b.DialogResult = System.Windows.Forms.DialogResult.OK;
b.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
b.Size = new System.Drawing.Size(96, 24);
b.Text = “&Save”;
b.Location = new System.Drawing.Point(8, 328);
b.Click += new System.EventHandler(abc);
Text = “Sonal Mukhi”;
AcceptButton = b;
ClientSize = new System.Drawing.Size(400, 373);
c.Anchor=AnchorStyles.Top AnchorStyles.Bottom AnchorStyles.Left
AnchorStyles.Right;
c.AutoScrollMinSize = new System.Drawing.Size(0, 0);
c.Size = new System.Drawing.Size(400, 310);
c.Text = “Vijay Mukhi”;
Controls.Add(b);
Controls.Add(c);
c.cust = ddd.rrr();
Size = new Size(400, (373 + SystemInformation.CaptionHeight));
}
void abc(object sender, System.EventArgs e)
{
c.aaa();
MessageBox.Show(“vijay “+ c.cust);
}
public static void Main(string[] args)
{
Application.Run(new zzz());
}
}
c.cs
using System;
using System.Windows.Forms;
using System.Drawing;
public class ccc : UserControl
{
TextBox t;
TextBox ID;
Label l;
ddd c;
public ccc()
{
t = new TextBox();
l = new Label();
ID = new TextBox();
Text = “Vijay Mukhi”;
Size = new System.Drawing.Size(384, 304);
t.Size = new System.Drawing.Size(88, 20);
t.Location = new System.Drawing.Point(88, 70);
l.Size = new System.Drawing.Size(64, 16);
l.Location = new System.Drawing.Point(8, 32);
l.Text = “ID:”;
ID.ReadOnly = true;
ID.Size = new System.Drawing.Size(200, 20);
ID.Location = new System.Drawing.Point(88, 30);
ID.Enabled = false;
Controls.Add(t);
Controls.Add(ID);
Controls.Add(l);
}
public ddd cust
{
get
{
return c;
}
set
{
c=value;
ID.Text = c.ID;
t.Text = c.ti;
}
}
public void aaa()
{
c.ti = t.Text;
}
}
cc.cs
using System;
using System.ComponentModel;
using System.IO;
public class ddd : Component
{
string i ;
string t ;
public static ddd rrr()
{
ddd c = new ddd(“111″);
c.ti = “Vijay”;
return c;
}
internal ddd(string s): base()
{
i = s ;
}
public string ID
{
get
{
return i ;
}
}
public string ti
{
get
{
return t ;
}
set
{
t = value ;
}
}
public override string ToString()
{
StringWriter sb = new StringWriter() ;
sb.WriteLine(“Sonal \n”);
sb.WriteLine(i);
sb.Write(t);
return sb.ToString();
}
}
a.bat
del *.exe
del *.dll
csc.exe /t:library /out:c.dll c.cs cc.cs
csc.exe /R:c.dll h.cs
h
This program is a rather protracted one. As usual, we start with the container
in h.cs. In the zzz constructor, we first create a button b and an object c that
is an instance of our user control ccc. The control is present in the assembly
c.dll. What this class presently does is not significant. We begin by
initializing a large number of properties in the button.
The Anchor property decides as to which edges of the control are to be anchored
with the edges of the container. Here, we have chosen the Bottom edge.
The DialogResult property is the value that is returned to the parent form when
we click on the button. The value returned is OK.
The FlatStyle property belongs to the ButtonBase class and is one of the
numerous properties that influence the flat style appearance of the button.
Knowledge of GUI programming implies cognizance and comprehension about all the
facets of making your application more comely and pleasing to the eye. The Size,
Text and Location properties were explained earlier.
Each time we click on the button, the function abc gets called. The Text
property decides on the title of the windows. The AcceptButton property requires
an object that represents a button. Every form has a feature, which associates
the Enter key with a button. The resultant effect is that pressing the Enter key
on the keyboard simulates a click on the associated button. Thus, in the above
form, pressing Enter or clicking on the button would result in a call to the
function abc. The ClientSize property decides the size of the windows.
Our User Control too can initialize properties since they belong to the Control
class. In the program, we have set the Anchor, AutoScrollMinSize, Size and Text
properties of our user-defined control class ccc, even though our control may
not have implemented these properties directly.
Using the Add function, we have then added the button and the control ccc to the
form. Finally, we have called a static function rrr from the class ddd that
initializes a property cust from our user-defined control.
When we run this program, we see two text boxes, a label and a button. It is
obvious that other than the button, the other widgets were created by the class
ccc. This provides ample credence to our belief that our user-defined control
can do its biding.
Screen 4.35
Screen 4.34
We shall now endeavor to comprehend what the constructor of class ccc in file
c.cs is attempting to do. The constructor contains two text boxes, one called ID
to store the id of the user, and the other called t to store the user’s name.
The label l is used to display a simple descriptive message. Since we do not
want the user to change the value contained in ID, we assign the value true to
its ReadOnly property and assign the value false to its Enabled property.
Thereafter, we add these three widgets to the form. Hence, we can now see four
widgets on the screen.
In the container h.cs, we call a static function, rrr off class ddd. The ddd
class is created in cc.cs and is derived from Component. In the rrr function, we
create an object c, which looks like ddd and pass a value of 111 to the
constructor. The constructor of class ddd initializes an instance variable i to
the value contained in s. The variable i stands for the user id.
Class ddd has a property called ti, which is initialized to my name, Vijay. This
property ti sets another instance variable t to the value ‘Vijay’. Thus, we have
initialized two instance members of class ddd to specific values.
The value returned on calling the rrr function is stored in the cust property of
the control c. The class ccc contains the property having type ddd.
The property Cust stores the ddd object in the variable c for later use. It also
initializes the Text property of the text boxes to the ID and Name of the user
that the two properties in the class ddd were initialized to. Thus, we see ’111′
and ‘Vijay’ displayed in the text boxes.
When we click on the button labeled ‘Save’, the function abc gets called. This
function first calls the function aaa from class ccc using the object c. In aaa,
we initialize the ti property of the control to the value present in the
textbox. The ID property is dimmed out, and hence its value can never be
changed. The object c represents the ddd object in class ccc.
To display a string, an object, whose data type is not a string, has to call the
ToString function in the datatype. The cust property in class ccc has the type
of ddd, which contains the ToString function. This function uses the
StringWriter class to concatenate the word ‘Sonal’ with the value of the
instance variables i and t, which eventually get displayed in the MessageBox.
The above program demonstrates two crucial points:
(a) All the code that refers to the user has been encapsulated in class ddd.
(b) The user interface code is entered in class ccc.
The container is oblivious to these classes and does not bother to verify
whether there are two classes or one. While the class ccc contains code
pertaining to User Interface interaction only, the class ddd contains code
relating to the actual object.
h.cs
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form {
RadioButton r1,r2;
GroupBox g1;
sss s;
public zzz()
{
r1 = new System.Windows.Forms.RadioButton();
r2 = new System.Windows.Forms.RadioButton();
r1.Location = new System.Drawing.Point(24, 24);
r1.Size = new System.Drawing.Size(128, 24);
r1.Text = “Vijay”;
r1.Checked = true;
r1.CheckedChanged += new System.EventHandler(r1f);
r2.Location = new System.Drawing.Point(24, 64);
r2.Size = new System.Drawing.Size(128, 24);
r2.Text = “Mukhi”;
r2.CheckedChanged += new System.EventHandler(r2f);
g1 = new System.Windows.Forms.GroupBox();
g1.Size = new System.Drawing.Size(192, 152);
g1.Text = “Sonal”;
g1.Location = new System.Drawing.Point(320, 16);
s = new sss();
Text = “Control Example”;
ClientSize = new System.Drawing.Size(528, 325);
s.Size = new System.Drawing.Size(304, 328);
s.TabIndex = 0;
s.Anchor = AnchorStyles.Left AnchorStyles.Right;
s.Font = new System.Drawing.Font(“TAHOMA”, 16f, System.Drawing.FontStyle.Bold,
System.Drawing.GraphicsUnit.World);
s.Text = “Simple Control”;
s.dmc += new System.EventHandler(sf);
Controls.Add(g1);
Controls.Add(s);
g1.Controls.Add(r2);
g1.Controls.Add(r1);
}
void r2f(object sender, System.EventArgs e)
{
if (r2.Checked)
{
s.dm = ddd.a2;
}
}
void r1f(object sender, System.EventArgs e)
{
if (r1.Checked)
{
s.dm = ddd.a1;
}
}
void sf(object sender, System.EventArgs e)
{
if (s.dm == ddd.a1)
MessageBox.Show(“hi”);
if (s.dm == ddd.a2)
MessageBox.Show(“bye”);
}
public static void Main()
{
Application.Run(new zzz());
}
}
c.cs
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing;
[DefaultProperty("dm"),DefaultEvent("dmc"),]
public class sss : Control
{
ddd d;
EventHandler eee;
public sss() :base()
{
d = ddd.a1;
ccc();
SetStyle(ControlStyles.ResizeRedraw, true);
}
[Category("Appearance"),Description("Controls how the control
paints"),DefaultValue(ddd.a1),Bindable(true),]
public ddd dm
{
get
{
return d;
}
set
{
d=value;
ccc();
dmf(EventArgs.Empty);
}
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.FillRectangle(new SolidBrush(BackColor), ClientRectangle);
Size textSize = e.Graphics.MeasureString(Text, Font).ToSize();
float x = (ClientRectangle.Width/2) – (textSize.Width/2);
float y = (ClientRectangle.Height/2) – (textSize.Height/2);
e.Graphics.DrawString(Text,Font,new SolidBrush(ForeColor),x, y);
}
protected override void OnTextChanged(EventArgs e) {
base.OnTextChanged(e);
Invalidate();
}
[Description("Raised when the DrawingMode changes")]
public event EventHandler dmc
{
add {
eee += value;
}
remove {
eee -= value;
}
}
protected virtual void dmf(EventArgs e)
{
Invalidate();
if (eee != null)
eee.Invoke(this, e);
}
void ccc()
{
if ( d == ddd.a1)
{
base.BackColor = Color.Yellow ;
base.ForeColor = Color.Green ;
}
if ( d == ddd.a2)
{
base.BackColor = Color.LightSlateGray ;
base.ForeColor = Color.White ;
}
}
}
public enum ddd
{
a1 = 0,
a2 = 1,
}
a.bat
del *.exe
del *.dll
csc /t:library c.cs
csc h.cs /r:c.dll
h
Let us now write a control that is considerably intricate. In the container
h.cs, we start with two radio buttons r1 and r2. Each time we select a radio
button, depending upon the option selected, either of the functions r1f or r2f
will get called. Thereafter, we create a group box called g1. The radio buttons
are added to the group box, and the group box is added to the Controls
collections. Apart from these controls, one more user control named s, which is
an instance of class sss, is added to the Controls collection.
Screen 4.36 Screen 4.37
Prior to this, we initialize various properties of this control such as
TabIndex, Font, Size, Anchor, Text etc. to some meaningful values. Besides
these, a property called dmc in the user control is initialized to an
EventHandler that calls function sf. Thus, whenever the event represented by dmc
is triggered, the function sf gets called.
The code implementing our user control s, resides in the file c.cs. The user
control s is an instance of sss and is derived from the Control class. At the
outset, the constructor of class sss calls the constructor of the base class
using the keyword base, even though this is optional, because the base class
constructor invariably gets called.
The class ddd is an enum, with two members a1 and a2, having values 0 and 1
respectively. We could conveniently have used numbers directly instead of an
enum, but since the original example used an enum, we have also done so. We set
the object d to the value 0 and call function ccc from class sss. The main
objective of placing code in a function is to enable the code to be called
several times.
In the function ccc, we start by checking the value of the object d. If it is a1
i.e. 0, we change the value of the two properties BackColor and ForeColor to
Yellow and Green respectively. If the object has a value of a2, then another
pair of colors is assigned to these properties. The properties are changed in
the base class using the keyword base. The function SetStyle ensures that the
form gets redrawn when it is resized.
We have already learnt that the OnPaint function is called whenever the window
needs to be redrawn. In this function, we first use the property BackColor to
fill the form background. Next, we use the width of the currently selected font,
to calculate the midpoint of our screen, and then, we write the value contained
in the text property in the center of the window.
When the second radio button is selected, function r2f gets called. In this
function, the program checks whether the radio button is already checked. If so,
it initializes the property dm, whose data type is ddd, to a2.
Similarly, when the first radio button is selected, function r1f gets called.
This function first ascertains if the radio button is already checked. If so, it
initializes the property dm to a1.
Now, we shall focus our attention on the property dm. In the set accessor of
property dm, the ddd object named d is initialized to either a1 or a2. Following
this action, a call is made to function ccc, which changes the background and
foreground color, depending on the value contained in d. The effect is observed
when the function OnPaint gets called. A call is made to the function dmf with a
parameter of an Empty event.
In function dmf, we first call Invalidate, which in turn, calls the OnPaint
function. Just as life offers no guarantees whatsoever, in much the same way,
the calls made to the OnPaint function are unpredictable. The Invalidate
function instantly calls the OnPaint function.
You may recall that in h.cs, the property dmc was initialized with the name of
the function sf. This property dmc is an event type that stores the EventHandler
or function sf in an instance object eee. So, the value in the object eee is
checked. If the value is not null, the function Invoke is called off the object
eee with two parameters. The first parameter is a reference to itself, i.e.
‘this’, and the second parameter is a null EventArgs object. The function
Invoke, in turn, calls function sf in the container, h.cs. The function sf
displays a message box, depending upon the value of the ddd object.
The main idea behind this exercise is to demonstrate that clicking on a radio
button in the container initializes a property in the user control. This in
turn, raises a property changed event, thus resulting in a call to a function
registered with a property of the control. The function resides in the container
and not in the user control.
This is a circuitous route for accomplishing results. The Invoke function is not
aware of and could not care less about the functions that it is calling.
All the other attributes in the code can be safely ignored, since they are
mainly meant for external tools or programs that display the metadata.
h.cs
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form
{
TextBox t;
Button b;
hhh h;
public zzz()
{
h = new hhh();
b = new Button();
t = new TextBox();
b.Size = new System.Drawing.Size(104, 40);
b.Text = “Vijay”;
b.Location = new System.Drawing.Point(336, 56);
ClientSize = new System.Drawing.Size(448, 157);
t.Location = new System.Drawing.Point(80, 16);
t.Text = “Vijay Mukhi”;
h.Dock = System.Windows.Forms.DockStyle.Bottom;
h.Size = new System.Drawing.Size(448, 40);
h.Location = new System.Drawing.Point(0, 117);
h.Text = “none”;
h.ppp(t, “TextBox selected”);
h.ppp(b, “Button Selected”);
Controls.Add(t);
Controls.Add(b);
Controls.Add(h);
}
public static void Main(string[] args)
{
Application.Run(new zzz());
}
}
c.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
public class hhh : Control
{
Hashtable h;
Control a;
public hhh()
{
h = new Hashtable();
BackColor = SystemColors.Info;
}
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) ]
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
}
}
private void ce(object s, EventArgs e)
{
a = (Control)s;
Invalidate();
}
private void cl(object s, EventArgs e)
{
if (s == a)
{
a = null;
Invalidate();
}
}
public void ppp(Control c, string v)
{
if (v == null)
{
v = string.Empty;
}
if (v.Length == 0)
{
h.Remove(c);
c.Enter -= new EventHandler(ce);
c.Leave -= new EventHandler(cl);
}
else
{
h[c] = v;
c.Enter += new EventHandler(ce);
c.Leave += new EventHandler(cl);
}
if (c == a)
{
Invalidate();
}
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
Rectangle rect = ClientRectangle;
Pen borderPen = new Pen(ForeColor);
pe.Graphics.DrawRectangle(borderPen, rect);
borderPen.Dispose();
if (a != null)
{
string te = (string)h[a];
if (te != null && te.Length > 0)
{
rect.Inflate(-2, -2);
Brush brush = new SolidBrush(ForeColor);
pe.Graphics.DrawString(te, Font, brush, rect);
brush.Dispose();
}
}
}
}
In this program, we have three controls: a user control h, contained in class
hhh, a TextBox t and a Button b. The basic properties like Size, Text and
Location for the Button and the Text box are set to the specified initial
values. Thereafter, the properties of the user control h are initialized. The
DockStyle for the Dock property is set to the Bottom of the form, and the Size
and the Location are specified. The Text property is also initialized.
The user control h has a property called ppp, which accepts two parameters, a
control and a string. We call it twice. When it is called for the first time,
the first parameter passed is a text box control. The next time it is called,
the first parameter passed is a button control. The button and the text box are
displayed on the screen and a yellow colored label with the word ‘TextBox
selected’ in the bottom pane.
Screen 4.38
When we click on the button, the words in the label change to ‘Button selected’.
Screen 4.39
Thus, depending upon the control selected, our user control displays a help
message which we have registered using the property ppp.
The user control h in file c.cs, has two instance variables, a HashTable h and a
Control a. The HashTable class stores values based on a certain key. This helps
in retrieving the value efficiently on being provided with the key. In the
constructor of class hhh, an instance h of the HashTable is created. The
property BackColor is initialized to a Read Only Color property from the class
SystemColors. This property represents the tool tip background color. On our
machine, it happens to be Yellow, and thus, we see a yellow color label.
There is a property called Text in the Control class. In order to implement our
property, we must override the existing one. Presently, in our property, we are
merely accessing the original Text in the base class.
The attributes make interesting reading even though they have little use in the
current example. The Browsable attribute with a parameter of False prevents this
property from showing up in the Property Browser. Also, the value assigned to
the property is not saved on disk. We are not doing anything useful in the Text
property at all.
The property ppp is called twice in the control, because the container has two
property initialization statements. Good programming style incorporates
comprehensive error checks.
We first check for a string value in the second parameter v. If it is null, we
initialize the variable v to an empty string. If the string is not empty, we add
the string contained in v to the hash table using the control parameter c as the
key. Thus, in a hash table, any data type can be used as a key to insert or
retrieve values.
The control class has Events called Enter and Leave. We use the += syntax to
register the function ce whenever the Event Enter gets fired. In the same
manner, the function cl gets called whenever the Event Leave gets fired.
If the user calls the property without a string, it signifies that the control
has to be removed from the hash table and the list of functions in the events
has to be called. Thus, we use the Remove function of the HashTable class to
remove the key c from the hash table. The -= syntax of the Events and Delegates
is employed to remove the functions registered with the Enter and Leave events.
In the OnPaint function, we have drawn a label and displayed some text in it.
There is one rule that must never be violated: ‘Call the function in the base
class first.’ This is because we are unaware of what the overridden function
accomplishes.
A Pen is created from the color stored in ForeColor. This color is the default
color stored in the Control class. The Background color is initialized in the
constructor of hhh class. A rectangle that is drawn using this pen displays
yellow as the background color, with the size defaulting to the value stored in
the ClientRectangle property in the control class. The pen is then disposed off,
to enable the system to retrieve the resources consumed by it. A check is
thereafter performed on the value in the Control object. It should be null since
the control has not been initialized in the beginning.
Before the OnPaint function gets called, a lot of activities get executed in the
background, i.e. many functions get called and numerous events get triggered. On
entering a field, the event OnControlEnter gets triggered and the function that
is registered with the Enter event is called. In our case, function ce is
called. The first parameter to this function is a handle to the control that
caused the event. In our case, it is the Text Box. We initialize the object a to
the Text Box control. Thus, in OnPaint, the value of control a is a Text Box.
We then retrieve the string stored in the hash table using the control as the
key. We then ascertain that the value contained in the string te is not null and
its length is greater than zero.
The Rect structure contains the size of the rectangle. We can then inflate or
deflate the rectangle using the Inflate method. As a result, the position and
the size of the Rectangle change. The X and Y properties are changed by the
amount specified and the Width and Height are modified to twice the amount
specified. The final outcome is that the size of the rectangle is inflated
without moving its geometric center. In our case, since the numbers are
negative, the rectangular will be deflated. Following this, a solid brush is
created using the property ForeColor. A string is drawn using the DrawString
function in the Pen class. Thereafter, we dispose of the brush.
When we leave the control, the event handler named Leave is called. The function
associated with this eventhandler is cl, which ascertains whether the control in
‘a’ and the one passed as parameter i.e. ‘s’ is the same. If so, then ‘a’ is
initialized to a Null value and the OnPaint function is called. The value
contained in ‘a’ is the deciding factor on whether we are inside a control or
not.
h.cs
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form
{
fff f;
public zzz()
{
f = new fff();
f.Dock = System.Windows.Forms.DockStyle.Fill;
f.ForeColor = System.Drawing.Color.White;
f.BackColor = System.Drawing.Color.Black;
f.Size = new System.Drawing.Size(600, 450);
f.vvv = 73;
f.Text = “Vijay Mukhi”;
ClientSize = new System.Drawing.Size(600, 450);
Controls.Add(f);
}
public static void Main()
{
Application.Run(new zzz());
}
}
c.cs
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class fff : Control {
int vvv1 = 0;
public int Min = 0;
public int Max = 100;
public bool ShowValue = false;
int dv = 0;
bool d = false;
public Color cs = Color.Red;
public Color ce = Color.LimeGreen;
Brush bb = null;
Brush bd = null;
public int vvv
{
get
{
if (d)
{
return dv;
}
return vvv1;
}
set
{
vvv1 = value;
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
bb=new LinearGradientBrush(new Point(0, 0),
new Point(ClientSize.Width, 0),cs,ce);
bd = new SolidBrush(Color.FromArgb(200, Color.Black));
e.Graphics.FillRectangle(bb, ClientRectangle);
Rectangle r = ClientRectangle;
float p = ((float)vvv / ((float)Max – (float)Min));
int a = (int)(p * (float)r.Width);
r.X += a;
r.Width -= a;
e.Graphics.FillRectangle(bd, r);
e.Graphics.Flush();
RectangleF r1 = new RectangleF();
SizeF ts = e.Graphics.MeasureString(Text, Font);
r1.Width = ts.Width;
r1.Height = ts.Height;
r1.X = (ClientRectangle.Width – r1.Width) / 2;
r1.Y = (ClientRectangle.Height – r1.Height) / 2;
e.Graphics.DrawString(Text, Font, new SolidBrush(ForeColor), r1);
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
Capture = true;
d = true;
sss(new Point(e.X, e.Y));
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if ( !d)
return;
sss(new Point(e.X, e.Y));
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if ( !d)
return;
Capture = false;
d = false;
vvv = dv;
}
void sss(Point l) {
Rectangle r = ClientRectangle;
float p = (float)l.X / (float)r.Width;
int a = (int)(p * (float)(Max – Min));
int o = dv;
dv = a;
float op = ((float)o / ((float)Max – (float)Min));
int ol = (int)(op * (float)r.Width);
float np = ((float)dv / ((float)Max – (float)Min));
int nl = (int)(np * (float)r.Width);
int mi = Math.Min(ol, nl);
int ma = Math.Max(ol, nl);
Rectangle r1 = new Rectangle(r.X + mi, r.Y, ma – mi, r.Height);
Invalidate(r1);
}
}
When we run the above program, we see a myriad of colors such as red, green and
black. We also see ‘Vijay Mukhi’ displayed in the center. On clicking the mouse,
more of the color black will be introduced upto the point of the mouse click on
the screen, whereas dragging the mouse will extend or diminish the colors
accordingly. This certainly is a sight for sore eyes!
Screen 4.40 Screen 4.41
We have to admit that the folks at Microsoft who worked on this sample did a
truly marvelous job. Our task here is to explain the program that they have
written, in absolute layman terms.
The h.cs file contains code that is already explained in the earlier program. As
always, along with the properties that are present in Control, we have also
initialized one of our own properties, vvv for control fff. The user-defined
property vvv is assigned a value of 73. The value contained in the Text property
is displayed at the center of the screen.
We now focus the spotlight on the actual code of our control fff that resides in
the file c.cs.
The container sets the value of the property vvv to 73. Therefore, in the
control, the property vvv gets called initially. The set accessor initializes
the int variable vvv1 to 73.
The function OnPaint is responsible for drawing the rainbow of colors on the
screen. In this function, we first call the original OnPaint function. Then, we
create two brushes to obtain the desired effect. The first brush is a
LinearGradientBrush, which can be accessed using the Brush object bb. The last
parameter to the constructor of this brush is the starting color cs, which has
been initialized to Red, and an ending color ce that is initialized to a value
of LimeGreen. The second brush is a SolidBrush bd, which is created to fill up
any shape. The function FromArgb of the Color class creates a new color, where
the first parameter is an alpha value 200 and the second parameter is the base
color.
The Graphics property of PaintEventArgs merely returns a Graphics object when we
call function FillRectangle. The two parameters to this function are : a Brush
that represents a gradient and the Size of the window whose value is stored in
the ClientRectangle property. If we stop at this stage, the red and the green
gradients will stand out glaringly.
Now, let us draw the black rectangle separating the red and green colors. We
create a temporary variable r to store the dimensions of the window.
Two instance variables, Max and Min having a value of 100 and 0 respectively,
are used to decide the factor by which the property vvv should be divided. Since
we need to retrieve the value for the property, the get accessor is called. The
value contained in d decides on the value to be assigned to the property.
Initially d is false, so the value 73 contained in vvv1 is returned.
The float variable p now has a value of .73. The Width of the window in our case
is 600 pixels. Thus, the variable ‘a’ is assigned a value of 438. You can use
the WriteLine function to display these values. The Width of the rectangle is
reduced by this amount and the X position is shifted by 438 pixels. Next, we
draw the black rectangle using the function FillRectangle by providing it with a
black brush and the new Rectangle object r. Modifying the value of the property
vvv from 73 to 3 will result in displaying the entire screen in black color.
Thus, the vvv parameter decides the size of the black rectangle; the smaller its
value, the larger will be the size of the rectangle. The Flush function ensures
that all pending graphic operations on the stack are executed immediately. If we
stop here, the gradient and the rectangle will be displayed, but no text will be
displayed in the center of the screen.
The Rectangular structure r1 stores the size and location of the rectangular
region that our string requires. The MeasureString function takes two
parameters:
• The first is a string value that is stored in our property Text.
• The second is a Font object.
The default Font property is supplied for the second parameter. The function
returns a SizeF object, whose Width and Height determine the display region that
the string requires. The width and height of rectangle r1 is initialized to the
Width and Height of the string. As the string is to be displayed in the center
of the form, objects X and Y, which are the members of rectangle r1, are given
the following values:
• The Width of the form = the width of the string divided by 2.
• The Height of the form = the height of the string divided by 2.
We then use the DrawString function to display the text. This function takes the
following parameters in the specified order:
• A string that is stored in the Text property.
• The default font.
• The color of the brush that is stored in the property ForeColor.
• The region, which is stored in object r1.
We have three mouse events that trigger off three functions. They are as
follows:
• OnMouseMove: When we move the mouse.
• OnMouseDown: When we click the left mouse button, with the mouse cursor placed
on the form.
• OnMouseUp: When we release the left mouse button.
If we move the mouse around, nothing happens. This is because the ‘if’ statement
results in a value of true, as the variable d has a value of false. So, the
function practically achieves nothing. Clicking the left mouse button toggles
the value of the variable d to true. Thus, if the variable d is True, we know
that the user has clicked the left mouse button, and when it reverts back to
false, we know that the left mouse button has been released. The OnMouseMove
function does something constructive and gainful only when the left mouse button
is depressed.
The Capture property reveals whether the control has been able to capture the
mouse or not. This property is optional. When we click within the window, the
framework is informed about our victory over the mouse, and the variable d is
set to true. The next most pertinent action is to call function sss with a Point
object. This object stores the position at which the mouse has been clicked. The
MouseEventArgs parameter e, has two members X and Y, which identify the current
position of the mouse.
The function sss is called yet again when the mouse is moved. The OnMouseUp
function changes the value of Capture, and sets the value of the boolean
variable d to false. It also initializes the value of a property vvv to dv.
The net achievement of the function sss is that, it calls the function OnPaint
using the Invalidate function. The OnPaint function does not draw or paint the
entire form or window, but only the specific part that has been invalidated.
This is done to achieve optimum efficiency, since it is preposterous to waste
effort in redrawing areas that have not been invalidated. The Invalidate
function is also passed a Rect object as a parameter, which takes a decision on
the area of the client rectangle that is to be re-drawn.
Function sss is therefore, given a smaller Rect object as a parameter, which
informs the OnPaint function about the specific part of the form that should be
invalidated. The Point parameter is employed to determine the area of the form,
that must be redrawn.
The co-ordinates of the entire screen are stored in ClientRectangle. We also
calculate a percentage p, depending upon our current position, i.e. l.X, divided
by the Width of the screen. Next, we multiply this percentage by 100 (the
difference between Max and Min). The value of dv is stored in variable o, since
we are initializing dv to ‘a’ in the next line.
Two new percentage values, op and np have been calculated. They are merely the
values of o and dv divided by 100. Thereafter, we calculate the minimum and
maximum values of ol and nl. The new rectangle created is the Client Rectangle,
where the minimum value is added to X, and Y is left untouched. The Width is set
to the difference of ma and mi, and the Height is left unaltered.
The difference between variables ma and mi is negligible. Correspondingly, the
width of the invalidated region too is insignificantly small. Further, mi is
used to decide the position at which the X of the black rectangle should
commence. Thus, if we replace r1 with ClientRectangle, everything would be hunky
dory, but the screen will flicker a great deal, while the mouse is in motion.
This is because the same screen has to be displayed when the movement ceases.
Thus, to thwart this flicker, we redraw only the specific part of the screen
that has been invalidated.
ScrollBar Control
a.cs
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form
{
VScrollBar v;
HScrollBar h;
PictureBox p;
Label l2;
Label l1;
bool d = false;
int ox, oy;
float vm;
float vp;
float hm;
float hp;
public zzz()
{
ClientSize = new System.Drawing.Size(520, 277);
l1 = new Label();
l1.Location = new System.Drawing.Point(408, 160);
l2 = new Label();
l2.Location = new Point(408, 184);
v = new VScrollBar();
v.Location = new Point(200, 24);
v.Size = new Size(16, 152);
v.Scroll += new ScrollEventHandler(vScroll);
v.Minimum = -100;
h = new HScrollBar();
h.Location = new Point(16, 176);
h.Size = new Size(184, 16);
h.Scroll += new ScrollEventHandler(hScroll);
h.Minimum = -100;
p = new PictureBox();
Bitmap b = new Bitmap(“water.bmp”);
p.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
p.Location = new Point(64, 32);
p.Size = new Size(96, 96);
p.Image = (Image)b;
p.MouseDown += new MouseEventHandler(pDown);
p.MouseUp += new MouseEventHandler(pUp);
p.MouseMove += new MouseEventHandler(pMove);
Controls.Add(l2);
Controls.Add(l1);
Controls.Add(h);
Controls.Add(v);
Controls.Add(p);
v.LargeChange = 20;
h.LargeChange = 20;
v.SmallChange = 1;
h.SmallChange = 1;
vmf();
hmf();
l1.Text = h.Value.ToString();
l2.Text = v.Value.ToString();
p.Cursor = Cursors.SizeAll;
}
void vmf()
{
float hsb = (float)(v.Height – p.Height);
float ticks = (float)(v.Maximum – v.Minimum);
vm = hsb / ticks;
}
void hmf()
{
float hsb = (float)(h.Width – p.Width) ;
float ticks = (float)(h.Maximum – h.Minimum) ;
hm = hsb / ticks ;
}
void vScroll(object sender, ScrollEventArgs e)
{
l2.Text = v.Value.ToString() ;
vp = (float)(v.Value- v.Minimum);
p.Top = v.Bottom – (int)(vm * vp) – p.Height;
}
void hScroll(object sender, ScrollEventArgs e)
{
l1.Text = h.Value.ToString() ;
hp = (float)(h.Value – h.Minimum);
p.Left = h.Right – (int)(hm * hp) – p.Width;
}
void pDown(object s, MouseEventArgs e)
{
d = true;
ox = e.X;
oy = e.Y;
}
void pMove(object s, MouseEventArgs e)
{
if (d)
{
int minY = v.Minimum;
int maxY = v.Maximum;
int minX = h.Minimum;
int maxX = h.Maximum;
int value = (int)(v.Value – (e.Y – oy)/vm);
if (value < minY)
{
v.Value = minY;
}
else if (value > maxY – v.LargeChange + 1)
{
v.Value = maxY – v.LargeChange + 1;
}
else
{
v.Value = value;
}
value = (int)(h.Value – (e.X – ox)/hm);
if (value < minX)
{
h.Value = minX;
}
else if (value > maxX – h.LargeChange + 1)
{
h.Value = maxX – h.LargeChange + 1;
}
else
{
h.Value = value;
}
l1.Text = h.Value.ToString() ;
l2.Text = v.Value.ToString() ;
value = p.Top + (e.Y – oy);
if (value < v.Top)
{
value = v.Top;
}
else if (value > v.Bottom – p.Height)
{
value = v.Bottom – p.Height;
}
p.Top = value;
value = p.Left + (e.X – ox);
if (value < h.Left)
{
value = h.Left;
}
else if (value > h.Right – p.Width)
{
value = h.Right – p.Width;
}
p.Left = value;
}
}
void pUp(object s, MouseEventArgs e)
{
d = false;
}
public static void Main()
{
Application.Run(new zzz());
}
}
>csc a.cs
Before running the program, copy the file water.bmp into the current directory.
This file is provided along with the samples in the installation program. The
singular methodology to comprehend large programs is by first examining their
output, because the output will motivate you to grasp the program.
We see two scrollbars, one vertical and the other horizontal, with a picture
within them. Clicking on the scrollbars will move the picture in the direction
of the scrollbar that is clicked. Let us understand how this is achieved.
Screen 4.42
We start by examining the constructor of class zzz. The default client size is
modified to the desired size. Then, there are two labels l1 and l2, which
display the current values of the two scroll bars. A vertical scrollbar v is an
instance of a class VScrollBar, and a horizontal scrollbar h is an instance of a
class HScrollBar. As is customary, we provide a Location and a Size to the
scrollbar. The Minimum and Maximum properties decide the range of values that
the user can select. Most controls such as a Text box or a Combo box already
have a scroll bar built into them. Hence, they do not require this control. The
Scroll event gets triggered whenever the scroll button moves. The button can be
moved either by using a mouse or by the keyboard. Moving the vertical scroll
button will call the vScroll function, whereas moving the horizontal scroll
button will call the hScroll function.
After setting the properties of the Scroll Bar, we create an object p, which is
an instance of a PictureBox class. Next, we create a Bitmap object, and in the
constructor, we pass the file water.bmp to it. The SizeMode decides on the
display of the image in the Picture Box. The enum value of StretchImage
stretches the image to fit it into the PictureBox, whereas, the enum value of
Normal places it in the upper left corner.
The image property is assigned to the picture or the bitmap that is to be
displayed. Three event handlers are attached to the image:
• pDown : This is called each time we click in the picture.
• pUp : This is called when we release the mouse button.
• pMove : This is called when we move the mouse within the picture.
Finally, all the controls i.e. two labels, two scroll bars and one image, are
appended to the Control class.
The property of LargeChange in the scrollbar control decides on the magnitude of
the change in the value of the scroll bar, when the scroll bar is clicked. The
SmallChange property is associated with the arrows on the scroll bar. We have
set the LargeChange property to 20 and the SmallChange to 1.
To dispel the banality of the somber explanation given above, let us digress
slightly to present a small, albeit important, elucidation on how Windows
handles the art of scrolling.
Screen 4.43
The value of the scrollbar ranges between the minimum and the maximum. The
maximum value is LargeChange+1. The rationale behind this is that, the scrollbar
has a property called Value, which represents the position of the top of the
thumb. The size of the scrollbar’s thumb is equal to the page value or
LargeChange. When we reach the last page, or when the thumb reaches the end of
the scroll bar, whereupon, we cannot scroll any further, the value property will
always be shown less than the maximum.
We shall now beguile you with the concepts of the vertical scroll bars. The
explanation for the horizontal scroll bars is much the same.
The function vmf, which is called from the constructor, initializes 3 variables.
The Height of the Vertical scroll bar is 152 pixels because the Size property of
the scrollbar is initialized to this value. The Height of the picture is 96
pixels because the Size property of the picture is initialized to this value.
The difference between these two heights, which is 56 pixels, is stored in the
variable hsb. The variable named ticks stores a value, which is within a range
that the vertical scroll bar can handle.
A Tick happens to be the smallest increment that a scrollbar can make. In our
case, as the Minimum is -100 and the Maximum is 100, the range of value stored
in ticks becomes 200. This variable represents the ticks that the scroll bar
needs in order to move from one end to the other. In the same vein, the variable
hsb denotes the amount of pixels the image needs to be relocated from one end of
the scroll bar to another. This is calculated by subtracting the height of the
image from the height of the scroll bar.
Screen 4.44
Dividing 56 (the number of pixels to be moved) by 200 (the total number of ticks
available), gives us a value of .28. Thus, every tick moved by the scroll bar
moves the image by .28 pixels. This value, known as the ‘pixels per tick’, is
stored in vm. A similar routine is followed for the hmf function while
implementing a horizontal scroll bar.
The labels initially display zero, since this is the default value stored in the
property value. The cursor property of the picture is changed to SizeAll. As a
result of this, whenever the cursor moves into the picture, it’s shape changes
into a four-headed monster.
At the outset, we want to move the picture downwards. So, we click on the
vertical scroll bar. This movement calls the vScroll function. In this function,
we initialize the Text property of the label l2 to the Value property of the
vertical scroll bar v. Then, variable vp is calculated, whose value is the
current value of the scroll bar, i.e. the value of the Minimum property of the
scroll bar.
Every downward movement of the scroll bar increments the Value property by 1.
This is because, the value of property SmallChange has been initialized to 1.
Thus, the value of vp will commence at 100 and will keep escalating thereafter.
The value stored in the Bottom property of the scroll bar is 176 and the Height
of the picture is 96 pixels. Thus, the picture will start at 52 pixels, and
then, the distance between the picture and the bottom property will start
reducing.
The formula for computing the top position where the picture should be placed is
as follows: the Bottom of the scroll bar, minus the pixels per tick, multiplied
by the current value of the property Value, minus the Height of the picture,
plus the value of Minimum.
Now, let us do the reverse i.e. let us move the picture around and see how the
scroll bars behave. Also, let us observe the corresponding transformation in the
values. To move the picture, we first have to click on it with the left mouse
button. This calls function pDown. In this function, the value of variable d,
which is a boolean, is set to true. Simultaneously, in the function pUp, the
value of variable d is set to false.
The parameter e of MouseEventArgs has two members X and Y, which furnish the
co-ordinates of the mouse pointer with regard to the picture, rather than the
window or form. We save these values in the variables ox and oy, which will be
utilized to calculate the distance that the picture has been dragged.
The function pMove is the focus of all attention since, this is where the real
excitement action lies! We place all the code in a large if statement, which
results in true when the variable d is true. This occurs only when the mouse
button is depressed. Each time the function is called, the minimum and maximum
values of the scroll bars are stored in 4 variables. This is futile, since the
values always remain constant.
We calculate a variable called Value as follows:
The initial Y position of the mouse before its dragging commenced, minus the
current Y position of the mouse. The result is then divided by the multiplier to
convert the pixels into scroll bar ticks. This simulates the scroll bar
scrolling in the opposite direction.
If the new value of variable Value is less than the minimum value allowed for
the scroll bar, the property of the scroll bar v is set to the minimum possible.
If the value is larger than the maximum permissible value, then it is
initialized to the largest possible value. This concept has been explained in
the small note we earlier presented on scrolling. If none of the above hold
true, then we merely change the property Value of the scroll bar to the variable
Value. These are simple error checks. The labels l1 and l2 are also updated.
The above explanation is also relevant for the horizontal scroll bar.
The property Top of the picture has to be updated to take into account the new
position. This is also stored in the variable value and computed as follows:
The original Top property value, plus the current position of the mouse, minus
the position of the mouse before the dragging commenced.
Like before, we make sure that we do not exceed the Top and Bottom limits of the
scroll bar. The picture has to be contained within the scroll bars. If it is
smaller than the Top property of the scroll bar, we change the value to that of
the Top property. The same holds true for the Bottom. Finally, we initialize the
Top property of the picture to Value and do the same for the Left property.
Up Down Control
a.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form {
DomainUpDown u;
NumericUpDown n;
DomainUpDown a;
public zzz() {
ClientSize = new Size(504, 352);
n = new NumericUpDown();
n.Location = new Point(132, 132);
n.Maximum = new System.Decimal(100d);
n.Minimum = new System.Decimal(0d);
n.DecimalPlaces = 2;
n.Text = “0.00″;
Controls.Add(n);
a = new DomainUpDown();
a.Location = new Point(152, 32);
a.Size = new Size(120, 23);
a.SelectedItemChanged += new EventHandler(abc);
Controls.Add(a);
u = new DomainUpDown();
u.Location = new Point(152, 64);
u.SelectedItemChanged += new EventHandler(pqr);
Controls.Add(u);
a.Items.Add(new yyy(“Center”,(int) HorizontalAlignment.Center));
a.Items.Add(new yyy(“Left”,(int)HorizontalAlignment.Left));
a.Items.Add(new yyy(“Right”,(int)HorizontalAlignment.Right));
u.Items.Add(new yyy(“Left”,(int)LeftRightAlignment.Left));
u.Items.Add(new yyy(“Right”,(int)LeftRightAlignment.Right));
u.SelectedIndex = 1;
}
void pqr(object s, EventArgs e)
{
yyy c = (yyy)(u.Items[u.SelectedIndex]) ;
n.UpDownAlign = (LeftRightAlignment)(c.i);
}
void abc(object s, EventArgs e)
{
yyy c = (yyy)(a.Items[a.SelectedIndex]) ;
n.TextAlign = (HorizontalAlignment)(c.i);
}
public static void Main()
{
Application.Run(new zzz());
}
class yyy
{
public string s;
public int i;
public yyy(string sz, int n)
{
s=sz;
i=n;
}
public override string ToString()
{
return s;
}
}
}
Screen 4.45
In the above program, we have a NumericUpDown control called n. A NumericUpDown
control is used when we have a single numeric value that needs to be incremented
or decremented.
Screen 4.46
This can be done by clicking on the Up and Down buttons of the control
respectively. This control has a property called ReadOnly, which has a value of
False by default.
This allows entry of a value directly in the control if the user finds it too
bothersome to click on the buttons to do so. From this perspective, it operates
akin to a text box. The properties Minimum and Maximum specify the minimum and
maximum values that the control can accept. Under no circumstances will the
control permit us to exceed the range specified by the above two properties.
Thus, error checking has been built into the control.
A NumericUpDown control has a large number of properties such as DecimalPlaces,
Hexadecimal, ThousandsSeparator etc. These properties format the value that is
displayed using the Text property.
Their roles are as follows:
• The DecimalPlaces property controls the number of decimal places.
• The Hexadecimal property displays numbers in hexadecimal format.
• The ThousandsSeparator property decides on the character to be used to
separate the 1000s. This is because the comma is not the universally accepted
separator.
The Increment property of the control which has a default value of 1 decides on
the amount of increase/decrease in the number. each time we click on the up or
down buttons. n.Increment=10; will increase the number by 10. The two functions
ParseEditText and UpdateEditText get called with every change in the number.
Thereafter, two DomainUpDown controls, which behave in a manner similar to a
NumericUpDown control, are created. These controls display a string instead of a
number. Thus, the value passed to this control can be of any class, since all
classes are derived from object. The user can also type in text directly. The
value typed in must obviously match an item in the collection. The ToString
function of the object is called, to display the value in the up-down control.
The DomainUpDown control has a property called Items, that returns a
DomainUpDown.DomainUpDownItems object. This object represents the object
collection. Thus, we can use the Add or Remove methods from the above collection
to add or remove the items individually. The Sort property sorts the collection.
We pass an object like yyy to the Add function, which accepts a string and an
enum called HorizontalAlignment. The enum value is stored in the variable i to
facilitate its retrieval at a later stage.
Each time we click on the control, function abc gets called. This is because the
SelectedItemChanged event has been initialized to this function. In this
function, we use the property SelectedIndex, which returns a number depicting
the item that has been selected. The property is used as an array index. It
returns the yyy object at that index. We store this object in c and then, access
the variable i stored in the object yyy.
Thus, the entire object yyy is stored as part of the collection. The string and
the int can both be accessed together. If the SelectedIndex property is not set,
it will not display any value in the control.
DateTimePicker Control
a.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form {
DateTimePicker d;
public zzz() {
ClientSize = new Size(504, 293);
d = new DateTimePicker();
d.Location = new Point(24, 24);
d.CalendarFont = new Font(“Times New Roman”, 8f);
d.Size = new Size(200, 20);
d.CalendarForeColor = System.Drawing.SystemColors.WindowText;
d.ShowCheckBox = true;
d.ForeColor = System.Drawing.SystemColors.WindowText;
d.Format = System.Windows.Forms.DateTimePickerFormat.Custom;
d.BackColor = System.Drawing.SystemColors.Window;
d.CustomFormat = “\’Date : \’yy MM d – HH\’:\’mm\’:\’s ddd”;
d.Anchor = AnchorStyles.TopAnchorStyles.Right AnchorStyles.Left;
DateTime now = DateTime.Now;
d.Value = now;
Controls.Add(d);
}
public static void Main()
{
Application.Run(new zzz());
}
}
Screen 4.47 Screen 4.48
On running the above program, we see a listbox displaying a date. This date is
the system date. You may wonder as to what is the big deal about displaying a
simple date. But, when you click on the down arrow of the list box, a
fine-looking calendar springs up! The current date is shown highlighted in red.
If you click on any date, it will be instantly displayed in the list box. Once
the date has been chosen, the calendar vanishes. You can change the month by
clicking on the arrows, which are on the left and right of the month.
Now, let us get behind the scenes and unravel the mysteries of this program. We
first create an object d as an instance of class DateTimePicker, thus
encapsulating the standard Windows date time picker control. The control has all
the standard properties like Location, Size, Color etc. The default size, in the
case of the DateTimePicker, is a width of 200 pixels and a height of 23 pixels.
The colors of the control can be changed using CalendarForeColor and ForeColor.
The property CalendarFont decides on the font to be used to display the dates.
Screen 4.49
The ShowCheckBox property , which has the default value of false, is set to
true, in order to display a check box on the extreme left of the date displayed
in the control. The checkbox presently is checked.
If the checkbox is checked, the date is valid. If not, the date is said to be
unset.
The Format property of the control, which is initialized to Custom, decides the
display format of the date and time in the control. This property can be
initialized to any four of the following enums: Custom, Short, Long and Time.
The last three values use the operating system’s format options.
Since the Format property has been given the value of Custom, the property
CustomFormat decides on the display format of the date in the control. This
property is initialized as follows: The word Date:, followed by the year, month
and day separated by a space, followed by a minus sign and finally, the time.
The three ‘ddd’ represent the day of the week in words.
The Now property of the DateTime class, that returns the current day, is finally
assigned to the Value property of the picker class, which then gets displayed in
the control.
LinkLabel Control:
a.cs
using System;
using System.Windows.Forms;
using System.Drawing;
public class zzz : Form {
PropertyGrid pg;
LinkLabel l;
Panel p;
GroupBox g;
public zzz() {
l = new LinkLabel();
l.DisabledLinkColor = (Color)System.Drawing.Color.Blue;
l.ForeColor = (Color)System.Drawing.Color.Gainsboro;
l.Location = new System.Drawing.Point(32, 128);
l.BackColor = (Color)System.Drawing.Color.Transparent;
l.LinkArea = new LinkArea(13, 28);
l.Font = new System.Drawing.Font(“Tahoma”, 12f, System.Drawing.FontStyle.Bold,
System.Drawing.GraphicsUnit.World);
l.Text = “please click on sonal to see a message box”;
l.Size = new System.Drawing.Size(136, 96);
l.LinkClicked += new LinkLabelLinkClickedEventHandler(abc);
ClientSize = new System.Drawing.Size(504, 445);
pg = new PropertyGrid();
pg.Dock = System.Windows.Forms.DockStyle.Fill;
pg.Location = new System.Drawing.Point(3, 16);
pg.CommandsVisibleIfAvailable = true;
pg.Text = “propertyGrid1″;
pg.Size = new System.Drawing.Size(242, 405);
pg.SelectedObject = l ;
g = new GroupBox();
g.Location = new System.Drawing.Point(248, 16);
g.Anchor = AnchorStyles.TopAnchorStyles.Right AnchorStyles.Left;
g.Text = “LinkLabel Properties”;
g.Size = new System.Drawing.Size(248, 424);
g.Controls.Add(pg);
Controls.Add(g);
p = new Panel();
p.Size = new System.Drawing.Size(200, 320);
p.Location = new System.Drawing.Point(24, 40);
p.BackgroundImage = (Bitmap) new Bitmap(“hikingboot.bmp”);
p.Controls.Add(l);
Controls.Add(p);
}
void abc(object sender, LinkLabelLinkClickedEventArgs e)
{
MessageBox.Show(“hi”) ;
l.LinkVisited = true ;
}
public static void Main() {
Application.Run(new zzz());
}
}
To avoid any exception from being thrown, you should copy the file
hikingboot.bmp to the current directory. Though the above program is not very
sizeable, its achievements are substantial. The output of this program shows our
screen divided into two parts as follows:
(a) The left pane has a picture in the background with some text that behaves
like a hyperlink. Clicking on the hyperlink displays a message box with the
message ‘hi’ and changes the color of the hyper link.
Screen 4.50
(b) On the right hand side, we come across a large number of properties. When we
click on the plus sign in front of the font property, yet more properties get
displayed. On varying the Font, the font of the text displayed in the window on
the left side changes. You can experiment with the other properties too.
Thus, you can achieve a lot without writing tons of code!
l is an instance of LinkLabel, which relates to text that can be displayed as a
hyper link. The DisabledLinkColor property of LinkLabel decides the color of the
hyperlink when it is disabled. The ForeColor and the BackColor specify the
foreground and the background color of the control respectively.
We may not intend to display the entire string as a hyperlink. So, we use the
LinkArea property to ascertain the text that is to be hyper linked. The default
position begins at 0,0.
The GraphicsUnit enumeration in the Font property specifies a unit of
measurement. The value displayed uses 1/75 of an inch, since the unit of measure
selected is World. We could also use any of the other 6 values in the enum, such
as pixel, millimeter etc.
Screen 4.51
The Text property is the most significant property, as it decides the text to be
displayed. The LinkClicked is the event property that will call function abc
each time we click on the link. In this function, we display a Message Box and
change the LinkVisited property to True, which results in a change in the color
of the hyper link. You may observe that this LinkLabel instance l is not added
to the form. Next, we create an instance of a PropertyGrid class and store it in
pg.
The Dock property decides on the edge of the container that this property will
be docked to. The usage of the Fill style allows docking of the property on all
sides.
The property CommandsVisibleIfAvailable displays the command pane only for those
objects that expose verbs. As always, the Text property is initialized to some
text that does not get displayed. The property of SelectedObject is assigned the
LinkLabel control, thereby linking it into the grid and facilitating browsing of
the LinkLable control properties. This property also allows us to browse
multiple objects. Finally, we add this object to the Form, thereby indirectly
adding the hyperlink also.
A group box is merely an anthology of other controls. The Group box control is
the one that contains the PropertyGrid object. We also create an instance of a
Panel object, which, like a GroupBox class, contains other controls. If the
Enabled property in the panels is set to False, all the controls within it will
be disabled. The panel control, by default, is drawn without any Borders. The
BorderStyle property provides us with two-dimensional or three-dimensional
borders, to distinguish a panel from other areas of the form. The Panel class
can also contain scrollbars.
The Panel class has a property called BackgroundImage that selects the picture
to be displayed in the panel. This Panel class has the LinkLabel added to it, to
which we add the Panel. Thus, we have two controls that have been added directly
to the form, the Panel that has the LinkLabel and the GroupBox that has the
PropertyGrid.
ListBox Control
a.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form
{
sss p;
ListBox l;
Button b;
ColorDialog c;
ImageList i;
public zzz()
{
b = new Button();
b.Location = new System.Drawing.Point(16, 200);
b.Text = “Color”;
b.Size = new System.Drawing.Size(75, 23);
b.Click += new EventHandler(abc);
Controls.Add(b);
c = new ColorDialog();
p = new sss();
p.Location = new System.Drawing.Point(64, 90);
p.Size = new System.Drawing.Size(64, 168);
Controls.Add(p);
i = new ImageList();
i.ImageSize = new Size(24, 22);
i.Images.Add(new Bitmap(“club.bmp”));
i.Images.Add(new Bitmap(“diamond.bmp”));
i.Images.Add(new Bitmap(“heart.bmp”));
i.Images.Add(new Bitmap(“spade.bmp”));
Size = new System.Drawing.Size(512, 320);
l = new ListBox();
l.ForeColor = (Color)System.Drawing.SystemColors.WindowText;
l.Location = new System.Drawing.Point(8, 24);
l.IntegralHeight = false;
l.Size = new System.Drawing.Size(232, 60);
l.ColumnWidth = 144;
l.SelectedIndexChanged += new EventHandler(pqr);
l.Items.AddRange (new object[] {“a1″, “a2″, “a3″, “a4″});
l.SelectionMode = SelectionMode.MultiSimple;
Controls.Add(l);
}
void abc(object sender, EventArgs e)
{
if (c.ShowDialog() == DialogResult.OK)
{
l.ForeColor = c.Color;
}
}
void pqr(object sender, EventArgs e)
{
p.ci();
int[] se = new int[l.SelectedIndices.Count];
l.SelectedIndices.CopyTo(se, 0);
for (int i=0; i<se.Length; i++)
{
int ind = se[i];
object it = l.Items[ind];
string s = it.ToString();
Image im = aaa(s);
p.ai(im);
}
p.Invalidate();
}
Image aaa(string b)
{
if (b.Equals(“a1″))
{
return i.Images[0];
}
else if (b.Equals(“a2″))
{
return i.Images[1];
}
else if (b.Equals(“a3″))
{
return i.Images[2];
}
else if (b.Equals(“a4″))
{
return i.Images[3];
}
else
{
return null;
}
}
public static void Main()
{
Application.Run(new zzz());
}
}
public class sss : Panel
{
Image[] i = new Image[4];
int Cnt=0;
public virtual void ai(Image img)
{
i[Cnt++] = img;
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
for (int j=0; j< Cnt; j++)
{
pe.Graphics.DrawImage(i[j], new System.Drawing.Point(0, 30 * j + 5));
}
}
public virtual void ci()
{
Cnt = 0;
}
}
Before running the executable file, the 4 files with the ‘bmp’ extension must be
copied from one of the sample directories into the current directory.
Screen 4.52 Screen 4.53
When we run the above program, we see a list box with 4 values and a button
labeled ‘Color’. When we click on the button, it displays a color dialog box,
which permits us to choose from amongst a plethora of colors. We can select a
color and then, click on the OK button. As a result, the color of the list box
items changes to the selected color. We are also allowed to create our own
custom colors in the dialog box.
Screen 4.54 Screen 4.55
Each time we select an item from the list box, an image representing the item is
displayed. If we reselect an item that we had selected earlier, the currently
selected image gets replaced by the latest selected one.
In the program, a Button b is created. It calls function abc whenever it is
clicked. Then, an instance c of class ColorDialog is created. We have created
the class sss. It is derived from the class Panel. It has a member i, which is
an array of 4 images and an int variable named Cnt. The variable Cnt is used as
an index to access the images in the array, and to store the count of the number
of images. When we add the sss object to our Form, nothing gets displayed in the
window. This is because, there are no controls in the panel.
Next, we create an ImageList object called i. This class stores a collection of
images that can be used by other controls such as the Toolbar or ListView.
Bitmaps or Icons are added to this class so that they are available for
exploitation by other controls. Images is a property of type
ImageList.ImageCollection, whose Add method is used to add a bitmap to the
collection. We add a total of 4 bitmaps to our ImageList object.
The ListBox object l has a property ColumnWidth, that decides on the width, in
pixels, of each column in the ListBox. In a multi-column ListBox, the
ColumnWidth property refers to the width of each column. A value of zero is
connotative of the fact that each column has a default width, taking into
account the data displayed in them. The property IntegralHeight decides whether
partial items can be displayed or not. If the value is set to true, then only
complete items are displayed. In this case, the ListBox will be resized to
ensure that partial items are not displayed.
Each time we select an item, the Event SelectedIndexChanged gets kick started.
This event, in turn, calls function pqr. Every list box has a property called
Items, which is a collection of items displayed in the list box. The data type
of Items is ListBox.ObjectCollection. It has a method called AddRange, which
accepts an array of Child Controls present in the list, sorted on the index.
Since we want the list box to display the text a1, a2, a3 and a4, we initialize
the object array to this array of strings.
The property SelectionMode decides on the number of items that can be chosen or
selected concomitantly.
Four options available:
• None : This means that no items can be selected. In effect, it disables the
ListBox.
• One : This allows us to select only one item at a time,
• MultiSimple : This lets us choose multiple items at the same time
• MultiExtended : This enables us to choose multiple items at a time and allows
us to use keys like SHIFT and CONTROL combinations to select the multiple
options.
We finally add the list box to the Form. Clicking on the button labeled Color
calls ShowDialog from the ColorDialog class, which displays a dialog box with a
zillion colors. Until we click on the OK button or the Cancel button, we cannot
leave the dialog box.
The button returns one of two values:
• DialogResult.OK if we click on OK button.
• DialogResult.CANCEL if we click on the Cancel button.
If the user opts to click on the OK button, the color selected is assigned to
the ForeColor property of the ListBox.
The function pqr is the vortex of action. The function is called when any item
is selected or unselected from the list box. We first call function ci from the
class sss, which initializes the variable Cnt to zero.
The SelectedIndices property returns a collection object named
ListBox.SelectedIndicesCollection, which lists all the items that are currently
selected in the list box. This is so because we can select multiple items from
the list box. First, an array called se of type int is created, depending on the
number of items selected. Then, the selected indices are copied to the array.
The variable ind in the for statement refers to each index selected. The Items
array returns an object that stores the value of each index. As we need the
string representation of the selected item, function ToString is used to convert
the object into a string. This facilitates the possibility of having a list box
containing pictures.
We then call the function aaa that accepts a string representing the ListBox
item selected, which can be either a1, a2, a3 or a4. It returns an image
representing the string. It then checks the value of the string passed and
returns an image stored in the ImageList object. The function ai merely uses the
variable Cnt to index the array of images with a new picture. Thus, if we select
three images, the array i will contain three pictures. When the for loop
terminates, we call the function Invalidate.
As we had learnt earlier, function OnPaint displays all the pictures. This is
achieved using the Cnt variable that contains the number of images stored in the
Array. Thus, we use the ImageList class to store the images and the array of
images in the sss class to store the images that need to be displayed each time.
StatusBar control
a.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form {
System.ComponentModel.Container c;
StatusBar s;
StatusBarPanel s1;
StatusBarPanel s2;
StatusBarPanel s3;
Timer t;
public zzz() {
c = new System.ComponentModel.Container();
s2 = new StatusBarPanel();
t = new Timer(c);
s = new StatusBar();
s3 = new StatusBarPanel();
s1 = new StatusBarPanel();
s2.AutoSize = (StatusBarPanelAutoSize)2;
s2.Alignment = HorizontalAlignment.Right;
s2.Width = 76;
s3.AutoSize = StatusBarPanelAutoSize.Contents;
s3.Width = 20;
s1.BorderStyle = StatusBarPanelBorderStyle.None;
s1.Icon = new Icon(“status.ico”);
s1.AutoSize = StatusBarPanelAutoSize.Contents;
s1.Width = 62;
s1.Text = “sonal”;
Size = new System.Drawing.Size(512, 320);
KeyUp += new KeyEventHandler(abc);
t.Interval = 1000;
t.Enabled = true;
t.Tick += new EventHandler(pqr);
s.Size = new Size(212, 20);
s.Location = new Point(0, 216);
s.BackColor = (Color)SystemColors.Control;
s.Text = “Vijay Mukhi”;
s.ShowPanels = true;
s.Panels.AddRange((StatusBarPanel[])new StatusBarPanel[] {s1, s2, s3});
Controls.Add(s);
s3.Text = “OVR”;
}
void abc(object se, KeyEventArgs e)
{
if (e.KeyCode == Keys.Insert)
{
string s = this.s3.Text;
if (s.Equals(“INS”))
s3.Text = “OVR”;
else
s3.Text = “INS”;
}
}
void pqr(object se, EventArgs e)
{
DateTime t = DateTime.Now;
string s = t.ToLongTimeString() ;
s2.Text = s ;
}
public static void Main()
{
Application.Run(new zzz());
}
}
Every Windows application displays a StatusBar control. This program is going to
introduce the status bar in our window. In order to accomplish this, the file
status.ico needs to be copied from one of the sample directories into the
current directory.
The above program merely presents a blank window with a status bar at the
bottom. The status bar has the following contents:
• an icon.
• the word ‘sonal’.
• the time ticking away in the middle.
• the status of the Insert key.
Screen 4.56 Screen 4.57
Each time we use the insert key, the text toggles between INS and OVR.
The class Container encapsulates zero to one or more components. We create three
status bar objects s1, s2 and s3, which are instances of class StatusBarPanel.
This class, in turn, is derived from class Component. It stores the StatusBar
control panel information. The object s is an instance of class StatusBar, which
represents a Window status bar control. This control has no panels by default.
The property AutoSize in the StatusBarPanel class regulates the changes
occurring in the panel of a status bar, whenever the status bar is resized. The
values are obtained from the enumeration StatusBarPanelAutoSize that has the
following three values:
• Contents: As is evident from the name, the contents of the status bar decide
how its size will change.
• None: The status bar panel does not change whenever the status bar is resized.
• Spring: The panel shares the available space with all other panels having a
setting of Spring, after yielding space to the panels having either the Contents
or None settings.
Text can be aligned in a status bar panel in 3 ways with reference to the status
bar, i.e. Left, Right and Center. The Alignment property, by default, has a
value of Left. For the status bar panel s2, we have set it to Right. The default
width is 100 pixels for a status bar panel. An Icon can be specified along with
the text that is displayed. To do so, we initialize the Icon property to a .ico
file. An .ico file is a small transparent bitmap image.
The KeyUp Event is fired whenever a key is released in the focused control. In
our case, function pqr is called. The Timer class, that implements a Windows
timer, merely activates an event at a particular time interval. This timer,
which can be used in a window, is designed for a single threaded environment
only, where User Interface threads are being executed. The Interval property
decides the time, in milliseconds, between timer ticks. The Enabled property
sets the timer On. The garbage collector does not interfere with the timer while
it is running. The Event Tick will call the function pqr whenever the time
interval set in the timer elapses. In our case, it is occurs after one second.
The StatusBar is made up of panels. The ShowPanels property displays all the
panels that are added using the AddRange method, i.e. s1, s2 and s3. Finally,
the text of the last panel is set to OVR and the StatusBar object is added to
the form.
The function abc gets called each time we press a key in our form. This function
is called with the parameter e of KeyEventArgs, which has a member KeyCode,
which contains a number corresponding to the key pressed. As it is difficult to
remember the numbers assigned to every key, the enumeration Keys is used to
represent the keys. If the Insert key is pressed, the current Text displayed in
the status bar panel s3 is retrieved and the value gets toggled from OVR to INS.
On completion of the timer interval, we use the Now property of the DateTime
class to provide us with the current time. This is then supplied to s2.Text,
which updates the status bar panel with the current time every second. What you
can display in a status bar is limited by your imagination. It is common to
display the status of keys and the time on the status bar.
Tab Control
a.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form {
GroupBox g2;
GroupBox g1;
ImageList i;
TabPage t1;
TabPage t2;
TabControl t;
public zzz()
{
ClientSize = new Size(546, 293);
g1 = new GroupBox();
g1.Location = new Point(12, 16);
g1.Text = “Sonal”;
g1.Size = new Size(202, 144);
g2 = new GroupBox();
g2.Location = new Point(12, 16);
g2.Text = “Vijay mukhi”;
g2.Size = new Size(202, 128);
i = new ImageList();
t1 = new TabPage();
t2 = new TabPage();
t = new TabControl();
t1.Text = “Mukhi”;
t1.Size = new Size(224, 193);
t1.ImageIndex = 0;
t1.TabIndex = 0;
t2.Text = “Vijay”;
t2.ImageIndex = 1;
t2.TabIndex = 1;
t.Location = new Point(24, 32);
t.Size = new Size(232, 220);
t.SelectedIndex = 0;
t.ImageList = i;
i.Images.Add((Bitmap)new Bitmap(“calendar.bmp”));
i.Images.Add((Bitmap)new Bitmap(“note.bmp”));
t.ImageList = i;
Controls.Add(t);
t1.Controls.Add(g1);
t2.Controls.Add(g2);
t.Controls.Add(t1);
t.Controls.Add(t2);
}
public static void Main()
{
Application.Run(new zzz());
}
}
This program requires two bitmaps called note.bmp and calendar.bmp.
We start by creating two GroupBox controls named g1 and g2, and change the
properties of Locations, Size and Text. The ImageList i stores a list of images.
We next create two TabPage objects t1 and t2. A TabPage class implements a
single page of the TabControl class. It is a panel class having the properties
of a TabItem.
Screen 4.58 Screen 4.59
The string assigned to the Text property is displayed as the tab page text. We
can set it to a certain size. The ImageIndex property is an index into the
ImageList list object, which is associated with the TabControl. The TabPage
objects are added to the TabControl using the Controls collection. The ImageList
property is initialized to the list of images denoted by the ImageList class.
Thus, the ImageIndex member decides on the image that will be displayed along
with the Text in the TabControl.
A TabControl shows a list of TabPages. Clicking on the tab activates them. A
TabControl, in other words, is like a series of Dialog boxes containing
controls, organized in a logical fashion. The controls that would earlier have
appeared in one large dialog box, are now placed in separate dialog boxes.
As mentioned earlier, we add the GroupBox to the Control collection of the
individual TabPage objects t1 and t2. The controls that we require are added to
the GroupBox. The GroupBox, in turn, gets added to the TabPage. From then on,
the TabControl takes over and we can flick between TabPages with ease.
ToolTip Control
a.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form {
System.ComponentModel.Container c;
ToolTip t;
PictureBox p1;
PictureBox p2;
public zzz()
{
c = new System.ComponentModel.Container();
t = new ToolTip(c);
t.Active = true;
t.ShowAlways = true;
t.AutomaticDelay = 100;
t.AutoPopDelay = 100;
t.InitialDelay = 100;
t.ReshowDelay = 100;
p1 = new PictureBox();
p2 = new PictureBox();
Size = new Size(512, 300);
p1.Location = new Point(8, 7);
p1.Size = new Size(20, 20);
p1.Image = new Bitmap(“open.bmp”);
t.SetToolTip(p1, “vijay”);
Controls.Add(p1);
p2.Location = new Point(28, 7);
p2.Size = new Size(20, 20);
p2.Image = new Bitmap(“new.bmp”);
t.SetToolTip(p2, “mukhi”);
Controls.Add(p2);
}
public static void Main()
{
Application.Run(new zzz());
}
}
All of us need help at some point in time. The ToolTip control is just the
antidote for all maladies. Before running the executable, copy the two bitmaps
open.bmp and new.bmp to the current directory.These two bitmaps will be
displayed as two separate images on our form. Whenever we move our cursor close
to them, a yellow colored help text comes into sight.
A ToolTip class gives us a small window that contains a single line of text that
describes what a control stands for. The real productivity of Bitmaps and Icons
can be fully harnessed only if we are able to discern their functionality. The
Active property, if set to false, disables the ToolTip control. The ShowAlways
property will show a ToolTip despite the fact that the control it is assigned
to, is currently disabled.
Screen 4.60 Screen 4.61
The following four properties have been set for the Tooltip control:
• AutomaticDelay: It denotes the time in milliseconds that elapses before the
tool tip appears. The default is 500 milliseconds.
• AutoPopDelay: It is the time in milliseconds for which the ToolTip remains
visible to the user while the mouse is stationary on the control. Its default
value is 10 times the value of the property AutomaticDelay.
• InitialDelay: It is the time in milliseconds for which the mouse must remain
fixed within the control, before the tool tip becomes visible again. The default
value is equal to that of property AutomaticDelay.
• ReshowDelay: It is the time in milliseconds that elapses, as the mouse moves
from one control to another. The default is 1/5 of the property AutomaticDelay.
Any amendment made to the property AutomaticDelay has an impact on the above
properties. Thus, it is advisable to first change the AutomaticDelay property,
followed by the other properties.
Two PictureBox controls have been added in our window. We add these two pictures
to the form. This class can display all types of images, including bitmaps,
icons, JPEG, GIF and many more. The Image property decides on the image that
gets displayed. By default, the image has no borders and can be clipped too.
The ToolTip class has a method called SetToolTip, which accepts a control as the
first parameter and the text of the tool tip as the second. Henceforth, whenever
the mouse is positioned on that control, the tool tip will be displayed.
TrackBar Control
a.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class zzz : Form
{
TrackBar t;
Label l;
public zzz() {
Size = new Size(512, 320);
t = new TrackBar();
t.Location = new Point(8, 24);
t.Size = new Size(200, 42);
t.ValueChanged += new EventHandler(abc);
t.TickFrequency = 5;
t.Minimum = 0;
t.Maximum = 100;
t.SmallChange = 5;
t.LargeChange = 25;
t.Orientation = Orientation.Horizontal;
t.TickStyle = TickStyle.Both;
l = new Label();
l.Location = new Point(112, 192);
l.Text = t.Value.ToString();
Controls.Add(t);
Controls.Add(l);
}
void abc(object source, EventArgs e)
{
l.Text = t.Value.ToString();
}
public static void Main() {
Application.Run(new zzz());
}
}
The TrackBar class resembles a scroll bar in many respects, but its interaction
with the user is distinct. We can configure values that the TrackBar represents
and also define increments for off-button clicks. The TrackBar can be positioned
horizontally or vertically. The number of ticks that are displayed can also be
configured.
Each time that the value of the TrackBar changes, the Event property
ValueChanged calls the function abc. The property TickFrequency specifies the
number of ticks that will be drawn. The lower limit of the range of the TrackBar
is confined to a value decided by the Minimum property, which in our case is 0.
The upper limit of the range of the TrackBar is confined to a value determined
by the Maximum property, which in our case is 100.
Screen 4.62 Screen 4.63
We may not want to draw 100 ticks to represent all the values. So, we specify a
value such as 5 as the TickFrequency. This will draw a tick mark at a spacing of
5, resulting in a total of 20 ticks. Thus, each tick will represent 5 units of
the TrackBar’s range of values.
The property SmallChange decides on the magnitude of change that occurs in the
TrackBar, whenever the user presses the Up arrow key or the Down arrow key to
move the TrackBar thumb. The value assigned to the LargeChange property is used
whenever we click on the side, or use the Page Up and Page Down keys.
The Orientation property can take only two values i.e. horizontal or vertical.
The TickStyle property uses an enumeration that can have four values:
• None: for no tick marks.
• Both: for tick marks on both sides of the control.
• BottomRight: for tick marks at the bottom for a horizontal control, and on the
right for a vertical control .
• TopLeft: this is the reverse of BottomRight.
We then use a label to display the value of the track bar on the form. This
value is stored in the property Value of the label class. Finally, we add the
label and TrackBar. Function abc merely updates the label to depict the current
position of the TrackBar.
Tree-Node Control
a.cs
using System;
using System.IO;
using System.Resources;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
public class zzz : Form
{
TreeView d;
ImageList i;
public zzz()
{
ClientSize = new System.Drawing.Size(502, 293);
i = new ImageList();
i.Images.Add(new Bitmap(“clsdfold.bmp”));
i.Images.Add(new Bitmap(“openfold.bmp”));
d = new TreeView();
d.ImageList = (ImageList)i;
d.ForeColor = (Color)System.Drawing.SystemColors.WindowText;
d.Location = new System.Drawing.Point(24, 16);
d.AllowDrop = true;
d.Indent = 19;
d.Text = “treeView1″;
d.SelectedImageIndex = 1;
d.Size = new System.Drawing.Size(200, 264);
d.AfterSelect += new TreeViewEventHandler(sss);
d.BeforeExpand += new TreeViewCancelEventHandler(eee);
Controls.Add(d);
string[] dr = Environment.GetLogicalDrives();
for (int ii = 0; ii < dr.Length; ii++)
{
if (PlatformInvokeKernel32.GetDriveType(dr[ii]) ==
PlatformInvokeKernel32.DRIVE_FIXED)
{
TreeNode c = new TreeNode(dr[ii]);
d.Nodes.Add(c);
ddd(c);
}
}
}
void ddd(TreeNode n)
{
DirectoryInfo dir = new DirectoryInfo(ppp(n));
DirectoryInfo[] e = dir.GetDirectories ();
for (int i = 0; i < e.Length; i++)
{
string na = e[i].Name;
if (!na.Equals(“.”) && !na.Equals(“..”))
{
n.Nodes.Add(new TreeNode(na));
}
}
}
void sss(object source, TreeViewEventArgs e)
{
Text = “Windows.Forms File Explorer – ” + e.Node.Text;
}
void eee(object source, TreeViewCancelEventArgs e)
{
TreeNode n = (TreeNode)e.Node;
for (int i = 0; i < n.Nodes.Count; i++)
{
ddd(n.Nodes[i]);
}
}
string ppp(TreeNode node)
{
if (node.Parent == null)
{
return node.Text;
}
return Path.Combine(ppp(node.Parent), node.Text);
}
[STAThread]
public static void Main(string[] args) {
Application.Run(new zzz());
}
}
public class PlatformInvokeKernel32
{
[DllImport("KERNEL32", CharSet=System.Runtime.InteropServices.CharSet.Auto)]
public static extern int GetDriveType(string lpRootPathName);
public const int DRIVE_FIXED = 3;
}
Copy the two bmp files clsdfold.bmp and openfold.bmp required for this program,
from the samples directory into your current directory.
Here, we first create a TreeView control called d. A TreeView displays a list of
items or nodes in a hierarchical manner. A node consists of a caption and a
bitmap; the bitmap is optional. The user can select a node and collapse it or
expand it by clicking on the plus or minus sign, displayed alongside,
respectively.
We have an ImageList control i that represents two images. We initialize the
ImageList property of the TreeView control to the Image List. Thus, the TreeView
can now use the images stored in the ImageList. The AllowDrop property
facilitates the use of the control for events and Drag and Drop operations.
The property Indent decides the indent in the pixels. The SelectedImageIndex
determines as to which picture is to be used from the ImageList when the user
selects an item in the control.
There are a large number of events that get triggered off, but we are capturing
only the following two:
• AfterSelect, that calls function sss
• BeforeExapnd, that calls functions eee.
The EventHandler functions use different delegate types.
The class Environment has a static function GetLogicalDrives that returns an
array of strings, which denotes the logical drives on our machine. If machine
has three drives, C:\, D:\and E:\, the for loop is repeated thrice.
Using the Platform invoke API, we can call any code in any dll. So, we first
call the external function GetDriveType and compare it with the value retrieved
for DRIVE_FIXED, which is 3 in this case.
If there are 3 fixed drives then the if statement is true for 3 of them,
resulting in the display of 3 nodes only. If you have only one fixed drive i.e.
C, you will see only one node on your screen.
Screen 4.64 Screen 4.65
A TreeNode object is created, which is used in the TreeView. The constructor is
passed the drive name to enable it to be displayed as a label for the node. A
TreeView is made up of Nodes. It has a property called Nodes, which is of type
TreeNodeCollection. Using the Add member of the collection, we add the tree
node. Thereafter, function ddd is called with this newly created and empty tree
node object.
Function ppp is called with an empty tree node. If the node has no parent, the
text associated with the node is returned. But if a parent exists, we use the
static function Combine, of the Path class, to combine the two strings together.
The Combine function calls the ppp function recursively.
To begin with, the value of ppp(n) is C:\. The object dir represents directories
in the C drive. To obtain all the directories present on the drive, we call
function GetDirectories, which returns an array of DirectoryInfo objects. The
Name member returns the name of the directory. If the return value is not a . or
.., we add the directory name to the list of Nodes.
In the AfterSelect event, function sss gets called. In this function, we change
the Text of the form to the node selected. This is possible because the node
label is passed as a parameter to TreeViewEventArgs. The function eee recovers
the current active node from the parameter passed to it. Using the Count
property, we calculate the number of nodes contained in the active node and
thereafter, iterate through each of these nodes. The same function ddd is called
with every node.
This is how we can display the sub-directories contained within directories and
the files displayed within the directories. Data is sent to the control, which
merely returns the same data in a tree like form. When we click on the first
plus (+) sign of C:\, the function ppp(n) returns the entire path name of each
directory in a recursive fashion.
Docking
a.cs
using System;
using System.Drawing;
using System.Windows.Forms;
public class zzz :Form {
Panel p1;
Panel p2;
GroupBox g;
Button b;
RadioButton r1;
RadioButton r2;
RadioButton r3;
RadioButton r4;
RadioButton r5;
RadioButton r6;
RadioButton rs;
Splitter s;
public zzz () {
g = new GroupBox();
rs = new RadioButton();
r5 = new RadioButton();
s = new Splitter();
b = new Button();
r6 = new RadioButton();
r4 = new RadioButton();
r1 = new RadioButton();
r3 = new RadioButton();
p1 = new Panel();
p2 = new Panel();
r2 = new RadioButton();
Location = new Point(100, 100);
SizeGripStyle = SizeGripStyle.Show;
ClientSize = new Size(448, 400);
g.Location = new Point(16, 152);
g.TabStop = false;
g.Text = “&Dock”;
g.Size = new Size(88, 176);
s.BackColor = Color.Blue;
s.Dock = DockStyle.Right;
s.Location = new Point(325, 0);
s.Size = new Size(3, 400);
b.BackColor = SystemColors.Control;
b.FlatStyle = FlatStyle.Popup;
b.Anchor = AnchorStyles.None;
b.Text = “Demo Button”;
rs.Text = “rs”;
rs.Size = new Size(100, 23);
r5.Location = new Point(8, 120);
r5.Text = “&Right”;
r5.Size = new Size(72, 24);
r5.Click += new System.EventHandler(abc);
r6.Location = new Point(8, 144);
r6.Text = “&Fill”;
r6.Size = new Size(72, 24);
r6.Click += new System.EventHandler(abc);
r4.Location = new Point(8, 96);
r4.Text = “&Bottom”;
r4.Size = new Size(72, 24);
r4.Click += new System.EventHandler(abc);
r1.Checked = true;
r1.Location = new Point(8, 24);
r1.Text = “&None”;
r1.Size = new Size(72, 24);
r1.Click += new System.EventHandler(abc);
r3.Location = new Point(8, 72);
r3.Text = “&Left”;
r3.Size = new Size(72, 24);
r3.Click += new System.EventHandler(abc);
p1.Text = “ButtonPanel”;
p1.Size = new Size(325, 400);
p1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
p1.Dock = DockStyle.Fill;
p1.BackColor = Color.Green;
p2.Location = new Point(328, 0);
p2.Text = “ControlsPanel”;
p2.Size = new Size(120, 400);
p2.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
p2.Dock = DockStyle.Right;
r2.Location = new Point(8, 48);
r2.Text = “&Top”;
r2.Click += new System.EventHandler(abc);
Controls.Add(p2);
Controls.Add(s);
Controls.Add(p1);
p1.Controls.Add(b);
p2.Controls.Add(g);
g.Controls.Add(r4);
g.Controls.Add(r3);
g.Controls.Add(r1);
g.Controls.Add(r5);
g.Controls.Add(r6);
g.Controls.Add(r2);
rs = r1;
aaa();
}
void aaa() {
if (rs == r1)
b.Dock = DockStyle.None;
else if (rs == r2)
b.Dock = DockStyle.Top;
else if (rs == r3)
b.Dock = DockStyle.Left;
else if (rs == r4)
b.Dock = DockStyle.Bottom;
else if (rs == r5)
b.Dock = DockStyle.Right;
else
b.Dock = DockStyle.Fill;
}
protected void abc(object s, EventArgs e)
{
rs = (RadioButton)s;
aaa();
}
public static void Main() {
Application.Run(new zzz());
}
}
This program may be generously proportioned, however it is very simple to
comprehend. We have created seven radio buttons, one GroupBox, two panels, one
splitter and one button. The SizeGripStyle property can have three values:
• Show: that always shows the sizing grip.
• Hide : that always hides the sizing grip.
• Auto : that shows and hides the sizing grip as necessary.
The sizing handle is always shown at the right hand corner of the form.
The button has a FlatStyle of Popup that determines the appearance of the
button. Clicking on any of the Radio Buttons results in a call to the function
abc. This is as a consequence of the Event property Click having been set to
abc. Next, we add all the controls to the Form. The repetitive code for adding
the controls results in a lengthy program.
Screen 4.66 Screen 4.67
The function abc gets called. Its first parameter is an object type. The object
stands for the control that calls the function. In function abc, the RadioButton
that was clicked on, is represented by s. The value contained in s is stored in
another RadioButton called rs. The function aaa is called thereafter. It
compares the value contained in rs with the 5 radio buttons r1 to r5. Depending
upon the value that matches, the Dock property of the button changes.
Screen 4.68 Screen 4.69
When a control is docked to the edge of a container, it will always take up a
position flush against the edge, if the control is resized.
The Left, Right, Top and Bottom styles position the control towards the
respective edge of the container. Fill will fill upto all the sides of the
control.
The control is resized to fill the container’s edges. The First dock style named
None, ensures that the control does not get docked at all.
Threads, Events and Mutexes { The C# Language }
Threads, Events and Mutexes
Threads operate on the premise that the computer is definitely more expeditious
than a human at
executing tasks, resulting in the computer idling away most of its processing
time, waiting for
the human operator. Threads are a means of overcoming this wastage of processing
time. They
perform multiple tasks on a computer in rapid succession, thereby, creating the
illusion of
these tasks being executed simultaneously. No application can ever be
comprehensive without
employing threads. Before we try to infer what a thread does in the programming
context, let us
rummage through a few examples given below.
Without being discursive, let us venture out on our odyssey of understanding the
concept of a
thread with the assistance of a very diminutive program.
a.cs
using System.Threading;
public class yyy
{
public static void abc()
{
System.Console.WriteLine(“Hi”);
}
}
public class zzz
{
public static void Main()
{
ThreadStart ts = new ThreadStart(yyy.abc);
Thread t = new Thread(ts);
System.Console.WriteLine(“Before Start”);
t.Start();
}
}
Output
Before Start
Hi
This one is bound to leave you astonished because, we had earlier talked about
starting out with
a ‘dimunitive’ program. However, by no stretch of the imagination can the above
program qualify
as miniscule. Besides, the only work accomplished by this function is that it
calls the static
function abc, which in turn displays ‘Hi’.
In Main, we create an object ts, which is an instance of the class ThreadStart,
which is
derived from Delegate. Therefore, even though ThreadStart is a class, it also
happens to be a
delegate, whose constructor is given a static function called abc. Function abc
is placed in the
yyy class so that other classes can also use it. The program will work in a
similar manner even
if the static function is placed in class zzz.
Next, we create another object t, which is an instance of Thread. The
constructor of this object
is given a ThreadStart object ts. Indirectly, ts stands for the static function
yyy.abc since it
is a delegate.
So far, tranquility prevails and nothing transpires. The function yyy.abc too
does not get
called. But, as soon as we call Start off the thread, the function abc gets
catapulted into
action. Thus, the function abc is called only when the Start function is called.
This is really
no big deal. Note that classes beginning with Thread belong to the
System.Threading namespace.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
System.Console.WriteLine(“Hi”);
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
t.Start();
}
}
Output
Hi
The above program is similar to the previous one and resembles the samples
supplied by
Microsoft. The function abc to be called, is non-static and hence, an object
name is needed to
reference it. The ThreadStart delegate object is directly passed as a parameter
to the Thread
constructor.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
for ( int i = 0; i<=3;i++)
{
System.Console.Write(i + ” “);
}
}
public void pqr()
{
for ( int i = 0; i<=3;i++)
{
System.Console.Write(i+ “…”);
}
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
Thread t1 = new Thread(new ThreadStart(a.pqr));
t.Start();
t1.Start();
}
}
Output
0 1 2 3 0…1…2…3…
The example embodies a very large amount of code. However, it does not create
any fresh ripples
in our pond of knowledge. We have merely created two Thread objects t and t1,
and passed their
constructors a different delegate or function name, i.e. abc and pqr,
respectively. Thereafter,
the Start function has been called in t and t1. Here, the function abc gets
called, which
displays four numbers. Thereafter, the function pqr gets called, which also
displays four
numbers, but with three dots.
You may wonder with trepidation as to when you will bite into the real meat.
Keep your
impatience in abeyance for a little while !
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
for ( int i = 0; i<=3;i++)
{
System.Console.Write(i + ” “);
Thread.Sleep(1);
}
}
public void pqr()
{
for ( int i = 0; i<=3;i++)
{
System.Console.Write(i+ “…”);
Thread.Sleep(1);
}
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
Thread t1 = new Thread(new ThreadStart(a.pqr));
t.Start();
t1.Start();
}
}
Output
0 0…1 1…2 2…3 3…
Now, we are at the threshold of excitement. By adding the function Sleep, which
is a static
function in the Thread class, the functions abc and pqr get called
simultaneously, although not
sequentially. This is how the concept of a thread is implemented, i.e. it
empowers your computer
to accomplish multiple jobs at the same time. This process is also termed as
multi-tasking.
Take the case of Microsoft Word. When you attempt to save a file, irrespective
of the size of
the file, Word appears to consume the same amount of time in completing this
task. To expect it
to save a 10MB file within a very short time period is a thought fraught with
absurdity and
contrary to reason. Actually, what the Word program does is that it creates a
thread and then
solicits the thread to save the file. While the thread is saving that file,
another thread waits
for the user to type something. Hence, the user is emancipated to continue
working with Word.
Thus, in effect, two tasks are being executed concurrently without the user
being cognizant of
it ! The same mechanism is employed by Excel and other similar products.
Any application under Windows runs in its own thread. Thus, if two programs are
launched, there
will be two threads running. The operating system refrains from playing
favorites and gives an
equal amount of time to each thread to execute. If there are two threads
co-existing, then, out
of every minute of processing time available, each of them will be allotted 30
seconds to
execute. If a third program is now executed, a new thread will be launched and
each of the three
threads will be allotted only 20 seconds of the processor time per minute to
execute.
If, instead of running a third program, what if the second program itself
creates a thread ? As
before, this will result in the creation of a third thread and each thread will
be allotted 20
seconds of time. The resultant effect would be that that the first program will
be allotted 20
seconds of time, whereas the second program will get 40 seconds per minute of
the processor
time. Thus, the larger number of threads that a program creates, more will be
the processor time
allotted to it. If you crave for more time and attention from your computer,
desist from
throwing a tantrum. Instead, generate greater number of threads.
The computer allots a specific amount of time to each thread and then puts it to
sleep.
Thereafter, it executes another thread. The time given to each thread to execute
its code is
designated as a Time Slice. This allocation of Time Slices occurs so swiftly
that each thread
suffers from the hallucination that it enjoys the undivided attention of the
computer.
The static function Sleep in the Thread class facilitates this process. Sleep is
like the
sandman. It puts the thread to sleep for a certain number of milliseconds, as
specified in its
parameter. In this case, it is 1 millisecond. In the earlier example, in a
single time slice,
the Thread executed all the code and the function completed execution. In this
case, the Sleep
delays it long enough for the next thread to execute, and thus, the code in the
functions get
called one after the other.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
for ( int i = 0; i<=3;i++)
{
Thread t2 = Thread.CurrentThread;
System.Console.Write(i + “.” + t2.Name + ” “);
Thread.Sleep(1);
}
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
Thread t1 = new Thread(new ThreadStart(a.abc));
t.Name=”One”;
t1.Name=”Two”;
t.Start();
t1.Start();
}
}
Output
0.One 0.Two 1.One 1.Two 2.One 2.Two 3.One 3.Two
Here, we have provided mental catharsis by simplifying the program. What we
actually have done
is given the same function name abc to our delegate. We have also used the
property Name of the
Thread class to assign a distinct name to each thread, i.e. One and Two
respectively. Under
normal circumstances, the names assigned are pre-ordained by the system. Now,
both the threads
will call the same function abc. How do we determine as to which thread is
calling the function
abc?
The Thread class can have members, which are either static or instance.
CurrentThread is a
static read-only property that returns a thread object, which represents the
thread that has
currently called the function. Here, Thread t2 will either represent t or t1,
and the property
Name will display the name of the thread executing the function.
a.cs
using System.Threading;
public class zzz : Thread
{
}
Compiler Error
a.cs(2,14): error CS0509: ‘zzz’ : cannot inherit from sealed class
‘System.Threading.Thread’
The Thread class is a sealed class, therefore we cannot derive from it. The
designers of the
Thread class at Microsoft have held very doctrinaire opinions, in that, they
believe that they
have envisaged all the possible features required in this class. Thus, they have
not provided
any facility to override or modify it. Therefore, we are constrained to use the
Thread class
exactly as provided, without introducing any code that will complement or add to
or subtract
from the code of the Thread class. For your information, the Thread class in
turn is derived
from the interface ISerializable.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
System.Console.WriteLine(“Hi”);
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
System.Console.WriteLine(t.IsAlive);
t.Start();
System.Console.WriteLine(t.IsAlive);
t.Abort();
System.Console.WriteLine(t.IsAlive);
}
}
Output
False
True
Hi
False
The Thread class has a property called IsAlive that reveals the current state of
the Thread.
When we create an object that looks like Thread, the thread is in a dead state
and hence, the
IsAlive property has a value of False. Start breathes life into the thread and
executes through
the delegate abc. The thread now comes alive, but the code in the thread
function gets executed
only after the subsequent WriteLine function, which displays the value of
IsAlive as True. When
we stop or Abort the Thread, the thread dies and the value of the property
IsAlive reverts back
to False. Thus, IsAlive can hold only one of the two values, False for dead or
True for alive.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
System.Console.WriteLine(“Hi”);
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
t.Start();
while ( !t.IsAlive )
System.Console.WriteLine(“hi1″);
}
}
Output
Hi
In today’s world, we are working on extremely fast machines. When we execute
t.Start(), the
thread is brought to life in a fraction of a second. Hence, the while loop is
not executed
because the condition becomes false immediately. Had the machine been slower,
the while loop may
have got called a few times, as it would have taken some time before the thread
could have been
brought to life. Thus, the speed of execution may vary, depending upon the speed
of the machine.
Whenever the sample code creates a thread, it always contains this while loop.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
for ( int i = 0; i<=3 ; i++)
System.Console.Write(“Hi ” + i + ” “);
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
t.Start();
System.Console.WriteLine(“Over”);
}
}
Output
Over
Hi 0 Hi 1 Hi 2 Hi 3
The executable, a.exe, runs in its own thread. So, before calling the function
Start, we already
have one thread running. On calling the function Start, two threads run
concurrently,
independent of each other. The second thread executes the function abc
independent of the first
thread. The first thread executes the last WriteLine function and then stops,
whereas the second
thread continues executing the function abc till there is no more code to call.
If, on your
machine, the first thread’s time slice gets over before executing the WriteLine
function, then
some code of the function abc may get executed before Over gets displayed in the
function Main.
It is reiterated yet again that, in case of threads, there can be no guarantee
as to when the
time slice of a thread will get over. Even the operating system is not sagacious
enough to offer
any guarantees in this regard.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
for ( int i = 0; i<=3 ; i++)
System.Console.Write(“Hi ” + i + ” “);
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
t.Start();
t.Join();
System.Console.WriteLine(“Over”);
}
}
Output
Hi 0 Hi 1 Hi 2 Hi 3 Over
The function Join is a blocking function. It makes the program linger at the
function Join till
the thread finishes execution. Any code after the Join, in this case the
WriteLine function,
will be executed only after the thread dies. A blocking function will wait until
the purpose it
is waiting for has reached fructification. Thus, if we want to wait for a thread
to complete
execution, we use a Join. Join behaves like a party host, who is always the last
to leave.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
for ( ; ; ) ;
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
t.Start();
bool b = t.Join(10000);
System.Console.WriteLine(“Over ” + b);
}
}
Output
Over False
The Join function accepts a number as a parameter, which represents the maximum
duration in
milliseconds that it should wait for the thread to complete execution. In our
example, the
function abc will never end. Thus, the thread t will go on forever. Therefore,
the Join function
waits for 10 seconds, gives up and finally returns a False.
Therefore, we are empowered to decide the duration for which we want to wait for
the thread to
complete execution. Let us not forget that our application has not yet
terminated and is still
hanging around in memory. At this stage, if you press Ctrl-Alt-Del, you will see
a list of
programs running. Now, select the End Task option for the program called ‘a’. An
alternative
approach could be to add the Abort function t.Abort() after the WriteLine
function.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
for ( ; ; ) ;
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
t.Join();
}
}
Output
Unhandled Exception: System.Threading.ThreadStateException: Thread has not been
started.
at System.Threading.Thread.Join()
at zzz.Main()
If there is no thread running, the Join function will throw the exception of
ThreadStateException.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
System.Console.WriteLine(“abc”);
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
t.Start();
Thread.Sleep(3);
t.Abort();
System.Console.WriteLine(“Over”);
Thread.Sleep(100);
t.Start();
}
}
Output
abc
Over
Unhandled Exception: System.Threading.ThreadStateException: Thread is running or
terminated.
Cannot restart.
at System.Threading.Thread.StartInternal(IPrincipal principal, StackCrawlMark &
stackMark)
at System.Threading.Thread.Start()
at zzz.Main()
At first, we start the thread t by calling the Start function. Thereafter, we
make the main
thread sleep for a little while. Then, we abort the thread and again sleep for a
little while,
in order to enable the thread to fulfill its last wishes and finally die. Now
that the thread is
dead, we try and infuse life into it by calling Start again. A thread, which has
died cannot be
resuscitated. Since we have had the audacity to attempt this, the results are
nearly
cataclysmic, resulting in the generation of an exception which has to be caught.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
System.Console.WriteLine(“abc”);
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
t.Start();
Thread.Sleep(3);
t.Abort();
System.Console.WriteLine(“Over”);
Thread.Sleep(100);
try
{
t.Start();
}
catch (ThreadStateException e)
{
System.Console.WriteLine(“In Exception”);
}
}
}
Output
abc
Over
In Exception
The exception thrown is ThreadStateException, which needs to be caught in our
code. Otherwise,
the runtime message box is displayed to the user.
The Thread class constructor can throw two types of exceptions:-
• ArgumentNullException – When it is called without a delegate in the
constructor.
• SecurityException – When the program does not have permission to create a
thread.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
System.Console.WriteLine(“abc”);
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
System.Console.WriteLine(t.ApartmentState);
t.ApartmentState = ApartmentState.STA;
t.Start();
System.Console.WriteLine(t.ApartmentState);
}
}
Output
Unknown
STA
abc
We all have our own apartments, which are normally done up as per our
preferences i.e. ‘We are
the kings of our castles’. Threads also have apartments. Any thread can be asked
to execute
either in a single-threaded apartment or in a multi-threaded apartment. These
values can be
established using the property ApartmentState, either once at the beginning, or
while the thread
is running. The various values that ApartmentState can assume are as follows:
• STA : Single Threaded Apartment.
• MTA : Multi Threaded Apartment.
• Unknown : Default value assigned when no value is set.
We, however, cannot use numbers for a string. Thus, an enum called
ApartmentState has been used.
It holds three values i.e. 0, 1 and 2 corresponding to STA, MTA and Unknown
respectively.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
System.Console.WriteLine(“abc ” + Thread.CurrentThread.IsBackground);
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
System.Console.WriteLine(“Main ” + Thread.CurrentThread.IsBackground);
t.Start();
}
}
Output
Main False
abc False
A thread executes either in the background or in the foreground. The property
IsBackground
indicates the mode in which the thread will run. An important point to be noted
here is that, a
thread which executes in the background automatically shuts down when its main
program quits.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
for ( int i = 0 ; i<=100; i++)
{
System.Console.WriteLine(“abc ” + i);
Thread.Sleep(1);
}
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
System.Console.WriteLine(“Main ” + Thread.CurrentThread.IsBackground);
t.IsBackground = true;
t.Start();
Thread.Sleep(10);
}
}
Output (t.IsBackground = true)
Main False
abc 0
Output (t.IsBackground = false)
Main False
abc 0
abc 1
abc 2
..
..
abc 100
When you change the true to false in t.IsBackground, the output shows the code
of function abc
being executed. The for loop displays values from 0 to 100.
By changing the property IsBackground of a thread to true, the thread terminates
when the main
program stops. Thus, the for loop does not have the time to display values upto
100. At times,
the program displays abc with a value of 0.
The sleeps are used to demonstrate the effect of the first thread shutting down
and the second
carrying on execution. On changing the property IsBackground to false, the
thread takes its own
sweet time to execute its code without any regard for the first thread, which
may or may not
have completed its job. So, the foreground threads behave exactly in the same
manner as the
background threads, when the property is initialized to true.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
for ( int i = 0; i<=10;i++)
{
System.Console.Write(i + ” “);
Thread.Sleep(1);
}
}
public void pqr()
{
for ( int i = 0; i<=10;i++)
{
System.Console.Write(i+ “…”);
Thread.Sleep(1);
}
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
Thread t1 = new Thread(new ThreadStart(a.pqr));
System.Console.WriteLine(t.Priority);
t.Priority = ThreadPriority.Highest;
t1.Priority = ThreadPriority.Lowest;
t.Start();
t1.Start();
}
}
Output
Normal
0 1 2 3 4 0…5 1…6 2…7 3…8 4…9 5…10 6…7…8…9…10…
We are at liberty to decide the duration of the time-slice to be allotted to our
thread
vis-a-vis other threads. This can be achieved by setting the Priority property
accordingly. By
default, the priority level is 2. Hence, we see ‘Normal’ displayed as the
output. All threads
start at this priority level. The ThreadPriority enum comprises of five levels
of priority,
which are as follows:
• 0 – Zero
• 1 – BelowNormal
• 2 – Normal
• 3 – AboveNormal
• 4 – Highest
In our program, we have changed the priority of thread t to the highest possible
and that of
thread t1 to the lowest. As a result, Thread t is accorded more time than thread
t1, and it
finishes its for loop much before thread t1 can do so.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
try
{
System.Console.WriteLine(“in abc”);
for ( ; ; ) ;
}
catch ( System.Exception e)
{
System.Console.WriteLine(“in abc Exception ” +e.ToString() );
}
finally
{
System.Console.WriteLine(“in abc Finally”);
}
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
t.Start();
Thread.Sleep(10);
t.Abort();
}
}
Output
in abc
in abc Exception System.Threading.ThreadAbortException: Thread was being
aborted.
at yyy.abc()
in abc Finally
Aborting a Thread while it is busy executing some code generates an exception.
Thus, the
function abc will be sent an exception, resulting in a call to the ‘catch’ and
‘finally’
clauses. The thread will be terminated after executing the code in the Catch and
the Finally
clause.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
try
{
System.Console.WriteLine(“in abc”);
for ( ; ; ) ;
}
catch ( System.Exception e)
{
System.Console.WriteLine(“in abc Exception ” +e.ToString() );
}
finally
{
System.Console.WriteLine(“in abc Finally”);
}
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
t.Abort();
t.Start();
}
}
Output
(no output)
Aborting a thread before it has started execution does not generate any
exceptions, since the
thread is in a dead state. Hence, no code in abc ever gets called. This
situation is akin to the
one where you don your best attire to watch cricket live at the stadium, but the
show gets
ruined due to heavy downpour, resulting in the match being abandoned without a
ball being bowled
!
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
for (int i = 1 ; i<= 10 ; i++ )
{
System.Console.Write(i + ” ” );
Thread.Sleep(1);
}
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
t.Start();
Thread.Sleep(10);
t.Suspend();
System.Console.WriteLine(“\n After Suspend”);
Thread.Sleep(10);
System.Console.WriteLine(“Before Resume”);
t.Resume();
}
}
Output
1
After Suspend
Before Resume
2 3 4 5 6 7 8 9 10
We are allowed to Suspend a thread executing its code, at any point in time. In
the above
program, we let the thread run for a little while and then Suspend it.
Thereafter, we Resume it.
Thus, the thread is like a puppet in our hands. We can decide when and which
strings to pull and
the thread does our biding. If the thread has already been suspended, then
Suspending it once
again has no effect, and thus, it does not result in an error. A single Resume
is enough to undo
a million Suspends.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
for ( int i = 0; i<=3;i++)
{
Thread t2 = Thread.CurrentThread;
System.Console.Write(i + ” ” + t2.GetHashCode() + ” “);
Thread.Sleep(1);
}
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
Thread t1 = new Thread(new ThreadStart(a.abc));
t.Start();
t1.Start();
}
}
Output
0 2 0 3 1 2 1 3 2 2 2 3 3 2 3 3
Every thread is assigned a number internally. This number is called the
HashCode. Hashing is the
process of assigning a number to an entity so that it can easily be located from
a long list.
This hash number is useful and is pressed into service when we are required to
keep track of a
large number of threads internally.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
Monitor.Enter(this);
for ( int i = 1; i<=5;i++)
{
System.Console.Write(i + ” ” + Thread.CurrentThread.GetHashCode() + ” “);
Thread.Sleep(1000);
}
Monitor.Exit(this);
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
Thread t1 = new Thread(new ThreadStart(a.abc));
t.Start();
t1.Start();
}
}
Output
1 2 2 2 3 2 4 2 5 2 1 3 2 3 3 3 4 3 5 3
Let us now clarify the abstract concept of the life of a thread with the help of
an example.
Any resource that many people desire is called a shared resource. Imagine the
chaos that would
be caused if five programs tried to simultaneously use a single network printer
for printing,
during their respectively time-slices. This would result in the printer printing
a few pages for
program 1, then a few pages for program 2, and so on. The printer will keep
printing merrily,
but the printed output will all be jumbled up.
Thus, ideally, one program should use a shared resource at a time, till its job
is complete. For
this, a method has to be implemented by means of which, unless one program
finishes executing
some specific code in a function, no other program should be allowed to execute
it. Thus, all
other programs would have to wait until the first thread finishes execution.
Let us assume that function abc contains code which sends a request to the
printer to print. The
thread t gets the first shot at running abc. The second thread t1 has to wait
until thread t
finishes execution, even if the duration of executing abc takes more time than
one time slice,
due to a large value being assigned as a parameter to the sleep function. This
is achieved by
using the static function Enter of the class Monitor.
The function Enter is given an object which refers to the class in which the
function resides.
From that point onwards, every thread has to wait at Enter. The barrier lowers
only when Exit is
called from the Monitor class. Enter acts as a fussy security guard who decides
as to which
thread is to be prohibited and which one is to be permitted to execute code. No
two threads can
ever enter the Enter function together. It is a rigid one way lane from Enter to
Exit. One car
is allowed in at one time and another car can pass through only after the first
car leaves Exit.
You can see that the names Enter and Exit have been chosen very aptly.
a.cs
using System;
using System.Threading;
public class ggg
{
};
public class yyy
{
public ggg g1;
public void abc( )
{
Monitor.Enter(g1);
for ( int i = 1; i <= 3; i++)
{
Console.Write(” Hell ” + i);
Monitor.Wait(g1);
Console.Write(” Waitabc ” + i);
Monitor.Pulse(g1);
}
Monitor.Exit(g1);
}
};
public class xxx
{
public ggg g1;
public void pqr( )
{
Monitor.Enter(g1);
for ( int i = 1; i <= 3; i++)
{
Console.WriteLine(” Everyone ” + i);
Monitor.Pulse(g1);
Monitor.Wait(g1);
Console.Write(” Waitpqr ” + i);
}
Monitor.Exit(g1);
}
};
public class zzz
{
public static void Main(String[] args)
{
ggg g = new ggg( );
yyy a = new yyy( );
a.g1 = g;
Thread t = new Thread(new ThreadStart(a.abc));
t.Start( );
xxx b = new xxx();
b.g1 = g;
Thread t1 = new Thread(new ThreadStart(b.pqr));
t1.Start( );
}
};
Output
Hell 1 Everyone 1
Waitabc 1 Hell 2 Waitpqr 1 Everyone 2
Waitabc 2 Hell 3 Waitpqr 2 Everyone 3
cs Waitabc 3 Waitpqr 3
The above example may be a protracted one, yet it is fascinating.
The world has never been able to work with each other. The only global body we
have is the UNO.
People are mostly loners.
In the entry point function Main, we create an object g, which is an instance of
a class ggg,
having no code or variable. It embodies the emptiness of space. This is a class
that is
incapable of doing any good or bad. We then create two objects that look like
yyy and xxx
respectively, and pass them as delegates, so that the threads can call functions
abc and pqr. We
also initialize the object g1, an instance of ggg, in each of them to g. This is
crucial. The
g1′s in yyy and xxx represent the same g that is created in Main.
After initializing the requisite members, we activate the two threads. As
earlier, let us assume
that the function abc gets called first.
Monitor.Enter considers taking a handle to any object, which is to be
synchronized between
multiple instances of threads, as its first parameter. This function needs an
Exit function to
release its handle. Thus, the number of Exits in the program must correspond to
the number of
Enters.
It prints “Hell”, and then due to the Wait function, it waits indefinitely, till
it finally
receives runtime notification. However, the thread accomplishes nothing
significant. Thus, we do
not see the Waitabc displayed, since it is entered after the Wait function.
The second thread starts executing the function pqr. Monitor.Enter uses the same
synchronized
object, and now it displays Everyone. The next function to be called in pqr is
Monitor.Pulse.
Pulse does a very simple thing. It wakes up any thread having the same handle,
which happens to
be waiting at a Wait. Thus, the thread in abc wakes up. However, the one in pqr
waits for
someone to Pulse it.
By using Wait and Pulse, we can easily play a game of tag. The thread t performs
some work and
waits for thread t1 to wrap up whatever work it is preoccupied with. Thread t
does not know how
long thread t1 will take to fulfill its side of the bargain. But no sweat!
Thread t waits at a
Wait for the thread t1 to Pulse it. Then, thread t1 waits at a Wait for thread t
to do some
work. When thread t concludes, it Pulses thread t1, and so on. This can go on
and may involve
some other threads too.
The threads use the handle g, which is the synchronized region. It is akin to
being an integral
part of a clandestine social group. The parameter to these Monitor functions
clubs them together
as a group. If you change the handle, the group breaks up, leaving your thread
in a lurch. The
Wait function can also be made to wait for a specified time period.
If we change the line Monitor.Wait(g1) to Monitor.Wait(g1,0), the output changes
dramatically.
A monitor is associated with an object on demand and it cannot be instantiated
at all. If the
constructor is private, we can never create an object. But, the static functions
of that object
can be used. The Monitor functions can be called from anywhere since they are
unbound. There are
no restrictions on the sources of the object to be called.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
Monitor.Enter(this);
Monitor.Enter(this);
for ( int i = 1; i<=3;i++)
{
System.Console.WriteLine(i + ” ” + Thread.CurrentThread.GetHashCode() + ” “);
}
Monitor.Exit(this);
}
}
public class zzz
{
public static void Main() {
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
Thread t1 = new Thread(new ThreadStart(a.abc));
t.Start();
t1.Start();
}
}
Output
1 2
2 2
3 2
The Exit function can be called as often as one desires. In this program, we
have two threads, t
and t1. Let us assume that thread t has the first go. At this time, it will pass
through the
first Monitor.Enter, as well as the second Monitor.Enter, without waiting at
all. However,
thread t1 will have to wait at the first Monitor.Enter, since the thread t
called the Enter
function twice, but Exited only once. Thus, the program never quits out. Had it
called Exit
twice, the thread t1 too would have woken up.
a.cs
using System.Threading;
public class yyy {
public int j=1;
public void abc()
{
Monitor.Enter(j);
for ( int i = 1; i<=3;i++)
{
System.Console.WriteLine(i + ” ” + Thread.CurrentThread.GetHashCode() + ” “);
}
Monitor.Exit(j);
}
}
public class zzz
{
public static void Main() {
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
t.Start();
}
}
Output
1 2
2 2
3 2
Unhandled Exception: System.Threading.SynchronizationLockException: Exception of
type
System.Threading.SynchronizationLockException was thrown.
at yyy.abc()
Monitor.Enter takes an object, and not a variable, as a parameter. This is the
reason why we
used this parameter or some reference object, in the earlier programs. If a
variable or a value
object is used, an exception will be thrown. Thus, value types or null objects
are not allowed
as parameters to Monitor.Enter.
a.cs
using System.Threading;
public class xxx
{
}
public class ttt
{
}
public class yyy
{
static int p = 1;
xxx x = new xxx();
ttt t = new ttt();
public void abc()
{
if ( p == 1)
{
Monitor.Enter(x);
p++;
}
else
Monitor.Enter(t);
for ( int i = 1; i<=3;i++)
{
Thread.Sleep(1000);
System.Console.WriteLine(i + ” ” + Thread.CurrentThread.GetHashCode() + ” “);
}
Monitor.Exit(x);
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
Thread t1 = new Thread(new ThreadStart(a.abc));
t.Start();
t1.Start();
}
}
Output
1 2
1 3
2 2
2 3
3 2
3 3
The Enter function must be used with some caution. The variable p initially has
a value of 1.
The first thread t sees the value of p as 1. Hence, the ‘if’ statement is true.
Thus, it crosses
Monitor.Enter with x as a parameter. It then takes a snooze at the Sleep
function, while the
second thread t1 executes function abc, where it sees the value of variable p as
2. This thread
now meets the Monitor.Enter function, with t as the parameter. The object
reference given to
this Enter is entirely at variance with the object given to the earlier one.
Thus, both the
threads execute the Enter function, which fails the very objective of the Enter
function. For
Enter to work as expected, it should be provided with the same object as a
parameter to itself.
The concept of a Lock is normally used to explain the Monitor class. One thread
gets a Lock,
while the others wait until the lock is released.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
bool b = Monitor.TryEnter(this);
System.Console.WriteLine(b);
for ( int i = 1; i<=3;i++)
{
Thread.Sleep(1000);
System.Console.WriteLine(i + ” ” + Thread.CurrentThread.GetHashCode() + ” “);
}
Monitor.Exit(this);
}
}
public class zzz
{
public static void Main() {
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
Thread t1 = new Thread(new ThreadStart(a.abc));
t.Start();
t1.Start();
}
}
Output
True
False
1 2
1 3
2 2
2 3
3 2
3 3
The TryEnter function is similar to the Enter function, but it does not block.
If the thread
enters successfully, TryEnter returns a true. This is what happens the first
time. But in the
case of the second thread t, it returns a false, even when it enters the
critical section.
a.cs
using System.Threading;
public class yyy
{
public void abc()
{
bool b = Monitor.TryEnter(this,1000);
System.Console.WriteLine(b);
for ( int i = 1; i<=3;i++)
{
Thread.Sleep(1000);
System.Console.WriteLine(i + ” ” + Thread.CurrentThread.GetHashCode() + ” “);
}
Monitor.Exit(this);
}
}
public class zzz
{
public static void Main()
{
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
Thread t1 = new Thread(new ThreadStart(a.abc));
t.Start();
t1.Start();
}
}
Output
True
False
1 2
1 3
2 2
2 3
3 2
3 3
The TryEnter function is overloaded to accept a number as a parameter, which
represents the time
duration for which the TryEnter function should block or wait. In our case, the
timeout exceeds
the time that thread t spends in the function. Hence, we get a value of False.
If we add an
extra zero to the time parameter, it will return a true instead. If the value of
the parameter
is infinite, the behaviour of the TryEnter becomes akin to that of Enter.
Thread Attributes
a.cs
using System.Threading;
public class yyy
{
[System.ThreadStaticAttribute ()]
public static int j = 1;
public static int k = 1;
public void abc()
{
for ( int i = 1; i<=3;i++)
{
Thread t2 = Thread.CurrentThread;
j++;k++;
System.Console.Write(i + “.” + t2.Name + ” j=” + j + ” k=” + k + ” “);
Thread.Sleep(1);
}
}
}
public class zzz
{
public static void Main() {
yyy a = new yyy();
Thread t = new Thread(new ThreadStart(a.abc));
Thread t1 = new Thread(new ThreadStart(a.abc));
t.Name=”One”;
t1.Name=”Two”;
t.Start();
t1.Start();
}
}
Output
1.One j=1 k=2 1.Two j=1 k=3 2.One j=2 k=4 2.Two j=2 k=5 3.One j=3 k=6 3.Two j=3
k=7
A static variable is a loner, as reiterated by us on numerous occasions. It
belongs to the class
and not to an object. Thus, there will always be a single static variable.
Let us consider a case where we have two threads t and t1 executing code in the
same function
abc, that also contains static variables j and k. The first question that
strikes us is ‘Will
each of the threads see the same static variable or a different one?’ Also, if
we assume that
both threads cater to the same static variable, when one thread increments the
variable, will
the other thread see the new value? And if not, then, is the compiler creating a
separate static
variable for each thread?
Too many questions that need answers!
We want to possess the best of both the worlds. At times, we need the threads to
work on the
same static variable, while at other times, we need the threads to access
separate copies of the
static variable. The default behaviour is that of the static variable being
shared. Thus, the
variable k is incremented by both the threads, and each thread sees the same
value. The value of
k finally reaches 7 at the end.
The variable j has an attribute ThreadStaticAttribute above it. This attribute
may have a fancy
name, but it merely creates a separate variable for each thread executing the
function. Thus, we
have two static variables j, one for each thread t and t1. Any changes made to
the value of the
variable j by t, does not get reflected in the static variable j of the thread
t1.
1.One j=2 k=2 1.Two j=3 k=3 2.Two j=4 k=4 2.One j=5 k=5 3.Two j=6 k=6 3.One j=7
k=7
The above attribute only acts on static variables. We see the above output when
we remove static
from the variables j and k. The compiler is very clear. The attribute
ThreadStaticAttribute is
only to be used on static variables, but when applied on non-static variables,
the compiler does
not generate any error or warning. It simply ignores the attribute. Thus,
instance variables are
shared across threads, whereas, local variables like i are independent of
threads. Thus, a
variable can either be shared across threads or may be independent of the
thread. No in-between
options are allowed.
Events
a.cs
using System.Threading;
public class zzz
{
public static void Main() {
ManualResetEvent a;
a = new ManualResetEvent(false) ;
System.Console.WriteLine(“Before WaitOne ” );
bool b = a.WaitOne(1000,false);
System.Console.WriteLine(“After WaitOne ” + b);
}
}
Output
Before WaitOne
After WaitOne False
We have created an object a that is an instance of a ManualResetEvent class. The
constructor is
assigned a value of false. Then, we call a function WaitOne from this class with
a number as the
first parameter, and a boolean value as the second parameter. This number, 1000,
is the number
of milliseconds for which we want the thread to wait at the function WaitOne.
The thread
therefore waits for one second before quitting out, and then returns a False.
Had we used the
enum Timeout.Infinite that has a value of -1, we could have made the function
wait forever.
Thus, we can keep a thread waiting either for a specified duration of time or
forever.
a.cs
using System.Threading;
public class zzz
{
public static void Main()
{
ManualResetEvent a;
a = new ManualResetEvent(true) ;
System.Console.WriteLine(“Before WaitOne ” );
bool b = a.WaitOne(1000,false);
System.Console.WriteLine(“After WaitOne ” + b);
}
}
Output
Before WaitOne
After WaitOne True
By changing the value in the constructor to true, the thread just refuses to
wait for anyone. It
just whizzes past, without even acknowledging the presence of the function
WaitOne. Further, the
return value of WaitOne i.e. b returns a True.
Let us delve a little deeper into this mystery. A ManualResetEvent is like a
boolean variable.
It can possess only one of the two values, true or false. When we first created
such an object,
we gave it a value of false. So, the function WaitOne waited till the Event
object turned into
True or the time value expired. Since the time duration got over while waiting,
and the value of
the Event object was not set to True, it stopped blocking and returned with a
value of false.
In the next example, the event object already has a value true and hence, there
is no wait.
Here, the function WaitOne returns true because the non-blocking in this case,
cannot be
attributed to a timeout. Thus, a ManualResetEvent has two states, true or false;
or in technical
lingo, signaled or non-signaled. A value of true implies a signaled state, while
a false would
mean that it is in the non-signaled state.
a.cs
using System.Threading;
public class zzz
{
public static void Main()
{
ManualResetEvent a;
a = new ManualResetEvent(true) ;
System.Console.WriteLine(“Before WaitOne ” );
bool b = a.WaitOne(1000,true);
System.Console.WriteLine(“After WaitOne ” + b);
b = a.WaitOne(10000,true);
System.Console.WriteLine(“After second WaitOne ” + b);
}
}
Output
Before WaitOne
After WaitOne True
After second WaitOne True
Since we do not consider ourselves to be very cerebral, we shall desist from
using fancy
terminology like signaled or non-signaled. Instead, we shall stick to the simple
true and false.
We initially created the object a to be true or signaled (Oops! There we go
again!). It then
passed the first and the second WaitOne without waiting. This occurred because
it is a Manual
ResetEvent (as the name itself suggests), and not automatic. Once it is set to
true, the event
cannot change to false automatically. This has to be accomplished manually.
a.cs
using System.Threading;
public class zzz
{
public static void Main()
{
ManualResetEvent a;
a = new ManualResetEvent(true) ;
System.Console.WriteLine(“Before WaitOne ” );
bool b = a.WaitOne(1000,true);
System.Console.WriteLine(“After WaitOne ” + b);
a.Reset();
b = a.WaitOne(10000,true);
System.Console.WriteLine(“After second WaitOne ” + b);
}
}
Output
Before WaitOne
After WaitOne True
After second WaitOne False
The Reset function can be used to change the state from true to false as it
changes the state to
false. Hence, it has to wait in queue like a commoner, since the event ignores
the earlier
WaitOne. After some time, it gets activated due to a timeout and the function
WaitOne returns a
False.
a.cs
using System.Threading;
public class zzz
{
public static void Main()
{
ManualResetEvent a;
a = new ManualResetEvent(false) ;
System.Console.WriteLine(“Before WaitOne ” );
a.Set();
bool b = a.WaitOne(1000,true);
System.Console.WriteLine(“After WaitOne ” + b);
b = a.WaitOne(10000,true);
System.Console.WriteLine(“After second WaitOne ” + b);
}
}
Output
Before WaitOne
After WaitOne True
After second WaitOne True
Now, despite the fact that that the Event is originally false or non-signaled,
the Set function
sets it to true or the signaled state. At this stage, no power on earth is
equipped to stop the
event as it surges past the WaitOnes, treating them with disdain. It is not in
the least
concerned about stopping-by to pay homage to them. Such arrogance !
The class ManualResetEvent is a sealed class, like most other classes belonging
to the Thread
family. The developers at Microsoft were of the belief that no one would be able
to add any more
value to the class, and thus forbade any changes. This class is also derived
from another class
called WaitHandle, which in fact, embodies functions like WaitOne etc. The
constructor has to be
passed a parameter that affirms the initial state of the Event object. No
default state is
available. The Set and Reset methods return a boolean value, which indicates
whether the change
has taken place successfully or otherwise.
WaitHandle is a class from which ManualResetEvent is derived. A class that
offers functions
which are polite, is called a synchronization class. People who are polite, wait
for others to
finish doing their work, and it is only then that they proceed with their own
work. Any class
that waits for another is called a synchronization class. All synchronization
objects derive
from WaitHandle, and so does ManualResetEvent.
We are cautioned not to use this class directly, since it is the other classes
that use this
class. The documentation very clearly specifies that the CLR i.e. the Common
Language Runtime or
code written by Microsoft, calls the constructor of WaitHandle. Therefore, do
not use this class
directly. You are being forewarned!
a.cs
using System.Threading;
public class zzz
{
public static void Main()
{
ManualResetEvent a,b;
a = new ManualResetEvent(false) ;
b = new ManualResetEvent(true) ;
WaitHandle [] w = new WaitHandle[2];
w[0] = a; w[1] = b;
System.Console.WriteLine(“Before WaitAll ” );
bool c = WaitHandle.WaitAll(w,10000,true);
System.Console.WriteLine(“After WaitAll ” + c);
}
}
Output
Before WaitAll
After WaitAll False
We can complicate life as much as we like. Similarly, we can complicate the
WaitOne, by
introducing its elder brother, WaitAll. This function will wait for all the
event objects to
become true or signaled, or it will stay there till the timeout occurs. In this
manner, life can
be made as complicated as we like. Until all the events reach the signaled
state, the WaitAll
remains extremely stubborn and does not budge from its vantage position. It does
not take
cognizance if a few of the events are signaled. All the events should be
signaled. This could be
a number approaching infinity.
a.cs
using System.Threading;
public class zzz
{
public static void Main()
{
ManualResetEvent a,b;
a = new ManualResetEvent(false) ;
b = new ManualResetEvent(true) ;
WaitHandle [] w = new WaitHandle[2];
w[0] = a; w[1] = b;
System.Console.WriteLine(“Before WaitAll ” );
bool c = WaitHandle.WaitAll(w);
System.Console.WriteLine(“After WaitAll ” + c);
}
}
Output
Before WaitAll
The number of functions in the class WaitHandle may be numerous, but most of
them are only
helper functions, which make life tranquil. The WaitAll function, if called with
a single
parameter, assumes that the second parameter is Timeout.Infinite or -1. These
overloads can be
ignored safely. It is the same for the WaitOne method.
a.cs
using System.Threading;
public class zzz
{
public static void Main()
{
ManualResetEvent a,b;
a = new ManualResetEvent(false) ;
b = new ManualResetEvent(true) ;
WaitHandle [] w = new WaitHandle[2];
w[0] = a; w[1] = a;
System.Console.WriteLine(“Before WaitAll ” );
bool c = WaitHandle.WaitAll(w);
System.Console.WriteLine(“After WaitAll ” + c);
}
}
Output
Before WaitAll
Unhandled Exception: System.DuplicateWaitObjectException: Exception of type
System.DuplicateWaitObjectException was thrown.
at System.Threading.WaitHandle.WaitMultiple(WaitHandle[] waitHandles, Int32
millisecondsTimeout, Boolean exitContext, Boolean WaitAll)
at System.Threading.WaitHandle.WaitAll(WaitHandle[] waitHandles, Int32
millisecondsTimeout,
Boolean exitContext)
at System.Threading.WaitHandle.WaitAll(WaitHandle[] waitHandles)
at zzz.Main()
The CLR (Common Language Runtime, if you have forgotten) never sleeps, but the
compiler can be
caught dozing at times. C# hates duplicates, and thus, we cannot pass the same
handle to the
WaitHandle array. If you attempt to do so, a runtime exception will be thrown
even when there
was no reason to throw an exception. The runtime could have simply ignored the
duplicate, but it
chose not to.
a.cs
using System.Threading;
public class zzz
{
public static void Main()
{
ManualResetEvent a,b;
a = new ManualResetEvent(false) ;
b = new ManualResetEvent(true) ;
WaitHandle [] w = new WaitHandle[2];
w[0] = b; w[1] = a;
System.Console.WriteLine(“Before WaitAll ” );
int c = WaitHandle.WaitAny(w);
System.Console.WriteLine(“After WaitAll ” + c);
}
}
Output
Before WaitAll
After WaitAll 0
Change line containing w[0] = b; w[1] = a; to w[0] = a; w[1] = b; and the output
changes to the
following:-
Output
Before WaitAll
After WaitAll 1
The WaitAny function waits till any of the events change to signaled or true.
When this occurs,
it returns the array index of the event which caused the exit. In this case,
object b is in the
signaled state, and since it is the first member of the array, the return value
is 0. On
interchanging the object to b, which is the second array member, the return
value becomes 1. If
both events are in the signaled state, then the lower array index is returned.
If a timeout
occurs, the value returned is 258.
AutoResetEvent Class
a.cs
using System.Threading;
public class zzz
{
public static void Main()
{
AutoResetEvent a;
a = new AutoResetEvent(false) ;
System.Console.WriteLine(“Before WaitAll ” );
bool c = a.WaitOne(1000,true);
System.Console.WriteLine(“After WaitOne ” + c);
}
}
Output
Before WaitAll
After WaitOne False
Let us now move up the value chain. The AutoResetEvent class works in the same
way like a
ManualResetEvent class in the above example. It simply waits for the timeout to
take place or
the Event to be signaled.
a.cs
using System.Threading;
public class zzz
{
public static void Main() {
AutoResetEvent a;
a = new AutoResetEvent(true) ;
System.Console.WriteLine(“Before WaitAll ” );
bool c = a.WaitOne(1000,true);
System.Console.WriteLine(“After WaitOne ” + c);
c = a.WaitOne(10000,true);
System.Console.WriteLine(“After WaitOne1 ” + c);
}
}
Output
Before WaitAll
After WaitOne True
After WaitOne1 False
Ah! Again the wait. The AutoResetEvent object safely crosses the first WaitOne.
But there is one
major variation now in that, the state of the object changes from true to false
or signaled to
non-signaled. Thus, it is obliged to pay homage for some time at the second
Wait. The name here
is Auto and not Manual. Just as we would change color if we saw a wild boar
charging at us, the
AutoResetEvent changes state at the Wait. Everything else remains identical.
Mutexes
a.cs
using System.Threading;
public class zzz
{
static Mutex m;
public static void Main()
{
m = new Mutex(true,”vijay”);
zzz a=new zzz( );
Thread t= new Thread(new ThreadStart(a.abc));
t.Start();
}
public void abc( )
{
System.Console.WriteLine(“abc start”);
m.WaitOne( );
System.Console.WriteLine(“abc end”);
}
}
Output
abc start
If the boolean value of true in the Mutex constructor is changed to false, the
output will
change to the following:-
Output
abc start
abc end
A mutex is a synchronization object derived from the class WaitHandle. A Mutex
is, in some
sense, similar to the other Event classes.
We commence by creating a mutex and assigning it a boolean value of either true
or false. In
this context, these values have a slightly different connotation. They decide
whether the
creating thread owns the object or not. True allows the thread to own it,
whereas False does
not. Thus, with a mutex, we need to become very materialistic and start taking
ownership of
things. The current thread owns the mutex while the other threads are inclined
to own it.
True or false, signaled or non-signaled, own it or not, are all the same
concepts. The main
thread already owns the mutex, which is named as vijay. The name is optional and
is used mostly
to refer to the mutex. Thereafter, we create a thread which calls the function
abc. After
displaying a message, the function WaitOne is called. This function will now
stake a claim for
ownership of the mutex, but since the main thread owns it, thread t cannot
proceed beyond this
function until the original thread gives up ownership. If we change the True in
the constructor
to False, then the ownership is not claimed. Thus, the thread t will not have to
wait at the
WaitOne as there is no owner.
a.cs
using System.Threading;
public class zzz
{
static Mutex m;
public static void Main()
{
m = new Mutex(true,”vijay”);
zzz a=new zzz( );
Thread t= new Thread(new ThreadStart(a.abc));
t.Start();
}
public void abc( )
{
System.Console.WriteLine(“abc start”);
m.ReleaseMutex();
m.WaitOne( );
System.Console.WriteLine(“abc end”);
}
}
Output
abc start
Unhandled Exception: System.ApplicationException: Attempt to release mutex not
owned by caller.
at System.Threading.Mutex.ReleaseMutexNative(IntPtr handle)
at System.Threading.Mutex.ReleaseMutex()
at zzz.abc()
The main thread becomes the owner of the mutex as the boolean value supplied in
the mutex
constructor is True. Then, in the function abc, we try to release the ownership
of the mutex by
using the function ReleaseMutex. The thread t does not own the mutex. Hence, we
get a runtime
error as only the owner i.e. the main thread, can release the ownership of a
mutex and pass it
on to another thread.
a.cs
using System.Threading;
public class zzz
{
static Mutex m;
public static void Main() {
m = new Mutex(true,”vijay”);
zzz a=new zzz( );
Thread t= new Thread(new ThreadStart(a.abc));
t.Start();
System.Console.WriteLine(“Before Thread Sleep”);
Thread.Sleep(10000);
System.Console.WriteLine(“After Thread Sleep”);
m.ReleaseMutex();
System.Console.WriteLine(“All over”);
}
public void abc( )
{
System.Console.WriteLine(“abc start”);
m.WaitOne( );
System.Console.WriteLine(“abc end”);
}
}
Output
Before Thread Sleep
abc start
After Thread Sleep
abc end
All over
Now alls well that ends well. The main thread sleeps for a little while and then
calls
ReleaseMutex. This awakens the thread t, and thus, the function abc now quits
out. Remember that
you cannot give away what you do not own.
a.cs
using System.Threading;
public class zzz
{
static Mutex m;
public static void Main()
{
m = new Mutex(true,”vijay”);
zzz a=new zzz( );
Thread t= new Thread(new ThreadStart(a.abc));
t.Start();
System.Console.WriteLine(“Before Thread Sleep”);
Thread.Sleep(1000);
System.Console.WriteLine(“After Thread Sleep”);
m.ReleaseMutex();
System.Console.WriteLine(“Before WaitOne”);
m.WaitOne();
System.Console.WriteLine(“All over”);
}
public void abc( )
{
System.Console.WriteLine(“abc start”);
m.WaitOne();
System.Console.WriteLine(“abc before sleep”);
Thread.Sleep(100000);
System.Console.WriteLine(“abc end”);
}
}
Output
Before Thread Sleep
abc start
After Thread Sleep
abc before sleep
Before WaitOne
There is a long sleep in the function abc. The mutex is still owned by the
thread t, while the
main thread is trying to regain its ownership. It can only gain ownership if the
thread t
releases the mutex or dies. Thus, until the thread t dies, we will not see All
Over displayed at
all. We could have had another sleep in abc and we could have released the mutex
before that
sleep. The choice is ours. Thus, a mutex is fully concerned with ownership
issues. Either you
own a mutex or you do not.
a.cs
using System.Threading;
public class zzz
{
static Mutex m;
static Mutex n;
public static void Main()
{
m = new Mutex(true,”vijay”);
n = new Mutex(true);
zzz a=new zzz( );
Thread t= new Thread(new ThreadStart(a.abc));
t.Start();
System.Console.WriteLine(“All over”);
}
public void abc( )
{
System.Console.WriteLine(“abc start”);
Mutex [] g = new Mutex[2];
g[0] = m;g[1] = n;
Mutex.WaitAll(g);
System.Console.WriteLine(“abc end”);
}
}
Output
All over
abc start
The program will hang. This is because we have two mutexses that have to be
waited upon. Both m
and n fill up the array g, and since both are true, the function abc will wait
forever. Same
rules of event classes apply to a mutex object.
a.cs
using System.Threading;
public class zzz
{
static Mutex m;
public static void Main()
{
m = new Mutex(true,”vijay”);
zzz a=new zzz( );
Thread t= new Thread(new ThreadStart(a.abc));
t.Start();
System.Console.WriteLine(“Before Thread Sleep”);
Thread.Sleep(1000);
System.Console.WriteLine(“After Thread Sleep”);
m.ReleaseMutex();
System.Console.WriteLine(“Before WaitOne”);
m.WaitOne();
System.Console.WriteLine(“All over”);
}
public void abc( )
{
System.Console.WriteLine(“abc start”);
m.WaitOne();
System.Console.WriteLine(“abc after Wait”);
m.WaitOne();
System.Console.WriteLine(“abc after second Wait”);
Thread.Sleep(10000);
System.Console.WriteLine(“End”);
}
}
Output
Before Thread Sleep
abc start
After Thread Sleep
abc after Wait
abc after second Wait
Before WaitOne
End
All over
The same rules about mutex ownership as mentioned earlier are discussed here.
There may be two
WaitOne functions, but the first WaitOne that gets the ownership, retains it.
Thus, there was no
wait between the first and the second wait. The concept of a mutex is extremely
simple. A
particular thread owns it. It can cross a wait without waiting and only when it
releases the
mutex can another thread claim and possess its ownership. Other threads cannot
own it and they
will have to wait at a wait. Dual ownership is not allowed.
a.cs
using System.Threading;
public class zzz
{
static Mutex m;
public static void Main()
{
m = new Mutex(false,”vijay”);
zzz a=new zzz( );
System.Console.WriteLine(“abc start”);
m.WaitOne( );
System.Console.WriteLine(“abc end”);
}
}
Output
abc start
abc end
A Mutex only works with multiple threads. So, using a mutex with a single thread
is futile,
because a mutex is concerned with determining as to which thread owns it.
Therefore, there must
be at least two threads.
Timers
a.cs
using System.Threading;
public class zzz
{
public static void Main() {
zzz a = new zzz();
a.pqr();
}
public void pqr()
{
Timer t;
yyy b = new yyy();
t = new Timer(new TimerCallback(b.abc),this,100,0);
Thread.Sleep(1000);
}
}
public class yyy
{
public void abc(object o)
{
System.Console.WriteLine(“hell”);
}
}
Output
hell
Timers are time-related. We have created a timer object and have passed it 4
values in its
constructor. The first parameter is the delegate object which represents the
function that
should be called after a certain interval elapses. The second parameter is the
object to be
passed to the timer function. The third and fourth parameters are numbers
representing time i.e.
time and period. If the thread is not asked to sleep, the main thread will quit
out and there
will be no thread running. The entire program will halt and all the unfired
timers will die.
Thus, if we remove the last Sleep, the word ‘hell’ does not get displayed. The
timer delegate
has to receive an object as a parameter since it is part of the delegate
signature. The timer
also has to wait for a specified time which is stated in the constructor, before
it can call the
function. This wait is accomplished using a thread in a thread pool. Needless to
say, the Timer
class is a sealed class.
a.cs
using System.Threading;
public class zzz
{
public static void Main()
{
zzz a = new zzz();
a.pqr();
}
public void pqr()
{
Timer t;
yyy b = new yyy();
t = new Timer(new TimerCallback(b.abc),this,100,0);
t.Dispose();
Thread.Sleep(1000);
}
}
public class yyy
{
public void abc(object o)
{
System.Console.WriteLine(“hell”);
}
}
Output
(none)
If we ever desire to cancel the timer for whatever reasons, we can call the
Dispose method to
kill the timer. The timer dies and the function to be called does not get called
at all.
a.cs
using System.Threading;
public class zzz
{
public static void Main()
{
zzz a = new zzz();
a.pqr();
}
public void pqr()
{
Timer t;
ManualResetEvent e = new ManualResetEvent(false);
yyy b = new yyy();
t = new Timer(new TimerCallback(b.abc),this,10000,300);
t.Dispose(e);
e.WaitOne();
System.Console.WriteLine(“All Over”);
}
}
public class yyy
{
public void abc(object o)
{
System.Console.WriteLine(“hell”);
}
}
Output
All Over
The Dispose function can also take a parameter which is an event that gets
signaled when the
timer dies. Thus, the program that would have waited indefinitely now quits out
because,
removing the timer from the timer queue instantly signals an event.
a.cs
using System.Threading;
public class zzz
{
public static void Main()
{
zzz a = new zzz();
a.pqr();
}
public void pqr()
{
Timer t;
yyy b = new yyy();
t = new Timer(new TimerCallback(b.abc),this,100,1);
Thread.Sleep(500);
}
}
public class yyy
{
public void abc(object o)
{
System.Console.WriteLine(“hell”);
}
}
Output
hell
hell
hell
hell
and so on
The third parameter to the timer constructor is the duration in milliseconds
after which the
timer should be fired. The second number can be either a 0 or any other number.
Zero means the
timer fires once and any other number signifies it as a periodic timer. This
periodic timer
keeps executing the function abc every 100 milliseconds, until told to do
otherwise.
a.cs
using System.Threading;
public class zzz
{
public static void Main()
{
zzz a = new zzz();
a.pqr();
}
public void pqr()
{
Timer t;
yyy b = new yyy();
t = new Timer(new TimerCallback(b.abc),this,100,1);
Thread.Sleep(115);
t.Change(100,0);
Thread.Sleep(1300);
}
}
public class yyy
{
public void abc(object o)
{
System.Console.WriteLine(“hell”);
}
}
Output
hell
hell
hell
The Change function is used to stop a periodic timer. Thus, the timer can be
made to run for
some time in a periodic manner and thereafter, the Change can be used to stop it
from executing
repeatedly.
a.cs
using System.Threading;
public class zzz
{
public static void Main()
{
zzz a = new zzz();
a.pqr();
}
public void pqr()
{
Timer t;
yyy b = new yyy();
t = new Timer(new TimerCallback(b.abc),this,100000,1);
Thread.Sleep(115);
t.Change(100,0);
Thread.Sleep(1300);
}
}
public class yyy
{
public void abc(object o)
{
System.Console.WriteLine(“hell”);
}
}
Output
hell
The timer initially is set to go off after a very long time. We let the main
thread sleep for a
little while but then change the due time of the timer to a smaller value. Thus,
we can use the
Change function to alter the initial settings of the timer.
a.cs
using System.Threading;
public class zzz
{
public static void Main()
{
zzz a = new zzz();
a.pqr();
}
public void pqr()
{
Timer t;
yyy b = new yyy();
t = new Timer(new TimerCallback(b.abc),this,100,300);
Thread.Sleep(1000);
}
}
public class yyy
{
public void abc(object o)
{
System.Console.WriteLine(“hell”);
}
}
Output
hell
hell
hell
Our dictum has always been to understand things one step at a time. The last
parameter to the
timer constructor can either be zero or any other value. If the value is 100, it
means that
every hundred seconds the timer will go off, until it is stopped by a Change or
a Dispose.
Earlier, we had used the number 1 to signify that the timer should be called
every 1 second. You
are free to decide these timer values depending upon what you intend to use the
timer for. The
values cannot be negative and cannot exceed 4294967294 either. If you do so, an
exception will
occur. Exceptions, lest you have forgotten, are generated at runtime only.
Thread Pool
a.cs
using System;
using System.Threading;
public class yyy {
public ManualResetEvent a = new ManualResetEvent(false);
public void abc(Object s)
{
a.WaitOne(10,true);
Console.WriteLine(“{0} “, Thread.CurrentThread.GetHashCode());
}
};
public class zzz {
public static void Main() {
ManualResetEvent b = new ManualResetEvent(false);
yyy y = new yyy();
for (int i=1;i<=5;i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(y.abc),1);
}
b.WaitOne(10000,true);
}
}
Output
2
2
2
2
2
The rationale of a thread pool is that the decision makers at Microsoft did not
want us to spend
time taking care of threads like mothers take care of babies.
We will first explain the above program and then delve into the nitty-gritty of
thread pools.
Since we are lazy, we use a for statement to avoid repeating the same code over
and over again.
The function QueueUserWorkItem will be called at least five times because it is
placed in this
for loop.
QueueUserWorkItem function requires one, or at the most, two parameters. The
first parameter,
which is the name of a function, is most crucial. The name is never ever
assigned directly, but
indirectly, using a WaitCallback object. The function to be called is y.abc.
Hence, it is passed
a parameter to WaitCallback. The second parameter to the function
QueueUserWorkItem is the
number 1. As a result, the function abc has to accept a parameter of type object
which will
contain the value 1. A parameter of type object does not have any useful code
and by itself is
totally useless.
Every entity in C# is finally derived from the object class. Thus, every entity
is of an object
type. Whenever we are not aware of the parameter type to be received in a
function, or in cases
where we want to receive multiple data types, the data type of object is used.
Object is like a
rubber man; it can fit anywhere and everywhere. By giving the function name abc
to the function
QueueUserWorkItem, abc will be called five times by a thread. The function waits
for a small
amount of time at an event and then uses the function GetHashCode to print the
name/id of the
thread. This function will print the thread id that is calling abc.
From our output, we can conclude that only two threads execute the function abc.
Commenting the
Wait statement will display the same id for the thread. This means that no new
thread executes
the function. Thus, instead of two threads, only a single thread is used.
Efficiency, thy name
is Thread Pool.
We are thus posting work items to a common place or thread pool. Then, a thread
is allocated to
execute the work item or function. The first available thread from the thread
pool executes the
function. We are only using one thread pool per program.
There is a lot of overhead in managing threads. A lot of time is spent figuring
out which thread
is sleeping, i.e. waiting for an event to occur. Most of the time, threads are
in this inert
state. At times, threads wake up to check/poll for a change that is expected to
occur, such as a
key press, or to update some counter or status information and fall asleep
again. Threads are
like bears, they love sleeping.
Thread Pooling is a means of managing threads more efficiently. This is done by
providing your
application with a pool of threads that are managed by the system. All threads
that do work are
called worker threads. There is a thread that monitors the status of threads
waiting at any of
the wait functions. When the wait is over, the callback function will be
executed.
The next program demonstrates how a function can be called after a specified
time duration has
elapsed. There is no way to cancel a function from being executed once it has
gone into the
thread pool. A prayer will also not help. Timers and Waits use the thread pool
without our
knowledge.
The thread pool is created when we call the function QueueUserWorkItem for the
first time or
when we use a timer function. The number of threads that form part of a thread
pool is limited
by the amount of memory available. By today’s standards, this means that more
threads can be
made available than we would ever need. These threads receive the default stack
size and run at
the default priority. No special priority is accorded. For the trivia fans, a
thread can handle
up to 63 wait operations. No concept is perfect. Thus, one significant
disadvantage of a thread
pool is that it involves a long calculation.
a.cs
using System;
using System.Threading;
public class yyy
{
public void abc(object s, bool b)
{
Console.WriteLine(“{0} {1}”, Thread.CurrentThread.GetHashCode(),b);
}
};
public class zzz
{
public static void Main()
{
ManualResetEvent a = new ManualResetEvent(false);
ManualResetEvent b = new ManualResetEvent(false);
yyy y = new yyy();
ThreadPool.RegisterWaitForSingleObject(a,new
WaitOrTimerCallback(y.abc),1,1000,true);
b.WaitOne(10000,true);
}
}
Output
2 True
As mentioned earlier, we would like our function to be called after a certain
wait. To
accomplish this, we use a function with a very large name called
RegisterWaitForSingleObject.
The first parameter is the handle of an event. This has been set to false or non
signaled. The
second parameter is a delegate of type WaitOrTimerCallback which is similar to a
WaitCallback.
The difference is that the signature of the function given to this function,
which is abc, has
to have two parameters, viz. an object and a boolean variable. The third
parameter is the object
that is to be passed to the function. The second last parameter is a timer that
decides the
duration after which the function should be called. Finally, the last parameter
decides whether
the function is to be executed only once or every time the timer expires.
As our event object is set to false, the function gets called after 1000
milliseconds. When the
timer expires, the function abc obtains a value of true.
By merely changing one line in the above example, the event can be changed to a
signaled state.
ManualResetEvent a = new ManualResetEvent(true);
Output
2 False
The function abc gets called immediately, unlike in the earlier program. Also,
there is no wait
as the event is true or signaled. The function abc also knows why it has been
called since the
bool parameter has a value of false, which was true in the earlier case. Thus,
the function abc
can look at the value of its second parameter, which indicates whether the timer
has elapsed or
the event has been signaled.
a.cs
using System;
using System.Threading;
public class yyy
{
public void Beta(object s, bool b)
{
Console.WriteLine(“{0} {1}”, Thread.CurrentThread.GetHashCode(),b);
}
};
public class zzz
{
public static void Main()
{
ManualResetEvent a = new ManualResetEvent(false);
ManualResetEvent b = new ManualResetEvent(false);
yyy y = new yyy();
ThreadPool.RegisterWaitForSingleObject(a,new
WaitOrTimerCallback(y.Beta),1,10000,true);
a.Set();
b.WaitOne(10000,true);
}
}
Output
2 False
In this program, initially we set the event to false and then, using the Set
function, we set it
to true immediately. Thus, the function gets called instantly. You would
remember that the
function gets called whenever the event is in a signaled state or the timer
expires. This
function first checks whether the wait object is signaled or not. If it is
signaled, it executes
the function. If it is not, it waits for the timer to elapse or the wait object
to come into the
signaled state, whichever occurs first.
Interlocked
a.cs
using System;
using System.Threading;
public class yyy
{
public ManualResetEvent a = new ManualResetEvent(false);
int i = 10;
public void abc(Object s)
{
Interlocked.Increment(ref i);
Console.WriteLine(“{0} {1}”, Thread.CurrentThread.GetHashCode(),i);
}
};
public class zzz
{
public static void Main() {
ManualResetEvent b = new ManualResetEvent(false);
yyy y = new yyy();
for (int i=1;i<=5;i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(y.abc),1);
}
b.WaitOne(10000,true);
}
}
Output
2 11
2 12
2 13
2 14
2 15
Threads can be dangerous for the unwary. The function abc gets called five times
by the same or
a different thread. Consider a case where one thread may be incrementing the
line i = i + 1. To
do so, it will start by asking what is the value of i. Let us suppose that it is
10. It then
increments the value by 1 to make it 11. It may so happen that just as it is
about to read its
value, its time slice may get over and another thread may increment the variable
i by 1 to make
its value 12.
When the first thread receives its next time slice and checks the value of i, it
will see the
value as 12 and not 11. Thus, the variable would have been incremented twice
instead of once.
The Interlocked class has a sole purpose in life, i.e. to ensure that the
variables are not
incremented more than once. In a sense, it synchronizes access to a variable
that is being
shared by a number of threads. The operation will either be done in a single
time slice or not
done at all. It will not be carried over into another time slice ever. These
operations, which
are guaranteed to be finished in a single time slice, are called atomic
operations.
a.cs
using System;
using System.Threading;
public class yyy
{
public ManualResetEvent a = new ManualResetEvent(false);
public int i = 10;
public void abc(Object s)
{
i++;
Interlocked.CompareExchange(ref i,100,12);
}
};
public class zzz
{
public static void Main()
{
ManualResetEvent b = new ManualResetEvent(false);
yyy y = new yyy();
for (int i=1;i<=5;i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(y.abc),1);
}
b.WaitOne(1000,true);
Console.WriteLine(y.i);
}
}
Output
103
The function abc gets called five times. If we intend to carry out comparisons,
we would face
the above dilemma. Thus, we use a simple function called CompareExchange. This
functions checks
the value of the first parameter that is passed as a reference, with the third
parameter, which
in our case is 12. If it matches the value, the first parameter i.e. i is
assigned the value of
the second parameter i.e. 100. All this is done in a single time slice. Thus,
whenever variable
i becomes 12, its value shoots up to 100, and as a result, the final answer
becomes 103.
Like the Increment, there is a Decrement function also that reduces the value of
a variable by
one. As an aside, a ref parameter cannot be replaced by an out parameter. In the
same vein, the
Exchange function changes the value of two variables passed to it as ref
parameters. This is
done in an atomic manner.
Internet Classes { The C# Language }
Internet Classes
Before weaving our way into the Internet, let us investigate a few more fundamentals about the C# programming language, in order to acquire a superior
appreciation of internet programming.
No computer in the world is equipped to recognize the letters of the alphabet on its own. Therefore, an internal representation has to be used. One such
representation is called ASCII, which allots a number falling between 0 to 255, to each of the letters. Here, each letter is represented by a byte, which is an
8-bit number. For instance, the letter ‘A’ is represented by the number 65, while the letter ‘a’ is represented by the number 97.
However, languages such as Japanese and Chinese are remarkably visual, and require a larger range of numbers to represent them in totality. In order to
accommodate the requirement of these languages, the computer industry came out with a standard called UNICODE, which can represent every known language in the
world in its entirety. But, to accomplish this, this standard imposes the condition that every character in the language should be represented by a 16-bit
number, and not by an 8-bit number.
a.cs
public class zzz
{
public static void Main()
{
byte i = 65;
System.Console.WriteLine(i);
System.Console.WriteLine((char)i);
i = (byte)’A';
System.Console.WriteLine(i);
}
}
Output
65
A
65
The C# language takes cognizance of the fact that the byte data type represents a number ranging from 0 to 255. Thus, by default, the WriteLine function
displays the number that the byte represents. If we typecast the byte i to a char, the WriteLine function displays its ASCII equivalent instead. The C#
language had to indoctrinate a new data type called ‘char’, which could represent a UNICODE character or a number ranging from 0 to 65535, since a byte
is too diminutive to be able to accommodate this range of values. A literal or char ‘A’ cannot be initialized to a byte, since we cannot equate a smaller 8-bit
data type to a larger 16-bit data type. Therefore, a cast operator has to be used.
a.cs
public class zzz
{
public static void Main()
{
char i = ‘A’;
System.Console.WriteLine(i);
}
}
Output
A
A variable of char data type cannot be equated to a number. It can only be initialized to a literal. The WriteLine function prefers to receive the ‘char’
data type, since it can display the value as a letter of the alphabet. Thus, the ‘char’ data type is used to store 16 bit UNICODE characters, while the ‘byte’
data type is employed to store small numbers. The ‘string’ data type is used for a sequence of UNICODE characters.
a.cs
public class zzz
{
public static void Main()
{
byte [] i = new byte[5];
i[0] = 65; i[1] = 66;
System.Console.WriteLine(i);
char [] j = new char[5];
j[0] = ‘A’; j[1] = ‘B’;
System.Console.WriteLine(j);
}
}
Output
System.Byte[]
AB
Dealing with an array of bytes is not as straightforward and uncomplicated as dealing with an array of chars. In case of an array of bytes, the WriteLine
function merely displays the data type of the array, whereas, in the case of an array of chars, it displays the entire array. Thus, we need a technique, by
means of which, we are able to display the bytes present in the form of an array of bytes, using the WriteLine function.
a.cs
public class zzz
{
public static void Main()
{
byte [] i = new byte[5];
i[0] = 65; i[1] = 66; i[2] = 67;i[3] = 68;
string s = System.Text.Encoding.ASCII.GetString(i, 1, 2);
System.Console.WriteLine(s);
}
}
Output
BC
The namespace System.Text.Encoding has a class called ASCII, which contains a static function GetString. This function accepts three parameters:
• The array of bytes, which is to be converted into a string.
• The starting point in the array ( in our case, 1) refers to the second element in the Array, which is the letter ‘B’.
• The number of characters or the length of the string. We have specified it as 2. Hence, only 2 characters are seen.
The function returns a string, which is displayed by employing the WriteLine function.
This methodology thereby, assists in converting a certain number of bytes from a byte array into a string, which can then be effortlessly printed by the
WriteLine function.
The rationale for introducing this function in this chapter is due to the existence of a large number of functions that return data as an array of bytes.
The approach demonstrated above may be employed, to display such data in all such cases.
a.cs
public class zzz
{
public static void Main()
{
string s1 = “vmukhi”;
byte[] i;
char [] j = s1.ToCharArray();
i = System.Text.Encoding.ASCII.GetBytes(j);
string s = System.Text.Encoding.ASCII.GetString(i, 0, 5);
System.Console.WriteLine(s);
}
}
Output
vmukh
We now need to resolve another quandary. In the internet-related classes, functions read and write data in the form of an array of bytes.
We are comfortable dealing with strings, as they are the most natural data type available for representing data. However, this data type has to be converted
into an array of bytes, if it has to be transmitted. But, there is no direct mechanism of attaining this conversion. The only possibility is to write our own
function.
The string is first converted into an array of chars, using ToCharArray method in the string class. As of now, there exists no magic formula in the string
class, which can directly provide an array of bytes. So, we are compelled to utilize the GetBytes function from the ASCII class, which accepts a character
array and converts it into an array of bytes. This is an additional step that needs to be executed in the conversion process.
The last two lines of the above program merely verify whether all data has been supplied correctly or otherwise.
a.cs
public class zzz
{
public static void Main()
{
byte [] a;
a= abc(“ABC”);
string s = System.Text.Encoding.ASCII.GetString(a, 0, 3);
System.Console.WriteLine(s);
}
public static byte [] abc( string a)
{
byte [] b = new byte[a.Length];
System.Console.WriteLine(a.Length);
for ( int i = 0 ; i < a.Length ; i++)
{
System.Console.WriteLine(a[i]);
b[i] = (byte)a[i];
}
return b;
}
}
Output
3
A
B
C
ABC
In this program, we write our own function that accepts a string as a parameter, and returns an array of bytes. This function eliminates the intermediate step of
converting the string into an array of chars, prior to its final conversion into an array of bytes.
The Length member in the string returns the length of the string, and also determines the size of the byte array, which is to be created. In the ‘for’
loop, every individual character can be accessed, one at a time, by using the indexer property of the string class. Concurrently, the byte array is also
filled up. The need for a cast operator has been explained earlier. Finally, at the end of the function, we return this freshly filled up array, and thereafter,
display the byte array. The array is displayed merely to corroborate that all actions have been executed as planned.
File Handling
a.cs
using System;
using System.IO;
class zzz
{
public static void Main()
{
FileStream f = new FileStream(“c:\\z.txt”, FileMode.Open, FileAccess.Read);
byte [] a;
a = new byte[512];
int b = f.Read(a, 0, 512);
Console.WriteLine(b);
string s = System.Text.Encoding.ASCII.GetString(a, 0, b);
Console.Write(s);
}
}
z.txt
vijay
mukhi
Output
12
vijay
mukhi
We start by creating an object f, which is an instance of class FileStream. The function is assigned the name of the file that we want to work with, along with
the mode in which it is to be opened. The two parameters, FileMode.Open and FileAccess.Read, are enums, which open a file for reading.
The Read function in the file stream reads the file. The function has 3 parameters:
• The first parameter is a byte array. The data from the file is to be read into this array.
• The second parameter is the offset position.
• The third parameter is the maximum number of bytes to be read.
Thus, the Read function shall attempt to read upto 512 bytes from the file, until it reaches the end of the file. Since the file contains only the words ‘vijay
mukhi’, the output is 12, which represents the number of bytes read. This is followed by the data, i.e. vijay mukhi.
Let us now browse through the data sent by a web server.
a.cs
using System;
using System.Net;
using System.IO;
class zzz
{
public static void Main()
{
WebRequest r = WebRequest.Create(“http://www.vijaymukhi.com”);
WebResponse re = r.GetResponse();
Stream s = re.GetResponseStream();
Byte[] a = new Byte[51200];
int b = s.Read(a, 0, 51200);
Console.WriteLine(b);
Console.Write(System.Text.Encoding.ASCII.GetString(a, 0, b));
}
}
Output
1085
<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.0 Frameset//EN”
“http://www.w3.org/TR/REC-html40/frame.dtd”>
<html>
<head>
<title>Vijay Mukhi’s Technology cornucopia</title>
</head>
<frameset frameborder=”0″ framespacing=”0″ border=”0″ cols=”*” rows=”97,*”>
<frame marginwidth=”0″ marginheight=”0″ src=”heading.html” mce_src=”heading.html” name=”heading”
noresize scrolling=”no”>
<frameset frameborder=”0″ framespacing=”0″ border=”0″ cols=”210,*” rows=”*”>
<frameset frameborder=”1″ framespacing=”0″ border=”0″ cols=”*” rows=”0,*”>
<frame marginwidth=”0″ marginheight=”0″ src=”code.html” mce_src=”code.html” name=”code” noresize
scrolling=”no” frameborder=”0″>
<frame marginwidth=”5″ marginheight=”5″ src=”menu_empty.html” mce_src=”menu_empty.html” name=”menu”
noresize scrolling=”auto” frameborder=”0″>
</frameset>
<frame marginwidth=”5″ marginheight=”5″ src=”main.html” mce_src=”main.html” name=”text” noresize>
</frameset>
<noframes>
<p>The <code>NOFRAMES</code> element is to be used to give useful content to
people with browsers that cannot display frames. One example is Lynx, a
text-based browser.</p>
</noframes>
</frameset>
</html>
We expect you to either have a web server installed on your machine, or at least be connected to the net.
Every machine on the internet is recognized by a name. For example. the Microsoft site is known as www.microsoft.com; the Java site on the net is known
as www.javasoft.com; and my site is known as www.vijaymukhi.com. However, every machine is also known by the common name of ‘localhost’. When the address
specified in the browser is www.microsoft.com, the web server of that machine sends across an HTML file to the browser. The file that is sent across is either
named index.html or default.html.
In the above program, we would like to simulate the functioning of a browser, i.e. we want to fetch an HTML file from a web server.
In the System.Net namespace, there is a class called WebRequest that has a static function called Create. This function requires the name of the machine
that runs a web server. While reading this book, in case you are not connected to the Internet, you may use ‘http://localhost/’ as the name of the machine. Or
else, you may connect to a site on the net such as www.vijaymukhi.com.
A WebRequest object r is returned by the Create function. The WebRequest class has another function called GetResponse, which returns a WebResponse object. The
value returned is stored in a variable called re. Calling the method GetResponseStream from the WebResponse object returns a Stream object that can be utilized to read the HTML file sent across by the web server.
The file handling concepts, learnt a short while ago, may be applied here too. The interaction between our program and the web server is handled by two
classes, i.e. WebRequest and WebResponse. We have no inkling as to what each class accomplishes.
In order to read the HTML file off the Stream object, we create a byte array having a size of 51200. Thereafter, we employ the Read function to read 51200
bytes, from the inception of the stream, into the array. The value returned by the Read function is indicative of the number of bytes that have been read off
the stream. On displaying this value, the number obtained is less then 51200 (in our case it is 1085), and not 51200. Therefore, despite our having commanded the
Read function to read upto 51200 bytes, it read only 1085 bytes. This is because it encountered an End Of File mark after having read 1085 bytes.
In case of an error, the WebResponse object displays a Status code. Few of the values that may be assigned to this Status code, are as follows:
• 200 indicates ‘success’.
• 403 indicates ‘permission denied’.
• 404 indicates that the ‘file name does not exist’.
Since no errors are visible in our case, we assume the status code to be 200.
Thereafter, the byte array is converted into a string, so that it can be displayed using the GetString function. Therefore, we see the first 1085 bytes
of the HTML file, sent by the web server.
a.cs
using System;
using System.Net;
using System.IO;
class zzz
{
public static void Main()
{
WebRequest r = WebRequest.Create(“http://www.yahoo.com”);
WebResponse re = r.GetResponse();
Stream s = re.GetResponseStream();
Byte[] a = new Byte[51200];
int b = s.Read(a, 0, 51200);
while (b > 0)
{
Console.Write(System.Text.Encoding.ASCII.GetString(a, 0, b));
b = s.Read(a, 0, 512);
System.Console.WriteLine(b);
}
}
}
We are not in a position to estimate the file size of the HTML file, which has been sent by the web server. So, we take recourse to the ‘while’ loop in
situations of incertitude or perplexity.
The Read function returns a zero, when there is no more data to be read from the stream. Thus, the ‘while’ loop terminates when the value of b becomes zero.
Therefore, the last line of the output would always display a zero. In fact, we can regulate the number of bytes that we want the Read function to read off the
stream. Here, we specify a value of 512. The framework enjoys the license to ignore this number.
In order to retrieve a specific file off the web server, such as a.html, we add the name of the file at the end of the URL. If the file appertains to the local
hard disk, the Create function will be written as follows:
WebRequest r = WebRequest.Create(“http://localhost/a.html”);
The next query which is likely to vex our minds is: Where would the file a.html be stored on the hard disk? The answer to this depends upon the web server
installed on the machine. In case of IIS or PWS from Microsoft, the file a.html would be placed in the sub-directory c:\inetpub\wwwroot. Reading a file sent by
a web server is similar to reading a file from the local disk. Therefore, we do not have to discover two separate ways of reading the same thing.
a.cs
using System;
using System.Net;
using System.IO;
class zzz
{
public static void Main()
{
Uri u = new Uri(“http://www.yahoo.com”);
HttpWebRequest wr = (HttpWebRequest) WebRequest.Create(u);
AsyncCallback a = new AsyncCallback(abc);
IAsyncResult r = (IAsyncResult) wr.BeginGetResponse(a,wr);
Console.ReadLine();
}
static void abc(IAsyncResult ar)
{
HttpWebRequest r = (HttpWebRequest) ar.AsyncState;
HttpWebResponse re = (HttpWebResponse) r.EndGetResponse(ar);
Stream s = re.GetResponseStream();
Console.WriteLine(“Status code: ” + re.StatusCode);
Byte[] a = new Byte[51200];
int b = s.Read(a, 0, 51200);
while (b > 0)
{
Console.Write(System.Text.Encoding.ASCII.GetString(a, 0, b));
b = s.Read(a, 0, 512);
System.Console.WriteLine(b);
}
}
}
The above program displays the same output as before, but with a difference. It follows a dissimilar method of writing code. In the previous example, the Read
function waited till the data was received from the Internet. When we are connected to the Internet, this wait could be indefinite. This effectively would
disable the program from proceeding any further. While awaiting data, the program cannot carry out any other activity. Thus, to avoid squandering of time and resources, we need a mechanism, by means of which, the program can be
intimated about the receipt of data for the HTML file.
The static function Create, now accepts a URI as a parameter. To create this URI object, we pass the same URI string as a parameter to the constructor. The
return value of this function is stored in a HttpWebRequest object, since this class is derived from the class WebRequest. This class has a function
BeginGetResponse, which accepts two parameters.
The first is an AsyncCallback delegate, which requires the name of a function. In the C# world, a delegate simply stands-in for a function to be called.
The second parameter is a State object. Despite our not making any use of any state related information, we are not allowed to supply a null value. It is
mandatory to supply a value, without which, an error is generated. Thus, we specify the object wr, of type HttpWebRequest, as the parameter. Under normal
circumstances, we are required to create a State object and furnish it as a parameter.
After a certain time duration, data arrives from the web server, and the function abc gets called. This function is passed IAsyncResult as a parameter,
which has a property AsyncState. Since this is an Object, we cast it to HttpWebRequest.
We require an HttpWebResponse object, in order to obtain a Stream object, which will be employed to read the file received from the web server. The
HttpWebRequest class has a EndGetResponse function, which accepts an IAsyncResult as a parameter, and returns a HttpWebResponse object. This object,
in turn, is derived from the HttpResponse class. This function terminates the asynchronous request. The remaining code is similar to the above program. We
however, are at liberty to execute other tasks, while the file is being received. Normally, a separate thread runs the function abc in an asynchronous
manner.
a.cs
using System;
using System.Net;
using System.IO;
class zzz
{
public static void Main()
{
Uri u = new Uri(“http://www.yahoo.com”);
HttpWebRequest w = (HttpWebRequest) WebRequest.Create(u);
IAsyncResult r = (IAsyncResult) w.BeginGetResponse(new AsyncCallback(abc),w);
Console.ReadLine();
}
static void abc(IAsyncResult ar)
{
HttpWebRequest r = (HttpWebRequest) ar.AsyncState;
HttpWebResponse re = (HttpWebResponse) r.EndGetResponse(ar);
int b = 0;
char[] a = new char[512];
StreamReader rd = new StreamReader(re.GetResponseStream(),
System.Text.Encoding.UTF8);
StringWriter w = new StringWriter();
b = rd.Read(a, 0, 512);
while (b != 0 )
{
w.Write(a, 0, 512);
b= rd.Read(a, 0, 512);
}
Console.WriteLine(“Message = ” + w.ToString());
}
}
There are a myriad ways of achieving the same output. In our case, we have presented a program that displays the same output as before, by using a
different set of classes. The variations are only in the function abc.
We first create an array of chars and not bytes. Thereafter, a StreamReader object rd, which is derived from TextReader, is created. This class has the
ability to read characters from the Stream object supplied to it. The second parameter to the constructor is the encoding-type to be used, while reading
character input from a stream. The Stream class is designed for byte input-output only. By default, it takes the UTF-8 encoding. Thus, we did not
have to pass it as a parameter. It does not default to the ANSI code page for the current system. The advantage of UTF-8 is that, it handles Unicode
characters appropriately, with localized versions of the operating system.
The StreamReader class is similar to the Stream class, in that, its Read function accepts an array of chars and not an array of bytes. The return value
also remains the same.
Thereafter, the StringWriter class concatenates this series of chars. Thus, the StringWriter object w, stores the HTML file as one large string, which gets
appended by the Write function.
We could also have used the WriteLine function to display the array of chars.
Domain Name System
a.cs
using System;
using System.Net;
class zzz
{
public static void Main()
{
String s = Dns.GetHostName();
System.Console.WriteLine(s);
}
}
Output
vmukhi
Whenever we need to connect to any computer, the machine’s IP address has to be determined. Every machine on the Internet is identified by a unique number,
which is called the machine’s IP address. This number is of a ‘long’ data type. Since it is humanly impossible to memorize 4 billion numbers, every machine is
also allotted a name. The system that maps a name to its corresponding IP address, is called the Domain Name System (DNS).
The class Dns contains only static entities, and hence, it is called a static class. One of its members is called GetHostName, which returns the name of the machine on which the current program is being executed.
As we mentioned earlier, every machine by default, is assigned the name of localhost. While installing Windows 2000, we have proactively assigned it the
name of ‘vmukhi’. Hence, the output shows ‘vmukhi’.
a.cs
using System;
using System.Net;
class zzz
{
public static void Main() {
IPHostEntry i = Dns.GetHostByName(“www.microsoft.com”);
IPAddress [] a = i.AddressList;
foreach ( IPAddress j in a)
System.Console.WriteLine(j.ToString()+ ” ” + j.Address);
}
}
Output
207.46.130.45 763506383
207.46.230.229 -437899569
207.46.131.91 1535323855
207.46.131.199 -947704113
207.46.230.219 -605671729
Every web server or client on the Internet is known by an IP address and a corresponding name. Moreover, the name on the Internet, such as,
www.microsoft.com, may not necessarily be restricted to a single IP address. It may have multiple IP addresses mapped onto it. This is what the above program
demonstrates.
The function GetHostByName is called and given the domain name, whose IP addresses are to be retrieved. An object is returned, which is an instance of
IPHostEntry. This class has a property AddressList, which returns an array of IPAddress objects. A single object is assigned to each IP address represented by
the domain name.
An IP address is a protracted number. It comprises of four numbers, each having a value, which falls within the range of 0 to 255. Thus, we can represent an IP
address in the dotted decimal notation, where the four numbers are separated from each other by a dot. Displaying the Address member directly exhibits a long
number. You can ascertain as to which of these is more readable. The ‘foreach’ statement is used to iterate through the array.
a.cs
using System;
using System.Net;
class zzz
{
public static void Main()
{
IPHostEntry i = Dns.GetHostByName(“www.microsoft.com”);
String [] a = i.Aliases;
foreach ( String j in a)
System.Console.WriteLine(j);
}
}
Output
www.microsoft.com
An IP address may contain other DNS names, which resolve to the same address. In this case, there are none. Thus, it is evident that the Microsoft site on the
net has no other aliases or names.
a.cs
using System;
using System.Net;
class zzz
{
public static void Main()
{
IPHostEntry i = Dns.GetHostByName(“www.microsoft.com”);
String a = i.HostName;
System.Console.WriteLine(a);
}
}
Output
www.microsoft.akadns.net
The property HostName gives us the DNS name of the host. The HostName for www.microsoft.com happens to be www.microsoft.akadns.net.
a.cs
using System;
using System.Net;
class zzz
{
public static void Main()
{
IPAddress i = IPAddress.Parse(“207.46.130.45″);
System.Console.WriteLine(i.ToString()+ ” ” + i.Address);
}
}
Output
207.46.130.45 763506383
At times, we may wish to convert a dotted decimal IP address to an int, i.e. an actual long number. The Parse function in IPAddress accepts a dotted decimal IP
address as a string, and returns an object that is an instance of IPAddress. We then display the value as a string and an int.
How do we achieve the reverse process? We may want to convert an IP Address from a dotted decimal notation, into an instance of IP Address. At other times, we
may want to convert an IPAddress, which is in the form of a string, into the dotted decimal notation. The same function can be used to accomplish both these
predilections.
a.cs
using System;
using System.Net;
class zzz
{
public static void Main()
{
String s = Dns.IpToString(763506383);
System.Console.WriteLine(s);
}
}
Output
207.46.130.45
The IP addresses are capable of being converted from an int format into a dotted decimal string or quad representation, by using the IpToString function.
a.cs
using System;
using System.Net;
class zzz
{
public static void Main()
{
IPHostEntry i = Dns.Resolve(“www.microsoft.com”);
System.Console.WriteLine(i.AddressList[0].ToString());
}
}
Output
207.46.230.229
The function Resolve in the Dns class, has no complications. We may be interested in an IP address, representing either a single domain name or
multiple domain names. One of the solutions is, to use the function GetHostByName. Alternatively, the Resolve function may be used to achieve the
same output.
Servers and Clients
Server program
a.cs
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class zzz
{
public static void Main()
{
DateTime n;
String s1;
TcpListener t = new TcpListener(13);
t.Start();
while (true)
{
System.Console.WriteLine(“Before”);
Socket s = t.AcceptSocket();
System.Console.WriteLine(“After”);
n = DateTime.Now;
s1 = n.ToShortDateString() + ” ” + n.ToLongTimeString();
Byte[] b = Encoding.ASCII.GetBytes(s1.ToCharArray());
s.Send(b, b.Length, 0);
Console.WriteLine(“Sent ” + s1);
}
}
}
Client program
b.cs
using System;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Text;
class zzz
{
public static void Main(String[] args)
{
TcpClient t = new TcpClient();
Byte[] r = new Byte[32];
t.Connect(“127.0.0.1″, 13);
Stream s = t.GetStream();
int n = s.Read(r, 0, r.Length);
String a = Encoding.ASCII.GetString(r);
Console.WriteLine(“Received ” + n );
Console.WriteLine(a);
t.Close();
}
}
Server Dos box
Before
After
We have supplied the following two programs, which are to be executed in two different DOS boxes:
• a TCP server program.
• a TCP client program.
The Server is to be executed first, since the client expects the server to be running. So, lets understand the server program first.
The DateTime class provides access to the current date and time, in a multitude of formats. We first create an object, which is an instance of the TcpListener
class. Thereafter, the constructor is passed a parameter of 13. It has no other significance, other than being called a Port Number.
Most people, when online, either surf the net, or download files, or send E-mail. These activities have certain rules/protocols that are required to be
followed for successful execution of the task. We cannot have separate machines to handle each of the above protocols. Therefore, the programs/servers are
executed on the same machine; however, they work on a specified number called, the Port Number.
Every packet of data is tagged with a number, which specifies the protocol that the packet belongs to. There is a world-wide body called IANA (Internet Assigned
Numbers Authority), which determines the number to be assigned to each protocol. Some of them are as follows:
• The World Wide Web has been allocated the number 80
• FTP has been allocated the number 21
• E-mail has been allocated the number 25.
Thus, if we notice a packet with a port number 80, we would immediately understand that the packet is carrying data for the World Wide Web, i.e. the www
protocol.
The TcpListener class constructor requires the Port Number, since our server only accepts packets that carry this number. We have asked our server to listen,
and accept packets on port 13. The first 1024 port numbers are reserved by IANA for various protocols. The rest of the numbers may be utilized by us, for our
own protocols. The number 13 falls within the range of reserved port numbers.
TCP stands for Transmission Control Protocol. Every TCP server needs a function to kick start the entire process. This function is called ‘Start’. As of now, we
are neither aware of, nor care about what ‘start’ actually does. We are only apprehensive and aware about the fact that, if this function is not called, none of the clients would ever be able to connect to our server. The TcpListener
class is used extensively while writing an Internet server.
Around 20 years ago, a large number of networking protocols existed. A networking protocol merely consists of a series of numbers in a specific format,
which two machines transmit to each other, in order to facilitate communication. It was extremely complex and cumbersome for programmers to comprehend the
numerous networking protocols. TCP/IP was also one such protocol. At that time, nobody would have known that, it would turn out to be the world’s most dominant
networking protocol, in the days to come.
A group of programmers banded together and came out with a series of functions, which could generate the bytes for a large number of networking protocols. For
reasons unknown, they named these functions as Sockets API. Thus, a networking coder is traditionally known as a Sockets programmer.
In the case of C#, we have a class called Socket. TcpListener is derived from it. The advantage of using socket classes like TCPListener is that, functions
like ‘Start’, call a tremendous amount of code from other Sockets classes. So, we do not have to delve into the innards of sockets programming, in order to be
able to use them. The TCPListener class, in a sense, provides us with networking services at a higher level of abstraction, than what could have been achieved,
by using the Socket class directly.
On running our server ‘a’, we only notice a single word ‘Before’. Thereafter, our server seems to wait forever. This wait is attributable to the Accept
function. In computer parlance, it goes off to sleep. Our server waits till some TCP client connects to our server on port 13. This wait could extend till
eternity.
The time has come to try and comprehend as to what a TCP client program is.
The TCPClient class is similar to a TcpListener class. This class has functions that use the code from Sockets classes and offer assistance in writing a TCP
client program.
The Connect function connects to a server. Therefore, it requires the domain name or IP address of the machine, which we desire to connect to. Further, the
port number also needs to be specified. We have specified the port number as 13. This is because our server only accepts packets marked with the port number 13.
When the Connect function attempts to connect to the server, the server moves beyond the Accept function.
The function Accept in the server, returns an instance of a Socket class. This object now becomes a handle, and is used to return data back to the client. The
DateTime class has a static member called ‘Now’, which gives the current date and time. Since the value is a DateTime object, we use one of the many functions
in the DateTime class, to display the date as a string in a specific format. Here, we use two functions, ToShortDateString and ToLongDateString; and store
the concatenated string value in a variable s1.
This string is now required to be sent across to the client. To accomplish this, we need to convert the date in the string, into an array of bytes. So, we first
convert the string s1 to an array of chars, and then, employ the GetBytes function to convert this array of chars into an array of bytes. The socket class
has a function called ‘Send’. It accepts a byte array as the first parameter and the length of the array as the second parameter; and eventually sends the data
to the client.
At the client end, the GetStream function is used to obtain a Stream object. Then, using the good old Read function, the bytes in the Stream are read into a
byte array. Finally, the byte array is converted into a string, and then its contents are displayed.
In one of our other C# books, we had demonstrated how the Web client connects to the Webserver on port 80. This program had used a set of classes distinct from
the ones used here. But, in the ultimate analysis, they all call code from the Sockets class.
-
Recent
- Windows Shortcut Keys
- DREAMWEAVER CS3 WITH CSS, AJAX, AND PHP PART-03
- DREAMWEAVER CS3 WITH CSS, AJAX, AND PHP 02
- DREAMWEAVER CS3 WITH CSS, AJAX, AND PHP 01
- Valentine Gifts & Valentine Jewelry
- Java Help, Java Tutorials, Java Programming, Java Tricks
- Java Help, Java Tutorials, Java Programming, Java Tricks
- Java Help, Java Tutorials, Java Programming, Java Tricks
- Java Help, Java Tutorials, Java Programming, Java Tricks
- C Language Help, C Language Tutorials, C Language Programming, C Language Tricks { The C# Language }
- C Language Help, C Language Tutorials, C Language Programming, C Language Tricks { The C# Language }
- C Language Help, C Language Tutorials, C Language Programming, C Language Tricks { The C# Language }
-
Links
- Sherawat Building Construction Jaipur
- Web Designing Company Jaipur
- world news update
- It Education
- it education video
- hindi online movie
- rajasthan election 2008
- rajasthan election
- Diamond Jewelry,Diamond Rings,Diamond Engagement Rings,Diamond Earrings,Diamond Pendants at DiamondJewelry4u.
- Tourism oF Rajasthan
- World News Update
- EDUCATION HEALTH CARE
- Rajasthan Tourism
- Best Songs
- Information Technology Education
- Rajasthan Election 2008
- Information Technology Video Education
- IT Service
- Web Solution
- SHERAWAT WEB SOLUTION
- Sherawat Web Solution
- World News Update – B
- About Diamond & Diamond Jewelry – b
- Education Health Care – b
- Best Songs – B
- Rajasthan Tourism -B
- Information Technology Education – B
- Information Technology Video Education – B
- sbinfosystem – B
- Web Solution – B
- Online Hotel Booking
- National Network of Tourism
- Hotels in India
- Hotels in Goa
- Hotels in Jammu & Kashmir
- Hotels in Madhya Pradesh
- Hotels in Pune
- Hotels in Rajasthan
- Hotels in Orissa
- Hotels in Karnataka
- Hotels in West Bengal
- Hotels in Andhra Pradesh
- Hotels in Nagaland
- Hotels in Tamilnadu
- Hotels in Assam
- Hotels in Kolkata
- Hotels in Mumbai
- Hotels in Arunachal
- Hotels in Bangalore
- Hotels in Uttar pradesh
- Hotels in Punjab
- Hotels in Lakshdweep
- Hotels in Delhi
- Hotels in Pondicherry
- Hotels in Himachal
- Hotels in Uttaranchal
- Hotels in Chennai
- Hotels in Chhattisgarh
- Hotels in Hyderabad
- Hotels in Kerala
- Hotels in Andaman
- Hotels in Gujarat
- Hotels in Haryana
- Hotels in Sikkim
- Movie Tickets Booking
- Programming Code
- Designing Templates
- Tourism in Bharat
- Andhra Exam Results
- Arunachal Exam Results
- Assam Exam Results
- Bihar Exam Results
- Chhattisgarh Exam Results
- Delhi Exam Results
- Goa Exam Results
- Gujarat Exam Results
- Haryana Exam Results
- Himachal Exam Results
- Jharkhand Exam Results
- Jammu Kashmir Exam Results
- Karnataka Exam Results
- Kerala Exam Results
- Maharashtra Exam Results
- Manipur Exam Results
- Meghalaya Exam Results
- Mizoram Exam Results
- MP Exam Results
- Orissa Exam Results
- Punjab Exam Results
- Rajasthan Exam Results
- Sikkim Exam Results
- Tamil Nadu Exam Result
- Tripura Exam Results
- UP Exam Results
- Uttaranchal Exam Results
- Westbengal Exam Results
-
Archives
- May 2009 (1)
- February 2009 (1)
- January 2009 (2)
- February 2008 (1)
- January 2008 (4)
- December 2007 (15)
- November 2007 (4)
-
Categories
-
RSS
Entries RSS
Comments RSS