4.

 

Designers

 

The very quintessence of this chapter is to work in consort with the Design time environment of Visual Studio.Net.

 

a.cs

using System;

using System.Collections;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Drawing;

using System.Windows.Forms;

using System.Windows.Forms.Design;

using System.IO;

[Designer(typeof(bbb))]

public class aaa : Control

{

protected override void OnPaint(PaintEventArgs e)

{

Graphics g = e.Graphics;

Brush b = new SolidBrush(ForeColor);

g.DrawString("Vijay1", Font , b , ClientRectangle);

}

}

public class bbb : ControlDesigner

{

int b1 = 10;

int a1

{

get

{

abc("get " + b1);

return b1;

}

set

{

abc("set " + value);

b1 = value;

}

}

protected override void PreFilterProperties (IDictionary p)

{

Type t = GetType();

abc("PreFilterProperties " + t.ToString());

PropertyDescriptor p1 = TypeDescriptor.CreateProperty(typeof(bbb),"a1",typeof(int));

p["zzz"] = p1;

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

 

a.txt

PreFilterProperties bbb

get 10

set 100

get 100

 

One good look at our control, and you would come to realise that with the exception of the OnPaint function, which is responsible for displaying Vijay1, there is virtually nothing new that you may be able to discern from it. Thus, the control may extract only the  properties from the class Control, since it is not required to draw out everything.

 

The control class aaa is tagged with the Designer attribute. This attribute is assigned the name of a class that implements the design time services. The class provided with the DesignerAttribute constructor is bbb, which is derived from the class ControlDesigner.

 

In the class bbb, a private property a1 of type int is declared and the variable b1 is used to store its state or value. The handy function abc is posited as always, so as to apprise us of when the function is getting called and which are its precise parameters.

 

The function PreFilterProperties is present in the class ControlDesigner to enable the programmers to attain the list of properties that the control possesses. Thereafter, it can be used to add, modify, delete, or do whatever else you fancy, to these properties. The IDictionary object p, which is passed as a parameter, is used to access the properties. The data type of the properties is PropertyDescriptor in the IDictionary parameter p.

 

The GetType function present in the class ControlDesigner returns the type of the class that it belongs to, viz. bbb. Then, using the static function CreateProperty from the class TypeDescriptor, a property is created that is represented by a class PropertyDescriptor.

 

This function takes three parameters:

     The first is the type that the property inhabits, i.e. class bbb.

     The second is the name of the property, i.e. a1.

     The third is the data type of the property, i.e. an int.

 

This PropertyDescriptor object is then added to the indexer of the IDictionary parameter p using a key of zzz.

 

A peek at screen 4.1 suggests that property a1 has been added with a value of 10.

 

Screen 4.1

 

However, if the value of the property is modified to 100, it triggers off a call to both, the get and the set functions. The get and set accessors get called from the class bbb, which has a private property called a1.

 

Thus, a property called a1 is added to the control, notwithstanding the fact that no property called a1 has been defined. The designer facilitates dynamic changes to the properties displayed in the properties window.

 

a.cs

using System;

using System.Collections;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Drawing;

using System.Windows.Forms;

using System.Windows.Forms.Design;

using System.IO;

[Designer(typeof(bbb))]

public class aaa : Control

{

protected override void OnPaint(PaintEventArgs e)

{

Graphics g = e.Graphics;

Brush b = new SolidBrush(ForeColor);

g.DrawString("Vijay1", Font , b , ClientRectangle);

}

}

public class bbb : ControlDesigner

{

int b1 = 10;

int a1

{

get

{

return b1;

}

set

{

b1 = value;

}

}

protected override void PreFilterProperties (IDictionary p)

{

base.PreFilterProperties(p);

abc(p.Count.ToString());

ICollection c = p.Keys;

abc(c.Count.ToString());

IEnumerator e = c.GetEnumerator();

while ( e.MoveNext() )

{

string s = (string) e.Current;

abc(s);

}

PropertyDescriptor p1 = TypeDescriptor.CreateProperty(typeof(bbb),"a1",typeof(int));

p["Dock"] = p1;

p.Remove("Anchor");

PropertyDescriptor p2 = (PropertyDescriptor )p["Text"];

abc(p2.Name + " " + p2.PropertyType.ToString() + " " + p2.Description + " " + p2.ComponentType.ToString());

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

 

a.txt

66

66

Text

Anchor

Dock

 

Prior to running the above program, carefully examine the screen saved by the last program, i.e. screen 4.1.

 

The image displays two properties of Dock and Anchor, which are placed at the extreme end, under the Layout Category.

 

On executing the above program, the two properties mentioned above, viz. Dock and Anchor, simply flee off, and a new property called a1 emerges instead. Let us now seek a clue into the magic that expelled certain properties from the properties toolbox.

 

The IDictionary object p, which is passed as a parameter, has a member named Count. This provides the total count of the items, which in our specific case reflects the number of PropertyDescriptor objects present in the IDictionary object. The file a.txt indicates that 67 objects are present in the object. This implies that there are 67 keys in the IDictionary object.

 

The real glitch here is that the IDictionary object stores values in consonance with the keys. Therefore, the key is used as the indexer while retrieving values present in the IDictionary parameter p. Thus, if the key for the PropertyDescriptor object stored in the IDictionary parameter is not known, it cannot be retrieved. The indexer cannot be in the form of p[0] in an endeavor to access  the first member.

 

The IDictionary parameter has a Keys property that returns an ICollection object. It merely contains the keys. The Count member confirms the presence of a total of 67 keys.

 

The GetEnumerator function is employed to return an IEnumerator interface, which iterates through all the keys in the ICollection interface. The MoveNext function activates the next entity in the collection. It returns a value of true in case more items exist in the collection, and a value of false in case there are none.

 

Owing to the presence of 67 keys, the loop iterates itself 67 times. The property Current provides access to each and every key, which is cast and stored into a string variable.

 

The a.txt file reveals the names of all the 67 keys under which the properties are stored. We have desisted from displaying all of them due to space constraint.

 

A new PropertyDescriptor object p1 is created. It represents the property a1 of type int in the class bbb. The PropertyDescriptor object, which was stored under the key Dock, is ousted and replaced with this newly created PropertyDescriptor. Thus, the key Dock now stores the details of the a1 property.

 

The Remove function of the IDictionary object accepts a property key as the parameter and deletes it from the IDictionary list. The key Anchor with the property Anchor is eliminated from the list.

 

The indexer of the IDictionary class takes a key and returns the corresponding PropertyDescriptor object. Thus, if we specify the key Text, the PropertyDescriptor of the Text property is returned. The Name of the property Text is displayed. It is followed by its type i.e. string. Then comes the Description or help, and finally, the type that it is bound to, viz. a component.

 

It would be a judicious move to call the base class function also. Hence, in the PreFilterProperties, we call the base class function first. To jog your memory, we repeat yet again that this base class function is purely optional. Regardless of whether we call it or not, the program runs as advertised. So, why take any chances.

 

a.cs

using System;

using System.Collections;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Drawing;

using System.Windows.Forms;

using System.Windows.Forms.Design;

using System.IO;

[Designer(typeof(bbb))]

public class aaa : Control   {

public string s = "Vijay1";

protected override void OnPaint(PaintEventArgs e)

{

Graphics g = e.Graphics;

Brush b = new SolidBrush(ForeColor);

g.DrawString(s, Font , b , ClientRectangle);

}

}

public class bbb : ControlDesigner

{

int b1 = 10;

int a1

{

get

{

return b1;

}

set

{

b1 = value;

ISelectionService s = (ISelectionService) GetService(typeof(ISelectionService));

Control c = (Control)s.PrimarySelection;

abc(c.ToString());

aaa a = (aaa) c;

a.s = b1.ToString();

a.Invalidate();

}

}

protected override void PreFilterProperties(IDictionary p)

{

base.PreFilterProperties(p);

PropertyDescriptor p1 = TypeDescriptor.CreateProperty(typeof(bbb),"a1",typeof(int));

p["zzz"] = p1;

}

public void abc(string s)  {

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

 

a.txt

aaa1 [aaa]

 

The above control appears and comports itself like a normal control. It displays Vijay1 and also displays the value of the property a1 as 10, as seen in screen 4.2.

 

Screen 4.2

 

The first apparent distinction is that, in lieu of constant text that was used in the earlier program, the text displayed with the aid of the DrawString function uses a variable named s.

 

The set accessor of the property a1 is provided with additional code. Now, when the value of the property a1 is changed to 1000 and the Enter key is pressed, the control also displays 1000. Thus, we have been able to successfully transfer a value of a present property to the controls property. However, the code of the property has been furnished in the class bbb and not in the class aaa.

 

In the set accessor, the value of the variable s is changed and the control is invalidated. The class ControlDesigner has a function called GetService that calls upon the hosting environment, viz. Visual Studio.Net, to allow access to the services within it.

 

We pass a type ISelectionService, since this is the service that we have set our hearts on. We use the interface ISelectionService to access the different components on the form. The property PrimarySelection returns the control that is currently selected.

 

In our case, it has to be the control aaa or the object aaa1. This is because we are changing its property a1, and thus, this control is currently active or selected. We then cast the control c to the type aaa, since this is what it really is. We also modify the public instance variable s to the current value of the property stored in the variable b1.

 

We then call the Invalidate function, which in turn invokes the OnPaint function. This function displays the current value of the string s.

 

Thus, when we change the value of the property a1 to 1000 in screen 4.3, the control also displays a value of 1000. This is how a control can be enlightened about the change in a property.

 

Screen 4.3

 

a.cs

using System;

using System.Collections;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Drawing;

using System.Windows.Forms;

using System.Windows.Forms.Design;

using System.IO;

 [Designer(typeof(bbb))]

public class aaa : Control

{

public string s = "Vijay1";

protected override void OnPaint(PaintEventArgs e)

{

Graphics g = e.Graphics;

Brush b = new SolidBrush(ForeColor);

g.DrawString(s, Font , b , ClientRectangle);

}

}

public class bbb : ControlDesigner

{

public override DesignerVerbCollection Verbs

{

get

{

DesignerVerb v1 = new DesignerVerb("Vijay Mukhi", new EventHandler(pqr));

DesignerVerb[] v = new DesignerVerb[] {v1};

DesignerVerbCollection v2 = new DesignerVerbCollection(v);

return v2;

}

}

void pqr(object sender, EventArgs e)

{

MessageBox.Show("Sonal");

}

}

 

On running the above control, we discern that a Verb gets added to the Designer, as shown in screen 4.4.

 

Screen 4.4

Screen 4.5

 

This verb displays Vijay Mukhi. If we click on the link or the verb, a message box gets displayed, as seen in screen 4.5.

 

The addition of verbs is yet another approach towards extending the designer.

 

The Visual Studio.Net framework ascertains whether the control supports design time Verbs or not. It does this by checking for the presence or absence of the property called Verbs, respectively. The class bbb is given the property named Verbs and a DesignerVerb object v1 is created. The constructor is provided with a string that the framework displays, and it is also geared with a function that would be called through a delegate. Thus, whenever the link Vijay Mukhi is clicked upon, the function pqr gets called.

 

A Designer verb is available as a menu option that can also be executed using the link under the properties window. Thus, when the right mouse button is clicked on the control, the menu that pops up displays the menu option of Vijay Mukhi.

 

Since there can be innumerable such verbs, an array of DesignerVerb objects is vital to store these individual designer verbs. Finally, a DesignerVerbCollection object is created with this single DesignerVerb array.

 

a.cs

public class bbb : ControlDesigner

{

DesignerVerb vb ;

public override DesignerVerbCollection Verbs

{

get

{

DesignerVerb v1 = new DesignerVerb("Vijay Mukhi", new EventHandler(pqr));

v1.Checked = true;

DesignerVerb va = new DesignerVerb("Sonal", new EventHandler(abc));

va.Enabled = false;

vb = new DesignerVerb("Mukhi", new EventHandler(xyz));

DesignerVerb[] v = new DesignerVerb[] {v1 , va , vb};

DesignerVerbCollection v2 = new DesignerVerbCollection(v);

return v2;

}

}

void pqr(object sender, EventArgs e)

{

MessageBox.Show("Sonal");

vb.Invoke();

}

void abc(object sender, EventArgs e)

{

MessageBox.Show("mukhi");

}

void xyz(object sender, EventArgs e)

{

MessageBox.Show("mukhi");

}

}

 

The above example has three DesignerVerb objects named v1, va and vb. They call the functions pqr, abc and xyz, respectively. The DesignerVerb array v now contains the three DesignerVerb objects. Thus, the screen 4.6 shows three verbs under the properties.

 

Screen 4.6

Screen 4.7

 

The designer verb is derived from the class MenuCommand. Hence, it has a large number of properties originating from the world of menus. The first one is checked. Hence, when the control is clicked on with the right mouse button, the verb Vijay Mukhi is displayed as checked, as seen in screen 4.7.

 

The second verb appears disabled in both, the properties window and in the menu, since the Enabled property has been set to false. Clicking on the first menu of Vijay Mukhi results in a call to the function abc, which first displays a message box, and then, beckons the Invoke function from the vb DesignerVerb object. This in turn invokes the function xyz, since summoning the Invoke function from a DesignerVerb object is akin to clicking on a designer verb.

 

To summarize in a nutshell, when we click on a DesignerVerb, the properties window merely calls the Invoke function.

 

a.cs

public class bbb : ControlDesigner

{

public override DesignerVerbCollection Verbs

{

get

{

DesignerVerb v1 = new DesignerVerb("Vijay Mukhi", new EventHandler(pqr));

DesignerVerb[] v = new DesignerVerb[] {v1};

DesignerVerbCollection v2 = new DesignerVerbCollection(v);

return v2;

}

}

void pqr(object sender, EventArgs e)

{

Font f = new Font("Times Roman", 14);

AttributeCollection a;

a = TypeDescriptor.GetAttributes(f);

abc(a.Count.ToString());

foreach ( Attribute b in a )

{

abc(b.ToString());

if ( b is TypeConverterAttribute)

{

TypeConverterAttribute c = (TypeConverterAttribute) b;

abc("TypeConverterAttribute  " + c.ConverterTypeName);

}

}

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

 

 

a.txt

3

System.ComponentModel.TypeConverterAttribute

 TypeConverterAttribute  System.Drawing.FontConverter, System.Drawing, Version=1.0.2411.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a

System.Runtime.InteropServices.ComVisibleAttribute

 System.ComponentModel.EditorAttribute

 

The verb DesignerVerb has been employed to elucidate the static members of the TypeDescriptor class. Since the code for the class aaa in the control does not change, this class is not depicted.

 

Each time the DesignerVerb is clicked upon, the function pqr gets summoned. The code displays the attributes present in the Font class. These attributes determine the manner in which the Font class would be displayed in the properties window.

 

A Font object is created. Subsequent to this, the Static member GetAttributes of the TypeDescriptor class is furnished either with an object or with a type name for the class that we have taken a fancy to. The TypeDescriptor class contains only static members.

 

The function GetAttributes returns an AttributeCollection object. Analogous to all Collection objects, this object too has a Count property that gives the quantity of attributes present in the Font class. The value visible in a.txt is 3. Then, using the foreach function, all these Attributes are examined. The indexer of the AttributeCollection class returns an Attribute type. The ToString function displays the attribute name and other details.

 

In brief, the Font class has three attributes placed on it TypeConverterAttribute, ComVisibleAttribute and EditorAttribute. Using the 'is' keyword, it is ascertained whether the data type of the attribute is TypeConverterAttribute or not. If this is so, then more details such as the TypeName, can be used subsequently.

 

The 'if' statements could have been separated out for each of the attribute types in order to display more data related to them.

 

a.cs

using System.Reflection;

 

void pqr(object sender, EventArgs e)

{

Type t = typeof(FontConverter);

MemberInfo [] m = t.GetMembers();

for ( int i = 0; i < m.Length ; i++)

{

if ( m[i].DeclaringType.ToString() == "System.Drawing.FontConverter")

{

abc(m[i].Name + " " + m[i].MemberType);

}

}

}

 

a.txt

GetPropertiesSupported Method

GetProperties Method

GetCreateInstanceSupported Method

CreateInstance Method

ConvertTo Method

ConvertFrom Method

CanConvertTo Method

CanConvertFrom Method

.ctor Constructor

FontNameConverter NestedType

FontUnitConverter NestedType

 

In the next example, the code for the pqr function has been transformed entirely. The program is written to determine the methods that the class FontConverter contains. This class has been derived from TypeConverter.

 

The typeof keyword is used to obtain the Type object, from which the GetMembers function is called. The function GetMembers returns an array of MemberInfo objects. It contains one object each for all the members in the class.

 

 

The stumbling block that we run into here is that the FontConverter class is derived from TypeConverter, which in turn is derived from object. However, we are primarily keen on acquainting ourselves only those functions that FontConverter overrides. This is because, if we need to implement a class that should act like FontConverter, we would know exactly which function is to be overridden.

 

For this purpose, the details of the function from the MemberInfo array are displayed, only if its DeclaringType property is FontConverter. The DeclaringType property provides details of the class that defines the function. In this manner, the a.txt file is filled up with a list of functions that the FontConverter class has defined.

 

a.cs

public class bbb : ControlDesigner

{

aaa a;

public override void Initialize(IComponent c)

{

abc("Initialize " + c.ToString());

base.Initialize(c);

a = (aaa) c;

ISelectionService s = (ISelectionService)GetService(typeof(ISelectionService));

s.SelectionChanged += new EventHandler(pqr);

}

void pqr(object o, EventArgs e)

{

ISelectionService s = (ISelectionService)o;

Control c = (Control)s.PrimarySelection;

abc(c.ToString());

a.s = c.Name;

a.Invalidate();

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

 

a.txt

Initialize aaa1 [aaa]

Form1 , Text: Form1

textBox1 , Text: textBox1

button1 , Text: button1

aaa1 [aaa]

 

A Button and a TextBox are also added to the Form. The control now displays the name of the Control that has being selected, as shown in screen 4.8.

 

Screen 4.8

 

This is due to the fact that the Initialize function gets called first. In this function, the IComponent parameter is only passed the control aaa. This value of the aaa control is stored in a public variable a.

 

The GetService function then provides a handle to the ISelectionService interface, which runs the designer in Visual Studio .Net. This designer has a simple event called  SelectionChanged. Henceforth, this event shall call the function pqr whenever the user selects or activates a new control.

 

It is in this manner that the designer is kept abreast about the activities performed by the user, even though the user-defined control is not being selected.

 

In the pqr function, the object parameter is the ISelectionService object. Hence, the PrimarySelection property is used to provide access to the Control that is currently selected by the user. The s property of the control is then set, using the instance variable a. The Invalidate function is invoked to redraw the control.

 

Thus, each time that a new control is launched, the name of the control gets displayed in the control aaa, as seen in screen 4.9.

 

Screen 4.9

 

a.cs

public class bbb : ControlDesigner

{

public override void Initialize(IComponent c)

{

abc("Initialize " + c.ToString());

base.Initialize(c);

ISelectionService s = (ISelectionService)GetService(typeof(ISelectionService));

s.SelectionChanged += new EventHandler(pqr);

s.SelectionChanging += new EventHandler(xyz);

}

void pqr(object o, EventArgs e)

{

ISelectionService s = (ISelectionService)o;

Control c = (Control)s.PrimarySelection;

abc("pqr " + c.ToString());

aaa a = (aaa)Component;

a.s = c.Name;

a.Invalidate();

}

void xyz(object o, EventArgs e)

{

ISelectionService s = (ISelectionService)o;

Control c = (Control)s.PrimarySelection;

abc("xyz " + c.ToString());

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

 

a.txt

Initialize aaa1 [aaa]

xyz Form1 , Text: Form1

pqr Form1 , Text: Form1

xyz textBox1 , Text: textBox1

pqr textBox1 , Text: textBox1

 

The ISelectionService interface consists of two events. The second one is SelectionChanging. This event is wired to the function xyz.

 

When the program is executed, the SeelctionChanging event gets fired first, followed by the SelectionChanged event. Both events take the same EventHandler delegate object. Also, in both cases, the first parameter is an interface ISelectionService. In both events, the parameter represents the same control. Therefore, the a.txt file displays the xyz and pqr functions, as being called with the same Control.

 

The Component property is used in the pqr function. This returns the component aaa, which has the attribute of Designer. There is absolutely no requirement for storing the value of the component in an instance variable, since the property Component can be used instead. Alternatively, the Control property too could have been used.

 

public class bbb : ControlDesigner  {

public override void Initialize(IComponent c)

{

base.Initialize(c);

SelectionRules s = SelectionRules;

MessageBox.Show(s.ToString());

}

}

 

The ControlDesigner class has a property named SelectionRules, which examines the movement of the control. The property returns a SelectionRules enum, which has been displayed using a message box. The Initialize function gets called perceptibly early in the game. Screen 4.10 displays three rules for movement, viz. AllSizeable, Moveable and Visible.

 

Screen 4.10

 

The AllSizeable value indicates that the control can be sized in all the directions, and that the selection service is not locked.

 

The Moveable value provides details on the aspect that the component has a Location property that can endow it with  ample mobility. The Visible property indicates the presence of a visible user interface having a border with the component. Bear in mind that the component also derives from the IComponent interface.

 

a.cs

public class bbb : ControlDesigner  {

public override void DoDefaultAction()

{

base.DoDefaultAction();

MessageBox.Show("hi");

}

}

 

Everything within Visual Studio.Net is customizable. Whenever we double click on a control, we wind up in the code generator. In the code generator, we are placed within a function having a certain signature. Visual Studio.Net simply calls the DoDefaultAction function. In our case, a double click on the control aaa brings up the MessageBox, as shown in the screen 4.11.

 

Screen 4.11

 

If the call to the base class function is annihilated, nothing ensues, since the signature is yet to be supplied. Before long, we shall be unveiling the magic behind the performance of this function.

 

a.cs

using System;

using System.Collections;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Drawing;

using System.Windows.Forms;

using System.Windows.Forms.Design;

[Designer(typeof(bbb))]

public class aaa : Control

{

}

public class bbb : ControlDesigner  {

public override DesignerVerbCollection Verbs

{

get

{

DesignerVerb v1 = new DesignerVerb("Vijay Mukhi", new EventHandler(pqr));

DesignerVerb[] v = new DesignerVerb[] {v1};

DesignerVerbCollection v2 = new DesignerVerbCollection(v);

return v2;

}

}

void pqr(object o, EventArgs e1)

{

IUIService e = (IUIService)GetService(typeof(IUIService));

Guid g = StandardToolWindows.ServerExplorer;

e.ShowToolWindow(g);

g = StandardToolWindows.ProjectExplorer;

e.ShowToolWindow(g);

}

}

 

This example functions with yet another service named IUIService. A click on the verb Vijay Mukhi triggers a call to the function pqr. The GetService function is employed as before to provide a handle to the service IUIService.

 

Each one of the standard toolbars that are exposed to the view in Visual Studio.Net, is identified by a unique number. This number is called a GUID and has a size of 16 bytes or 128 bits. This number is unique across time and space. Since the C# language is not equipped with any established means of displaying a 16 byte number, the GUID structure is used to represent this number.

 

The class StandardToolWindows has static read-only properties, such as ServerExplorer and ProjectExplorer, which represent the 16 byte number. They uniquely represent the individual windows. The function ShowToolWindow needs to be called to display the windows.

 

In the screen 4.12, the Server Explorer and Solution Explorer windows are open. Also, the Message Box is displayed with the unique number that the Project is identified by.

 

Screen 4.12

 

Extender Providers

 

a.cs

using System;

using System.IO;

using System.Collections;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Drawing;

using System.Windows.Forms;

using System.Windows.Forms.Design;

[ProvideProperty("a1",typeof(Control))]

public class aaa : Control, IExtenderProvider

{

bool IExtenderProvider.CanExtend(object t)

{

abc("Can Extend " + t.ToString());

if (t is Control && !(t is aaa))

{

abc("Can Extend1 ");

return true;

}

return false;

}

public string Geta1(Control c)

{

abc("Geta1 " + c.ToString());

return "Vijay";

}

public void Seta1(Control c, string s)

{

abc("Seta1 " + c.ToString() + " " + s);

}

protected override void OnPaint(PaintEventArgs e)

{

Brush b = new SolidBrush(ForeColor);

e.Graphics.DrawString("mukhi2", Font,  b , ClientRectangle);

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

 

Exit from VisualStudio.Net and restart the application. Then introduce a button. Once the form is double-clicked, no function is found to be associated with the button named button1. Further, the button does not have a property called a1 in the properties window. Now, initiate the aaa control into the toolbox, and then, incorporate it into the form window, as is customary.

 

Screen 4.13

 

The control assumes the appearance of a normal control, as is apparent from the screen 4.13. The function OnPaint spurs the word 'mukhi2' to be displayed in the control. But, no sooner is the control ushered in, does a property named a1 on aaa1 gets mysteriously hitched on to the control button. As is evident, it is not on the control aaa, as can be seen in screen 4.14.

 

Screen 4.14

 

This property also gets added to the Form object. The name of the property is a1, followed by the name of the object, i.e. aaa1. The main rationale behind the naming convention is that, there may be more than one instance of the control class aaa. Thus, we have achieved success in adding properties to a control. The code painter reveals two lines of code that have been added.

 

aaa1.Seta1(this.button1, "Vijay");

aaa1.Seta1(this, "Vijay");

 

Evidently, the aaa1 control has a function named Seta1. This function gets called with two parameters, i.e. a control or a form object, and the text or the value of the property. The addition of the property a1 to the controls on the page and the form can indubitably be attributed to the above lines of code. This makes the above control an Extender control since it facilitates addition of properties to other controls.

 

a.txt

Can Extend aaa1 [aaa]

Can Extend Form1 , Text: Form1

Can Extend1

Can Extend button1 , Text: button1

Can Extend1

Geta1 button1 , Text: button1

Seta1 button1 , Text: button1 Vijay1

Geta1 button1 , Text: button1

 

To begin with, let us examine the class aaa and the functions that it implements, so as to facilitate the ease of understanding of the process of adding properties to other controls.

 

The attribute ProvideProperty marks a class as an ExtenderProvider, thereby enabling it to offer properties to other controls. The first parameter is the name of the extender property, i.e. a1. This is the name of the property for the other controls. Since the name of the class is aaa, the name of the first instance becomes aaa1. The second parameter is the type on which the extender can implement an Extender control.

 

An Extender provider should not self-indulgently add the property to all data types. Since a Control has been specified here, only those types that are related to Control, must receive the a1 property. The class that has the attribute on it, must implement the interface IExtenderProvider. An Extender provider, as had been stated before, is a component whose sole purpose is to offer properties to other components.

 

The interface IExtenderProvider has only one function called CanExtend. It is pertinent to note that while the control is being placed in the form window, two components, viz. a form called Form1 and a button called button1, already exist. Thus, there are a total of three controls.

 

Visual Studio.Net is highly perceptive and notices the control marked with the attribute ProviderProperty. Therefore, it calls the function CanExtend. This function is called thrice due to the presence of three entities in the form. The string representation of the control is displayed for each of them.

 

This function ascertains from the user whether the Extender properties are to be specified with the control or not. A return value of true is indicative of the fact that the property a1 is to be added to the list of properties. The condition that we check for is whether the object passed as a parameter is a control or not. Further, it should not be the control aaa, since the Extender property a1 cannot to be added to itself. A value of true is returned only for the controls button1 and Form1, but not for the control aaa.

 

Thus, the CanExtend function, which gets called for each of the three controls, returns a true value for the controls Form1 and button1. The sequence in which the controls are called is inconsequential.

 

On selecting the button control, the properties of button become visible in the properties window. If we scroll further down, a1 meets the eye, thus invoking the function Geta1, so that a value can be displayed in the properties window. Since the value returned is Vijay, the value of the property a1 is displayed as Vijay.

 

Changing the value to Vijay1 results in a call to the Seta1 function. Here, the first parameter is the active control and the second parameter is Vijay1, which is the new value of the property. The new value of the property is set internally, so that when the Geta1 function gets called, this new value is returned. Since we have not stored the new value in a variable, the value has not been reflected. This explains the functioning of an Extender provider.

 

a.cs

using System;

using System.IO;

using System.Collections;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Drawing;

using System.Windows.Forms;

using System.Windows.Forms.Design;

[ProvideProperty("a1",typeof(Control))]

[ProvideProperty("a2",typeof(Button))]

public class aaa : Control, IExtenderProvider

{

bool IExtenderProvider.CanExtend(object t)

{

abc("Can Extend " + t.ToString());

if (t is Control && !(t is aaa))

{

abc("Can Extend1 ");

return true;

}

return false;

}

public string Geta2(Control c)

{

abc("Geta2 " + c.ToString());

return "Mukhi";

}

public void Seta2(Control c, string s)

{

abc("Seta2 " + c.ToString() + " " + s);

}

public string Geta1(Control c)

{

abc("Geta1 " + c.ToString());

return "Vijay";

}

public void Seta1(Control c, string s)

{

abc("Seta1 " + c.ToString() + " " + s);

}

protected override void OnPaint(PaintEventArgs e)

{

Brush b = new SolidBrush(ForeColor);

e.Graphics.DrawString("mukhi2", Font,  b , ClientRectangle);

}

public void abc(string s) {

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

a.txt

Can Extend aaa1 [aaa]

Can Extend Form1 , Text: Form1

Can Extend1

Can Extend button1 , Text: button1

Can Extend1

Geta1 button1 , Text: button1

Geta2 button1 , Text: button1

Geta1 Form1 , Text: Form1

 

While working with Extender properties, Visual Studio.Net augments the form with some of its own code. Thus, while testing the code after the incorporation of the additional code, it is advisable to add the control aaa afresh each time to the form. When the above control is added to the form, two Extender properties named a1 and a2 get displayed with the button, as seen in screen 4.15.

 

Screen 4.15

 

However, the form has only one Extender property a1 added to it. This implies that the same control aaa is now very selective about the properties that are to be added to each control.

 

Two ProvideProperty attributes are added to the class aaa. The new attribute that is added is named as a2, but its type is Button. Thus, the a2 property can only be added to a button object, and not to any other type, such as a form or a textbox.

 

The CanExtend function gets called only once, and since the value returned for the object aaa is false, these properties do not get added for aaa type controls. Similarly, the Geta1 and Geta2 functions get called for the button1 object.

 

In case of the Form1 object, only the Geta1 function gets called since the data type that can accept the property a2, should necessarily be a button. The Form1 object is of type Form and not of type Button. Therefore, returning a value of true in the CanExtend function is insignificant, since the data type of the form prohibits the system from associating the Extended property a2 with it.

 

The basic principle here is that the CanExtend function shall always be called, once for each control. The framework maintains a list of all the controls that provide a return value of true. It also ascertains whether the data type of the ProvideProperty attribute matches with the data type of the widget or not. If it does match, then and only then, will it call the Get function to assign a value to the property.

 

The Get function shall be called only if the property value is to be exhibited in the property window. While the Set function shall only be called if the value of the property is to be changed.

 

a.cs

using System;

using System.IO;

using System.Collections;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Drawing;

using System.Windows.Forms;

using System.Windows.Forms.Design;

[ProvideProperty("a1",typeof(Control)),Designer(typeof(bbb))]

public class aaa : Control, IExtenderProvider

{

Hashtable ht;

public Control a;

public aaa()

{

abc("aaa Constructor");

ht = new Hashtable();

}

bool IExtenderProvider.CanExtend(object t)

{

abc("CanExtend "+ t.ToString());

if (t is Control &&  !(t is aaa))

{

abc("CanExtend true");

return true;

}

return false;

}

public string Geta1(Control c)

{

string t = (string)ht[c];

abc("Geta1 " + t + " " + c.ToString() + " " + a.ToString());

return t;

}

public void Seta1(Control c , string v)

{

abc("Seta1 " + v + " " + c.ToString() + " " + a.ToString());

ht[c] = v;

if (c== a)

{

abc("Seta1 =" + v + " " + c.ToString());

Invalidate();

}

}

protected override void OnPaint(PaintEventArgs pe)

{

base.OnPaint(pe);

abc("OnPaint");

Rectangle r = ClientRectangle;

Pen p = new Pen(ForeColor);

pe.Graphics.DrawRectangle(p , r);

if (a != null)

{

string t = (string)ht[a];

if (t != null && t.Length > 0)

{

Brush b = new SolidBrush(ForeColor);

pe.Graphics.DrawString(t , Font, b , r );

}

}

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

public class bbb : ControlDesigner

{

public bbb()

{

abc("bbb Constructor");

}

public override void Initialize(IComponent c)

{

abc("bbb Initialize");

base.Initialize(c);

ISelectionService s = (ISelectionService)GetService(typeof(ISelectionService));

s.SelectionChanged += new EventHandler(xyz);

}

void xyz(object o, EventArgs e)

{

ISelectionService s = (ISelectionService)o;

Control c = s.PrimarySelection as Control;

aaa h = (aaa)Control;

abc("bbb xyz " + h.ToString() + " " + c.ToString());

if (c != null)

{

abc("bbb xyz if " + c.ToString());

h.a = c;

h.Invalidate();

}

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

 

a.txt

aaa Constructor

bbb Constructor

bbb Initialize

CanExtend aaa1 [aaa]

CanExtend Form1 , Text: Form1

CanExtend true

OnPaint

CanExtend textBox1 , Text: textBox1

CanExtend true

CanExtend button1 , Text: button1

CanExtend true

bbb xyz aaa1 [aaa] aaa1 [aaa]

bbb xyz if aaa1 [aaa]

OnPaint

Geta1  textBox1, Text: textBox1 textBox1 , Text: textBox1

Seta1 hell textBox1 , Text: textBox1 textBox1 , Text: textBox1

Seta1 =hell textBox1 , Text: textBox1

Geta1 hell textBox1 , Text: textBox1 textBox1 , Text: textBox1

bbb xyz aaa1 [aaa] button1 , Text: button1

bbb xyz if button1 , Text: button1

OnPaint

Seta1 bye button1 , Text: button1 button1 , Text: button1

Seta1 =bye button1 , Text: button1

Geta1 bye button1 , Text: button1 button1 , Text: button1

 

The optimum approach to comprehend the above program is to run it, and to incorporate a TextBox and a Button control. Thereafter, incorporate the control aaa. Then, assign a value of 'bye' to the property value a1 of the button and a value of 'hell' to the property value a1 of the textbox. This is displayed in the screens 4.16 and 4.17.

 

Screen 4.16

Screen 4.17

 

The control embodies a mechanism for storing the values of the property a1 for each control placed on the form. It also displays the value of the property a1, which is contingent upon the control that is activated.

 

A HashTable object is created in the constructor of the control class aaa. A HashTable class stores values on a key.

 

First, the aaa object constructor gets called, which is followed by the constructor of the Designer class bbb. The HashTable object ht is used by the control to store the value of the property a1 of each control that is placed on the form. This object stores the value of the property using a key, which happens to be the control itself. Our control is tagged as possessing both, an Extender property, as well as a Designer attribute.

 

At design-time, the events do not get called when triggered. Thus, a click on the button at design-time, does not result in a call to the click event, unlike what occurs during run-time. The same rule applies to the Extender property. For it to function in a manner akin to its working during run-time, we need to implement our own designer, which can keep track of the events that occur.

 

Thus, implementation of design-time Extender properties is a far more arduous task than that of run-time properties. The CanExtend function does not get called first. This honor is bestowed upon the Initialize function. The base class function gets called first. Thereafter, the GetService function is employed to retrieve the ISelectionService interface. Then, the event SelectionChanged is associated with the function xyz, which gets called each time the active control is altered.

 

The first function to be called from the aaa class is CanExtend. Since there are four controls, this function gets called four times. However, the 'if' statement will evaluate to a value of true only thrice, since the condition necessitates the object to be a control, and not aaa itself. Resultantly, all controls, except control aaa, shall have the Extender property a1.

 

Now, when a new control is brought in, the above three functions get called.  Then, the event handler gets called, since the new control gets activated and the selection undergoes modification.

 

The Event Handler calls the xyz function because the new control has been selected. Now, to access the selected control, the PrimarySelection property from the ISelectionService, or the Control, or the Component property from the ControlDesigner class, is used.

 

Since the control c does not have a value of null, the control a in class aaa is set to the active control. Thus, whenever the aaa object needs to identify the currently selected control, it uses the object 'a' for this purpose. The control is then invalidated for the OnPaint function to be called.

 

In the OnPaint function, the control is redrawn, and the value of the property a1 of the active or selected control is displayed. For this purpose, a Rectangle r that represents the size of the control in the form, is created using the property ClientRectangle. Thereafter, the Pen fills up the rectangle with the current foreground color.

 

A routine check is carried out to identify the active or selected control, although this may not be absolutely necessary. Then, the value of the property is determined.

 

We want you to examine the Seta1 function first. We are printing out the value of the variable v, which is the value of the property a1. Normally, the active control and c are the same each time. In our case, it is the control textbox that is currently active. Then, using the indexer of the HashTable class with the currently selected control c, the property value v is stored.

 

In the Get property, the same indexer is then used to retrieve the value of the property. This implies that in the Geta1 function, the parameter c is used as the indexer to retrieve the property value. This parameter c is the currently selected control, which is passed to the function. Since the controls are unique, it can be used as an indexer or a hash value to store the individual properties.

 

Reverting back to the OnPaint function, the value of the property is retrieved using the active control 'a'. Then, the property is verified to ascertain if it is null or not. If it has a length larger than zero, then the value is rendered in the control.

 

The next example demonstrates how we can elicit design-time feedback from the Designer.

 

a.cs

using System;

using System.IO;

using System.Collections;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Drawing;

using System.Windows.Forms;

using System.Windows.Forms.Design;

[ProvideProperty("a1",typeof(Control)),Designer(typeof(bbb))]

public class aaa : Control, IExtenderProvider

{

Hashtable ht;

public Control a;

public aaa()

{

abc("Constructor");

ht = new Hashtable();

}

bool IExtenderProvider.CanExtend(object t)

{

abc("CanExtend " + t.ToString());

if (t is Control && !(t is aaa))

{

return true;

}

return false;

}

public string Geta1(Control c)

{

abc("Geta1 " + c.ToString());

string t = (string)ht[c];

if (t == null)

{

t = string.Empty;

}

return t;

}

void Ent(object s, EventArgs e)

{

a = (Control)s;

abc("Ent " + a.ToString());

Invalidate();

}

void Lea(object s, EventArgs e)

{

abc("Lea " + a.ToString() + " " + s.ToString());

if (s == a)

{

abc("Lea if" + a.ToString() + " " + s.ToString());

a = null;

Invalidate();

}

}

public void Seta1(Control c, string v)

{

abc("Set "  + c.ToString());

if (v == null)

{

v = string.Empty;

}

if (v.Length == 0)

{

abc("Set if " + c.ToString());

ht.Remove(c);

c.Enter -= new EventHandler(Ent);

c.Leave -= new EventHandler(Lea);

}

else

{

abc("Set else " + c.ToString());

ht[c] = v;

c.Enter += new EventHandler(Ent);

c.Leave += new EventHandler(Lea);

}

if (c== a)

{

abc("Set ==" + a.ToString() + " " + c.ToString());

Invalidate();

}

}

protected override void OnPaint(PaintEventArgs pe)

{

base.OnPaint(pe);

Rectangle r = ClientRectangle;

Pen p = new Pen(ForeColor);

pe.Graphics.DrawRectangle(p, r);

if (a != null)

{

string t = (string)ht[a];

if (t!= null && t.Length > 0)

{

Brush b= new SolidBrush(ForeColor);

pe.Graphics.DrawString(t, Font, b, r);

}

}

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

public class bbb : ControlDesigner

{

public bbb()

{

abc("bbb Constructor");

}

public override void Initialize(IComponent c)

{

abc("bbb Initialize");

base.Initialize(c);

ISelectionService s = (ISelectionService)GetService(typeof(ISelectionService));

s.SelectionChanged += new EventHandler(xyz);

}

void xyz(object o, EventArgs e)

{

ISelectionService s = (ISelectionService)o;

Control c = s.PrimarySelection as Control;

aaa h = (aaa)Control;

abc("bbb xyz " + h.ToString() + " " + c.ToString());

if (c != null)

{

abc("bbb xyz if " + c.ToString());

h.a = c;

h.Invalidate();

}

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

 

a.txt

Geta1 textBox2 , Text: textBox2

Set textBox2 , Text: textBox2

Set else textBox2 , Text: textBox2

Set ==textBox2 , Text: textBox2 textBox2 , Text: textBox2

aaa Constructor

Set System.Windows.Forms.TextBox, Text:

Set else TextBox, Text:

Set TextBox, Text:

Set if TextBox, Text:

Set w10.Form1, Text:

Set if w10.Form1, Text:

Ent Button, Text: button2

Lea Button, Text: button2 Button, Text: button2

Lea if Button, Text: button2 .Button, Text: button2

Ent TextBox, Text: textBox2

Lea TextBox, Text: textBox2 .TextBox, Text: textBox2

Lea if TextBox, Text: textBox2 .TextBox, Text: textBox2

Ent Button, Text: button2

Geta1 button2 [.Button], Text: button2

 

The code in the bbb or designer class remains unchanged, but significant additions have been made to the control class aaa. The earlier program did not extend the Extender properties to the run-time phase, since these properties were available only during the design-time process.

 

The control aaa performs its routine chores when initiated into the form. Eventually, it sets the property a1 for the button and the textbox. This has been witnessed in the earlier program. However, presently, when the program is executed and the textbox or button is clicked on, the property a1 gets displayed in the control, as depicted in screen 4.18. The control now has run-time support too.

 

Screen 4.18

Screen 4.19

 

In order to understand as to how it functions, we examine the file a.txt. We have not depicted the entire a.txt file since most of it remains unaltered.

 

The Set function gets called when the property a1 of the textbox control is to be changed. Here, the value of the property is held in the string parameter v, and the active control is accommodated in the first parameter c. The property v is then checked to see if it is null. If this is the case, then it is set to an empty string. Then, the length of the property is checked to see if it is nil, which in our case is not true. As an outcome, the else block gets called. The hash table ht is set to store the value of the property a1 using the control as a hash key.

 

For run-time support, the Enter and Leave events of the control are associated with the function Ent and Lea, respectively. These events are set by the control. Thus, each time the textbox is entered, the function Ent gets called, and when it is exited, the Lea function of this control gets called.

 

Please note that the control gets deactivated the moment some other control gets selected. Therefore, first the Ent function of the other control gets called, and then, the Lea function of the deactivated control gets summoned. This is how the events of run-time, and not that of the design-time, can be tracked.

 

The Ent and Lea functions get called only at run-time. They have absolutely nothing to do at design-time. In the Ent function, the control named 'a' needs constant updating to enable storing of the currently selected control. This is the same task that it performed at design-time. The parameter s that is passed to this function has the newly selected control.

 

In the new control, the Invalidate function must be called to ensure that the OnPaint function gets called. No fresh code is added in the OnPaint function. So, it performs the same tasks as described earlier.

 

In the Lea function, the 'if' condition will be true, since the active control stored in 'a' and the control that has just been exited from, happen to be one and the same. The active control is made null. Moreover, the control aaa is invalidated since the previous control has been deactivated and the property value a1 need not be displayed any longer.

 

If the length of the property is zero, the control must be eliminated. The Ent and Lea functions are called for this purpose. The -= syntax is used in the Set function to inhibit these functions from being called for the control. This occurs because now it is devoid of the property value a1. This merely makes our programs run at a faster pace.

 

Also, if the active control is the same as the control that is passed, the control gets invalidated. Bear in mind that the bbb constructor does not get called at run-time. It only gets called at design-time.

 

This completes our elucidation of how to incorporate run-time support for the Extender properties.

 

Advanced Designers

 

Close all the open applications and start anew by creating a simple Windows Application project. We have named this project as w6. Click on the View menu and incorporate the Solutions Explorer as shown in screen 4.20. The explorer exhibits the relevant files that are being used in the solution or the project.

 

Screen 4.20

Screen 4.21

 

Clicking on the references item would spring up a list of dlls that are being referred to by the project. We would be addressing these references in a short while from now. Click with the right-mouse button on Form1.cs in the explorer window. This displays a menu, as shown in screen 4.21.

 

The menu has two options that are of considerable significance. The first one is named View Code. When it is clicked on, it brings up the code painter. The second one is called View Designer, which elicits the Designer screen. This is the one that is normally perceptible. Thus, every item has two modes, viz. a Code mode and a Designer mode. The Designer mode facilitates interaction with the form and acts as the User Interface.

 

However, right clicking on the file AssemblyInfo.cs  as in screen 4.22, does not engender the menu option of View Design. This indicates that the file is devoid of any Designer Surface or User Interface. However, the creators of this software cannot have the sole discretion of determining the UI that a control may require. Therefore, our endeavour in this section is to enlighten you with the process of creating a unique UI or Designer for a control.

 

Screen 4.22

 

a.cs

using System;

using System.IO;

using System.ComponentModel;

using System.ComponentModel.Design;

public class aaa : Component

{

public aaa()

{

sss.abc("aaa Constructor");

}

}

public class sss

{

public static void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

 

a.txt

aaa Constructor

 

A brand new control can be freshly added to the toolbox by pursuing the steps cited in the earlier chapters. However, in the case of the aaa control, it refuses to be inducted into the form. Instead, it positions itself at the bottom of the screen, as is seen in screen 4.23. This occurs because the class aaa is derived from Component, and not from UserControl.

 

Screen 4.23

 

The class UserControl is ultimately derived from Control, which in turn is derived from Component. A control that does not require the user to interact with it, derives itself from Component. The aaa constructor gets called once, when the control is placed in the form.

 

Screen 4.23 also brings out the fact that in the References section, the system has already added a file named a.dll, which is the assembly file. Thus, this can effectively be perceived to be an alternative approach to adding a control to the form.

 

The Visual Studio.Net framework merely adds the line "private aaa aaa1;" in order to create an instance variable. Then, in the function InitializeComponent, it introduces the line "aaa1 = new aaa();" in order to create a new instance of the control aaa1. No position parameters need be initialized at this stage.

 

Now, surge ahead with us and follow the ensuing instructions meticulously. First, remove the aaa control from the toolbox and the solution explorer. Then, delete the files Form1.cs and Assemblyinfo.cs from the Solution Explorer by selecting them, clicking on the right mouse button, and then, clicking on the delete option. The Solution Explorer window will appear, as in screen 4.24.

 

Screen 4.24

Screen 4.25

 

Then, select the project and click on the Add option to add a file to the solution. We have not selected the solution, since a solution is constituted of projects. Finally, choose the last option named Add Class, as is evident in screen 4.25.

 

Screen 4.26

 

The second pane of the window reveals a dialog box with the class selected. The name of the file is Class1.cs.

 

Screen 4.27

 

Clicking on the Open dialog button transports us to screen 4.27. In the Solution Explorer, a new file called Class1.cs has been added and the cursor in the Code Designer starts blinking. The machine-generated code is very straightforward. It is as follows:-

 

using System;

namespace w6

{

public class Class1

{

public Class1()

{

}

}

}

 

Since the project is named w6, all the code is placed in the w6 namespace. Only a solitary 'using' statement exists. The name of the class is Class1 and it comprises of an empty constructor. A right click on the item Class1 in the Solution Explorer divulges the absence of the View Designer menu item. The toolbox also flaunts only a very few items.

 

Screen 4.28

Screen 4.29

 

Modify the code to derive the class Class1 from the control class aaa.

 

namespace w6

{

public class Class1 : aaa

{

 

Now, click on the menu Build and then on the menu option Build Solution, to arrive at screen 4.30. The screen displays an error since the assembly containing the class aaa is conspicuous by its absence.

 

Screen 4.30