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!