1.

 

Controls - Attributes, Properties and Events

 

Scores of programmers make a living by writing Controls, and then they allow others to toil on their creations. For instance, the controls exposed to the view in the Toolbox window are designed by the Microsoft programmers. These controls are then utilized to build complex applications.

 

The world of Visual Studio.Net caters to two primary sets of people; one that uses controls from the toolbox, and the other, that consists of persons who are significantly more brilliant and who pen down controls on their own.

 

Controls shield the user from the complexity of the underlying software infrastructure.

 

The first chapter in this book titled Visual Studio.Net - Controls and Add-ins expounds how a user-defined control can be displayed in the toolbox of Visual Studio.Net, together with its attributes, properties and events.

 

A word of caution: In order to guard against the eventuality of the dll not re-loading into memory, it would be prudent to restart Visual Studio.Net under the following circumstances:-

 

     with every new application.

     after incorporating any modifications.

     when you do not see an output similar to the one displayed in this book.

 

So, follow our dictum, i.e. if you fail to spot the same screen as shown in this book, restart Visual Studio.Net.

 

We set off by creating a new project in Visual Studio.Net. To achieve this, click on the File menu option, then on the New menu option followed by the Project menu option. Choose the second option of Visual C# Projects in the dialog box and from the second pane, select the option of Windows Application. Resultantly, an executable file is generated.

 

This project is titled as www and is placed in the folder v2. Henceforth, all files related to this project shall be created in the folder c:\v2\www. Once the OK button has been clicked upon, ensure that the properties window and the toolbox are in sight.

 

Screen 1.1

 

Alternatively, simply click on the View menu option, and then, select the Properties option for the Properties window. Similarly, select the option of View-Toolbox for the Toolbox.

 

Thereafter, click on any one of the controls in the toolbox, and then, click the right mouse button. The menu that springs forth, exhibits the possible actions that can be carried out with the control. The screen 1.1 displays these menu options.

 

From amongst the various choices, we have opted for the Customize Toolbox option. This leads to the display of a dialog box, as observed in screen 1.2.

 

Screen 1.2

 

 

This screen reveals the fact that there are two types of controls, viz. the COM Components that belonged to the world of OLE, and the .Net Components, which have been introduced by the .Net world. Select the tab of .Net Framework Components. After a significant pause, a new dialog box shows up on the screen.

 

Screen 1.3

 

The dialog box embodies a list of controls, wherein a few of them have  a tick mark placed in front of them. The controls with a tick mark in their check boxes are visible in the toolbox.

 

The very intent of this chapter is to initiate one of our controls into the toolbox. This implies that the control should be visible in the dialog box and should have a tick mark associated with it. To achieve this, we open a new DOS box. Then, in the root directory, we create a folder named a1. Thereafter, the following program named a.cs is entered in the folder c:\a1.

 

a.cs

using System.Windows.Forms;

public class aaa : UserControl

{

}

 

To compile the above program into a library or a dll, the following command is employed:

 

C:\a1>csc /t:library a.cs

 

Consequent to this, a file named a.dll is created.

 

Click on the browse button in the dialog box, which appears with the Customize Toolbox option.

 

Screen 1.4

 

 

Ensure that the folder a1 is open, as depicted in the screen 1.4. Then, double click on the file a.dll or select the file and click on the Open button. The Open dialog box indubitably affirms that only the exe or the dll file can be selected.

 

On selecting the file, the dialog box gets updated, wherein the first member of the list box displays the name aaa, which is the same as the name of the class in the dll. Moreover, the name of the assembly transforms into 'a', which is the name of the dll.

 

The control is selected by clicking on the check box, as depicted in screen 1.5. Then, the OK button is clicked.

 

Screen 1.5

 

Nothing earth shattering occurs on the screen. However, on scrolling down the toolbox, a control called aaa, which has been added to the toolbox, meets the eye. This is shown in screen 1.6.

 

Screen 1.6

 

Now, select it like any other control and drag and drop it into the form, as shown in screen 1.7.

 

Screen 1.7

 

The control gets inducted into the form. In the properties window, a zillion properties get displayed along with it. The name of the control in the properties window is aaa1, while the name of the class is aaa. If we press the F5 key on the keyboard to run the program, a blank screen gets displayed, as shown in Screen 1.8.

 

Screen 1.8

 

The program a.cs is responsible for the creation of the control, which purports itself like any other control in the toolbox. The program begins by creating a class aaa that is derived from UserControl. This class belongs to the namespace System.Windows.Forms.

 

This class boasts of a very distinguished pedigree. It is derived from ContainerControl, which in turn is derived from ScrollableControl , Control, Component, MarshalByRefObject, and from Object, like all other classes. The Control class is one of the largest classes in terms of properties, methods and events.

 

The UserControl class is the base class for creating user-controls. Thus, it can be safely assumed that all controls visible in the ToolBox have been derived from UserControl. If a control has not been derived from UserControl, it will not appear in the dialog box. Thus, the first rule whilst creating a user-defined control is that, it must derive from UserControl.

 

We shall now examine the code generated by Visual Studio.Net for instantiating our control. The View Code options display the following:

 

Screen 1.9

 

private aaa aaa1;

 

A public object aaa1 of control type aaa is created. The actual instance of the aaa1 object is created in the function InitializeComponent.

 

this.aaa1 = new aaa();

this.aaa1.Location = new System.Drawing.Point(80, 64);

this.aaa1.Name = "aaa1";

this.aaa1.TabIndex = 0;

 

Three properties of the control, viz. Location, Name and TabIndex are initialized to their respective values. A salient point to be noted here is that none of these three properties exists in the class aaa. They dwell in the derived class, i.e. UserControl, and hence, they obviously originate from there.

 

The Location property is a read/write property of type Point. It decides the position at which the control will appear on the container. It contains the coordinates of the upper left corner of the control with respect to the left corner of the window or container. The Location property determines the position where the control would be placed in the window.

 

The Name property assigns a name to the control. Thereafter, the control can be referenced by this name. The TabIndex property decides as to which control shall bask in all the attention each time that the tab key is pressed.

 

this.Controls.AddRange(new System.Windows.Forms.Control[] {this.aaa1});

 

Finally, the control is added to the Controls Collection using the AddRange function.

 

On unselecting the control by removing the tick mark from the check box, the control flees from the toolbox. This action is applicable to any control whose check box has initially been selected. Now, we progress on to displaying some text in the control.

 

a.cs

using System.Windows.Forms;

using System.Drawing;

public class aaa : UserControl

{

protected override void OnPaint(PaintEventArgs e)

{

Graphics g = e.Graphics;

Brush b = new SolidBrush(ForeColor);

g.DrawString("Vijay1", Font , b , ClientRectangle);

}

}

 

Compile this program like before, and then press F5 to run the program. The output that is generated is shown in screen 1.10, which displays Vijay1 as the content of the control.

 

Screen 1.10

 

Let us now try to fathom how this could have transpired.

 

The class UserControl, or the class that it is derived from, has a function called OnPaint, which gets invoked each time the control has to display or render something in its window. To override this function, we induct a function by the same name i.e. OnPaint. However, it contains the keyword 'override'. In the absence of this keyword, the newly introduced OnPaint function would be treated as a new OnPaint function, and thus, would never be called.

 

There is no specific or predetermined time for the function OnPaint to be called. We are merely aware of the fact that whenever Windows, in its exalted opinion, decides to invalidate the control's window, it calls the OnPaint function.

 

To achieve the objective of redrawing the window, the OnPaint function is furnished with a parameter e, of data type PaintEventArgs. This class has innumerable functions. Its most worthwhile property is Graphics, which is of type Graphics. We have made things uncomplicated and clear-cut by assigning the same name to the property and to its data type.

 

The DrawString function is called from the Graphics class and supplied with four parameters. The function writes the string at specific coordinates in the controls window.

     The first parameter passed to this function is the Text that is to be written, viz. Vijay1.

     The second parameter is the Font to be used to display the string.

     The third parameter is the Brush to be employed to write the text.

     The last parameter is the Position of the rectangle at which the text is to be written.

 

Font and ClientRectangle are properties of the UserControl class. The Font property represents a Font and the ClientRectangle property determines the size of the window. This is precisely the manner in which Vijay1 gets displayed in the control.

 

Now, let us render more complexity to this control, by making it carry another control.

 

a.cs

using System.Windows.Forms;

using System.Drawing;

using System;

public class aaa : UserControl

{

Button b;

public aaa()

{

b = new Button();

b.Location = new Point(24, 56);

b.Text = "vijay";

b.Click += new EventHandler(abc);

Controls.AddRange(new Control[] {b});

}

void abc(object sender, System.EventArgs e)

{

MessageBox.Show("Hi");

}

}

 

Run the above application by using the F5 key. A button with the text 'vijay' will emerge in the output window. This is seen in screen 1.11. Now, click on the button. You would encounter a message box with the greeting 'hi'. The screen 1.12 displays this box.

 

Screen 1.11

Screen 1.12

 

This substantiates the fact that the control now has a button with an event attached to it. This is why it displays the message box.

 

The above has been achieved by creating an object b of type Button. As before, the object b is instantiated to an instance of the button class. Then, the Location property is assigned the coordinates of the position at which the button is to appear in the controls window. The Location property is of type Point where the x and y co-ordinates are specified as pixels and passed to the constructor.

 

The Text property is the text that would be displayed as the caption of the button. Finally, the Click event is wired to the function abc using the delegate EventHandler. Thus, each time we click on the button, the function abc gets called and the message box appears. Therefore, the purpose of the above exercise is to demonstrate the fact that the user-defined control can contain other controls too. 

 

Now, let us proceed further and build a simple control that retrieves data from a database. This is achieved by using a DataGrid control.

 

a.cs

using System.Windows.Forms;

using System.Drawing;

using System;

using System.IO;

using System.ComponentModel;

using System.Data;

using System.Data.SqlClient;

public class aaa : UserControl

{

Button b;

TextBox t;

DataGrid d;

DataSet c;

public aaa()

{

t = new TextBox();

t.Location = new Point(1, 0);

t.Text = "Customers";

b = new Button();

b.Location = new Point(1, 25);

b.Text = "vijay1";

b.Click += new EventHandler(abc);

d = new DataGrid();

d.Location = new Point(1,50);

d.Size = new Size(150, 136);

Controls.AddRange(new Control[] { t,b,d });

}

void abc(object sender, System.EventArgs e)

{

SqlConnection con = new

SqlConnection("server=(local)\\NetSDK;database=northwind; Trusted_Connection=yes");

SqlDataAdapter Cust = new SqlDataAdapter ("Select * from " + t.Text, con);

c = new DataSet();

d.DataSource = c;

Cust.Fill(c, "zzz");

d.DataMember = "zzz";

}

}

 

The above example exhibits the productivity and utility of controls. On compiling the code into a dll, and then, on running the program, the screen that gets displayed is discernible in screen 1.13.

 

Screen 1.13

 

An empty DataGrid along with a button and a textbox are visible. The textbox contains the text 'Customers' written in it. Augment the size of the form to accommodate all the three controls.

 

A click on the button fills up the data grid with records from the  Customers table, which can be witnessed in the screen 1.14.

 

Screen 1.14

Screen 1.15

 

Now, in the textbox, change Customers to Orders, and then, click on the button again. This results in the display of records from the Orders table within the data grid. The screen 1.15 amply corroborates this fact.

 

Thus, by merely entering the table name in the textbox and by clicking on the button, all the data from that table can be retrieved into the data grid. The user can be kept insulated from the nitty-grittie involved in handling databases, data grids, etc. This presents a grand business opportunity to be seized!

 

Let us now understand how the above control was written.

The TextBox object t, created for the textbox, is instantiated in the constructor. Then, the Location property is set to position it on the form. Thereafter, the Size property is set to a Size object by passing the width and the height in pixels to the constructor. The Text property, which is used to display content in the textbox, is assigned the word 'Customers'.

 

Finally, the textbox is added in a manner akin to that employed to add the Button. We choose to use the AddRange function over Add, since the Add function comes handy only when a single control has to be added at one time. Contrastingly, the AddRange function proves worthwhile when multiple controls have to be added simultaneously.

 

In the case of the data grid, a DataGrid object is instantiated and its Location and Size are set accordingly. This control is also added to the Controls Collection.

 

Since three controls have been affixed to the controls collection, we witness a total of three controls in the form window, viz. a button, a   textbox with 'Customers' written in it, and an empty DataGrid.

 

A mouse click on the button invokes the function abc. In this function, an SqlConnection object named con is created. This object represents a connection to a database. During instantiation, the constructor is passed a string that determines the server and the database with which the program is to communicate on that server.

 

In this program, the database location specified with the Server variable is NetSDK, which is the one installed by the .NET framework. This installed database does not require a user name and password, since the connection is marked as 'Trusted'. The database variable is assigned the value of Northwind.

 

In SQLServer, a database stores tables. The Northwind database contains tables such as Customers and Orders, which get created and installed during the installation of the .NET framework.

 

The next object that is created is of type SqlDataAdapter. It is this object that understands or works with the SQL statement. A Select statement in SQL is used to retrieve data from the database. The SQL statement assigned as the parameter in the constructor, obtains the table name from the Text property of the TextBox t.

 

The Fill function in the SqlAdapter class takes two parameters, i.e. a DataSet and the name of the table that would eventually contain this data. Therefore, a DataSet Object c is created and the table name is specified as zzz.

 

A DataSet object is constituted of multiple DataTable objects. It is a container of data. The DataSource property in the DataGrid is initialized to the DataSet object c. Thus, the data displayed in the data grid will come from this DataSet. However, since a DataSet object embodies multiple tables, the DataMember property is then set to the table name zzz in the DataSet, so as to display the records.

 

It is not imperative for the user of this control to comprehend the above explanations, since the primary aim here is to enter the table name and to view the records present therein. What transpires behind the scenes is of no consequence to the user. This is how we insulate the underlying complexity of the controls from the user.

 

Properties

 

a.cs

using System.Windows.Forms;

using System.Drawing;

using System;

using System.IO;

using System.ComponentModel;

using System.Data;

using System.Data.SqlClient;

public class aaa : UserControl

{

int b1;

public int a1

{

get

{

abc("a1 get " + b1);

return b1;

}

set

{

b1 = value;

abc("a1 set " + b1);

}

}

public aaa()

{

abc("Constructor");

b1 = 100;

}

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

a1 get 100

a1 set 100

 

Constructor

a1 get 100

a1 set 100

a1 get 100

a1 get 100

a1 set 200

a1 get 200

 

Just as falling in love is the most splendid thing that can happen to anyone in this world, the introduction of the concept of properties is one of the most laudable milestones in the world of computers.

 

The above example introduces a whole set of namespaces. Furthermore, the property a1 has also been created. This property is of type int and uses the variable b1 to store its state.

 

In the get accessor, the value stored in the variable b1 is returned, while in the set accessor, the value contained  in the special variable value is assigned to the instance variable b1.

 

Prior to this, the variable b1 is assigned a value of 100 in the constructor.

 

Now, in order to ascertain when the property a1 gets called, a function abc that accepts a string as a parameter, is pressed into action. In this function, a FileStream object fs is created, where the constructor is assigned three parameters as follows:-

     The first is the name of the file that is to be worked with.

     The second parameter is an enum, which specifies if any additions are to be effected at the end of the file.

     The third parameter specifies the operation to be performed on the file, i.e. the Write operation.

 

The stumbling block at this instant is that the FileStream object cannot be used directly. All operations have to be performed using a StreamWriter object, which eventually writes to this file. The object is provided with the FileStream object in its constructor.

 

Finally, the WriteLine function is used to write the string passed to the file. The Flush function ensures that the string gets written to the disk.

 

At this stage, you may have to restart Visual Studio.Net to ensure that a fresh copy of the dll gets loaded into memory.

 

In the application, the control gets displayed in the form. Simultaneously, 3 lines are entered in the file a.txt. The constructor gets called once. Thereafter, the get accessor is called.

 

The get accessor is called whenever the system wants to display the value of a property, while the set accessor is summoned whenever the value of a property changes.

 

A property in Visual Studio.Net is akin to a variable. It contains a value that can be changed, but unlike a variable, actual code gets executed. On scrolling to the very end of the properties window, we encounter a heading or a category called Misc, which contains the property a1 with a value of 100, as seen in screen 1.16.

 

Screen 1.16

Screen 1.17

 

The file a.txt shows the get accessor being called twice.

 

The documentation renders a clarification as to why an accessor gets called. However, nowhere does it furnish a count of the number of occurrences of these accessors. Hence, it is baseless to use a MessageBox, since these accessors get called too often.

 

The screen 1.17 is captured during the process of changing the value of the property from 100 to 200.

 

In the file a.txt, we can yet again observe a large number of 'gets' being called. The concept is that a set accessor is called with the value 200, which is followed by copious number of get accessors. A public variable is not deemed to be a property by the system.

 

a.cs

using System.Windows.Forms;

using System.Drawing;

using System;

using System.IO;

using System.ComponentModel;

using System.Data;

using System.Data.SqlClient;

public class aaa : UserControl

{

string b1;

Button b;

DataGrid d;

DataSet c;

public string a1

{

get

{

return b1;

}

set

{

b1 = value;

}

}

public aaa()

{

b = new Button();

b.Location = new Point(1, 0);

b.Text = "vijay1";

b.Click += new EventHandler(abc);

b1 = "Customers";

d = new DataGrid();

d.Location = new Point(100,0);

d.Size = new Size(150, 136);

Controls.AddRange(new Control[] { b,d });

}

void abc(object sender, System.EventArgs e)

{

SqlConnection con = new SqlConnection("server=(local)\\NetSDK;database=northwind;Trusted_Connection=yes");

SqlDataAdapter Cust = new SqlDataAdapter ("Select * from " + a1, con);

c = new DataSet();

d.DataSource = c;

Cust.Fill(c, "zzz");

d.DataMember = "zzz";

}

}

 

The property a1, which gets added to the control, is now of type string, instead of int, as seen in the earlier case. The properties window displays the value of the property as Customers, as reflected in the screen 1.18.

 

Screen 1.18

 

The first point that is corroborated here is that there is no difference whatsoever between a property that has the data type of int and the one that has the data type of string.

 

A click on the button summons the function abc. The code entered in this function remains unchanged, except for the table name.

 

Earlier, the SQL Select statement had received the table name from the text property of the textbox. However, now it retrieves it from the property a1. But, from the user's point of view, the data grid exhibits data from the Customers table, as shown in the screen 1.19.

 

Screen 1.19

 

When the value of the property is changed to Orders, the records will be displayed from the Orders table. Thus, the user is offered two options; either to use a property or a textbox. The data handling code remains unaltered in either of the cases.

 

a.cs

using System.Windows.Forms;

public enum bbb

{

aa ,bb , cc

}

public class aaa : UserControl

{

bbb b1;

public bbb a1

{

get

{

return b1;

}

set

{

b1 = value;

}

}

public aaa()

{

b1 =  bbb.bb;

}

}

 

The same property a1 is now modified, whereby the data type is an enum bbb. An enum, for those who tuned in late, is merely a way of restricting the values that an object can have. The data type of the variable b1 is bbb, which is used to store the value of the property.

 

In the constructor, the variable b1 is initialized to the value bb. Hence, in screen 1.20, the value of the property is bb.

 

Screen 1.20

Screen 1.21

 

Since the value of the property b1 is a list of finite values, a list box is displayed instead of a textbox. The list box is clearly visible in the screen 1.21.

 

Three values are displayed, since the enum can theoretically contain the values aa, bb and cc. This also leads to the conclusion that when values for a property are to be selected from a list box, the data type for that property should be an enum.

 

We shall reiterate at regular intervals that Visual Studio.Net needs to be restarted because, during the design phase, the modified copy of the dll is not picked up from the disk.

 

a.cs

using System.Drawing;

using System.Windows.Forms;

public class aaa : UserControl  {

Color b1;

public Color a1

{

get

{

return b1;

}

set

{

b1 = value;

}

}

public aaa()

{

b1 = Color.Red;

}

}

 

Screen 1.22

 

In the above program, the property a1 is of type Color. The Color class has myriads of static properties that represent individual colors.

 

This makes the b1 variable also of type Color. In the constructor, b1 is initialized to the static member Red. Thus, when the control is placed in our form, the color Red is shown as the selected color.

 

If you click on the down arrow, you shall notice a custom color dialog box, as seen in the screen 1.23

 

Screen 1.23

 

Thus, depending upon the data type of the property, the user interface applicable to the specific data type is presented.

 

Therefore, Visual Studio.Net selects the pertinent user interface when the value of a property is changed. The user interface is contingent on the data type of the property.

 

a.cs

using System.Drawing;

using System.Windows.Forms;

public class aaa : UserControl

{

Image b1;

public Image a1

{

get

{

return b1;

}

set

{

b1 = value;

}

}

}

 

The property a1 is now assigned a data type of Image. The Image data type shows three dots, as seen in screen 1.24. By clicking on them, a File Open dialog box with a list of images emerges on the screen, as can be viewed in screen 1.25.

 

Screen 1.24

Screen 1.25

 

Any image file can be opted for and the Open button can be clicked upon. Doing so would display a thumb nail of the image, as is witnessed in screen 1.26.

 

Screen 1.26

Screen 1.27

 

Moreover, when the plus sign is clicked on (as is apparent in screen 1.27), a list of sub-properties, which constitute the Image property, gets displayed. Thus, Visual Studio.Net does in Rome what the Romans do!

 

a.cs

using System.Drawing;

using System.Windows.Forms;

public class aaa : UserControl

{

Cursor b1;

public Cursor a1

{

get

{

return b1;

}

set

{

b1 = value;

}

}

}

The data type of the property is now changed to a Cursor. This effectuates into the display of a list of cursors, as seen in screen 1.28. These are the predefined cursors that may be employed.

 

Screen 1.28

Screen 1.29

 

a.cs

bool b1;

public bool a1

 

The data type of bool is attached to the property. This leads to the display of only two values in the list box, viz. true and false, as shown in screen 1.29.

 

Before we progress any further and create our own complex properties, let us attend to certain other important concepts that necessitate immediate attention.

 

Attributes

 

An attribute is merely a class derived from Attribute. Attributes offer a more fine-grained control over the properties.

 

 

a.cs

using  System.ComponentModel;

using System.Windows.Forms;

public class aaa : UserControl

{

[Category("Vijay")]

public int a1

{

get

{

return 100;

}

set

{

}

}

}

 

The screen 1.30 depicts a new category, which has 'Vijay' affixed to the properties window.

 

Screen 1.30

 

Since there is no predefined category named 'Vijay', a new one gets added.

 

The Category attribute determines the category in which the property resides. However, since no category has been specified, the default of Misc is utilized.

 

[Category("Design")]

 

In the above case, the property is placed in a category called Design. The screen 1.31 establishes the presence of the property a1 within the Design category.

 

Screen 1.31

 

Thus, a property can be placed in a category of our choice. If the category does not exist, a new category gets created.

 

The category attribute is used to organize the properties into groups that share a common behavior. There are no restrictions whatsoever on the name that can be assigned to a category.

 

Contrastingly, if the second bitmap in the properties window is clicked on, the properties get listed alphabetically, as seen in screen 1.32. This option completely repudiates the Category attribute.

 

Screen 1.32

Screen 1.33

 

The next bitmap only displays the properties, as seen in screen 1.33. While studying the attribute concept, ensure that the first option is selected, wherein the properties are grouped into various categories.

 

a.cs

[Category("Design")]

[Description("Vijay Mukhi")]

public int a1

 

Screen 1.34

 

The attribute Description introduced in the code permits us to specify some help or description about a property. Thus, when the property is clicked upon in the property window, its description pops up in the window pane below. For the property a1, we notice the words 'Vijay Mukhi' mentioned below the name a1, as is evident in screen 1.34. You may have to augment the size of the window to see the description in its entirety.

 

a.cs

[Browsable(false)]

public int a1

 

The next attribute that we shall analyze is Browsable, which has been set to a value of false. The effect of assigning this value to the attribute is that, the property is no longer visible in the properties window. Check out screen 1.35 to validate this.

 

Screen 1.35

 

Thus, effectively the user is not entitled to browse this property.

 

a.cs

using  System.ComponentModel;

using System.Windows.Forms;

[DefaultProperty("a1")]

public class aaa : UserControl

{

[Category("Design")]

public int a1

{

get

{

return 100;

}

set

{

}

}

}

 

The next attribute is the DefaultProperty. This attribute must be placed on a class and not on a property. This attribute identifies a property that shall be the default property whenever a control is selected. Thus, when our control is selected (as is apparent in screen 1.36), the property a1 is depicted as selected.

 

Screen 1.36

 

a.cs

[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]

public int a1

 

Screen 1.37

 

The attribute of DesignerSerializationVisibility on the property is provided with an enum DesignerSerializationVisibility value of Visible. This attribute determines how the property a1 shall be represented in the code.

 

Now, when we change the value from 100 to 200 and press enter, the value reverts back to its original value of 100. This occurs because the new value assigned to the property has not been saved. The code generated in the background has the following line:

 

this.aaa1.a1 = 100;

 

Thus, the value of the property a1 has been serialized or saved as part of the code. However, on occasions galore, we may not want this serialization to be entered in the C# code that is generated. The option available is to use the enum Hidden in the following manner:

 

a.cs

[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]

public int a1

 

On changing the value to Hidden, there is no trace of the property a1 anywhere in the code that is generated. Thus, there is no way of determining the previous value of the property a1, since it has not been saved or serialized.

 

Before we move on to more intricate issues of attributes, we shall analyze the attributes that come into play when a project in Visual Studio.Net creates a control.

 

The mechanics are as follows: Click on the menu options File, New, Project, in this sequence.

 

Screen 1.38

 

From the first pane, choose the option of Visual C# projects, and from the second pane, select Windows Control Library. The project is assigned the name ccc and is positioned in the folder v3, as seen in screen 1.38. The screen that emerges is obvious in the screen 1.39. This screen has an uncanny likeness to a Windows application, with the exception of its form, which is of a significantly smaller size.

 

Screen 1.39

 

The next step is to drag-and-drop a button from the toolbox on to the form, which has been attained in screen 1.40.

 

Screen 1.40

 

Once these steps have been executed, click on the menu Build, and then, on the Build option to build the control. Save the entire project and close it.

 

Now, open up the earlier C# application and delete the control aaa from the toolbox. Thereafter, conforming to the laws of inserting a control, click the right mouse button on the toolbox, choose Customize Toolbox, and then select the .Net framework tab.

 

On browsing the folder c:\v3\ccc\bin\debug, the file ccc.dll shall appear. It is to be selected and opened. This launches the control UserControl1 into the dialog box. Ensure that the checkbox has been selected, so as to make it noticeable in the toolbox. Finally, click on OK.

 

The toolbox now accommodates this control, but it becomes apparent only when we scroll down to the very end, as shown in screen 1.41.

 

Screen 1.41

Screen 1.42

 

Now, drag-and-drop the control into our form, as seen in the screen 1.42.

 

Build and run the application. A window with a single button materializes on the terminal. Thus, we have been able to create a control with a button, without writing a single line of code.

 

Now, to establish what Visual Studio.Net has brought along to the party, let us revert back to the control project and have a look at the code generated.

 

a.cs

using System;

using System.Collections;

using System.ComponentModel;

using System.Drawing;

using System.Data;

using System.Windows.Forms;

 

namespace ccc

{

/// <summary>

/// Summary description for UserControl1.

/// </summary>

 

public class UserControl1 : System.Windows.Forms.UserControl

 {

    private System.Windows.Forms.Button button1;

 

/// <summary>

/// Required designer variable.

/// </summary>

 

private System.ComponentModel.Container components = null;

public UserControl1()

{

// This call is required by the Windows.Forms Form Designer.

      InitializeComponent();

 

// TODO: Add any initialization after the InitForm call

}

 

/// <summary>

/// Clean up any resources being used.

/// </summary>

       protected override void Dispose( bool disposing )

       {

             if( disposing )

                {

            if( components != null )

                        components.Dispose();

                        }

            base.Dispose( disposing );

            }

 

#region Component Designer generated code

/// <summary>

/// Required method for Designer support - do not modify

/// the contents of this method with the code editor.

/// </summary>

 

private void InitializeComponent()

{

                        this.button1 = new System.Windows.Forms.Button();

                        this.SuspendLayout();

//

// button1

//

 

this.button1.Location = new System.Drawing.Point(48, 112);

this.button1.Name = "button1";

this.button1.TabIndex = 0;

this.button1.Text = "button1";

//

// UserControl1

//

 

this.Controls.AddRange(new System.Windows.Forms.Control[] {                                                                          this.button1});

this.Name = "UserControl1";

this.Load += new System.EventHandler(this.UserControl1_Load);

this.ResumeLayout(false);

}

#endregion

 

private void UserControl1_Load(object sender, System.EventArgs e)

{

}

}

}

 

If you examine the code generated by Visual Studio.Net, you would notice that it starts with the usual gang of suspects, i.e. the Using statements. In all, there are six Using statements. The name of our project is ccc. Hence, all the code is placed in the namespace ccc. The dll is also named as ccc.dll.

 

Then, we encounter a class called UserControl1, which is derived from the class UserControl. This class name may be modified. However, the constructor name must also be correspondingly changed in such a case. The function InitializeComponent is called in the constructor.

 

In the InitializeComponent function, at first, a new instance of the Button class is created. It is stored in the instance variable button1. Then, the Location property is set to the position where the button is to be placed in the form. The Text property is set to a default value of button1, which is also the name of the button. The button is then added to the controls collection.

 

The region directive can either expand or contract code. Correspondingly, either the code for the entire function will be visible or merely the name of the function will be seen.

 

The SuspendLayout function can be eliminated. It simply casts a hint to the framework not to render the form. This is because the process of placing controls in the from has not yet been undertaken. The last function called Dispose gets called whenever the control is disposed or erased from memory. It is worthless to us at this stage.

 

The remarkable point here is that, other than the Location property of the button, Visual Studio.Net offers nothing fresh while generating code for the controls. The very aim of inserting this property is to avoid the time wastage involved in aligning controls for orderliness.

 

Thus, it would be a sagacious move to write the code for our controls in a word processor, then compile it and call it from the toolbox, since it enhances our understanding of the entire process. Thus, we would be adopting the manual approach henceforth.

 

Event Handling in Controls

 

To grasp the basics of an event with respect to a Windows Forms, we shall review a simple example. Most of you are wise and erudite, and therefore may be aware of this approach, but we still implore you to stay with us.

 

Create a new project by clicking on the menu options File, New, Project. As is customary, choose the second option of Visual C# Projects from the first pane and Windows Application from the second pane.

 

Name the project as w4 and the folder as v2. Then, drag and drop a button onto the form, as was done before. The properties window shall display a list of properties for the button. There is an icon of a lightning bolt in the properties window. Click on this icon and you shall spot a list of events, as in screen 1.43.

 

Screen 1.43

Screen 1.44

 

The event that appears first is the Click event. When it is selected in the list box, nothing meets the eye initially.

Now, double click on the Click event in the properties toolbox. This would transport you straight to the code generator, with the following lines of code added:

 

this.button1.Click += new System.EventHandler(this.button1_Click);

 

private void button1_Click(object sender, System.EventArgs e)

{

}

 

Before we explicate the code, we shall run a quick eye over the Click event in the properties toolbox. We can see button1_Click in the list box. Additionally, there is some extra code generated in the InitializeComponent function. The button has a Click event that has been connected to or added to the function button1_Click by the delegate EventHandler.

 

As an outcome, the Click event of the button would call this function. However, the snag here is that, it is only the button that has the knowledge of when the Click event should be fired. By using a delegate, we have registered a function with the Click event. Thus, the code in the function gets executed when the event is triggered by the button.

 

Apart from the introduction of extra statements in the code, insert the following lines. The function abc has been associated with the Click event. Now, the Click event calls two functions and both these functions merely display a Message Box.

 

this.button1.Click += new System.EventHandler(this.button1_Click);

 

button1.Click += new System.EventHandler(abc);

 

private void button1_Click(object sender, System.EventArgs e)

{

MessageBox.Show("1");

}

private void abc(object sender, System.EventArgs e)

{

MessageBox.Show("2");

}

 

When the program runs and the button is clicked, the Click event gets fired, thereby invoking all the functions associated with it. The functions are called in the same sequence in which they have been registered. Thus, the resultant output is that first a Message Box displaying the number 1 is spotted, and then a Message Box exhibiting the number 2 is displayed.

 

Screen 1.45

Screen 1.46

 

We wish to accentuate the fact that the button is totally oblivious to the exact number of functions that are being called. This is because the functions are placed in the code area and not within the buttons. Thus, the event concept spans two ideas.

 

The control triggers the event by using its own sets of rules and only those functions that are registered with the event get called. The listbox in the Click event shows the two functions that are attached to the Click event, as seen in screen 1.46.

 

Just to drive home the concept of events, scroll down the properties window until you come to an event called Mouse_Enter. Double click on this event. This will lead to the insertion of the following code in the code painter:-

 

button1.MouseEnter += new System.EventHandler(this.button1_MouseEnter);

 

private void button1_MouseEnter(object sender, System.EventArgs e)

{

MessageBox.Show("3");

}

 

The InitializeComponent function has the event MouseEnter associated with the function button1_MouseEnter, which would get called each time this event gets triggered off.

 

For this reason, as soon as the mouse cursor attempt to enter the button while the program is running, a message box blocks the way. This is because the event MouseEnter gets triggered each time the mouse enters the button.

 

Screen 1.47

 

We now proceed further to create our own event.

 

a.cs

using System.Windows.Forms;

using System;

public class aaa : UserControl

{

public event EventHandler ev

{

add

{

}

remove

{

}

}

}

 

As always, let us start anew. So, create a new project in the same manner as before and name it as w5. Place the control aaa in the form. Then, click on the lightning bolt icon for events in the properties window. On scrolling further down, you would notice that the event called ev gets added to the list of events under the Misc section, as is evident in screen 1.48. It is as simple as this!

 

Screen 1.48

 

An event in a control starts with the public modifier, followed by the reserved word event. This is followed by the type or the delegate that is to be associated with the event. The delegate type determines the parameters to the function that gets created in the code painter.

 

Since each property has a get and a set accessor, the events have corresponding add and remove accessors, both of which are mandatory. The event is assigned the name of ev.  Double clicking on the event ev displays the code painter with the following lines of code:-

 

this.aaa1.ev += new System.EventHandler(this.aaa1_ev);

 

private void aaa1_ev(object sender, System.EventArgs e)

{

}

 

All events follow the laid down rules. Since the name of the event is ev, the function that gets called is assigned the name of aaa1_ev, which is formed by a combination of the name of the control aaa1 and the name of the event ev.

 

Now, to trigger this event ev so that the function aaa1_ev gets executed, let us revert back to the control.

 

a.cs

using System.Windows.Forms;

using System;

public class aaa : UserControl

{

public event EventHandler ev

{

add

{

}

remove

{

}

}

protected override void OnMouseDown(MouseEventArgs e)

{

MessageBox.Show("Mouse Down");

}

}

 

 

The OnPaint function is triggered off by the Paint event and the OnMouseDown function is triggered by the MouseDown event. The outcome of the above code is that, when we click inside the window, a MessageBox shall emerge, as seen in the screen 1.49.

 

Screen 1.49

 

We employ this function to trigger off the event ev, which would eventually call all the functions that have been registered with it.

 

a.cs

using System.Windows.Forms;

using System;

public class aaa : UserControl  {

EventHandler e;

public event EventHandler ev

{

add

{

MessageBox.Show("Add");

e += value;

}

remove

{

MessageBox.Show("Remove");

e -= value;

}

}

protected override void OnMouseDown(MouseEventArgs ee)

{

MessageBox.Show("Mouse Down");

e.Invoke(this, null);

}

}

 

Now, a single line of code is added to the control for both the 'add' and 'remove' accessors. The property accessors receive a free variable called value, which actually becomes a parameter to the function that the property gets converted to. The event also gets the same value object. However, it represents the name of the function to be executed.

 

The delegate e of type EventHandler stores or adds this value member to the delegate e. In the remove accessor, the reverse approach is adopted, wherein, the value is subtracted from the existing one. A message box is displayed each time the accessor gets called.

 

In the OnMouseDown function, the Invoke function is called with two parameters. The first one represents the 'this', which is the current object or the entity calling or firing the event. The second parameter is an EventArgs entity. These two parameters are essential, since the delegate is of type EventHandler.

 

this.aaa1.ev += new System.EventHandler(this.aaa1_ev);

this.aaa1.ev += new System.EventHandler(this.aaa2_ev);

this.aaa1.ev += new System.EventHandler(this.aaa3_ev);

this.aaa1.ev -= new System.EventHandler(this.aaa1_ev);

 

private void aaa3_ev(object sender, System.EventArgs e)

{

MessageBox.Show("3");

}

private void aaa2_ev(object sender, System.EventArgs e)

{

MessageBox.Show("2");

}

private void aaa1_ev(object sender, System.EventArgs e)

{

MessageBox.Show("1");

}

 

Screen 1.50

 

When the above project is executed, it displays the message box with Add thrice. Then, it shows the MessageBox with Remove just once.

 

This occurs because the constructor gets called before the window is displayed.  This results in the Add accessor of the event ev being called thrice due to the += syntax, and the Remove accessor being called once. Clicking on the window displays the MessageBox with mouse down, followed by the MessageBox having the values of 2 and 3.

 

Thus, to add the three functions aaa1_ev, aaa2_ev and aaa3_ev, the EventHandler delegate is added to. Moreover, to remove the first function aaa1_ev, the -= syntax is used.

 

The MouseDown event that gets triggered in the control, triggers off the Invoke function from the delegate. Each time the Add accessor gets called, it adds the value parameter that represents each of the above functions. The -= activates the Remove accessor, which eliminates the function from the event list.

 

The Invoke function invokes all the functions associated with the event. You are not required to keep track of them, since the onus of this lies with the event handling code of the framework. You simply have to trigger off the event, and the rest shall be taken care of.

 

Screen 1.51

 

The event icon in the properties window exhibits all the functions that are visible, as depicted in screen 1.51.

 

a.cs

using System.Windows.Forms;

using System;

public class aaa : UserControl {

EventHandler e;

public event EventHandler ev

{

add

{

MessageBox.Show("Add");

e += value;

}

remove

{

MessageBox.Show("Remove");

e -= value;

}

}

public int a1

{

get

{

return 1;

}

set

{

e.Invoke(this, null);

}

}

}

 

In the above control, the function OnMouseDown is replaced by the property a1. The event ev is now called in the 'set' accessor of the property a1.

 

In the project, add a button to the form. Then, double click on the button and add the following code in the Click event of the Button.

 

private void button1_Click(object sender, System.EventArgs e)

{

            aaa1.a1 = 10;

}

 

private void aaa3_ev(object sender, System.EventArgs e)

{

            MessageBox.Show("3");

            MessageBox.Show(aaa1.a1.ToString());

}

 

private void aaa2_ev(object sender, System.EventArgs e)

{

                        MessageBox.Show("2");

}

 

 

private void aaa1_ev(object sender, System.EventArgs e)

{

                        MessageBox.Show("1");

}

 

Thereafter, run the project and click on the button. The 'set' accessor of the property a1 gets called, which in turn calls the event ev using the Invoke function. The Invoke function in turn, calls the three functions that were attached to the event, thus displaying three message boxes.

 

To summarize, the event has to be triggered by the control. This can be accomplished by using a property. It can also be achieved by triggering an event in one of the predefined events, which the control responds to, or by firing it through any other method.