5.

 

Writing C# Code

 

In the ensuing chapter, we shall devise user-defined controls whose primary task is to generate C# code in the code editor. In other words, the basic intent of this chapter is to incorporate controls that in turn shall add more code to the already existent code generated by the designer.

 

As usual, we set out with the smallest possible control.

 

a.cs

using System.ComponentModel;

public class aaa : Component

{

}

 

Create a new Windows Application called WindowsApplication2. The designer that is provided with Visual Studio.Net, generates the code that ensues. We shall now concede some time for discerning this code in its entirety.

 

Form1.cs

using System;

using System.Drawing;

using System.Collections;

using System.ComponentModel;

using System.Windows.Forms;

using System.Data;

namespace WindowsApplication2

{

public class Form1 : System.Windows.Forms.Form

{

private System.ComponentModel.Container components = null;

public Form1()

{

InitializeComponent ();

}

#region Windows Form Designer generated code

private void InitializeComponent()

{

this.components = new System.ComponentModel.Container();

this.Size = new System.Drawing.Size(300,300);

this.Text = "Form1";

}

#endregion

[STAThread]

static void Main()

{

Application.Run(new Form1());

}

}

}

 

Every code fragment always gets underway with the usual gang of suspects, viz. the 'using' statements. There are six such 'using' statements in all. We shall shortly fill you in on why these six namespaces are so special to the designer.

 

The namespace shares its name with our project, i.e. WindowsApplication2. Then, the code is inserted within the class Form1, which is derived from the class Form. To jog your memory, the class Form is provided as the base class, since the application requires a Windows Form user interface.

 

In the Main function, a new object is created, which is an instance of the class Form1. Then, the Run function is called from the Application property.

The constructor of the Form1 class simply calls the function InitializeComponent. This is where most of the code generated by the individual controls would be posited. The entire code is placed in the function named InitializeComponent instead of the constructor, merely to cater to a programming style quirk.

 

In this function, the instance variable components are initialized first, along with the two properties of Size and Text. The rest of the properties assume their default values. The Dispose function, which has not been depicted here, is called at the time of winding up. Region, which is a preprocessor directive, is utilized either to expand or to contract the code.

 

It is always preferred to use a RAD tool such as VisualStudio.Net, since the framework generates tons of essential, albeit mundane code. A RAD tool proves to be extremely beneficial, since it aids in easing out the tedium and monotony of writing large amounts of humdrum code.

 

When you double-click on the control aaa, the control gets placed in the lower area of the window. This is because the control has no User Interface, since it is derived from Component and not from Control or UserControl. The code painter reveals two lines of code that have been freshly inducted.

 

private aaa aaa1;

 

The first line illustrates the creation of an instance variable aaa1. The name of our control class is aaa. Resultantly, the variable or object is named as aaa1.

 

this.aaa1 = new aaa();

 

In the function InitializeComponent, an instance of the object aaa1 is created. Keep in mind that it is the designer who inserts the above two lines of code.

 

Now, we wish to write controls that would add supplementary code to the already existing code in the code painter.

 

a.cs

using System;

using System.IO;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.CodeDom;

using System.ComponentModel.Design.Serialization;

class ccc : CodeDomSerializer

{

public override object Deserialize(IDesignerSerializationManager manager, object codeObject)

{

sss.abc("Deserialize");

return null;

}

public override object Serialize(IDesignerSerializationManager m, object v)

{

sss.abc("Serialize ");

return null;

}

}

[DesignerSerializer (typeof(ccc), typeof(CodeDomSerializer))]

public class aaa : Component

{

}

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

Serialize

 

Like before, create a new project and name it as w7. Then, bring in the control aaa, which has an attribute DesignerSerializer added to it. This attribute calls upon the code painter or the designer serialization manager to bring into play the custom serializer and to also write code in the code painter. The constructor to this attribute is passed two parameters; the first parameter is the class that contains functions to serialize and deserialize, while the second parameter is the base data type of the serializer.

 

The file a.txt establishes the fact that the function Serialize actually gets called. This function educates the Code painter regarding the most appropriate code to be generated for the object aaa. The value returned is null. Therefore, the code painter does not generate any code for the control aaa.

 

The next task is to get the code painter to generate code that is specific for our control.

 

a.cs

public override object Serialize(IDesignerSerializationManager m, object v)

{

sss.abc("Serialize " + v.GetType().ToString());

CodeDomSerializer a = (CodeDomSerializer)m.GetSerializer(typeof(Component),typeof(CodeDomSerializer));

object c = a.Serialize(m, v);

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

return c;

}

 

a.txt

Serialize aaa

System.CodeDom.CodeStatementCollection

 

All the action transpires in the function Serialize. Hence, we have only delineated this function. All the residual code remains unaffected, and hence, is not displayed.

 

The Serialize function gets called with the second parameter of type aaa, which is our control. Using v, which is an object of type aaa, a CodeDOM object is created. This object is transformed into tangible code by the code painter.

 

The first parameter m of the type interface IDesignerSerializationManager, provides the interface that handles design time serialization. This class renders services to deal with objects during the process of serialization. Using the parameter m, a function named GetSerializer is called. The first parameter to this function specifies the type of object that is to be serialized. The second parameter specifies the type of serializer that is required.

 

The control class aaa is derived from the class Component. The run time data type returned from the GetSerializer function is ccc, which is the Code Serializer class. Thus, the Serializer is of type CodeDomSerializer, which would serialize a Component.

 

The object 'a' is used to call the Serialize function of the CodeDomSerializer class. The second parameter 'v' that is supplied to the function, is the aaa control. This has to be serialized.

 

The base Serialize function cannot be called directly, since it is abstract. Therefore, a serialization manager is used, which creates a CodeStatementCollection to represent the control aaa. This CodeStatementCollection object is returned. It eventually gets serialized, i.e. converted into two lines of code. Thus, using the serialization manager, the default serialization for a Component object is accomplished.

 

a.cs

public override object Serialize(IDesignerSerializationManager m, object v)

{

CodeStatementCollection c = new CodeStatementCollection();

CodeVariableDeclarationStatement d = new CodeVariableDeclarationStatement(typeof(zzz), "Vijay");

c.Add(d);

return c;

}

 

Form1.cs

public class zzz

{

}

#region Windows Form Designer generated code

/// <summary>

/// Required method for Designer support - do not modify

/// the contents of this method with the code editor.

/// </summary>

private void InitializeComponent()

{

zzz Vijay;

//

 

In the Serialize function, the CodeStatementCollection object that is to be returned, is created initially. Since the task is to insert a simple statement that creates a variable in the code painter, the class named CodeVariableDeclarationStatement is pressed into action. The constructor is passed two parameters, viz. the data type of the variable as a type, and its name Vijay as a string. Then, using the Add function, the object d is added to the Collection class. Therefore, when the control is placed in the form, a variable called Vijay of type zzz is exposed to view.

 

a.cs

public override object Serialize(IDesignerSerializationManager m, object v)

{

CodeStatementCollection c = new CodeStatementCollection();

CodeVariableDeclarationStatement d = new CodeVariableDeclarationStatement(typeof(zzz), "Vijay");

CodeExpression e = new CodeObjectCreateExpression(typeof(yyy));

d.InitExpression = e;

c.Add(d);

return c;

}

 

public class yyy

{

}

 

Form1.cs

private void InitializeComponent()

{

zzz Vijay = new yyy();

//

// Form1

//

 

After creating a variable, the next task is to initialize it. The variable Vijay is to be initialized to some instance. Therefore, the InitExpression property of type CodeExpression is put to use. A new CodeExpression object is created and the constructor is given a data type of yyy.

 

The InitExpression property is initialized to this CodeExpression object. This results in the object Vijay being initialized to a new instance of the class yyy. This function performs no error checks. Therefore, attempts to build the form in Visual Studio.Net, would result in the generation of an error.

 

a.cs

public override object Serialize(IDesignerSerializationManager m, object v)

{

CodeStatementCollection c = new CodeStatementCollection();

CodeVariableDeclarationStatement d = new CodeVariableDeclarationStatement(typeof(zzz), "Vijay");

CodeExpression e = new CodeObjectCreateExpression(typeof(yyy));

d.InitExpression = e;

CodeLinePragma p = new CodeLinePragma("sonal.txt" , 100);

d.LinePragma = p;

c.Add(d);

return c;

}

 

Form1.cs

#line 100 "sonal.txt"

zzz Vijay = new yyy();

#line default

 

The LinePragma property appends the file name and the line number to the code in the editor. The pragma #line requires two parameters, viz. a line number and a filename. In case of an error in the file, the code painter resorts to its own line numbering and file name.

The pragma option allows a modification to be effected in both, the file name and the line number, since the code is being inserted by the code painter.

 

The default parameter resets the line number to the default value of the code painter. In the above code, the LinePragma property is initialized to the new CodeLinePragma object, whose constructor is assigned the file name and the line number.

 

a.cs

public override object Serialize(IDesignerSerializationManager m, object v)

{

CodeStatementCollection c = new CodeStatementCollection();

CodeVariableDeclarationStatement d = new CodeVariableDeclarationStatement(typeof(zzz), "Vijay");

CodeExpression e = new CodeArgumentReferenceExpression ("mukhi");

d.InitExpression = e;

c.Add(d);

return c;

}

 

Form1.cs

private void InitializeComponent()

{

zzz Vijay = mukhi;

 

The CodeExpression class is a base class for a large number of classes. The latest progressive feature to be incorporated is that the object Vijay can be initialised to a variable or a parameter of a function. Towards this end, the class CodeArgumentReferenceExpression is used, and its constructor is provided with the name of the variable as a string.

 

a.cs

public override object Serialize(IDesignerSerializationManager m, object v)

{

CodeStatementCollection c = new CodeStatementCollection();

CodeVariableDeclarationStatement d = new CodeVariableDeclarationStatement(typeof(int), "Vijay");

CodeExpression e = new CodePrimitiveExpression (100);

d.InitExpression = e;

c.Add(d);

return c;

}

 

Form1.cs

int Vijay = 100;

 

The class CodePrimitiveExpression is used whenever the value types are to be initialized to a specific value. In the above case, Vijay is declared to be of type int and is initialized to a value type 100.

 

a.cs

public override object Serialize(IDesignerSerializationManager m, object v)

{

CodeStatementCollection c = new CodeStatementCollection();

CodeVariableReferenceExpression r = new CodeVariableReferenceExpression ("mukhi");

CodeMethodInvokeExpression cm = new CodeMethodInvokeExpression(r, "Vijay");

c.Add(cm);

return c;

}

 

Form1.cs

mukhi.Vijay();

 

The next step is to convert Vijay into a function and to call it off the object mukhi. Therefore, an entity named 'r', of type CodeVariableReferenceExpression is created. This class is used for reference purposes, and as a function in our case. Thereafter, a CodeMethodInvokeExpression object named cm is created, and the constructor is assigned the reference object 'r', as well as the name of the function, i.e. Vijay. The outcome can be viewed in the code painter.

 

The point being driven home is that, since the control takes on the onus of writing its own code, the user is liberated from the necessity of comprehending a large amount of code in order to be more productive and efficient.

 

a.cs

public override object Serialize(IDesignerSerializationManager m, object v)

{

CodeStatementCollection c = new CodeStatementCollection();

CodeVariableReferenceExpression r = new CodeVariableReferenceExpression("mukhi");

CodeMethodInvokeExpression cm = new CodeMethodInvokeExpression(r, "Vijay");

CodeExpressionCollection ec = cm.Parameters;

CodeExpression e = new CodePrimitiveExpression (100);

ec.Add(e);

c.Add(cm);

return c;

}

 

Form1.cs

mukhi.Vijay(100);

 

Now, let us add a parameter to the function. We start with the simplest parameter, which is the number 100, as featured above. The Parameters property of the CodeMethodInvokeExpression returns a CodeExpressionCollection, which is a collection of CodeExpressions. All the CodeExpressions that we have encountered so far, are worthy of being reused. The Add member of the Collection object permits the addition of as many parameters to the function Vijay, as is deemed fit.

 

a.cs

public override object Serialize(IDesignerSerializationManager m, object v)

{

CodeStatementCollection c = new CodeStatementCollection();

CodeVariableReferenceExpression r = new CodeVariableReferenceExpression("mukhi");

CodeMethodInvokeExpression cm = new CodeMethodInvokeExpression(r, "Vijay");

CodeExpressionCollection ec = cm.Parameters;

CodeExpression e = new CodePrimitiveExpression (100);

CodeExpression e1 = SerializeToReferenceExpression (m, v);

ec.Add(e);

ec.Add(e1);

c.Add(cm);

return c;

}

 

Form1.cs

mukhi.Vijay(100, this.aaa1);

 

Moreover, the next variation is that, one of the parameters to the function Vijay should be the aaa1 object, which has just been passed to the Serialize function. In order to realize this objective, the function SerializeToReferenceExpression from the class CodeDomSerializer is used, which obtains the name of the object from the second parameter of the Serialize function.

 

Since the parameter v represents the object aaa1, a reference is made to this object in the code painter. The 'this' reference is inserted by the code painter.

 

a.cs

public override object Serialize(IDesignerSerializationManager m, object v)

{

CodeStatementCollection c = new CodeStatementCollection();

CodeVariableDeclarationStatement d = new CodeVariableDeclarationStatement(typeof(int []), "Vijay");

CodeArrayCreateExpression a = new  CodeArrayCreateExpression(typeof(int) , 10);

d.InitExpression = a;

c.Add(d);

return c;

}

 

Form1.cs

int[] Vijay = new int[10];

 

Creating an array is easier said than done. To create a variable Vijay of type int[], the class CodeArrayCreateExpression is brought into play. This class creates an array of data type int, having a size of 10.

 

We would sincerely recommend that you spend some time trying to gain an insight into the CodeDom namespace. We would also advise you to try out the classes independently. Not only are they  simple and functional, but they also augment your knowledge about the inner mechanism of compilers.

 

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;

class ccc : CodeDomSerializer

{

public override object Deserialize(IDesignerSerializationManager manager, object codeObject)

{

return null;

}

public override object Serialize(IDesignerSerializationManager m, object v)

{

sss.abc("Serialize");

CodeDomSerializer a = (CodeDomSerializer)m.GetSerializer(typeof(Component),typeof(CodeDomSerializer));

object c = a.Serialize(m, v);

PropertyDescriptor p = TypeDescriptor. GetProperties (v)["Mukhi"];

Control c1 = (Control)p.GetValue(v);

sss.abc(m.GetName(v));

string s = m.GetName(v) + "Sonal";

CodeVariableDeclarationStatement d = new CodeVariableDeclarationStatement(typeof(zzz), s);

d.InitExpression = new CodeObjectCreateExpression(typeof(zzz));

CodeVariableReferenceExpression r = new CodeVariableReferenceExpression(s);

CodeMethodInvokeExpression cm = new CodeMethodInvokeExpression(r, "Bad");

CodeExpressionCollection ec = cm.Parameters;

CodeExpression e1 = SerializeToReferenceExpression(m, c1);

ec.Add(e1);

CodeExpression e2 = SerializeToReferenceExpression(m, v);

ec.Add(e2);

CodeStatementCollection st = c as CodeStatementCollection;

st.Add(d);

st.Add(cm);

return c;

}

}

class ddd : ComponentDesigner

{

private Control c;

Control Mukhi

{

get

{

sss.abc("Mukhi get");

return c;

}

set

{

sss.abc("Mukhi set");

c = value;

}

}

public override void Initialize(IComponent component)

{

sss.abc("Initialize");

base.Initialize(component);

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

Mukhi = (Control)h.RootComponent ;

}

protected override void PreFilterProperties(IDictionary p)

{

sss.abc("PreFilterProperties");

base.PreFilterProperties(p);

p["zzz"] = TypeDescriptor.CreateProperty(this.GetType(), "Mukhi", typeof(Control));

}

}

[Designer(typeof(ddd))]

[DesignerSerializer(typeof(ccc), typeof(CodeDomSerializer))]

public class aaa : Component

{

}

public class zzz

{

}

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

Initialize

Mukhi set

PreFilterProperties

Mukhi get

Mukhi get

Serialize

Mukhi get

Mukhi get

aaa1

 

Form1.cs

zzz aaa1Sonal = new zzz();

this.aaa1 = new aaa();

//

// aaa1

//

this.aaa1.Mukhi = this;

aaa1Sonal.Bad(this, this.aaa1);

The focal point of any control is the Initialize function. While writing any control, this is the first function to be overridden, since it facilitates modification of the behaviour of the ComponentDesigner class.

 

Firstly, a handle to the IDesignerHost service is obtained, and then, the RootComponent property is used to access the Form object Form1. Thus, Mukhi now represents the window or the form, where the control aaa would be placed. The 'this' object in the code painter is a reference to the object that is an instance of the Form1 class.

 

It is amply evident that the Set accessor of the Mukhi property gets called first, and the control c is utilized to maintain state for the control. The system then calls PreFilterProperties. Thereafter, using the hash value zzz, a property called Mukhi is created. It is for this reason that the presence of a property called Mukhi in the designer class ddd is imperative.

 

Although the property Mukhi is not being called directly, the system still wants to internally ascertain its value, since the properties window is inactive. Hence, the Get accessor gets called. The system now calls the Serialize function, as the code painter wishes to establish how it should represent control aaa in the code. It is at this stage that the code painter would like the control to serialize itself, or to write out the code that represents the control.

 

The original Serialize function is called, wherein the base Serialize writes the basic two lines of code. Then, the PropertyDescriptor object p is used to retrieve the value of the property called Mukhi.

 

This value is retrieved by first calling the static GetProperties functions with the object v, which represents the control aaa. The return value is a PropertyDescriptorCollection object, since there may be innumerable properties present in the control. Then, with the assistance of the indexer, the property Mukhi is accessed. The GetValue function finally provides access to the value of the property. However, in order to access this function, the Get accessor of the property Mukhi must be called. This is the only possible avenue by which a property can be accessed.

 

For reasons unknown, the Get accessor is called twice. The GetName function of the manager class assigns the name aaa1 to the control. However, we have changed it to Sonal. As a consequence, a new variable called aaa1Sonal is created and initialized to a new zzz object, and the system adds the line "aaa1 = new aaa()" to the code.

 

Now, we intend to create a function Bad with two parameters, viz. the form object and the aaa1 object, and then, we wish to call it off the object aaa1Sonal. This is certainly a tall order, but we shall attempt it nonetheless. The final output should look like this:-

 

aaa1Sonal.Bad(this, this.aaa1);

 

A reference r that stands for the string, or the object aaa1Sonal, is created first. Then, it is associated with a method named Bad.

 

Once this is done, two parameters are required. The Form1 object is passed as the first parameter. It is stored in the control object c1, which has just been retrieved from the property Mukhi. This renders absolute control over what is to be serialized and how this is to be done.

 

Even though the Mukhi property in the aaa1 object has not been initialized, the system is astute enough to realize that the Mukhi property is set to the Form1 object, and it is capable of putting two and two together!