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

 

To elude this error, click on the references item. Then, click the right mouse button to arrive at the screen 4.31.

 

Screen 4.31

Screen 4.32

 

Here, select the Add References option. You will come to screen 4.32, where a gigantic list of assemblies is perceptible.

 

Now, in order to introduce our own assembly, click on the browse button, and thereafter, navigate to the folder c:\a1. Once this has been accomplished, select the file a.dll and Click on OK.

 

The assembly 'a' gets added to the references item, as is visible in screen 4.33.

 

Screen 4.33

Screen 4.34

 

Build the application by clicking on the menu Build, and then, on the menu option Build. An uncommon error message is flashed, which talks about there being 'no entry point' in the application. Since Form1 has been removed, no entry point exists. Hence, there is no wisdom in building the application. The error is displayed in screen 4.34.

 

However, when the right mouse button is clicked on the file Class1.cs in the Solution Explorer, an additional menu item named View Designer comes into view, as displayed in screen 4.35. (If this option does not appear, then close the code window and right click on Class1.cs).

 

Screen 4.35

Screen 4.36

 

If you click on this menu option, it will prompt a display  of a screen, as shown in screen 4.36. This corroborates the fact that every control has a default designer associated with it. Subsequent to this, activate the toolbox by clicking on View, followed by Toolbox. Then, bring in a button, to arrive at screen 4.37.

 

Screen 4.37

 

Everything else works conventionally. The code view now displays the following code:-

 

using System;

namespace w6

{

public class Class1 : aaa

{

private System.Windows.Forms.Button button1;

private void InitializeComponent()

{

this.button1 = new System.Windows.Forms.Button();

//

// button1

//

this.button1.Location = new System.Drawing.Point(194, 104);

this.button1.Name = "button1";

this.button1.TabIndex = 0;

this.button1.Text = "button1";

}

public Class1()

{

//

// TODO: Add constructor logic here

//

}

}

}

 

The above code, inclusive of the comments, has been written by the framework. The next task on hand is to create a designer.

 

The class Component uses a designer called ComponentDesigner. Therefore, the class ccc, which derives from the class Component, also inherits this designer.

 

Now, exit from Visual Studio.Net and revert back to the control.

 

a.cs

[Designer(typeof(ComponentDesigner))]

public class aaa : Component

{

public aaa()

{

sss.abc("aaa Constructor");

}

}

 

Adding the Designer attribute to the class aaa is of no major significance, since the Component class is already tagged with the attribute Designer taking a type ComponentDesigner. Thus, effectively, the attribute is merely being replicated. The code of the class sss is not shown, since the function abc is never subjected to any amendments.

 

a.cs

[Designer(typeof(ddd), typeof(IRootDesigner))]

public class aaa : Component

{

public aaa()

{

sss.abc("aaa Constructor");

}

}

public class ddd : ComponentDesigner

{

public ddd()

{

sss.abc("ddd Constructor");

}

}

 

a.txt

aaa Constructor

ddd Constructor

 

The designer class ddd is derived from the class ComponentDesigner. No errors were expected. However, the screen 4.38 has a different story to tell. This occurs when the project name on the start page in Visual Studio.Net is clicked upon. Nevertheless, the constructors of both, the control class aaa and the designer class ddd, get called.

 

Screen 4.38

 

The fault lay in the Designer attribute. The type assigned to the attribute was the interface IRootDesigner. However, class ddd is incapable of implementing this interface IRootDesigner. Hence, an invalid cast exception is thrown, because the system tries to cast the designer class to a IRootDesigner, and the cast operation fails. The second parameter enables more than one designer type to be attached to the control class.

 

a.cs

using System;

using System.IO;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Windows.Forms.Design;

using System.Windows.Forms;

[Designer(typeof(ddd), typeof(IRootDesigner))]

public class aaa : Component

{

public aaa()

{

sss.abc("aaa Constructor");

}

}

public class ddd : ComponentDesigner , IRootDesigner

{

public ddd()

{

sss.abc("ddd Constructor");

}

ViewTechnology [] IRootDesigner.SupportedTechnologies

{

get

{

sss.abc("ddd SupportedTechnologies");

ViewTechnology [] v = new ViewTechnology[] {ViewTechnology.WindowsForms};

return v;

}

}

Control v;

object IRootDesigner.GetView(ViewTechnology t)

{

sss.abc("ddd GetView " + t.ToString());

v = new Control();

return v;

}

}

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

ddd Constructor

ddd SupportedTechnologies

ddd SupportedTechnologies

ddd GetView WindowsForms

 

Screen 4.39

 

The designer class ddd now implements the interface IRootDesigner. This interface has two members, viz. a property called SupportedTechnologies and a method named GetView. The property SupportedTechnologies is a read-only property. It returns an array of ViewTechnology objects. The ViewTechnology enum has only two members, viz. Passthrough and WindowsForms.

 

This property is summoned to acquaint the framework with the technology being used for the User Interface. The Passthrough enum signifies a mode of display for the designer, which will be an ActiveX control (Old is gold!). However, Microsoft attaches a caveat that this mode may not be available on other platforms that implement Visual Studio.Net.

 

However, in the program, WindowsForms is employed, thereby implying that a Windows Forms control would provide the display for the designer. As an outcome, a control shall be eventually displayed to the user in the document window.

 

Now, inject a minor variation in the above code.

 

sss.abc("ddd SupportedTechnologies");

ViewTechnology [] v = new ViewTechnology[] {};

return v;

 

Here, instead of returning a single enum in an array, return an empty array. This brings about an error, as displayed in screen 4.40. The error message clearly indicates that the designer has employed a UI technology that is beyond the comprehension of Visual Studio.Net.

 

Screen 4.40

 

The function GetView gets called right after the property SupportedTechnologies. This function is passed the same value of the ViewTechnology enum that has been specified in the property SupportedTechnologies WindowsForms. An object of type Control must be supplied, since this object will provide a user interface to the user.

 

The data type of the function GetView is not a Control. It is a more generic object, since nobody can actually anticipate the kind of user technology that would be in vogue in the near future. Therefore, it would be unreasonable to confine ourselves to using a Control object as a user interface.

 

Since the above program shows a Control as the user interface, no other control can be dragged and dropped into the designer from the toolbar.

 

a.cs

using System;

using System.Drawing;

using System.IO;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Windows.Forms.Design;

using System.Windows.Forms;

[Designer(typeof(ddd), typeof(IRootDesigner))]

public class aaa : Component

{

}

public class ddd : ComponentDesigner , IRootDesigner

{

ViewTechnology[] IRootDesigner.SupportedTechnologies

{

get

{

ViewTechnology [] v = new ViewTechnology[] {ViewTechnology.WindowsForms };

return v;

}

}

ccc v;

object IRootDesigner.GetView(ViewTechnology t)

{

v = new ccc(this);

return v;

}

}

class ccc: Control

{

public ccc(ddd designer)  {

sss.abc("vvv Constructor");

}

protected override void OnPaint(PaintEventArgs e)  {

sss.abc("vvv OnPaint");

base.OnPaint(e);

Graphics g = e.Graphics;

Brush b = new SolidBrush(ForeColor);

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

}

}

 

a.txt

vvv Constructor

vvv OnPaint

 

In the above control, the function GetView is called because it determines the designer surface. An object that looks like class ccc, which in turn is derived from the Control class, is specified as the return value. Whenever the window is to be redrawn, the OnPaint function gets called. The original OnPaint function has been overridden by the freshly introduced function. This function merely calls the original function and it displays 'Vijay1', as is evident in screen 4.41.

 

Screen 4.41

 

Thus, it is the code in the class that finally decides on the UI that the user shall see.

 

a.cs

class ccc: Control

{

float x1 = 0,y1 = 0;

public ccc(ddd designer)

{

}

protected override void OnMouseDown(MouseEventArgs e)

{

base.OnMouseDown(e);

x1 = (float) e.X;

y1 = (float) e.Y;

sss.abc(x1.ToString() + " " + y1.ToString() );

Invalidate();

}

protected override void OnPaint(PaintEventArgs e)

{

base.OnPaint(e);

Graphics g = e.Graphics;

Brush b = new SolidBrush(ForeColor);

PointF p = new PointF(x1,y1);

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

}

}

 

a.txt

190 123

116 199

 

The focal point of the above code is the control class ccc. Each time we click in the window, the MouseDown event gets fired, which in turn calls the function OnMouseDown. This function is passed a MouseEventArgs parameter. The MouseEventArgs parameter has two members X and Y, which denote the Mouse click location in pixels.

 

Screen 4.42

 

These values are stored in two float variables named x1 and y1, and subsequently, written to the file a.txt. Thereafter, the Invalidate function is called to enforce the calling of the OnPaint function.

 

An object that looks like PointF is created and the x1 and y1 instance variables are passed as parameters to the constructor. A PointF class is a Point class, but with float members.

 

The DrawString function uses the PointF parameter to place the string instead of the ClientRectangle object. Therefore, Vijay1 follows the mouse click in the designer window. Thus, the coder can manipulate the user's interaction with the designer with consummate ease.

 

a.cs

class ccc: Control

{

int ii = 0; Point p1,p2;

public ccc(ddd designer)

{

}

protected override void OnMouseDown(MouseEventArgs e)

{

base.OnMouseDown(e);

if ( ii == 0)

{

ii = 1;

p1 = new Point(e.X, e.Y);

}

else

{

ii = 0;

p2 = new Point(e.X, e.Y);

Invalidate();

}

}

protected override void OnPaint(PaintEventArgs pe)

{

base.OnPaint(pe);

Pen p = new Pen(Color.Red);

Graphics g = pe.Graphics;

g.DrawLine(p,p1,p2);

}

}

 

Screen 4.43

 

The screen 4.43 displays the designer with a line running through it. This line is drawn by clicking on the two endpoints of the screen.

 

The above example amply demonstrates as to how the UI of the control can be effortlessly manoeuvred.

 

Initially, three instance variables, viz. an int called ii and two points named p1 and p2, are created. Then, in the OnMouseDown function, the 'if' statement determines the value of the variable ii. The first time when we click in the window, the variable ii has a value of zero.

 

The 'if' statement  changes this value to 1. When we click in the window for the second time, the 'else' block gets called, wherein the value of the variable ii is reverted back to zero again. Thus, the value of ii oscillates between the values  zero and one. Thus, on every even click of the mouse, the 'if' statement gets called and on every odd click, it is the 'else' statement that gets called.

 

In the first round itself, a new Point object p1 is created, utilizing the values contained in the X and Y members of the MouseEventArgs parameter. In the 'else' block in the second round, another Point object p2 is created with the new coordinates of the latest mouse click location in the window.

 

Finally, the Invalidate function is called to draw the Line in the designer.

 

The OnPaint function, which follows the Invalidate function call, creates a new Pen object and employs the DrawLine function to draw a line. The DrawLine function exploits three parameters to draw the required line. These parameters are the Pen, the starting point and the ending point.

 

a.cs

using System;

using System.Drawing;

using System.IO;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Windows.Forms.Design;

using System.Windows.Forms;

using System.Drawing.Design;

[Designer(typeof(ddd), typeof(IRootDesigner))]

public class aaa : Component

{

}

public class ddd : ComponentDesigner , IRootDesigner

{

ViewTechnology[] IRootDesigner.SupportedTechnologies

{

get

{

ViewTechnology [] v = new ViewTechnology[] {ViewTechnology.WindowsForms };

return v;

}

}

ccc v;

object IRootDesigner.GetView(ViewTechnology t)

{

v = new ccc();

return v;

}

public override void Initialize(IComponent c)

{

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

base.Initialize(c);

IDesignerHost h = (IDesignerHost)GetService(typeof(IDesignerHost));

sss.abc("IDesignerHost " + h.ToString());

h.LoadComplete += new EventHandler(pqr);

}

void pqr(object s, EventArgs e)

{

sss.abc("pqr");

IDesignerHost h = (IDesignerHost)s;

h.LoadComplete -= new EventHandler(pqr);

IToolboxService t = (IToolboxService)GetService(typeof(IToolboxService));

sss.abc(t.ToString());

string s1 = h.RootComponentClassName;

sss.abc(s1);

ToolboxItem i = new ToolboxItem();

i.TypeName = "Vijay";

i.DisplayName = "Sonal";

t.AddLinkedToolboxItem (i , "Mukhi", h );

}

}

public class ccc : Control

{

}

 

a.txt

Initialize Class1 [aaa]

IDesignerHost Microsoft.VisualStudio.Designer.Host.DesignerHost

pqr

Microsoft.VisualStudio.Designer.Service.ToolboxService

w6.Class1

 

A new toolbox item called mukhi is added to the toolbar. Clicking on this toolbox flashes two toolbox items, viz. Pointer and Sonal, as is evident in screen 4.44. Before we begin expounding the above program, we would like to illustrate this effect in Visual Studio.Net.

 

Screen 4.44

 

Now, let us progress on to the program.

 

The class ccc contains no code whatsoever. The third function to be called is the regular Initialize function. This function is passed an IComponent parameter, which as before, is the aaa Control class.

 

This also reflects the name Class1, which is the name of our class in Visual Studio.Net.

 

In accordance with appropriate programming procedures, the original Initialize function is called first. Then, the GetService function is employed to retrieve the IDesignerHost interface. This interface works with the designer and provides access to the current state of the designer.

 

The next task is to determine whether the designer has completed loading the control or not. This is inescapable because changes can be effected to the designer only after the control has been completely loaded. For this purpose, the event LoadComplete is trapped. This event calls the function pqr whenever the designer concludes loading the control. The object parameter passed to the pqr function is actually an IDesignerHost interface. Once the control is loaded, the function pqr first gets detached from the LoadComplete event.

 

Thereafter, the focus shifts to working with the toolbox. The IToolboxService interface is used in order to gain access to the toolbox. The property RootComponentClassName provides the full name of the control, i.e. the name of the project w6, followed by a dot, followed by the name of the class Class1.

 

A new ToolboxItem object  called i is created and its DisplayName is set to sonal. Hence, we observe sonal in the toolbox. The function AddLinkedToolboxItem is then used to add a toolbox item named i. The toolbox item is passed as the first parameter. The second parameter is the name of the toolbox mukhi. The third  parameter is the IDesignerHost interface object h.

 

The Linked Tool has a special property. It is linked only to a particular designer and when the solution that employs the project is closed, it is automatically eliminated. The linked tool requires the fully qualified name of the class that the designer is used for. This name resides in the property RootComponentClassName, which becomes available only after the designer is completely loaded.

 

The question that comes to the fore is that, since it is easier to use customize toolbox option to add a control to the toolbox, what is the rationale behind our using a roundabout method? The less a user has to learn about the internal workings of Visual Studio, the better it is. Afterall, basic objective of employing an RAD tool ( Rapid Application Development Tool) is that the user should become productive without having to learn too much.

 

We have presented diverse techniques of achieving the same objective. It is for you to decide which one of these suits your purpose the best.

 

a.cs

void pqr(object s, EventArgs e)

{

sss.abc("pqr");

IDesignerHost h = (IDesignerHost)s;

h.LoadComplete -= new EventHandler(pqr);

IToolboxService t = (IToolboxService)GetService(typeof(IToolboxService));

ToolboxItem i1 = new ToolboxItem();

i1.TypeName = "Vijay1";

i1.DisplayName = "Sonal1";

t.AddToolboxItem(i1,"zzz");

CategoryNameCollection c = t.CategoryNames;

sss.abc(c.Count.ToString());

for ( int i = 0 ; i< c.Count ; i++)

sss.abc(c[i]);

}

 

a.txt

pqr

2

zzz

Mukhi

 

In the above example, a new ToolboxItem object is created and the function AddToolboxItem is used to add an item to the new category zzz. Then, the property CategoryNames is pressed into action to return a CategoryNameCollection object. This collection object contains the toolbars that are to be added, viz. Mukhi and zzz.

 

 

Then, by iterating through the collection class using a 'for' loop, the different toolbar categories can be printed out. The indexer comes in handy at this stage. There are functions available in the IToolboxService that indicate the currently selected category,  together with the number of items in the category.

 

a.cs

[ToolboxItemFilter ("System.Windows.Forms", ToolboxItemFilterType.Require)]

public class ddd : ComponentDesigner , IRootDesigner

 

Screen 4.45

 

Before you restart Visual Studio.Net, scrutinize screen 4.45 heedfully. In the components category in the toolbox, you shall encounter a large number of items.

 

The above attribute is added to the designer class ddd. The screen display is shown in screen 4.46. All the items in the Components category have been permanently grayed out.

 

The second parameter to the attribute is the name of the enum, i.e. ToolboxItemFilterType. This enum takes four different values, viz. Allow, Custom, Prevent and Require. The Require option ensures that only those controls that belong to the namespace  System.Windows.Forms, get displayed.

 

Screen 4.46

 

Thus in screen 4.46, the Components category is filled with items but, since the category items do not belong to the namespace System.Windows.Forms, they are not noticeable.

 

a.cs

[ToolboxItemFilter("System.Windows.Forms", ToolboxItemFilterType.Prevent)]

 

Screen 4.47

 

The enum value of Prevent ensures that only those items in the toolbox that relate to the namespace System.Windows.Forms, get displayed. As per screen 4.47, the Windows Forms category has all its items disabled, whereas the Category tab Components has all its items enabled.

 

a.cs

using System;

using System.IO;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Drawing;

using System.Windows.Forms;

using System.CodeDom;

using System.ComponentModel.Design.Serialization;

using System.Collections;

using System.Drawing.Design;

class eeed : ComponentDesigner

{

protected override void PreFilterProperties(IDictionary p)

{

sss.abc("eeed PreFilterProperties");

base.PreFilterProperties(p);

p["Box1"] = TypeDescriptor.CreateProperty(GetType(),"Box",typeof(Rectangle));

}

public Rectangle Box

{

get

{

sss.abc("eeed Box get");

return new Rectangle(10,10,200,150);

}

set

{

Rectangle r = (Rectangle) value;

sss.abc("eeed Box set " + r.ToString());

}

}

}

class ddd : ComponentDesigner, IRootDesigner, IToolboxUser

{

vvv v;

ViewTechnology[] IRootDesigner.SupportedTechnologies

{

get

{

sss.abc("ddd SupportedTechnologies");

return new ViewTechnology[] {ViewTechnology.WindowsForms};

}

}

object IRootDesigner.GetView(ViewTechnology technology)

{

sss.abc("ddd  GetView");

v = new vvv(this);

return v;

}

bool IToolboxUser.GetToolSupported(ToolboxItem item)

{

return true;

}

void IToolboxUser.ToolPicked(ToolboxItem i)

{

sss.abc("ddd ToolPicked");

v.abc(i);

}

class vvv: Control

{

eee s ;

ddd d;

public vvv(ddd d1)

{

sss.abc("ddd Constructor");

d = d1;

}

public void abc(ToolboxItem i)

{

sss.abc("vvv abc");

IDesignerHost h = (IDesignerHost )d.GetService(typeof(IDesignerHost));;

IComponent[] c1 = i.CreateComponents(h);

sss.abc(c1.Length.ToString());

Type t = c1[0].GetType();

sss.abc(t.ToString());

sss.abc(c1[0].ToString());

if ( t.ToString() == "eee")

{

Rectangle b = new Rectangle();

b.X = 50;

b.Y = 50;

b.Width = 100;

b.Height = 100;

s = (eee)c1[0] ;

Invalidate();

sss.abc("vvv abc 1");

PropertyDescriptor b1 = TypeDescriptor.GetProperties(s)["Box"];

sss.abc("vvv abc 2");

b1.SetValue(s, b);

}

}

protected override void OnPaint(PaintEventArgs pe)

{

sss.abc("vvv OnPaint");

base.OnPaint(pe);

if ( s != null)

s.pqr(pe.Graphics);

else

{

Graphics g = pe.Graphics;

Brush b = new SolidBrush(ForeColor);

g.DrawString("Double Click on a Control", Font , b , ClientRectangle);

}

}

}

}

[Designer(typeof(ddd), typeof(IRootDesigner))]

public class aaa : Component

{

}

[Designer(typeof(eeed))]

public class eee: Component

{

Color c = SystemColors.WindowText;

public Color Col

{

get

{

sss.abc("eee Col get");

return c;

}

set

{

c = value;

}

}

public void pqr(Graphics g)

{

sss.abc("eee pqr");

using (Pen p = new Pen(Col))

{

g.DrawEllipse(p, new Rectangle(1,1,50,75));

}

}

}

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

ddd SupportedTechnologies

ddd SupportedTechnologies

ddd  GetView

ddd Constructor

vvv OnPaint

ddd ToolPicked

vvv abc

1

eee

eee1 [eee]

vvv abc 1

eeed PreFilterProperties

vvv abc 2

eeed Box get

eeed Box set {X=50,Y=50,Width=100,Height=100}

vvv OnPaint

eee pqr

eee Col get

eeed Box get

 

The Class1 class is derived from the class aaa, as usual. The code present in the file Class1.cs is repeated here for your convenience.

 

Class1.cs

using System;

 

namespace w7

{

public class Class1 : aaa

{

private void InitializeComponent()

{

}

public Class1()

{

}

}

}

In the above control, two entities co-exist: a custom designer and a user-defined control. First, launch the control eee in the toolbox, as shown in screen 4.48. A message is displayed in the window.

 

Screen 4.48

Screen 4.49

 

When you double click on the eee object, an ellipse will be seen, as shown in screen 4.49. Next, click on the tab Class1 code to arrive at the code that is generated, as shown in screen 4.50.

 

Screen 4.50

 

The class aaa derives from the class Component. It does not contain any code at all. Then, a custom designer of type ddd and IrootDesigner is added. The class ddd extends not only ComponentDesigner and IrootDesigner, but also the interface IToolboxUser.

 

As usual, the function SupportedTechnologies gets called twice and the GetView function gets called once. There is nothing anomalous or queer here. In the GetView function, the class vvv derived from Control, is saved in an instance variable v. The class vvv determines the appearance of the designer's user interface.

 

The vvv or Control class may harbour a desire to access the designer class ddd. Therefore, the UI class vvv is provided with the handle or the reference to the  designer class ddd, which is stored in an instance variable d. Now, Visual Studio.Net calls the OnPaint function of the UI class ddd, so that the user is able to see something in the designer.

 

As is generally the case, the base class function is called first. Then, the value of a variable v is inspected to confirm that it is of data type eee. The class eee is the control class that the assembly carries with it, along with the designer class ddd. The variable s stores the current control, which merely draws an ellipse. If the value is null, a message is displayed calling upon the user to double click on the icon eee. However, if the value is not null, the pqr function in the control class eee, is called instead.

 

When the eee control is double clicked in the toolbar, the function ToolPicked gets called. This is the sole rationale behind deriving from the interface IToolboxUser. This interface has only two functions, viz. GetToolSupported and ToolPicked.

 

The designer class entrusts the management of UI issues to the class vvv, which handles the double click and steers the control on to the screen. You may recall that the vvv object is stored in an instance variable v. The abc function is called to handle the double click. This function is supplied with the ToolboxItem parameter i.

 

In the abc function, it is the IDesignerHost interface that is accessed first. Since only one item is selected or double-clicked on, the CreateComponents function returns an array of a single Component object, which is the eee control. The ToolboxItem object provides  access to the control that has been selected.

 

Now, let us place the control on the screen.

The rectangle object b is used to place the ellipse as per the random values to which the four properties of X, Y, Width and Height are initialized. The class object contains a GetType function that provides the runtime type of the class. Even though the function CreateComponents returns an array of IComponent objects, their runtime data types are different.

 

The ToString versions of both, the type and the actual control, are displayed. The error checks prevent the remaining code from being executed, unless the runtime data type is an eee object. If it is any other type, such as a button, then it performs some other activities. Thus, the designer pays considerable heed while displaying eee objects. The Invalidate function invalidates or calls the OnPaint function, which results in the drawing of the ellipse.

 

The next task is to determine the value of a property called Box. This property is in the control eee, since the eee object is supplied as a parameter to the function GetProperties.

 

The designer class for the control eee now revs into action and the function PreFilterProperties gets called. Here, the property called Box is created under the hash value Box1.  The SetValue function of the property descriptor b1 is then utilized to set the value of the Box property to the new rectangle b that has recently been created. 

 

Had the properties window been active, the value of the property would have been the value of the rectangle b. At this stage, the Get accessor of the Box property gets called twice, wherein the return value is some rectangle value. Then, the Set accessor of the box gets called with the value of the rectangle as 50,50. This is because these are the values assigned to the property of the Box. This Box property is not called by our code, but is called internally by the system.

 

These values are required to be saved in the Set accessor. We have skipped this step here. But we assure you that they would surely be incorporated in the future versions. Finally, the OnPaint function gets called. Since the value of the object s is not null, the pqr function in the control object eee gets called.

 

In the pqr function, a Pen object p is created using the color property Col, which takes the assistance of the variable c to maintain state. Then, an ellipse is drawn using a Rectangle that has been created earlier.

 

The next call of duty beckons us to employ the Box property to determine the size of the ellipse. We have decided to call a halt here since the program has started becoming too convoluted. The code generated by the system appears as follows:-

 

using System;

namespace t7

{

public class Class1 : aaa

{

private eee eee1;

private void InitializeComponent()

{

this.eee1 = new eee();

//

// eee1

//

this.eee1.Box = new System.Drawing.Rectangle(10, 10, 200, 150);

this.eee1.Col = System.Drawing.SystemColors.WindowText;

}

public Class1()

{

}

}

}

 

Since we have a single eee object, an instance variable eee1 gets created. In the InitializeComponent function, a new instance of the class eee is created. A property named Col exists in the class eee and a property named Box inhabits the designer class.

 

The code of this property is placed in the designer class for eee named eeed. The value of the Col property is the default value that has been set manually, while the value of the Box property is what is returned in the Get. These values are at great variance with what has been specified in the Set accessor.