2.

 

Creating a UI Type Editor

 

This chapter examines certain basic concepts. There is no law in the world that forbids a dll from being bestowed with more than one control. The next program demonstrates precisely how it is possible for a dll to own two controls.

 

a.cs

using System.Windows.Forms;

public class a1 : UserControl

{

}

public class a2 : UserControl

{

}

public class a3

{

}

 

The program a.cs has a total of three classes.  Out of these classes, two of them, viz. a1 and a2 have been derived from the class UserControl. Add the control to the toolbox as demonstrated in the earlier chapter. On doing so, two separate controls a1 and a2 are conceived, as seen in screen 2.1.

 

Screen 2.1

 

Now, when we click on OK, two controls with the same bitmap but with distinct names, i.e. a1 and a2 appear in the General section, as seen in Screen 2.2.

 

Screen 2.2

 

The third class a3 is not visible, as it has not been derived from class UserControl. By a mere glance at the toolbox, it is a near impossibility to ascertain as to which dll the control originate from. Besides, who cares to find that out anyway!

 

If things do not materialize as expected, simply select the control from the toolbox, click on the right mouse button and delete it as displayed in screen 2.3. Alternatively, uncheck it from the Customize Toolbox window.

 

Screen 2.3

 

On being eradicated from the toolbox, it also gets displaced from the Customize toolbox dialog. If more than one control is to be deleted, then each control will have to be axed individually.

 

a.cs

using System;

using System.IO;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Diagnostics;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Drawing.Design;

using System.Windows.Forms;

using System.Windows.Forms.ComponentModel;

using System.Windows.Forms.Design;

public class fff : System.Drawing.Design.UITypeEditor

{

IWindowsFormsEditorService e = null;

public override object EditValue(ITypeDescriptorContext c, IServiceProvider p, object v)

{

abc("fff EditValue Start");

abc("Value of parameter " + v.ToString());

aaa t = new aaa();

t.fff += new EventHandler(abc);

t.a1 = (int)v;

e  = (IWindowsFormsEditorService) p.GetService(typeof(IWindowsFormsEditorService));

abc("Before DropDownControl");

e.DropDownControl(t);

abc("fff EditValue End");

return t.a1;

}

public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext c)

{

abc("fff GetEditStyle");

return UITypeEditorEditStyle.DropDown;

}

void abc(object s, EventArgs e1)

{

abc("fff abc");

e.CloseDropDown();

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

public class aaa : System.Windows.Forms.UserControl

{

int v = 0;

EventHandler eee;

public aaa()

{

abc("Constructor");

Size = new System.Drawing.Size(100, 23);

}

[Editor(typeof(fff), typeof(UITypeEditor))]

public int a1

{

get

{

abc("get a1");

return v;

}

set

{

abc("set a1");

v = value;

}

}

public event EventHandler fff

{

add

{

abc("fff event add");

eee += value;

}

remove

{

abc("fff event remove");

}

}

protected override void OnMouseDown(MouseEventArgs e)

{

abc("OnMouseDown");

v = e.X;

}

protected override void OnMouseUp(MouseEventArgs e)

{

abc("OnMouseUp");

eee.Invoke(this, null);

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

 

a.txt

Constructor

get a1

set a1

get a1

fff GetEditStyle

get a1

fff EditValue Start

Value of parameter 7

Constructor

fff event add

set a1

Before DropDownControl

OnMouseDown

OnMouseUp

fff abc

get a1

fff EditValue End

set a1

get a1

fff GetEditStyle

get a1

fff EditValue Start

Value of parameter 84

Constructor

fff event add

set a1

Before DropDownControl

OnMouseDown

OnMouseUp

fff abc

get a1

fff EditValue End

set a1

get a1

fff GetEditStyle

get a1

 

Ever heard of this old adage? If things do not work out the way the book avers, eliminate the control from the toolbox. Then, restart Visual Studio.Net and add the control to the toolbox. Whenever there is some activity on the control, the file a.txt also gets written.

 

When the control is initiated into the form, relevant functions that write to the file a.txt, get called. Effectively, through the function abc, the file keeps track of the functions that get called, and also of the sequence in which they are called. Thus, the function abc is truly a savior and can be used in lieu of the debugger.

 

Our file reveals that only two functions get called.

 

The first function to be called is the Constructor of the aaa class. It is so because the class is derived from UserControl and is placed within the form.

 

Then, there is one property in the control named a1 and one event called fff. Check the Misc segments of the properties section in order to verify the above. The get and set accessors get called innumerable times. Hence, we have not placed the abc functions in the property a1.

 

A click on the property a1 invokes the function GetEditStyle from the class fff. The presence of the attribute Editor on the property a1 is responsible for summoning the function GetEditStyle from the class fff. This attribute determines as to what would shoot up in the editor, while the value of the property in the property window is being changed.

 

The property window creates a new instance of a class, which interacts with the user while the property is being changed. The user interface may either be a dialog box or a drop-down window. 

 

The constructor has two parameters, both of which are of class Type. The first parameter must be a class that derives from the class UITypeEditor. The second parameter is the Type that represents the object that would be created to don the mantle of an editor class. This is used as a key to locate a specific editor, since the data type can have more than one editor associated with it.

 

The class fff is derived from the class UITypeEditor for the first parameter to the constructor. The second parameter is the type UITypeEditor. This class provides a base class possessing some basic functionality to represent a value editor, which can be used as a user interface for representing and editing values of different data types. Since we want our own value editor to be used during the design time, we extend from this class.

 

Also, there will be occasions when the value of a property demands a different type of interaction between the user and the properties window, for which the existing UIs are inadequate.

 

In such situations, a unique User Interface can be summoned, which is what we are attempting to achieve here.

 

Screen 2.4

 

Clicking on the a1 property displays a drop-down listbox, but with an empty window, as shown in screen 2.4. However, when the mouse is clicked on the right of the window, the listbox closes automatically and displays a number as the value of the property.

 

You can visualise the window representing sequence numbers from 0 at one end to 100 at the other.  The number changes depending upon where you click, and it gets displayed. Although this may not be the most elegant way of changing the value of the property, it definitely is an original one.

 

The first function to be called is GetEditStyle. This function returns an enum UITypeEditorEditStyle, which takes three values, viz. DropDown, Modal and None. Here, the value that is returned is DropDown, since the UI that we want is a drop-down listbox. This function determines the type of UI that the user shall encounter.

 

Effect the following modifications to the return value and rewrite this function as follows:-

 

public override UITypeEditorEditStyle GetEditStyle( ITypeDescriptorContext c)  {

abc("fff GetEditStyle");

return UITypeEditorEditStyle.Modal;

}

 

The properties window containing three dots has now replaced the drop-down listbox as shown in screen 2.5.

 

Screen 2.5

 

When the three dots are clicked upon, the User Interface that pops up is the same as delineated before. The change occurs only in the initial method of activation of the user interface.

 

Now, remove the above function from the code. This does not affect the framework in any way, since it is then at liberty to treat the property as an int. Hence, it allows it to be edited.

 

The function GetEditStyle is called from the base class, i.e. UITypeEditor class, which merely returns the enum value None. The functions in the code that display the UI do not get called at all. Thus, to obtain a user-defined User Interface, the function GetEditStyle present in UITypeEditor must be overridden.

 

The next function is EditValue, which performs all the mundane work. This function is responsible for displaying the UI. The value that this function returns eventually gets displayed as the value of the property. The third parameter is the current value of the property.

 

Thus, the parameter to the function apprises us what is displayed, while the value that we return is what the property window would finally display. We now intend to display our User Interface. We commence by creating a new instance called t of the same class aaa, which is derived from the class UserControl.

 

We could have used another class for the user interface. However, we chose to use the same class aaa. We would like to employ this object t for our user interface. The constructor of the aaa class merely sets the Size property to a value that can fit as a window to the drop-down listbox.

 

We have an event fff defined in the control aaa. We hook up a function abc that will be called whenever the event fff is fired in the control. In the event fff, we store the parameter value in the Add accessor, which is the function abc from the class fff.

 

We employ the EventHandler eee to keep track of all the code that gets added or removed. The Remove accessor contains no code, since it is not called at all. Thus, whenever the aaa control calls the Invoke function of the delegate eee, the function abc shall get called in the class fff.

 

The property concept remains unchanged irrespective of the user interface associated with it. The set accessor of the property must be initialized whenever its value is changed, while the get accessor must be used to fetch the value.

 

The property a1 is initialized using the last parameter of the function. The value is stored in the variable v, in order that it may be utilised by others too. Since the program is not coded in a manner that allows for the variable to be used elsewhere, this step remains optional.

 

Now that the control has finally been instantiated, we intend to display this control in the properties window.

 

We need access to some object that would display our control in the drop-down listbox. Towards this end, the second parameter to the function, i.e. the IServiceProvider type parameter p, comes into play. Only those classes that wish to render some service to other classes implement this interface.

 

There are only four classes that implement this interface. These are HttpContext, LicenseContext, MarshallByValueComponent and ServiceContainer.

 

This interface has a single function named GetService, which can fetch the object whose services are to be obtained. The single parameter to the function is the type of Object. It is given in the interface of IWindowsFormsEditorService. The classes derived from this interface have the requisite familiarity of working with custom user interfaces.

 

The function DropDownControl is well appraised of how a control is to be displayed as a window in the properties toolbox. When this function is executed, an empty window gets displayed, with the drop-down listbox in the properties window.

 

Thereafter, when the right mouse button is clicked in the window, the function OnMouseDown gets invoked in the control aaa. The OnMouseDown function gets called with a single parameter e of type MouseEventArgs. The variable v is utilised to store the current value of the property.

 

This parameter has a property x, which contains the position in pixels on the horizontal or along the X axis, where the mouse button is clicked. In our case, the maximum value is decided by the Size property and it does not exceed 100 pixels. This value is stored in the variable v, so that whenever the get accessor is called next, this value can be returned.

 

On releasing the mouse button, the function OnMouseUp gets triggered. At this point in time, the control can infer that the user has made a choice, and hence, the drop-down window needs to be closed. Thus, this function should be responsible for closing the control window. However, the problem at this juncture is that there is no access to the class fff. In order to grapple with this problem, the event fff is set. Thus, it can be fired and the abc function can be called in the class fff. This function shall shut the window down.

 

So, all that transpires in the function OnMouseDown is that, the Invoke function is used to call the function abc. This object is supplied as the first parameter and a null is provided to the EventArgs object.

 

This will now call the function abc in the class fff. We are fully aware of the fact that there are two functions, both named abc in the class fff. However, both these functions possess distinct parameters. Each one of us is blessed with distinct tastes and personal preferences when it comes to naming his/her favourite object. In our case, we have a soft spot for the function name abc.

 

In the abc function, the IWindowsFormsEditorService object e is used to close the drop-down listbox by calling the function CloseDropDown. This will ensure that whenever the mouse button is released, the listbox would close. The function DropDownControl waits at its position, displaying the dropdown listbox, until the function CloseDropDown is called.

 

In the OnMouseDown function, the variable v has already been initialized. Thus, when the value of the property a1 is returned, effectively, it is the value of the variable v that is being returned. Thereafter, this value is displayed by the properties window.

 

 

The output file a.txt, minus the 'get' accessor's as corroborates our act. After we reach the End of the EditValue, the remainder of the a.txt is a mere repetition of the earlier part. As is evident from the file, the whole process gets repeated, beginning with the GetEditStyle.

 

a.cs

using System;

using System.IO;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Diagnostics;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Drawing.Design;

using System.Windows.Forms;

using System.Windows.Forms.ComponentModel;

using System.Windows.Forms.Design;

public class fff : System.Drawing.Design.UITypeEditor

{

IWindowsFormsEditorService e = null;

public override object EditValue(ITypeDescriptorContext c, IServiceProvider p, object v)

{

abc("fff EditValue Start");

abc("Value of parameter " + v.ToString());

bbb t = new bbb();

t.fff += new EventHandler(abc);

t.b1 = (int)v;

e  = (IWindowsFormsEditorService) p.GetService(typeof(IWindowsFormsEditorService));

abc("Before DropDownControl");

e.DropDownControl(t);

abc("fff EditValue End");

return t.b1;

}

public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext c) {

abc("fff GetEditStyle");

return UITypeEditorEditStyle.DropDown;

}

void abc(object s, EventArgs e1) {

abc("fff abc");

e.CloseDropDown();

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

public class aaa : System.Windows.Forms.UserControl

{

int v = 0;

public aaa()

{

abc("aaa Constructor");

}

[Editor(typeof(fff), typeof(UITypeEditor))]

public int a1

{

get

{

return v;

}

set

{

v = value;

}

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

class bbb : System.Windows.Forms.UserControl

{

int w = 0;

EventHandler eee;

public bbb()

{

abc("bbb Constructor");

Size = new System.Drawing.Size(100, 23);

}

public int b1

{

get

{

return w;

}

set

{

w = value;

}

}

public event EventHandler fff

{

add

{

abc("bbb fff event add");

eee += value;

}

remove

{

}

}

protected override void OnMouseDown(MouseEventArgs e)

{

abc("bbb OnMouseDown");

w = e.X;

}

protected override void OnMouseUp(MouseEventArgs e)

{

abc("bbb OnMouseUp");

eee.Invoke(this, null);

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

 

a.txt

aaa Constructor

fff GetEditStyle

fff EditValue Start

Value of parameter 3

bbb Constructor

bbb fff event add

Before DropDownControl

bbb OnMouseDown

bbb OnMouseUp

fff abc

fff EditValue End

fff GetEditStyle

fff EditValue Start

Value of parameter 88

bbb Constructor

bbb fff event add

Before DropDownControl

bbb OnMouseDown

bbb OnMouseUp

fff abc

fff EditValue End

fff GetEditStyle

 

The earlier program has been simplified in this program. Although it still performs the same task, it now incorporates two separate classes. The class aaa is public and represents the control. It contains code that solely relates to the control.

 

The user-interface code has been placed in another class named bbb. Thus, all that we have is a property a1, along with the variable v to store its value. The abc function does not contribute to the control.

The constructor in the aaa class is summoned at the very beginning, followed by the get and set accessors. The function EditValue creates an object, which has a likeness to the bbb class and not to the aaa class. Other than changing the name of the property from a1 to b1, all the code in the class fff remains unaltered.

 

The class bbb is not public, and hence, the default access modifier is internal. Thus, this class can be accessed and is visible to other classes present within the dll named a.dll. The constructor initializes the Size property, thereby determining the size of the window. The property b1 simply uses the variable 'w' to maintain state. The Mouse functions also remain unchanged.

 

Thus, by separating the User Interface Editor code in the class bbb from the code of the control, it becomes much easier to distinguish between the code relevant to the control and the code pertaining to the UI type editor.

 

Henceforth, any changes that are incorporated to the control shall be visible in the class aaa, and any window related modification that gets displayed with a click on the drop-down list button shall be viewed in the class bbb. Finally, the class that handles the UI Type Editor shall be sighted in the class fff.  If there is no change in the class, it can be copied from the earlier program. However, when a class changes, it would be displayed in full.

 

In the ensuing program, we have not provided the code of the bbb class, since it remains unaltered. So, kindly retain the same code as was sighted in the previous program.  Also, the abc function has been removed from the program, since there is no necessity to trace out the functions that are called.

 

a.cs

using System;

using System.IO;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Diagnostics;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Drawing.Design;

using System.Windows.Forms;

using System.Windows.Forms.ComponentModel;

using System.Windows.Forms.Design;

public class fff : System.Drawing.Design.UITypeEditor

{

IWindowsFormsEditorService e = null;

public override object EditValue(ITypeDescriptorContext c, IServiceProvider p, object v)

{

bbb t = new bbb();

t.fff += new EventHandler(abc);

t.b1 = (int)v;

e  = (IWindowsFormsEditorService)p.GetService(typeof(IWindowsFormsEditorService));

e.DropDownControl(t);

return t.b1;

}

public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext c)

{

aaa a = (aaa)c.Instance;

if ( a.UIType == yyy.DropDown)

return UITypeEditorEditStyle.DropDown;

if ( a.UIType == yyy.Modal)

return UITypeEditorEditStyle.Modal;

return UITypeEditorEditStyle.None;

}

void abc(object s, EventArgs e1)

{

e.CloseDropDown();

}

}

public class aaa : System.Windows.Forms.UserControl

{

int v ;

yyy w = yyy.DropDown;

public yyy UIType

{

get

{

return w;

}

set

{

w = value;

}

}

[Editor(typeof(fff), typeof(UITypeEditor))]

public int a1

{

get

{

return v;

}

set

{

v = value;

}

}

}

public enum yyy

{

DropDown, Modal, None

}

 

This program has an enum yyy that takes three values, viz. the option that the UI Type Editor offers for a user interface, i.e. a drop-down listbox, a Modal dialog box and None. Yet another property called UIType is created in the control class, and its type is set to the enum yyy. The variable w stores the value of the property and the default option is set to DropDown.

 

The real action lies in the function GetEditStyle. This function is passed a parameter named c of type ITypeDescriptorContext. This interface is also passed to the function EditValue.

 

This interface is of considerable significance since it has a member called Instance, which  actually is an instance of the control for which the UI Editor is being created. Thus, in this particular case, the instance is that of the class aaa. Therefore, using this object 'a', the value of the property UIType is ascertained, and depending upon its value, a different EditStyle is returned.

 

Screen 2.6

Screen 2.7

 

Thus, screens 2.6 and 2.7 reveal how the User Interface for the property can be dynamically modified. If things do not get updated dynamically, click outside the control, and then, select the control once again.

 

The vital point brought out by the above program is that, at any given time, this control can be accessed in the class fff by using the ITypeDescriptorContext parameter. In turn, the control can run code in the class fff by firing an event.

 

a.cs

public class fff : System.Drawing.Design.UITypeEditor

{

IWindowsFormsEditorService e = null;

Form f = new Form();

public override object EditValue(ITypeDescriptorContext c, IServiceProvider p, object v)

{

aaa a = (aaa)c.Instance;

e  = (IWindowsFormsEditorService) p.GetService(typeof(IWindowsFormsEditorService));

if ( a.UIType == yyy.DropDown)

{

bbb t = new bbb();

t.fff += new EventHandler(abc);

t.b1 = (int)v;

e.DropDownControl(t);

return t.b1;

}

else if (a.UIType == yyy.Modal)

{

Button b = new Button ();

TextBox t = new TextBox();

b.Text = "OK";

b.Location = new Point (10, 10);

b.Click += new EventHandler(pqr);

t.Location = new Point (10, 100);

t.Text = a.a1.ToString();

f.Text = "Vijay's Dialog Box";

f.Controls.Add(b);

f.Controls.Add(t);

e.ShowDialog(f);

string s = t.Text;

return Convert.ToInt32(s);

}

else

return 0;

}

public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext c)

{

aaa a = (aaa)c.Instance;

if ( a.UIType == yyy.DropDown)

return UITypeEditorEditStyle.DropDown;

if ( a.UIType == yyy.Modal)

return UITypeEditorEditStyle.Modal;

return UITypeEditorEditStyle.None;

}

void abc(object s, EventArgs e1)

{

e.CloseDropDown();

}

void pqr(object s, EventArgs e1)

{

f.Close();

}

}

 

Once again, since the window shown in the UI remains unchanged, the code for the class bbb is not exhibited. The user control class aaa  is also not displayed, since the control requires no modification. All the amendments have been effected in the fff class.

 

The function GetEditStyle does not undergo any change whatsoever. It returns an enum value, depending upon the value of the property UIType. The modifications have been incorporated in the function EditValue. The value contained in the property UIType is determined using the first parameter c of type ITypeDescriptorContext. If its value is DropDown, then the task performed earlier is resorted to once again.

 

If it is Modal, then a Form object is created, which is subsequently populated with a Button and a TextBox. The form object f is an instance variable. So, other functions in the class fff can access this variable. The Location property of the textbox and button are set to some decent values and the Click event of the button is associated with the function pqr.

 

The function simply closes the dialog box using the Close function and not the CloseDropDown function. The TextBox control t is initialized to the current value of the control property a1. The ShowDialog function is then called with the form object f as a parameter.

 

In Visual Studio.Net, modify the UIType property to Modal and click anywhere outside the control. Then, select the control again. Now, when the property a1 is clicked upon, the three dots become visible, which when clicked on, display screen 2.8.

 

Screen 2.8

 

The textbox displays the value of the property. Now, change the value from 85 to 850 and click on OK. This closes the Dialog box, since the ShowDialog function terminates. We use the static function ToInt32 from the Convert class to convert the string content of the textbox into a number and to return this value.

 

This value is thereafter displayed in the properties window. The last 'else' is not deemed necessary, since the UIType of None will never call the function EditValue. This is a classic example of how we can build our own unique UI Type editors.

 

a.cs

using System;

using System.IO;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Diagnostics;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Drawing.Design;

using System.Windows.Forms;

using System.Windows.Forms.ComponentModel;

using System.Windows.Forms.Design;

public class fff : System.Drawing.Design.UITypeEditor

{

IWindowsFormsEditorService e = null;

Form f = new Form();

public override object EditValue(ITypeDescriptorContext c, IServiceProvider p, object v)

{

aaa a = (aaa)c.Instance;

e  = (IWindowsFormsEditorService)p.GetService(typeof(IWindowsFormsEditorService));

bbb t = new bbb();

t.fff += new EventHandler(abc);

t.b1 = (int)v;

e.DropDownControl(t);

return t.b1;

}

public override bool GetPaintValueSupported(ITypeDescriptorContext c)

{

abc("GetPaintValueSupported");

return true;

}

public override void PaintValue(PaintValueEventArgs e)

{

abc("PaintValue");

Graphics g = e.Graphics;

Rectangle r = e.Bounds;

abc(r.ToString());

object o = e.Value;

int i = (int)o;

abc("Value from PaintEventArgs " + i.ToString());

ITypeDescriptorContext c = e.Context;

aaa a = (aaa)c.Instance;

abc("Value from Control " + a.a1.ToString());

Brush b = new SolidBrush(Color.Red);

Font f = new Font("Times New Roman", 10);

g.DrawString(i.ToString(), f, b , r);

}

public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext c)

{

return UITypeEditorEditStyle.DropDown;

}

void abc(object s, EventArgs e1)

{

e.CloseDropDown();

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

public class aaa : System.Windows.Forms.UserControl

{

int v ;

[Editor(typeof(fff), typeof(UITypeEditor))]

public int a1

{

get

{

return v;

}

set

{

v = value;

}

}

}

class bbb : System.Windows.Forms.UserControl

{

int w = 0;

EventHandler eee;

public bbb()

{

Size = new System.Drawing.Size(100, 23);

}

public int b1

{

get

{

return w;

}

set

{

w = value;

}

}

public event EventHandler fff

{

add

{

eee += value;

}

remove

{

}

}

protected override void OnMouseDown(MouseEventArgs e)

{

w = e.X;

}

protected override void OnMouseUp(MouseEventArgs e)

{

eee.Invoke(this, null);

}

}

 

 

 

a.txt

GetPaintValueSupported

PaintValue

{X=1,Y=1,Width=20,Height=14}

Value from PaintEventArgs 56

Value from Control 56

 

The entire program has been displayed, despite no changes being incorporated in the classes aaa and bbb. The UIType property and the enum yyy have been completely eliminated. Bear in mind that if things do not work as specified, remove the control aaa from the toolbox, restart the framework and add the Control again.

 

The program introduces a new function named GetPaintValueSupported, which takes a ITypeDescriptorContext parameter. This facilitates access to the object. This function gets called so often that the framework is able to discern whether the function called PaintValue has been implemented or not. A value of 'true' indicates that a function by that name is present in the class fff. The default value that is returned is 'false'.

 

Life is all about giving and taking. And sharing is a virtue.

The PaintValue function writes into the properties window. The content or value of the function is written here. Thus, it is possible to add what we desire into a small window that is offered to us, just prior to the properties window displaying the value of the property.

 

The documentation clearly states that there are two functions, both named GetPaintValueSupported, but only one of them is capable of being used. This is because, the one without any parameter is not marked as 'virtual', and hence, it cannot be overriden.

 

The PaintValue function gets called with a parameter PaintValueEventArgs, which contains everything that is required to write into the properties window. To write to any window, a graphics object is needed. The Graphics property of the parameter e returns an object of type Graphics.

 

The Bound property is used to return a rectangle, which returns the dimensions of the window into which the contents are to be written.  The Value property contains the value that would be displayed in the properties window by Visual Studio.Net. The file a.txt verifies that the window that has been provided is very small.

 

Finally, the Context property is used to access the ITypeDescriptorContext object. The instance property is obtained from this object to access the control. Further, the value of the a1 property is written. It is but obvious that the value property and the value of property a1 shall be the same.

 

The next task is to write to the window. So, a Red brush is created alongwith a Font object, which is assigned the name of the Font and the Font Size. The dependable DrawString function is used to write to the window. The screen 2.9 reveals the tiny window that contains the text that is to be displayed, however, we have drawn a small picture instead.

 

Screen 2.9

 

a.cs

public class fff : System.Drawing.Design.UITypeEditor

{

public override object EditValue(ITypeDescriptorContext c, IServiceProvider o, object v)

{

PropertyDescriptor p = c.PropertyDescriptor;

Type t = p.PropertyType;

abc(t.ToString());

abc(p.DisplayName);

abc(p.Category);

abc(p.ComponentType.ToString());

IContainer co = c.Container;

abc(co.ToString());

ComponentCollection coc = co.Components ;

abc(coc.Count.ToString());

IComponent c1 = coc[0];

abc(c1.ToString());

c1 = coc[1];

abc(c1.ToString());

aaa a = (aaa)coc[1];

abc(a.a1.ToString());

return 100;

}

public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext c)

{

return UITypeEditorEditStyle.DropDown;

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

 

a.txt

System.Int32

a1

Misc

aaa

Microsoft.VisualStudio.Designer.Host.DesignerHost

2

Form1 , Text: Form1

aaa1 [aaa]

56

 

Screen 2.10

 

The abc function with the ee object has been deleted from the program code. Moreover, in the EditValue function, a value of 100 is returned in lieu of a listbox . Here, the aaa and the bbb classes are not shown, since they have not been modified.

 

The ITypeDescriptorContext parameter contains two properties, which have not been touched upon so far. So, we shall check them out prior to moving ahead. We start off with the property PropertyDescriptor. This property and its type have the same name, i.e. PropertyDescriptor.

 

The property PropertyType has the type of Type. When the string representation of the type is displayed, it becomes evident that the property is of type Int32 or int. Thus, the PropertyDescriptor property is the one that encapsulates all information related to our property a1.

 

The DisplayName property assigns the name a1, which is used by the property window. The Category property specifies the category that the property is placed in, i.e. Misc. The ComponentType property is obviously of type aaa, which is the type of our control. The last property of the ITypeDescriptorContext interface is Container.

 

The Container property is of type Container. On displaying its string value, we obtain the name of the class, viz. DesignerHost. This class manages all the controls that the user places in it.  Also, this class represents the form which has two controls, viz. a Form1 and our control aaa.

 

The IContainer interface has a property named Components of type ComponentCollection that iterates through all the components that are present. The Count member gives a count of two, since there happen to be two components. The indexer can be used to access each and every component. The first component is the Form1. We have displayed its string representation.

 

The second component is the control aaa.  We cast the IComponent object c1 to an aaa object and display the value of the property a1. This value gets displayed in the property window. Thus, using the Container property, one can delve into the innards of Visual Studio.Net Designer. More details on this shall be tendered later.

 

a.cs

using System;

using System.IO;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Diagnostics;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Drawing.Design;

using System.Windows.Forms;

using System.Windows.Forms.ComponentModel;

using System.Windows.Forms.Design;

public class fff : System.Drawing.Design.UITypeEditor

{

IWindowsFormsEditorService e = null;

public override object EditValue(ITypeDescriptorContext c, IServiceProvider p, object v)

{

aaa a = (aaa)c.Instance;

e  = (IWindowsFormsEditorService)p.GetService(typeof(IWindowsFormsEditorService));

bbb t = new bbb();

t.fff += new EventHandler(abc);

t.b1 = (int)v;

e.DropDownControl(t);

a.a1 = t.b1;

return t.b1;

}

public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext c)

{

return UITypeEditorEditStyle.DropDown;

}

void abc(object s, EventArgs e1)

{

e.CloseDropDown();

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

public class aaa : System.Windows.Forms.UserControl

{

int v ;

[Editor(typeof(fff), typeof(UITypeEditor))]

public int a1

{

get

{

return v;

}

set

{

v = value;

}

}

protected override void OnPaint(PaintEventArgs e)

{

Graphics g = e.Graphics;

Brush b = new SolidBrush(ForeColor);

g.DrawString("vijay" + a1.ToString(), Font , b , ClientRectangle);

}

}

class bbb : System.Windows.Forms.UserControl

{

int w = 0;

EventHandler eee;

public bbb()

{

Size = new System.Drawing.Size(100, 123);

}

public int b1

{

get

{

return w;

}

set

{

w = value;

}

}

public event EventHandler fff

{

add

{

eee += value;

}

remove

{

}

}

protected override void OnMouseDown(MouseEventArgs e)

{

w = e.X;

}

protected override void OnMouseUp(MouseEventArgs e)

{

eee.Invoke(this, null);

}

protected override void OnPaint(PaintEventArgs e)

{

Graphics g = e.Graphics;

Brush b = new SolidBrush(ForeColor);

g.DrawString(b1.ToString(), Font , b , ClientRectangle);

}

}

 

Screen 2.11

 

To facilitate your comprehension of this program with effortless ease, we urge you to examine the output shown in screen 2.11.

 

To begin with, the text displayed in the control window is vijay0. When we click on the drop-down listbox, the value of the property a1, i.e. 0, is displayed. Thus, this example demonstrates how the text can be displayed in both, the control and the drop-down listbox.

 

We are aware of your familiarity with the fact that the class aaa represents the control.  To enable the control to display something, the OnPaint function is employed, wherein the text is displayed using the DrawString function. Not only do we display a standard boilerplate text like 'vijay', but also the property a1.

 

This property a1 is initialized by the EditValue function in class fff. This property had also remained uninitialized in the earlier program. It is the OnMouseDown function that sets the value of the property b1 in the class bbb.

 

The OnPaint function influences the appearance of the window. This OnPaint function is added to the class bbb. Thus, the value of the property b1 is displayed when the drop-down listbox is clicked upon. There is no new feature that necessitates an explanation here. You are merely required to override the OnPaint function to render a fresh look to the window.

 

The Size property is used to determine the size of the drop-down window. The height of the Font, which is used to draw the object, can be used to place options on multiple lines. In the OnMouseUp function, the Y property is employed to figure out the option that has been chosen. We save it as an exercise for our readers, to mentally grapple with on a rainy day.

 

a.cs

using System;

using System.IO;

using System.ComponentModel;

using System.ComponentModel.Design;

using System.Diagnostics;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Drawing.Design;

using System.Windows.Forms;

using System.Windows.Forms.ComponentModel;

using System.Windows.Forms.Design;

public class fff : System.Drawing.Design.UITypeEditor

{

IWindowsFormsEditorService e = null;

public override object EditValue(ITypeDescriptorContext c, IServiceProvider p, object v)

{

aaa a = (aaa)c.Instance;

e  = (IWindowsFormsEditorService)p.GetService(typeof(IWindowsFormsEditorService));

aaa t = new aaa();

t.cs = a.cs;

t.ce = a.ce;

t.final = a.final;

t.textl = a.textl;

t.Fontl = a.Fontl;

t.fff += new EventHandler(abc);

t.a1 = (int)v;

e.DropDownControl(t);

return t.a1;

}

public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext c)

{

return UITypeEditorEditStyle.DropDown;

}

void abc(object s, EventArgs e1)

{

e.CloseDropDown();

}

}

public class aaa : System.Windows.Forms.UserControl

{

EventHandler eee;

int dv = 0;

Font f1 = new Font("Times Roman", 10);

public Color cs = Color.Red;

public Color ce = Color.LimeGreen;

public Color cf = Color.Blue;

Brush bb = null;

Brush bd = null;

string z = "vijay";

public aaa()

{

Size = new System.Drawing.Size(100, 23);

}

int v ;

[Editor(typeof(fff), typeof(UITypeEditor))]

public int a1

{

get

{

return v;

}

set

{

v = value;

}

}

public Color start

{

get

{

return cs;

}

set

{

cs = value;

}

}

public Color end

{

get

{

return ce;

}

set

{

ce = value;

}

}

public Color final

{

get

{

return cf;

}

set

{

cf = value;

}

}

public string textl

{

get

{

return z;

}

set

{

z = value;

}

}

public Font Fontl

{

get

{

return f1;

}

set

{

f1 = value;

}

}

public event EventHandler fff

{

add

{

eee += value;

}

remove

{

}

}

protected override void OnMouseDown(MouseEventArgs e)

{

base.OnMouseDown(e);

abc("OnMouseDown e.X=" + e.X.ToString());

float p = (float)e.X / (float)ClientRectangle.Width * 100;

abc("OnMouseDown p=" + p.ToString());

dv = (int)p;

Invalidate();

}

protected override void OnMouseUp(MouseEventArgs e)

{

base.OnMouseUp(e);

a1 = dv;

eee.Invoke(this, null);

}

protected override void OnPaint(PaintEventArgs e)

{

base.OnPaint(e);

abc(ClientSize.ToString());

bb=new LinearGradientBrush(new Point(0, 0), new Point(ClientSize.Width, 0),cs,ce);

abc(ClientRectangle.ToString());

e.Graphics.FillRectangle(bb, ClientRectangle);

Rectangle r = ClientRectangle;

float p = ((float)a1 / (float)100);

abc("a1= " + a1.ToString() + " p= " + p.ToString());

int a = (int)(p * (float)r.Width);

abc("a= " + a.ToString());

r.X += a;

r.Width -= a;

abc("r.X= " + r.X.ToString() + " r.Width= " + r.Width.ToString());

bd = new SolidBrush(cf);

abc(r.ToString());

e.Graphics.FillRectangle(bd, r);

e.Graphics.Flush();

RectangleF r1 = new Rectangle();

SizeF ts = e.Graphics.MeasureString(textl, Fontl);

abc("ts= " + ts.ToString());

r1.Width = ts.Width;

r1.Height = ts.Height;

r1.X = (ClientRectangle.Width - r1.Width) / 2;

r1.Y = (ClientRectangle.Height - r1.Height) / 2;

abc("r1= " + r1.ToString());

e.Graphics.DrawString(textl, Fontl, new SolidBrush(Color.White), r1);

}

public void abc(string s)

{

FileStream fs = new FileStream("c:\\a1\\a.txt", FileMode.Append, FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine(s);

w.Flush();

w.Close();

}

}

 

a.txt

{Width=100, Height=23}

{X=0,Y=0,Width=100,Height=23}

a1= 0 p= 0

a= 0

r.X= 0 r.Width= 100

{X=0,Y=0,Width=100,Height=23}

ts= {Width=32.08116, Height=16.75781}

r1= {X=33.95942,Y=3.121095,Width=32.08116,Height=16.75781}

{Width=100, Height=23}

{X=0,Y=0,Width=100,Height=23}

a1= 0 p= 0

a= 0

r.X= 0 r.Width= 100

{X=0,Y=0,Width=100,Height=23}

ts= {Width=32.08116, Height=16.75781}

r1= {X=33.95942,Y=3.121095,Width=32.08116,Height=16.75781}

{Width=100, Height=23}

{X=0,Y=0,Width=100,Height=23}

a1= 0 p= 0

a= 0

r.X= 0 r.Width= 100

{X=0,Y=0,Width=100,Height=23}

ts= {Width=32.08116, Height=16.75781}

r1= {X=33.95942,Y=3.121095,Width=32.08116,Height=16.75781}

{Width=100, Height=23}

{X=0,Y=0,Width=100,Height=23}

a1= 0 p= 0

a= 0

r.X= 0 r.Width= 100

{X=0,Y=0,Width=100,Height=23}

ts= {Width=32.08116, Height=16.75781}

r1= {X=33.95942,Y=3.121095,Width=32.08116,Height=16.75781}

{Width=100, Height=23}

{X=0,Y=0,Width=100,Height=23}

a1= 0 p= 0

a= 0

r.X= 0 r.Width= 100

{X=0,Y=0,Width=100,Height=23}

ts= {Width=32.08116, Height=16.75781}

r1= {X=33.95942,Y=3.121095,Width=32.08116,Height=16.75781}

{Width=100, Height=23}

{X=0,Y=0,Width=100,Height=23}

a1= 0 p= 0

a= 0

r.X= 0 r.Width= 100

{X=0,Y=0,Width=100,Height=23}

ts= {Width=32.08116, Height=16.75781}

r1= {X=33.95942,Y=3.121095,Width=32.08116,Height=16.75781}

OnMouseDown e.X=89

OnMouseDown p=89

{Width=100, Height=23}

{X=0,Y=0,Width=100,Height=23}

a1= 89 p= 0.89

a= 88

r.X= 88 r.Width= 12

{X=88,Y=0,Width=12,Height=23}

ts= {Width=32.08116, Height=16.75781}

r1= {X=33.95942,Y=3.121095,Width=32.08116,Height=16.75781}

{Width=100, Height=23}

{X=0,Y=0,Width=100,Height=23}

a1= 89 p= 0.89

a= 88

r.X= 88 r.Width= 12

{X=88,Y=0,Width=12,Height=23}

ts= {Width=39.64626, Height=16.75781}

r1= {X=30.17687,Y=3.121095,Width=39.64626,Height=16.75781}

{Width=100, Height=23}

{X=0,Y=0,Width=100,Height=23}

a1= 89 p= 0.89

a= 88

r.X= 88 r.Width= 12

{X=88,Y=0,Width=12,Height=23}

ts= {Width=39.64626, Height=16.75781}

r1= {X=30.17687,Y=3.121095,Width=39.64626,Height=16.75781}

 

The optimum approach of appreciating the above example is by scanning its output. Here, the control displays a host of colors.

 

Screen 2.12

 

Next, click on the property a1. The screen 2.12 emerges with the drop-down listbox. Let us spend some time discerning and appreciating the riot of colors that meets the eye.

 

The program has five properties. These properties are as follows:-

     Three color properties- end, final and start.

     A string property- textl.

     A font property - Font1

 

Variables are also present, which store the values of these properties. Therefore, the properties and the variables can be used interchangeably, although we are aware that this is not the correct approach.

 

Let us begin with the first function that gets called, i.e. the OnPaint function. This function is responsible for the display of the myriad colors in the control, as also in the UI type editor. It is for this very reason that the two classes aaa and bbb have been coalesced together.

 

In a derived class, the programmers initially call functions from the original base class. So, in consonance with this ritual, the OnPaint function is called from the base class UserControl using the reserved keyword 'base'. It is not mandatory to call the base class function. However, if you have called this function, it is innocuous and shall mean no harm to you. Moreover, things just do not work at times, unless the original function is called.

 

The Brush object LinearGradientBrush is created by assigning four parameters to the constructor, i.e. two point objects and two colors. The points specify a starting and an ending point of the rectangle. In our case, it begins at 0,0 and ends at the length of our window.

 

In the a.txt file, the end X axis value is 100 since the Size of the window is 100 pixels, as has been stipulated by the Size property in the constructor. The height is 23 pixels as is evident. The starting and the ending colors have been specified by the variables se and ce, respectively. We could have used the properties 'start' and 'end' instead, but we are in a mood to violate quite a few rules today!

 

The FillRectangle function from the Graphics class is used to fill up a rectangle named ClientRectangle. The size of this rectangle indicates to us that it starts at 0,0 and has a width of 100 and height of 23, as the ClientSize point structure. This rectangle is initialized by the Size property that has been set in the constructor.

 

Pause here and run the program. If you have followed our instructions to the T, you should see two colors. The starting color is Red, since the initial value of the variable cs is Red. The color ends with Green, which is the value of the end color ce. Thus, the LinearGradientBrush draws a gradient between two points with the two colors that have been specified. This is how a spectacular visual effect can be achieved by using the right brush.

 

Now, we shall add some more color to the background.

The FillRectangle is used to fill up the entire control. One more Rectangle r is created and the ClientRectangle property is set. 

 

At this juncture, the property a1 has a value of 78. So, the freshly created variable p is assigned a value of .78, which is obtained  simply by dividing 78 by 100. The Width of the rectangle is multiplied by 100, thus resulting in a value 78. Thus, the variable p is used as a percentage to decrease the value of the Width of the window.

 

We then add the X property of the rectangle with this value of 'a'. The value of X is '0', thus resulting in a value of 78. The Width, which is the distance from the start to the end, is now reduced from 100 to 22, since 78 has been subtracted from it.

 

Then, a SolidBrush is created by employing the color cf, which is the property called final. Once again, the FillRectangle function is used to fill this rectangle r with a solid brush.

 

A point of substance here is that the property a1 is used as a percentage, and by increasing the value of the X axis by 78 and reducing the width by the same amount, the size of the rectangle r has been reduced by 78%. Thus, 22% of the control will now be soaked in blue color, or whichever color we have chosen for the property 'final'.

 

Also, the property a1 now reflects the area, in percentage, of the control, and the UI editor that the final color should not cover. If you were to increase its value, the first two colors of the gradient would occupy more space. However, if you were to reduce its value,  the property final color shall swamp more real estate.

 

It is the onus of the Flush function to finally display a thing. We could ask the framework to ensure that everything gets displayed. But, since this is such a time consuming process, everything that needs to be displayed gets added to a buffer, until the buffer is flushed. Unless the Flush function has been executed, there is no guarantee that the items would be displayed.

 

The remainder of the code ascertains that the property textl has been displayed in the center of the window. The DrawString function is used for this purpose. The only prerequisite here is that the text must be centered, irrespective of the font and the size.

 

To attain this, a RectangleF object is created. This is a rectangle whose data types are floats and not ints. Also, a SizeF object is created, which is a Size object with the data type of floats. The next task is to ascertain the amount of space (both vertically and horizontally), which shall be inhabited by the text in the string property textl. The MeasureString function in the Graphics class is the perfect panacea for our maladies.

 

This function is furnished with the string and the current Font, which has been returned as a SizeF object. The Font object is determined by the property Fontl, which uses the variable f1 to store the value of the property. In the Font that we are using, the string 'vijay' takes up 26 pixels horizontally and 13 pixels vertically. 

 

Thereafter, the Width and Height of the r1 Rectangle are set to the Width and Height of the string. The X and Y coordinates of the point from which the text is to be displayed, is the Width of the window, minus the width of the text divided by two. This computation ensures that the string gets aligned to the centre.

 

With the help of the DrawString function, this rectangle is used to determine where the text should be displayed. Now, click on the drop-down listbox to behold a palette full of colors.

 

In order to change the proportion of the color displayed, just click on any part of the window. The MouseEventArgs parameter gives the X and Y coordinates of the mouse click. We need to convert e.X into a percentage of the width of the window, to obtain the value in the variable p. In our case, since the Width of the rectangle is 100, both p and e.X share the same values.  The value of p is stored in a global variable dv. The Invalidate function is called, so that the Paint message can redraw the window whenever the drop-down listbox is clicked next. This spurs a fresh diffusion of colors.

 

When the mouse button is released, the function OnMouseUp gets called. In this function, the value of the property a1 is initialized to dv. The use of variable dv is purely optional. One could have used the property a1 in the function OnMouseDown instead. It is the Invoke function that finally closes the drop-down listbox.

 

As a final point, the new aaa object must be initialized in the Edit Value function. It would display the drop-down listbox embodying all the values of the control. This is imperative since the control that is displayed and the control that is used in the drop-down listbox, may belong to the same class aaa, but they are different instances.

 

Thus, all the properties from the object 'a' must be transferred to the newly created object 't', or else the properties that are newly initialized in the control, shall not be used by the drop-down listbox. You may experiment by modifying the Font and the colors of the properties.

 

You have covered considerable ground so far. Have a break, since we feel that you truly deserve it!