Thursday, January 17, 2008

Event-driven programming with WinForm

Event-driven programming with WinForm

Windows applications that we create fall under two major categories—managed and unmanaged. Unmanaged Windows applications can be created using Microsoft Foundation Classes (MFC). Managed applications run under Common Language Runtime (CLR) and are created using .NET Framework Class Library. These applications are called WinForm applications. WinForms (Windows Forms) create graphical user interface (GUI) for Windows applications. A form is a top-level window of an application. It can be a window, a dialog or a multiple document interface (MDI) window.

Creating WinForms has many advantages over creating applications using MFC. In unmanaged Windows-based programs, we need to apply certain styles to the window only when it is created. WinForms eliminate this quirk. If we apply styles that are meant to be applied at creation time, .NET destroys the previous window and creates a new one with new styles.

The .NET Framework Class Library is richer than MFC. Also, the classes remain the same for all the .NET compliant languages. All the classes that are used to create and design a WinForm are encapsulated in System.Windows.Forms namespace. For example, the System.Windows.Forms.Form class is used to create the form, System.Windows.Forms.Button class is used to create a button and so on. To create a GUI, we place components and controls on the form and so, a form acts as a container of components and controls. To create the GUI we drag and drop the controls from Toolbox and change their properties to suit our requirements. When we drag the control on form and change properties, Visual Studio.NET generates suitable code for us. However, only adding controls to create a user interface is not enough. Our application must also respond when the user interacts with the application. A user can interact with the application by moving the mouse, clicking the mouse button, pressing a key, etc. Whenever a user interacts with the GUI, events are generated. Events are notifications sent to the container, which can then respond to it. For example, when we click a button, an event is generated notifying that the user has clicked the button. In response, we can display a message box or do something else. This job is done in methods called ‘event handlers’. When a particular event is generated, the corresponding event handler gets called to process the event. For calling the event handlers, .NET takes help of events and delegates.

An event in C# is a multicast delegate having a predefined prototype. The prototype is such that every event returns a void and always accepts two parameters. The first parameter is always a reference to an object of System.Object class and the second parameter is always a reference to an object of the System.EventArgs class or a class derived from it. The EventArgs object contains information about the event. The object that raises an event is called an ‘event raiser’ and the object that receives an event is called an ‘event receiver’. The event raiser class always declares the event and the receiver class must have an event handler to handle that event.

The first parameter collected by the event handler is always a reference to an event raiser object. The addresses of event handlers are stored inside the events (remember that events are actually delegates) and hence they also must have the same signature as the event. This kind of delegate is declared using the keyword event. To understand events, consider the following program.

using System;
namespace Sample
{
            public class mouseeventargs
   {
                        public int x,y;
                        public mouseeventargs (int x1, int y1)
                        {
                                     x = x1;
                                     y = y1;
                        }
   }
 
 
 
 
 
 
   public delegate void click (object m, mouseeventargs e);
 
   public class MyForm
   {
                        public event click c1;
                        public void mouseclick()
                        {
                                     mouseeventargs m = new mouseeventargs (10,20);
                                     c1 (this, m);
                        }
   }
 
            class MyForm1:MyForm
   {
                        public MyForm1()
                        {
                                     c1 += new click (button_click);
                                     mouseclick();
                        }
                        public void button_click (object m, mouseeventargs e)
                        {
                                     Console.WriteLine (“Mouse Coordinates: “ + e.x + “ “ + e.y) ;
                                     Console.WriteLine (“Type is: “ + m.GetType().ToString()) ;
                        }
                        static void Main ( string[ ] args )
                        {
                                     MyForm1 f = new MyForm1( ) ;
                        }
   }
}

In this program we have declared three classes—mouseeventargs, MyForm and MyForm1 derived from MyForm. The mouseeventargs class corresponds to the EventArgs class. This class contains two int variables x and y and a constructor to initialise them. We have declared a multicast delegate click whose signature is the same as the event handler button_click( ). The MyForm class here is an event raiser class and hence contains an event as a data member called c1 of the type click. It also contains a method called mouseclick( ). In this method we have created an object of the mouseeventargs class. In the statement,

c1 (this, m);

c1 encapsulates the button_click( ) method. this contains the reference of MyForm object (event raiser) and m is a reference of the mouseeventargs object. In the MyForm1 class, we have defined the method button_click( ) (i.e. event handler). When the compiler encounters the statement c1 += new click (button_click), it creates an event object (delegate object) that wraps up button_click( ) method. So whenever the c1 event is raised (or called, as in this program), the method button _click( ) would get executed.

We could call a method even by using a delegate. Then what are events for? Take a look at the following statement.

c1 = new click(button_click);

where, c1 is a delegate reference. This statement has a bug. Here, we are assigning a new delegate object to c1, and so, all the delegates previously added are now lost. Instead of = operator we should have used += operator to add a new delegate object to the delegate list. This would never happen in case of events. This is because we can perform only two operations on events, namely, += and -=. We cannot use = operator with events. As such, events provide a protection layer on delegates. When we use the += and -= operators with delegates, they get converted into a call to Delegate.Combine( ) and Delegate.Remove( ) methods. If we use += and -= operators with events they get converted into a call to add_c1( ) and remove_c1( ) methods. These methods in turn call the Delegate.Combine( ) and Delegate.Remove( ) methods.

WinForms also follow the same mechanism. Only thing is, we don’t need to invoke the event handlers ourselves. They get called automatically when events occur. The compiler adds suitable code such that when an event occurs our event handler would get called. Let us now see what code gets generated when we create a WinForm application and add an event handler.

The minimal code that is required to create a WinForm application is given below:

using System;
using System.Windows.Forms;
namespace WinFormDemo
{
            public class Form1 : Form
            {
                        static void Main() 
                        {
                                     Application.Run ( new Form1( ) ) ;
                        }
            }
}

Here, we have derived a class Form1 from the Form class and passed a reference to its object to the Application.Run( ) method. This method creates the form, displays it on screen and starts a message loop for it. Of course, we don’t need to write this code, Wizard generates it for us. If we add a control to the form, a reference to the class representing the control gets added to the class as a private member. If we add a button the reference to the Button class would get added as shown below.

private System.Windows.Forms.Button button1;

The reference button1 would get initialised in the InitializeComponent( ) method as given below.

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

On adding a handler for the Click event, the code generated for it looks as given below.

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

The EventHandler is a delegate defined in the System namespace. This statement wraps the EventHandler around the button1_Click( ) method so that when the Click event is fired, button1_Click( ) method would get called. The definition of button1_Click( ) method is shown below.

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

The first parameter passed to button1_Click( ) method identifies the object that fired the event (button1 in this case). The second parameter contains additional information about the event.

The objects of every control class fire events. Now you can appreciate how easy it is to handle these events under .NET

0 Comments: