3.

 

Type Conversion

 

The preceding chapter was riveted around the creation of a User Interface Type Editor. However, in this chapter, we shall explore how to convert one type into another.

 

a.cs

using System;

using System.IO;

using System.ComponentModel;

using System.Globalization;

using System.Drawing;

using System.Windows.Forms;

public class aaa : UserControl 

{

Point b1;

public Point a1

{

get {

return b1;

}

set {

b1 = value;

}

}

}

 

The control used in the previous chapter has been reemployed here. This control has a property a1 of type Point. On initiating this control into the form, the Properties window metamorphoses (as witnessed in screen 3.1), to incorporate the properties associated with the control. The property a1 displays a plus sign, which signifies that some properties exist within this control.

 

Screen 3.1

 

The values displayed are the string representation of the values of the individual properties.

 

A Point object actually consists of two properties X and Y. Thus, the values observed are the current values of the properties X and Y, separated by a comma. Clicking on the plus sign, brings forth the individual values of the two properties X and Y, as seen in screen 3.2. In this case, both of these share a value of 0.

 

Screen 3.2

 

a.cs

using System;

using System.IO;

using System.ComponentModel;

using System.Globalization;

using System.Drawing;

using System.Windows.Forms;

public class aaa : UserControl

{

[TypeConverter(typeof(StringConverter))]

public Point a1

{

get

{

return new Point(10,20);

}

set

{

}

}

}

 

A few amendments have been made to the earlier control. The attribute called TypeConverter has been added, thereby specifying a type to the constructor. This type is the class StringConverter. Resultantly, the plus sign gets displaced from the property a1 and merely a string representation of the object is visible, as is evident in screen 3.3.

 

Screen 3.3

 

The property now returns a Point object, which is displayed as a string. It is as an outcome of the TypeConverter employing the StringConverter class to display the type as a string and not as a Point.

 

a.cs

using System;

using System.IO;

using System.ComponentModel;

using System.Globalization;

using System.Drawing;

using System.Windows.Forms;

public class aaa : UserControl

{

[TypeConverter(typeof(bbb))]

Point b1 = new Point(10,20);

public Point a1

{

get

{

return b1;

}

set

{

b1 = value;

}

}

}

public class bbb : PointConverter

{

}

 

Now, the parameter changes to type bbb and is assigned to the TypeConverter attribute. This class bbb is derived from PointConverter. However, screen 3.4 does not display anything new. Everything remains in its pristine state. The Point property a1 displays its wares just as a point representation is expected to.

 

Screen 3.4

 

Thus, it is safe to assume that the Point class has an attribute TypeConverter, and a PointConverter is passed as a parameter to it. Subsequent to this, we shall unfold the process of tracking down all the attributes that a class is filled by.

 

a.cs

using System;

using System.IO;

using System.ComponentModel;

using System.Globalization;

using System.Drawing;

using System.Windows.Forms;

public class aaa : UserControl

{

yyy b1 = new yyy(1,2);

[TypeConverter(typeof(bbb))]

public yyy a1

{

get

{

sss.abc("aaa get ");

return b1;

}

set

{

sss.abc("aaa set ");

b1 = value;

}

}

}

public class yyy

{

public yyy(int x , int y)

{

sss.abc("yyy Constructor");

}

public override string ToString()

{

sss.abc("yyy ToString");

return "Vijay";

}

}

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();

}

}

public class bbb : TypeConverter

{

public bbb()

{

sss.abc("bbb Constructor");

}

}

 

a.txt

yyy Constructor

aaa get

bbb Constructor

aaa get

yyy ToString

aaa get

yyy ToString

 

In the above control, the property a1 is of type yyy. This class yyy is not derived from any other class. Hence, it derives from the base class object. It has a constructor with two parameters, and it overrides the ToString function from the object. This function is invoked whenever a String representation of the yyy object is required.

 

When the control is ushered into the form, the Properties window establishes how the yyy object should be displayed by it. If the TypeConverter attribute is missing, it calls the ToString function of the class to display the string representation of the object.

 

All the classes need recourse to the function abc. Hence, we devised a class sss and installed the function abc as the static member of this class. This is how it is made accessible to the other three classes.

 

Being an Instance type, the variable b1 gets created first. Hence, the yyy constructor is seen as the first line in our file a.txt.  Then, since the property a1 is to be displayed, the get accessor of the property is summoned. For reasons unclear, it gets called a good many times. We presume that this is so because the property window does not have adequate faith in the get accessor of the property. This get accessor returns a yyy object. But, prior to displaying the yyy object, the Properties window shall use the TypeConverter attribute to determine how a yyy type should be displayed. Since the class bbb exists with the attribute, it calls the constructor from the class bbb.

 

The class bbb is derived from the class TypeConverter. In fact, all Converter classes are derived from the TypeConverter. Therefore, any class specified with the TypeConverter attribute must necessarily be a class derived from TypeConverter. The PointConverter class that we had used a short while ago, has also been derived from the class TypeConverter.

 

The get accessor gets called yet again. It returns the yyy object, which we shall explore shortly. The ToString function of the class yyy gets called, and since it returns 'Vijay', we see 'Vijay' as the value of the property in the Properties window, as displayed in screen 3.5.

 

Screen 3.5

 

Since the property a1 of type yyy is a read only property, we are prohibited from making any changes to it.

 

a.cs

public class bbb : TypeConverter

{

public override bool CanConvertFrom(ITypeDescriptorContext c,Type t)

{

sss.abc("CanConvertFrom " + t.ToString());

return true;

}

public override bool CanConvertTo(ITypeDescriptorContext c,Type t)

{

sss.abc("CanConvertTo " + t.ToString());

return true;

}

}

 

 

a.txt

yyy Constructor

aaa get

CanConvertFrom System.String

aaa get

yyy ToString

 

In this program, the classes aaa, sss and yyy remain unaltered. The changes effected in the class bbb are reflected above. Two functions named CanConvertFrom and CanConvertTo, which reside in the class TypeConverter, are added to this class.

 

After the 'get' accessor returns the yyy object, Visual Studio.Net calls the CanConvertFrom function to convert the yyy type into a string. This is precisely why the Type parameter t is of type String. Moreover, the Properties window displays everything as a string. The return value of True confirms the conversion from a yyy type to a string type.

 

The Properties window feels elated on encountering a value of True. However, when we change the value of the string displayed and press enter, an error window is hurled at us, as seen in screen 3.6.

 

Screen 3.6

 

a.cs

public class yyy

{

public int a,b;

public yyy(int x , int y)

{

sss.abc("yyy Constructor");

a = x;

b = y;

}

public override string ToString()

{

sss.abc("yyy ToString");

return a + "," + b;

}

}

 

public class bbb : TypeConverter

{

public override bool CanConvertFrom(ITypeDescriptorContext c,Type t)

{

sss.abc("CanConvertFrom " + t.ToString());

return true;

}

public override bool CanConvertTo(ITypeDescriptorContext c,Type t)

{

sss.abc("CanConvertTo " + t.ToString());

return true;

}

public override object ConvertTo(ITypeDescriptorContext c,CultureInfo cu, object v, Type t)

{ 

sss.abc("ConvertTo " + t.ToString() + " " + v.ToString());

yyy a1 = (yyy)v;

string s1 = a1.a + "-" + a1.b;

return s1;

}

}

 

 

a.txt

yyy Constructor

aaa get

CanConvertFrom System.String

aaa get

yyy ToString

ConvertTo System.String 1,2

 

In a bid to ensure that we are able to view some values in our property window, we now add an adequate amount of code to our control.

 

The class yyy has two public instance variables a and b, which are meant to hold the values of the parameters passed to the constructor.

 

A Size object is known by the Width and Height properties; a Point object is known by the X and Y properties; and the class yyy is best recognized by the properties a and b. The ToString function returns the values of the instance variables a and b after placing a comma between them.

 

The ConvertTo function gets called since the CanConvertFrom function returns a value of true. In this function, the Type object is of string type, while the second last parameter is the yyy object. This function is called in order that the yyy object may be converted into a string and then returned back. This string that is returned would eventually be displayed in the Properties window.

 

Because we are calling the ToString function of the yyy object v in the function abc, we see the ToString function being called first in the a.txt file. This also corroborates the fact that the ToString function returns the values of the variables a and b, separated by a space.

 

Thus, to recapitulate, the CanConvertFrom function consents to convert a yyy object returned from the 'get' accessor into a string, with a return value of true. Therefore, the ConvertTo function gets called. The actual yyy object passed to this function is stored in a yyy object a1. A string is returned, which contains a minus sign between the values a and b, which we presume is for good luck.

 

Screen 3.7

 

The screen 3.7 exhibits the value of the property as 1-2. If the ToString function of the yyy object is eliminated, the yyy object returned by the get accessor simply remains unchanged and does not get converted. The get and set accessors are completely oblivious to our actions. Hence, they will only deal with a yyy object, thus calling upon the program to implement code for converting the object to a string, and vice-versa.

 

a.cs

public override bool CanConvertFrom(ITypeDescriptorContext c,Type t)

{

sss.abc("CanConvertFrom " + t.ToString());

return false;

}

 

Changing the return value from true to false in the CanConvertFrom function simply notifies the Properties window about the fact that the string cannot be converted into a yyy object. Hence, the property value shall be displayed as 1-2, but it would be read-only.

 

a.cs

public class bbb : TypeConverter

{

public override bool CanConvertFrom(ITypeDescriptorContext c,Type t)

{

sss.abc("CanConvertFrom " + t.ToString());

return true;

}

public override bool CanConvertTo(ITypeDescriptorContext c,Type t)

{

sss.abc("CanConvertTo " + t.ToString());

return true;

}

public override object ConvertTo(ITypeDescriptorContext c,CultureInfo cu, object v, Type t)

{ 

sss.abc("ConvertTo " + t.ToString() + " " + v.ToString());

yyy a1 = (yyy)v;

string s1 = a1.a + "-" + a1.b;

return s1;

}

public override object ConvertFrom(ITypeDescriptorContext c,CultureInfo cu, object o)

{

sss.abc("ConvertFrom " + o.ToString());

string s1 = (string)o;

char [] c1  = new char[] {'-'};

string[] v = s1.Split(c1);

sss.abc("a=" + v[0] + " b= "+ v[1]);

int a1,b1;

a1 = int.Parse(v[0]);

b1 = int.Parse(v[1]);

yyy y1 = new yyy(a1,b1);

sss.abc("ConvertFrom End");

return y1;

}

}

 

public class aaa : UserControl  {

yyy b1 = new yyy(1,2);

[TypeConverter(typeof(bbb))]

public yyy a1

{

get

{

sss.abc("aaa get " + b1.ToString());

return b1;

}

set

{

sss.abc("aaa set " + value.ToString());

b1 = value;

}

}

 

a.txt

yyy Constructor

yyy ToString

aaa get 1,2

CanConvertFrom System.String

yyy ToString

ConvertTo System.String 1,2

CanConvertFrom System.String

ConvertFrom 10-20

a=10 b= 20

yyy Constructor

ConvertFrom End

yyy ToString

aaa get 1,2

yyy ToString

aaa set 10,20

yyy ToString

aaa get 10,20

yyy ToString

ConvertTo System.String 10,20

 

Screen 3.8

 

The screen 3.8 is ample testimony of the fact that the control now permits amendments to the value of the property a1. The values have been changed from 1-2 to 10-20. The new function to be implemented is ConvertFrom, while the rest of the code remains the same. This function gets called whenever the Properties window perceives a change in the property.

 

The last parameter is a string since the property value is written as a string value of 10-20. This string value now necessitates conversion into a yyy object through the TypeConverter class bbb, since it represents a yyy type.

 

In this particular case, a minus sign has been inserted between the two values in the function ConvertTo. So, the first task at hand is to banish this minus sign.

 

To work towards this end, the value in the parameter o that represents the string has been assigned to a string variable s1. Thereafter, we create an array of chars that holds only one char, viz. a minus sign. The Split function of the string class splits a string on the array of chars passed as a parameter.

 

In the case of the existing minus sign, the Split function shall split the string s1 into two strings.  Thus, it returns an array of size two, and the values are stored in the array of strings named v.  The first member of this array v[0] would hold the value of the variable a, while the second v[1] would accommodate the value of variable b.

 

Then, the int class is used to convert the strings into ints. Thereafter, a yyy object is created with these ints as parameters, and duly returned. Then, the set accessor gets called with this yyy object. Also, the yyy object is displayed in the property a1.

 

Thus, the set accessor needs a yyy object. The ConvertFrom function gets called which returns a yyy object when it is given a string containing the types. The ConvertTo function gets called next, as the get accessor returns a yyy object with the values of 10 and 20. This yyy object is then converted into a string to be displayed in the Properties window.

 

a.cs

public override bool GetStandardValuesSupported(ITypeDescriptorContext c)

{

sss.abc("GetStandardValuesSupported");

return true;

}

 

a.txt

GetStandardValuesSupported

CanConvertFrom System.String

 

Yet another function called GetStandardValuesSupported is added to the class bbb. This function returns a value of true. On viewing screen 3.9, you would notice that the property a1 now has a listbox User Interface.

 

Screen 3.9

 

This function gets called pretty early, even before the CanConvertFrom function gets called. Therefore, a click on the listbox down arrow does not lead to the display of any data at all. Now, its time to pack the listbox with some yyy objects.

 

a.cs

public override StandardValuesCollection GetStandardValues (ITypeDescriptorContext c)

{

sss.abc("GetStandardValues Start");

yyy [] v = new yyy[2];

v[0] = new yyy(10,20);

v[1] = new yyy(100,200);

TypeConverter.StandardValuesCollection s = new TypeConverter.StandardValuesCollection(v);

sss.abc("GetStandardValues End");

return s;

}

 

a.txt

GetStandardValues Start

yyy Constructor

yyy Constructor

GetStandardValues End

 

The newly added function GetStandardValues returns a StandardValuesCollection object. The constructor of this object requires an ICollection parameter interface, which is supported by a large number of data types, including an array. An array of two yyy objects is created and the individual members named v[0] and v[1] are initialized. This array is then handed down to the StandardValuesCollection constructor and the value i.e ‘s’, is then returned.

 

On executing the control, when the drop-down listbox of property a1 is clicked on, two values meet the eye, separated by a minus sign, as observed in screen 3.10.

 

Screen 3.10

Screen 3.11

 

The ToString function of the yyy class places a comma between the two instance variable values a and b. On selecting any of the values in the listbox, the value of the property also undergoes change, as shown in screen 3.11.

 

Thus, the function ConvertTo has to be called, which converts the yyy object into a string, thereby displaying it in the listbox. It is with the help of this approach that our list of yyy objects can be placed in a drop-down listbox.

 

The only downside of the earlier method is that we are permitted to amend the values directly if we so desire, as has been done in screen 3.12.

 

Screen 3.12

 

a.cs

public override bool GetStandardValuesSupported(ITypeDescriptorContext c)

{

sss.abc("GetStandardValuesSupported");

return true;

}

public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext c)

{

sss.abc("GetStandardValues Start");

yyy [] v = new yyy[2];

v[0] = new yyy(10,20);

v[1] = new yyy(100,200);

TypeConverter.StandardValuesCollection s = new TypeConverter.StandardValuesCollection(v);

sss.abc("GetStandardValues End");

return s;

}

public override bool GetStandardValuesExclusive(ITypeDescriptorContext c)

{

sss.abc("GetStandardValuesExclusive");

return true;

}

 

a.txt

GetStandardValuesSupported

CanConvertFrom System.String

GetStandardValuesExclusive

 

We have added one more function named GetStandardValuesExclusive that returns a value of true, in case the drop-down listbox happens to be the only avenue for assigning a value to the property. This function GetStandardValuesExclusive gets called after GetStandardValuesSupported returns true.

 

Screen 3.13

 

Since a value of true has been returned, the screen 3.13 makes it sufficiently evident that we cannot change the value of the property a1 using the keyboard. Instead, we have to resort to the drop-down listbox. Now, delete the three functions that we have added so far.

 

a.cs

public override bool GetPropertiesSupported (ITypeDescriptorContext c) {

sss.abc("GetPropertiesSupported");

return true;

}

 

a.txt

CanConvertFrom System.String

GetPropertiesSupported

 

The new function GetPropertiesSupported returns a boolean value of true or false, based on whether the property supports sub-properties or not.

 

Screen 3.14

 

On returning a value of true, we see the screen 3.14, where the property a1 has a plus sign preceding it. But, clicking on the plus sign does not display anything, since the properties have not been specified yet.

 

This function is summoned immediately after the CanConvertFrom function.

 

a.cs

using System;

using System.IO;

using System.ComponentModel;

using System.Globalization;

using System.Drawing;

using System.Windows.Forms;

using System.Collections;

public class aaa : UserControl

{

yyy b1 = new yyy(1,2);

[TypeConverter(typeof(bbb))]

public yyy a1

{

get

{

return b1;

}

set

{

b1 = value;

}

}

}

public class yyy

{

public int aa,bb;

public int a

{

get

{

return aa;

}

set

{

aa = value;

}

}

public int b

{

get

{

return bb;

}

set

{

bb = value;

}

}

public yyy(int x , int y)

{

sss.abc("yyy Constructor");

a = x;

b = y;

}

public override string ToString()

{

sss.abc("yyy ToString");

return a + "," + b;

}

}

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();

}

}

public class bbb : TypeConverter

{

public override bool CanConvertFrom(ITypeDescriptorContext c,Type t)

{

sss.abc("CanConvertFrom " + t.ToString());

return true;

}

public override bool CanConvertTo(ITypeDescriptorContext c,Type t)

{

sss.abc("CanConvertTo... " + t.ToString());

return true;

}

public override object ConvertTo(ITypeDescriptorContext c,CultureInfo cu, object v, Type t)

{ 

sss.abc("ConvertTo " + t.ToString() + " " + v.ToString());

yyy a1 = (yyy)v;

string s1 = a1.a + "-" + a1.b;

return s1;

}

public override object ConvertFrom(ITypeDescriptorContext c,CultureInfo cu, object o)

{

sss.abc("ConvertFrom " + o.ToString());

string s1 = (string)o;

char [] c1  = new char[] {'-'};

string[] v = s1.Split(c1);

sss.abc("a=" + v[0] + " b= "+ v[1]);

int a1,b1;

a1 = int.Parse(v[0]);

b1 = int.Parse(v[1]);

yyy y1 = new yyy(a1,b1);

sss.abc("ConvertFrom End");

return y1;

}

public override bool GetPropertiesSupported(ITypeDescriptorContext c)

{

sss.abc("GetPropertiesSupported");

return true;

}

public override PropertyDescriptorCollection GetProperties (ITypeDescriptorContext c, object v ,Attribute[] a)

{

sss.abc("GetProperties....");

return null;

}

}

 

a.txt

yyy Constructor

CanConvertFrom System.String

GetPropertiesSupported

yyy ToString

ConvertTo System.String 1,2

GetPropertiesSupported

yyy ToString

ConvertTo System.String 1,2

ConvertTo System.String 10,2

yyy ToString

 

In the above program that has been displayed in its entirety, the function GetPropertiesSupported gets called as usual. Since the value returned is true, the plus sign is displayed in front of the property a1. Clicking on the plus sign should expose the list of properties. However, the function GetProperties is bypassed despite its presence in the file. This function does not get called to retrieve a list of properties.

 

Previously, there were two public variables in the class yyy, which have now been converted into properties a and b; and the variables aa and bb are used to store their values, respectively.

 

Now, when the properties are expanded by clicking on the plus sign, the two properties a and b get displayed, as seen in screen 3.15.

 

Screen 3.15

 

On amending only one of the properties, 'a' for instance, the change in the value of the property shall become visible, only when you first click on the minus sign, followed by a click on the plus sign (as in screen 3.16). In other words, only when it is collapsed and expanded. Changing the value of the property with the help of the Enter key, shall be dealt with a little later.

 

Screen 3.16

 

a.cs

public class yyy

{

public int aa,bb;

public Size d

{

get

{

return new Size(1000,2000);

}

set

{

}

}

public xxx c

{

get

{

return new xxx();

}

set

{

}

}

public int a

{

get

{

return aa;

}

set

{

aa = value;

}

}

public int b

{

get

{

return bb;

}

set

{

bb = value;

}

}

public yyy(int x , int y)

{

sss.abc("yyy Constructor");

a = x;

b = y;

}

public override string ToString()

{

sss.abc("yyy ToString");

return a + "," + b;

}

}

 

public class xxx

{

public Point d

{

get

{

return new Point(100,200);

}

set

{

}

}

}

 

 

The above control is displayed in its execution mode in screen 3.17. Clicking on the plus sign of the property a1 shows four properties, viz. the ints a and b, the property c of type xxx and the Size property d.

 

Screen 3.17

Screen 3.18

 

Besides, there is a plus sign in front of the Size property. The instant it is clicked upon, we arrive at the screen 3.18, where the two properties of Width and Height are visible within Size.

 

The xxx type property c has a property d of type Point, but there is no trace of the plus sign in front of it, since there is no TypeConverter attribute in it. This renders the property as read-only, and the ToString function from the object merely returns the name of the class.

 

Thus, there may subsist as many sub-levels of properties as is desired. The sole caveat is that the classes must necessarily be tagged with the relevant TypeConverter attribute.

 

a.cs

[TypeConverter(typeof(SizeConverter))]

public class xxx

 

Now, the TypeConverter attribute has been added with the class name of SizeConverter. Further, the control has also been introduced. At this stage, the plus sign can be spotted with the property a1.

 

This property has a plus sign placed before the property c. Clicking on this plus sign reveals the screen 3.19, wherein the two properties Width and Height are evident, but with erroneous values.

 

 

Screen 3.19

 

A salient point to be committed to memory is that, each time you wish to display the value of a property in the Properties window, when its type is a class created by you, you must ensure that the attribute TypeConverter is used. There are over a million such classes that end with Converter.

 

a.cs

using System;

using System.IO;

using System.ComponentModel;

using System.Globalization;

using System.Drawing;

using System.Windows.Forms;

using System.Collections;

public class aaa : UserControl  {

yyy b1 = new yyy(1,2);

[TypeConverter(typeof(bbb))]

public yyy a1

{

get

{

return b1;

}

set

{

b1 = value;

}

}

}

public class yyy {

public int a,b;

public yyy(int x , int y)

{

a = x;

b = y;

}

}

public class bbb : TypeConverter

{

public override bool CanConvertFrom(ITypeDescriptorContext c,Type t)

{

return true;

}

public override object ConvertTo(ITypeDescriptorContext c,CultureInfo cu, object v, Type t)

{ 

yyy a1 = (yyy)v;

string s1 = a1.a + "-" + a1.b;

return s1;

}

public override object ConvertFrom(ITypeDescriptorContext c,CultureInfo cu, object o)

{

string s1 = (string)o;

char [] c1  = new char[] {'-'};

string[] v = s1.Split(c1);

int a1,b1;

a1 = int.Parse(v[0]);

b1 = int.Parse(v[1]);

if ( b1 >= 10)

{

Exception e = new Exception("Vijay");

throw e;

}

yyy y1 = new yyy(a1,b1);

return y1;

}

}

 

The concluding example of this chapter happens to be the smallest working control written with the TypeConverter attribute. It has been our endeavour to build-in an error check, wherein the user is not authorised to type in a value larger than 10 for the property b. Thus, the value of b is verified in the function ConvertFrom. If it is larger than 10, an Exception object 'e' is created and supplied with a string 'Vijay'. This Exception is then hurled at us.

 

In the control, if the second value after the minus sign is changed to a number larger than 10, an exception gets thrown as shown in screen 3.20. Here, we have changed it to 20, and sure enough, an exception is tossed at us. The Details button shows the string 'Vijay', which was provided to the constructor.

 

Screen 3.20

 

Normally, some meaningful error message is offered in place of the string 'Vijay'. Thus, we can exercise total control over the values that the user is legally permitted to enter.