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!