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.